ZStack 串口应用笔记(一)
软件平台:ZStack-CC2530-2.5.1a\SerialApp
硬件平台:FB2530EB V1.0 2010-09-20
1.初始化串口DMA参数函数 HalUARTInitDMA(_hal_uart_dma.c)
串口外设端口选择P0.2-->RX,P0.3-->TX。
文档《CC253x_4x User's Guide.pdf》中关于DMA参数配置的说明。
DMA Configuration Setup
The DMA channel parameters such as address mode, transfer mode, and priority, described in the previous section, must be configured before a DMA channel can be armed and activated. The parameters are not configured directly through SFR registers, but instead they are written in a special DMA configuration-data structure in memory. Each DMA channel in use requires its own DMA configurationdata structure. The DMA configuration-data structure consists of eight bytes and is described in Section 8.6. A DMA configuration-data structure may reside at any location decided on by the user software, and the address location is passed to the DMA controller through a set of SFRs, DMAxCFGH:DMAxCFGL. Once a channel has been armed, the DMA controller reads the configuration data structure for that channel, given by the address in DMAxCFGH:DMAxCFGL. It is important to note that the method for specifying the start address for the DMA configuration data structure differs between DMA channel 0 and DMA channels 1 –4 as follows:
DMA0CFGH:DMA0CFGL gives the start address for the DMA channel 0 configuration data structure.
DMA1CFGH:DMA1CFGL gives the start address for the DMA channel 1 configuration data structure, followed by the channel 2–4 configuration-data structures.
halDMADesc_t dmaCh1234[4];//数组对应DMA通道1-4
TX使用DMA通道4,8位单字节传输,使能DMA中断。
RX使用DMA通道3,16位双字节传输,没有使用中断。其中RX DMA传输数据使用了一个技巧来判断是否收到新的数据。首先看一下文档《CC253x_4x User's Guide.pdf》中的Table 2-1. SFR Overview (continued)中U0DBUF地址为0xC1,U0BAUD地址为0xC2。

