0x6A Logbook

0x6A Logbook
Shi6a的筆記本
  1. 首頁
  2. 自動化技巧
  3. 正文

ESP32 與 STM32 SPI 通訊實戰:從時序圖看懂資料傳輸

2026 年 5 月 21 日 617點熱度 0人點贊 0條評論

ESP32 與 STM32 SPI 通訊實戰封面

前言:SPI 是嵌入式開發的必修課,但它其實不難

SPI 由 Motorola 在 1980 年代提出,定位很明確:全雙工、同步、主從式的通訊。它用四條線解決問題,換來的是比 I2C 快得多的速度(可達 40 MHz 以上)。

缺點也很老實:每多一個 Slave 就多一條 CS 線。所以常見的配置是「一個 Master、少數幾個 Slave」,而不是像 I2C 那樣掛一整排。

四條線解決什麼問題

訊號線 全名 作用
SCLK / SCK Serial Clock 時脈,由 Master 產生
MOSI Master Out Slave In Master → Slave 資料線
MISO Master In Slave Out Slave → Master 資料線
CS / SS Chip Select / Slave Select 片選,低電位有效

把方向畫出來就更容易理解為什麼它叫「全雙工」:

SPI 四條線的資料方向

時序圖:Mode 0 的一個 byte

Mode 0(CPOL = 0、CPHA = 0)是最常見的組合,看懂它就懂八成了:

SPI Mode 0 的時序波形

四個觀察重點:CS 拉低表示開始、SCLK 由 Master 產生、MOSI 在每個上升緣送出一個 bit(從 D7 開始)、MISO 在同一個上升緣同步送回資料。一個 byte 就是 8 個時脈週期。

送出一個 byte 的完整過程

四種 Mode:CPOL 與 CPHA 的組合

Mode CPOL CPHA 空閒 CLK 資料採樣 常用裝置
0 0 0 低電位 上升緣 W25Qxx、ST7735、MAX6675
1 0 1 低電位 下降緣 某些 ADC
2 1 0 高電位 下降緣 NRF24L01
3 1 1 高電位 上升緣 某些感測器

記法很簡單:Mode 0 與 Mode 3 都是上升緣採樣,差別只在空閒時 CLK 是高還是低。而且 90% 的感測器都用 Mode 0,所以不確定時先用它。

Mode 0 與 Mode 3 的差異

ESP32 當 Master

Arduino IDE 環境下的 SPI Master 寫起來很短,重點只有「設定」與「選取 Slave」兩件事:

#include <SPI.h>

#define CS_PIN   5
#define SCK_PIN  18
#define MOSI_PIN 23
#define MISO_PIN 19

#define SPI_FREQ  1000000
#define SPI_MODE  SPI_MODE0

void setup() {
    Serial.begin(115200);
    SPI.begin(SCK_PIN, MISO_PIN, MOSI_PIN, CS_PIN);
    SPI.beginTransaction(SPISettings(SPI_FREQ, MSBFIRST, SPI_MODE));
    pinMode(CS_PIN, OUTPUT);
    digitalWrite(CS_PIN, HIGH);
    Serial.println("ESP32 SPI Master 已啟動");
}

void loop() {
    uint8_t tx_data[] = {0xAA, 0xBB, 0xCC, 0xDD};
    uint8_t rx_data[4] = {0};

    digitalWrite(CS_PIN, LOW);
    delayMicroseconds(10);

    for (int i = 0; i < 4; i++) {
        rx_data[i] = SPI.transfer(tx_data[i]);
    }

    digitalWrite(CS_PIN, HIGH);
    delayMicroseconds(10);

    Serial.print("TX: ");
    for (int i = 0; i < 4; i++) {
        Serial.printf("%02X ", tx_data[i]);
    }
    Serial.print("| RX: ");
    for (int i = 0; i < 4; i++) {
        Serial.printf("%02X ", rx_data[i]);
    }
    Serial.println();

    delay(1000);
}

