目次
  1. はじめに:STM32が産業機器に選ばれる理由
  2. STM32CubeMXによるペリフェラル設定
  3. UART通信の実装
  4. I2C通信の実装
  5. SPI通信の実装
  6. CAN通信の実装
  7. 通信方式の使い分けガイド
  8. よくあるトラブルと対策
  9. まとめ

はじめに:STM32が産業機器に選ばれる理由

STMicroelectronics(ST社)の STM32シリーズは、産業機器・医療機器・車載システム・IoT機器など、あらゆる組込みシステムで広く採用されているARM Cortex-Mベースのマイコンです。弊社でも多数のSTM32を用いた受託開発実績があります。

STM32が選ばれる主な理由を以下にまとめます。

  • 豊富なペリフェラル:UART・I2C・SPI・CAN・USB・Ethernet・ADC・DAC・TIMERなどをワンチップに搭載
  • HALライブラリ:ST公式のHardware Abstraction Layerにより、ハードウェアを意識せずに開発できる
  • STM32CubeMX:GUIでピン割り当てや初期化コードを自動生成。開発効率が大幅に向上
  • コストパフォーマンス:高性能ながら低コスト。量産時のコスト最適化がしやすい
  • エコシステムの充実:ST-LINK/V2などのデバッガ、Keil MDK・IAR・STM32CubeIDE などの開発環境が揃っている
弊社での活用実績

弊社では STM32F4・STM32G4・STM32H7 シリーズを中心に、センサーデータ収集装置・モーター制御・産業用通信ゲートウェイなど多数の組込み開発を手がけています。本記事はその実案件で得たノウハウをもとに作成しています。

STM32CubeMXによるペリフェラル設定

STM32CubeMXはST公式の無償ツールで、GUIでマイコンのピン配置・クロック設定・ペリフェラル設定を行い、HAL初期化コードを自動生成できます。従来はレジスタを直接叩いていた初期化作業が大幅に短縮されます。

基本的な使い方

  1. 使用するSTM32マイコンを選択(例:STM32F446RE)
  2. 「Pinout & Configuration」タブでペリフェラルを有効化(例:USART2 → Asynchronous)
  3. ピンアサインを確認・変更(コンフリクトがあれば自動警告)
  4. 「Clock Configuration」タブでシステムクロックを設定
  5. 「Project Manager」でプロジェクト名・IDEを選択(STM32CubeIDE / Keil / IAR)
  6. 「Generate Code」でHAL初期化コードを自動生成

生成されるファイル構成(STM32CubeIDE の場合)

MyProject/
├── Core/
│   ├── Inc/
│   │   ├── main.h          # ペリフェラルのdefine定義
│   │   ├── usart.h
│   │   └── i2c.h
│   └── Src/
│       ├── main.c          # メイン処理(ユーザーコードをここに追加)
│       ├── usart.c         # UART初期化コード(自動生成)
│       └── i2c.c           # I2C初期化コード(自動生成)
└── Drivers/
    ├── STM32F4xx_HAL_Driver/  # HALライブラリ本体
    └── CMSIS/
注意点

CubeMXで再生成すると、/* USER CODE BEGIN *//* USER CODE END */ の間に書いたコードは保持されますが、それ以外の箇所に直接書いたコードは上書きされます。必ずユーザーコード領域に記述してください。

UART通信の実装

UART(Universal Asynchronous Receiver/Transmitter)は、シリアル通信の中でも最もシンプルな方式です。デバッグ出力・PCとの通信・GPSモジュール・Bluetoothモジュール・各種センサーとの通信に広く使われます。

基本送受信(ポーリング方式)

最もシンプルな実装です。送信完了または受信完了まで CPU がブロックされます。

/* UART送信(ポーリング)*/
char tx_buf[] = "Hello STM32!\r\n";
HAL_UART_Transmit(&huart2, (uint8_t*)tx_buf, strlen(tx_buf), HAL_MAX_DELAY);

/* UART受信(ポーリング)*/
uint8_t rx_buf[64];
HAL_StatusTypeDef ret = HAL_UART_Receive(&huart2, rx_buf, 10, 1000); // 10byte, 1000msタイムアウト
if (ret == HAL_OK) {
    // 受信成功
}

割り込み方式(推奨)

実案件では受信タイミングが不定なため、割り込み方式を使います。CPUをブロックせず効率的に処理できます。

