如何写一个状态指示灯?

ID :
工作时,我们常常会有这样的需求:
用一个 LED 灯指示机器的工作状态:正常或故障 。
这样一个需求,对于我们入门就写点灯程序的点灯小能手来说,根本就是小意思 。
因为这只需要控制 IO 输出高电平或者低电平就可以达到要求 。尽管如此简单,如果用的不好,那也是存在风险的:《引脚输出的隐藏BUG》 。
本篇笔记记录的当然不是简单的指示正常和故障这两种状态,而是多种状态 。
比如,快闪、慢闪、1秒闪1次,1秒闪3次等 。
不过,因精力有限,鱼鹰介绍一个简单的实现1.5秒闪烁1~5次的方法,并且通过对该方法的详细介绍,你也可以实现更复杂的指示功能(当然也可用于蜂鸣器等) 。
该方法有几点优势:
1、代码实现简单;
2、可用于多线程;
3、移植方便;
4、裸机、操作系统均可使用,可不需要定时器 。
当然也有缺点,就是只能指示最新的当前状态(也就是说,如果有多个线程同时调用,将指示最后一个调用时的状态),不过对于状态指示来说,这样足够了 。
为了实现1.5秒内闪烁5次,并且让代码尽可能的简单,我们可以将 1.5 s划分成 10 个等份,每个等份为 150 ms 。

如何写一个状态指示灯?

文章插图
假定,高电平为灭,低电平为亮 。
那么,如果我们要实现1.5 s 闪烁一次的效果,只需要在0 时刻设置为低电平,1时刻设置为高电平即可,其他时刻不需要操作电平,此时电平效果如下:
如何写一个状态指示灯?

文章插图
这样,通过在指定时刻设置IO电平,即可达到闪烁的效果 。
现在的问题是,如何获得稳定的周期时间,这已经是老生常谈的问题,如果不明白,可以参考以下笔记:《延时功能进化论(合集)》 。
这里我们需要实现两个周期,一个是 1.5 s,一个是 150 ms,所以需要两个变量保存时间戳,同时为了实现单次延时,再增加一个变量,即关于时间的变量共有三个 。
下面贴代码,看看代码实现(滑动查看):
#define LED_TIME_CYCLE1500// ms#define LED_TIME_OUTPUT150// mstypedef enum {LED_ON,LED_OFF,}led_level_def;typedef struct {uint16_t last_time_show_cycle;// 状态查询时间uint16_t set_time_cycle;// 循环时间uint16_t set_last_time;// 上一次电平输出时间uint8_tcurr_number;// 当前uint8_tnext_number;// 下一个指示次数void(*led_set)(led_level_def); }led_para_def;// LED 设置void led_set_level(led_level_def level){if(level == LED_ON){GPIO_ResetBits(GPIOB, GPIO_Pin_13);}else{GPIO_SetBits(GPIOB, GPIO_Pin_13); }}// 参数初始化led_para_def led_para = {.set_time_cycle= (uint16_t)-1,.curr_number=0,.led_set=led_set_level,};// 调用频率小于 10 msvoid led_set_handle(led_para_def *p_led_para, uint32_t time){// 设置 LEDif(((uint16_t)(time - p_led_para->set_last_time)) >= p_led_para->set_time_cycle) {p_led_para->set_last_time = time;if(p_led_para->curr_number) {p_led_para->led_set(((p_led_para->curr_number & 1) == 1) ? LED_OFF : LED_ON);p_led_para->curr_number--;if(p_led_para->curr_number == 0) {p_led_para->set_time_cycle = (uint16_t)-1;// 下一个周期不再进入}}}// 更新当前参数(1.5 s 更新一次)if(((uint16_t)(time - p_led_para->last_time_show_cycle)) >= LED_TIME_CYCLE){p_led_para->last_time_show_cycle = time;if(!p_led_para->curr_number){uint8_t number = p_led_para->next_number; // 获取当前指示次数// 限制闪烁次数if(number < LED_TIME_CYCLE / LED_TIME_OUTPUT / 2){p_led_para->curr_number= number * 2 - 1;p_led_para->set_last_time= time;p_led_para->set_time_cycle = LED_TIME_OUTPUT;p_led_para->led_set(LED_ON);}}}}// number 闪烁次数void led_show(led_para_def *p_led_para, uint8_t number){p_led_para->next_number = number;}