guowenxue
2026-07-06 d97ecd3a2976ff4541bc8d4af9cbadfb245dbcef
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
/**********************************************************************
*   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"
 
/*
 *+----------------------+
 *|  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==OFF ? GPIO_PIN_SET : GPIO_PIN_RESET;
    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 来兼容这两个编译器所需要的函数原型;
 */
#if defined(__ARMCC_VERSION) /* MDK ARM Compiler */
FILE __stdout;
FILE __stdin;
#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);
}