【ESP32】SPIの使い方(C言語)
こんにちは、星杜なぎさです。
今回は、ESP32-WROOM-32Eを使ってC言語でSPI通信を使ったサンプルを動作させてみました。
環境
- PC:Windows11 Home
- ESP-IDF : v5.5.1
- 使用言語:C言語
- マイコン:ESP32-WROOM-32E
1. SPIとは?
以下、参考サイト参照のこと
SPIの基本を学ぶ
2. ESP32でSPIを使う上での知識
- ESP32には4つのSPIが存在する(SPI0/1/2/3)
- SPI0とSPI1は、内蔵フラッシュなどで予約・使用済みのため、極力避けるべし
- つまり、SPI2とSPI3を使うのがベター(画像中の赤枠・青枠の部分)
3. 実用例
ここからは、SPI通信を使ってデバイスと通信するサンプルコードを記載していく。
3.1 microSDcard Adapter
以下の動作をするプログラムを作成しました。
- microSDcardにファイル"HELLO.TXT"を作成
- 文字列"Hello, ESP32 SPI microSD!"を書き込む
- 再度"HELLO.TXT"を読み込んで、内容を表示する
microSDcardの準備
microSDカードのフォーマットを"FAT32"にしてください(32GB以下推奨)
具体的なフォーマット方法
- USBなどで接続した後、エクスプローラーから「フォーマット」を選択
- フォーマットをFAT32にして、開始をクリック
- フォーマットが完了するまで待つ
- プロパティを見たときに、フォーマットがFATになってればOK
配線図
以下のように配線する。
| ESP32 | microSDcardAdapter |
|---|---|
| 3V3 | 3V3 |
| GPIO 5 | CS |
| GPIO 23 | MOSI |
| GPIO 18 | CLK |
| GPIO 19 | MISO |
| GND | GND |
プログラム
簡単に解説すると、以下の流れである
- 24~37行目:SPI通信のHOST(master)側の設定
- 39~42行目:SPI通信のDEVICE(slave)側の設定
- 44~51行目:microSDcardのマウント処理
- 59~67行目:microSDcard内にtxtファイルを作成し、書き込む
- 71行目:microSDcardをアンマウント処理
長いので折り畳み
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/spi_common.h"
#include "driver/sdspi_host.h"
#include "driver/gpio.h"
#include "sdmmc_cmd.h"
#include "esp_vfs_fat.h"
#include "esp_log.h"
#define MOUNT_POINT "/sdcard"
static const char *TAG = "SD_SPI";
void app_main(void)
{
esp_err_t ret;
sdmmc_card_t* card;
const char *text = "Hello, ESP32 SPI microSD!\n";
// SPIの設定
sdmmc_host_t host = SDSPI_HOST_DEFAULT();
spi_bus_config_t bus_cfg = {
.mosi_io_num = 23,
.miso_io_num = 19,
.sclk_io_num = 18,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 4000,
};
ret = spi_bus_initialize(host.slot, &bus_cfg, SDSPI_DEFAULT_DMA);
if (ret != ESP_OK){
ESP_LOGE(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(ret));
return;
};
// SDカードの設定
sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
slot_config.gpio_cs = 5; // CSピン
slot_config.host_id = host.slot;
// FATファイルシステムのマウント設定
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = 5,
.allocation_unit_size = 16 * 1024
};
ret = esp_vfs_fat_sdspi_mount(MOUNT_POINT, &host, &slot_config, &mount_config, &card);
if (ret != ESP_OK){
ESP_LOGE(TAG, "Failed to mount filesystem: %s", esp_err_to_name(ret));
return;
};
ESP_LOGI(TAG, "SD card mounted");
// ファイルの書き込み
FILE* f = fopen(MOUNT_POINT"/Hello.txt", "w");
if (f == NULL){
ESP_LOGE(TAG, "Failed to open file for writing");
esp_vfs_fat_sdcard_unmount(MOUNT_POINT, card);
return;
};
fprintf(f, "%s", text);
fclose(f);
ESP_LOGI(TAG, "File written");
// アンマウント
esp_vfs_fat_sdcard_unmount(MOUNT_POINT, card);
ESP_LOGI(TAG, "Card unmounted");
}
出力
正常に動作すれば、microSDカードに、HELLO.TXTという名前のファイルが書き込まれる。

