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协议解决方案的开发过程中有很重要的作用。如果能理清这个串口的工作原理,那么开发调试起来就会得心应手。