/* main.c – 受信割り込み開始 */
uint8_t rx_byte;
HAL_UART_Receive_IT(&huart2, &rx_byte, 1);  // 1バイト受信割り込みを有効化

/* stm32f4xx_it.c または main.c に追加 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART2) {
        // rx_byte に受信データが入っている
        process_received_byte(rx_byte);

        // 次の受信に備えて再度セット
        HAL_UART_Receive_IT(&huart2, &rx_byte, 1);
    }
}

/* DMA方式(大量データの高速受信)*/
uint8_t dma_rx_buf[256];
HAL_UART_Receive_DMA(&huart2, dma_rx_buf, 256);
// 256バイト受信完了でHAL_UART_RxCpltCallbackが呼ばれる

printfのリダイレクト(デバッグ用)

標準のprintfをUART経由でPCのターミナルに出力するよう設定できます。デバッグ作業が大幅に楽になります。

/* syscalls.c または main.c に追加 */
#include <stdio.h>

int __io_putchar(int ch)
{
    HAL_UART_Transmit(&huart2, (uint8_t*)&ch, 1, HAL_MAX_DELAY);
    return ch;
}

/* 使用例 */
printf("温度: %.1f ℃, 電圧: %.3f V\r\n", temp, voltage);

I2C通信の実装

I2C(Inter-Integrated Circuit)は、SDA(データ)とSCL(クロック)の2線式シリアル通信です。センサー・EEPROM・RTC・LCD・DAC/ADCなど、低〜中速のデータ転送が必要なデバイスとの通信に適しています。最大127台のデバイスを同一バスに接続できます。

メモリ書き込み・読み出し(EEPROMの例)

#define EEPROM_ADDR  0xA0  // 7bitアドレス 0x50 を左シフト → 0xA0

/* EEPROMへの書き込み(HAL_I2C_Mem_Write) */
uint8_t write_data[4] = {0x01, 0x02, 0x03, 0x04};
HAL_StatusTypeDef ret = HAL_I2C_Mem_Write(
    &hi2c1,          // I2Cハンドル
    EEPROM_ADDR,     // デバイスアドレス
    0x0000,          // メモリアドレス(書き込み先)
    I2C_MEMADD_SIZE_16BIT, // メモリアドレスサイズ
    write_data,      // 送信データ
    4,               // データ長
    HAL_MAX_DELAY
);

/* EEPROMからの読み出し(HAL_I2C_Mem_Read) */
uint8_t read_data[4];
HAL_I2C_Mem_Read(
    &hi2c1,
    EEPROM_ADDR,
    0x0000,
    I2C_MEMADD_SIZE_16BIT,
    read_data,
    4,
    HAL_MAX_DELAY
);

汎用デバイスの読み書き(レジスタアクセス)

/* センサーのレジスタ1バイトを読み出す */
uint8_t reg_addr = 0x75;  // WHO_AM_I レジスタ(例:MPU-6050)
uint8_t reg_data;

HAL_I2C_Master_Transmit(&hi2c1, 0xD0, ®_addr, 1, HAL_MAX_DELAY);  // レジスタアドレス送信
HAL_I2C_Master_Receive (&hi2c1, 0xD1, ®_data, 1, HAL_MAX_DELAY);  // データ受信

/* あるいは Mem_Read で簡潔に書ける */
HAL_I2C_Mem_Read(&hi2c1, 0xD0, 0x75, I2C_MEMADD_SIZE_8BIT, ®_data, 1, HAL_MAX_DELAY);
実案件でのポイント

STM32のI2C実装ではクロックストレッチ問題に注意が必要です。一部のセンサーはクロックストレッチを使用するため、STM32F1系ではドライバのバグが報告されています。STM32F4以降では改善されていますが、センサーのデータシートを必ず確認してください。プルアップ抵抗は4.7kΩを推奨します。

SPI通信の実装

SPI(Serial Peripheral Interface)は、MOSI・MISO・SCLK・NSS(CS)の4線式フルデュプレックス通信です。I2Cより高速(最大数十MHz)で、高サンプリングレートが必要なADC・加速度センサー・フラッシュメモリ・ディスプレイドライバなどに適しています。

フラッシュメモリ(W25Qシリーズ)への読み書き

#define FLASH_CS_PORT  GPIOA
#define FLASH_CS_PIN   GPIO_PIN_4

