广州手机网站设计,电商网站备案,曲靖市住房和城乡建设局网站,网站开发的教学视频前言
项目中需要用到很多的LED灯#xff0c;存在不同的闪烁方式#xff0c;比如单闪#xff0c;双闪#xff0c;快闪#xff0c;慢闪等等#xff0c;我需要一个有如下特性的LED驱动
方便的增加不同闪烁模式可以切换闪烁模式增加LED数目不会有太多的改动方便移植#x…前言
项目中需要用到很多的LED灯存在不同的闪烁方式比如单闪双闪快闪慢闪等等我需要一个有如下特性的LED驱动
方便的增加不同闪烁模式可以切换闪烁模式增加LED数目不会有太多的改动方便移植要有良好的硬件对接接口
好那就开整吧。 PS本文中的程序源码只做演示可运行的代码文末有链接
数据结构分析
首先考虑一颗LED的相关数据结构。 显然构建LED结构体应该有onoff接口如下
typedef struct{void (*init)(void); //初始化EDvoid (*on)(void); //打开LEDvoid (*off)(void); //关闭LED
}led_t;LED闪烁是亮灭的交替我们可以关注其中的两个参数
LED亮起时长标记为ontimeLED闪烁周期标记为cycle 将这两个参数抽象为led_mode_t结构体
typedef struct {uint16_t cycle; //LED闪烁周期uint16_t ontime; //LED亮起的时长
}led_mode_t;一颗LED可能会有很多闪烁模式不同LED闪烁模式数量不同所以当我们将led_mode_t集成到led_t的时候应该采用指针形式在运行的时候申请该结构的内存。由此丰富led_t结构体如下
typedef struct{void (*init)(void); //初始化EDvoid (*on)(void); //打开LEDvoid (*off)(void); //关闭LEDled_mode_t *mode; //LED闪烁模式具体的数据uint8_t mode_count; //LED闪烁模式个数uint8_t mode_current; //当前闪烁模式编码
}led_t;OKLED结构体初步搭建完毕接下来假设我们有4颗LED每一颗闪烁的时间参数如下
快闪200ms亮起200ms熄灭周期400ms慢闪500ms亮起500ms熄灭周期1000ms单闪100ms亮起900ms熄灭周期1000ms双闪30ms亮起 70ms熄灭30ms亮起 870ms熄灭周期1000ms
前三个都好说最后一个需要简单分析一下。双闪可以看作两种闪烁模式的切换。第一种30ms亮起 70ms熄灭周期100ms。第二种30ms亮起 870ms熄灭周期900ms。
程序框架搭建
所以初始化该情况下的代码如下
typedef struct {uint16_t cycle; //LED闪烁周期uint16_t ontime; //LED亮起的时长
}led_mode_t;typedef struct{void (*init)(void); //初始化EDvoid (*on)(void); //打开LEDvoid (*off)(void); //关闭LEDled_mode_t *mode; //LED闪烁模式具体的数据uint8_t mode_count; //LED闪烁模式个数uint8_t mode_current; //当前闪烁模式编码
}led_t;led_t led_array[4];void led0_init(void){}
void led1_init(void){}
void led2_init(void){}
void led3_init(void){}
void led0_on(void){}
void led1_on(void){}
void led2_on(void){}
void led3_on(void){}
void led0_off(void){}
void led1_off(void){}
void led2_off(void){}
void led3_off(void){}void bsp_led_init(void)
{//初始化函数指针led_array[0].on led0_on;led_array[0].off led0_off;led_array[0].init led0_init;led_array[0].mode_count 1;led_array[1].on led1_on;led_array[1].off led1_off;led_array[1].init led1_init;led_array[1].mode_count 1; led_array[2].on led2_on;led_array[2].off led2_off;led_array[2].init led2_init;led_array[2].mode_count 1;led_array[3].on led3_on;led_array[3].off led3_off;led_array[3].init led3_init;led_array[3].mode_count 2;for(uint8_t i 0; i sizeof(led_array)/led_array[0]; i){led_array[i].mode malloc(sizeof(led_mode_t) * led_array[i].mode_count);memset(led_array[i].mode, 0, sizeof(led_mode_t) * led_array[i].mode_count);}//初始化mode时间参数led_array[0].mode[0].ontime 200; led_array[0].mode[0].cycle 400;led_array[1].mode[0].ontime 500;led_array[1].mode[0].cycle 1000;led_array[2].mode[0].ontime 100;led_array[2].mode[0].cycle 1000;led_array[3].mode[0].ontime 30;led_array[3].mode[0].cycle 100;led_array[3].mode[1].ontime 30;led_array[3].mode[1].cycle 900;
}代码很长主要长度占用在以下三部分
每一个led都有initonoff函数初始化函数指针初始化mode时间参数
第一部分暂时不做优化这样会方便我按照顺序说下去吧 第二第三部分显然程序中有很多重复的代码我们可以使用可变参数宏来优化
#define INIT_PTR(__index, __onptr, __offptr, __initptr, __modecount) \led_array[__index].on __onptr; \led_array[__index].off __offptr; \led_array[__index].init __initptr; \led_array[__index].mode_count __modecount; #define INIT_MODE(__index, __mode, __ontime, __cycle) \led_array[__index].mode[__mode].ontime __ontime; \led_array[__index].mode[__mode].cycle __cycle; 使用这两个宏之后第二第三部分的代码被优化为如下看起来少了好多
void bsp_led_init(void)
{INIT_PTR(0, led0_on, led0_off, led0_init, 1);INIT_PTR(1, led1_on, led1_off, led1_init, 1);INIT_PTR(2, led2_on, led2_off, led2_init, 1);INIT_PTR(3, led3_on, led3_off, led3_init, 2);for(uint8_t i 0; i sizeof(led_array)/led_array[0]; i){led_array[i].mode malloc(sizeof(led_mode_t) * led_array[i].mode_count);memset(led_array[i].mode, 0, sizeof(led_mode_t) * led_array[i].mode_count);}INIT_MODE(0, 0, 200, 400);INIT_MODE(1, 0, 500, 1000);INIT_MODE(2, 0, 100, 100);INIT_MODE(3, 0, 30, 100);INIT_MODE(3, 1, 30, 900);
}OK我们已经设定了各个LED的闪烁模式对接了初始化亮起熄灭的函数是时候让他跑起来了。 假设我们有一个bsp_led_tick函数该函数每1ms调用一次。我们在该函数中定义一个递增的变量tick比较其它和LED灯ontime的大小。tick比ontime小则LED亮起比ontime大则LED熄灭。为了循环往复的工作我们采用的比较值应该是tick对周期的求余而不是tick本身。示例如下
static void bsp_led_tick(void){
#define i_CYCLE_LENGTH (led_array[i].mode[led_array[i].mode_current].cycle)
#define i_ON_TIME (led_array[i].mode[led_array[i].mode_current].ontime)static uint64_t tick;for(uint8_t i 0; i i sizeof(led_array)/led_array[0]; i){if(tick % i_CYCLE_LENGTH i_ON_TIME)led_array[i].on();elseled_array[i].off();}tick;
}我们使用了两个宏i_CYCLE_LENGTH i_ON_TIME 来减少代码长度增加可读性。 可以看出切换闪烁模式的话直接修改led_array[i].mode_current即可。 到此我们的驱动框架就很清晰了。 接下来我会指出该框架的问题并逐一修改。
问题修复
问题1LED无法关闭 上面代码中LED一直会闪烁无法关闭 解决办法 LED关闭可以认为是一种模式。其中ontime为0周期为任意值0除外 所以我们可以占用mode[0]将其所有LED的mode[0]初始化为ontime0 cycle1用户设置mode_current为0的时候调用该模式关闭LED。注意此时用户设置的闪烁模式需要从mode[1]开始
那么同样的如果需要LED常亮呢我认为LED熄灭是大部分项目中必要的而常亮却不一定所以在代码中不做常亮模式的预先设置如果用户需要的话可以另外设置一个mode其中的ontime大于cycletimecycletime不可以为0即可
问题2on off重复调用 满足if(tick % i_cycle i_ontime )条件的时候程序会重复调用led_array[i].on();即使此时LED已经打开了。 解决办法 在led_t中增加state成员标记LED状态已经打开的时候不要重复调用on已经关闭的不要重复调用off ···for(uint8_t i 0; i sizeof(led_array)/led_array[0]; i){//增加关闭模式此后mode[0]就被占用了用户定义的闪烁模式要从mode[1]开始led_array[i].mode malloc(sizeof(led_mode_t) * (led_array[i].mode_count)1);memset(led_array[i].mode, 0, sizeof(led_mode_t) * (led_array[i].mode_count)1t);}INIT_MODE(0, 1, 200, 400);INIT_MODE(1, 1, 500, 1000);INIT_MODE(2, 1, 100, 100);INIT_MODE(3, 1, 30, 100);INIT_MODE(3, 2, 30, 900);···static void bsp_led_tick(void){······//cycletime 0 LED不再闪烁if(i_CYCLE_LENGTH 0){if( (lled_array[i]..state 1)){led_array[i].state 0;led_array[i].off();}continue;}else{if(tick % i_CYCLE_LENGTH i_ON_TIME){if( (led_array[i].state 0)){//如果之前是关闭的那么开启led_array[i].state 1;led_array[i].on();}}else{if( led_array.led[i].state 1){//如果之前是开启的那么关闭led_array[i].state 0;led_array[i].off();}}}tick;
}问题3如何实现双闪 我们之前说过双闪是两种闪烁模式的交替闪烁那么如何实现交替切换模式呢 很简单我们只要再bsp_led_tick中判断tick是否增加到mode[].cycle即可。 即
//周期回调函数
if(i_CYCLE_LENGTH-1 (tick % i_CYCLE_LENGTH))
{led_array[i].cycle_func();
}cycle_func是led_t中增加的新成员作为周期结束的回调函数。使用前需要初始化指向led3_cyclefun在该函数中做模式切换
static void led3_cyclefun()
{if(led_array[3].mode_current 1)led_array[3].mode_current 2;else if(led_array[3].mode_current 2)led_array[3].mode_current 1;
}有了周期回调函数再复杂的LED显示效果我们都可以做出来只需要再周期结束修改显示模式即可。 但是理想很丰满现实很骨感这个地方还有一小片乌云等待解决。 我没有按照led3设置mode时间值按照图上的会直观一点。 可以看到在第二个闪烁模式tick2时间段此时ontime1tick%cycletime2%52那么第二模式根本不会亮起。此时的流程实际上变成了如下图 为了解决这个问题我们需要明确模式切换之后tick起始点是不同的不可以笼统的写作tick%cycletime模式切换之后应该有自己的tick起始点tick_start按照tick-tick_start)%cycletime求得余数 问题4同步 为了解决问题三我们引入了起点tick_start机制这回带来新的问题。 每一个LED都有自己的起点在模式切换的时候更新为当前tick。模式切换是使用该驱动的人决定的这会造成即使相同周期的LED灯闪烁相位的差异看起来在乱闪。 解决办法 在led_t中引入sync成员ticks_start只有配置了sync为0的时候才会更新为当前tick否则设置为0。 这将会明确一个事实sync配置为1的LED无法实现循环的闪烁模式切换。即类似于双闪这种循环的切换模式是不被允许的单次的切换可以。 双闪如果要做同步该怎么办应该可以在周期回调函数中做一些工作留给大家思考。
优化
这部分主要优化的是代码体积假设我有十个灯每一个都需要onoffcyclefunc这也太恐怖了。所以我们应该再led_t结构体之上再做一个结构体led_array_t。内容如下
typedef struct{led_t led[LED_COUNT];void (*on)(void *parameter); //打开LEDvoid (*off)(void *parameter); //关闭LEDvoid (* cycle_func)(void *parameter); //cycle结束的回调函数
}led_array_t;这里设置了parameter参数调用函数的时候将led_t传入以标识身份在函数内部判断我是哪一个LED进行相应的操作 举例如下
static void bsp_led_on(void *parameter)
{led_t *p (led_t *)parameter;switch(p-led_array.led){case 0:DRV_DIO_ChannelOutSet(DRV_DIO_ID_PIO_9);break;case 1:DRV_DIO_ChannelOutSet(DRV_DIO_ID_PIO_25);break;case 2:DRV_DIO_ChannelOutSet(DRV_DIO_ID_PIO_14);break;case 3:DRV_DIO_ChannelOutSet(DRV_DIO_ID_PIO_15);break;default:break;};
}OK内容到此结束了这样看来写好一个LED并不容易。 下面提供了一个基于RTOS的源码如果要修改为裸机的并不需要耗费很大功夫相信你可以做到 程序源码在此https://gitee.com/nwwhhh/led_flash_driver