当RX的DMA源地址设置为U0DBUF且以16位双字节传输时,小端模式下读回来的数据为((uint16)U0BAUD寄存器值<<8)+U0DBUF寄存器值,而寄存器U0BAUD的值在设定好串口的波特率后就是一个确定值,当把接收缓存区初始化为一个非U0BAUD值时,只要串口有接收到数据,接收缓存区对应的高字节就会更新为U0BAUD的值,以此来判断否收到新的数据。接收缓存区数据被读取后,设定对应的高字节为一个非U0BAUD值,zstack设定为(U0BAUD^0xFF)。
串口接收数据缓存区为dmaCfg.rxBuf。
【源码剖析】
static void HalUARTInitDMA(void)
{
halDMADesc_t *ch;
P2DIR &= ~P2DIR_PRIPO;
P2DIR |= HAL_UART_PRIPO; //P0外设优先级设置
// 00:
//1st priority: USART 0
//2nd priority: USART 1
//3rd priority: Timer 1
#if (HAL_UART_DMA == 1)
PERCFG &= ~HAL_UART_PERCFG_BIT; // Set UART0 I/O to Alt. 1 location on P0.
#else
PERCFG |= HAL_UART_PERCFG_BIT; // Set UART1 I/O to Alt. 2 location on P1.
#endif
PxSEL |= HAL_UART_Px_RX_TX; // Enable Tx and Rx on P1.
ADCCFG &= ~HAL_UART_Px_RX_TX; // Make sure ADC doesnt use this.
UxCSR = CSR_MODE; // Mode is UART Mode.
UxUCR = UCR_FLUSH; // Flush it.
// Setup Tx by DMA. TX DMA通道参数配置
ch = HAL_DMA_GET_DESC1234( HAL_DMA_CH_TX );// TX使用DMA通道4
// The start address of the destination.
HAL_DMA_SET_DEST( ch, DMA_UDBUF );
// Using the length field to determine how many bytes to transfer.
HAL_DMA_SET_VLEN( ch, HAL_DMA_VLEN_USE_LEN );
// One byte is transferred each time.
HAL_DMA_SET_WORD_SIZE( ch, HAL_DMA_WORDSIZE_BYTE );
// The bytes are transferred 1-by-1 on Tx Complete trigger.
HAL_DMA_SET_TRIG_MODE( ch, HAL_DMA_TMODE_SINGLE );
HAL_DMA_SET_TRIG_SRC( ch, DMATRIG_TX );
// The source address is incremented by 1 byte after each transfer.
HAL_DMA_SET_SRC_INC( ch, HAL_DMA_SRCINC_1 );
// The destination address is constant - the Tx Data Buffer.
HAL_DMA_SET_DST_INC( ch, HAL_DMA_DSTINC_0 );
// The DMA Tx done is serviced by ISR in order to maintain full thruput.
HAL_DMA_SET_IRQ( ch, HAL_DMA_IRQMASK_ENABLE );
// Xfer all 8 bits of a byte xfer.
HAL_DMA_SET_M8( ch, HAL_DMA_M8_USE_8_BITS );
// DMA has highest priority for memory access.
HAL_DMA_SET_PRIORITY( ch, HAL_DMA_PRI_HIGH );
// Setup Rx by DMA. RX DMA通道参数配置
ch = HAL_DMA_GET_DESC1234( HAL_DMA_CH_RX );// TX使用DMA通道3
// The start address of the source.
HAL_DMA_SET_SOURCE( ch, DMA_UDBUF );
// Using the length field to determine how many bytes to transfer.
HAL_DMA_SET_VLEN( ch, HAL_DMA_VLEN_USE_LEN );
/* The trick is to cfg DMA to xfer 2 bytes for every 1 byte of Rx.
* The byte after the Rx Data Buffer is the Baud Cfg Register,
* which always has a known value. So init Rx buffer to inverse of that
* known value. DMA word xfer will flip the bytes, so every valid Rx byte
* in the Rx buffer will be preceded by a DMA_PAD char equal to the
* Baud Cfg Register value.
*/
HAL_DMA_SET_WORD_SIZE( ch, HAL_DMA_WORDSIZE_WORD );
// The bytes are transferred 1-by-1 on Rx Complete trigger.
HAL_DMA_SET_TRIG_MODE( ch, HAL_DMA_TMODE_SINGLE_REPEATED );
HAL_DMA_SET_TRIG_SRC( ch, DMATRIG_RX );
// The source address is constant - the Rx Data Buffer.
HAL_DMA_SET_SRC_INC( ch, HAL_DMA_SRCINC_0 );
// The destination address is incremented by 1 word after each transfer.
HAL_DMA_SET_DST_INC( ch, HAL_DMA_DSTINC_1 );
HAL_DMA_SET_DEST( ch, dmaCfg.rxBuf );
HAL_DMA_SET_LEN( ch, HAL_UART_DMA_RX_MAX );
// The DMA is to be polled and shall not issue an IRQ upon completion.
HAL_DMA_SET_IRQ( ch, HAL_DMA_IRQMASK_DISABLE );
// Xfer all 8 bits of a byte xfer.
HAL_DMA_SET_M8( ch, HAL_DMA_M8_USE_8_BITS );
// DMA has highest priority for memory access.
HAL_DMA_SET_PRIORITY( ch, HAL_DMA_PRI_HIGH );
}
2.DMA通道配参数地址设定 HalDmaInit(hal_dma.c)
【源码剖析】
void HalDmaInit( void )
{
HAL_DMA_SET_ADDR_DESC0( &dmaCh0 );//DMA通道0参数配置数据结构起始地址
HAL_DMA_SET_ADDR_DESC1234( dmaCh1234 );//DMA通道1234参数配置数据结构起始地址
#if (HAL_UART_DMA || \
((defined HAL_SPI) && (HAL_SPI == TRUE)) || \
((defined HAL_IRGEN) && (HAL_IRGEN == TRUE)))
DMAIE = 1;
#endif
}
3.打开串口 HalUARTOpenDMA(_hal_uart_dma.c)
设置串口回调函数、波特率、初始化接收数据缓存区、启动串口接收DMA传输
【源码剖析】
static void HalUARTOpenDMA(halUARTCfg_t *config)
{
dmaCfg.uartCB = config->callBackFunc;//串口回调函数处理HalUARTPollDMA中触发事件
// 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;
dmaCfg.txTick = 35; // (32768Hz / (9600bps / 10 bits))
// 10 bits include start and stop bits.
//发送超时计时器,保证发送数据需要的时间间隔
break;
case HAL_UART_BR_19200:
UxGCR = 9;
dmaCfg.txTick = 18;
break;
case HAL_UART_BR_38400:
UxGCR = 10;
dmaCfg.txTick = 9;
break;
case HAL_UART_BR_57600:
UxGCR = 10;
dmaCfg.txTick = 6;
break;
default:
// HAL_UART_BR_115200
UxGCR = 11;
dmaCfg.txTick = 3;
break;
}
// 8 bits/char; no parity; 1 stop bit; stop bit hi.
if (config->flowControl)
{
UxUCR = UCR_FLOW | UCR_STOP;
PxSEL |= HAL_UART_Px_CTS;
// DMA Rx is always on (self-resetting). So flow must be controlled by the S/W polling the Rx
// buffer level. Start by allowing flow.
PxOUT &= ~HAL_UART_Px_RTS;
PxDIR |= HAL_UART_Px_RTS;
}
else
{
UxUCR = UCR_STOP;
}
dmaCfg.rxBuf[0] = *(volatile uint8 *)DMA_UDBUF; // Clear the DMA Rx trigger.
HAL_DMA_CLEAR_IRQ(HAL_DMA_CH_RX);
HAL_DMA_ARM_CH(HAL_DMA_CH_RX); //启动RX DMA传输
osal_memset(dmaCfg.rxBuf, (DMA_PAD ^ 0xFF), HAL_UART_DMA_RX_MAX*2);//初始化接收缓存区为(U0BAUD ^ 0xFF)
UxCSR |= CSR_RE; //串口接收使能
// Initialize that TX DMA is not pending
dmaCfg.txDMAPending = FALSE;
dmaCfg.txShdwValid = FALSE;
}
4. uartDMACfg_t结构解析(_hal_uart_dma.c)
【源码剖析】
typedef struct
{
uint16 rxBuf[HAL_UART_DMA_RX_MAX];//接收数据缓存区
rxIdx_t rxHead; //接收数据缓存区头索引值
rxIdx_t rxTail; //接收数据缓存区尾索引值
uint8 rxTick; //接收数据超时计时器,用来判断串口接收数据流的结束
uint8 rxShdw; //影子计时器,保存接收到新数据时计时器Sleep Timer 的值
uint8 txBuf[2][HAL_UART_DMA_TX_MAX];//发送数据缓存区
txIdx_t txIdx[2]; //发送数据缓存区索引值
volatile uint8 txSel; //发送缓存区选择
uint8 txMT; //指示发送数据完成,用来触发事件
uint8 txTick; // 1-character time in 32kHz ticks according to baud rate,
// to be used in calculating time lapse since DMA ISR
// to allow delay margin before start firing DMA, so that
// DMA does not overwrite UART DBUF of previous packet
volatile uint8 txShdw; // Sleep Timer LSB shadow.
volatile uint8 txShdwValid; // TX shadow value is valid
uint8 txDMAPending; // UART TX DMA is pending数据待发送标识
halUARTCBack_t uartCB;
} uartDMACfg_t;
5.主循环串口DMA查询 HalUARTPollDMA(_hal_uart_dma.c)
主循环调用HalUARTPollDMA函数,处理串口接收数据缓存区和发送数据缓存区的状态更新,触发响应的事件供串口回调函数在应用层处理。
uartDMACfg_t结构中的rxHead和rxTail是两个独立的索引,rxHead用来供HalUARTReadDMA函数从数据缓存区读取数据;而rxTail是用来处理结束数据流的结束超时标识。
uartDMACfg_t结构中定义了2个发送数据缓存区是因为在DMA发送数据时,已经使用的发送缓存区在DMA数据传送完成前不能再往里面写数据,在启动DMA触发启动时,发送缓存区的数据长度等参数已经确定,DMA不需要MCU再进行干预会按规则自动传输完成。如果在个过程中改写使用的发送缓存区数据会造成数据的紊乱。于是zstack提供了1个备用的发送缓存区,以供DMA传输时应用程序往发送缓存区写数据,2个缓存区交替使用。
【源码剖析】
static void HalUARTPollDMA(void)
{
uint16 cnt = 0;
uint8 evt = 0;
//串口接收数据查询
if (HAL_UART_DMA_NEW_RX_BYTE(dmaCfg.rxHead))//接收数据缓存区Head索引处收到新数据
{
rxIdx_t tail = findTail(); //找出接收数据缓存区Tail索引
// If the DMA has transferred in more Rx bytes, reset the Rx idle timer.
if (dmaCfg.rxTail != tail)//DMA RX通道收到新的数据
{
dmaCfg.rxTail = tail; //更新接收数据缓存区Tail索引
// Re-sync the shadow on any 1st byte(s) received.
if (dmaCfg.rxTick == 0)
{
dmaCfg.rxShdw = ST0; //接收到第一个字节时同步更新影子计时器值
}
dmaCfg.rxTick = HAL_UART_DMA_IDLE;//接收数据流结束超时计时(about 1ms)
}
else if (dmaCfg.rxTick)
{
// Use the LSB of the sleep timer (ST0 must be read first anyway).
uint8 decr = ST0 - dmaCfg.rxShdw;//Sleep Timer计时器时间间隔
if (dmaCfg.rxTick > decr) //没有超时
{
dmaCfg.rxTick -= decr; //递减超时计时器,重新计时
dmaCfg.rxShdw = ST0;
}
else //接收数据流结束超时
{
dmaCfg.rxTick = 0;
}
}
cnt = HalUARTRxAvailDMA();//接收数据缓存区中的有效字节数
}
else
{
dmaCfg.rxTick = 0;
}
if (cnt >= HAL_UART_DMA_FULL)
{
evt = HAL_UART_RX_FULL; //触发接收缓存区FULL事件
}
else if (cnt >= HAL_UART_DMA_HIGH)
{
evt = HAL_UART_RX_ABOUT_FULL; //触发接收缓存区ABOUT_FULL事件
PxOUT |= HAL_UART_Px_RTS; // Disable Rx flow.
}
else if (cnt && !dmaCfg.rxTick)
{
evt = HAL_UART_RX_TIMEOUT;//触发接收数据流结束超时事件
}
//串口发送数据查询
if (dmaCfg.txMT)
{
dmaCfg.txMT = FALSE;
evt |= HAL_UART_TX_EMPTY;//发送数据完成时触发TX缓存区EMPTY事件
}
if (dmaCfg.txShdwValid) //串口发送影子计时器有效
{
uint8 decr = ST0;
decr -= dmaCfg.txShdw; //Sleep Timer时间间隔
if (decr > dmaCfg.txTick)
{
// No protection for txShdwValid is required
// because while the shadow was valid, DMA ISR cannot be triggered
// to cause concurrent access to this variable.
dmaCfg.txShdwValid = FALSE; //超时,串口发送影子计时器无效,保证DMA数据发送完成,
//防止发送未完成时,应用层再次触发DMA数据发送导致丢数据
}
}
if (dmaCfg.txDMAPending && !dmaCfg.txShdwValid)//DMA发送数据完成处于空闲状态,缓存区有数据待发送
{
// UART TX DMA is expected to be fired and enough time has lapsed since last DMA ISR
// to know that DBUF can be overwritten
halDMADesc_t *ch = HAL_DMA_GET_DESC1234(HAL_DMA_CH_TX);
halIntState_t intState;
// Clear the DMA pending flag
dmaCfg.txDMAPending = FALSE; //清除标识
HAL_DMA_SET_SOURCE(ch, dmaCfg.txBuf[dmaCfg.txSel]);
HAL_DMA_SET_LEN(ch, dmaCfg.txIdx[dmaCfg.txSel]);
dmaCfg.txSel ^= 1; //切换发送数据缓存区,方便应用程序调用HalUARTWriteDMA往串口发送缓存区写数据
HAL_ENTER_CRITICAL_SECTION(intState);
HAL_DMA_ARM_CH(HAL_DMA_CH_TX);//启动串口发送DMA数据传输
do
{
asm("NOP");
} while (!HAL_DMA_CH_ARMED(HAL_DMA_CH_TX));
HAL_DMA_CLEAR_IRQ(HAL_DMA_CH_TX);
HAL_DMA_MAN_TRIGGER(HAL_DMA_CH_TX);
HAL_EXIT_CRITICAL_SECTION(intState);
}
else
{
halIntState_t his;
HAL_ENTER_CRITICAL_SECTION(his);
if ((dmaCfg.txIdx[dmaCfg.txSel] != 0) && !HAL_DMA_CH_ARMED(HAL_DMA_CH_TX)
&& !HAL_DMA_CHECK_IRQ(HAL_DMA_CH_TX))
{
HAL_EXIT_CRITICAL_SECTION(his);
HalUARTIsrDMA();//中断响应延迟时且发送缓存区有数据待发送时强制响应中断
}
else
{
HAL_EXIT_CRITICAL_SECTION(his);
}
}
if (evt && (dmaCfg.uartCB != NULL))
{
dmaCfg.uartCB(HAL_UART_DMA-1, evt);//串口回调函数处理触发事件
}
}
6.DMA中断响应函数 HAL_ISR_FUNCTION( halDmaIsr, DMA_VECTOR )(hal_dma.c)
调用HalUARTIsrDMA函数
【源码剖析】
HAL_ISR_FUNCTION( halDmaIsr, DMA_VECTOR )
{
extern void HalUARTIsrDMA(void);
HAL_ENTER_ISR();
DMAIF = 0;
#if HAL_UART_DMA
if (HAL_DMA_CHECK_IRQ(HAL_DMA_CH_TX))
{
HalUARTIsrDMA();
}
#endif // HAL_UART_DMA
#if (defined HAL_SPI) && (HAL_SPI == TRUE)
if ( HAL_DMA_CHECK_IRQ( HAL_DMA_CH_RX ) )
{
HAL_DMA_CLEAR_IRQ( HAL_DMA_CH_RX );
HalSpiRxIsr();
}
if ( HAL_DMA_CHECK_IRQ( HAL_DMA_CH_TX ) )
{
HAL_DMA_CLEAR_IRQ( HAL_DMA_CH_TX );
HalSpiTxIsr();
}
#endif // (defined HAL_SPI) && (HAL_SPI == TRUE)
#if (defined HAL_IRGEN) && (HAL_IRGEN == TRUE)
if ( HAL_IRGEN == TRUE && HAL_DMA_CHECK_IRQ( HAL_IRGEN_DMA_CH ) )
{
HAL_DMA_CLEAR_IRQ( HAL_IRGEN_DMA_CH );
HalIrGenDmaIsr();
}
#endif // (defined HAL_IRGEN) && (HAL_IRGEN == TRUE)
CLEAR_SLEEP_MODE();
HAL_EXIT_ISR();
}
7.串口DMA响应处理函数 HalUARTIsrDMA(_hal_uart_dma.c)
【源码剖析】
void HalUARTIsrDMA(void)
{
HAL_DMA_CLEAR_IRQ(HAL_DMA_CH_TX);//清除中断标识
// Indicate that the other buffer is free now.
dmaCfg.txIdx[(dmaCfg.txSel ^ 1)] = 0;//发送缓存区索引清零,在HalUARTPollDMA中已做切换,所以这里仍需切换回来
dmaCfg.txMT = TRUE;
// Set TX shadow
dmaCfg.txShdw = ST0;
dmaCfg.txShdwValid = TRUE;
// If there is more Tx data ready to go, re-start the DMA immediately on it.
if (dmaCfg.txIdx[dmaCfg.txSel])//准备发送备用的缓存区数据
{
// UART TX DMA is expected to be fired
dmaCfg.txDMAPending = TRUE;
}
}
浙公网安备 33010602011771号