MCUXpressoでUART printfの改善

前回の記事でUARTでのprintfの速度は実装しだいと書いた。

UARTの送信をブロッキング処理でおこなうと相応の時間を要するが、バッファにためて割り込みやDMAで送信すればprintf関数の処理時間を短くすることができる。ただし、当然ながら通信のスループットはUARTのボーレートで決まる。

ブロッキング処理でのUART printf

たとえばMCUXpressoでLPCOpenプロジェクトの場合、下記のようなソースでprintf関数の出力をUARTに送信できる。実験にはLPC1857マイコンを使用した。ボーレートは115200。

#include <stdio.h>
#include "board.h"

// called every 1msec
void SysTick_Handler(void)
{
    static int cnt = 0;
    static int x = 0;
    if (x++ > 1000) {
        x = 0;
        cnt++;
        Board_LED_Set(0, false);

        printf("Count up! %d\n", cnt);

        Board_LED_Set(0, true);
    }
}

int main(void)
{
    SystemCoreClockUpdate();
    Board_Init();
    Board_Debug_Init(); // initialize Debug UART

    // call SysTick_Handler every 1msec
    SysTick_Config(SystemCoreClock / 1000);

    printf("Hello, world!\n");

    while(1) {
        ;
    }
    return 0 ;
}

このときprintf関数で10文字送信するのに要する時間は約710usecであった。ボーレート115200で1文字送信するのに要する時間は86.8usecであるから、これは(10-2)文字送信する時間 + printf関数の書式処理時間とみられる。-2文字はUARTの送信データレジスタと送信シフトレジスタのぶんである。

このように、セミホスティングよりマシではあるが、IMTやRTTに比べるとprintf関数の所要時間が長い。IMTやRTTが利用できるならそちらを検討したほうが良いだろう。

割り込み処理による改善

ただし、どうしてもUARTにログを出したい場合もあるだろう。その場合には送信データをリングバッファにためて割り込み処理で送信すればprintf関数の所要時間を短縮できる。たとえばMCUXpressoでLPCOpenプロジェクトの場合、下記のような修正でUART printfを割り込み処理にできる。

(1) デバッグUARTの割り込み番号と割り込みハンドラ名を定義

ボードライブラリのboard.hに下記のような定義を追加する。ただし、UART番号はターゲットの基板に合わせること。

#define DEBUG_UART LPC_USART3
// 以下を追記
#define DEBUG_UART_IRQn        USART3_IRQn
#define DEBUG_UART_IRQHandler  UART3_IRQHandler
(2) デバッグUART用のリングバッファと割り込みハンドラを定義

ボードライブラリのboard.cに下記の定義を追加する。バッファサイズは適宜調整すること。

// Ring buffer
#define UART_RB_SIZE 256
static uint8_t txbuff[UART_RB_SIZE], rxbuff[UART_RB_SIZE];
static RINGBUFF_T txring, rxring;

// Debug UART interrupt handler
void DEBUG_UART_IRQHandler(void)
{
    Chip_UART_IRQRBHandler(DEBUG_UART, &rxring, &txring);
}
(3) Board_Debug_Init関数を修正

board.cのBoard_Debug_Init関数を下記のように修正する。

/* Initialize debug output via UART for board */
void Board_Debug_Init(void)
{
#if defined(DEBUG_UART)
    Board_UART_Init(DEBUG_UART);

    Chip_UART_Init(DEBUG_UART);
    Chip_UART_SetBaud(DEBUG_UART, 115200);
    Chip_UART_ConfigData(DEBUG_UART, UART_LCR_WLEN8 | UART_LCR_SBS_1BIT | UART_LCR_PARITY_DIS);

    /* Enable UART Transmit */
    Chip_UART_TXEnable(DEBUG_UART);

    // 以下を追記
    // Initialize ring buffer
    RingBuffer_Init(&txring, txbuff, 1, UART_RB_SIZE);
    RingBuffer_Init(&rxring, rxbuff, 1, UART_RB_SIZE);
    // Enable UART transmit interrupt
    Chip_UART_IntEnable(DEBUG_UART, UART_IER_RBRINT);
    NVIC_SetPriority(USART3_IRQn, 1);
    NVIC_EnableIRQ(USART3_IRQn);
#endif
}
(4) Board_UARTPutChar関数を修正

board.cのBoard_UARTPutChar関数を下記のように修正する。

/* Sends a character on the UART */
void Board_UARTPutChar(char ch)
{
#if defined(DEBUG_UART)

//  /* Wait for space in FIFO */
//  while ((Chip_UART_ReadLineStatus(DEBUG_UART) & UART_LSR_THRE) == 0) {}
//  Chip_UART_SendByte(DEBUG_UART, (uint8_t) ch);

    // 以下に変更
    Chip_UART_SendRB(DEBUG_UART, &txring, &ch, 1);

#endif
}

以上の変更を施したところ、10文字送信するprintf関数の処理時間は710usecから80usecに短縮された。ただし最初に述べたように、UARTの送信時間じたいはボーレートで決まり、約870usec(86.8usec×10文字)かかることに留意されたい。