Z-STACK中串口採用DMA和ISR兩種方式,本章主要講解ISR方式的串口驅動。在OASL操作系統輪詢時調用了Hal_ProcessPoll ()函數,在此函數中如果定義了HAL_UART=TRUE,則輪詢串口,看時候有數據要發送或有數據要接收。定位到HalUARTPoll()函數中,如果是採用ISR方式即HAL_UART_ISR爲1或2時,調用ISR串口輪詢函數HalUARTPollISR(),在這個函數中調用了串口的回調函數,這個過程等會兒講。
先來看看頭文件hal_uart.h,此頭文件中定義了typedef void (*halUARTCBack_t) (uint8 port, uint8 event);串口回調函數的函數指針,定義了串口緩衝區halUARTBufControl_t結構體以及針對串口配置的結構體halUARTCfg_t,halUARTIoctl_t結構體沒用到不用管。其中還有相關的宏定義,這個根據datasheet看。
接下來看_hal_uart_isr.c文件。HAL_UART_ISR_RX_AVAIL()這個宏定義是返回接收緩衝區中可接收數據的長度大小,HAL_UART_ISR_TX_AVAIL()這個宏定義返回發送緩衝區中空位置的長度大小。下面看看這個結構體
typedef struct
{
uint8 rxBuf[HAL_UART_ISR_RX_MAX];
#if HAL_UART_ISR_RX_MAX < 256
uint8 rxHead;
volatile uint8 rxTail;
#else
uint16 rxHead;
volatile uint16 rxTail;
#endif
uint8 rxTick;
uint8 rxShdw;
uint8 txBuf[HAL_UART_ISR_TX_MAX];
#if HAL_UART_ISR_TX_MAX < 256
volatile uint8 txHead;
uint8 txTail;
#else
volatile uint16 txHead;
uint16 txTail;
#endif
uint8 txMT;
halUARTCBack_t uartCB;
} uartISRCfg_t;
這個是串口ISR方式的發送和接收緩衝區結構體,裏面具體成員的意義弄懂了,後面幾個串口驅動函數就很好理解了。rxBuf指接收緩衝區,大小有不同的定義,rxHead指示接收緩衝區接收到的數據的首位置或首地址,rxTail指示接收緩衝區接收到的數據的末位置。rxTick這個成員表示串口經過rxtick時間之後開始發送數據。在輪詢串口的時候,即在HalUARTPollISR()函數中會檢查rxTick是否爲0,如果爲0,才調用串口的回調函數進行數據發送,如果不爲0,說明還沒有到發送數據的時間,得繼續等待直到rxTick爲0,這裏對rxTick的計時是採用了cc2530的睡眠定時器,等會兒在後面會講。rxShdw這個參數表示當前睡眠定時器的ST0,即睡眠定時器的count value的低八位。txBuf就是發送緩衝區,需要發送的數據都放在此緩衝區裏面,一旦允許發送中斷,就開始將發送緩衝區裏面的數據發送出去。txHead
、txTail和rxHead、rxTail含義相同,txMT是指發送緩衝區滿或者空的標誌位。uartCB爲串口的回調函數,具體內容由自己定義,在輪詢中被調用。static uartISRCfg_t isrCfg;聲明瞭一個靜態變量isrCfg,此變量只在本源文件中起作用,是針對於ISR方式的串口配置變量。
看看串口初始化函數HalUARTInitISR(),
static void HalUARTInitISR(void)
{
// Set P2 priority - USART0 over USART1 if both are defined.
P2DIR &= ~P2DIR_PRIPO;
P2DIR |= HAL_UART_PRIPO; //沒看懂這個
#if (HAL_UART_ISR == 1)
PERCFG &= ~HAL_UART_PERCFG_BIT; // Set UART0 I/O location to P0.
#else
PERCFG |= HAL_UART_PERCFG_BIT; // Set UART1 I/O location to P1.
#endif
PxSEL |= UxRX_TX; // Enable Tx and Rx peripheral functions on pins.
ADCCFG &= ~UxRX_TX; // Make sure ADC doesnt use this.
UxCSR = CSR_MODE; // Mode is UART Mode.
UxUCR = UCR_FLUSH; // Flush it.
}
初始化代碼中對P2的操作沒看懂,看了datasheet,串口跟P2一毛錢關係都沒有,而且P2DIR 是方向寄存器,如果誰知道求指教。接下來初始化的過程對照datasheet中串口部分,容易明白。在這裏說一下,我這個板子上P0位置用作串口,P1的串口位置被SPI複用,用來仿真調試了,所以HAL_UART_ISR就爲1,那初始化中的幾個宏定義就知道其值了。
看一下串口打開函數static void HalUARTOpenISR(halUARTCfg_t *config)
isrCfg.uartCB = config->callBackFunc;
// Only supporting subset of baudrate for code size - other is possible.
HAL_UART_ASSERT((config->baudRate == HAL_UART_BR_9600) ||
(config->baudRate == HAL_UART_BR_19200) ||
(config->baudRate == HAL_UART_BR_38400) ||
(config->baudRate == HAL_UART_BR_57600) ||
(config->baudRate == HAL_UART_BR_115200));
if (config->baudRate == HAL_UART_BR_57600 ||
config->baudRate == HAL_UART_BR_115200)
{
UxBAUD = 216;
}
else
{
UxBAUD = 59;
}
switch (config->baudRate)
{
case HAL_UART_BR_9600:
UxGCR = 8;
break;
case HAL_UART_BR_19200:
UxGCR = 9;
break;
case HAL_UART_BR_38400:
case HAL_UART_BR_57600:
UxGCR = 10;
break;
default:
UxGCR = 11;
break;
}
// 8 bits/char; no parity; 1 stop bit; stop bit hi.
if (config->flowControl)
{
UxUCR = UCR_FLOW | UCR_STOP;
PxSEL |= HAL_UART_Px_RTS | HAL_UART_Px_CTS;
}
else
{
UxUCR = UCR_STOP;
}
UxCSR |= CSR_RE;
URXxIE = 1;
UxDBUF = 0; // Prime the ISR pump.
將回調函數賦值給isrCfg的uartCB成員,然後根據設置的不同波特率對UxBAUD和UxGCR作相應的設置,最後使能串口接收以及打開串口接收中斷,將UxDBUF清零。
看下串口讀數據函數
static uint16 HalUARTReadISR(uint8 *buf, uint16 len)
{
uint16 cnt = 0;
while ((isrCfg.rxHead != isrCfg.rxTail) && (cnt < len))
{
*buf++ = isrCfg.rxBuf[isrCfg.rxHead++];
if (isrCfg.rxHead >= HAL_UART_ISR_RX_MAX)
{
isrCfg.rxHead = 0;
}
cnt++;
}
return cnt;
}
這個函數和串口寫數據函數HalUARTWriteISR是在回調函數中被調用,由用戶自定義操作。此函數只是將接收緩衝區中的數據賦值給buf然後相應rxHead增加len個長度,返回讀的數據長度。這個函數要對照串口接收終端函數理解
#if (HAL_UART_ISR == 1)
HAL_ISR_FUNCTION( halUart0RxIsr, URX0_VECTOR )
#else
HAL_ISR_FUNCTION( halUart1RxIsr, URX1_VECTOR )
#endif
{
uint8 tmp = UxDBUF;
isrCfg.rxBuf[isrCfg.rxTail] = tmp;
// Re-sync the shadow on any 1st byte received.
if (isrCfg.rxHead == isrCfg.rxTail)
{
isrCfg.rxShdw = ST0;
}
if (++isrCfg.rxTail >= HAL_UART_ISR_RX_MAX)
{
isrCfg.rxTail = 0;
}
isrCfg.rxTick = HAL_UART_ISR_IDLE;
}
在接收中斷函數中,將接收到的一字節數據填入rxBuf中,注意在接收數據時是將數據填入rxBuf,然後將rxTail加1,即在緩衝區讀數據時是在緩衝區首位置開始讀,在串口接收數據時是將數據放在rxTail即緩衝區末位置。看最後一句代碼,將HAL_UART_ISR_IDLE即198賦值給rxTick,這個值爲系統輪詢串口時是否需要操作串口的等待時間,其實可以計算一下,採用的是32.768kHz的外部時鐘,用198除以32.768約等於6ms,即每次等待時間爲6ms,如果每次系統經過輪詢之後檢查6ms是否用完,是則調用回調函數。
if (isrCfg.rxHead == isrCfg.rxTail)
{
isrCfg.rxShdw = ST0;
}
這句代碼的意思是當緩衝區清空的時候,即接收的數據全部被讀出來了之後,重新將rxShdw賦值ST0,以計算下一次輪詢串口需要讀取數據的時間。
接下來看串口寫數據函數
static uint16 HalUARTWriteISR(uint8 *buf, uint16 len)
{
uint16 cnt;
// Accept "all-or-none" on write request.
if (HAL_UART_ISR_TX_AVAIL() < len)
{
return 0;
}
for (cnt = 0; cnt < len; cnt++)
{
isrCfg.txBuf[isrCfg.txTail] = *buf++;
isrCfg.txMT = 0;
if (isrCfg.txTail >= HAL_UART_ISR_TX_MAX-1)
{
isrCfg.txTail = 0;
}
else
{
isrCfg.txTail++;
}
// Keep re-enabling ISR as it might be keeping up with this loop due to other ints.
IEN2 |= UTXxIE;
}
return cnt;
}
先檢查發送緩衝區是否有可寫的len長度的位置。然後將buf裏面的數據複製給txBuf,同時將txTail增加,這正好跟read相反,即往緩衝區裏面填充數據是在末尾填充,當發送的時候是在發送緩衝區裏面取數據,即在緩衝區開頭取數據。每次填充一個數據時就開一次發送中斷,這樣時數據及時發送出去。
看下串口發送中斷函數
#if (HAL_UART_ISR == 1)
HAL_ISR_FUNCTION( halUart0TxIsr, UTX0_VECTOR )
#else
HAL_ISR_FUNCTION( halUart1TxIsr, UTX1_VECTOR )
#endif
{
if (isrCfg.txHead == isrCfg.txTail)
{
IEN2 &= ~UTXxIE;
isrCfg.txMT = 1;
}
else
{
UTXxIF = 0;
UxDBUF = isrCfg.txBuf[isrCfg.txHead++];
if (isrCfg.txHead >= HAL_UART_ISR_TX_MAX)
{
isrCfg.txHead = 0;
}
}
}
當發送緩衝區中沒有數據要發送的時候,即txHead等於txTail,此時將禁止發送中斷,將txMT標誌位置1,表示發送緩衝區爲空,否則(有數據要發送)將串口發送中斷標誌清零,然後往UxDBUF 寫數據。
最後很重要的一個函數即串口輪詢函數
static void HalUARTPollISR(void)
{
if (isrCfg.uartCB != NULL)
{
uint16 cnt = HAL_UART_ISR_RX_AVAIL();
uint8 evt = 0;
if (isrCfg.rxTick)
{
// Use the LSB of the sleep timer (ST0 must be read first anyway).
uint8 decr = ST0 - isrCfg.rxShdw;
if (isrCfg.rxTick > decr)
{
isrCfg.rxTick -= decr;
}
else
{
isrCfg.rxTick = 0;
}
}
isrCfg.rxShdw = ST0;
if (cnt >= HAL_UART_ISR_RX_MAX-1)
{
evt = HAL_UART_RX_FULL;
}
else if (cnt >= HAL_UART_ISR_HIGH)
{
evt = HAL_UART_RX_ABOUT_FULL;
}
else if (cnt && !isrCfg.rxTick)
{
evt = HAL_UART_RX_TIMEOUT;
}
if (isrCfg.txMT)
{
isrCfg.txMT = 0;
evt |= HAL_UART_TX_EMPTY;
}
if (evt)
{
isrCfg.uartCB(HAL_UART_ISR-1, evt);
}
}
}
這個函數在上面說了是在系統每次循環的時候被調用。如果rxTick不爲0,則
uint8 decr = ST0 - isrCfg.rxShdw;
if (isrCfg.rxTick > decr)
{
isrCfg.rxTick -= decr;
}
else
{
isrCfg.rxTick = 0;
}
ST0表示睡眠定時器當前的計數值,而rxShdw記錄的是上次串口接收時候的計數值,這樣decr就表示輪詢了一次之後經過的時間,如果此時間比rxTick大就將rxTick清零,表示時間到了需要接收數據,否則就將rxTick值減去decr,還要繼續等待rxTick-decr這麼長時間。接下來isrCfg.rxShdw = ST0;記錄當前的睡眠定時器的值以便下一次輪詢時候的比較。接下來便是串口事件
if (cnt && !isrCfg.rxTick)
{
evt = HAL_UART_RX_TIMEOUT;
}
看這行代碼,當cnt不爲0且rxTick爲0的時候則標誌串口接收超時事件,
最後
if (evt)
{
isrCfg.uartCB(HAL_UART_ISR-1, evt);
}
如果有串口事件則調用回調函數,對緩衝區中的數據進行處理。
以上便是Z-STCAK中串口驅動的ISR方式,如果有理解不到位的地方還希望指示。串口在ZigBee協議解決方案的開發過程中有很重要的作用。如果能理清這個串口的工作原理,那麼開發調試起來就會得心應手。