概要
シリアルコンソール(Serial Console)とは、UARTなどのシリアル通信インターフェースを通じてデバイスとテキストでやり取りする端末インターフェースである。デバイス側からはデバッグログの出力や起動メッセージの表示に、開発者側からはコマンド入力やインタラクティブなシェル操作に使われる。
組み込みシステムの開発・デバッグにおいて、シリアルコンソールはいわゆる「生命線」とも呼ばれる。GUIがなく、ディスプレイも接続されていない組み込みデバイスでも、UARTとシリアルコンソールがあれば動作状況を確認し、設定を変更できる。Linuxカーネルの起動ログ確認、U-Bootブートローダの操作、マイコンのprintfデバッグなど、組み込み開発の全段階で活躍する。
歴史・背景
シリアルコンソールの起源は、1960年代〜1970年代のメインフレームコンピュータ時代にある。当時のコンピュータは「テレタイプ端末」(TTY)と呼ばれる電気機械的な入出力装置を使用していた。TTYはシリアル通信(RS-232C)でメインフレームと接続され、キーボード入力とプリンタ出力を提供した。
Unixの設計においても、TTY(teletypeの略)はシステムの基盤的な概念として組み込まれ、現代のLinuxでも/dev/ttyS0(シリアルポート)や/dev/ttyUSB0(USB-シリアル変換)という命名にその歴史が残っている。
マイクロコンピュータ時代(1970年代〜)に入ると、RS-232C経由でパソコンとマイコンを接続してデバッグするスタイルが定着した。現代では、USBシリアル変換チップ(CP2102、CH340、FT232等)の普及により、従来のRS-232C 9ピンコネクタではなくUSB経由でシリアル通信を行うのが一般的になっている。
技術仕様
主要な設定パラメータ(UARTの場合)
シリアルコンソールの設定には以下のパラメータを一致させる必要がある:
| パラメータ | 一般的な値 | 説明 |
|---|---|---|
| ボーレート | 115200 bps(標準)、9600、38400等 | 1秒間に転送するビット数 |
| データビット | 8bit(標準) | 1文字分のデータビット数 |
| パリティ | None(標準) | エラー検出用パリティビット |
| ストップビット | 1bit(標準) | 1文字の終端を示すビット数 |
| フロー制御 | None(ほとんどの場合) | ハードウェア/ソフトウェアフロー制御 |
この設定を「8N1」(8データビット、パリティなし、1ストップビット)と呼ぶことが多い。
PC接続の方法
[組み込みデバイス] [PC]
UART TX (3.3V) ──────→── RX ┐
UART RX (3.3V) ──────←── TX ├── USBシリアル変換器
GND ─────────── GND┘ (CP2102/CH340/FT232)
|
| USB
|
[PCのUSBポート]
/dev/ttyUSB0 (Linux)
COM3 (Windows)
/dev/cu.usbserial-* (macOS)
電圧レベルの注意点:
- 3.3V UARTを5V系のシリアル変換器に直結すると3.3Vロジックのデバイスが破損する可能性がある
- RS-232Cの規格電圧(±12V)は3.3V/5V UARTと直結してはいけない(MAX232等の変換ICが必要)
動作原理
UART通信の基本原理
シリアルコンソールはUART(Universal Asynchronous Receiver Transmitter)の非同期通信に基づく:
送信される文字 'A'(0x41 = 0100 0001b)の波形:
アイドル状態は High (1)
_______________ ________________
| | LSB MSB |
____| |_|_|_|_|_|_|_|_| |________________
S 1 0 0 0 0 0 1 0 P Stop
t d d d d d d d d a p
a 0 1 2 3 4 5 6 7 r i
r i t
t t y
printfのシリアルコンソールへのリダイレクト
マイコン上でprintfをUARTに出力させるための実装:
#include <stdio.h>
#include "stm32f4xx_hal.h"
UART_HandleTypeDef huart2;
// _write関数をオーバーライドしてUART出力にリダイレクト
int _write(int fd, char *ptr, int len) {
HAL_UART_Transmit(&huart2, (uint8_t *)ptr, len, HAL_MAX_DELAY);
return len;
}
int main(void) {
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
HAL_UART_Init(&huart2);
printf("System started!\r\n");
printf("Version: %s\r\n", FIRMWARE_VERSION);
while (1) {
uint32_t val = read_sensor();
printf("Sensor: %lu\r\n", val);
HAL_Delay(1000);
}
}
ターミナルソフトウェアの使い方
# Linux/macOS: screen コマンド
screen /dev/ttyUSB0 115200
# 終了: Ctrl+A, K
# Linux: minicom
minicom -D /dev/ttyUSB0 -b 115200
# Linux/macOS: picocom(軽量)
picocom -b 115200 /dev/ttyUSB0
# 終了: Ctrl+A, Ctrl+X
# Python(スクリプトからの接続)
python3 -m serial.tools.miniterm /dev/ttyUSB0 115200
用途・ユースケース
組み込みLinuxの起動ログ確認(Raspberry Pi等):
Raspberry Piのシリアルコンソール(GPIO 14/15, 115200bps)接続後:
[ 0.000000] Booting Linux on physical CPU 0x0
[ 0.000000] Linux version 5.15.0-v7+
...
[ 7.134521] systemd[1]: systemd 247.3 running in system mode
...
raspberrypi login: pi
Password:
pi@raspberrypi:~$ <- ここからシェル操作が可能
U-Bootでのブート操作:
U-Boot 2022.01 (Jan 20 2022)
DRAM: 1 GiB
Loading Environment from MMC... OK
Hit any key to stop autoboot: 3 <- ここでキー入力してU-Bootコンソールへ
=> printenv # 環境変数を表示
=> setenv bootargs "console=ttyS0,115200 root=/dev/mmcblk0p2"
=> saveenv # 環境変数を保存
=> boot # 起動を継続
マイコンのデバッグログ出力:
// デバッグレベル付きログマクロの実装例
#define LOG_LEVEL_ERROR 0
#define LOG_LEVEL_WARN 1
#define LOG_LEVEL_INFO 2
#define LOG_LEVEL_DEBUG 3
#ifndef CONFIG_LOG_LEVEL
#define CONFIG_LOG_LEVEL LOG_LEVEL_INFO
#endif
#define LOG_E(fmt, ...) if(CONFIG_LOG_LEVEL >= LOG_LEVEL_ERROR) \
printf("[ERROR] " fmt "\r\n", ##__VA_ARGS__)
#define LOG_W(fmt, ...) if(CONFIG_LOG_LEVEL >= LOG_LEVEL_WARN) \
printf("[WARN] " fmt "\r\n", ##__VA_ARGS__)
#define LOG_I(fmt, ...) if(CONFIG_LOG_LEVEL >= LOG_LEVEL_INFO) \
printf("[INFO] " fmt "\r\n", ##__VA_ARGS__)
#define LOG_D(fmt, ...) if(CONFIG_LOG_LEVEL >= LOG_LEVEL_DEBUG) \
printf("[DEBUG] " fmt "\r\n", ##__VA_ARGS__)
// 使用例
void sensor_init(void) {
LOG_I("Initializing sensor...");
int ret = hw_sensor_init();
if (ret != 0) {
LOG_E("Sensor init failed: %d", ret);
return;
}
LOG_D("Sensor init complete, ID=0x%02X", sensor_read_id());
}
実装・開発のポイント
1. バッファリングの注意
標準ライブラリのprintfはデフォルトでバッファリングされることがある。クラッシュ直前のログが出力されない問題を避けるため、バッファリングを無効化する:
// stdioのバッファリングを無効化(常に即座に出力)
setvbuf(stdout, NULL, _IONBF, 0);
// または各printfの後にフラッシュ
printf("About to do risky operation...\n");
fflush(stdout);
risky_operation();
2. 割り込みコンテキストでの注意
割り込みハンドラ内でprintfを呼び出すことは避けるべきである。printfはリエントラントでないことが多く、デッドロックや文字化けの原因になる:
// 悪い例:割り込みハンドラでprintf
void TIM2_IRQHandler(void) {
printf("Timer interrupt!\n"); // 危険:メインのprintfと競合する可能性
}
// 良い例:フラグを立ててメインループで出力
volatile bool timer_fired = false;
void TIM2_IRQHandler(void) {
timer_fired = true;
}
void main_loop(void) {
if (timer_fired) {
timer_fired = false;
printf("Timer interrupt!\n");
}
}
3. 量産品でのシリアルコンソール無効化
開発中はシリアルコンソールを使うが、量産品ではセキュリティのため無効化することを検討する:
#ifdef PRODUCTION_BUILD
// 量産ビルドではログ出力を無効化
#define LOG_I(fmt, ...) do {} while(0)
#define LOG_D(fmt, ...) do {} while(0)
#endif
4. ボーレートの精度確認
MCUのクロック周波数によっては設定ボーレートと実際のボーレートに誤差が生じる。オシロスコープで1ビット時間を測定して確認する:
- UART 115200bps: 1ビット = 8.68μs
- 誤差は通常2%以内が許容範囲
- 誤差が大きい場合はMCUのクロック設定を確認する
他技術との比較
| 比較項目 | シリアルコンソール | ITM/SWOトレース | インサーキットデバッガ |
|---|---|---|---|
| 必要なピン数 | 2〜3(TX/RX/GND) | 1(SWOのみ) | 2〜5(JTAG/SWD) |
| プログラム停止 | しない | しない | 停止させる |
| 双方向通信 | 可能(TX+RX) | 出力のみ | 可能 |
| 専用ハードウェア | USBシリアル変換のみ | J-Link等が必要 | J-Link/ST-Link等が必要 |
| 量産品でも使える | ピンを残せば可能 | デバッグポート必要 | デバッグポート必要 |
| リアルタイム性への影響 | printfの処理時間分だけ遅延 | ほぼなし | 停止させるため影響大 |
UARTはシリアルコンソールの通信プロトコルの基盤となる。ブートローダやU-Bootはシリアルコンソールを通じて操作・設定変更ができる。インサーキットデバッガと組み合わせることで、より強力なデバッグ環境が構成できる。