最近做的一个项目要求用三个GPIO同时独立的控制三个不同颜色的LED灯,且每个LED灯存在亮、灭、快闪、慢闪等四种状态。一开始想到在一个线程里面完成,但无法独立的控制三个LED灯的闪烁频率,无法避免相互影响。无奈之下,先用三个线程控制三个LED实现该功能,如果以后熟悉了线程定时器的做法,再优化。
(1)NLED设置结构体说明
struct NLED_SETTINGS_INFO
{
UINT LedNum; // @FIELD LED number, 0 is first LED
INT OffOnBlink; // @FIELD 0 == off, 1 == on, 2 == blink
LONG TotalCycleTime; // @FIELD total cycle time of a blink in microseconds
LONG OnTime; // @FIELD on time of a cycle in microseconds
LONG OffTime; // @FIELD off time of a cycle in microseconds
INT MetaCycleOn; // @FIELD number of on blink cycles
INT MetaCycleOff; // @FIELD number of off blink cycles
};
主要需要用到的几个参数是:LedNum标识不同的LED;OffOnBlink标识不同的工作状态;OnTime标识灯亮时间,OffTime标识灯灭时间,两者仅blink时有用。在很多LED的控制场合,只需要配置这个结构体之后,调用系统函数NLedSetDevice即可。例如:
void LightOPLed(int onoff)
{
NLED_SETTINGS_INFO SettingsInfo;
BOOL nled_result;
SettingsInfo.LedNum = 0; //0号LED
if(onoff)
SettingsInfo.OffOnBlink = 1; //灯亮
else
SettingsInfo.OffOnBlink = 0; //灯灭
SettingsInfo.OnTime = ~0x0;
SettingsInfo.OffTime = 0;
nled_result = NLedSetDevice( NLED_SETTINGS_INFO_ID, &SettingsInfo);
}
(2)GPIO的使用
用普通的GPIO来控制LED,置高亮,置低灭。因为在WINCE中需要对硬件进行虚拟映射之后才可以访问,一般是用MmMapIoSpace,本处是用的另外的一种方式,达到一样的效果。在NLedDriverInitialize中完成对所需硬件的映射,包括I2C,OSTimer,GPIO等。以下由于篇幅关系省略了错误判断和TRACE。
BYTE *pBaseVirtual; //虚拟映射地址变量
DWORD dwBasePhysical; //硬件基地址变量
LONG dwMMLen; //所需的内存长度
dwMMLen = ((sizeof(XLLP_I2C_T) +4*1024-1)/(4*1024)) * 4*1024 +
((sizeof(XLLP_OST_T) +4*1024-1)/(4*1024)) * 4*1024 +
((sizeof(XLLP_GPIO_T) +4*1024-1)/(4*1024)) * 4*1024;
/*以上是为了保证4K的页内存处理,所以会加4K取整*/
pBaseVirtual = (BYTE *)VirtualAlloc(NULL, dwMMLen, MEM_RESERVE, PAGE_NOACCESS); //分配虚拟内存
dwBasePhysical = MONAHANS_BASE_REG_PA_I2C; //I2C硬件地址
bRet = VirtualCopy((LPVOID)pBaseVirtual, (LPVOID)((unsigned long)dwBasePhysical >> 8), sizeof(XLLP_I2C_T), (PAGE_READWRITE | PAGE_NOCACHE | PAGE_PHYSICAL));
s_Device.m_pI2CCtrlReg = (P_XLLP_I2C_T)pBaseVirtual; //将分配的I2C映射地址赋给NLED全局变量
(BYTE *)pBaseVirtual += ((sizeof(XLLP_I2C_T) +4*1024-1)/(4*1024)) * 4*1024; //跳过刚用过的I2C内存
dwBasePhysical = MONAHANS_BASE_REG_PA_OST; //OSTimer硬件地址
bRet = VirtualCopy((LPVOID)pBaseVirtual, (LPVOID)((unsigned long)dwBasePhysical >> 8), sizeof(XLLP_OST_T), (PAGE_READWRITE | PAGE_NOCACHE | PAGE_PHYSICAL));
s_Device.m_pOSTimer = (P_XLLP_OST_T)pBaseVirtual; //将分配的OSTimer映射地址赋给NLED全局变量
(BYTE *)pBaseVirtual += ((sizeof(XLLP_OST_T) +4*1024-1)/(4*1024)) * 4*1024; //跳过刚用过的OSTimer内存
dwBasePhysical = MONAHANS_BASE_REG_PA_GPIO; //GPIO硬件地址
bRet = VirtualCopy((LPVOID)pBaseVirtual, (LPVOID)((unsigned long)dwBasePhysical >> 8), sizeof(XLLP_GPIO_T), (PAGE_READWRITE | PAGE_NOCACHE | PAGE_PHYSICAL)); //将分配的GPIO映射地址赋给NLED全局变量
s_Device.m_pGPIOReg = (P_XLLP_GPIO_T)pBaseVirtual; //将分配的GPIO映射地址赋给NLED全局变量
(3)对LED的亮和灭的控制
首先要在NLedDriverSetDevice添加对不同LED的控制,为此需添加三个LED的宏定义。由于三个LED类似,为此这里仅专门对红色LED的控制列举。对闪烁的控制需要建立一个线程和事件来控制,在下一节说明。如下:
BOOL WINAPI NLedDriverSetDevice(INT nInfoId, PVOID pInput )
{
.................
struct NLED_SETTINGS_INFO *p = ( struct NLED_SETTINGS_INFO* )pInput;
.................
switch(p->LedNum)
{
case RED_LED:
if(p->OffOnBlink == 1) //如果是全亮
{
ResetEvent(nled_Redset_event); //LED状态改变,取消闪烁事件
p->OffTime = 0; //灭时间为0
LedRGBSet(XLLP_GPIO2_2_RED_LED,p->OnTime,p->OffTime); //设置GPIO
}
else if(p->OffOnBlink == 0) //如果是全灭
{
ResetEvent(nled_Redset_event); //LED状态改变,取消闪烁事件
p->OnTime = 0; //亮时间为0
LedRGBSet(XLLP_GPIO2_2_RED_LED,p->OnTime,p->OffTime); //设置GPIO
}
else //如果是闪烁
{
RED.OffTime = p->OffTime;
RED.OnTime = p->OnTime; //获得亮灭的时间,并赋给全局变量,以便线程中调用
SetEvent( nled_Redset_event); //置闪烁事件有效
}
break;
}
}
其中,GPIO的控制函数如下:
void LedRGBSet(UINT8 pinNum,DWORD onTime, DWORD offTime) //zhangcheng 20101101
{
XllpGpioSetDirection(s_Device.m_pGPIOReg, pinNum, XLLP_GPIO_DIRECTION_OUT);
if((onTime !=0)&&(offTime == 0))
XllpGpioSetOutputLevel(s_Device.m_pGPIOReg, pinNum, XLLP_HI);
else if((onTime == 0)&&(offTime != 0))
XllpGpioSetOutputLevel(s_Device.m_pGPIOReg, pinNum, XLLP_LO);
}
(4)LED闪烁线程的处理
定义三个LED的事件句柄和准备传入线程的全局变量
HANDLE nled_Redset_event,nled_Greenset_event,nled_Blueset_event;
struct NLED_SETTINGS_INFO RED,GREEN,BLUE;
在NLedDriverInitialize中要创建时间跟线程:
nled_Redset_event = CreateEvent(NULL, TRUE, FALSE,NULL); //最后一个参数不需事件名字
nled_Greenset_event = CreateEvent(NULL, TRUE, FALSE,NULL); //初始化是FALSE,必须是设置灯闪烁才为SETEVENT
nled_Blueset_event = CreateEvent(NULL, TRUE, FALSE,NULL); //手动清信号,这个很重要,否则线程循环会阻塞
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)nled_Redset_thread, 0, 0, NULL);
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)nled_Greenset_thread, 0, 0, NULL);
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)nled_Blueset_thread, 0, 0, NULL);
以红色LED的线程说明:
DWORD WINAPI nled_Redset_thread()
{
DWORD dwWaitTime = INFINITE;
while(1)
{
int status;
status = WaitForSingleObject(nled_Redset_event, dwWaitTime); //线程阻塞,必须事件有效才会往后执行
if (status == WAIT_FAILED){ //如果事件是线程等待函数执行完后清掉了,第二次就阻塞在这里了,所以要用不自动清的事件
RETAILMSG(1,(TEXT("nled_Redset_thread: wait failed!/r/n")));
}
XllpGpioSetDirection(s_Device.m_pGPIOReg, XLLP_GPIO2_2_RED_LED, XLLP_GPIO_DIRECTION_OUT);
XllpGpioSetOutputLevel(s_Device.m_pGPIOReg, XLLP_GPIO2_2_RED_LED, XLLP_HI);
Sleep(RED.OnTime); //亮OnTime时间
XllpGpioSetOutputLevel(s_Device.m_pGPIOReg, XLLP_GPIO2_2_RED_LED, XLLP_LO);
Sleep(RED.OffTime); //灭OffTime时间,形成闪烁
}
return TRUE;
}