前回の記事で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文字)かかることに留意されたい。