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