STM32F1のUSART通信:標準ペリフェラルライブラリによるシリアル制御実装

シリアル通信の基礎概念

シリアル通信では、データは1ビットずつ直列に送受信されます。主な端子とパラメータは以下のように定義されます。

  • Tx (Transmit):データ送信用の出力ピン
  • Rx (Receive):データ受信用の入力ピン
  • ボーレート:1秒間に伝送するビット数。一般的な値として9600、115200、921600などが利用されます。

データフォーマットは通常、スタートビット、データビット(8ビット)、パリティビット(なし)、ストップビット(1ビット)で構成されます。例えば、整数の27を送信する場合や、文字列"Hello"を送信する場合、各文字は対応するASCIIコード(H:0x48, e:0x65, l:0x6c, o:0x6f)に変換され、ビット列として逐次シフト送出されます。

ペリフェラルクロックとGPIOの設定

STM32F1シリーズでは、USARTを使用する前に対応するAPB2バスおよびAFIOモジュールのクロックを有効化する必要があります。また、ピンリマップ機能を用いることで、デフォルトのPA9/PA10からPB6/PB7へ端子配線を変更することができます。

#include "stm32f10x.h"

void UartMgr_ConfigPB67(void)
{
    GPIO_InitTypeDef pin_cfg;
    USART_InitTypeDef usart_cfg;

    /* AFIOおよびGPIOBのクロック有効化 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOB, ENABLE);
    
    /* USART1をPB6(Tx)/PB7(Rx)へリマップ */
    GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);

    /* PB6: Tx (Alternate Function Push-Pull) */
    pin_cfg.GPIO_Pin = GPIO_Pin_6;
    pin_cfg.GPIO_Mode = GPIO_Mode_AF_PP;
    pin_cfg.GPIO_Speed = GPIO_Speed_10MHz;
    GPIO_Init(GPIOB, &pin_cfg);

    /* PB7: Rx (Input Pull-Up) */
    pin_cfg.GPIO_Pin = GPIO_Pin_7;
    pin_cfg.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOB, &pin_cfg);

    /* USART1クロック有効化およびパラメータ設定 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    
    usart_cfg.USART_BaudRate = 115200;
    usart_cfg.USART_WordLength = USART_WordLength_8b;
    usart_cfg.USART_StopBits = USART_StopBits_1;
    usart_cfg.USART_Parity = USART_Parity_No;
    usart_cfg.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    usart_cfg.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
    
    USART_Init(USART1, &usart_cfg);
    USART_Cmd(USART1, ENABLE);
}

データ送信処理と送信フラグの扱い

送信処理では、ハードウェアの内部レジスタ状態を監視する必要があります。TxEフラグは送信データレジスタ(DR)が空の状態で立ち上がり、新しいバイトの書き込みが可能であることを示します。TCフラグは、DRとシフトレジスタの両方が完全に空になり、送信が完了した時点で立ち上がります。信頼性の高い送信を行うためには、逐次TxEを待機してデータを書き込み、最後にはTCを待機して処理を終了するのが標準的な手法です。

#include "stm32f10x.h"

static void UartMgr_WriteBlock(USART_TypeDef *dev, const uint8_t *buf, uint16_t count)
{
    if (count == 0) return;
    
    for (uint16_t idx = 0; idx < count; idx++)
    {
        /* 送信データレジスタが空になるまで待機 */
        while (USART_GetFlagStatus(dev, USART_FLAG_TXE) == RESET);
        
        /* DRへデータ転送 */
        USART_SendData(dev, buf[idx]);
    }
    
    /* シフトレジスタからの最終出力が完了するまで待機 */
    while (USART_GetFlagStatus(dev, USART_FLAG_TC) == RESET);
}

int main(void)
{
    UartMgr_ConfigPB67();
    
    uint8_t tx_payload[] = {0x01, 0x02, 0x03, 0x04, 0x05};
    UartMgr_WriteBlock(USART1, tx_payload, sizeof(tx_payload));
    
    while (1)
    {
        /* メインループ */
    }
}

