首先看的当然是main()函数,不过这个函数不是今天的重点,里面有我添加的注释,先就一笔带过吧。
int main( void )
{
// Turn off interrupts
osal_int_disable( INTS_ALL );//关闭全局中断EA=0,初始化过程不响应任何中断
// Initialization for board related stuff such as LEDs
HAL_BOARD_INIT();//配置了时钟、LED、串口
// Make sure supply voltage is high enough to run
zmain_vdd_check();//检查电源电压
// Initialize stack memory
zmain_ram_init();//初始化堆内存
// Initialize board I/O /初始化板子用到的IO口
InitBoard( OB_COLD );
// Initialze HAL drivers
HalDriverInit();//初始化外设
// Initialize NV System //系统初始化
osal_nv_init( NULL );
// Initialize basic NV items//任务初始化
zgInit();
// Initialize the MAC
ZMacInit();
// Determine the extended address //确定长地址
zmain_ext_addr();
#ifndef NONWK
// Since the AF isn't a task, call it's initialization routine
afInit();
#endif
// Initialize the operating system
osal_init_system(); //系统初始化
// Allow interrupts
osal_int_enable( INTS_ALL );//使能中断
// Final board initialization //后期初始化
InitBoard( OB_READY ); //sd rest
// Display information about this device //显示设备信息
zmain_dev_info();
/* Display the device info on the LCD */
#ifdef LCD_SUPPORTED
zmain_lcd_init(); //显示信息
#endif
#ifdef WDT_IN_PM1
/* If WDT is used, this is a good place to enable it. */
WatchDogEnable( WDTIMX ); //使用看门狗
#endif
osal_start_system(); // No Return from here//正常情况下不返回
// Shouldn't get here
return ( 0 );
} // main()
其中含有osal的都是与操作系统相关的。这里主要提一下这些函数:
// Initialze HAL drivers
HalDriverInit();//初始化外设
片内外设与片外外设基本上在这个函数中初始化,像Timer、DMA、LCD等。该函数调用后设备即可使用。
// Initialize basic NV items
zgInit();
这个函数通过调用
// Initialize the items table
zgInitItems( setDefault );
初始化了zgItemTable[]//ZGlobal Item Table
我反正没搞懂这个数组干嘛用的,至少跟我们今天讨论的任务没有关系。我们讨论的任务在
// Initialize the operating system
osal_init_system();
函数中调用osalInitTasks()进行初始化,在该函数中为每一个任务分配了一个ID号,这个ID号在任务切换的时候将用到。该函数中的初始化函数的顺序与函数指针数组
const pTaskEventHandlerFn tasksArr[]
中对应的任务的顺序是一致的,这一点不难理解,就是为了保证任务与ID号的对应。该函数中还有这么两天语句值得注意:
tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);//申请空间,用于存放任务
osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt)); //用0初始化申请到的空间
tasksEvents是一个指针,C语言好的不用看它的定义都看得出来。任务切换的时候就是通过tasksEvents来查找需要处理的任务。tasksEvents指向的对象保存的是对应任务的掩码。
最后通过调用函数osal_start_system(); /* No Return from here*/启动操作系统,该函数正常情况下是不返回的。
#if !defined ( ZBIT ) && !defined ( UBIT )
for(;;) // Forever Loop
#endif
然后所有的任务都在这个for循环中被处理:
{
uint8 idx = 0;
osalTimeUpdate();//定时器任务更新
//轮询处理
Hal_ProcessPoll(); // This replaces MT_SerialPoll() and osal_check_timer().
do {
if (tasksEvents[idx]) // Task is highest priority that is ready.//查找优先级最高的任务
{
break;
}
} while (++idx < tasksCnt);//tasksCnt为总的任务数
if (idx < tasksCnt)//任务数检查
{
uint16 events;
halIntState_t intState;
HAL_ENTER_CRITICAL_SECTION(intState);//保存中断状态
events = tasksEvents[idx];//取出任务后立即清除
tasksEvents[idx] = 0; // Clear the Events for this task.
HAL_EXIT_CRITICAL_SECTION(intState);//恢复中断状态
events = (tasksArr[idx])( idx, events );//执行任务,返回值是未处理的事件的掩码
HAL_ENTER_CRITICAL_SECTION(intState);
tasksEvents[idx] |= events; // Add back unprocessed events to the current task.//添加未被处理的任务
HAL_EXIT_CRITICAL_SECTION(intState);
}
#if defined( POWER_SAVING )
else // Complete pass through all task events with no activity?
{
//当任务ID号出错时进入睡眠
osal_pwrmgr_powerconserve(); // Put the processor/system into sleep
}
#endif
}
当有任务需要处理时便调用函数osal_set_event()添加任务:
/*********************************************************************
* @fn osal_set_event
*
* @brief
*
* This function is called to set the event flags for a task. The
* event passed in is OR'd into the task's event variable.
**设置事件标志,这些事件保存到变量task
* @param uint8 task_id - receiving tasks ID
* @param uint8 event_flag - what event to set
*
* @return SUCCESS, INVALID_TASK
*/
uint8 osal_set_event( uint8 task_id, uint16 event_flag )
{
if ( task_id < tasksCnt )//正确的ID
{
halIntState_t intState;
HAL_ENTER_CRITICAL_SECTION(intState); // Hold off interrupts
tasksEvents[task_id] |= event_flag; // Stuff the event bit(s)/添加需要处理的事件的掩码
HAL_EXIT_CRITICAL_SECTION(intState); // Release interrupts
}
else
return ( INVALID_TASK );
return ( SUCCESS );
}
再看函数osal_start_system()对于tasksEvents就比较清楚了。
在所有调用osal_set_event()的函数中比较值得关注的是void osalTimerUpdate( uint16 updateTime )
先来看看函数体:
/*********************************************************************
* @fn osalTimerUpdate
*
* @brief Update the timer structures for a timer tick.
*
* @param none
*
* @return none
**更新定时器任务
*********************************************************************/
void osalTimerUpdate( uint16 updateTime )
{
halIntState_t intState;
osalTimerRec_t *srchTimer;
osalTimerRec_t *prevTimer;
HAL_ENTER_CRITICAL_SECTION( intState ); // Hold off interrupts./保存中断状态
// Update the system time/更新系统时间
osal_systemClock += updateTime;
HAL_EXIT_CRITICAL_SECTION( intState ); // Re-enable interrupts./恢复中断状态
// Look for open timer slot
if ( timerHead != NULL )
{
// Add it to the end of the timer list/添加到定时器列表
srchTimer = timerHead;
prevTimer = (void *)NULL;
// Look for open timer slot/遍历链表
while ( srchTimer )
{
osalTimerRec_t *freeTimer = NULL;
HAL_ENTER_CRITICAL_SECTION( intState ); // Hold off interrupts.
if (srchTimer->timeout <= updateTime)//超时检查
{
srchTimer->timeout = 0;
}
else
{
srchTimer->timeout = srchTimer->timeout - updateTime;
}
// When timeout or delete (event_flag == 0)/需要处理的事件
if ( srchTimer->timeout == 0 || srchTimer->event_flag == 0 )
{
// Take out of list
if ( prevTimer == NULL )
timerHead = srchTimer->next;
else
prevTimer->next = srchTimer->next;
// Setup to free memory/设置要被释放的资源
freeTimer = srchTimer;
// Next
srchTimer = srchTimer->next;
}
else
{
// Get next/下一个任务
prevTimer = srchTimer;
srchTimer = srchTimer->next;
}
HAL_EXIT_CRITICAL_SECTION( intState ); // Re-enable interrupts.
if ( freeTimer )//释放任务
{
if ( freeTimer->timeout == 0 )
{
osal_set_event( freeTimer->task_id, freeTimer->event_flag );//时间到了,设置事件标志以等待处理
}
osal_mem_free( freeTimer );//释放该定时器任务的资源
}
}
}
}
在我所使用的版本中该函数只被两个函数调用,分别是:
/*********************************************************************
* @fn osal_adjust_timers
*
* @brief Update the timer structures for elapsed ticks.
*
* @param none
*
* @return none
*********************************************************************/
void osal_adjust_timers( void )
{
uint16 eTime;
if ( timerHead != NULL )
{
// Compute elapsed time (msec)
eTime = TimerElapsed() / TICK_COUNT;
if ( eTime )
osalTimerUpdate( eTime );
}
}
/*********************************************************************
* FUNCTIONS
*********************************************************************/
/*********************************************************************
* @fn osalTimeUpdate
*
* @brief Uses the free running rollover count of the MAC backoff timer;
* this timer runs freely with a constant 320 usec interval. The
* count of 320-usec ticks is converted to msecs and used to update
* the OSAL clock and Timers by invoking osalClockUpdate() and
* osalTimerUpdate(). This function is intended to be invoked
* from the background, not interrupt level.
*
* @param None.
*
* @return None.
*/
void osalTimeUpdate( void )//定时器任务更新
{
uint16 tmp;
uint16 ticks320us;
uint16 elapsedMSec = 0;
// Get the free-running count of 320us timer ticks//设置时间片
tmp = macMcuPrecisionCount();//获取溢出值,该溢出值是一个累计的溢出值
if ( tmp != previousMacTimerTick )//相等则代表没有溢出
{
// Calculate the elapsed ticks of the free-running timer.//计算已经消耗的时间
ticks320us = tmp - previousMacTimerTick;
// Store the MAC Timer tick count for the next time through this function.
previousMacTimerTick = tmp;//保存当前时间
/* It is necessary to loop to convert the usecs to msecs in increments so as
* not to overflow the 16-bit variables.
**这是必要的循环转换usecs毫秒的增量,以免溢出16位变量。
*/
while ( ticks320us > MAXCALCTICKS )
{
ticks320us -= MAXCALCTICKS;
elapsedMSec += MAXCALCTICKS * 8 / 25;
remUsTicks += MAXCALCTICKS * 8 % 25;
}
// update converted number with remaining ticks from loop and the
// accumulated remainder from loop/更新转换从循环和循环积累的其余部分与其余蜱
tmp = (ticks320us * 8) + remUsTicks;
// Convert the 320 us ticks into milliseconds and a remainder
elapsedMSec += tmp / 25;
remUsTicks = tmp % 25;
// Update OSAL Clock and Timers//更新系统定时器
if ( elapsedMSec )
{
osalClockUpdate( elapsedMSec );//更新系统时间
osalTimerUpdate( elapsedMSec );//更新定时器任务,并设置需要处理的任务的掩码标志
}
}
}
都是跟定时器相关的。在函数osalTimerUpdate开头定义了两个指针:
osalTimerRec_t *srchTimer;
osalTimerRec_t *prevTimer;
其结构osalTimerRec_t定义为
typedef struct
{
void *next;
uint16 timeout;
uint16 event_flag;
uint8 task_id;
} osalTimerRec_t;
操作一个单向链表,链表头为timerHead,该链表中保存了所有需要定时执行的任务。这些任务在函数osalTimerRec_t * osalAddTimer( uint8 task_id, uint16 event_flag, uint16 timeout )中被创建。而该函数(只)被uint8 osal_start_timerEx( uint8 taskID, uint16 event_id, uint16 timeout_value )调用
uint8 osal_start_timerEx( uint8 taskID, uint16 event_id, uint16 timeout_value )
{
halIntState_t intState;
osalTimerRec_t *newTimer;
HAL_ENTER_CRITICAL_SECTION( intState ); // Hold off interrupts.//保存中断状态
// Add timer//添加定时器任务
newTimer = osalAddTimer( taskID, event_id , timeout_value );
HAL_EXIT_CRITICAL_SECTION( intState ); // Re-enable interrupts.
return ( (newTimer != NULL) ? SUCCESS : NO_TIMER_AVAIL );
}
其中event_id跟函数uint8 osal_set_event( uint8 task_id, uint16 event_flag )中event_flag的作用是一样的,都是最后被写入到tasksEvents中的掩码。通过函数osal_set_event()更改tasksEvents:
tasksEvents[task_id] |= event_flag; // Stuff the event bit(s)/添加需要处理的事件的掩码
函数osal_start_timerEx使用结构osalTimerRec_t临时保存newTimer->event_flag = event_flag;
最后通过在主循环中调用osalTimeUpdate()又调用osalTimerUpdate( elapsedMSec )
最后通过代码
if ( freeTimer )//释放任务
{
if ( freeTimer->timeout == 0 )
{
osal_set_event( freeTimer->task_id, freeTimer->event_flag );//时间到了,设置事件标志以等待处理
}
osal_mem_free( freeTimer );//释放该定时器任务的资源
}
看到没,最后还是调用函数osal_set_event()对tasksEvents进行更改。当一个任务处理完毕时在主循环中清除相应的掩码。
当然当一开始进入函数osal_start_system()中时,应该是没有任务的,这个时候任务是通过中断层层调用,最后还是调用函数osal_set_event()添加任务。在ZigBee协议栈中,中断函数是通过宏来实现的。这些宏在Hal_mcu.h文件中定义:
#define _PRAGMA(x) _Pragma(#x)
#define HAL_ISR_FUNC_DECLARATION(f,v) _PRAGMA(vector=v) __near_func __interrupt void f(void)
#define HAL_ISR_FUNC_PROTOTYPE(f,v) _PRAGMA(vector=v) __near_func __interrupt void f(void)
#define HAL_ISR_FUNCTION(f,v) HAL_ISR_FUNC_PROTOTYPE(f,v); HAL_ISR_FUNC_DECLARATION(f,v)
当需要编写一个中断函数实体时使用宏HAL_ISR_FUNCTION(f,v),如:
HAL_ISR_FUNCTION( halKeyPort0Isr, P0INT_VECTOR )//P0口中断服务函数
{
if ((HAL_KEY_SW_6_PXIFG & HAL_KEY_SW_6_BIT)||(HAL_KEY_SW_7_PXIFG & HAL_KEY_SW_7_BIT))
{
halProcessKeyInterrupt();
}
/*
Clear the CPU interrupt flag for Port_0
PxIFG has to be cleared before PxIF
*/
//清除中断标志
HAL_KEY_SW_6_PXIFG = 0;
HAL_KEY_CPU_PORT_0_IF = 0;
}
HAL_ISR_FUNCTION( halTimer1Isr, T1_VECTOR )//定时器1中断服务函数
{
halProcessTimer1 ();
}
HAL_ISR_FUNCTION( macMcuRfIsr, RF_VECTOR )//RF中断服务函数
只要对这些宏进行展开就得到了CC2530中断服务函数的形式。
下面就通过来看一下中断服务函数是怎么添加任务的。在P0口中断服务函数中调用了这么一个函数halProcessKeyInterrupt(),该函数的实现如下:
/**************************************************************************************************
* @fn halProcessKeyInterrupt
*
* @brief Checks to see if it's a valid key interrupt, saves interrupt driven key states for
* processing by HalKeyRead(), and debounces keys by scheduling HalKeyRead() 25ms later.
*
* @param
*
* @return
**************************************************************************************************/
void halProcessKeyInterrupt (void)
{
bool valid=FALSE;
//检查中断源
if (HAL_KEY_SW_6_PXIFG & HAL_KEY_SW_6_BIT) /* Interrupt Flag has been set */
{
HAL_KEY_SW_6_PXIFG = ~(HAL_KEY_SW_6_BIT); /* Clear Interrupt Flag */
valid = TRUE;
}
if (HAL_KEY_SW_7_PXIFG & HAL_KEY_SW_7_BIT) /* Interrupt Flag has been set */
{
HAL_KEY_SW_7_PXIFG = ~(HAL_KEY_SW_7_BIT); /* Clear Interrupt Flag */
valid = TRUE;
}
if (HAL_KEY_JOY_MOVE_PXIFG & HAL_KEY_JOY_MOVE_BIT) /* Interrupt Flag has been set */
{
HAL_KEY_JOY_MOVE_PXIFG = ~(HAL_KEY_JOY_MOVE_BIT); /* Clear Interrupt Flag */
valid = TRUE;
}
if (valid)//添加定时器任务
{
osal_start_timerEx (Hal_TaskID, HAL_KEY_EVENT, HAL_KEY_DEBOUNCE_VALUE);//使用定时器定时触发任务
}
}
不知道你是否还记得这个函数:osal_start_timerEx(),在这个函数中添加了一个定时任务,其中ID号就不用我多说了,HAL_KEY_EVENT是将要被添加到tasksEvents的任务的掩码,HAL_KEY_DEBOUNCE_VALUE则是用来进行消抖的一个时间值。用过单片机的都知道当按键按下时会产生抖动,而通常消抖的方法是延时。所以我们也可以知道在这里产生IO口中断的是按键。如果使用IO口中断来读其它设备则可以根据实际情况读或调用函数osal_set_event()对添加任务。当然并不是每个中断都必须添加对应的任务,如定时器中断。在中断服务函数中是否添加对应的任务应视具体情况而定。