/* CS をアサート(Low)してSPI送受信 */
static void SPI_CS_Select(void)   { HAL_GPIO_WritePin(FLASH_CS_PORT, FLASH_CS_PIN, GPIO_PIN_RESET); }
static void SPI_CS_Deselect(void) { HAL_GPIO_WritePin(FLASH_CS_PORT, FLASH_CS_PIN, GPIO_PIN_SET); }

/* Manufacture/Device IDの読み出し(JEDEC ID: 0x9F) */
uint8_t tx[4] = {0x9F, 0x00, 0x00, 0x00};
uint8_t rx[4];

SPI_CS_Select();
HAL_SPI_TransmitReceive(&hspi1, tx, rx, 4, HAL_MAX_DELAY);
SPI_CS_Deselect();

// rx[1]:Manufacturer ID, rx[2]:Memory Type, rx[3]:Capacity
// W25Q64: 0xEF, 0x40, 0x17

/* ページ書き込み(256バイト単位) */
void Flash_PageWrite(uint32_t addr, uint8_t *data, uint16_t len)
{
    uint8_t cmd[4];
    cmd[0] = 0x02;            // Page Program コマンド
    cmd[1] = (addr >> 16) & 0xFF;
    cmd[2] = (addr >>  8) & 0xFF;
    cmd[3] = (addr      ) & 0xFF;

    SPI_CS_Select();
    HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY);   // コマンド+アドレス送信
    HAL_SPI_Transmit(&hspi1, data, len, HAL_MAX_DELAY); // データ送信
    SPI_CS_Deselect();
}

DMAによる高速SPI転送

ADCデータの高速収集やディスプレイのフレームバッファ転送など、大量データを扱う場合はDMAを使ってCPUをフリーにします。

/* DMA送信(ノンブロッキング)*/
HAL_SPI_Transmit_DMA(&hspi1, frame_buffer, FRAME_SIZE);

/* 転送完了コールバック */
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
    if (hspi->Instance == SPI1) {
        SPI_CS_Deselect();
        // 次のフレームの準備など
    }
}

CAN通信の実装

CAN(Controller Area Network)は、自動車・産業機器・医療機器などで標準的に使われる堅牢な差動通信プロトコルです。マルチマスター方式でノイズ耐性が高く、長距離・高ノイズ環境での多ノード通信に最適です。STM32F4・G4・H7系はbxCANコントローラを内蔵しています。

CAN送信の実装

/* CANの初期化(CubeMXで設定後、手動で有効化)*/
HAL_CAN_Start(&hcan1);
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING); // 受信割り込み有効化

/* CAN送信 */
CAN_TxHeaderTypeDef tx_header;
uint8_t tx_data[8];
uint32_t tx_mailbox;

tx_header.StdId = 0x123;         // 標準IDフレーム(11bit ID)
tx_header.ExtId = 0;
tx_header.IDE   = CAN_ID_STD;
tx_header.RTR   = CAN_RTR_DATA;  // データフレーム
tx_header.DLC   = 8;             // データ長(最大8バイト)
tx_header.TransmitGlobalTime = DISABLE;

// 送信データのセット
tx_data[0] = 0x01;
tx_data[1] = 0x02;
tx_data[2] = (uint8_t)(temperature * 10);  // 温度×10(小数点1桁)
// ...

HAL_CAN_AddTxMessage(&hcan1, &tx_header, tx_data, &tx_mailbox);

CAN受信(割り込み方式)

/* 受信割り込みコールバック */
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
    CAN_RxHeaderTypeDef rx_header;
    uint8_t rx_data[8];

    if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, rx_data) == HAL_OK) {
        // 受信IDで処理を振り分け
        switch (rx_header.StdId) {
            case 0x100:
                handle_motor_command(rx_data);
                break;
            case 0x200:
                handle_sensor_data(rx_data);
                break;
            default:
                break;
        }
    }
}

CANフィルタ設定(受信IDのフィルタリング)

CANバス上の全フレームをすべて受信すると処理負荷が増大します。受信したいIDだけを通過させるフィルタを設定します。

/* IDリストモード(特定のIDのみ受信) */
CAN_FilterTypeDef filter;

filter.FilterBank           = 0;
filter.FilterMode           = CAN_FILTERMODE_IDLIST;  // リストモード
filter.FilterScale          = CAN_FILTERSCALE_16BIT;
filter.FilterIdHigh         = 0x100 << 5;  // 受信するID1
filter.FilterIdLow          = 0x200 << 5;  // 受信するID2
filter.FilterMaskIdHigh     = 0x300 << 5;  // 受信するID3(マスクモード時はマスク値)
filter.FilterMaskIdLow      = 0x400 << 5;  // 受信するID4
filter.FilterFIFOAssignment = CAN_RX_FIFO0;
filter.FilterActivation     = ENABLE;

