/**********************************************************************
|
* 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);
|
}
|