【ESP32-IDF】高級外設開發4:SPI

系列文章目錄

持續更新中…


文章目錄

  • 系列文章目錄
  • 前言
  • 一、SPI概述
    • 1.主要功能
    • 2.SPI控制器架構
    • 3.SPI通信模式
    • 4.SPI數據幀與事務
    • 5.DMA與傳輸性能
    • 6.中斷與驅動事件
  • 二、SPI類型定義及相關API
  • 三、SPI示例程序
  • 總結


前言

在嵌入式開發中,SPI(串行外設接口)是一種常用的高速同步串行通信接口。ESP32 作為一款高性能 MCU,集成了多組 SPI 控制器,支持多主從設備連接、雙全工/半雙工通信以及 DMA 數據傳輸等高級功能。本篇文章將深入解析 ESP32(以 ESP32-S3 為例)的 SPI 外設,從硬件架構到軟件使用,幫助開發者掌握其高級應用技巧。


一、SPI概述

SPI 協議是由摩托羅拉公司提出的通訊協議 (Serial Peripheral Interface),即串行外圍設備接口,是一種高速全雙工的通信總線。它被廣泛地使用在 ADC設備、LCD 等設備與 MCU 間,要求通訊速率較高的場合。

1.主要功能

ESP32 系列芯片集成了 4 個 SPI 控制器(SPI0/1/2/3)。其中 SPI0 和 SPI1 主要用于內部連接 Flash 和 PSRAM,不提供給用戶;SPI2 和 SPI3 則作為通用 SPI(GP-SPI)對外開放,可用于連接各類 SPI 從設備。

SPI 控制器支持主機/從機模式,默認作為主機使用,可獨立配置時鐘頻率(ESP32-S3 默認時鐘源為 80 MHz APB,可分頻得到常用頻率,部分模式下最高支持 80MHz,特定八線模式下可達 120MHz)、數據傳輸模式(SPI Mode0/1/2/3,對應時鐘極性CPOL和相位CPHA組合)、傳輸字節序(MSB或LSB優先)等。SPI 支持 全雙工 同步收發(默認 MOSI/MISO 雙線),也支持 半雙工 模式(如三線 SPI,共用單數據線收發)。
GP-SPI2 和 GP-SPI3 支持的數據模式
在這里插入圖片描述
ESP32 的 SPI 控制器具備多線并行傳輸能力,可工作在 Dual SPI、Quad SPI、Octal SPI等模式以提升吞吐量(常用于高速閃存、顯示屏等外設)。在數據傳輸方面,SPI 硬件提供發送/接收 FIFO 緩沖,并且可以結合 DMA(直接存儲器訪問)實現大數據塊的高速搬運,減少CPU負擔。通過上述功能組合,ESP32 的 SPI 接口既適用于傳感器等中低速外設,也能勝任顯示屏、存儲等大數據量高速場景。

2.SPI控制器架構

ESP32-S3 內部的 4 個 SPI 控制器架構如圖所示
在這里插入圖片描述
SPI0 和 SPI1 控制器共享一套外部總線信號(稱作 SPI 主存儲總線,包括 D/Q 數據線、CS0CS2 片選、CLK 時鐘、WP/HD 輔助線等),通過硬件仲裁器實現對外部 Flash/PSRAM 的訪問(SPI0 常用于緩存操作,SPI1 用于向 Flash 寫入等)。由于這兩路總線承載著程序存儲器,IDF 驅動并不支持用戶直接操作 SPI0/1 控制器。SPI2 和 SPI3 控制器則擁有各自獨立的通用信號總線(通常也稱 HSPI 和 VSPI),可自由映射到支持輸出的任意 GPIO

在 ESP32-S3 中,SPI2 控制器提供6 條片選線(CS0-CS5),SPI3 提供3 條片選線(CS0-CS2),意味著單個 SPI 主機最多可掛載 6 個或 3 個從設備。硬件會根據片選自動控制總線占用,實現多設備的時分復用。此外,SPI1~SPI3 控制器共用兩個 DMA 通道資源:當啟用 DMA 傳輸時,硬件可在這兩個 DMA 信道上調度數據搬運,從而實現高吞吐的連續讀寫。

每個 SPI 控制器內部包含發送/接收 FIFO(深度為 64 字節)、時鐘分頻器、模式控制邏輯等模塊。在主模式下,ESP32-S3 的 SPI 控制器通過配置寄存器即可自動完成從 拉低 CS、發送指令/地址、讀寫數據,到釋放 CS 的整個事務序列,期間支持硬件插入空周期以及精確的時序控制。ESP32 的 SPI 硬件架構為多主多從、高速大數據傳輸提供了靈活且強大的支撐。

3.SPI通信模式

SPI 通信由主設備產生時鐘并發起傳輸。ESP32 的 SPI 主控模式下支持 4 種時序模式(Mode0/1/2/3),分別對應時鐘空閑電平和數據采樣時機的不同組合:(0,0)、(0,1)、(1,0)、(1,1)。這些模式可以通過設備配置的 mode 參數來設置,以匹配不同 SPI 從設備的時序要求。
GP-SPI 功能塊圖:
在這里插入圖片描述
數據線方面,默認 SPI 使用 MISO 和 MOSI 兩根數據線實現全雙工通信——在一個時鐘周期內,主機從 MOSI 發送1比特的同時,也從 MISO 接收1比特。如果外設不需要同時發送數據,或硬件只有單數據引腳,可以將 SPI 配置為 半雙工 模式,此時主機可以使用同一引腳(連接在 SPI 的 MOSI 引腳上)分時發送和接收數據,即經典“三線 SPI”接口(CLK、DATA、CS)。

開啟半雙工模式的方法是在設備配置的標志位中設置 SPI_DEVICE_HALFDUPLEX(IDF 會自動管理數據線方向)。除了標準的單比特串行,ESP32 的 GP-SPI 控制器還支持 多路并行模式:例如 Dual SPI(雙線)和 Quad SPI(四線)模式。在這些模式下,主機將同時使用 2 個或 4 個數據引腳進行并行傳輸,大幅提高有效帶寬。這通常用于與支持多I/O模式的存儲芯片或顯示屏通信。要使用并行模式,需要硬件上將 SPI 的 WP/HD 等引腳連接到從設備,并在驅動中啟用相應的總線標志(如 SPICOMMON_BUSFLAG_DUAL/QUAD 等)以及在事務中設置 SPI_TRANS_MODE_DIO/QIO 標志。

