在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的流程大致應該是這樣的,其中有一些細節地方還有待理解!