对于大多数单片机的学习者或者是从事单片机行业的工程师来说,单片机驱动LED闪烁起来的时候,基本都是我们入坑的开始,同时当时的那种兴奋与喜悦都是难以忘怀的,从LED灯亮起,到闪烁,再到各种流水灯,能开心好几个晚上。
一个LED灯的驱动程序可以说是最简单的,但是随着学习的深入,以及经验的积累,慢慢的会发现,想写一个高效的、普适的、可移植的驱动模块又不是那么容易,每次一般都是根据实际的情况现写一个,当然现写一个也不会花很多时间,但是我们却每次做项目都会要重复着造着这么一个最简单的轮子,因此在这里我想写一个从最原始的到目前我的能力所能达到的最好用的LED驱动模块,当然,我本身也是一个小白,希望能抛砖引玉,欢迎各路大神,提出宝贵意见。
基本实际的项目里面LED的数量不会很多,大多数情况不会超过3个(需要单片机控制的),一般只有两种接法(极个别的时候动态扫描的方式除外):一种是拉电流,一种是灌电流,说白了就是高电平亮还是低电平亮的问题,在这里我们主要以单个的LED驱动的方式来说明。
一般的实际项目当中,LED灯的模式主要是以下几种:
常亮/常灭:这个就没什么好说的了
快闪/慢闪:主要是用于不同的状态模块如:正在联网到网络连接成功
触发反转:主要是用于通信状态的指示,当收到数据的时候通过不停的亮灭来指示灯正在通信。
呼吸模式:主要是用于等待用户操作的一个状态
故障码输出:类似摩斯码的当时通过亮的长短来组成一组故障码
以上几种模式不是绝对的,只是相对的,具体怎么用户不同的工程师不同的产品,具体情况具体分析,只要合乎逻辑就可以了。
现在我想要做就是比较好用且相对来说高效的一个LED驱动模块,先定一个小目标,达到以下功能要求:
高电平低电平驱动都可使用(当然这一条算是废话)
非常容易移植,随便哪个工程只需要修改引脚即可,自己配置模式即可
几乎涵盖了所有项目对于一个指示灯功能的需求,什么模式都可以使用
理论上支持无限多个LED指示灯(局限于内存空间)
C语言编写(曾经使用C++写过,但是当LED比较多的时候,效率会比较低,当然也与自己的编程水平有关,所以在这里还是使用C语言来写),但是使用起来类似面向对象的使用方式。
当然在这里,我不是一上来直接就这个LED驱动程序写出来,既然标题是单片机驱动LED灯的进阶之路,其实我更想通过一个简单的LED的驱动程序的不断演变,来传递一点编程的理念与编程思想,以及我们在编程的过程中所不断摸索出的一些技巧,这些东西的价值可能远大于一个LED灯的驱动程序。
入门阶段:
LED_ON;
Delayms(500);
LED_OFF;
Delayms(500);
当我们在写这种驱动程序的时候,相信思想还是比较符合人类的思考,这时候我们还没有被冠以IT男或者程序猿的标签,找对象还是比较好找的,但是这种写法,可能很难满足实际的项目需求了,或者说如果在项目中使用这种代码,那么用户用你开发的产品一定是很难受的。
初级阶段:
Void led_loop()
{
LED_Count++;
If(LED_Count>10)
LED_Count=0;
Switch(LED_Mode)
{
Case 0: //灭
LED_OFF;
Break;
Case 1: //亮
LED_ON;
Break;
Case 2: //闪烁
If(LED_Count<2)LED_ON;
Else LED_OFF;
Break&#xff1b;
}
}
当这样来写LED驱动程序的时候&#xff0c;相信基本以及能做很多简单的单片机产品了&#xff0c;甚至很多时候已经完全通过这种方式来满足我们很多的产品需求&#xff0c;这时候我们对单片机也比较了解了&#xff0c;重要的是我们知道了周期与刷新的概念&#xff0c;知道每隔多长时间刷新一次&#xff0c;一个控制周期是多少&#xff0c;也可以通过这种方式来实现多种多样的模式&#xff0c;甚至很多时候&#xff0c;我们会停留在这中方式很久&#xff0c;哪怕是后面&#xff0c;我们可以通过PWM&#xff0c;定时器等方式组合出呼吸灯、闪烁呀&#xff0c;也可以把每个变量变成数组的方式来驱动多个指示灯等等&#xff0c;但是我们的代码依然没法满足模块化的方式。
中级阶段
Typedef struct
{
Char mode;
Char status;
Int count;
Void (*led_set)(char );
}LedOpt_t;
Void led_register(LedOpt_t *led);
Void led_loop(LedOpt_t *led);
这时候&#xff0c;我们已经对与结构体、函数指针、面向对象等已经比较熟悉了&#xff0c;而且我相信&#xff0c;我们也已经做过好多个不大不小的项目了&#xff0c;其实这时候&#xff0c;只要我们善于发现与总结&#xff0c;善于学习更多大神的宝贵经验我们完全有能力写出一套更加实用的驱动程序来&#xff0c;其实这时候比较麻烦的是当有多个指示灯的时候&#xff0c;我们难免还是需要修改一点代码&#xff0c;这让我觉得很不爽&#xff0c;所以还是想写一个驱动部分不想修改的驱动模块。
高级阶段
typedef struct iLed_t
{
uint8_t state; //信号灯当前的状态
uint16_t *mode; //用于指明当前参照闪烁数组中的第几个成员
uint16_t modeLen; //每种模式的长度
uint16_t tickCnt; //tick计数
uint16_t modeIndex; //一个周期内的闪烁次数
int32_t times; //执行次数
void (*on)(void); //led亮
void (*off)(void); //led灭
void (*overCallBack)(void); //led闪烁结束回调函数
struct iLed_t *next; //下一个led灯
} iLed_t;
这个时候&#xff0c;我们使用到了c语言的链表功能&#xff0c;通过一个链表&#xff0c;来实现多个led灯查找&#xff0c;而不用在使用for(i&#61;0;i
比如&#xff1a;
static iLed_t Led1;
static iLed_t Led2;
static iLed_t Led3;
const uint16_t LED_MODE_1[2]&#61;{500,500}; //模式1&#xff1a;亮500 灭500
const uint16_t LED_MODE_2[4]&#61;{200,200,100,800};//模式2&#xff1a;亮200&#xff0c;灭200&#xff0c;亮100&#xff0c;灭800
const uint16_t LED_MODE_3[4]&#61;{200,700,100,800,200,600};//模式3&#xff1a;亮200&#xff0c;灭700&#xff0c;亮100&#xff0c;灭800&#xff0c;亮200&#xff0c;灭600
我们可以根据我们的实际使用需求来随意定义自己的模式即可
led_creat(&Led1,led_1_on, led_1_off);
led_creat(&Led2,led_2_on, led_2_off);
led_creat(&Led3,led_3_on, led_3_off);
led_set_mode(&Led1,mode,modeLen,times);
Mode&#xff1a;选择模式如 LED_MODE_1
modeLen:当前所选模式的所占空间大小&#xff1a;sizeof(LED_MODE_1)&#xff0c;
Times&#xff1a;当前模式执行次数
这样的一个LED驱动程序基本可以实现我们对一个LED指示灯近乎变态的需求&#xff0c;呼吸灯呀&#xff0c;故障码呀什么的都是可以的&#xff0c;另外其实不建议通过这种方式驱动呼吸灯&#xff0c;最好还是使用硬件PWM的方式&#xff0c;当然这个驱动程序也是支持的&#xff0c;下面我就我相对完成的代码全部写出来&#xff0c;另外里面有些思想也是参考了晚上很多大拿的代码&#xff0c;具体我也记不清是谁的了&#xff0c;反正是当时搜索了很多代码&#xff0c;如果有雷同&#xff0c;也是有可能的。
#ifndef _ILED_H_
#define _ILED_H_
#include "stdint.h"
#include "string.h"
#define LED_TICK_TIME 50 //心跳函数调用的时间间隔(单位:ms)
typedef struct iLed_t
{
uint8_t state; //信号灯当前的状态
uint16_t *mode; //用于指明当前参照闪烁数组中的第几个成员
uint16_t modeLen; //
uint16_t tickCnt; //
uint16_t modeIndex; //一个周期内的闪烁次数
int32_t times; //执行次数
void (*on)(void);
void (*off)(void);
void (*overCallBack)(void); //led闪烁结束回调函数
struct iLed_t *next;
} iLed_t;
void led_init(iLed_t *handle,void (*on)(void), void (*off)(void));
void led_set_mode(iLed_t *handle,uint16_t *mode,uint16_t modeLen,int16_t times);
int8_t led_start(iLed_t *handle);
void led_stop(iLed_t *handle);
void led_switch(iLed_t *handle,char state);
void led_toggle(iLed_t *handle);
int8_t led_creat(iLed_t *handle,void (*on)(void), void (*off)(void));
void led_reg_call_back(iLed_t *handle,void (*overCallBack)(void));
void led_tick_loop(void);
void led_loop(void);
void led_tick();
#endif
---------------------------------分割线----------------------------------------------------------------
#include "iLed.h"
#include "stddef.h"
static struct iLed_t * head_led &#61; NULL;
static uint32_t _led_ticks &#61; 0;
//初始化&#xff0c;主要是注册led
void led_init(iLed_t *handle,void (*on)(void), void (*off)(void))
{
memset(handle, 0, sizeof(struct iLed_t));
handle->on&#61;on;
handle->off&#61;off;
handle->modeIndex&#61;0;
handle->modeLen&#61;0;
handle->tickCnt&#61;0;
handle->state&#61;0;
handle->off();
}
//执行完成回调&#xff0c;可以不使用&#xff0c;当一个led灯执行完成之后会调用此函数
void led_reg_call_back(iLed_t *handle,void (*overCallBack)(void))
{
handle->overCallBack&#61;overCallBack;
}
//启动led灯
int8_t led_start(iLed_t *handle)
{
struct iLed_t * target &#61; head_led;
while(target)
{
if(target &#61;&#61; handle) return -1;
target &#61; target->next;
}
handle->next &#61; head_led;
head_led &#61; handle;
return 0;
}
//创建led&#xff0c;注册并启动
int8_t led_creat(iLed_t *handle,void (*on)(void), void (*off)(void))
{
led_init(handle,on,off);
return led_start(handle);
}
//设置led模式
void led_set_mode(iLed_t *handle,uint16_t *mode,uint16_t modeLen,int16_t times)
{
struct iLed_t * target &#61; head_led;
while(target)
{
if(target &#61;&#61; handle)
{
target->mode&#61;mode;
target->times&#61;times;
target->modeLen&#61;modeLen/sizeof(mode[0]);
break;
}
else
{
target &#61; target->next;
}
}
}
//led灯停止
void led_stop(iLed_t *handle)
{
struct iLed_t** curr;
for(curr &#61; &head_led; *curr; )
{
struct iLed_t* entry &#61; *curr;
if (entry &#61;&#61; handle)
{
*curr &#61; entry->next;
}
else
curr &#61; &entry->next;
}
}
//led开关
void led_switch(iLed_t *handle,char state)
{
switch(state)
{
case 1:
handle->on();
handle->state&#61;state;
break;
case 0:
handle->off();
handle->state&#61;state;
break;
default:
break;
}
}
//触发反转
void led_toggle(iLed_t *handle)
{
handle->state&#61;!handle->state;
led_switch(handle,handle->state);
}
//led主要的控制逻辑
void led_handle(iLed_t *handle)
{
if((handle->times!&#61;0)&&(handle->modeLen>0))
{
handle->tickCnt&#43;&#43;;
if((handle->tickCnt*LED_TICK_TIME)<&#61;(handle->mode[handle->modeIndex]))
{
led_switch(handle,!(handle->modeIndex%2));
}
else
{
handle->tickCnt&#61;0;
handle->modeIndex&#43;&#43;;
if((handle->modeIndex)>&#61;(handle->modeLen))
{
handle->modeIndex&#61;0;
if(handle->times>0)
{
handle->times--;
if(handle->times&#61;&#61;0)
{
if(handle->overCallBack!&#61;NULL)
{
handle->overCallBack();
}
}
}
}
}
}
}
/*************************使用方法1***********************************/
//可以放在系统循环中
void led_loop(void)
{
struct iLed_t* target;
if(_led_ticks>&#61;LED_TICK_TIME)
{
for(target&#61;head_led; target; target&#61;target->next) //通过链表查询
{
led_handle(target);
}
_led_ticks&#61;0;
}
}
//系统滴答定时器中1ms执行一次
void led_tick()
{
_led_ticks&#43;&#43;;
}
/*************************使用方法2***********************************/
//定时调用该函数 调用周期为 LED_TICK_TIME
void led_tick_loop(void)
{
struct iLed_t* target;
for(target&#61;head_led; target; target&#61;target->next) //通过链表查询
{
led_handle(target);
}
}