zigbee zstack 串口,按键,消息,定时器
协议栈中的串口接收流程
串口在底层使用的是中断,轮询还是DMA呢??答案从下面这个文件定义的宏可以得知
在C:\Texas Instruments\ZStack-CC2530-2.3.0-1.4.0\Components\hal\target\CC2530EB\hal_board_cfg.h
//默认开启DMA #ifndef HAL_DMA #define HAL_DMA TRUE #endif //只要定义了以下4个宏,就相当于需要使用串口 #ifndef HAL_UART #if (defined ZAPP_P1) || (defined ZAPP_P2) || (defined ZTOOL_P1) || (defined ZTOOL_P2) #define HAL_UART TRUE #else #define HAL_UART FALSE #endif #endif // HAL_UART和HAL_DMA都已经定义过了,所以协议栈里的串口默认使用的是DMA,而不是中断 #if HAL_UART // Always prefer to use DMA over ISR. #if HAL_DMA #ifndef HAL_UART_DMA #if (defined ZAPP_P1) || (defined ZTOOL_P1) #define HAL_UART_DMA 1 #elif (defined ZAPP_P2) || (defined ZTOOL_P2) #define HAL_UART_DMA 2 #else #define HAL_UART_DMA 1 #endif #endif #define HAL_UART_ISR 0 #else #ifndef HAL_UART_ISR #if (defined ZAPP_P1) || (defined ZTOOL_P1) #define HAL_UART_ISR 1 #elif (defined ZAPP_P2) || (defined ZTOOL_P2) #define HAL_UART_ISR 2 #else #define HAL_UART_ISR 1 #endif #endif #define HAL_UART_DMA 0 #endif // Used to set P2 priority - USART0 over USART1 if both are defined. #if ((HAL_UART_DMA == 1) || (HAL_UART_ISR == 1)) #define HAL_UART_PRIPO 0x00 #else #define HAL_UART_PRIPO 0x40 #endif #else//否则 #define HAL_UART_DMA 0 #define HAL_UART_ISR 0 #endif
在某个应用的初始化函数中顺序执行下面两个函数,比如在SampleApp_Init函数中,
MT_UartInit();
MT_UartRegisterTaskID(task_id);
MT_UartRegisterTaskID//用于给SampleApp注册串口,这样当串口在MT_UartProcessZToolData(mt_uart.c)发数据到上层时,会发到SampleApp。如果多个app都执行了MT_UartRegisterTaskID,则最后一个注册的有效,因为前面的被覆盖掉了。
void MT_UartRegisterTaskID( byte taskID )
{
App_TaskID = taskID;
}
比如,在osalInitTasks函数中
void osalInitTasks( void ) { uint8 taskID = 0; tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt); osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt)); macTaskInit( taskID++ ); nwk_init( taskID++ ); Hal_Init( taskID++ ); #if defined( MT_TASK ) MT_TaskInit( taskID++ );//会执行MT_UartRegisterTaskID函数 #endif APS_Init( taskID++ ); #if defined ( ZIGBEE_FRAGMENTATION ) APSF_Init( taskID++ ); #endif ZDApp_Init( taskID++ ); #if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT ) ZDNwkMgr_Init( taskID++ ); #endif SampleApp_Init( taskID );//会执行MT_UartRegisterTaskID函数,把MT_TaskInit函数注册的串口覆盖掉了。串口有数据发给上层时,SampleApp会收到消息。 }
MT_UartInit将会指定串口有数据到来时,调用哪个callback函数来处理。如果在option的preprocessor中定义了ZTOOL_P1即串口0(cc2530有2个串口,ZTOOL_P2或ZAPP_P2代表串口1),则调用的是MT_UartProcessZToolData,见下面
/*************************************************************************************************** * @fn MT_UartInit * * @brief Initialize MT with UART support * * @param None * * @return None ***************************************************************************************************/ void MT_UartInit () { halUARTCfg_t uartConfig; /* Initialize APP ID */ App_TaskID = 0; /* UART Configuration */ uartConfig.configured = TRUE; uartConfig.baudRate = MT_UART_DEFAULT_BAUDRATE; uartConfig.flowControl = MT_UART_DEFAULT_OVERFLOW; uartConfig.flowControlThreshold = MT_UART_DEFAULT_THRESHOLD; uartConfig.rx.maxBufSize = MT_UART_DEFAULT_MAX_RX_BUFF; uartConfig.tx.maxBufSize = MT_UART_DEFAULT_MAX_TX_BUFF; uartConfig.idleTimeout = MT_UART_DEFAULT_IDLE_TIMEOUT; uartConfig.intEnable = TRUE; #if defined (ZTOOL_P1) || defined (ZTOOL_P2) uartConfig.callBackFunc = MT_UartProcessZToolData; #elif defined (ZAPP_P1) || defined (ZAPP_P2) uartConfig.callBackFunc = MT_UartProcessZAppData; #else uartConfig.callBackFunc = NULL; #endif /* Start UART */ #if defined (MT_UART_DEFAULT_PORT)//默认串口,如果定义了ZTOOL_P1则是串口0,ZTOOL_P2则是串口1 HalUARTOpen (MT_UART_DEFAULT_PORT, &uartConfig); #else /* Silence IAR compiler warning */ (void)uartConfig; #endif /* Initialize for ZApp */ #if defined (ZAPP_P1) || defined (ZAPP_P2) /* Default max bytes that ZAPP can take */ MT_UartMaxZAppBufLen = 1; MT_UartZAppRxStatus = MT_UART_ZAPP_RX_READY; #endif }
MT_UartProcessZToolData用于处理串口接收缓冲区的数据,pMsg->msg的结构体的数据如下
/*************************************************************************************************** * @fn MT_UartProcessZToolData * * @brief | SOP | Data Length | CMD | Data | FCS | * | 1 | 1 | 2 | 0-Len | 1 | * * Parses the data and determine either is SPI or just simply serial data * then send the data to correct place (MT or APP) * * @param port - UART port * event - Event that causes the callback * * * @return None ***************************************************************************************************/ void MT_UartProcessZToolData ( uint8 port, uint8 event ) { uint8 ch; uint8 bytesInRxBuffer; (void)event; // Intentionally unreferenced parameter while (Hal_UART_RxBufLen(port)) { HalUARTRead (port, &ch, 1);//每次读一个字节 switch (state) { case SOP_STATE://0 if (ch == MT_UART_SOF)// 如果读到的字符时Start-of-frame即0xFE,则进入LEN_STAT状态,进行读LEN state = LEN_STATE; break; case LEN_STATE://1 LEN_Token = ch;//消息的长度 tempDataLen = 0; /* Allocate memory for the data */ pMsg = (mtOSALSerialData_t *)osal_msg_allocate( sizeof ( mtOSALSerialData_t ) + MT_RPC_FRAME_HDR_SZ + LEN_Token ); if (pMsg) { /* Fill up what we can */ pMsg->hdr.event = CMD_SERIAL_MSG; pMsg->msg = (uint8*)(pMsg+1); pMsg->msg[MT_RPC_POS_LEN] = LEN_Token;//pMsg->msg[0]=len state = CMD_STATE1;//读完长度,就进入CMD_STATE1状态,进行读命令 } else { state = SOP_STATE; return; } break; case CMD_STATE1: pMsg->msg[MT_RPC_POS_CMD0] = ch;//pMsg->msg[1]=命令低字节 state = CMD_STATE2; break; case CMD_STATE2: pMsg->msg[MT_RPC_POS_CMD1] = ch;//pMsg->msg[2]=命令高字节 /* If there is no data, skip to FCS state */ if (LEN_Token) { state = DATA_STATE;//读完命令,如果数据长度不为0,就进入DATA_STATE状态,进行读数据 } else { state = FCS_STATE;//否则进入FCS_STATE状态,进行校验 } break; case DATA_STATE: /* Fill in the buffer the first byte of the data */ pMsg->msg[MT_RPC_FRAME_HDR_SZ + tempDataLen++] = ch; /* Check number of bytes left in the Rx buffer */ bytesInRxBuffer = Hal_UART_RxBufLen(port); /* If the remain of the data is there, read them all, otherwise, just read enough,读数量较小的数据 */ if (bytesInRxBuffer <= LEN_Token - tempDataLen)//如果剩余数据<=len-已读长度,全部读出 { HalUARTRead (port, &pMsg->msg[MT_RPC_FRAME_HDR_SZ + tempDataLen], bytesInRxBuffer); tempDataLen += bytesInRxBuffer; } else//否则只读出(len-已读长度)的数据 { HalUARTRead (port, &pMsg->msg[MT_RPC_FRAME_HDR_SZ + tempDataLen], LEN_Token - tempDataLen); tempDataLen += (LEN_Token - tempDataLen); } /* If number of bytes read is equal to data length, time to move on to FCS */ if ( tempDataLen == LEN_Token ) state = FCS_STATE; break; case FCS_STATE: FSC_Token = ch; /* Make sure it's correct */ if ((MT_UartCalcFCS ((uint8*)&pMsg->msg[0], MT_RPC_FRAME_HDR_SZ + LEN_Token) == FSC_Token))//计算fcs { osal_msg_send( App_TaskID, (byte *)pMsg ); } else { /* deallocate the msg */ osal_msg_deallocate ( (uint8 *)pMsg ); } /* Reset the state, send or discard the buffers at this point */ state = SOP_STATE; break; default: break; } } }
所以如果PC发数据给zigbee串口,需要先发送FE...或者修改这个MT_UartProcessZToolData函数以适应自己的应用
协议栈中的串口发送流程
在某个app_init()中初始化和注册串口(如果此app不需要接收数据,可以不用注册)
直接使用如下代码发送数据即可
HalUARTWrite(0,"Hello World\n",12);
如果在opention中定义了MT_TASK,则在发送实际的数据之前会发送一个11字节的数据头(下行红色数据),在PC接收到如下形式的数据:
FE 06 41 80 01 02 00 02 03 00 C5 48 65 6C 6C 6F 20 57 6F 72 6C 64 0A
后面黑色字体的12个字节才是真正的数据
将MT_TASK取消定义则直接发送真实的数据
关于串口buffer的和应用层的串口帧大小的关系:
cc2530的串口buffer是1字节,可以看出串口帧的大小与串口buffer没啥关系,因为帧大小一般总是大于1字节的。
U0DBUF (0xC1) – USART 0 Receive/Transmit Data Buffer
这也说明串口在最低层发送的时候,是把上层给的数据一个字节一个字节的塞进buffer里面发出去的)
但是在协议栈中只要发送的串口帧大于128字节,就发不出去,这是因为单片机内存分给帧最大长度是128字节造成的。位于OnBoard.h。可修改之。
#define MT_UART_TX_BUFF_MAX 128
#define MT_UART_RX_BUFF_MAX 128
如果你想要发送大于128字节的帧而不愿意修改上面的宏,可以在while里面用HalUARTWrite一个字节一个字节的发,每发出一个字节执行一次HalUARTPoll。(相当于flush)。
协议栈中的按键流程
以其为例C:\Texas Instruments\ZStack-CC2530-2.3.0-1.4.0\Projects\zstack\Samples\SampleApp
1.在main函数中会执行InitBoard(),用于初始化led和按键
OnBoard.c
void InitBoard( uint8 level ) { if ( level == OB_COLD ) { // Interrupts off osal_int_disable( INTS_ALL ); // Turn all LEDs off HalLedSet( HAL_LED_ALL, HAL_LED_MODE_OFF ); // Check for Brown-Out reset ChkReset(); } else // !OB_COLD { /* Initialize Key stuff */ OnboardKeyIntEnable = HAL_KEY_INTERRUPT_ENABLE;//此处需要使能按键中断 HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback);//配置按键终端产生之后,触发OnBoard_KeyCallback函数 } }
配置函数HalKeyConfig,用于配置哪个按键(哪个端口)
回调函数OnBoard_KeyCallback,此函数执行OnBoard_SendKeys,将按键事件发给上层注册按键的应用
RegisterForKeys( SampleApp_TaskID );
如果按键已被注册,则后来注册的就会失败。
uint8 RegisterForKeys( uint8 task_id )
{
// Allow only the first task
if ( registeredKeysTaskID == NO_TASK_ID )
{
registeredKeysTaskID = task_id;
return ( true );
}
else
return ( false );
}
类似于注册串口,但串口刚好相反。
不管有没有使用中断,对于button1和joystick的按键,都会响应
如果按键没有启用中断,则会每隔100ms轮询一次按键状态,见下
如果注册了中断,则在按键中断发生时,会触发hal_key.c中的中断处理函数,注意kal_key.c中只注册了p2和p0口的中断处理函数,见下
**************************************************************************************************/
HAL_ISR_FUNCTION( halKeyPort2Isr, P2INT_VECTOR )
{
if (HAL_KEY_JOY_MOVE_PXIFG & HAL_KEY_JOY_MOVE_BIT)
{
halProcessKeyInterrupt();
}
/*
Clear the CPU interrupt flag for Port_2
PxIFG has to be cleared before PxIF
Notes: P2_1 and P2_2 are debug lines.
*/
HAL_KEY_JOY_MOVE_PXIFG = 0;
HAL_KEY_CPU_PORT_2_IF = 0;
}
HAL_ISR_FUNCTION( halKeyPort0Isr, P0INT_VECTOR )
{
if (HAL_KEY_SW_6_PXIFG & HAL_KEY_SW_6_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;
}
而halProcessKeyInterrupt 只是中断标志。没有对中断做具体处理,同时延时25ms设置一个HAL_KEY_EVENT事件,让Hal_TaskID去处理。延时的目的是去抖动。
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_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);//
}
}
在循环任务中有一个Hal_ProcessEvent (hal_driver.c) ,
if (events & HAL_KEY_EVENT)
{
#if (defined HAL_KEY) && (HAL_KEY == TRUE)
/* Check for keys */
HalKeyPoll();//读取时按个按键
/* if interrupt disabled, do next polling */
if (!Hal_KeyIntEnable)//如果没有使用中断,就重新延时100ms设置事件HAL_KEY_EVENT。从此处可以看出,如果按键没有启用中断,则会每隔100ms轮询一次按键状态
{
osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT, 100);
}
#endif // HAL_KEY
return events ^ HAL_KEY_EVENT;
}
其调用了HalKeyPoll(hal_key.c),在这个函数中
**************************************************************************************************/
void HalKeyPoll (void)
{
uint8 keys = 0;
//读取joystick的状态
if ((HAL_KEY_JOY_MOVE_PORT & HAL_KEY_JOY_MOVE_BIT)) /* Key is active HIGH */
{
keys = halGetJoyKeyInput();//返回joystick的哪个按键按下
}
/* If interrupts are not enabled, previous key status and current key status
* are compared to find out if a key has changed status.
*/
if (!Hal_KeyIntEnable)//如果没有启动中断
{
if (keys == halKeySavedKeys)
{
/* Exit - since no keys have changed */
return;
}
/* Store the current keys for comparation next time */
halKeySavedKeys = keys;
}
else
{
/* Key interrupt handled here */
}
//读取button1状态,
if (HAL_PUSH_BUTTON1())//返回button1是否按下
{
keys |= HAL_KEY_SW_6;
}
//如果按键按下设置了回调函数,就调用回调函数,如下
/* Invoke Callback if new keys were depressed */
if (keys && (pHalKeyProcessFunction))
{
(pHalKeyProcessFunction) (keys, HAL_KEY_STATE_NORMAL);
}
}
onboard.c中定义了回调函数
*********************************************************************/
void OnBoard_KeyCallback ( uint8 keys, uint8 state )
{
uint8 shift;
(void)state;
/* Get shift key status */
shift = ((keys & HAL_KEY_SW_6) ? true : false);
if ( OnBoard_SendKeys( keys, shift ) != ZSuccess )//如果上传消息失败(即应用层没有注册按键事件),就就地处理。
{
// Process SW1 here
if ( keys & HAL_KEY_SW_1 ) // Switch 1
{
}
// Process SW2 here
if ( keys & HAL_KEY_SW_2 ) // Switch 2
{
}
// Process SW3 here
if ( keys & HAL_KEY_SW_3 ) // Switch 3
{
}
// Process SW4 here
if ( keys & HAL_KEY_SW_4 ) // Switch 4
{
}
// Process SW5 here
if ( keys & HAL_KEY_SW_5 ) // Switch 5
{
}
// Process SW6 here
if ( keys & HAL_KEY_SW_6 ) // Switch 6
{
}
}
}
由上可知,
/* Switches (keys) */
#define HAL_KEY_SW_1 0x01 // Joystick up
#define HAL_KEY_SW_2 0x02 // Joystick right
#define HAL_KEY_SW_5 0x04 // Joystick center
#define HAL_KEY_SW_4 0x08 // Joystick left
#define HAL_KEY_SW_3 0x10 // Joystick down
#define HAL_KEY_SW_6 0x20 // Button S1 if available
协议栈中只对这6个按键由响应。而HalKeyConfig (hal_key.c) 函数中也只是对这6个按键进行了配置。
协议栈中的消息流程
3个不得不知道的结构体
typedef struct { void *next; uint16 len; uint8 dest_id; } osal_msg_hdr_t; typedef struct { uint8 event; uint8 status; } osal_event_hdr_t; typedef void * osal_msg_q_t;
//无线收到数据向上层发送的消息 typedef struct { osal_event_hdr_t hdr; /* OSAL Message header */ uint16 groupId; /* Message's group ID - 0 if not set */ uint16 clusterId; /* Message's cluster ID */ afAddrType_t srcAddr; /* Source Address, if endpoint is STUBAPS_INTER_PAN_EP, it's an InterPAN message */ uint16 macDestAddr; /* MAC header destination short address */ uint8 endPoint; /* destination endpoint */ uint8 wasBroadcast; /* TRUE if network destination was a broadcast address */ uint8 LinkQuality; /* The link quality of the received data frame */ uint8 correlation; /* The raw correlation value of the received data frame */ int8 rssi; /* The received RF power in units dBm */ uint8 SecurityUse; /* deprecated */ uint32 timestamp; /* receipt timestamp from MAC */ afMSGCommandFormat_t cmd; /* Application Data */ } afIncomingMSGPacket_t; //按键按下时向上层发送的消息格式 typedef struct { osal_event_hdr_t hdr; uint8 state; // shift uint8 keys; // keys } keyChange_t; //串口接到数据向上层发送的消息 typedef struct { osal_event_hdr_t hdr; uint8 *msg; } mtOSALSerialData_t;
定时器
cc2530-ZStack-CC2530-2.3.0-1.4.0
Hal_timer.c
NOTE: The following mapping is done between the logical timer
names defined in HAL_TIMER.H and the physical HW timer.
HAL_TIMER_0 --> HW Timer 3 (8-bits)
HAL_TIMER_2 --> HW Timer 4 (8-bits)
HAL_TIMER_3 --> HW Timer 1 (16-bits)
NOTE: The timer code assumes only one channel, CHANNEL 0, is used
for each timer. There is currently no support for other
channels.
HAL_ISR_FUNCTION( halTimer1Isr, T1_VECTOR )
{
halProcessTimer1 ();//是真实的硬件定时器1处理函数,默认没有开启
}
HAL_ISR_FUNCTION( halTimer3Isr, T3_VECTOR )
{
halProcessTimer3 ();//是真实的硬件定时器3处理函数,默认没有开启
}
HAL_ISR_FUNCTION( halTimer4Isr, T4_VECTOR )
{
halProcessTimer4 ();//是真实的硬件定时器4处理函数,默认没有开启
}
还有一个硬件定时器2,也是mac timer,已被协议栈占用
mac_mcu.c
/**************************************************************************************************
* @fn macMcuTimer2Isr
*
* @brief Interrupt service routine for timer2, the MAC timer.
*
* @param none
*
* @return none
**************************************************************************************************
*/
HAL_ISR_FUNCTION( macMcuTimer2Isr, T2_VECTOR )
osal_start_timerEx使用的是mac timer
但是osal_start_timerEx最快可以实现1ms的定时,而我测在mac timer的中断处理函数中使用P1_1=!P1_1测得的中断周期却大于100ms.?????
是使用的mac tiemr吗???
如下是说是使用的MAC backoff timer,这个timer又是啥玩意?
/*********************************************************************
* @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 )