并行模式通常意味著通信只能半雙工進行(因為數據線被復用為輸出),因此驅動要求在多線模式下設備標志需包含 SPI_DEVICE_HALFDUPLEX。在大多數應用中,標準 4 線 SPI 已能滿足需求,而當追求極致速度時,可考慮使用并行模式并合理調整時序以確保可靠性。

4.SPI數據幀與事務

**SPI 的主從通信以事務為單位完成。**一次完整的 SPI 主機事務通常包括以下階段:主機拉低 CS(片選)以選中目標從設備,然后依次發送命令碼(可選,016位)、地址(可選,064位)、插入若干空等待周期(Dummy,滿足從設備時序要求),接著進入數據傳輸階段,包括發送數據(寫階段)和/或接收數據(讀階段),最后主機釋放 CS 結束該事務。

這些階段是否存在及長度,取決于設備配置和每次事務配置。例如,對某些存儲器或顯示屏操作,可能需要在正式數據之前發出命令或地址信息;而對一般傳感器可能只需簡單的讀寫數據而無額外命令。ESP-IDF 提供的 spi_device_interface_config_t 結構體中有專門的字段用于配置默認的命令位數和地址位數,以及每次事務可以按需調整的數據長度。在執行事務時,驅動會根據這些配置自動完成前序命令/地址的發送,然后進行數據階段。
主機模式下數據流控制:
在這里插入圖片描述
SPI 的讀寫可以同時發生:在全雙工模式下,當主機發送每一比特時,從設備的輸出比特會同步被采集,這樣讀階段和寫階段實際上重疊進行,事務總時長取決于兩者中較長的一個。如果不希望同時讀寫(例如從設備要求先發后收),可以使用半雙工模式,在事務配置中分別指定發送數據長度和接收數據長度,驅動將按先發送后接收的順序完成。對于不需要的讀或寫相位,可以將對應緩沖區指針設為 NULL,SPI 控制器將自動跳過該階段。
從機模式下數據流控制:

在這里插入圖片描述
總的來說,ESP32 SPI 通過硬件支持靈活的事務分段和自動片選控制,使復雜協議的實現更加簡潔高效。

5.DMA與傳輸性能

當進行小數據量傳輸時(比如幾字節),SPI 主機驅動可以直接通過 CPU 向硬件 FIFO 寄存器寫入/讀取數據完成通信;這種方式開銷低、速度快。但是對于較大數據塊(幾十上百字節乃至數KB),頻繁的中斷和字節搬運會給 CPU 帶來較大負擔。

為此,ESP32-S3 的 SPI 控制器支持 DMA(直接內存訪問)傳輸:通過給 spi_bus_initialize 提供 DMA通道參數,驅動將在發送/接收超過 FIFO 深度的數據時,自動啟用 DMA 控制器將數據塊搬運到 SPI FIFO。這使得單次事務可以發送非常長的數據(IDF 默認在 DMA 模式下單次傳輸可達約4092字節,理論上可調整受內存限制),同時將 CPU 從逐字節搬運中解放出來。ESP32-S3 的 SPI2 和 SPI3 控制器各自配備 DMA請求接口(共享2個 DMA信道),通過配置可分別占用一個 DMA通道實現并行數據傳輸。需要注意:如果選擇使用 DMA,則發送/接收緩沖區必須放在可被DMA訪問的內存區域(例如內部 SRAM,并避免使用cache映射的PSRAM),并且最好滿足 4 字節對齊,以發揮DMA最大效率。
主機模式下 DAM 控制的分段配置傳輸:
在這里插入圖片描述

IDF 中提供了 heap_caps_malloc等API用于分配DMA合規的內存,也可以使用 spi_bus_dma_memory_alloc 輔助分配函數。另外,為確保高速下數據穩定,驅動允許用戶設置從設備的信號采樣延遲或調整采樣點位置。通常在低于8MHz時無需調整,而更高速率下根據線長、電平翻轉等情況,適當的延時設置可以改善可靠性。引腳選擇方面,SPI 若使用GPIO矩陣映射引腳,由于引入了約 2ns 的延遲,穩定工作的最高全雙工頻率約為 26MHz,半雙工約 40MHz;若全部使用IO_MUX指定的原生引腳,則可支持全雙工 40MHz、半雙工 80MHz的速率,甚至更高(實際最大受限于時鐘源及從設備性能)。

因此在設計高速 SPI 總線時,盡量選用芯片的硬件接口管腳并降低連線電容,以獲取最佳信號質量和速度。總體而言,通過 DMA、高速引腳和合理的時序配置,ESP32的 SPI 主接口可在高吞吐與低CPU占用之間取得良好平衡,滿足苛刻的數據傳輸需求。

6.中斷與驅動事件

ESP-IDF 的 SPI 主機驅動對底層硬件中斷和狀態變化進行了封裝,用戶通常不需要直接處理 SPI 中斷。驅動在后臺利用中斷檢測事務完成、隊列調度等事件,并提供了回調機制供用戶在特定時機(傳輸前后)執行操作。

在主模式下,如果采用異步隊列接口(spi_device_queue_trans),驅動會在每個事務完成時通過中斷將結果放入內部隊列,用戶可以通過 spi_device_get_trans_result 等API等待或輪詢完成事件。在設備配置結構中還可以指定 pre_cb 和 post_cb 回調函數,分別會在每次事務開始前和結束后被ISR調用,可用于控制引腳、電源管理等(注意需放置于IRAM以滿足中斷上下文要求)。

當多個任務共享同一 SPI 設備時,由于 SPI 驅動線程非安全(訪問同一設備需串行化),一種方式是使用 spi_device_acquire_bus/release_bus 手動鎖定總線;更簡單的做法是保證每個 SPI設備僅由一個任務訪問,或在應用層對共享訪問加互斥鎖。合理利用驅動提供的中斷/隊列機制,可以實現非阻塞的 SPI 通信和多設備的高效調度,充分發揮 SPI 總線的并發能力和數據吞吐。

二、SPI類型定義及相關API