STM32 當 Slave

Slave 端用 CubeMX 配置成 SPI Slave,並用中斷方式接收:

/* STM32 SPI Slave 接收程式 */
/* 使用 STM32CubeMX 配置 SPI1 為 Slave */

#include "main.h"

SPI_HandleTypeDef hspi1;
uint8_t rx_buffer[4];
uint8_t tx_buffer[4] = {0x11, 0x22, 0x33, 0x44};
volatile uint8_t spi_ready = 0;

void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) {
    if (hspi->Instance == SPI1) {
        spi_ready = 1;
    }
}

int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_SPI1_Init();

    HAL_SPI_Receive_IT(&hspi1, rx_buffer, 4);

    while (1) {
        if (spi_ready) {
            spi_ready = 0;
            HAL_SPI_TransmitReceive_IT(&hspi1, tx_buffer, rx_buffer, 4);
            printf("SPI 收到: ");
            for (int i = 0; i < 4; i++) {
                printf("%02X ", rx_buffer[i]);
            }
            printf("\r\n");
        }
    }
}

void MX_SPI1_Init(void) {
    hspi1.Instance = SPI1;
    hspi1.Init.Mode = SPI_MODE_SLAVE;
    hspi1.Init.Direction = SPI_DIRECTION_2LINES;
    hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
    hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
    hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
    hspi1.Init.NSS = SPI_NSS_HARD_INPUT;
    hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
    hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
    hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
    hspi1.Init.CRCPolynomial = 10;
    HAL_SPI_Init(&hspi1);
}

接線與電壓

ESP32(Master) → STM32(Slave)
GPIO18 (SCK) → PA5 (SCK)
GPIO23 (MOSI) → PA7 (MOSI)
GPIO19 (MISO) → PA6 (MISO)
GPIO5 (CS) → PA4 (NSS)
GND → GND

SPI 接線與電壓注意事項

這一段看起來像廢話,但 SPI 的問題有一半在這裡:MOSI 接 MOSI、MISO 接 MISO、務必共地、確認兩邊邏輯電壓相同。

用邏輯分析儀驗證

時序圖本身可以用 Python + WaveDrom 產生,先定義再驗證:

{
    "signal": [
        {"name": "SCLK", "wave": "P....|...."},
        {"name": "CS",   "wave": "1.0..|..0."},
        {"name": "MOSI", "wave": "0.101|01.0"},
        {"name": "MISO", "wave": "0..0.|.1.0"},
        {"name": "Data", "wave": "x.==.|==.x", 
         "data": ["D7","D6","D5","D4","D3","D2","D1","D0"]}
    ]
}

接上邏輯分析儀(Saleae、Kingst、DSLogic 等)之後,你應該看到:

邏輯分析儀上應該看到的波形

四個常見問題

SPI 四個常見問題

其中 Q3 最隱蔽:STM32 的 NSS 在 Slave 模式下必須設為 Hardware Input。設成 Software 的話,硬體不會自動識別 CS 訊號,你會覺得「明明線都對,它就是不反應」。

總結

SPI 的知識點其實只有幾條,但每一條都會在實作時咬人:

  • 四條線、一個 Master:SCLK、MOSI、MISO、每個 Slave 一條 CS
  • 全雙工:送與收共用同一組時脈,同時發生
  • Mode 0 是預設:90% 的感測器都用它,不確定就先試這個
  • CS 要拉對:收到全 0xFF 或全 0x00 時,第一個要查的就是它
  • 時脈別亂催:麵包板超過 20 cm 就開始不穩,必要時降到 500 kHz

把這五點記住,剩下的就只剩查 datasheet 上的 Mode 與時序參數了。

標籤: 教學
最後更新:2026 年 9 月 30 日

shi6a

這個人很懶,什麼都沒留下

點贊
下一篇 >

文章評論

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回覆

COPYRIGHT © 2026 0x6A Logbook. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang