单片机软件常用设计分享(二)驱动设计之LED灯显示设计
前言
本人从事单片机软件设计工作多年,既从事过裸机系统的设计,也在小型嵌入式实时操作系统下进行过设计。因在工作当中发现有些人对单片机软件的设计非常随意,尤其是在驱动方面,其考虑问题过于单一,并且应用层与底层驱动之间耦合度较大。基于此,本人整理工作当中做过常用的最基本设计分享到这里,希望对需要的人有所帮助或参考。当然,可能我的设计方法并不是很好,所以也算是一种学习交流。
在整理的过程中,可能会缺乏统一规划,仅先整理一部分常用的驱动设计。至于其它部分的内容,待今后跟进需要再逐一补充。
《驱动设计–LED灯显示驱动》
似乎LED灯的显示没有什么,无非就是点亮与熄灭。确实,点亮或者熄灭一个LED灯很简单,就是操作一下I/O口就可以了。但是,在平时做项目时,经常会遇到要控制LED灯的闪烁,包括持续闪烁和闪烁一定的次数,甚至还包括需要设置闪烁的频率,亮灯的占空比等等。还有就是可能有多个LED灯在闪烁,如果闪烁频率相同时,每个LED灯闪烁时不同步,看起来就有些杂乱无章。
因此,还是有必要设计一个统一管理控制LED灯动作的一个驱动程序,使得其在使用时非常的方便。另外,这个倒是与linux操作系统的驱动有较大区别,可能有些人会认为这超出了通常LED灯驱动的设计。不过,我反而认为做这样的功能在驱动中,在一定小范围内显得通用性很好。
另外,有一个问题值得思考。用一个单色LED灯,用比较明显的方式,可以表示出多少种很容易区分的状态呢?这个问题我想在你看完整个完整的驱动后一定会有一个不一样的答案。当然,这也是在说明设计这个驱动的必要性。
说明:在调试完成数码屏驱动后,对此驱动做了一定修改,主要是取消了对于LED_LIGHT_MAX定义的依赖。
一、LED灯工作方式
一般包括3种工作方式,常亮、常灭、闪烁;但现在还需要追加一种,即间歇性闪烁。这种工作方式只是我自己对此的一种叫法,在这里首先对这种闪烁方式做一个简单说明。
比如有一种用LED闪烁的故障表示法: LED灯闪烁以3短1长的方式持续周期显示。即连续执行3次较快的闪烁,接着再执行1次较慢的闪烁。当然,这种显示方式有很多变化,我会在后面的说明中阐述。我把这种显示方式统一的称为:“间歇性闪烁”。
因此,一般LED灯包括4种工作方式,常亮、常灭、闪烁、间歇性闪烁。
1.常亮
LED灯一直处于点亮状态,这个比较简单,无需多作说明;
2.常灭
LED灯一直处于熄灭状态,这个也无需多作说明;
3.闪烁
LED灯处于两种交替状态,即点亮与熄灭状态,这个需要设置的参数就较多。包括:持续闪烁或闪烁一定次数,闪烁频率,点亮的占空比;
4.间歇性闪烁
LED灯处于两种较明显区别的频率交替显示,即快速闪烁与慢速闪烁,同时增加一个间歇。这样就需要设置的两组闪烁参数。同时,还需要设置这种显示方式的次数,以及间歇时间;
说明:以下在描述时只列出了相关部分代码,但会在最后给出完整代码。
二、LED灯驱动数据结构
LED灯驱动数据结构设计,包括LED灯状态、LED灯工作方式,LED灯闪烁参数,LED灯驱动结构,以下分别描述各个部分内容;
1.LED灯状态
LED灯状态仅两种,即点亮与熄灭,这种状态适合以枚举类型来定义
LED灯状态 (以下LLED为LED LIGHT缩写,并将LIGHT缩写L写在前面)
typedef enum
{
LLED_STATUS_ON, //点亮状态
LLED_STATUS_OFF //熄灭状态
}tLLedStatus;//LED灯状态
2.LED灯工作方式
LED灯工作方式前面已经讲过,就4种,也适合以枚举类型来定义
LED灯工作方式
typedef enum
{
LLED_MODE_BRIGHT, //常亮方式
LLED_MODE_DARKNESS, //熄灭方式
LLED_MODE_FLASH //闪烁方式
LLED_MODE_FLASH_INTERVAL //间歇性闪烁方式
}tLLedMode;//LED灯工作方式
3.LED灯闪烁
LED灯闪烁参数包括多个参数,分别是闪烁频率,闪烁占空比,闪烁次数,闪烁结束LED灯状态。
说明:LED灯的闪烁频率相邻之间其实观察起来并不明显,因此不必要每一个频率都有,而且有些频率按照设置参数不容易被整数划分为计数值。所以,以下仅定义了部分频率。
LED灯闪烁频率
typedef enum
{
LLED_FREQ_1_10Hz=100, //0.1Hz
LLED_FREQ_2_10Hz=50, //0.2Hz
LLED_FREQ_5_10Hz=20, //0.5Hz
LLED_FREQ_1Hz=10, //1Hz
LLED_FREQ_2Hz=5, //2Hz
LLED_FREQ_5Hz=2, //5Hz
LLED_FREQ_10Hz=1, //10Hz
}tLLedFreq;//LED灯闪烁频率
LED灯闪烁参数
注意:以下的占空比最后在计算为计数值时,是受闪烁频率与扫描时间限制的,并不一定会得到一个整数值。当然,扫描时间设置为最小值10ms,是考虑了性能问题的,其决定了实际可计算得到的占空比对应的计数值。
typedef struct
{
tLLedFreq freq; //闪烁频率
tLLedStatus endStatus; //闪烁结束LED灯状态
uint8_t dutyRatio; //取值范围[10/20/30/40/50/60/70/80/90]
uint8_t counter; //闪烁次数[0--255],0表示持续闪烁
}tLLedFlashParam;//LED灯闪烁参数
LED灯间歇闪烁参数
typedef struct
{
uint8_t count; //间歇闪烁执行次数
uint16_t darkTime; //熄灭时间,单位ms,但必须设置为扫描时间的整数值
tLLedFlashParam first; //闪烁first参数
tLLedFlashParam second; //闪烁second参数
}tLLedFlashIntervalParam;//LED灯间歇闪烁参数
LED灯闪烁计数器
typedef struct
{
uint8_t max; //最大计数值
uint8_t exec; //执行计数值
}tLLedCnt;//LED灯闪烁计数器
LED灯闪烁运行
以下的onCntMax与offCntMax主要由闪烁频率、占空比、扫描时间参数计算得出。
typedef struct
{
bool cntDetect; //计数检测标志(取值TRUE才可以检测计数值)
uint8_t intervalLevel; //记录间歇性运行的阶段
uint16_t count; //计数器
uint16_t onCntMax; //点亮计数器最大值
uint16_t offCntMax; //熄灭计数器最大值
tLLedCnt flashCnt; //闪烁计数器
tLLedCnt intervalCnt; //间歇闪烁计数器
tLLedStatus ndStatus; //闪烁结束LED灯状态
}tLLedFlashRun;//LED灯闪烁运行
4.LED灯驱动数据结构
1)LED灯定义(已经取消LED_LIGHT_MAX定义)
LED灯应该由具体的应用程序根据实际需要进行定义,除所有LED灯外,应该还需要定义LED_LIGHT_MAX项,因驱动中需要使用此参数。
LED灯定义(示例)
#define LED_LIGHT_RUN 0 //运行指示灯
#define LED_LIGHT_FAULT 1 //故障指示灯
#define LED_LIGHT_ALARM 2 //告警指示灯
#define LED_LIGHT_COMM 3 //通信指示灯
#define LED_LIGHT_MAX LED_LIGHT_COMM+1 //LED灯个数
2)LED灯驱动函数
以下驱动函数由初始化时外部提供,用以安装
LED灯驱动函数原型定义
typedef void(*tLLedVoid)(void); //LED无参函数原型
typedef bool(*tLLedCtrlFunc)(uint8_t,void*); //LED灯工作控制函数原型
typedef void(*tLLedOutputFunc)(tLLedStatus); //LED灯输出控制函数原型
LED灯应用层提供的驱动函数
//LED灯应用层提供的驱动函数
typedef struct
{
tLLedOutputFunc *output; //安装的LED灯输出控制函数
tLLedVoid init; //安装的LED灯底层初始化函数(可以设置为空)
tLLedVoid unInit; //安装的LED灯底层去初始化函数(可以设置为空)
tLLedVoid lock; //安装的LED灯互斥锁获取函数(可以设置为空)
tLLedVoid unLock; //安装的LED灯互斥锁归还函数(可以设置为空)
}tLLedDrvFunc;//LED灯驱动函数
3)LED灯控制参数结构
LED灯控制参数(调用LED灯工作控制时的入口参数),其中runParam的具体应用与含义在驱动文件中有详细说明。
typedef struct
{
tLLedMode mode; //LED灯工作模式
void* runParam; //LED工作运行参数
}tLLedCtrlParam;//LED灯控制参数
4)LED灯驱动结构
typedef struct
{
tLLedMode mode; //LED灯工作模式
tLLedTrig trig; //LED点亮及熄灭触发标志
tLLedStatus status; //LED灯状态
tLLedFlashRun run; //LED灯闪烁运行
tLLedFlashIntervalParam flashIntervalParam; //LED灯间歇性闪烁保存参数
}tLLed;//LED灯结构
typedef struct
{
tLLedVoid poll; //poll函数
tLLedSetupFunc ctrl; //LED灯控制功能函数
}tLLedHandle;//LED 灯驱动提供给应用层的操作句柄
typedef struct
{
uint8_t scanUnit; //LED扫描时间单位,ms
bool needChange; //LED灯需要改变状态时,将被设置为TRUE)
tLLed_Lock swLock; //LED软件互斥锁
tLLed *led; //LED灯组
tLLedDrvFunc drvFunc; //外部安装的驱动函数
tLLedHandle handle; //外部调用的功能函数(此类型说明在后面安装驱动中说明)
}tLLedDriver;//LED灯驱动数据结构
三、LED灯驱动代码设计
1.参数及变量定义
LLEDSCAN_UINT_DEF参数用于在应用层未提供扫描时间时使用。
//LLED_INTERVAL_MFLASH_DLY设置用于增加FIRST FLASH 与SECOND FLASH直接较明显的间隔
#define LLED_INTERVAL_MFLASH_DLY 1
//
#define LLEDSCAN_UINT_DEF 10 //LED灯扫描默认时间单位(ms)
//以下定义内存申请与释放
#define MALLOC_MEM(memSize) pvPortMalloc(memSize) //内存申请宏
#define FREE_MEM(pMem) vPortFree(pMem) //内存释放宏
tLLedDriver *pLLedDrv=NULL; //仅用于驱动内部的驱动指针
2.驱动处理
LED灯驱动轮询输出函数,这里仅说明其控制逻辑,涉及到的详细子模块在驱动文件中可查看。
从以下代码中其实可以看出,主要处理闪烁与间歇性闪烁。同时,当所有LED灯不需要再改变状态时,POLL将不会往下执行,这其中主要依靠pLLedDrv->needChange变量控制。
另外,如果有LED灯仅仅需要执行常亮或常灭,最好也通过ctrl函数的调用由poll来处理。或者你压根不用放在此驱动中。
void LLed_Poll(void)
{
uint8_t light;
bool output;
LLed_Lock();
//没有安装驱动,拒绝执行或 没有LED灯状态需要改变,则直接退出
if (pLLedDrv==NULL||pLLedDrv->needChange==FALSE)
{
LLed_UnLock();
return;
}
pLLedDrv->needChange=FALSE;
//循环处理所有LED灯
for (light=0;light<pLLedDrv->lightMax;light++)
{
//输出控制,并不是每次都需要输出,仅在状态发生改变时才执行(或强制输出)
output=FALSE;
if (pLLedDrv->led[light].mode==LLED_MODE_FLASH)
{
//闪烁操作处理
if (pLLedDrv->led[light].status==LLED_STATUS_ON)
output=LLed_FlashOnCheck(light);//闪烁点亮计时,并检测切换至闪烁灭状态
else
output=LLed_FlashOffCheck(light);//闪烁熄灭计时,并检测切换至闪烁亮状态,同时,检测闪烁次数
}
else if (pLLedDrv->led[light].mode==LLED_MODE_FLASH_INTERVAL)
{
//间歇性闪烁操作处理
if (pLLedDrv->led[light].run.intervalLevel==LLED_FLASH_INTERVAL_DARK)
{
pLLedDrv->led[light].run.count++;
if (pLLedDrv->led[light].run.count>=pLLedDrv->led[light].flashIntervalParam.darkTime)
pLLedDrv->led[light].run.intervalLevel=LLED_FLASH_INTERVAL_FINISH;
}
else
{
//间歇性闪烁的第一步及第二步均执行闪烁操作
if (pLLedDrv->led[light].status==LLED_STATUS_ON)
output=LLed_FlashOnCheck(light);
else
output=LLed_FlashOffCheck(light);
if (pLLedDrv->led[light].mode!=LLED_MODE_FLASH_INTERVAL)
LLed_FlashIntervalSwitchToNext(light);
}
if (pLLedDrv->led[light].run.intervalLevel==LLED_FLASH_INTERVAL_FINISH)
LLed_FlashIntervalFinish(light);
}
if (pLLedDrv->led[light].mode>=LLED_MODE_FLASH)
pLLedDrv->needChange=TRUE;
if (pLLedDrv->led[light].trig==LLED_TRIG_ON||output==TRUE)
{
pLLedDrv->led[light].trig=LLED_TRIG_OFF;
pLLedDrv->drvFunc.output[light](pLLedDrv->led[light].status);
}
}
LLed_UnLock();
}
3.LED灯工作控制操作
此函数执行每一个LED灯的工作控制操作。包括常亮、常灭、闪烁、间歇性闪烁4种工作方式设置。
bool LLedFuncCtrl(uint8_t light,void* pParam)
{
tLLedCtrlParam *pCtrlParam;
bool result=TRUE;
pCtrlParam=(tLLedCtrlParam*)pParam;
if (pLLedDrv==NULL||light>=pLLedDrv->lightMax)
return FALSE;
LLed_Lock();
//
pLLedDrv->led[light].run.cntDetect=FALSE;
switch (pCtrlParam->mode)
{
case LLED_MODE_BRIGHT:
if (pLLedDrv->led[light].mode!=LLED_MODE_BRIGHT)
{
//设置指定的LED灯工作于常亮
pLLedDrv->led[light].mode=LLED_MODE_BRIGHT;
pLLedDrv->led[light].status=LLED_STATUS_ON;
}
else
result=FALSE;
break;
case LLED_MODE_DARKNESS:
if (pLLedDrv->led[light].mode!=LLED_MODE_DARKNESS)
{
//设置指定的LED灯工作与常灭
pLLedDrv->led[light].mode=LLED_MODE_DARKNESS;
pLLedDrv->led[light].status=LLED_STATUS_OFF;
}
else
result=FALSE;
break;
case LLED_MODE_FLASH:
if (pLLedDrv->led[light].mode!=LLED_MODE_FLASH)
{
tLLedFlashParam* pFlashParam;
pFlashParam=(tLLedFlashParam*)pCtrlParam->runParam;
//设置LED灯闪烁参数
result=LLed_FlashParamSetup(light,pFlashParam);
if (result==TRUE)
{
//同步相同频率的LED灯闪烁(未找到时,从点亮开始)
LLed_SyncFlash(light);
pLLedDrv->led[light].mode=LLED_MODE_FLASH;
}
}
else
result=FALSE;
break;
case LLED_MODE_FLASH_INTERVAL:
if (pLLedDrv->led[light].mode!=LLED_MODE_FLASH_INTERVAL)
{
tLLedFlashIntervalParam* pFlashIntervalParam;
pFlashIntervalParam=(tLLedFlashIntervalParam*)pCtrlParam->runParam;
result=LLed_FlashIntervalParamCheck(pFlashIntervalParam);
if (result==TRUE)
{
//间歇性闪烁应该不再需要考虑同步相同频率的问题
LLed_FlashParamSetup(light,&pFlashIntervalParam->first);
if (pLLedDrv->led[light].status==LLED_STATUS_ON)
pLLedDrv->led[light].status=LLED_STATUS_OFF;
else
pLLedDrv->led[light].status=LLED_STATUS_ON;
pLLedDrv->led[light].run.intervalCnt.exec=0;
pLLedDrv->led[light].run.intervalCnt.max=pFlashIntervalParam->count;
pLLedDrv->led[light].run.intervalLevel=LLED_FLASH_INTERVAL_FIRST;
pLLedDrv->led[light].flashIntervalParam=*pFlashIntervalParam;
pLLedDrv->led[light].flashIntervalParam.darkTime/=pLLedDrv->scanUnit;
pLLedDrv->led[light].mode=LLED_MODE_FLASH_INTERVAL;
}
}
else
result=FALSE;
break;
}
if (result==TRUE)
{
pLLedDrv->led[light].trig=LLED_TRIG_ON;
pLLedDrv->needChange=TRUE;