需包含的公共頭文件:#include “driver/spi_master.h”
SPI類型定義

// ==========================================================  
// SPI 主機編號類型  
// ==========================================================  
typedef int spi_host_device_t;          // SPI 主機控制器代號  
#define SPI1_HOST  (0)                 // SPI1(一般用于內部Flash,不開放)  
#define SPI2_HOST  (1)                 // SPI2(用戶可用,一般默認HSPI)  
#define SPI3_HOST  (2)                 // SPI3(用戶可用,一般默認VSPI)  
#define SPI_HOST_MAX  (3)  // SPI主機別名(兼容舊稱呼)  
#define HSPI_HOST  SPI2_HOST  
#define VSPI_HOST  SPI3_HOST  // ==========================================================  
// SPI DMA通道選擇  
// ==========================================================  
typedef enum {  SPI_DMA_DISABLED = 0,   // 不啟用 DMA,將受限于 FIFO 長度  SPI_DMA_CH1      = 1,   // 使用 DMA通道1  SPI_DMA_CH2      = 2,   // 使用 DMA通道2  SPI_DMA_CH_AUTO  = 3    // 自動分配可用 DMA通道  
} spi_dma_chan_t;  // ==========================================================  
// SPI 總線初始化配置結構  
// ==========================================================  
typedef struct {  int mosi_io_num;         // MOSI 引腳編號(主出從入數據線),-1表示不使用  int miso_io_num;         // MISO 引腳編號(主入從出數據線),-1表示不使用  int sclk_io_num;         // SCLK 引腳編號(時鐘線),-1表示不使用  int quadwp_io_num;       // WP 引腳編號(寫保護,用于Quad模式),-1表示不使用  int quadhd_io_num;       // HD 引腳編號(保持,用于Quad模式),-1表示不使用  int data4_io_num;        // 數據線4(Octal模式),-1表示不使用  int data5_io_num;        // 數據線5(Octal模式),-1表示不使用  int data6_io_num;        // 數據線6(Octal模式),-1表示不使用  int data7_io_num;        // 數據線7(Octal模式),-1表示不使用  bool data_io_default_level;  // 空閑時數據線默認電平(輸出使能時),一般為false  int max_transfer_sz;     // 單次最大傳輸字節數(DMA模式下默認4092字節,非DMA模式下默認為 SOC_SPI_MAXIMUM_BUFFER_SIZE)  uint32_t flags;          // 總線能力標志位(SPICOMMON_BUSFLAG_* 的組合,用于校驗硬件能力)  esp_intr_cpu_affinity_t isr_cpu_id; // 中斷分配到的CPU核心(默認不指定)  int intr_flags;          // 中斷分配標志(ESP_INTR_FLAG_LEVELx/IRAM等,一般使用默認0或ESP_INTR_FLAG_IRAM)  
} spi_bus_config_t;  // ==========================================================  
// SPI 從設備接口配置結構(設備初始化時提供)  
// ==========================================================  
typedef struct {  uint8_t  command_bits;   // 命令階段默認位寬(0~16位)  uint8_t  address_bits;   // 地址階段默認位寬(0~64位)  uint8_t  dummy_bits;     // 地址階段后插入的空等待時鐘周期數  uint8_t  mode;           // SPI 模式(0~3,對應 (CPOL, CPHA))  // uint8_t  非預留字段 (占位,以4字節對齊)  uint16_t duty_cycle_pos; // 時鐘正脈沖占空比(1~256,對應占空比百分比=該值/256,128=50%)  uint16_t cs_ena_pretrans;// 傳輸開始前 CS 提前拉低的時鐘周期數(0~16),半雙工模式下有效  uint8_t  cs_ena_posttrans;// 傳輸結束后 CS 保持低電平的時鐘周期數(0~16)  int      clock_speed_hz; // 時鐘頻率(Hz)  int      input_delay_ns; // 輸入信號延遲(ns)補償(從屬設備數據準備時間,0表示不延遲)  // 新版clock_source和sample_point省略,使用默認APB時鐘源  int      spics_io_num;   // 該設備使用的CS引腳(GPIO編號),-1表示不由驅動控制  uint32_t flags;          // 設備標志位(SPI_DEVICE_*,如 LSBFIRST/3WIRE/HALFDUPLEX 等)  int      queue_size;     // 事務隊列長度(驅動可同時掛起的未完成事務數)  transaction_cb_t pre_cb; // 每次傳輸開始前的回調(中斷內調用,需放IRAM)  transaction_cb_t post_cb;// 每次傳輸結束后的回調(中斷內調用,需放IRAM)  
} spi_device_interface_config_t;  // SPI 設備標志位宏(部分常用列舉)  
#define SPI_DEVICE_TXBIT_LSBFIRST   (1<<0)  // 發送數據使用LSB優先(默認MSB先)  
#define SPI_DEVICE_RXBIT_LSBFIRST   (1<<1)  // 接收數據使用LSB優先  
#define SPI_DEVICE_BIT_LSBFIRST     (SPI_DEVICE_TXBIT_LSBFIRST | SPI_DEVICE_RXBIT_LSBFIRST)  // 發送接收均LSB優先  
#define SPI_DEVICE_3WIRE            (1<<2)  // 啟用3線模式(共用MOSI引腳收發,等效半雙工)  
#define SPI_DEVICE_POSITIVE_CS      (1<<3)  // CS信號極性取反(默認低有效,設置此標志后為高有效)  
#define SPI_DEVICE_HALFDUPLEX       (1<<4)  // 半雙工模式(發送完再接收,不同時進行)  
// ...(其他標志如 SPI_DEVICE_NO_DUMMY/SPI_DEVICE_CLK_AS_CS 等,可根據需要使用)  // ==========================================================  
// SPI 事務描述結構(每次傳輸參數)  
// ==========================================================  
typedef struct {  uint32_t flags;      // 事務標志(SPI_TRANS_*,如 VARIABLE_ADDR/CMD, CS_KEEP_ACTIVE 等)  uint16_t cmd;        // 本次事務使用的命令值(實際發送的位寬由 device 的 command_bits 定義)  uint64_t addr;       // 本次事務使用的地址值(實際發送位寬由 address_bits 定義)  size_t   length;     // 本次發送數據總長度(bit為單位)  size_t   rxlength;   // 本次接收數據長度(bit為單位,不大于length,全雙工為0則默認為length)  void    *user;       // 用戶自定義指針(可用來標識事務來源等)  const void *tx_buffer; // 發送數據緩沖區指針(若無發送則可為 NULL)  uint8_t tx_data[4];  // 短數據直接存放在此(設置 SPI_TRANS_USE_TXDATA 時啟用)  void    *rx_buffer;  // 接收數據緩沖區指針(若不需要接收可為 NULL)  uint8_t rx_data[4];  // 短數據直接接收至此(設置 SPI_TRANS_USE_RXDATA 時啟用)  
} spi_transaction_t;  // SPI 事務標志位(常用)  
#define SPI_TRANS_VARIABLE_CMD  (1<<0)  // 本次事務使用非常規長度的命令階段(spi_transaction_ext_t 擴展)  
#define SPI_TRANS_VARIABLE_ADDR (1<<1)  // 使用非常規長度的地址階段  
#define SPI_TRANS_USE_TXDATA    (1<<2)  // 使用 tx_data 中的數據而非緩沖區指針  
#define SPI_TRANS_USE_RXDATA    (1<<3)  // 接收數據直接存入 rx_data  
#define SPI_TRANS_CS_KEEP_ACTIVE (1<<4) // 事務結束后保持 CS 拉低(需配合后續事務或手動控制)  // SPI 設備句柄類型  
typedef struct spi_device_t *spi_device_handle_t;  

