/********************************************************************** * Copyright: (C)2024 LingYun IoT System Studio * Author: GuoWenxue * Description: ISKBoard Hardware Abstract Layer driver * * ChangeLog: * Version Date Author Description * V1.0.0 2024.08.29 GuoWenxue Release initial version ***********************************************************************/ #include "miscdev.h" /* *+----------------------+ *| 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 ) 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); HAL_Delay(interval); turn_led(which, OFF); HAL_Delay(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); }