ESP32 ソフトウェア 回路設計

ESP32のSPI通信が不安定になる原因|クロック・配線・信号品質の対策

投稿日:

ESP32でSPI通信を実装して、

「低速なら動くのに、クロックを上げると通信エラーになる」

「机の上では動いていたのに、基板にしたら不安定になった」

「同じプログラムなのに、ときどき受信データが化ける」

といった現象に遭遇することがあります。

SPI通信が一応動いているため、プログラムの問題なのか、配線なのか、クロックなのか判断しにくいトラブルです。

特にSPIは通信速度を上げるほど、単純な「HIGHとLOW」だけでは説明できない信号品質の影響が大きくなります。

今回はESP32-C5とESP-IDFを例に、

SPI通信が不安定になるとき、どこから確認すればよいのか

を実際の切り分け方法と合わせて解説します。

SPIクロックを下げて確認する

SPI通信が不安定な場合、最初に試したいのがクロック周波数を下げることです。

例えば現在20MHzで通信しているのであれば、

20MHz
 ↓
10MHz
 ↓
5MHz
 ↓
1MHz

のように下げて確認します。

ここで1MHzなら安定して通信できるのであれば、

  • SPI Modeが完全に間違っている
  • MOSI/MISOを逆接続している
  • CSがまったく動いていない

といった根本的な問題である可能性は低くなります。

一方、1MHzなら安定するが20MHzではエラーになる

のであれば、クロック速度によって顕在化する問題を疑えます。

このように、条件を一つずつ変えて原因候補を減らしていくことが重要です。

今回使用するサンプルコード

今回はSPI Deviceへ8byteを送信し、同時に8byteを受信する簡単なテストコードを使用します。

実際のセンサーなどに依存しないよう、SPI通信部分そのものを確認する構成です。

※実際に受信値を検証する場合は、接続するSPI Deviceの仕様に合わせて送信コマンドを変更してください。

#include <stdio.h>
#include <string.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#include "driver/spi_master.h"

#include "esp_err.h"
#include "esp_log.h"

#define SPI_HOST        SPI2_HOST

#define PIN_NUM_MOSI    GPIO_NUM_4
#define PIN_NUM_MISO    GPIO_NUM_5
#define PIN_NUM_CLK     GPIO_NUM_6
#define PIN_NUM_CS      GPIO_NUM_7

#define SPI_CLOCK_HZ    (1 * 1000 * 1000)

static const char *TAG = "SPI_TEST";

static spi_device_handle_t spi_handle;

static void spi_test_transfer(void)
{
    uint8_t tx_data[8] = {
        0x10, 0x20, 0x30, 0x40,
        0x50, 0x60, 0x70, 0x80
    };

    uint8_t rx_data[8] = {0};

    spi_transaction_t transaction = {
        .length = sizeof(tx_data) * 8,
        .tx_buffer = tx_data,
        .rx_buffer = rx_data,
    };

    esp_err_t ret = spi_device_transmit(
        spi_handle,
        &transaction);

    if (ret != ESP_OK)
    {
        ESP_LOGE(
            TAG,
            "SPI transmit failed: %s",
            esp_err_to_name(ret));

        return;
    }

    ESP_LOGI(
        TAG,
        "RX: %02X %02X %02X %02X %02X %02X %02X %02X",
        rx_data[0],
        rx_data[1],
        rx_data[2],
        rx_data[3],
        rx_data[4],
        rx_data[5],
        rx_data[6],
        rx_data[7]);
}