標準出力リダイレクトによる書式付きプリント

C言語標準ライブラリのprintfをシリアル出力に接続するには、fputc関数をオーバーライドして、文字データを送信DRへ転送するように実装します。これにより、フォーマット文字列や可変引数を直接ハードウェアペリフェラルへ渡すことが可能になります。

#include "stm32f10x.h"
#include 
#include 

static USART_TypeDef *g_printf_target = NULL;

void UartMgr_Printf(USART_TypeDef *dev, const char *fmt, ...)
{
    g_printf_target = dev;
    va_list args;
    va_start(args, fmt);
    vprintf(fmt, args);
    va_end(args);
}

/* 標準ライブラリからの呼び出しをインターセプト */
int fputc(int ch, FILE *f)
{
    (void)f;
    if (g_printf_target == NULL) return ch;
    
    while (USART_GetFlagStatus(g_printf_target, USART_FLAG_TXE) == RESET);
    USART_SendData(g_printf_target, (uint8_t)ch);
    return ch;
}

上記実装を組み合わせて、以下のように定期的に時刻情報を出力させることができます。

int main(void)
{
    UartMgr_ConfigPB67();
    /* 時刻管理用のティックカウント初期化は別途実装 */
    
    while (1)
    {
        uint32_t total_ms = GetTick();
        uint32_t ms_part = total_ms % 1000;
        total_ms /= 1000;
        uint32_t sec_part = total_ms % 60;
        total_ms /= 60;
        uint32_t min_part = total_ms % 60;
        uint32_t hr_part = total_ms / 60;
        
        UartMgr_Printf(USART1, "%02lu:%02lu:%02lu.%03lu\r\n", 
                       hr_part, min_part, sec_part, ms_part);
        
        Delay(100);
    }
}

データ受信とステータスフラグ

受信側では以下のフラグ状態を監視します。

  • RXNE:受信データレジスタが空でない(データ読み出し可能)
  • PE:パリティエラー
  • FE:フレームエラー(ストップビット位置で有効レベル検出)
  • NE:ノイズエラー
  • ORE:オーバーランエラー(前回のデータを読み出しきれずに新たなデータが到来)

以下は、受信した文字列に応じてオンボードLEDを制御するポーリング実装の例です。

void UartMgr_LedControlLoop(void)
{
    while (1)
    {
        /* 受信データレジスタが空でないことを確認 */
        if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET)
        {
            uint8_t rx_data = USART_ReceiveData(USART1);
            
            if (rx_data == '0')
            {
                GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_SET); /* LED ON */
            }
            else if (rx_data == '1')
            {
                GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_RESET); /* LED OFF */
            }
        }
    }
}

高機能シリアル制御モジュールの構築

実務では、送信・受信処理、タイムアウト制御、行単位のデータパースを統括するモジュールを作成するのが一般的です。以下に、ヘッダーファイルと実装ファイルを再構成した例を示します。

/* usart_mgr.h */
#ifndef USART_MGR_H
#define USART_MGR_H

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

#define SEP_CR    0x00
#define SEP_LF    0x01
#define SEP_CRLF  0x02

void UartMgr_Init(USART_TypeDef *dev);
void UartMgr_WriteByte(USART_TypeDef *dev, uint8_t data);
void UartMgr_WriteBlock(USART_TypeDef *dev, const uint8_t *buf, uint16_t len);
void UartMgr_Printf(USART_TypeDef *dev, const char *fmt, ...);

uint8_t UartMgr_ReadByte(USART_TypeDef *dev);
uint16_t UartMgr_ReadBlock(USART_TypeDef *dev, uint8_t *buf, uint16_t len, int32_t timeout_ms);
int32_t UartMgr_ReadLine(USART_TypeDef *dev, char *buf, uint16_t max_len, uint16_t sep_type, int32_t timeout_ms);

