在http://blog.csdn.net/crystal736/article/details/8541443中已经讲了Z-STACK中串口驱动的ISR方式,本文介绍串口驱动的另一种方式DMA,实际上Z-STACK中就是采用的这种方式,看hal_board_cfg.h文件中如下代码
#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
#define HAL_UART_ISR 0
#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
#define HAL_UART_DMA 0
#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
可以看出z-stack中串口驱动使用的是DMA方式。上篇文章讲了DMA具体工作方式及原理,这里就说说串口是如何使用DMA的。先看驱动源文件_hal_uart_dma.c
先看一下串口DMA方式中很重要的一个结构体uartDMACfg_t
typedef struct
{
uint16 rxBuf[HAL_UART_DMA_RX_MAX];
#if HAL_UART_DMA_RX_MAX < 256
uint8 rxHead;
uint8 rxTail;
#else
uint16 rxHead;
uint16 rxTail;
#endif
uint8 rxTick;
uint8 rxShdw;
uint8 txBuf[2][HAL_UART_DMA_TX_MAX];
#if HAL_UART_DMA_TX_MAX < 256
uint8 txIdx[2];
#else
uint16 txIdx[2];
#endif
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;
跟uartISRCfg_t有很多相同之处,如rxTick、rxShdw。这里txBuf定义成二维数组,以增加缓冲区长度容纳更多的数据,其他的成员在讲具体函数时会提到。先看DMA串口驱动的初始化函数
static void HalUARTInitDMA(void)
{
halDMADesc_t *ch;
P2DIR &= ~P2DIR_PRIPO;
P2DIR |= HAL_UART_PRIPO;
#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 |= 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.
// Setup Tx by DMA.
ch = HAL_DMA_GET_DESC1234( HAL_DMA_CH_TX );
// 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.
ch = HAL_DMA_GET_DESC1234( HAL_DMA_CH_RX );
// 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 );
}
在开头的这两句 P2DIR &= ~P2DIR_PRIPO; P2DIR |= HAL_UART_PRIPO; 在串口驱动详解上中没弄明白,这次看了下datasheet,发现P2DIR的6、7位是Port 0 peripheral priority control. These bits determine the order of priority in the case when PERCFG assigns several peripherals to the same pins.即控制P0口外设功能的优先级。将P2DIR高两位设为00,则1st priority: USART ,02nd priority: USART 1,3rd priority: Timer 1,P0外设功能有USART0/1,TIMER1。
然后设定UART的位置为P0或P1,使能外设功能,设定USART的mode为UART,清除串口内容。
然后就是设置TX和RX的DMA配置描述符,其通道号分别为4、3。这个设置过程跟FLASH控制器的DMA配置描述符的设置过程相似,只是设置参数不同。从代码中看出RX和TX的DMA配置描述符的不同,TX采用逐个字节的传输,而RX采用逐个字(即两个字节)地传输,TX的触发采用HAL_DMA_TMODE_SINGLE,即在DMA触发之后开始传输,传输完了就结束DMA传输,RX的触发采用HAL_DMA_TMODE_SINGLE_REPEATED,即在DMA触发之后开始传输,传输完之后rearm RX的DMA,则DMA重新开始传输下一个数据。另外就是TX使能了DMA中断,RX禁用了DMA中断。
再看一下HalUARTOpenDMA做了哪些工作
static void HalUARTOpenDMA(halUARTCfg_t *config)
{
dmaCfg.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;
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);
osal_memset(dmaCfg.rxBuf, (DMA_PAD ^ 0xFF), HAL_UART_DMA_RX_MAX*2);
UxCSR |= CSR_RE;
UxDBUF = 0; // Prime the DMA-ISR pump.
// Initialize that TX DMA is not pending
dmaCfg.txDMAPending = FALSE;
dmaCfg.txShdwValid = FALSE;
}
首先根据配置设定UART的波特率及txTick,如果采用流控制,则要设置UxUCR、PxSEL、PxOUT、PxDIR。一般都没有采用硬件流控制。
dmaCfg.rxBuf[0] = *(volatile uint8 *)DMA_UDBUF;这行代码读取uart的缓冲寄存器,其作用是清除Rx的DMA触发。
然后启动Rx的DMA传输,注意到初始化函数中有这段注释:
/* 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.
*/
所以将DMA_PAD ^ 0xFF填充整个rxBuf,即osal_memset(dmaCfg.rxBuf, (DMA_PAD ^ 0xFF), HAL_UART_DMA_RX_MAX*2);
接着使能串口接收,将UxDBUF置0,Prime the DMA-ISR pump,这一句不知道具体作用是什么。
最后将dmaCfg的txDMAPending和txShdwValid置为FALSE。
看看HalUARTReadDMA这个函数,它从UART中读取一些数据到Buf中,
static uint16 HalUARTReadDMA(uint8 *buf, uint16 len)
{
uint16 cnt;
for (cnt = 0; cnt < len; cnt++)
{
if (!HAL_UART_DMA_NEW_RX_BYTE(dmaCfg.rxHead))
{
break;
}
*buf++ = HAL_UART_DMA_GET_RX_BYTE(dmaCfg.rxHead);
HAL_UART_DMA_CLR_RX_BYTE(dmaCfg.rxHead);
if (++(dmaCfg.rxHead) >= HAL_UART_DMA_RX_MAX)
{
dmaCfg.rxHead = 0;
}
}
PxOUT &= ~HAL_UART_Px_RTS; // Re-enable the flow on any read.
return cnt;
}
这个函数里面主要理解几个宏定义。读取数据的时候是从rxHead位置开始读取,每读一个数据rxHead就加1。串口在每次接收到一个数据时,是将其放在rxBuf每个16位字的前八位,后八位为DMA_PAD,所以当该数据的高八位等于DMA_PAD时表明rxBuf中有数据了。
#define HAL_UART_DMA_NEW_RX_BYTE(IDX) (DMA_PAD == HI_UINT16(dmaCfg.rxBuf[(IDX)]))
根据Rx的DMA配置知道,串口每次接收一个数据完成时便触发DMA传输,此时将U0DBUF中的8位数据传送到rxBuf中
,rxBuf中的数据位置是逐字增加的,假如串口接收到10个数据,则rxBuf中前10个位置便是这10个数据,只是每个数据后面有填充位为DMA_PAD 。这里有一个不明白的地方:在初始化的时候rxBuf中填充的是DMA_PAD^0xFF,为什么将HI_UINT16(dmaCfg.rxBuf[(IDX)与DMA_PAD比较,且两者相同时就说明rxBuf中有数据了。然后就是每次串口接收数据将其发送到rxBuf中某个内存的低地址还是高地址,this is a problem!从宏定义上来看,串口中的数据和DMA_PAD^0xFF的值组成16位数,且串口中的数据位低八位,DMA_PAD^0xFF为高八位。在数据后面加一个PAD位有什么作用呢?Thinking...
以上问题等想通了再tell you。
Come back!HalUARTReadDMA函数返回读取到数据的长度。
接下来看看这个函数HalUARTWriteDMA,其功能是将buf中的数据写进txBuf,写的数据长度为len
static uint16 HalUARTWriteDMA(uint8 *buf, uint16 len)
{
uint16 cnt;
halIntState_t his;
uint8 txIdx, txSel;
// Enforce all or none.
if ((len + dmaCfg.txIdx[dmaCfg.txSel]) > HAL_UART_DMA_TX_MAX)
{
return 0;
}
HAL_ENTER_CRITICAL_SECTION(his);
txSel = dmaCfg.txSel;
txIdx = dmaCfg.txIdx[txSel];
HAL_EXIT_CRITICAL_SECTION(his);
for (cnt = 0; cnt < len; cnt++)
{
dmaCfg.txBuf[txSel][txIdx++] = buf[cnt];
}
HAL_ENTER_CRITICAL_SECTION(his);
if (txSel != dmaCfg.txSel)
{
HAL_EXIT_CRITICAL_SECTION(his);
txSel = dmaCfg.txSel;
txIdx = dmaCfg.txIdx[txSel];
for (cnt = 0; cnt < len; cnt++)
{
dmaCfg.txBuf[txSel][txIdx++] = buf[cnt];
}
HAL_ENTER_CRITICAL_SECTION(his);
}
dmaCfg.txIdx[txSel] = txIdx;
if (dmaCfg.txIdx[(txSel ^ 1)] == 0)
{
// TX DMA is expected to be fired
dmaCfg.txDMAPending = TRUE;
}
HAL_EXIT_CRITICAL_SECTION(his);
return cnt;
}
下面分析一下函数的具体代码实现。先说一下uartDMACfg_t中txSel是指txBuf二维数组中的哪一维,0或者1,例如txBuf中第一维数据满了,第二维数据未满,则其为1,否则为0;txIdx[2]指示txBuf中某一维数组的数据的具体位置(应该是指示txBuf中数据的末位置),例如txBuf中第一维数据满了,则txIdx[0]则为HAL_UART_DMA_TX_MAX,txIdx[1]为第二维数组中数据的长度,否则txIdx[0]为当前数据的长度,txIdx[1]为0;
if ((len + dmaCfg.txIdx[dmaCfg.txSel]) > HAL_UART_DMA_TX_MAX) { return 0; }
如果要写入的数据加上rxBuf中的数据长度大于HAL_UART_DMA_TX_MAX,则直接返回0,此时不能写入数据
txSel = dmaCfg.txSel;
txIdx = dmaCfg.txIdx[txSel];
局部变量txSel和txIdx分别暂存dmaCfg中的txSel和txIdx。
for (cnt = 0; cnt < len; cnt++)
{
dmaCfg.txBuf[txSel][txIdx++] = buf[cnt];
}
将buf中的数据复制到txBuf中。if (txSel != dmaCfg.txSel)后面的一段代码没看懂,既然前面将dmaCfg.txSel赋值给了txSel了,那么他们必然相等,不用比较,不过还有一种可能就是在这个之前,发生了DMA中断,但是在DMA中断程序中也没看到dmaCfg.txSel的变更,这个令人相当费解。Let's pass it!
if (dmaCfg.txIdx[(txSel ^ 1)] == 0)
{
// TX DMA is expected to be fired
dmaCfg.txDMAPending = TRUE;
}
接下来判断发送的另一个缓冲区是否为空(其实将发送缓冲区看做是二维数组,也可以看成是两个长度相同的一维数组,即两个缓冲区)。如果另一个缓冲区没有数据,则将txDMAPending 置为TRUE,在下一次调用串口轮询函数HalUARTPollDMA时,将txBuf中的数据发送出去。
接下来看看HalUARTPollDMA这个非常重要的函数。
static void HalUARTPollDMA(void)
{
uint16 cnt = 0;
uint8 evt = 0;
if (HAL_UART_DMA_NEW_RX_BYTE(dmaCfg.rxHead))
{
uint16 tail = findTail();
// If the DMA has transferred in more Rx bytes, reset the Rx idle timer.
if (dmaCfg.rxTail != tail)
{
dmaCfg.rxTail = tail;
// Re-sync the shadow on any 1st byte(s) received.
if (dmaCfg.rxTick == 0)
{
dmaCfg.rxShdw = ST0;
}
dmaCfg.rxTick = HAL_UART_DMA_IDLE;
}
else if (dmaCfg.rxTick)
{
// Use the LSB of the sleep timer (ST0 must be read first anyway).
uint8 decr = ST0 - dmaCfg.rxShdw;
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;
}
else if (cnt >= HAL_UART_DMA_HIGH)
{
evt = HAL_UART_RX_ABOUT_FULL;
PxOUT |= HAL_UART_Px_RTS;
}
else if (cnt && !dmaCfg.rxTick)
{
evt = HAL_UART_RX_TIMEOUT;
}
if (dmaCfg.txMT)
{
dmaCfg.txMT = FALSE;
evt |= HAL_UART_TX_EMPTY;
}
if (dmaCfg.txShdwValid)
{
uint8 decr = ST0;
decr -= dmaCfg.txShdw;
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;
}
}
if (dmaCfg.txDMAPending && !dmaCfg.txShdwValid)
{
// 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;
HAL_ENTER_CRITICAL_SECTION(intState);
HAL_DMA_ARM_CH(HAL_DMA_CH_TX);
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);
}
if (evt && (dmaCfg.uartCB != NULL))
{
dmaCfg.uartCB(HAL_UART_DMA-1, evt);
}
}
这个poll函数即轮询发送缓冲区也轮询接收缓冲区。第一个if语句里面判断接收缓冲区中是否有数据,如果有则tail = findTail(),找到缓冲区数据的末位置,如果dmaCfg.rxTail != tail,则还有数据要传输,分别设置dmaCfg.rxShdw和dmaCfg.rxTick,这两个成员跟串口驱动ISR方式中isrCfg的这两个成员意义相同,这里不再赘余。如果没有数据需要接收了,则判断dmaCfg.rxTick是否为0,如果不为0,则将其置为减去上次轮询的时候到本次程序跑的时间的值,decr为程序跑的时间,如果rxTick大于decr,则说明还没有到进行数据传输的时间,如果小于则时间到了将rxTick置为0,在下一次轮询的时候,则可以调用回调函数读取接收缓冲区的数据了。该代码块最后调用
HalUARTRxAvailDMA()计算出接收缓冲区的有效数据的长度。
如果HAL_UART_DMA_NEW_RX_BYTE判断的结果是缓冲区没有数据,则仅将txTick置为0。
第二个if语句若cnt >= HAL_UART_DMA_FULL,则置串口事件HAL_UART_RX_FULL表示缓冲区已满。
接下来如果cnt不等于0,且dmaCfg.rxTick为0,则置串口事件HAL_UART_RX_TIMEOUT表示到了读取缓冲区数据的事件,赶紧调用串口回调函数读取吧!
if (dmaCfg.txMT)
{
dmaCfg.txMT = FALSE;
evt |= HAL_UART_TX_EMPTY;
}
txMT标志发送缓冲区是否为空,为TRUE为空,置为HAL_UART_TX_EMPTY事件。接下来两个if语句至关重要。
if (dmaCfg.txShdwValid)
{
uint8 decr = ST0;
decr -= dmaCfg.txShdw;
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;
}
}
txShdValid这个成员变量是对发送缓冲区有效的,即如果为true,则发送数据只有经过txTick时间之后才能进行数据传输,否则则说明txTick时间已到,这个txTick是串口初始化的时候就已经确定的,是个定值,跟rxTick不一样。所以就定义了txShdwValid这个成员变量。
第二个if语句就先判断txDMAPending和txShdwValid,如果前者为TRUE,后者为FALSE,则UART TX DMA is expected to be fired and enough time has lapsed since last DMA ISR to know that DBUF can be overwritten;真正发送数据到UxDBUF中 是在这里面进行的!因为这里重新配置了TX的DMA配置描述符,有一句代码HAL_DMA_MAN_TRIGGER(HAL_DMA_CH_TX);手动开启DMA传输,则数据直接从dmaCfg.txBuf[dmaCfg.txSel]传输到UxDBUF中,dmaCfg.txSel ^= 1;这一句代码有一点不解,意思是将txSel取为另一个缓冲区,但是这是为何呢!
最后一个if语句是evt不为空且回调函数不为空时,调用串口的回调函数!
最后看一下这个TX的DMA中断程序
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;
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;
}
}
这个中断函数在什么时候执行呢?根据datasheet,每当Tx complete时便发生DMA中断。这个Tx complete是指每次从UxDBUF中发送一个字节的数据出去完成时。
dmaCfg.txIdx[(dmaCfg.txSel ^ 1)] = 0;
dmaCfg.txMT = TRUE;
这两句代码有点不解,按注释意思是中断发生时另一个缓冲区为空,执行这两句代码。。。??
然后执行dmaCfg.txShdw = ST0; dmaCfg.txShdwValid = TRUE; 这是在下一次进行DMA传输时需要设置的。
if (dmaCfg.txIdx[dmaCfg.txSel])
{
// UART TX DMA is expected to be fired
dmaCfg.txDMAPending = TRUE;
}
最后如果本缓冲区中还有数据,则txDMAPending置为TRUE,以便将剩余的数据进行DMA传输给串口。
以上是大致串口DMA驱动的代码详解分析,其中有三个问题没有弄明白,一个是Tx时,两个缓冲区是如何进行切换使用的?一个就是DMA_PAD的问题!还有一个就是if (txSel != dmaCfg.txSel)这个地方!
接下来来简单分析一下串口DMA驱动的流程!
在使用串口时,对于Rx,即从串口读取一定字符串的过程;系统在串口数据到来之前调用HalUARTPollDMA函数轮询串口中是否有数据。这里说一下,当UxDBUF中有数据时,直接利用DMA传输,一一将UxDBUF的数据发送到了rxBuf中了,而HalUARTPollDMA轮询时候只是检查rxBuf中时候有新的数据,就是宏HAL_UART_DMA_NEW_RX_BYTE的作用。当检查到rxBuf中有数据时,则会经过很小的一段时间,这个时间可以通过HAL_UART_DMA_IDLE计算出来,此值为198,乘以32KHz的时钟频率即是!程序中会判断是否经过了这么长时间,如果是,则会置相应串口事件,从而调用串口回调函数,在这个回调函数里面,通常我们会调用HalUARTReadDMA函数将缓冲区中的数据读取出来。
对于Tx,即向串口中发送一定字符串的过程,这个过程可能略显复杂。但是和Rx有相似之处。通常在我们的Z-STACK的应用程序中调用HalUARTWriteDMA向发送缓冲区中写字符串,这个是在HalUARTPollDMA轮询之前进行的,即如果没有调用HalUARTWriteDMA,则HalUARTPollDMA将不会检测到有数据要发送到串口!当txBuf中有数据了,那么接下来发生什么呢?在HalUARTPollDMA函数中将强制启动DMA传输,将txBuf中的数据发送打UxDBUF中去,这个DMA传输是一个字节一个字节的传输,当一个字节传输完成时,串口将UxDBUF中数据发送出去,然后发生DMA 中断,在中断函数里面,判断是否还有数据要发送,如果有,则当系统轮询调用HalUARTPollDMA这个函数进行剩余数据的DMA传输,Tx的流程大致应该是这样的,其中有一些细节地方还有待理解!