HAL_CAN_ConfigFilter(&hcan1, &filter);
産業現場での活用例

弊社では複数の製造設備をCAN接続したシステムを開発した実績があります。PLCとSTM32間のCAN通信で設備状態をリアルタイム収集し、生産管理システムへ転送するゲートウェイ装置です。CANの堅牢性により、工場の電気ノイズ環境でも安定稼働しています。

通信方式の使い分けガイド

実案件では複数の通信方式を組み合わせるケースがほとんどです。それぞれの特徴を理解して適切に選択することが重要です。

方式 速度 接続数 線数 主な用途
UART 最大数Mbps 1:1 2線(TX/RX) PCデバッグ、GPS、Bluetooth、各種モジュール
I2C 100k / 400k / 1Mbps 1:127 2線(SDA/SCL) センサー、EEPROM、RTC、LCD(近距離・多デバイス)
SPI 数十Mbps 1:N(CS切替) 4線+CS ADC、フラッシュ、ディスプレイ(高速・高精度)
CAN 最大1Mbps マルチマスター 差動2線 車載・産業機器・長距離・高ノイズ環境
Ethernet 100M / 1Gbps 多数 4線〜 クラウド連携・大量データ転送・Web通信
Bluetooth(BLE) 最大2Mbps 多数 無線 スマホ連携・ウェアラブル・IoT端末
典型的な組み合わせ例
  • センサー収集 + クラウド送信:I2C/SPI(センサー) + UART(デバッグ) + Ethernet(クラウド)
  • 産業用制御装置:CAN(設備間通信) + SPI(ADC) + I2C(表示器) + UART(設定用)
  • ウェアラブル機器:I2C(センサー) + BLE(スマホ連携) + SPI(フラッシュログ)

よくあるトラブルと対策

症状原因対策
UARTでデータ化け ボーレート不一致 / ノイズ 両デバイスのボーレートを再確認。過誤率(Error Rate)を計算して許容範囲内か確認。ケーブルを短くしシールド線を使用
I2CがHAL_BUSYを返す バスロックアップ(前回通信が異常終了) HAL_I2C_DeInit(&hi2c1); HAL_I2C_Init(&hi2c1); でリセット。またはGPIOをトグルしてSCLに9クロック印加してスレーブをリセット
SPIで受信データが0xFF / 0x00になる CS信号のタイミングミス / SPIモード不一致 データシートでSPIモード(CPOL/CPHA)を確認。NSS管理をソフトウェア制御(SSM=1)に変更して手動でCS操作
CANがACKエラーを出す 終端抵抗なし / ノード数1台 CANバスの両端に120Ω終端抵抗を必ず接続。テスト時は最低2ノード必要(ループバックモードで単体テスト可能)
DMA転送が途中で止まる DMAストリームの競合 / バッファオーバーフロー CubeMXでDMAストリームの競合がないか確認。ダブルバッファモードを使用して連続転送を安定化
デバッグツールの活用
  • ロジックアナライザ(例:Saleae Logic):UART/I2C/SPIの信号を可視化。プロトコルデコード機能でデータ内容も確認できる
  • オシロスコープ:信号の波形品質(立ち上がり・ノイズ)を確認
  • ST-LINK/V2 + STM32CubeIDE:ブレークポイント・変数ウォッチでリアルタイムデバッグ
  • CAN Analyzer(例:PEAK PCAN-USB):CANバスのトラフィックをモニタリング

まとめ

この記事のまとめ

  • STM32はUART・I2C・SPI・CANを全搭載し、産業機器から民生品まで幅広く対応できる
  • STM32CubeMXでペリフェラルの初期化コードを自動生成し、開発工数を大幅に削減できる
  • UART:デバッグ・外部モジュール接続、I2C:センサー・多デバイス接続、SPI:高速ADC・フラッシュ、CAN:産業・車載の多ノード通信 と使い分ける
  • 本番運用では割り込み・DMA方式を活用してCPUの処理効率を上げる
  • ロジックアナライザとオシロスコープを使ったハードウェアデバッグが通信トラブル解決の近道

STM32を使った組込みシステム開発・通信設計でお困りのことがあれば、弊社にお気軽にご相談ください。要件定義から設計・実装・量産対応まで、一貫してサポートします。