您的位置:首页 > 其它

Z-STACK之cc2530串口驱动详解上

2013-01-25 13:30 246 查看
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协议解决方案的开发过程中有很重要的作用。如果能理清这个串口的工作原理,那么开发调试起来就会得心应手。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: