本文以TI的BLE1.3.2示例工程SimpleBLEPeripheral作关于UART的备忘笔记。
首先看hal_board_cfg.h,其中有关于底层驱动的默认配置:
/* Driver Configuration */
/* Set to TRUE enable H/W TIMER usage, FALSE disable it */
#ifndef HAL_TIMER
#define HAL_TIMER FALSE
#endif
/* Set to TRUE enable ADC usage, FALSE disable it */
#ifndef HAL_ADC
#define HAL_ADC TRUE
#endif
/* Set to TRUE enable DMA usage, FALSE disable it */
#ifndef HAL_DMA
#define HAL_DMA TRUE
#endif
/* Set to TRUE enable Flash access, FALSE disable it */
#ifndef HAL_FLASH
#define HAL_FLASH TRUE
#endif
/* Set to TRUE enable AES usage, FALSE disable it */
#ifndef HAL_AES
#define HAL_AES TRUE
#endif
#ifndef HAL_AES_DMA
#define HAL_AES_DMA TRUE
#endif
/* Set to TRUE enable LCD usage, FALSE disable it */
#ifndef HAL_LCD
#define HAL_LCD TRUE
#endif
/* Set to TRUE enable LED usage, FALSE disable it */
#ifndef HAL_LED
#define HAL_LED TRUE
#endif
#if (!defined BLINK_LEDS) && (HAL_LED == TRUE)
#define BLINK_LEDS
#endif
/* Set to TRUE enable KEY usage, FALSE disable it */
#ifndef HAL_KEY
#define HAL_KEY TRUE
#endif
/* Set to TRUE enable UART usage, FALSE disable it */
#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
#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
#ifndef HAL_UART_ISR
#define HAL_UART_ISR 0
#endif
#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
#ifndef HAL_UART_DMA
#define HAL_UART_DMA 0
#endif
#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
#if !defined HAL_UART_SPI
#define HAL_UART_SPI 0
#endif
#ifdef __cplusplus
}
#endif
#endif /* HAL_BOARD_CFG_H */
INT_HEAP_LEN=3072
HALNODEBUG
OSAL_CBTIMER_NUM_TASKS=1
HAL_AES_DMA=TRUE
HAL_DMA=TRUE
POWER_SAVING
xPLUS_BROADCASTER
HAL_LCD=TRUE
HAL_LED=FALSE
原来这个示例工程包含了按键的应用,从配置文件里面我们发现:
/* Set to TRUE enable KEY usage, FALSE disable it */
#ifndef HAL_KEY
#define HAL_KEY TRUE
#endif
下面着重探究下UART在DMA使能下的具体操作。
前面已经提到,定义HAL_UART=1后,配置文件默认使用UART的DMA方式,IRS中断方式是关闭的,另外可别同时在同一个Port(P0或P1)使能UART和SPI哦!
实际上,宏HAL_UART_DMA并不是两个值(TRUE or FALSE),而是有3个值(0,1,2)。当HAL_UART_DMA=1时,程序使用UART0的第一引脚配置(Alt 1)。
工程默认情况下是有低功耗的,在POWER_SAVING已定义的情况下,DMA_PM=1,使能CT脚的下降沿触发中断。CT、RT脚是当UART使用流控制时的引脚,CT脚为输入、RT脚为输出,连接方法是:
数据手册是这么说的:The RTS output is driven low when the receive register is empty and reception is enabled. Transmission of a byte does not occur before the CTS input goes low.
下面将举个例子来理解下cc2540的流控制,当A和B的Uart连接时,在双方没有数据交互时,A、B的RTS均输出低电平,因为他们的接收寄存器都为空。假设A比B先发送数据,那么B收到数据后它的RTS脚输出高(A的CTS为高),此时A停止发送数据,待B的DMA把数据从Uart的接受寄存器提取到上层后(某个内存空间,一般是接收队列),B的RTS重新变为低电平(接收寄存器变为空),此时因为A的CTS为低电平,A继续发送一个字节的数据...如此往复的过程,直至A把数据发送完成,反之亦然。此外,在流控制下,A和B同样是可以实现全双工通讯的。总结下,A发送数据,则和A的CTS(B的RTS)信号有关;B发送数据时则和B的CTS(A的RTS)有关。
如果我们要使用HAL的UART,则需要做如下设置:
首先是要设置相应的宏定义:
HAL_UART=TRUE
HAL_KEY=FALSE
然后,在SimpleBLEPeripheral.c文件包含hal_uart.h头文件,并在SimpleBLEPeripheral_ProcessEvent()函数的SBP_START_DEVICE_EVT事件中添加代码:
/*
* UART Port open
*/
{
halUARTCfg_t config;
config.baudRate = HAL_UART_BR_115200;
config.flowControl = TRUE;
config.callBackFunc = uartRxCB;
HalUARTOpen(HAL_UART_PORT_0, &config); /* 打开串口 */
}
其中uartRxCB需要自己定义一下,一般声明成静态函数。回调函数的格式要参照typedef void (*halUARTCBack_t) (uint8 port, uint8 event);
这样就成功打开了串口0,并且使用115200波特率,并且关闭流控制,并将自定义的回调函数uartRxCB()传递给HAL,当有关UART的事件发生后可以进行相应的处理。前面已经提到:UART默认下是使用DMA来对数据继续进行传输的,我们先来了解下UART在DMA方式下的工作原理(想了解CC2540 DMA的工作原理可以点击这里)。
查看HAL的UART关于DMA的配置代码,UART的TX 和 RX 的DMA通道配置如下:
UART TX通道
通道号 4
SRCADDR 存放待发送数据的首地址
DESTADDR 0x70C1(UART0的发送寄存器地址)
VLEN 0x00 - 使用LEN字段来规定需要传输的字节数
LEN 待发送数据的数量(这里是字节数)
WORDSIZE 设置以字节为单位传输
UART RX通道
通道号 3
SRCADDR 0x70C1(UART0的接收寄存器地址)
DESTADDR dmaCfg.rxBuf
VLEN 使用LEN字段来规定需要传输的字节数
LEN 最大传输的数据量,这里是128字节
WORDSIZE 以字(16 bit)为单位传输
PRIORITY 高优先级
我们先看一个重要的数据结构uartDMACfg_t:
typedef struct
{
uint16 rxBuf[HAL_UART_DMA_RX_MAX];
rxIdx_t rxHead;
rxIdx_t rxTail;
#if HAL_UART_DMA_IDLE
uint8 rxTick;
#endif
#if HAL_UART_TX_BY_ISR
uint8 txBuf[HAL_UART_DMA_TX_MAX];
volatile txIdx_t txHead;
txIdx_t txTail;
uint8 txMT;
#else
uint8 txBuf[2][HAL_UART_DMA_TX_MAX];
txIdx_t txIdx[2];
uint8 txMT; // Indication that at least one of two Tx buffers are free.
uint8 txTick; // ST ticks of delay to allow at least one byte-time at a given baud rate.
uint8 txTrig; // Flag indicating that Tx should be manually triggered after txTick expires.
// Although all of the txVars above are modified by the Tx-done ISR, only this one should need
// the special volatile consideration by the optimizer (critical sections protect the rest).
volatile uint8 txSel;
#endif
} uartDMACfg_t;
我们主要看当宏HAL_UART_TXBY_ISR=0时有效的数据结构成员,其中txBuf有两个:txBuf[0]、txBuf[1],txIdx[0]或txIdx[1]表示对应的Buffer中含有的待发送的数据字节数,txMT表示至少有一个buffer是空的,txTrig表示需要手动触发DMA,txSel这个比较关键,官方代码在操作这个成员的时候也非常小心,txSel指示当前可用的buffer,即未被作为DMA通道发送源的buffer。
当上层需要发送len个字节的数据时,调用api HalUARTWrite(uint8 port, uint8 *buf, uint16 len),最终执行如下代码:
/******************************************************************************
* @fn HalUARTWriteDMA
*
* @brief Write a buffer to the UART, enforcing an all or none policy if the requested length
* exceeds the space available.
*
* @param buf - pointer to the buffer that will be written, not freed
* len - length of
*
* @return length of the buffer that was sent
*****************************************************************************/
static uint16 HalUARTWriteDMA(uint8 *buf, uint16 len)
{
#if HAL_UART_TX_BY_ISR
// Enforce all or none.
if (HAL_UART_DMA_TX_AVAIL() < len)
{
return 0;
}
for (uint16 cnt = 0; cnt < len; cnt++)
{
dmaCfg.txBuf[dmaCfg.txTail] = *buf++;
dmaCfg.txMT = 0;
if (dmaCfg.txTail >= HAL_UART_DMA_TX_MAX-1)
{
dmaCfg.txTail = 0;
}
else
{
dmaCfg.txTail++;
}
// Keep re-enabling ISR as it might be keeping up with this loop due to other ints.
IEN2 |= UTXxIE;
}
#else
txIdx_t txIdx;
uint8 txSel;
halIntState_t his;
HAL_ENTER_CRITICAL_SECTION(his);
txSel = dmaCfg.txSel;
txIdx = dmaCfg.txIdx[txSel];
HAL_EXIT_CRITICAL_SECTION(his);
// Enforce all or none.
if ((len + txIdx) > HAL_UART_DMA_TX_MAX)
{
return 0;
}
(void)memcpy(&(dmaCfg.txBuf[txSel][txIdx]), buf, len);
HAL_ENTER_CRITICAL_SECTION(his);
/* If an ongoing DMA Tx finished while this buffer was being *appended*, then another DMA Tx
* will have already been started on this buffer, but it did not include the bytes just appended.
* Therefore these bytes have to be re-copied to the start of the new working buffer.
*/
if (txSel != dmaCfg.txSel)
{
HAL_EXIT_CRITICAL_SECTION(his);
txSel ^= 1;
(void)memcpy(&(dmaCfg.txBuf[txSel][0]), buf, len);
HAL_ENTER_CRITICAL_SECTION(his);
dmaCfg.txIdx[txSel] = len;
}
else
{
dmaCfg.txIdx[txSel] = txIdx + len;
}
// If there is no ongoing DMA Tx, then the channel must be armed here.
if (dmaCfg.txIdx[(txSel ^ 1)] == 0)
{
HAL_EXIT_CRITICAL_SECTION(his);
HalUARTArmTxDMA();
}
else
{
dmaCfg.txMT = FALSE;
HAL_EXIT_CRITICAL_SECTION(his);
}
#endif
return len;
}
正常情况下,代码会选择txSel指定的buffer进行数据装填,当DMA通道被ARM以后,txSel被按位取反,即指向另外一个buffer。下面我们来考虑另外一种情况:
假设buffer[0]正在进行DMA传输(此时txSel=1),buffer[1]已经被写了一些数据(此时txIdx[1]不为0),上层再次请求发送数据时将继续向buffer[1]写数据,但在写txIdx之前,若buffer[0]的DMA传输完成,根据中断代码(后面介绍)将会继续对buffer[1]的数据进行发送,此时上层刚刚写入的数据是无法被发送的,因此只能在buffer[0]重新写数据。见如下代码:
HAL_ENTER_CRITICAL_SECTION(his);
/* If an ongoing DMA Tx finished while this buffer was being *appended*, then another DMA Tx
* will have already been started on this buffer, but it did not include the bytes just appended.
* Therefore these bytes have to be re-copied to the start of the new working buffer.
*/
if (txSel != dmaCfg.txSel)
{
HAL_EXIT_CRITICAL_SECTION(his);
txSel ^= 1;
(void)memcpy(&(dmaCfg.txBuf[txSel][0]), buf, len);
HAL_ENTER_CRITICAL_SECTION(his);
dmaCfg.txIdx[txSel] = len;
}
else
{
dmaCfg.txIdx[txSel] = txIdx + len;
}
因为之前已经提过,当UART接收到数据,且DMA完成数据搬移后,DMA是不会触发中断干预CPU的,OSAL会在Hal_ProcessPoll()的HalUARTPoll()的HalUARTPollDMA()对UART的DMA接收BUFFER进行轮询处理,以下是代码:
/******************************************************************************
* @fn HalUARTPollDMA
*
* @brief Poll a USART module implemented by DMA, including the hybrid solution in which the Rx
* is driven by DMA but the Tx is driven by ISR.
*
* @param none
*
* @return none
*****************************************************************************/
static void HalUARTPollDMA(void)
{
uint8 evt = 0;
uint16 cnt;
#if DMA_PM
PxIEN &= ~DMA_RDYIn_BIT; // Clear to not race with DMA_RDY_IN ISR.
{
if (dmaRdyIsr || HAL_UART_DMA_RDY_IN() || HalUARTBusyDMA())
{
// Master may have timed-out the SRDY asserted state & may need a new edge.
#if HAL_UART_TX_BY_ISR
if (!HAL_UART_DMA_RDY_IN() && (dmaCfg.txHead != dmaCfg.txTail))
#else
if (!HAL_UART_DMA_RDY_IN() && ((dmaCfg.txIdx[0] != 0) || (dmaCfg.txIdx[1] != 0)))
#endif
{
HAL_UART_DMA_CLR_RDY_OUT();
}
dmaRdyIsr = 0;
if (dmaRdyDly == 0)
{
(void)osal_set_event(Hal_TaskID, HAL_PWRMGR_HOLD_EVENT);
}
if ((dmaRdyDly = ST0) == 0) // Reserve zero to signify that the delay expired.
{
dmaRdyDly = 0xFF;
}
HAL_UART_DMA_SET_RDY_OUT();
}
else if ((dmaRdyDly != 0) && (!DMA_PM_DLY || ((uint8)(ST0 - dmaRdyDly) > DMA_PM_DLY)))
{
dmaRdyDly = 0;
(void)osal_set_event(Hal_TaskID, HAL_PWRMGR_CONSERVE_EVENT);
}
}
PxIEN |= DMA_RDYIn_BIT;
#endif
#if !HAL_UART_TX_BY_ISR
HalUARTPollTxTrigDMA();
#endif
if (!HAL_UART_DMA_NEW_RX_BYTE(dmaCfg.rxHead))
{
if (HAL_UART_DMA_NEW_RX_BYTE(uartRxBug))
{
do {
HAL_UART_RX_IDX_T_INCR(dmaCfg.rxHead);
} while (!HAL_UART_DMA_NEW_RX_BYTE(dmaCfg.rxHead));
uartRxBug = dmaCfg.rxHead;
dmaCfg.rxTail = dmaCfg.rxHead;
}
HAL_UART_RX_IDX_T_INCR(uartRxBug);
}
cnt = HalUARTRxAvailDMA(); // Wait to call until after the above DMA Rx bug work-around.
#if HAL_UART_DMA_IDLE
if (dmaCfg.rxTick)
{
// Use the LSB of the sleep timer (ST0 must be read first anyway) to measure the Rx timeout.
if ((ST0 - dmaCfg.rxTick) > HAL_UART_DMA_IDLE)
{
dmaCfg.rxTick = 0;
evt = HAL_UART_RX_TIMEOUT;
}
}
else if (cnt != 0)
{
if ((dmaCfg.rxTick = ST0) == 0) // Zero signifies that the Rx timeout is not running.
{
dmaCfg.rxTick = 0xFF;
}
}
#else
if (cnt != 0)
{
evt = HAL_UART_RX_TIMEOUT;
}
#endif
if (cnt >= HAL_UART_DMA_FULL)
{
evt |= HAL_UART_RX_FULL;
}
else if (cnt >= HAL_UART_DMA_HIGH)
{
evt |= HAL_UART_RX_ABOUT_FULL;
if (!DMA_PM && (UxUCR & UCR_FLOW))
{
HAL_UART_DMA_CLR_RDY_OUT(); // Disable Rx flow.
}
}
if (dmaCfg.txMT)
{
dmaCfg.txMT = FALSE;
evt |= HAL_UART_TX_EMPTY;
}
if ((evt != 0) && (dmaCfg.uartCB != NULL))
{
dmaCfg.uartCB(HAL_UART_DMA-1, evt);
}
if (DMA_PM && (dmaRdyDly == 0) && !HalUARTBusyDMA())
{
HAL_UART_DMA_CLR_RDY_OUT();
}
}
读这段代码,头有点大了:
if (!HAL_UART_DMA_NEW_RX_BYTE(dmaCfg.rxHead))
{
if (HAL_UART_DMA_NEW_RX_BYTE(uartRxBug))
{
do {
HAL_UART_RX_IDX_T_INCR(dmaCfg.rxHead);
} while (!HAL_UART_DMA_NEW_RX_BYTE(dmaCfg.rxHead));
uartRxBug = dmaCfg.rxHead;
dmaCfg.rxTail = dmaCfg.rxHead;
}
HAL_UART_RX_IDX_T_INCR(uartRxBug);
}
这段完全没明白,HAL_UART_DMA_NEW_RX_BYTE的宏定义是
#define HAL_UART_DMA_NEW_RX_BYTE(IDX) ((uint8)DMA_PAD == HI_UINT16(dmaCfg.rxBuf[(IDX)]))
首先,我打开了UART,但是我没有在UART接收回调里做任何处理,之后我向设备发送2个相同的字节0xAA,在调试器里我看到:
联想到DMA,因为用于UART接收的DMA通道是一次传送16-bit的数据的,然后源地址是寄存器U0BUF的地址,它只有1个字节,因此自然而然U0BAUD被'不小心'传过去了,但实际上,虽然rxBuf数组的类型是uint16的,但其实它的每个元素的低字节用于存储接收到的数据,高地址为当前U0BAUD寄存器的值。其实,U0BAUD的值被放在高8位,就是表明这个位置存放了新接收的数据而且这个数据没有被应用层处理过。有了这个线索,后面的代码就好理解了。先看函数HalUARTRxAvailDMA():
/**************************************************************************************************
* @fn HalUARTRxAvailDMA()
*
* @brief Calculate Rx Buffer length - the number of bytes in the buffer.
*
* @param none
*
* @return length of current Rx Buffer
**************************************************************************************************/
static uint16 HalUARTRxAvailDMA(void)
{
// First, synchronize the Rx tail marker with where the DMA Rx engine is working.
rxIdx_t tail = dmaCfg.rxTail;
do
{
if (!HAL_UART_DMA_NEW_RX_BYTE(tail))
{
break;
}
HAL_UART_RX_IDX_T_INCR(tail);
} while (tail != dmaCfg.rxHead);
dmaCfg.rxTail = tail;
uint16 cnt = tail - dmaCfg.rxHead;
// If the DMA Rx may have overrun the circular queue, investigate further.
if ((cnt == 0) && HAL_UART_DMA_NEW_RX_BYTE(tail))
{
/* Ascertain whether this polling is racing with the DMA Rx which may have clocked in a byte
* since walking the tail. The Rx queue has wrapped only if the byte before the head is new.
*/
tail = dmaCfg.rxHead;
HAL_UART_RX_IDX_T_DECR(tail);
if (HAL_UART_DMA_NEW_RX_BYTE(tail))
{
if (HAL_UART_RX_FLUSH)
{
(void)memset(dmaCfg.rxBuf, (DMA_PAD ^ 0xFF), HAL_UART_DMA_RX_MAX*2);
uartRxBug = dmaCfg.rxHead;
dmaCfg.rxTail = dmaCfg.rxHead;
}
else
{
cnt = HAL_UART_DMA_RX_MAX;
}
}
else
{
cnt = 1;
}
}
else if (cnt > HAL_UART_DMA_RX_MAX) // If the tail has wrapped at the end of the Rx queue.
{
cnt += HAL_UART_DMA_RX_MAX;
}
return cnt;
}
do
{
if (!HAL_UART_DMA_NEW_RX_BYTE(tail))
{
break;
}
HAL_UART_RX_IDX_T_INCR(tail);
} while (tail != dmaCfg.rxHead);
dmaCfg.rxTail = tail;
uint16 cnt = tail - dmaCfg.rxHead;
我们发现此时rxTail的值已经从下标0~127轮询了一遍,此时if ((cnt == 0) && HAL_UART_DMA_NEW_RX_BYTE(tail))条件满足了,说明缓存已经满了,接下来的处理就是把dmaCfg.rxBuf全部设置为U0BAUD的值的取反,相当于BUFFEER清空,并设置dmaCfg.rxHead=dmaCfg.rxTail。