3.2 加速度センサ:ADXL345
私の持っているADXL345は、SPI通信も対応していたため、使用してみた。
配線図
- SPI通信にありがちなMOSIやMISOではないため分かりづらいが、以下の配線でOK
| ESP32 | ADXL345 |
|---|---|
| GND | GND |
| 3V3 | VCC |
| GPIO 5 | CS |
| GPIO 19 | SDO |
| GPIO 23 | SDA |
| GPIO 18 | SCL |
こんな感じ。

プログラム
重要なところだけ解説する
- void spi_init(void)
-
spi_bus_initialize()でHOST側の設定 -
spi_bus_add_device()でSPI通信用のデバイスを追加する
-
- デバイスへの書き込みは
spi_device_transmit()で行う
長いので折り畳み
#include <stdio.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#define TAG "ADXL345_SPI"
// ピン定義
#define PIN_NUM_MISO 19 // SDOに繋ぐ
#define PIN_NUM_MOSI 23 // SDAに繋ぐ
#define PIN_NUM_CLK 18 // SCLに繋ぐ
#define PIN_NUM_CS 5 // CSに繋ぐ
#define SPI_HOST SPI3_HOST
// ADXL345 レジスタ
#define REG_DEVID 0x00
#define REG_POWER_CTL 0x2D
#define REG_DATA_FORMAT 0x31
#define REG_DATAX0 0x32
static spi_device_handle_t spi;
// SPI初期化
void spi_init(void)
{
spi_bus_config_t buscfg = {
.miso_io_num = PIN_NUM_MISO,
.mosi_io_num = PIN_NUM_MOSI,
.sclk_io_num = PIN_NUM_CLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
};
spi_device_interface_config_t devcfg = {
.clock_speed_hz = 1 * 1000 * 1000, // 1MHz
.mode = 3, // SPI Mode 3
.spics_io_num = PIN_NUM_CS,
.queue_size = 1,
};
ESP_ERROR_CHECK(spi_bus_initialize(SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));
ESP_ERROR_CHECK(spi_bus_add_device(SPI_HOST, &devcfg, &spi));
}
// 1バイト書き込み
void adxl_write(uint8_t reg, uint8_t data)
{
spi_transaction_t t = {0};
uint8_t tx[2];
tx[0] = reg & 0x3F; // 書き込み
tx[1] = data;
t.length = 8 * 2;
t.tx_buffer = tx;
spi_device_transmit(spi, &t);
}
// 複数バイト読み込み
void adxl_read(uint8_t reg, uint8_t *data, int len)
{
spi_transaction_t t = {0};
uint8_t tx[1 + len];
uint8_t rx[1 + len];
tx[0] = reg | 0xC0;
memset(&tx[1], 0, len);
t.length = (1 + len) * 8;
t.tx_buffer = tx;
t.rx_buffer = rx;
spi_device_transmit(spi, &t);
memcpy(data, &rx[1], len);
}
// 初期設定
void adxl_init(void)
{
// Standby
adxl_write(REG_POWER_CTL, 0x00);
// フル解像度 ±2g
adxl_write(REG_DATA_FORMAT, 0x08);
// 測定モード
adxl_write(REG_POWER_CTL, 0x08);
}
// 加速度取得
void read_accel(int16_t *x, int16_t *y, int16_t *z)
{
uint8_t data[6];
adxl_read(REG_DATAX0, data, 6);
*x = (int16_t)(((uint16_t)data[1] << 8) | data[0]);
*y = (int16_t)(((uint16_t)data[3] << 8) | data[2]);
*z = (int16_t)(((uint16_t)data[5] << 8) | data[4]);
}
// メイン
void app_main(void)
{
spi_init();
adxl_init();
ESP_LOGI(TAG, "ADXL345 Initialized");
while (1) {
int16_t x, y, z;
read_accel(&x, &y, &z);
ESP_LOGI(TAG, "X=%d Y=%d Z=%d", x, y, z);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
出力
このようにX, Y, Zの計測値が表示される。

3.3 OLEDディスプレイ:ssd1306
nopnop2002さんのgithubからesp-idf-ssd1306を使用させていただきました。
準備
- git cloneで、リポジトリをローカルに保存する
git clone https://github.com/nopnop2002/esp-idf-ssd1306.git
-
mainフォルダのある階層にgit cloneした中にあるcomponentsフォルダをコピーする

-
mainフォルダがある方のCMakeLists.txtを以下の1文を追記する。
set(EXTRA_COMPONENT_DIRS ../components/ssd1306)

- menuconfigの設定を書き換え
SSD1306 Configuration / Interface (I2C Interface) / SPI Interfaceにチェックを入れる
具体的なmenuconfigの操作画面
SSD1306 Configurationで右矢印キーを押す

Interface (I2C Interface)で右矢印キーを押す

( )SPI Interface で右矢印キーを押す

Escキーでホームまで戻り、Yボタンで設定を反映する

配線図
| ESP32 | SSD1306(SPI) |
|---|---|
| GND | GND |
| 3V3 | VCC |
| GPIO 18 | D0 |
| GPIO 23 | D1 |
| GPIO 15 | RES |
| GPIO 4 | DC |
| GPIO 5 | CS |
つまり、こんな感じ。

プログラム
nopnop2002さんのgithub上のプロジェクト「TextDemo」を使用しました。
出力

3.4 RFIDタグ:RFID-RC522
Sponge5さんのgithubを使わせていただきました。
準備
mainフォルダと同じ階層にcomponentsフォルダを作成し、そこでgit cloneする。
mkdir components
cd components
git clone https://github.com/Sponge5/rc522.git
依存関係を修正する。
rc522フォルダ内のCMakeLists.txtとidf_component.yml

mainフォルダの中のCMakeLists.txtに追記

配線図
SPI通信で接続する場合は、以下のように配線する
IRQ(割り込み)は、今回使用しないため未接続にしている
| ESP32 | RFID_RC522 |
|---|---|
| GPIO 5 | SDA |
| GPIO 18 | SCL |
| GPIO 23 | MOSI |
| GPIO 19 | MISO |
| -(N.C) | IRQ |
| GND | GND |
| GPIO 22 | RST |
| 3V3 | 3.3V |
こんな感じ。

プログラム
githubのexampleから、GPIOのPIN番号変更のみ行っています。
短いけど折り畳み
#include <esp_log.h>
#include <inttypes.h>
#include "rc522.h"
static const char* TAG = "rc522-demo";
static rc522_handle_t scanner;
static void rc522_handler(void* arg, esp_event_base_t base, int32_t event_id, void* event_data)
{
rc522_event_data_t* data = (rc522_event_data_t*) event_data;
switch(event_id) {
case RC522_EVENT_TAG_SCANNED: {
rc522_tag_t* tag = (rc522_tag_t*) data->ptr;
ESP_LOGI(TAG, "Tag scanned (sn: %" PRIu64 ")", tag->serial_number);
}
break;
}
}
void app_main()
{
rc522_config_t config = {
.spi.host = VSPI_HOST,
.spi.miso_gpio = 19,
.spi.mosi_gpio = 23,
.spi.sck_gpio = 18,
.spi.sda_gpio = 5,
};
rc522_create(&config, &scanner);
rc522_register_events(scanner, RC522_EVENT_ANY, rc522_handler, NULL);
rc522_start(scanner);
}
出力
カードやタグを近づけると、読み取った番号が表示されます。

4. まとめ
- ESP32でC言語を用いたSPI通信を使った事例を試した
- github上にある先駆者のコードを使うことで、簡単に使用することができた
- menuconfigなどの設定は見落としがちなので注意!
Discussion