#endif
/* usart_mgr.c */
#include "usart_mgr.h"
#include <string.h>
#include <stdarg.h>

static USART_TypeDef *g_printf_ptr = NULL;

void UartMgr_Init(USART_TypeDef *dev)
{
    /* GPIOおよびリマップ設定は呼び出し側または別途定義するものとします */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    USART_InitTypeDef cfg = {0};
    cfg.USART_BaudRate = 115200;
    cfg.USART_WordLength = USART_WordLength_8b;
    cfg.USART_StopBits = USART_StopBits_1;
    cfg.USART_Parity = USART_Parity_No;
    cfg.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    cfg.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
    USART_Init(dev, &cfg);
    USART_Cmd(dev, ENABLE);
}

void UartMgr_WriteByte(USART_TypeDef *dev, uint8_t data)
{
    UartMgr_WriteBlock(dev, &data, 1);
}

void UartMgr_WriteBlock(USART_TypeDef *dev, const uint8_t *buf, uint16_t len)
{
    if (len == 0) return;
    for (uint16_t i = 0; i < len; i++)
    {
        while (USART_GetFlagStatus(dev, USART_FLAG_TXE) == RESET);
        USART_SendData(dev, buf[i]);
    }
    while (USART_GetFlagStatus(dev, USART_FLAG_TC) == RESET);
}

void UartMgr_Printf(USART_TypeDef *dev, const char *fmt, ...)
{
    g_printf_ptr = dev;
    va_list args;
    va_start(args, fmt);
    vprintf(fmt, args);
    va_end(args);
}

uint8_t UartMgr_ReadByte(USART_TypeDef *dev)
{
    while (USART_GetFlagStatus(dev, USART_FLAG_RXNE) == RESET);
    return USART_ReceiveData(dev);
}

uint16_t UartMgr_ReadBlock(USART_TypeDef *dev, uint8_t *buf, uint16_t len, int32_t timeout_ms)
{
    uint16_t cnt = 0;
    uint32_t expiry = (timeout_ms >= 0) ? (GetTick() + timeout_ms) : 0xFFFFFFFF;
    
    do
    {
        if (USART_GetFlagStatus(dev, USART_FLAG_RXNE) == SET)
        {
            buf[cnt++] = USART_ReceiveData(dev);
            if (cnt >= len) break;
        }
    } while (timeout_ms < 0 || GetTick() < expiry);
    
    return cnt;
}

int32_t UartMgr_ReadLine(USART_TypeDef *dev, char *buf, uint16_t max_len, uint16_t sep_type, int32_t timeout_ms)
{
    int32_t status = -1;
    uint32_t expiry = (timeout_ms >= 0) ? (GetTick() + timeout_ms) : 0xFFFFFFFF;
    uint16_t idx = 0;
    
    do
    {
        if (USART_GetFlagStatus(dev, USART_FLAG_RXNE) == SET)
        {
            char ch = (char)USART_ReceiveData(dev);
            buf[idx++] = ch;
            
            if (idx > max_len)
            {
                status = -2;
                break;
            }
            
            if (sep_type == SEP_CR && ch == '\r')
            { status = 0; buf[idx] = '\0'; break; }
            if (sep_type == SEP_LF && ch == '\n')
            { status = 0; buf[idx] = '\0'; break; }
            if (sep_type == SEP_CRLF && idx >= 2 && buf[idx-2] == '\r' && ch == '\n')
            { status = 0; buf[idx] = '\0'; break; }
        }
    } while (timeout_ms < 0 || GetTick() < expiry);
    
    return status;
}

int fputc(int ch, FILE *f)
{
    (void)f;
    if (g_printf_ptr)
    {
        while (USART_GetFlagStatus(g_printf_ptr, USART_FLAG_TXE) == RESET);
        USART_SendData(g_printf_ptr, (uint8_t)ch);
    }
    return ch;
}

6月21日 18:23 投稿