SPI相關API

// ======================= SPI 總線控制 =======================  
/*** @brief  初始化 SPI 總線  ** @param host_id    SPI 主機端口號 (SPI2_HOST / SPI3_HOST)  * @param bus_config 指向總線配置結構體的指針  * @param dma_chan   DMA通道選擇:*                   - SPI_DMA_DISABLED 不使用DMA(限制傳輸長度)  *                   - SPI_DMA_CHx 使用指定 DMA通道 (ESP32-S3 通常用 1 或 2)  *                   - SPI_DMA_CH_AUTO 由驅動自動分配可用 DMA通道  ** @note  SPI0/SPI1 為內部總線,驅動不支持初始化 (調用此函數會返回錯誤)。  *        調用成功后,即完成GPIO引腳矩陣配置、FIFO和中斷初始化等。  *        如果指定了 DMA 通道,需確保后續使用的傳輸緩沖區在 DMA 可訪問內存中。  * @return  *       - ESP_OK: 初始化成功  *       - ESP_ERR_INVALID_ARG: 參數非法  *       - ESP_ERR_NOT_FOUND: 無可用DMA通道(當請求AUTO時)  *       - ESP_ERR_INVALID_STATE: 指定主機已經初始化過  */  
esp_err_t spi_bus_initialize(spi_host_device_t host_id, const spi_bus_config_t *bus_config, spi_dma_chan_t dma_chan);  /*** @brief  釋放 SPI 總線  ** @param host_id SPI 主機端口號  * @note  調用此函數前,需確保該總線上的所有設備已被移除 (spi_bus_remove_device)。  * @return  *       - ESP_OK: 釋放成功  *       - ESP_ERR_INVALID_ARG: 參數非法  *       - ESP_ERR_INVALID_STATE: 總線未初始化 或 上面仍掛有未移除的設備  */  
esp_err_t spi_bus_free(spi_host_device_t host_id);  // ======================= SPI 設備控制 =======================  
/*** @brief  向 SPI 總線掛載一個從屬設備  ** @param host_id   SPI 主機端口號 (SPI2_HOST / SPI3_HOST)  * @param dev_config 指向設備接口配置結構體的指針  * @param handle    輸出:返回的設備句柄地址  ** @note  此函數會根據 dev_config 分配并初始化一個 SPI 從設備,*        包括為該設備分配一個 CS (片選) 引腳并通過 GPIO Matrix 連接。  *        ESP32-S3 的 SPI2 支持最多 6 個 CS,引腳編號通常為 CS0~CS5;SPI3 支持 3 個。  *        如果超出主機可用的 CS 插槽數量,將返回 ESP_ERR_NOT_FOUND 錯誤。  *        默認支持最高 40MHz (IO_MUX引腳) / 26MHz (GPIO矩陣) 的速度,全雙工模式下矩陣引腳建議不超過 26MHz:contentReference[oaicite:15]{index=15}。  * @return  *       - ESP_OK: 設備添加成功  *       - ESP_ERR_INVALID_ARG: 參數非法(比如配置沖突)  *       - ESP_ERR_NOT_FOUND: 主機沒有空余 CS 插槽可用  *       - ESP_ERR_NO_MEM: 內存分配失敗  */  
esp_err_t spi_bus_add_device(spi_host_device_t host_id, const spi_device_interface_config_t *dev_config, spi_device_handle_t *handle);  /*** @brief  從 SPI 總線上移除一個從屬設備  ** @param handle 要移除的設備句柄  * @return  *       - ESP_OK: 移除成功  *       - ESP_ERR_INVALID_ARG: 參數非法  *       - ESP_ERR_INVALID_STATE: 設備已被移除或未曾添加  ** @note 調用后該設備占用的CS引腳和資源將釋放,可用于添加新設備 */  
esp_err_t spi_bus_remove_device(spi_device_handle_t handle);  // ======================= SPI 數據傳輸 =======================  
/*** @brief  阻塞方式發送一個 SPI 事務  ** @param handle      設備句柄(spi_bus_add_device取得)  * @param trans_desc  指向事務描述結構體的指針(需提前填充好發送/接收緩沖等)  * @return  *       - ESP_OK: 傳輸成功,數據已發送/接收完成  *       - ESP_ERR_INVALID_ARG: 參數非法(如 trans_desc 內容不合法)  ** @note  此函數相當于依次調用 spi_device_queue_trans() 和 spi_device_get_trans_result(),  *        內部會等待傳輸完成再返回。因此不應在已有掛起事務未完成時再次調用本函數。  *        默認情況下,同一設備上的串行調用是線程安全的,但若多個任務并發訪問同一設備句柄,需要自行確保互斥。  */  
esp_err_t spi_device_transmit(spi_device_handle_t handle, spi_transaction_t *trans_desc);  /*** @brief  輪詢方式發送 SPI 事務  ** @param handle      設備句柄  * @param trans_desc  事務描述結構指針  * @return ESP_OK 表示傳輸完成且成功  ** @note  本函數與 spi_device_transmit 類似,也會等待傳輸完成,但采用 “忙輪詢” 方式驅動硬件而不使用中斷。  *        這種方式適用于短小事務且對實時性要求高的場景,可避免中斷調度的延遲。  *        調用前后無需 acquire_bus,但不同任務并發仍需注意互斥。  */  
esp_err_t spi_device_polling_transmit(spi_device_handle_t handle, spi_transaction_t *trans_desc);  /*** @brief  異步排隊一個 SPI 事務(中斷驅動)  ** @param handle      設備句柄  * @param trans_desc  事務描述結構指針  * @param ticks_to_wait 等待可用隊列空間的超時時間(RTOS ticks,portMAX_DELAY 表示永不超時)  * @return  *       - ESP_OK: 事務已成功加入隊列  *       - ESP_ERR_TIMEOUT: 在指定時間內隊列無空閑,事務未加入  *       - ESP_ERR_NO_MEM: 內部申請DMA臨時緩沖失敗(極少見)  *       - ESP_ERR_INVALID_ARG: 參數非法,或指定了不支持的標志組合等  *       - ESP_ERR_INVALID_STATE: 前一個事務未完成(正常不會發生,因為隊列有容量控制)  ** @note  將事務加入驅動隊列后即立即返回,SPI硬件會通過中斷在后臺執行傳輸。  *        可以通過 spi_device_get_trans_result 獲取完成的事務結果。  *        同一設備上的事務將按調用順序依次執行;多個設備間則由驅動自動仲裁總線。  */  
esp_err_t spi_device_queue_trans(spi_device_handle_t handle, spi_transaction_t *trans_desc, TickType_t ticks_to_wait);  /*** @brief  獲取一個已完成的 SPI 異步事務結果  ** @param handle       設備句柄  * @param trans_desc   輸出:指向完成的事務描述指針的存放地址  * @param ticks_to_wait 最長等待時間(RTOS ticks)  * @return  *       - ESP_OK: 成功獲取到已完成的事務  *       - ESP_ERR_INVALID_ARG: 參數非法  *       - ESP_ERR_TIMEOUT: 在指定時間內沒有事務完成  ** @note  本函數用于與 spi_device_queue_trans 配合,實現異步傳輸的結果獲取。  *        如果在隊列中尚有未完成的事務,本函數會等待直至有事務完成或超時。  *        獲得結果后,可檢查 trans_desc->rx_buffer 中的數據或其他標志,并且可以重復利用或釋放該事務結構。  */  
esp_err_t spi_device_get_trans_result(spi_device_handle_t handle, spi_transaction_t **trans_desc, TickType_t ticks_to_wait);  /*** @brief 手動占用 SPI 總線以獨占訪問  ** @param handle 設備句柄  * @param ticks_to_wait 等待總線可用的時間  * @return ESP_OK 表示成功占用總線  ** @note 調用此函數后,其他設備的事務將被暫掛,直到調用 spi_device_release_bus 釋放總線。  *       適用于需連續執行一組事務且中間不插入其他設備通信的場景。  *       使用完畢后務必調用 release_bus 釋放,否則會阻塞低優先級任務的SPI通信。  */  
esp_err_t spi_device_acquire_bus(spi_device_handle_t handle, TickType_t ticks_to_wait);  /*** @brief 釋放通過 spi_device_acquire_bus 占用的 SPI 總線  ** @param handle 設備句柄  */  
esp_err_t spi_device_release_bus(spi_device_handle_t handle);  

