嵌入式裸机系统开发中利用TIMER实现异步事件机制介绍

       在嵌入式产品开发中,从业务逻辑分拆进行面向过程的软件编码中,会有一种和多种任务来对业务逻辑进行实现,从而达到宏观层面的产品功能。在软件层面中,任务的执行过程中会涉及到对硬件的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. 异步事件列表项下各事件的设定计时相差越大,事件计时越准确,相差越小,造成的计时误差也越大。

嵌入式裸机系统开发中利用TIMER实现异步事件机制介绍_第1张图片

嵌入式裸机系统开发中利用TIMER实现异步事件机制介绍_第2张图片

嵌入式裸机系统开发中利用TIMER实现异步事件机制介绍_第3张图片

你可能感兴趣的:(嵌入式开发-定时间应用)