文章總列表

假日嵌入式系統工程師:SEGGER RTT (1)

假日嵌入式系統工程師:SEGGER RTT (1)

UART真的很麻煩 (Target-Side)

從我學C語言開始,我認定最好用的debug工具叫printf()。在嵌入式系統的世界,一般我們會用UART (Universal Asynchronous Receiver/Transmitter)來印出printf()。幾年下來,學的東西稍微多一點以後,我實在是越來越討厭這東西。

  1. 首先UART很慢,9600bps傳1byte大概要0.1ms。如果CPU跑在16Mhz,代表1byte需要1600 CPU cycles。
  2. 一般的UART硬體緩衝區都少得可憐(16550 UART, 16bytes),一般embedded MCU只有4bytes這種數量級。如果用polling簡單實作的UART driver,MCU跑BLE這種應用時,根本不可能透過UART丟訊息。
  3. 另一招是UART-IRQ功能,在buffer快光的時候打中斷出來,然後再塞資料給他。這需要額外的工作去搞定這個模式。而且還需要設計一個ring buffer來當write buffer存字串。
  4. 為了要有(3)描述的先進功能,我們還得逐一搞定UART HW。更天怒人怨的是:這東西每顆晶片玩都還不一樣。
  5. 最後一點和UART無關,只是printf()合成字串也要花不少時間,沒準還得準備個小型的C library,不然Code size可能無法接受。

UART在PC更麻煩

1994年USB推出以後,配備COM port的電腦大概已經絕跡了15年以上了。其實也不是絕跡,想要還是有的。不要問我怎麼讓他會動,很麻煩的!現代流行的貨色是UART轉USB的晶片,有家叫FTDI的公司,或著台灣的旺玖也有做這種產品。2016年買得到的開發板,上面都會有這種晶片。不過這個解法還是不夠完美。

要看到開發版丟出的資訊,一般會開個叫做"TeraTerm"的程式,人類需要設定底下的參數:

  • UART速度
  • COM port編號
  • Start bits數目
  • Data bits數目
  • End bits數目
  • Parity bit是否存在

這些參數真的很不直覺,開發嵌入式系統這麼多年了,每次我都會遲疑。這裡面最討厭的是COM port編號,就算是同一片板子,只要插的USB孔不一樣,編號也會變。每次都得開裝置管理員做插拔測試,才找得到port編號。

SEGGER RTT Introduction

enter image description here

每次我拿到新的開發版,為了要印出我最愛的"Hello World",我就得搞定UART介面。一般我會先用polling先丟出來再說,只是這方法真的很醜又很爛。IRQ的招數要花時間,而且很容易出錯 (搞定IRQ永遠比較麻煩)。我個人的美感不允許我用很爛的招數(polling),但我又懶得搞定UART IRQ,最後這片板子就被我丟一邊了。

我要感謝ARM的AHB-AP,其實我不知道他是什麼(笑)。但我知道他能讓Debugger在不中斷CPU的前提下,讀取系統記憶體。更精確一點,MCU正在執行程式,同時SEGGER-JLINK能讀寫系統記憶體。這個能力真的超讚的!!底下解釋為什麼我這麼愛這個功能。

SEGGER-RTT在MCU裡實作了一個ring buffer,看起來像下面這樣:

struct {
    u8 token[12];
    u8 buf[1024];
    u32 wptr; // MCU updates when write to buf[]
    u32 rptr; // PC updates the value
} ring_buf;

MCU要丟字串出去,就去關中斷,寫buf[],以及更新wptr即可。這些操作潮快的 (<30 cycles),大概比UART@9600bps快50x倍。

ring_buf rtt = {
    .token = "SEGGER-RTT"; // Special token enables SEGGER-JLINK to find structure
};

rtt_write_char(char c)
{
    disable_irq();
    rtt.buf[rtt.wptr] = c;
    rtt.wptr = (rtt.wptr+1) & (1024-1); // ring buffer management
    enable_irq();
}

SEGGER-JLINK會透過AHB-AP掃描data memory,尋找SEGGER-RTT關鍵字,這樣就能定位ring buffer address。接下來SEGGER-JLINK就會定時起來去讀一下RTT的wptr/ rptr,看看是不是有資料等著PC抽走:

check_target_buf()
{
    u8 local_buf[32KB];
    u8 *ptr = &local_buf;
    u32 wptr = read_target_memory(rtt.wptr);
    u32 rptr = read_target_memory(rtt.rptr);
    
    while(wptr != rptr) {
        *ptr++ = read_target_memory(rtt.buf[rptr]);
        rptr = (rptr + 1) & 1023;
    }
    
    write_target_memory(rtt.rptr, rptr); // updates rptr
}

上面這段code讀到buffer內容以後,就可以把資料印出來,或著做後處理,看要什麼都行。

和最高速的UART-IRQ相比,兩招都需要ring buffer放字串,寫ring buffer沒什麼大問題。不過UART-IRQ需要MCU介入搬資料 (從rtt.buf[]搬到UART-TX buffer)。如果是RTT版,資料搬移是PC和SEGGER-JLINK做掉,MCU本體不用浪費code & cycle做這個事。從這個角度來看,RTT版的Logging system,額外開銷比較低。

MCU應用很在乎腳位數,RTT是架在SWD (Serial Wire Debug)介面上,這個介面只有兩根腳 {SWDIO, SWCLK}。我們本來就需要SWD來燒code或做debugging,所以RTT不需要增加腳位。如果是UART,還需要額外兩根線 {RX, TX} 才有log可以看。以腳位數來看,RTT完勝。

RTT最大的死穴是$$,最便宜的SEGGER JLINK-EDU也要80塊美金。但是FTDI的USB轉UART我猜不用10塊美金;沒名氣的晶片價格可以更低(笑)。

我自己有買SEGGER JLINK-EDU,而且現在的開發版很多都內建JLINK,比如Nordic這片NRF51-DK,更不用說大陸謎版的開發版,上面的J-Link幾乎是免費送。我自己用過RTT以後,就完全沒再考慮用UART了。

下一篇文章,我會介紹怎麼移植SEGGER-RTT到開發版上頭。

歷史的遺跡

enter image description here

留言

這個網誌中的熱門文章

幼犬書桌椅選擇心得 升降桌 兒童桌椅

STM32 UART + DMA,使用HAL實作TX/RX,以及不定長度接收

CANON G3000 廢墨瓶改裝