三、SPI示例程序

在 ESP32S3 上通過SPI驅動 LCD 屏幕顯示圖片
main.c

#include <stdio.h>
#include "lcd.h"
#include "yingwu.h"void app_main(void)
{bsp_i2c_init();pca9557_init();bsp_lcd_init(); // 液晶屏初始化lcd_draw_pictrue(0, 0, 320, 240, gImage_yingwu); // 顯示3只鸚鵡圖片while(1){}
}

lcd.c

#include "lcd.h"static const char *TAG = "BSP";esp_err_t bsp_i2c_init(void)
{i2c_config_t i2c_conf = {.mode = I2C_MODE_MASTER,.sda_io_num = BSP_I2C_SDA,.sda_pullup_en = GPIO_PULLUP_ENABLE,.scl_io_num = BSP_I2C_SCL,.scl_pullup_en = GPIO_PULLUP_ENABLE,.master.clk_speed = BSP_I2C_FREQ_HZ};i2c_param_config(BSP_I2C_NUM, &i2c_conf);return i2c_driver_install(BSP_I2C_NUM, i2c_conf.mode, 0, 0, 0);
}// 讀取PCA9557寄存器的值
esp_err_t pca9557_register_read(uint8_t reg_addr, uint8_t *data, size_t len)
{return i2c_master_write_read_device(BSP_I2C_NUM, PCA9557_SENSOR_ADDR, &reg_addr, 1, data, len, 1000 / portTICK_PERIOD_MS);
}// 給PCA9557的寄存器寫值
esp_err_t pca9557_register_write_byte(uint8_t reg_addr, uint8_t data)
{uint8_t write_buf[2] = {reg_addr, data};return i2c_master_write_to_device(BSP_I2C_NUM, PCA9557_SENSOR_ADDR, write_buf, sizeof(write_buf), 1000 / portTICK_PERIOD_MS);
}// 初始化PCA9557 IO擴展芯片
void pca9557_init(void)
{// 寫入控制引腳默認值 DVP_PWDN=1  PA_EN = 0  LCD_CS = 1pca9557_register_write_byte(PCA9557_OUTPUT_PORT, 0x05);// 把PCA9557芯片的IO1 IO1 IO2設置為輸出 其它引腳保持默認的輸入pca9557_register_write_byte(PCA9557_CONFIGURATION_PORT, 0xf8);
}// 設置PCA9557芯片的某個IO引腳輸出高低電平
esp_err_t pca9557_set_output_state(uint8_t gpio_bit, uint8_t level)
{uint8_t data;esp_err_t res = ESP_FAIL;pca9557_register_read(PCA9557_OUTPUT_PORT, &data, 1);res = pca9557_register_write_byte(PCA9557_OUTPUT_PORT, SET_BITS(data, gpio_bit, level));return res;
}// 控制 PCA9557_LCD_CS 引腳輸出高低電平 參數0輸出低電平 參數1輸出高電平
void lcd_cs(uint8_t level)
{pca9557_set_output_state(LCD_CS_GPIO, level);
}// 背光PWM初始化
esp_err_t bsp_display_brightness_init(void)
{// Setup LEDC peripheral for PWM backlight controlconst ledc_channel_config_t LCD_backlight_channel = {.gpio_num = BSP_LCD_BACKLIGHT,.speed_mode = LEDC_LOW_SPEED_MODE,.channel = LCD_LEDC_CH,.intr_type = LEDC_INTR_DISABLE,.timer_sel = 0,.duty = 0,.hpoint = 0,.flags.output_invert = true};const ledc_timer_config_t LCD_backlight_timer = {.speed_mode = LEDC_LOW_SPEED_MODE,.duty_resolution = LEDC_TIMER_10_BIT,.timer_num = 0,.freq_hz = 5000,.clk_cfg = LEDC_AUTO_CLK};ESP_ERROR_CHECK(ledc_timer_config(&LCD_backlight_timer));ESP_ERROR_CHECK(ledc_channel_config(&LCD_backlight_channel));return ESP_OK;
}// 定義液晶屏句柄
static esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_panel_io_handle_t io_handle = NULL;// 設置液晶屏顏色
void lcd_set_color(uint16_t color)
{// 分配內存 這里分配了液晶屏一行數據需要的大小uint16_t *buffer = (uint16_t *)heap_caps_malloc(BSP_LCD_H_RES * sizeof(uint16_t), MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);if (NULL == buffer){ESP_LOGE(TAG, "Memory for bitmap is not enough");}else{for (size_t i = 0; i < BSP_LCD_H_RES; i++) // 給緩存中放入顏色數據{buffer[i] = color;}for (int y = 0; y < 240; y++) // 顯示整屏顏色{esp_lcd_panel_draw_bitmap(panel_handle, 0, y, 320, y + 1, buffer);}free(buffer); // 釋放內存}
}// 背光亮度設置
esp_err_t bsp_display_brightness_set(int brightness_percent)
{if (brightness_percent > 100){brightness_percent = 100;}else if (brightness_percent < 0){brightness_percent = 0;}ESP_LOGI(TAG, "Setting LCD backlight: %d%%", brightness_percent);// LEDC resolution set to 10bits, thus: 100% = 1023uint32_t duty_cycle = (1023 * brightness_percent) / 100;ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH, duty_cycle));ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH));return ESP_OK;
}// 關閉背光
esp_err_t bsp_display_backlight_off(void)
{return bsp_display_brightness_set(0);
}// 打開背光 最亮
esp_err_t bsp_display_backlight_on(void)
{return bsp_display_brightness_set(100);
}// 液晶屏初始化
esp_err_t bsp_display_new(void)
{esp_err_t ret = ESP_OK;// 背光初始化ESP_RETURN_ON_ERROR(bsp_display_brightness_init(), TAG, "Brightness init failed");// 初始化SPI總線ESP_LOGD(TAG, "Initialize SPI bus");const spi_bus_config_t buscfg = {.sclk_io_num = BSP_LCD_SPI_CLK,.mosi_io_num = BSP_LCD_SPI_MOSI,.miso_io_num = GPIO_NUM_NC,.quadwp_io_num = GPIO_NUM_NC,.quadhd_io_num = GPIO_NUM_NC,.max_transfer_sz = BSP_LCD_H_RES * BSP_LCD_V_RES * sizeof(uint16_t),};ESP_RETURN_ON_ERROR(spi_bus_initialize(BSP_LCD_SPI_NUM, &buscfg, SPI_DMA_CH_AUTO), TAG, "SPI init failed");// 液晶屏控制IO初始化ESP_LOGD(TAG, "Install panel IO");const esp_lcd_panel_io_spi_config_t io_config = {.dc_gpio_num = BSP_LCD_DC,.cs_gpio_num = BSP_LCD_SPI_CS,.pclk_hz = BSP_LCD_PIXEL_CLOCK_HZ,.lcd_cmd_bits = LCD_CMD_BITS,.lcd_param_bits = LCD_PARAM_BITS,.spi_mode = 2,.trans_queue_depth = 10,};ESP_GOTO_ON_ERROR(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)BSP_LCD_SPI_NUM, &io_config, &io_handle), err, TAG, "New panel IO failed");// 初始化液晶屏驅動芯片ST7789ESP_LOGD(TAG, "Install LCD driver");const esp_lcd_panel_dev_config_t panel_config = {.reset_gpio_num = BSP_LCD_RST,.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,.bits_per_pixel = BSP_LCD_BITS_PER_PIXEL,};ESP_GOTO_ON_ERROR(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle), err, TAG, "New panel failed");esp_lcd_panel_reset(panel_handle);               // 液晶屏復位lcd_cs(0);                                       // 拉低CS引腳esp_lcd_panel_init(panel_handle);                // 初始化配置寄存器esp_lcd_panel_invert_color(panel_handle, true);  // 顏色反轉esp_lcd_panel_swap_xy(panel_handle, true);       // 顯示翻轉esp_lcd_panel_mirror(panel_handle, true, false); // 鏡像return ret;err:if (panel_handle){esp_lcd_panel_del(panel_handle);}if (io_handle){esp_lcd_panel_io_del(io_handle);}spi_bus_free(BSP_LCD_SPI_NUM);return ret;
}// LCD顯示初始化
esp_err_t bsp_lcd_init(void)
{esp_err_t ret = ESP_OK;ret = bsp_display_new();                             // 液晶屏驅動初始化lcd_set_color(0x0000);                               // 設置整屏背景黑色ret = esp_lcd_panel_disp_on_off(panel_handle, true); // 打開液晶屏顯示ret = bsp_display_backlight_on();                    // 打開背光顯示return ret;
}// 顯示圖片
void lcd_draw_pictrue(int x_start, int y_start, int x_end, int y_end, const unsigned char *gImage)
{// 分配內存 分配了需要的字節大小 且指定在外部SPIRAM中分配size_t pixels_byte_size = (x_end - x_start) * (y_end - y_start) * 2;uint16_t *pixels = (uint16_t *)heap_caps_malloc(pixels_byte_size, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);if (NULL == pixels){ESP_LOGE(TAG, "Memory for bitmap is not enough");return;}memcpy(pixels, gImage, pixels_byte_size);                                                    // 把圖片數據拷貝到內存esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_end, y_end, (uint16_t *)pixels); // 顯示整張圖片數據heap_caps_free(pixels);                                                                      // 釋放內存
}