void app_main(void)
{
    spi_bus_config_t bus_config = {
        .mosi_io_num = PIN_NUM_MOSI,
        .miso_io_num = PIN_NUM_MISO,
        .sclk_io_num = PIN_NUM_CLK,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
    };

    ESP_ERROR_CHECK(
        spi_bus_initialize(
            SPI_HOST,
            &bus_config,
            SPI_DMA_CH_AUTO));

    spi_device_interface_config_t device_config = {
        .clock_speed_hz = SPI_CLOCK_HZ,
        .mode = 0,
        .spics_io_num = PIN_NUM_CS,
        .queue_size = 1,
    };

    ESP_ERROR_CHECK(
        spi_bus_add_device(
            SPI_HOST,
            &device_config,
            &spi_handle));

    ESP_LOGI(
        TAG,
        "SPI initialized: %d Hz",
        SPI_CLOCK_HZ);

    while (1)
    {
        spi_test_transfer();

        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

このコードではSPIクロックを、

#define SPI_CLOCK_HZ    (1 * 1000 * 1000)

としているため、1MHzで通信します。

まずこの状態で安定して通信できることを確認します。

クロック周波数を変更する

先ほどのサンプルコードでは、SPIクロックをマクロで定義しています。

#define SPI_CLOCK_HZ    (1 * 1000 * 1000)

そしてDevice登録時に、その値を使用しています。

抜粋コード

spi_device_interface_config_t device_config = {
.clock_speed_hz = SPI_CLOCK_HZ,
.mode = 0,
.spics_io_num = PIN_NUM_CS,
.queue_size = 1,
};

例えば5MHzで確認する場合は、

#define SPI_CLOCK_HZ    (5 * 1000 * 1000)

へ変更します。

ただし、この5MHzのコードは上記サンプルコードそのものではなく変更例です。

サンプルコードとして掲載している完成版では、最初の動作確認を目的として1MHzにしています。

「最大クロック」と「安定して使えるクロック」は別

SPI Deviceのデータシートを見ると、

Maximum SCLK Frequency:20MHz

などと記載されていることがあります。

ここで注意したいのは、

20MHzまで対応していることと、現在の基板・配線条件で20MHzが安定することは同じではない

ということです。

通信品質には、

  • ESP32側の信号
  • Device側の入力特性
  • PCBパターン
  • ジャンパ線
  • コネクタ
  • 配線長
  • 負荷容量
  • GND
  • 電源品質

なども関係します。

そのため開発初期では、まず低速で通信を成立させ、その後クロックを上げていく方が原因を切り分けやすくなります。

SPI Modeを確認する

クロックを十分下げても通信内容がおかしい場合は、SPI Modeも確認します。

今回のサンプルコードでは、

spi_device_interface_config_t device_config = {
    .clock_speed_hz = SPI_CLOCK_HZ,
    .mode = 0,
    .spics_io_num = PIN_NUM_CS,
    .queue_size = 1,
};

としているため、SPI Mode 0です。

接続するDeviceがMode 3を要求しているのであれば、この設定では正常に通信できません。

SPI Modeは、

  • CPOL
  • CPHA

によって決まり、Deviceのデータシートに指定されています。

通信速度を下げても改善しない場合には、クロック周波数だけを疑い続けず、Mode設定も再確認します。

エラーと「データ化け」は分けて考える

SPI通信のトラブルでは、

ESP-IDFがエラーを返す場合

と、

APIとしては成功しているのに受信値がおかしい場合

を分けて考える必要があります。

今回のサンプルコードでは、

esp_err_t ret = spi_device_transmit(
    spi_handle,
    &transaction);

if (ret != ESP_OK)
{
    ESP_LOGE(
        TAG,
        "SPI transmit failed: %s",
        esp_err_to_name(ret));

    return;
}

として、spi_device_transmit()の戻り値を確認しています。

ここでESP_OKが返ったからといって、

接続先Deviceから正しいデータが返ってきたことまで保証されるわけではありません。

SPI Master Driverから見ればトランザクション処理が正常に完了していても、

  • Deviceがコマンドを正しく認識していない
  • SPI Modeが違う
  • 信号品質が悪い
  • 読み出すレジスタを間違えている

などによって受信値がおかしくなることがあります。

受信値をログで確認する

そこで今回のサンプルでは、受信した8byteをログへ出しています。

ESP_LOGI(
    TAG,
    "RX: %02X %02X %02X %02X %02X %02X %02X %02X",
    rx_data[0],
    rx_data[1],
    rx_data[2],
    rx_data[3],
    rx_data[4],
    rx_data[5],
    rx_data[6],
    rx_data[7]);

例えば、

1MHz  → 毎回同じ正常値
5MHz  → 毎回同じ正常値
10MHz → ときどき異常値
20MHz → 頻繁に異常値

という結果になった場合、

通信速度と異常発生に相関があることが分かります。

この情報が得られれば、ソフトウェアのロジックだけでなく、信号品質やタイミングを確認する段階へ進めます。

ジャンパ線では動作条件が変わる

ブレッドボードやジャンパ線でSPI通信を確認する場合は、特に高速通信へ注意が必要です。

SPIクロックを高速化すると、ジャンパ線も単なる「導線」としてだけでは扱えなくなってきます。

例えば、

ESP32
 │
 │ 長いジャンパ線
 │
 ├──────── SCLK
 ├──────── MOSI
 ├──────── MISO
 └──────── CS
                    │
                 SPI Device

のような構成では、配線長やGNDの取り方によって波形が悪化することがあります。

低速では問題が見えなくても、高速化したときに初めて問題が現れるケースがあります。

確認したい切り分け順序

SPI通信が不安定なとき、私は次のように原因候補を減らしていくのが良いと考えています。

① SPIクロックを下げる
        ↓
② SPI Modeを確認
        ↓
③ CS動作を確認
        ↓
④ MOSI / MISO / SCLKを確認
        ↓
⑤ GND・電源を確認
        ↓
⑥ オシロスコープで波形確認
        ↓
⑦ 信号品質対策を検討

重要なのは、最初からすべてを変更しないことです。

クロック、Mode、配線、抵抗値を同時に変更してしまうと、

何を変更したことで改善したのか分からなくなります。

一つ変更して、一つ結果を見る。

これはSPIに限らず、組み込み開発のデバッグ全般で有効な考え方です。

SCLK波形を確認する

SPI通信が低速では安定しているのに、高速化すると不安定になる場合は、まずSCLK波形を確認したいところです。

デジタル信号は、論理的にはHIGHとLOWの2値ですが、実際の配線上では立ち上がり時間、立ち下がり時間、オーバーシュート、リンギングなどの影響を受けます。

特にSCLKは一定周期で繰り返し変化するため、信号品質の問題が顕在化しやすい信号です。

立ち上がり・立ち下がりが遅い場合

SCLKのエッジが鈍っていると、Device側でしきい値を跨ぐタイミングが曖昧になります。

理想波形

┌─────┐      ┌─────┐
│          │      │          │
│          │      │          │
┘          └───┘          └─────


立ち上がりが遅い波形

   /─────
 /
┘

のような違いです。

クロック周波数が低いと多少エッジが遅くても余裕がありますが、高速化するとセットアップ時間やホールド時間に対する余裕が減ります。

その結果、同じ回路でも1MHzでは正常、20MHzでは不安定という現象が起こります。

オーバーシュートとリンギング

高速なデジタル信号では、配線長やドライバの立ち上がり速度によって、波形が一度大きく振れてから振動することがあります。

これがオーバーシュートやリンギングです。

理想

──────┐
            │
            └──────

リンギングあり

─────╮╲╱╲╱────────

この振動が入力しきい値を何度も跨ぐと、Device側が余分なクロックエッジとして認識する可能性があります。

特にSCLKで発生すると、1bit余計に読み込む、ビット位置がずれる、といった問題につながることがあります。

シリーズ抵抗を検討する

リンギングが確認された場合、SCLKやMOSIへシリーズ抵抗を追加すると改善することがあります。

例えば、

ESP32 GPIO ──[22Ω〜47Ω]── SCLK ── SPI Device

のように、信号源の近くへ抵抗を配置します。

シリーズ抵抗は信号の立ち上がりを少し緩やかにし、反射やリンギングを抑える目的で使用します。

ただし、

とりあえず抵抗を入れればよい

というものではありません。

抵抗値が大きすぎると立ち上がり・立ち下がりが遅くなり、高速通信では逆にタイミング余裕を減らす可能性があります。

そのため実際には、オシロスコープで波形を確認しながら調整するのが理想です。

SCLKだけでなくMOSIも確認する

クロックだけ正常でも、MOSI側のデータ波形が崩れていれば通信は不安定になります。

確認したいのは、

  • SCLK
  • MOSI
  • MISO
  • CS

の4信号です。

特に、SCLKとMOSIを同時に観測すると、

データがクロックエッジに対して十分早く確定しているか

を確認しやすくなります。

CSのタイミングを確認する

通信開始直前や終了直後のCSタイミングも重要です。

今回のサンプルコードでは、Device登録時にCSをESP-IDFへ任せています。

spi_device_interface_config_t device_config = {
    .clock_speed_hz = SPI_CLOCK_HZ,
    .mode = 0,
    .spics_io_num = PIN_NUM_CS,
    .queue_size = 1,
};

この場合、spi_device_transmit()に合わせてSPI Master DriverがCSを制御します。

esp_err_t ret = spi_device_transmit(
    spi_handle,
    &transaction);

それでも接続するDeviceによっては、

  • CSを下げてから最初のクロックまでの待ち時間
  • 最後のクロックからCSを上げるまでの待ち時間
  • トランザクション間の待ち時間

などに条件がある場合があります。

そのため、DeviceのデータシートにあるCS timingも確認します。

GNDリターンパスを確認する

SPI信号はGPIOからDeviceへ流れて終わりではありません。

電流はGND側を通って戻ります。

つまり、信号線だけでなくリターンパスも重要です。

例えばPCB上でSCLKの直下に連続したGNDプレーンがあれば、比較的きれいなリターンパスを確保できます。

一方、

  • GNDプレーンが途中で分断されている
  • スリットを跨いでいる
  • コネクタ経由で遠回りしている

といった状態では、信号品質が悪化する可能性があります。

高速SPIでは、

信号線だけを見るのではなく、戻り電流がどこを流れるか

まで考える必要があります。

配線の分岐を短くする

複数DeviceへSPIを分岐する場合、前回の記事で紹介したようにSCLK・MOSI・MISOを共有できます。

ただし、ハードウェア的には分岐が増えるほど信号品質は厳しくなります。

例えば、

ESP32
  │
  ├──────────── Device A
  │
  └──────────── Device B

よりも、

ESP32 ──────────┬── Device A
                          └── Device B

のように、長いスタブができる構成では反射の影響が出やすくなります。

特にSCLKは、不要に長い分岐を避けた方が安全です。

ブレッドボードと製品基板は同じではない

開発初期にブレッドボードやジャンパ線で動作確認できたとしても、製品基板で必ず同じ結果になるとは限りません。

逆に、ブレッドボードでは10MHzが不安定でも、PCBでは問題なく20MHzで動作することもあります。

これは、

  • 配線長
  • 配線インピーダンス
  • GND
  • コネクタ
  • Device配置

などの条件が異なるためです。

そのため、

試作環境で確認できた最大クロック = 製品での最終クロック

とは考えない方がよいです。

ロジックアナライザとオシロスコープの使い分け

SPIデバッグでは、ロジックアナライザとオシロスコープの両方が役立ちます。

ロジックアナライザ

向いている確認項目は、

  • MOSIデータ
  • MISOデータ
  • CS
  • SPI Mode
  • コマンド内容
  • 通信順序

です。

プロトコルデコーダを使えば、送受信データをHEX表示できるため非常に便利です。

オシロスコープ

向いているのは、

  • 立ち上がり時間
  • 立ち下がり時間
  • オーバーシュート
  • リンギング
  • ノイズ
  • 電圧レベル
  • クロックとデータのタイミング関係

です。

つまり、

通信内容を見る → ロジックアナライザ
波形品質を見る → オシロスコープ

という使い分けが基本です。

「ロジアナで正常」は信号品質が正常とは限らない

ここは注意したいポイントです。

ロジックアナライザは入力信号をHIGH / LOWとして判定します。

そのため、多少波形が崩れていても、しきい値を正しく跨いでいれば正常に見えることがあります。

一方、実際のSPI Deviceでは入力条件が異なり、同じ波形でも誤動作する可能性があります。

つまり、

ロジックアナライザで正しくデコードできた

だけで、信号品質まで保証されたわけではありません。

不安定現象がある場合は、オシロスコープでも確認する価値があります。

電源品質も確認する

SPI通信が高速化すると、Device側の内部回路も高速に動作します。

そのため、電源が不安定な場合は通信エラーとして現れることがあります。

確認したいのは、

  • 3.3V電源
  • デカップリングコンデンサ
  • GND
  • Device直近の電源波形

です。

特にDeviceの近くにデカップリングコンデンサがない場合や、長い配線で電源を供給している場合は注意が必要です。

不安定な通信を切り分ける実践フロー

今回の記事の内容を実際の切り分け順にまとめると、次のようになります。

       SPI通信が不安定
               ↓
       クロックを下げる
               ↓
           安定する?
 ┌──────┴──────┐
YES               NO
 ↓               ↓
信号品質を疑う   Mode・配線・CS確認
 ↓
SCLK/MOSI観測
 ↓
リンギング確認
 ↓
配線・GND確認
 ↓
必要ならシリーズ抵抗
 ↓
徐々にクロックUP

この順番なら、無闇に条件を変更せずに原因を絞り込めます。

実務で大切なのは「最大速度」ではない

SPI通信では高速化に目が向きがちですが、製品設計では必ずしも最大クロックで使う必要はありません。

例えば20MHzまで対応しているDeviceでも、5MHzで必要な処理が十分間に合うなら、あえて5MHzで使う判断もあります。

通信速度を上げれば、

  • 信号品質
  • EMI
  • クロストーク
  • タイミング余裕

は厳しくなります。

必要な性能を満たしているなら、

余裕を持った通信速度を選ぶ

ことも立派な設計判断です。

まとめ

ESP32のSPI通信が不安定な場合、プログラムだけを疑うのではなく、クロック・配線・GND・信号品質まで含めて確認する必要があります。

特に、

  • 低速では安定する
  • 高速化するとエラーが増える

という現象は、重要な手掛かりです。

まずクロックを下げ、

次にSPI ModeやCSを確認し、

それでも高速化時だけ問題が出るのであれば、オシロスコープで信号品質を確認します。

必要に応じて、

  • 配線の短縮
  • GNDリターンパス改善
  • シリーズ抵抗
  • クロック周波数の見直し

などを検討します。

SPI通信では、

「最大何MHzまで出せるか」より、「必要な性能を安定して満たせるか」

の方が重要です。

安定した条件を一つずつ確認しながら、必要な性能へ近づけていく。

この進め方が、結果的に最短のデバッグになることがあります。

技術は、経験から価値になる。

DLROW Design|現場エンジニア

スポンサーリンク

スポンサーリンク

-ESP32, ソフトウェア, 回路設計

Copyright© 江藤良樹の仕事は物づくり , 2026 All Rights Reserved.