STM32 UART + DMA,使用HAL實作TX/RX,以及不定長度接收
前言
- 我一般用Segger-RTT來debug。不過這東西也有限制,比如MCU進出sleep mode,他的data stream就會斷掉,也是不方便
http://lihgong.blogspot.com/2016/05/segger-rtt-1.html
http://lihgong.blogspot.com/2016/05/segger-rtt-2.html
http://lihgong.blogspot.com/2016/05/segger-rtt-3_6.html - 我不是很喜歡UART,根本原因是把他用好其實不容易。當年選用Segger RTT其實就是要逃避這題(笑);很多年過去了,總是要回來解決這題
- STM32 HAL UART driver足足有3726Lines,這代表他的複雜度。假如UART跑在115200bps來說,傳送1byte需要約100us,假設CPU跑在16Mhz就需要1600T。簡單用Polling mode丟資料(或是接收資料)效能很悲劇;用了中斷模式會好一點,但是每個字元都來中斷,遇到高速UART其實效率也不大好;這題的根本解法是需要DMA mode
- 這篇我會介紹怎在使用STM32CubeIDE,把UART+DMA mode跑起來,TX/RX都有,實作我心裡最佳的框架。這篇直接講結論和步驟,原理很複雜,但是架起來卻很簡單
環境
- STM32CubeIDE
你改用STM32CubeIDE了嗎?如果還沒,可以試試看;我用起來很滿意! - Nucleo F411RE Board
上面有ST-LINK加開發版,帶條USB線就能出去玩;缺點是MINI-USB線有點討厭 - MobaXTerm
好用的Terminal工具,可以連SSH/ RDP/ Serial Terminal
UART-TX設定步驟
- STM32CUBEIDE設定UART,關鍵是要打開UART中斷,以及打開TX/RX的DMA
Enable UART IRQ
Enable UART DMA channels - 假如要傳送資料,這一段code能幫上忙。這裡面有個資料結構內藏ring-buf,每次user要寫東西就往buf[]擺,並且更新wptr;資料擺上去會呼叫UART HAL library的API來傳送;或是DMA傳送完了,也會檢查還有沒有剩下的要傳。因為有Global variable的關係,操作之前都會先關閉中斷,這裡也要特別注意
- 這個函數也不能忘記,UART HAL library在完成時,會呼叫他;我們要在裡面檢查是否還有沒搬完的資料
- 我們在一個無窮迴圈裏反覆送出訊息;也可以在user code不同地方丟出字串。這段軟體用DMA mode搬,效能不錯
UART-RX設定步驟
- STM32CubeIDE在前面已經設定完畢
- 軟體的部分,首先要宣告一塊buffer讓DMA當緩衝,然後要呼叫HAL Library進行接收。注意喔,這裡呼叫的是HAL_UARTEx_ReceiveToIdle_DMA,算是裡面比較進階的函數。他會在收滿資料,或是UART IDLE一個字元以後呼叫callback
- UART RX callback,這個函數是STM32 HAL規定的callback function name,注意喔,size是這次有多少資料有效;然後原先塞給UART HAL的buffer可以從huart->pRxBuffer取用。這個callback function我會把收到的資料加點料,然後再原封不動地送回。實際跑起來照片也貼出來
- 前面的TX範例有宣告一個ring-buffer,其實RX這裡也會需要。一般是在上述callback把資料寫進ring-buffer,然後可能呼叫某個processing API對裡面資料進行處理,這裡只是展示功能就簡單做了
結論
- 使用DMA可以把資料搬移自動化,只是DMA需要事前知道資料長度。對UART-TX這題不是問題;但是UART-RX這題就很麻煩,所以實際上還參考了UART都有個"IDLE FLAG",當UART沒收到資料1-byte以後會拉起,通知CPU可以處理
- 承上,其實這個範例的UART-RX,加上不定長度資料接收就是如此複雜,只是HAL Library全部包掉了。不意外的,他的API名字帶著"Ex",代表他是進階的版本。這個由官方提供的實作幾乎是最完美的
- 如我最前面敘述的,要把UART用得很好橫跨多個主題,其實不大容易。我斷斷續續摸了幾個月,才把整路弄通;不過最後發現,其實STM32 HAL都寫好了,沒什麼要寫(只是code size有點大,足足12KB)。用STM32 HAL最大的好處是有專人幫忙維護Lib,可以保證以後能繼續用
- UART, IRQ, IDLE Flag
- DMA
- STM HAL - STM HAL包裝得很好,用起來很爽;Mastering STM32這本書有介紹STM怎麼包裝他們的library,搭配HAL看會比較清楚。這篇文章是我的best practice,寫下來下次直接抄答案
- 一些我覺得不錯的資源
- Google搜尋"STM32 UART DMA 不定長度",可以找到一堆資料
- https://stm32f4-discovery.net/2017/07/stm32-tutorial-efficiently-receive-uart-data-using-dma/
- https://leanpub.com/mastering-stm32
留言
關於RX 端的實際編撰,個人才疏學淺,能否 PO 上 RX 相對應的 ring-buffer 函式?
"前面的TX範例有宣告一個ring-buffer,其實RX這裡也會需要。一般是在上述callback把資料寫進ring-buffer,然後可能呼叫某個processing API對裡面資料進行處理,這裡只是展示功能就簡單做了"
多謝了~
1. my_uart_tx_t, 這個資料結構有wptr/rptr/buf[], 宣告rbuf就這三個東西
2. 然後對rbuf寫入資料可以看my_uart_tx_write(), 裡面有個for-loop, 每寫1byte就讓wptr往前走; 注意要處理wrap-around. 如果buf-size是2^n, 那麼範例裡的wrap-round的處理是效率很高的
3. 從rbuf取東西的sample code, 注意, rptr == wptr代表buffer空了
__disable_irq(); // because wptr might be updated by other thread, lock CPU first
u32 rptr = UART_TX->rptr;
u32 wptr = UART_TX->wptr;
u32 bytes_received = 0;
while(rptr != wptr) {
buf[bytes_received++] = UART_TX->[rptr];
rptr = (rptr+1) & (SIZE-1);
}
UART_TX->rptr = rptr; // update rptr
__enable_irq();
後面的軟體就能拿buf[]來使用