lcd.h

#ifndef LCD_H
#define LCD_H#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_lcd_types.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "driver/ledc.h"
#include "driver/spi_master.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_check.h"
#include "driver/i2c.h"
#include <string.h>#define SET_BITS(_m, _s, _v) ((_v) ? (_m) | ((_s)) : (_m) & ~((_s)))#define BSP_I2C_SDA (GPIO_NUM_1) // SDA引腳
#define BSP_I2C_SCL (GPIO_NUM_2) // SCL引腳
#define BSP_I2C_NUM (0)          // I2C外設
#define BSP_I2C_FREQ_HZ 100000   // 100kHz#define PCA9557_INPUT_PORT 0x00
#define PCA9557_OUTPUT_PORT 0x01
#define PCA9557_POLARITY_INVERSION_PORT 0x02
#define PCA9557_CONFIGURATION_PORT 0x03
#define PCA9557_SENSOR_ADDR 0x19 /*!< Slave address of the MPU9250 sensor */
#define LCD_CS_GPIO BIT(0)       // PCA9557_GPIO_NUM_1
#define PA_EN_GPIO BIT(1)        // PCA9557_GPIO_NUM_2
#define DVP_PWDN_GPIO BIT(2)     // PCA9557_GPIO_NUM_3#define BSP_LCD_PIXEL_CLOCK_HZ (80 * 1000 * 1000)
#define BSP_LCD_SPI_NUM (SPI3_HOST)
#define LCD_CMD_BITS (8)
#define LCD_PARAM_BITS (8)
#define BSP_LCD_BITS_PER_PIXEL (16)
#define LCD_LEDC_CH LEDC_CHANNEL_0#define BSP_LCD_H_RES (320)
#define BSP_LCD_V_RES (240)#define BSP_LCD_SPI_MOSI (GPIO_NUM_40)
#define BSP_LCD_SPI_CLK (GPIO_NUM_41)
#define BSP_LCD_SPI_CS (GPIO_NUM_NC)
#define BSP_LCD_DC (GPIO_NUM_39)
#define BSP_LCD_RST (GPIO_NUM_NC)
#define BSP_LCD_BACKLIGHT (GPIO_NUM_42)// 函數聲明
esp_err_t bsp_i2c_init(void);
esp_err_t pca9557_register_read(uint8_t reg_addr, uint8_t *data, size_t len);
esp_err_t pca9557_register_write_byte(uint8_t reg_addr, uint8_t data);
void pca9557_init(void);
esp_err_t bsp_lcd_init(void);
esp_err_t bsp_display_brightness_init(void);
esp_err_t bsp_display_new(void);
void lcd_draw_pictrue(int x_start, int y_start, int x_end, int y_end, const unsigned char *gImage);#endif // !LCD_H

