From c6a1e7c00cd2946cef7716b205026a907136778b Mon Sep 17 00:00:00 2001
From: guowenxue <guowenxue@gmail.com>
Date: Sun, 12 Jul 2026 18:41:28 +0800
Subject: [PATCH] Add ex1.servo-sg90

---
 ex1.servo-sg90/Board/miscdev.c |  406 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 406 insertions(+), 0 deletions(-)

diff --git a/ex1.servo-sg90/Board/miscdev.c b/ex1.servo-sg90/Board/miscdev.c
new file mode 100644
index 0000000..986afbc
--- /dev/null
+++ b/ex1.servo-sg90/Board/miscdev.c
@@ -0,0 +1,406 @@
+/**********************************************************************
+*   Copyright: (C)2024 LingYun IoT System Studio
+*      Author: GuoWenxue<guowenxue@gmail.com>
+* Description: ISKBoard Hardware Abstract Layer driver
+*
+*   ChangeLog:
+*        Version    Date       Author            Description
+*        V1.0.0  2024.08.29    GuoWenxue      Release initial version
+***********************************************************************/
+
+#include "miscdev.h"
+
+/*
+ *+----------------------+
+ *|    STM32 GPIO API    |
+ *+----------------------+
+ */
+
+void gpio_clk_enable(GPIO_TypeDef *port)
+{
+    if      (port == GPIOA) __HAL_RCC_GPIOA_CLK_ENABLE();
+    else if (port == GPIOB) __HAL_RCC_GPIOB_CLK_ENABLE();
+    else if (port == GPIOC) __HAL_RCC_GPIOC_CLK_ENABLE();
+    else if (port == GPIOD) __HAL_RCC_GPIOD_CLK_ENABLE();
+#ifdef GPIOE
+    else if (port == GPIOE) __HAL_RCC_GPIOE_CLK_ENABLE();
+#endif
+#ifdef GPIOF
+    else if (port == GPIOF) __HAL_RCC_GPIOF_CLK_ENABLE();
+#endif
+#ifdef GPIOG
+    else if (port == GPIOG) __HAL_RCC_GPIOG_CLK_ENABLE();
+#endif
+#ifdef GPIOH
+    else if (port == GPIOH) __HAL_RCC_GPIOH_CLK_ENABLE();
+#endif
+}
+
+
+/*
+ *+----------------------+
+ *|  GPIO Led/Relay API  |
+ *+----------------------+
+ */
+
+/* ISKBoard 有一个继电器 */
+gpio_t     relay = { "Relay", GPIOD, GPIO_PIN_2, 1 };
+
+/* ISKBoard 有三个Led灯 */
+gpio_t     leds[LedMax] =
+{
+    { "RedLed",   GPIOC, GPIO_PIN_9, 0 },
+    { "GreenLed", GPIOC, GPIO_PIN_6, 0 },
+    { "BlueLed",  GPIOB, GPIO_PIN_2, 0 },
+};
+
+/**
+ * @brief  继电器初始化:配置继电器控制引脚对应的GPIO
+ */
+void init_relay(void)
+{
+    /* set default output level to inactive */
+    HAL_GPIO_WritePin(relay.group, relay.pin, !relay.active);
+
+    return;
+}
+
+/**
+ * @brief  控制继电器的开关状态
+ * @param  status: ON 或 OFF
+ */
+void turn_relay(int status)
+{
+    GPIO_PinState       level;
+
+    level = status==OFF ? !relay.active : relay.active;
+    HAL_GPIO_WritePin(relay.group, relay.pin, level);
+}
+
+/**
+ * @brief  LED初始化:配置各LED对应的GPIO状态。
+ */
+void init_led(void)
+{
+    int                 which;
+
+    /* Initial all leds GPIO */
+    for(which=0; which<LedMax; which++)
+    {
+        /* set default output level to inactive */
+        HAL_GPIO_WritePin(leds[which].group, leds[which].pin, !leds[which].active);
+    }
+
+    return;
+}
+
+/**
+ * @brief  控制指定LED的开关状态
+ * @param  which:  LED编号(参考 lednum_t,如 Led_R/Led_G/Led_B)
+ * @param  status: ON 或 OFF
+ */
+void turn_led(int which, int status)
+{
+    GPIO_PinState       level;
+
+    if( which >= LedMax )
+        return ;
+
+    level = status==ON ? leds[which].active : !leds[which].active;
+    HAL_GPIO_WritePin(leds[which].group, leds[which].pin, level);
+}
+
+/**
+ * @brief  翻转指定LED当前状态(亮变灭,灭变亮)
+ * @param  which: LED编号(参考 lednum_t)
+ */
+void toggle_led(int which)
+{
+    if( which >= LedMax )
+        return ;
+
+    HAL_GPIO_TogglePin(leds[which].group, leds[which].pin);
+}
+
+/**
+ * @brief  阻塞式闪烁指定LED一次(亮interval毫秒,灭interval毫秒)
+ * @param  which:    LED编号(参考 lednum_t)
+ * @param  interval: 亮灭各自持续时间(ms)
+ */
+void blink_led(int which, uint32_t interval)
+{
+    turn_led(which, ON);
+    mdelay(interval);
+
+    turn_led(which, OFF);
+    mdelay(interval);
+}
+
+/**
+ * @brief  绿灯"滴-答"双闪心跳灯:模拟真实心跳节奏(两次快闪 + 一段停顿).
+ */
+void heartbeat_led(void)
+{
+    /* 亮100ms(滴) -> 灭100ms -> 亮100ms(答) -> 灭700ms(停顿) -> 循环 */
+    static const uint32_t hb_duration[4] = {100, 100, 100, 700};
+    static const uint8_t  hb_status[4]   = {ON,  OFF, ON,  OFF};
+
+    static uint8_t  state = 0;
+    static uint32_t last_tick = 0;
+    static uint8_t  first_run = 1;
+
+    uint32_t now = HAL_GetTick();
+
+    if (first_run)
+    {
+        turn_led(Led_G, hb_status[state]);
+        last_tick = now;
+        first_run = 0;
+    }
+
+    if (now - last_tick >= hb_duration[state])
+    {
+        state = (state + 1) % 4;
+        turn_led(Led_G, hb_status[state]);
+        last_tick = now;
+    }
+}
+
+/*
+ *+----------------------+
+ *|     GPIO Key API     |
+ *+----------------------+
+ */
+ /* 全局按键状态位图定义,初始为0(所有按键都未按下) */
+int  g_keys_status = 0;
+
+/**
+ * @brief  GPIO外部中断统一回调函数(HAL库弱函数重写),
+ *         按键触发的EXTI中断都会进入这里,根据引脚号区分是哪个按键按下
+ * @param  GPIO_Pin: 触发中断的引脚号(如 GPIO_PIN_12/13/14)
+ */
+void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
+{
+    if( GPIO_PIN_12 == GPIO_Pin ) /* Key1 */
+    {
+        g_keys_status |= KEY1_PRESSED;
+    }
+    else if( GPIO_PIN_13 == GPIO_Pin ) /* Key2 */
+    {
+        g_keys_status |= KEY2_PRESSED;
+    }
+    else if( GPIO_PIN_14 == GPIO_Pin ) /* Key3 */
+    {
+        g_keys_status |= KEY3_PRESSED;
+    }
+}
+
+/*
+ *+----------------------+
+ *|      printf API      |
+ *+----------------------+
+ */
+
+/* gcc 编译器的 printf 将会调用 __io_putchar() 函数,实现最终的字符打印
+ * keil编译器的 printf 将会调用 fputc() 函数,实现最终的字符打印
+ * 这里我们定义一个宏 PUTCHAR_PROTOTYPE 来兼容两个编译器所需要的函数原型
+ *
+ * 注意:
+ *  1. Keil5 工程需要勾选:Options for Target -> Target -> Use MicroLIB
+ *  2. Keil5 的编译器 armclang(AC6) 会定义 __GNUC__ 宏,而旧版 armcc(AC5) 则不会
+ */
+#if defined(__ARMCC_VERSION) /*  */
+#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
+#elif defined(__GNUC__) /* GCC */
+#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
+#endif
+
+PUTCHAR_PROTOTYPE
+{
+    /* 调用STM32 HAL库的串口发送函数,将printf要打印的这个字符通过串口发送出去 */
+    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
+    return ch;
+}
+
+/*
+ *+----------------------------+
+ *| ADC noisy & lux sensor API |
+ *+----------------------------+
+ */
+
+#include "adc.h"
+
+/**
+ * @brief  读取指定通道的ADC值
+ * @param  channel: 通道号,例如 ADC_CHANNEL_15 / ADC_CHANNEL_16
+ * @param  value:   读取结果输出指针
+ * @retval 0 成功, -1 失败
+ */
+int adc_read_channel(uint32_t channel, uint32_t *value)
+{
+    ADC_ChannelConfTypeDef sConfig = {0};
+    uint32_t timeout = 0xffffff;
+
+    /* 咪头(麦克风)输出的是交流信号,叠加在某个直流偏置上。 当声音信号处于波形过零点附近时,
+     * 2.5 个采样周期极短,ADC 输入阻抗在如此短的时间内无法对麦克风输出的高阻信号完成充电,
+     * 信号还没稳定就完成了采样,所以这里修改采样时间为 247.5 Cycles。 */
+    sConfig.Channel      = channel;
+    sConfig.Rank         = ADC_REGULAR_RANK_1;
+    sConfig.SamplingTime = ADC_SAMPLETIME_247CYCLES_5;
+    sConfig.SingleDiff   = ADC_SINGLE_ENDED;
+    sConfig.OffsetNumber = ADC_OFFSET_NONE;
+    sConfig.Offset       = 0;
+
+    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
+    {
+        return -1;
+    }
+
+    HAL_ADC_Start(&hadc1);
+
+    if (HAL_ADC_PollForConversion(&hadc1, timeout) != HAL_OK)
+    {
+        HAL_ADC_Stop(&hadc1);
+        return -1;
+    }
+
+    *value = HAL_ADC_GetValue(&hadc1);
+    HAL_ADC_Stop(&hadc1);
+
+    return 0;
+}
+
+/**
+ * @brief  在指定时间窗口内对麦克风多次采样,返回峰峰值作为响度
+ * @param  noisy:        响度输出(峰峰值,范围 0~4095)
+ * @param  window_ms:    采样时间窗口,建议 50~100ms(覆盖完整音节)
+ * @retval 0表示成功,-1 表示失败
+ */
+int adc_read_noisy_peak(uint32_t *noisy, uint32_t window_ms)
+{
+    uint32_t val;
+    uint32_t max_val = 0;
+    uint32_t min_val = 0xFFFFFFFF;
+    uint32_t deadline = HAL_GetTick() + window_ms;
+
+    while (HAL_GetTick() < deadline)
+    {
+        if (adc_read_channel(ADC_CHANNEL_16, &val) != 0)
+            return -1;
+
+        if (val > max_val) max_val = val;
+        if (val < min_val) min_val = val;
+    }
+
+    /* 峰峰值消除直流偏置影响,更真实反映响度 */
+    *noisy = (max_val > min_val) ? (max_val - min_val) : 0;
+    return 0;
+}
+
+/* ------------------- Lux 滤波相关参数 ------------------- */
+#define LUX_SAMPLE_COUNT   10   /* 每次采集的原始样本数 */
+#define LUX_TRIM_COUNT      2   /* 排序后,头尾各丢弃的样本数 */
+
+/**
+ * @brief  简单冒泡排序(数据量很小,不追求性能)
+ */
+static void sort_u32(uint32_t *arr, int n)
+{
+    for (int i = 0; i < n - 1; i++)
+    {
+        for (int j = 0; j < n - 1 - i; j++)
+        {
+            if (arr[j] > arr[j + 1])
+            {
+                uint32_t tmp = arr[j];
+                arr[j] = arr[j + 1];
+                arr[j + 1] = tmp;
+            }
+        }
+    }
+}
+
+/**
+ * @brief  多次采样光照度ADC值,排序后去掉两端的离群样本,再取平均
+ * @param  lux: 输出参数,滤波后的光照度值
+ * @retval 0 成功, -1 失败
+ */
+static int adc_read_lux_filtered(uint32_t *lux)
+{
+    uint32_t samples[LUX_SAMPLE_COUNT];
+    uint32_t sum = 0;
+    int valid_count;
+
+    /* 多次采样光照度ADC值 */
+    for (int i = 0; i < LUX_SAMPLE_COUNT; i++)
+    {
+        if (adc_read_channel(ADC_CHANNEL_15, &samples[i]) != 0)
+            return -1;
+    }
+
+    /* 将多次采样的数据排序 */
+    sort_u32(samples, LUX_SAMPLE_COUNT);
+
+    /* 丢弃排序后头尾各 LUX_TRIM_COUNT 个可能的离群值 */
+    valid_count = LUX_SAMPLE_COUNT - 2 * LUX_TRIM_COUNT;
+    if (valid_count <= 0)
+    {
+        /* 参数配置不合理时兜底,避免除0 */
+        valid_count = LUX_SAMPLE_COUNT;
+        for (int i = 0; i < LUX_SAMPLE_COUNT; i++)
+            sum += samples[i];
+    }
+    else
+    {
+        for (int i = LUX_TRIM_COUNT; i < LUX_SAMPLE_COUNT - LUX_TRIM_COUNT; i++)
+            sum += samples[i];
+    }
+
+    *lux = sum / valid_count;
+
+    return 0;
+}
+
+ /**
+ * @brief  采集光照度与噪声值
+ * @param  lux:   输出参数,采集到的光照度值
+ * @param  noisy: 输出参数,采集到的噪声值
+ * @return 0表示成功,-1 表示失败
+ */
+int adc_sample_lux_noisy(uint32_t *lux, uint32_t *noisy)
+{
+    if (adc_read_lux_filtered(lux) != 0)
+        return -1;
+
+    if (adc_read_noisy_peak(noisy, 50) != 0)
+        return -2;
+
+    return 0;
+}
+
+/*
+ *+----------------------------+
+ *|      Timer delay API       |
+ *+----------------------------+
+ */
+#include "tim.h"
+
+/**
+ * @brief  微秒级延时,最大支持 60000us,更长延时可以使用 HAL_Delay()
+ * @param  us: 延时时长(微秒)
+ */
+void udelay(uint16_t us)
+{
+    uint16_t differ = 60000-us;
+
+    HAL_TIM_Base_Start(&htim6);
+
+    __HAL_TIM_SET_COUNTER(&htim6, differ);
+
+    while( differ < 60000 )
+    {
+        differ=__HAL_TIM_GET_COUNTER(&htim6);
+    }
+
+    HAL_TIM_Base_Stop(&htim6);
+}

--
Gitblit v1.10.0