在嵌入式产品开发中,从业务逻辑分拆进行面向过程的软件编码中,会有一种和多种任务来对业务逻辑进行实现,从而达到宏观层面的产品功能。在软件层面中,任务的执行过程中会涉及到对硬件的IO,中断的响应,信号资源的处理等。有时候借助于异步任务或事件的使用,从而可以防止主线程的阻塞。
本文中先对异步事件的实现做一介绍,然后再从应用层面来列举几个示例说明异步事件实现的优点。
我们在嵌入式开发中,经常会用到硬件定时器(Timer),简单的应用中,可能只是用定时器产生一定的硬延时,有时用定时器特殊功能实现PWM波输出等。一般而言,大多数MCU都会提供多个硬件定时器供产品开发使用。下面就介绍一下利用硬件定时器实现异步事件的方法。
首先,我们要配置定时器以一定时基进行定时溢出中断。以下代码以新唐Cortex-M0核为准,对定时器TIMER0时行配置,以半周期0.5ms的时基定时产生溢出中断。代码如下:
global void gvIF_Timer0Init( void )
{
lvIF_TimerEventTableInit();
/* TIMER0 and TIMER1 module clock enable. */
CLK->APBCLK |= CLK_APBCLK_TMR0_EN_Msk;
#if DEBUG_TIMER_CLK_OUTPUT == 1U
/*!< To config P9.0 as push-pull output mode to output TIMER clock temperary. */
GPIO->PWMPOEN &= ~(GPIO_PWMPOEN_HZ_Even0_Msk | GPIO_PWMPOEN_HZ_Odd0_Msk);
P0->PMD = (P0->PMD & (~(GPIO_PMD_QUASI << 8))) | (GPIO_PMD_OUTPUT << 8);
P5->PMD = (P5->PMD & (~(GPIO_PMD_QUASI << 4))) | (GPIO_PMD_OPEN_DRAIN << 4);
#endif /*!< DEBUG_TIMER_CLK_OUTPUT */
/*!< 50MHz / ((4 + 1) * 5000) = 2KHz 1/2KHz = 0.5ms (Half duty)*/
TIMER0->TCSR &= (~(TIMER_TCSR_MODE_Msk | TIMER_TCSR_PRESCALE_Msk | TIMER_TCSR_IE_Msk));
TIMER0->TCSR |= (TIMER_PERIODIC_MODE | (4 << TIMER_TCSR_PRESCALE_Pos) | TIMER_TCSR_IE_Msk );
TIMER0->TCMPR = (TIMER_TCMP_TCMP_Msk & 5000);
NVIC_SetPriority(TMR0_IRQn, NVIC_PRIORITY(2));
NVIC_EnableIRQ(TMR0_IRQn);
TIMER_Start(TIMER0);
SystemCoreClockUpdate();
}
然后根据异步事件的特点,我们定义一个结构体来对应异步事件的特点.
/*!< Note: pFun is a self-define type, like this, typedef void (*pFun)(void); */
typedef struct tagIntervalTimer
{
uint32_t dwTargetCnt;
uint32_t dwCounter;
pFun ptCallBack;
bool yIsRunning;
}IntervalTimer_t;
对于异步事件的管理,我们再定义一个枚举变量来代表不同的异步事件,见名知义,方便维护。
typedef enum
{
eIntervalDetection = 0,
exxxxStatusChk,
eDetectorTimeCnt, /*!< For count the time of object detector, no matter it was blocked or not. */
eI2CWriteFinish,
eObjectDetectorTimeCnt,
eCanRcvTaskTimeCount, /*!< To monitor the CAN receiver task and count the time. To change
*!< task step to Transfer process after a long time wait in receive
*!< task. */
exxxxAysnStop, /*!< To disable the control pin after stop puls output several minutes. */
#if DEBUG_TIMER_CLK_OUTPUT == 1U
eClockOutput,
#endif
eAysnMax
}AysnEvent_e;
定义一个静态全局的异步事件数组变量来存储异步事件的要素。
local IntervalTimer_t tEventTable[eAysnMax];
对此数组进行初始化要素值。
local void lvIF_TimerEventTableInit( void )
{
register uint32_t i;
for( i = 0; i < COUNTOF( tEventTable ); i++ )
{
tEventTable[i].dwTargetCnt = ITIMER0_TIME_1MS;
tEventTable[i].dwCounter = 0;
tEventTable[i].ptCallBack = NULL;
tEventTable[i].yIsRunning = FALSE;
}
}
如上所示,初始时,所有异步事件均配置为未生效状态,回调实现功能函数指针为空。
下面,对其它任务调用异步事件给出接口,接口分参数配置后启动和异步事件停止两种。
global bool gyIF_TimerEventSet( AysnEvent_e e, uint32_t dwTar, void(*p)(void), bool yRunFlag )
{
if( e < eAysnMax )
{
tEventTable[e].dwTargetCnt = dwTar;
tEventTable[e].ptCallBack = p;
tEventTable[e].yIsRunning = yRunFlag;
return TRUE;
}
else
{
return FALSE;
}
}
global void gvIF_TimerEventStop( AysnEvent_e e )
{
if( e < eAysnMax )
{
tEventTable[e].yIsRunning = FALSE;
}
}
根据上面所述,我们设计异步事件的实现,主要是利用计时器的定时溢出中断,因此, 中断函数就是具体对异步事件回调函数的功能实现,如下为代码实现:
global void TMR0_IRQHandler(void)
{
register uint32_t i;
if(TIMER_GetIntFlag(TIMER0) == 1U) /*!< Overflow interrupt of TIMER0. */
{
/* Clear Timer0 time-out interrupt flag */
TIMER_ClearIntFlag(TIMER0);
for( i = 0; i < eAysnMax; i++ )
{
/*!< If aysn-event was not started, so nothing need to do here, check next. */
if( !tEventTable[i].yIsRunning ) continue;
/*!< If the counter equal to the target counter , it means that time to implement the aysn-event. */
if( tEventTable[i].dwCounter >= tEventTable[i].dwTargetCnt )
{
tEventTable[i].dwCounter = 0;
if( tEventTable[i].ptCallBack != NULL )
{
(*tEventTable[i].ptCallBack)(); // Aysn-event implementation.
}
}
else
{
tEventTable[i].dwCounter++;
}
}
}
}
所有设置完成后,至此,异步事件的实现机制已经全部完成,只待具体的异步事件配置启动与功能实现后的停止。
接下来,我们以I2C的写应用来做一示例。
I2C在写数据流程中,在待写数据最后一字节写入后,需要等待状态码0xF8,收到后表示停止位。一次完整的I2C协议完成。 此时,就需要异步事件来等待状态码,并置完成标志位。
代码示例如下:
local void lvIF_I2C0RWFinishChk( void )
{
volatile struct tagI2CReadWritePara *pt;
pt = (volatile struct tagI2CReadWritePara*)&tTxd;
if( I2C_GET_STATUS(I2C0) == 0xF8 )
{
pt->lyFinishFlag = TRUE;
pt->lwTranscieveBytes = 0;
gyIF_TimerEventStop( eI2CWriteFinish );
}
}
local void lvIF_I2C0_Write( const uint8_t *pbData, uint16_t wLen, uint16_t wAddr )
{
uint8_t abBlock;
uint8_t abAddr;
abAddr = (uint8_t)(wAddr % EEPROM_BLOCK_SIZE);
abBlock = (uint8_t)(wAddr >> 8);
tTxd.lbCtrlBlock = EEPROM_CTRL_BLOCK_WRITE(abBlock);
tTxd.lbRomAddr = abAddr;
tTxd.lpbDataBuff = (uint8_t*)pbData;
tTxd.lwBufIndex = 0;
tTxd.lyFinishFlag = FALSE;
tTxd.lwTranscieveBytes = wLen;
fnI2C0Handler = lvIF_I2C_MasterTx;
/* I2C as master sends START signal */
I2C_SET_CONTROL_REG(I2C0, I2C_I2CON_STA);
/*!< If here doesn't use the aysn-event function , it will block the flow until the finish flag to be true. Like below.
uint32_t adwCnt = 0;
do{
adwCnt++;
}while( !tTxd.lyFinishFlag && (adwCnt < 1000u) );
*/
gyIF_TimerEventSet( eI2CWriteFinish, ITIMER0_TIME_10MS, lvIF_I2C0RWFinishChk, ENABLE );
}
如此应用,即可实现主线程不阻塞,异步事件中置位I2C写数据完成标志位。异步事件停止。对于保证任务线程的实时性即起到一定作用。
在使用中需要注意的事项如下:
1. 异步事件的实现函数,即中断中的回调函数,要尽可能简短,不可处理太多的事务,只处理一些标志位等简易操作,以此缩小中断处理时间;
2. 异步事件列表不宜过多,一般不超过10个,即 ‘eAysnMax’最大不要达到11的值。因为异步事件列表项过多,会造成异步事件计时误差加大,异步事件不按设定计时触发。
3. 异步事件列表项下各事件的设定计时相差越大,事件计时越准确,相差越小,造成的计时误差也越大。