總結

本文圍繞 ESP32 的 SPI 外設,從硬件資源、通信機制到軟件接口進行了全面的介紹。在實際應用中,開發者可根據需求選擇合適的傳輸方式:對于少量數據的即時通信,可直接使用阻塞發送;對于大量數據或需要并發處理的場景,可采用 DMA 加中斷隊列的異步方式以降低 CPU 占用。在后續的項目中,開發者可以依據本文提供的知識框架,快速上手 SPI 編程并針對性能瓶頸進行優化,從而充分發揮 ESP32 SPI 外設的強大能力。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/94367.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/94367.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/94367.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

遙感機器學習入門實戰教程|Sklearn案例⑧:評估指標(metrics)全解析

很多同學問&#xff1a;“模型好不好&#xff0c;怎么量化&#xff1f;” 本篇系統梳理 sklearn.metrics 中常用且“夠用”的多分類指標&#xff0c;并給出一段可直接運行的示例代碼&#xff0c;覆蓋&#xff1a;準確率、宏/微/加權 F1、Kappa、MCC、混淆矩陣&#xff08;計數/…

【Bluedroid】深入解析A2DP SBC編碼器初始化(a2dp_sbc_encoder_init)

SBC(Subband Coding)作為藍牙 A2DP 協議的標準編解碼器,其編碼器的初始化與參數配置直接影響音頻傳輸的音質、效率與兼容性。本文基于Andoird A2DP 協議棧源碼,系統剖析 SBC 編碼器的初始化流程,包括核心參數(比特池、采樣率、聲道模式等)的解析、計算與動態調整邏輯,以…

linux shell測試函數

在 C 語言中&#xff0c;int main(int argc, char *argv[])是程序的入口函數&#xff0c;而??在 main函數中調用專門的測試邏輯&#xff08;如測試函數&#xff09;??的程序結構&#xff0c;通常被稱為??測試程序&#xff08;Test Program&#xff09;??或??測試驅動…

【Java SE】抽象類、接口與Object類

文章目錄一、 抽象類&#xff08;Abstract Class&#xff09;1.1 什么是抽象類&#xff1f;1.2 抽象類的語法1.2.1 定義抽象類1.2.2 繼承抽象類1.3 抽象類的特性1.3.1 不能直接實例化1.3.2 抽象方法的限制1.3.3 抽象類可以包含構造方法1.3.4 抽象類不一定包含抽象方法1.3.5 抽象…

Autodl 創建新虛擬環境 python3.9

問題&#xff1a;本人在autodl上保存的環境因為很長時間沒有開機&#xff0c;autodl竟然給我刪除了。后來看了官網的介紹我才發現&#xff0c;原來15天不開機&#xff0c;autodl就會自動釋放實例。 因此&#xff0c;我就自己重新選了一個虛擬環境&#xff0c;從頭開始配置。 GP…

應急響應靶機-WindowsServer2022挖礦事件

