EPS32 で SPI のスレーブ通信を行う方法とと注意点を紹介します.
はじめに
ESP32 の SPI サンプルはいろいろありますが,どれも ESP32 がマスターになるもので,スレーブになるサンプルがなかったので紹介します.
次の観点に留意して記述しています.
- 利用者が多くて馴染みやすい,Aruduino を前提にすること
- CPU に負担を与えない,DMA の使用前提にすること
- 簡単に動作確認できるよう,SPI 通信のマスターとスレーブを同じ ESP32 で動かすこと
コード
ESP32 は SPI 全二重通信用のペリフェラルとして VSPI と HSPI の2つを内蔵していますが,今回は VSPI をマスター用に,HSPI をスレーブ用に使いました.
スレーブ側の処理の流れは次のようになります.
- バスを
spi_slave_initialize
で初期化. spi_slave_queue_trans
で送信データをセット.- マスターが通信を行うと,
spi_slave_interface_config_t
のpost_trans_cb
に指定したコールバック関数が呼ばれる. - 必要に応じて,受信値の読出しや次回の通信用の送信データのセットを行う.
- 3. に戻る.(次回通信用の送信データをセットした場合)
コード全体は下記のようになります.
マスターとスレーブの端子をジャンパで接続すれば,1秒ごとに通信を行います.使用端子は VSPI と HSPI のデフォルトのものを使用しています.具体的な番号はソース参照.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
#include "driver/spi_master.h" #include "driver/spi_slave.h" #include <Arduino.h> #include <SPI.h> // SPI 通信周波数 static const uint32_t SPI_CLK_HZ = 4500000; // 通信サイズ static const uint32_t TRANS_SIZE = 8192; // 通信バッファ uint8_t* spi_master_tx_buf; uint8_t* spi_master_rx_buf; uint8_t* spi_slave_tx_buf; uint8_t* spi_slave_rx_buf; // SPI マスタの設定 spi_transaction_t spi_master_trans; spi_device_interface_config_t spi_master_cfg; spi_device_handle_t spi_master_handle; spi_bus_config_t spi_master_bus; // SPI スレーブの設定 spi_slave_transaction_t spi_slave_trans; spi_slave_interface_config_t spi_slave_cfg; spi_bus_config_t spi_slave_bus; // VSPI の端子設定 static const uint8_t SPI_MASTER_CS = 5; static const uint8_t SPI_MASTER_CLK = 18; static const uint8_t SPI_MASTER_MOSI = 23; static const uint8_t SPI_MASTER_MISO = 19; // HSPI の端子設定 static const uint8_t SPI_SLAVE_CS = 15; static const uint8_t SPI_SLAVE_CLK = 14; static const uint8_t SPI_SLAVE_MOSI = 13; static const uint8_t SPI_SLAVE_MISO = 12; // デバッグ用のダンプ関数 void dump_buf(const char* title, uint8_t* buf, uint32_t start, uint32_t len) { if (len == 1) { Serial.printf("%s [%d]: ", title, start); } else { Serial.printf("%s [%d-%d]: ", title, start, start + len - 1); } for (uint32_t i = 0; i < len; i++) { Serial.printf("%02X ", buf[start + i]); } Serial.println(); } // デバッグ用の比較関数 void cmp_bug(const char* a_title, uint8_t* a_buf, const char* b_title, uint8_t* b_buf, uint32_t size) { for (uint32_t i = 0; i < size; i++) { uint32_t j = 1; if (a_buf[i] == b_buf[i]) { continue; } while (a_buf[i + j] != b_buf[i + j]) { j++; } dump_buf(a_title, a_buf, i, j); dump_buf(b_title, b_buf, i, j); i += j - 1; } } // SPI 通信に使用するバッファの初期化 void spi_buf_init() { spi_master_tx_buf = (uint8_t*)heap_caps_malloc(TRANS_SIZE, MALLOC_CAP_DMA); spi_master_rx_buf = (uint8_t*)heap_caps_malloc(TRANS_SIZE, MALLOC_CAP_DMA); spi_slave_tx_buf = (uint8_t*)heap_caps_malloc(TRANS_SIZE, MALLOC_CAP_DMA); spi_slave_rx_buf = (uint8_t*)heap_caps_malloc(TRANS_SIZE, MALLOC_CAP_DMA); for (uint32_t i = 0; i < TRANS_SIZE; i++) { spi_master_tx_buf[i] = i & 0xFF; spi_slave_tx_buf[i] = (0xFF - i) & 0xFF; } memset(spi_master_rx_buf, 0, TRANS_SIZE); memset(spi_slave_rx_buf, 0, TRANS_SIZE); } // マスタとして動作させる VSPI の初期化 void spi_master_init() { spi_master_trans.flags = 0; spi_master_trans.length = 8 * TRANS_SIZE; spi_master_trans.rx_buffer = spi_master_rx_buf; spi_master_trans.tx_buffer = spi_master_tx_buf; spi_master_cfg.mode = SPI_MODE3; spi_master_cfg.clock_speed_hz = SPI_CLK_HZ; spi_master_cfg.spics_io_num = SPI_MASTER_CS; spi_master_cfg.queue_size = 1; // キューサイズ spi_master_cfg.flags = SPI_DEVICE_NO_DUMMY; spi_master_cfg.queue_size = 1; spi_master_cfg.pre_cb = NULL; spi_master_cfg.post_cb = NULL; spi_master_bus.sclk_io_num = SPI_MASTER_CLK; spi_master_bus.mosi_io_num = SPI_MASTER_MOSI; spi_master_bus.miso_io_num = SPI_MASTER_MISO; spi_master_bus.max_transfer_sz = 8192; ESP_ERROR_CHECK(spi_bus_initialize(VSPI_HOST, &spi_master_bus, 1)); // DMA 1ch ESP_ERROR_CHECK( spi_bus_add_device(VSPI_HOST, &spi_master_cfg, &spi_master_handle)); } // スレーブの通信完了後に呼ばれるコールバック void spi_slave_tans_done(spi_slave_transaction_t* trans) { Serial.println("SPI Slave 通信完了"); } // スレーブとして動作させる HSPI の初期化 void spi_slave_init() { spi_slave_trans.length = 8 * TRANS_SIZE; spi_slave_trans.rx_buffer = spi_slave_rx_buf; spi_slave_trans.tx_buffer = spi_slave_tx_buf; spi_slave_cfg.spics_io_num = SPI_SLAVE_CS; spi_slave_cfg.flags = 0; spi_slave_cfg.queue_size = 1; spi_slave_cfg.mode = SPI_MODE3; spi_slave_cfg.post_setup_cb = NULL; spi_slave_cfg.post_trans_cb = spi_slave_tans_done; spi_slave_bus.sclk_io_num = SPI_SLAVE_CLK; spi_slave_bus.mosi_io_num = SPI_SLAVE_MOSI; spi_slave_bus.miso_io_num = SPI_SLAVE_MISO; spi_slave_bus.max_transfer_sz = 8192; ESP_ERROR_CHECK( spi_slave_initialize(HSPI_HOST, &spi_slave_bus, &spi_slave_cfg, 2)); // DMA 2ch } void spi_init() { spi_buf_init(); spi_master_init(); spi_slave_init(); } void setup() { Serial.begin(9600); spi_init(); } void loop() { spi_transaction_t* spi_trans; Serial.println("[LOOP]"); // スレーブの送信準備 ESP_ERROR_CHECK( spi_slave_queue_trans(HSPI_HOST, &spi_slave_trans, portMAX_DELAY)); // マスターの送信開始 ESP_ERROR_CHECK(spi_device_queue_trans(spi_master_handle, &spi_master_trans, portMAX_DELAY)); // 通信完了待ち spi_device_get_trans_result(spi_master_handle, &spi_trans, portMAX_DELAY); Serial.println("SPI Master 通信完了"); // チェック if (memcmp(spi_master_rx_buf, spi_slave_tx_buf, TRANS_SIZE)) { Serial.println("マスターの受信値が期待値と一致しません."); cmp_bug("受信値", spi_master_rx_buf, "期待値", spi_slave_tx_buf, TRANS_SIZE); } if (memcmp(spi_slave_rx_buf, spi_master_tx_buf, TRANS_SIZE)) { Serial.println("スレーブの受信値が期待値と一致しません."); cmp_bug("受信値", spi_slave_rx_buf, "期待値", spi_master_tx_buf, TRANS_SIZE); } delay(1000); } |
注意点
実は,上記を実行すると下記のような出力がされます.スレーブが末尾の 4Byte を受信できていません.
これは既知の問題のようで,公式フォーラムにも以下のやりとりがあります.シリコン修正しないと直らないようです.
SPI slave driver, last 4 Bytes missing (using DMA)
https://www.esp32.com/viewtopic.php?f=12&t=7339&sid=2257561718efae97d5b805c039b5764e&start=10
対策としては,以下のいずれかの方法があります.
- 4Byte 分余分に通信を行う
spi_slave_initialize
の最後の引数を 0 にして,DMA を使わないようにする
ただし,後者の場合,最大転送サイズは64Byteとなりますので,制約が大きいです.それより大きいサイズを指定すると次のようなエラーがでます.
1 |
spi_slave_queue_trans(263): data transfer > host maximum |
余談
今まで,ESP32 を Arduino から使う場合,Arduino IDE を使っていたのですが,最近は Visual Studio Code (VSCode)に PlatformIO を入れたものを愛用しています.
PlatformIO には ESP32 の製造元もコミットしているので,ボタンクリックだけで導入できるので楽です.さらに VSCode だと Arduino IDE にはない補完やタグジャンプが設定なしで有効になるので,初めて使うライブラリでもサクサク記述できます.
おすすめです.
コメント
SPI_MODE0 か SPI_MODE2であれば現象は確認されないようです。
詳細な記事をありがとうございました!こちらの記事を参考に手元でも動かしてみましたので、その時のメモをQiitaにまとめさせていただきました。