首先,为什么要做LED灯的驱动设计呢?
可能很多人觉得,似乎LED灯的显示没有什么,无非就是点亮与熄灭。确实,点亮或者熄灭一个LED灯很简单,就是操作一下I/O口就可以了。但是,在平时做项目时,经常会遇到要控制LED灯的闪烁,包括持续闪烁和闪烁一定的次数,甚至还包括需要设置闪烁的频率,亮灯的占空比等等。还有就是可能有多个LED灯在闪烁,如果闪烁频率相同时,每个LED灯闪烁时却不同步,看起来就有些杂乱无章。
因此,还是有必要设计一个统一管理控制LED灯动作的一个驱动程序,使得其在使用时非常的方便。另外,这个倒是与linux操作系统的驱动有较大区别,可能有些人会认为这超出了通常LED灯驱动的设计。不过,我反而认为做这样的功能在驱动中,在一定小范围内显得通用性很好。
另外,有一个问题值得思考。用一个单色LED灯,用比较明显的方式,可以表示出多少种很容易区分的状态呢?这个问题我想在你看完整个完整的驱动后一定会有一个不一样的答案。当然,这也是在说明设计这个驱动的必要性。
以下将对LED灯驱动设计做一个较为详细的说明。
一、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种,也适合以枚举类型来定义
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灯应该由具体的应用程序根据实际需要进行定义,除所有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;lightlightMax;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;
}
LLed_UnLock();
return result;
}
4.LED灯驱动安装
LED灯驱动安装主要完成驱动内存申请,驱动函数安装及功能函数返回等。
返回tLLedHandle*类型的LED灯操作句柄,用于应用程序调用,若返回为NULL,则安装失败;返回tLLedVoid类型的poll被用于POLL调用,tLLedCtrlFunc类型的ctrl被用于应用程序控制LED灯工作模式时调用,若返回为NULL,则安装失败;
tLLedHandle* LLed_Initial(tLLedInitParam *pInitParam)
{
bool drvFunc=TRUE;
uint8_t light;
if (pInitParam==NULL||pLLedDrv!=NULL)
return NULL;
for (light=0;lightlightMax;light++)
{
//检查所有LED灯输出函数是否为空
if (pInitParam->drvFunc.output[light]==NULL)
{
drvFunc=FALSE;
break;
}
}
//检查扫描时间
if (pInitParam->scanUnit>0&&(pInitParam->scanUnitscanUnit>LLED_SCAN_MAX||(pInitParam->scanUnit%10)!=0))
drvFunc=FALSE;
//检查互斥锁(必须提供成对操作,不能单一为空)
if ((pInitParam->drvFunc.lock!=NULL&&pInitParam->drvFunc.unLock==NULL)||(pInitParam->drvFunc.lock==NULL&&pInitParam->drvFunc.unLock!=NULL))
drvFunc=FALSE;
if (drvFunc==TRUE)//必须提供LED灯输出函数,否则安装失败
{
//申请驱动指针内存
pLLedDrv=MALLOC_MEM(sizeof(tLLedDriver)+sizeof(tLLed)*pInitParam->lightMax+sizeof(tLLedOutputFunc)*pInitParam->lightMax);
pLLedDrv->led=(tLLed*)((uint8_t*)pLLedDrv+sizeof(tLLedDriver));
pLLedDrv->drvFunc.output=(tLLedOutputFunc*)((uint8_t*)pLLedDrv->led+sizeof(tLLed)*pInitParam->lightMax);
pLLedDrv->needChange=FALSE;
pLLedDrv->lightMax=pInitParam->lightMax;
pLLedDrv->drvFunc=pInitParam->drvFunc;
for (light=0;lightlightMax;light++)
pLLedDrv->drvFunc.output[light]=pInitParam->drvFunc.output[light];
//初始化轮询扫描时间单位
if (pInitParam->scanUnit==0)
pLLedDrv->scanUnit=LLEDSCAN_UINT_DEF;
else
pLLedDrv->scanUnit=pInitParam->scanUnit;
if (pLLedDrv->drvFunc.lock==NULL)
pLLedDrv->swLock=LLED_UNLOCKED;
//执行硬件底层初始化
if (pLLedDrv->drvFunc.init!=NULL)
pLLedDrv->drvFunc.init();
//初始化输出(默认初始输出熄灭)
for (light=0;lightlightMax;light++)
{
//对于闪烁,cntDetect控制着在执行熄灭时是否进行闪烁计数
pLLedDrv->led[light].run.cntDetect=FALSE;
pLLedDrv->led[light].status=LLED_STATUS_OFF;
pLLedDrv->led[light].mode=LLED_MODE_DARKNESS;
pLLedDrv->led[light].trig=LLED_TRIG_OFF;
pLLedDrv->drvFunc.output[light](LLED_STATUS_OFF);
}
//设置LED灯外部操作控制功能函数及POLL函数指针
pLLedDrv->handle.ctrl=LLedFuncCtrl;
pLLedDrv->handle.poll=LLed_Poll;
return &pLLedDrv->handle;
}
else
{
//安装失败
pLLedDrv=NULL;
return NULL;
}
}
5.LED灯驱动卸载
LED灯驱动的卸载调用前,最好手动停止LED灯轮询的定时调用,此处不对其进行检查,仅使用应用层提供的互斥锁。
bool LLed_UnInitial(void)
{
bool result=TRUE;
LLed_Lock();
if (pLLedDrv!=NULL)
{
//执行低级去初始化操作
if (pLLedDrv->drvFunc.unInit!=NULL)
pLLedDrv->drvFunc.unInit();
FREE_MEM(pLLedDrv);
pLLedDrv=NULL;
}
else
result=FALSE;
LLed_UnLock();
return result;
}
四.总结
以上的驱动,实际上就4个主要函数:(在实际驱动文件中,包含更多的是辅助被调用的模块)
void LLed_Poll(void); ------LED灯轮询函数;
bool LLedFuncCtrl(uint8_t light,void* pParam);------LED灯工作控制函数;
tLLedHandle* LLed_Initial(tLLedInitParam *pInitParam);------LED灯初始化函数;
bool LLed_UnInitial(void); ------LED灯去初始化函数;
1.必要的移植修改设计说明
1) 需要定义LED灯;
2) 至少需要设计LED灯输出函数(output),且每一个LED灯有一个对应函数;
3) 不管是裸机运行还是在操作系统下运行,均需要进行LED灯驱动安装;
4) 设计中将轮询输出函数提供给应用层去决定如何执行,这样可以进一步降低耦合度以及增强LED灯驱动使用的灵活性。对于裸机运行可将轮询输出函数放在硬件定时器产生的POLL中执行;对于运行在操作系统(如FreeRTOS)下可以将轮询输出函数放在某个软件定时器的回调函数中执行(或发送事件到某个任务执行);
5) 可能还会有一些没能想到的功能,如果有需要,仍然可以添加到轮询输出函数及外部功能函数中;
2.外部调用接口
驱动安装函数LLed_Initial是唯一直接提供给应用层必须调用的接口;
驱动卸载函数LLed_UnInitial是直接接提供给应用层可选调用的接口;
轮询输出函数LLed_Poll是间接提供给应用层周期性调用的接口;
外部功能函数LLedFuncCtrl是间接提供给应用层控制LED灯显示行为调用的接口;
3.LED灯驱动使用示例
本来是打算在这里给出全部的代码及示例的,但感觉在头条上的朋友可能并不需要这个。因此将不再像上一个文章中那样给出全部代码,也以此减少你对本文章的阅读负担。
当然,如果关注此文章的人较多,并且有意愿获得这部分代码的人也较多,我将在后面添加这部分内容。
以下是省略的代码title
1)LLedDriver.h文件代码(驱动.h文件);
2)LLedDriver.c文件代码(驱动.c文件);
3)LedLight.c文件代码(测试代码);
页面更新:2024-05-24
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号