依舊手癢開局&#xff0c;知攻善防實驗室的原創靶機 https://mp.weixin.qq.com/s/URrNHvQSnFKOyefHKXKjQQ 相關賬戶密碼&#xff1a; Administrator/zgsf123 注意&#xff1a;做個原始快照&#xff08;方便日后復習&#xff09;&#xff0c;安裝VMware tool&#xff08;安裝后圖…

PCB電路設計學習3 電路原理圖設計 元件PCB封裝設計與添加

目錄PCB電路設計學習3五、電路原理圖設計5.1 32個發光二極管電路5.2 單片機外圍電路5.3 供電與程序下載電路5.4 連接各部分網絡&#xff0c;繪制邊框和說明六、元件PCB封裝設計與添加6.1 名詞解釋6.2 繪制PCB附學習參考網址歡迎大家有問題評論交流 (* ^ ω ^)PCB電路設計學習3 …

redis---常用數據類型及內部編碼

Redis 中每種常用數據類型都對應多種內部編碼&#xff0c;這些編碼會根據數據特征&#xff08;如大小、數量&#xff09;自動切換&#xff0c;以平衡存儲效率和操作性能。1.字符串&#xff08;String&#xff09;用途&#xff1a;存儲文本、數字或二進制數據&#xff0c;是最基…

crypto.randomUUID is not a function

在本地運行時 crypto.randomUUID 好使&#xff0c;build 后放到服務器上用域名訪問就不好使。原因&#xff1a;瀏覽器策略&#xff0c;瀏覽器在非https、localhost的環境中訪問時&#xff0c;crypto.randomUUID 是不可用的開發時使用的是localhost正常訪問 生產臨時使用的是htt…

【思考】什么是服務器?什么是服務?什么是部署?

文章目錄1 什么是服務器&#xff1f;什么是服務&#xff1f;端口是什么意思&#xff1f;2 什么是部署&#xff1f;1 什么是服務器&#xff1f;什么是服務&#xff1f;端口是什么意思&#xff1f; 服務器本質是一臺運行著程序的電腦&#xff0c;它可以運行著很多程序&#xff0c…

自動駕駛導航信號使用方式調研

1 總結 本文調研在給定導航信號后&#xff0c;如何在端到端架構下&#xff0c;利用導航信息引導軌跡生成。 目前主流的方案可以分為2種。一種是將導航作為“前置引導”深度融入軌跡生成過程&#xff08;導航前置型&#xff09;&#xff1b;另一種則是將導航作為“后置評價”標準…

玳瑁的嵌入式日記D21-08020(數據結構)

雙向鏈表double link listtypedef struct dou_node { DATATYPE data; struct dou_node *prev; struct dou_node *next; }DouLinkNode;雙向鏈表&#xff1a;節點 數據 NEXT PREV . 手撕代碼(增加刪除) 增加&#xff0c;刪除的操作&#xff0c; 需要 tmp 停止待操作節點的前一…

Uipath查找元素 查找子元素 獲取屬性活動組合使用示例

Uipath 查找元素 查找子元素 獲取屬性組合使用示例使用場景案例介紹項目流程圖附加瀏覽器查找元素查找子元素遍歷循環獲取屬性點擊元素使用場景 在實際場景中&#xff0c;有時需RPA自動點擊某組范圍元素或獲取某組范圍元素的值&#xff0c;如需獲取指定的父元素&#xff0c;再…

【MongoDB與MySQL對比】

MongoDB 與 MySQL 全方位對比分析在現代軟件開發中&#xff0c;數據庫的選擇直接影響系統性能、擴展性和開發效率。MongoDB 和 MySQL 作為兩種主流數據庫&#xff0c;分別代表了 NoSQL 和關系型數據庫的典型&#xff0c;各自在不同場景中發揮著重要作用。本文將拋開代碼示例&am…

Spring AI開發指導-對話模型

對話模型接口描述Spring AI基于Spring Cloud的架構體系&#xff0c;定義了一系列可擴展的API接口&#xff0c;支持對接不同類型的AI大模型的核心功能&#xff0c;這些API接口支持同步編程模式或者異步編程模式&#xff1a;接口ModelModel是同步編程模式接口&#xff0c;其參數支…

Win11 下卸載 Oracle11g

目錄 1、停止服務 2、啟動 Universal install 應用 3、執行 deinstall.bat 腳本 4、刪除注冊表相關數據 5、刪除環境變量中的oracle相關路徑 6、刪除安裝文件 7、刪除C盤中的相關Oracle文件 8、刪除 Oracle 數據存放目錄 9、檢查 10、重裝oracle可能還會碰到的問題 &…

深入剖析Spring Boot應用啟動全流程

目錄 前言 啟動流程概覽 一、第一階段&#xff1a;初始化SpringApplication 二、第二階段&#xff1a;運行SpringApplication 三、第三階段&#xff1a;環境準備 四、第四階段&#xff1a;創建應用上下文 五、第五階段&#xff1a;準備應用上下文 六、第六階段&#xf…

Matplotlib 可視化大師系列(三):plt.bar() 與 plt.barh() - 清晰對比的柱狀圖

目錄Matplotlib 可視化大師系列博客總覽Matplotlib 可視化大師系列&#xff08;三&#xff09;&#xff1a;plt.bar() 與 plt.barh() - 清晰對比的柱狀圖一、 柱狀圖是什么&#xff1f;何時使用&#xff1f;二、 函數原型與核心參數plt.bar(x, height, ...) - 垂直柱狀圖plt.ba…

基于 FastAPI 和 OpenFeature 使用 Feature Flag 控制業務功能

模擬業務場景&#xff1a;多租戶系統跨域轉賬&#xff0c;需要控制某租戶下某用戶是否可以在某域轉賬 open_feature_util.py import typing from abc import abstractmethod, ABCMeta from typing import Sequencefrom openfeature.evaluation_context import EvaluationContex…

Stm32通過ESP8266 WiFi連接阿里云平臺

本文將介紹stm32如何通過WiFi來連接阿里云&#xff0c;上傳數據和接收指令。要先與阿里云建立TCP連接&#xff0c;然后再通過MQTT協議交互。 大體流程&#xff1a;1、在阿里云網頁上創建產品和設備&#xff1b;2、stm32通過WiFi連接云平臺&#xff1b;3、MQTT連接阿里云&#…