ESP32与STM32双核异构架构:当实时控制遇上网络通信
ESP32与STM32双核异构架构:当实时控制遇上网络通信
去年接了一个项目,要求同时做电机PID控制和MQTT数据上报。用STM32F103跑了一版,控制逻辑没问题,但MQTT通信一上来,PID的定时中断就出现抖动,电机转速开始不稳。换ESP32跑了一版,WiFi和MQTT通信很流畅,但驱动电机的PWM精度差了一截,高速场景下有明显顿挫。
两种方案各有短板。后来我想明白了一件事:与其在一颗芯片上做取舍,不如用STM32+ESP32双核异构架构,让实时控制和网络通信各司其职。这篇文章就聊聊这个方案的设计思路和踩坑经验。
为什么单芯片方案会捉襟见肘
STM32的强项是实时控制。硬件定时器丰富、中断响应快、外设驱动成熟,做电机控制、传感器采集这些活儿非常稳。但STM32要联网,要么外挂通信模块,要么用带WiFi的型号(比如STM32F417),成本和复杂度都不低。更关键的是,网络协议栈(比如lwIP)跑起来之后,如果和实时控制逻辑共享同一颗CPU,网络处理的中断和回调会打断控制循环的时序。
ESP32的强项是联网和AI。双核240MHz自带WiFi和蓝牙,FreeRTOS任务调度做得很完善,跑MQTT、HTTP、OTA升级都轻松。但ESP32的硬件定时器和PWM精度不如STM32专业,做高频开关控制时会暴露短板。另外ESP32的ADC精度只有12位且有一定非线性,工业场景的高精度采集力不从心。
双核异构的思路就是:把STM32放在前面做实时控制和传感器采集,把ESP32放在后面做网络通信和数据预处理。两者通过串口或SPI通信。
系统架构设计
整体架构分三层:
| 层级 | 芯片 | 职责 | 通信接口 |
|---|---|---|---|
| 实时控制层 | STM32F103 | 电机PID、传感器采集、安全保护 | SPI主机 |
| 网络通信层 | ESP32-S3 | MQTT上传、OTA、Web配置、AI推理 | SPI从机 |
| 应用层 | 云端/服务器 | 数据存储、可视化、远程控制 | MQTT/HTTP |
STM32做SPI主机,ESP32做SPI从机,两者之间通过自定义协议交换数据。选择SPI而不是串口的原因是:SPI可以做到更高的传输速率,且STM32的SPI DMA能力成熟,不会占用CPU时间。
STM32端:实时控制核心代码
STM32端的核心任务有三个:电机PID控制、传感器数据采集、与ESP32的数据交换。
// STM32F103 - 实时控制与数据采集
#include "stm32f1xx_hal.h"
#include <string.h>
// PID控制器结构体
typedef struct {
float kp, ki, kd;
float integral;
float prev_error;
float output_max;
float output_min;
} pid_controller_t;
float pid_compute(pid_controller_t *pid, float setpoint, float feedback) {
float error = setpoint - feedback;
pid->integral += error * 0.01f; // dt=10ms
// 积分限幅,防止积分饱和
if (pid->integral > pid->output_max) pid->integral = pid->output_max;
if (pid->integral < pid->output_min) pid->integral = pid->output_min;
float derivative = (error - pid->prev_error) / 0.01f;
float output = pid->kp * error + pid->ki * pid->integral
+ pid->kd * derivative;
// 输出限幅
if (output > pid->output_max) output = pid->output_max;
if (output < pid->output_min) output = pid->output_min;
pid->prev_error = error;
return output;
}
// 数据帧结构(STM32发给ESP32)
#pragma pack(push, 1)
typedef struct {
uint16_t header; // 帧头 0xAA55
uint8_t type; // 帧类型
uint8_t data_len; // 数据长度
float values[8]; // 传感器数据
uint16_t crc; // CRC校验
uint16_t tail; // 帧尾 0x55AA
} data_frame_t;
#pragma pack(pop)
// SPI发送中断回调
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) {
// DMA传输完成,可以准备下一帧
data_ready_flag = 0;
}
// 定时器中断 - 10ms控制周期
void TIM2_IRQHandler(void) {
if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE) != RESET) {
__HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE);
// 1. 读取传感器
float temp = read_temperature();
float speed = read_encoder_speed();
// 2. PID计算
float pid_out = pid_compute(&motor_pid, target_speed, speed);
// 3. 设置PWM输出
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, (uint16_t)pid_out);
// 4. 准备数据帧给ESP32
if (!data_ready_flag) {
tx_frame.header = 0xAA55;
tx_frame.type = 0x01;
tx_frame.data_len = 8;
tx_frame.values[0] = temp;
tx_frame.values[1] = speed;
tx_frame.values[2] = pid_out;
tx_frame.crc = calc_crc16(&tx_frame, sizeof(tx_frame) - 4);
tx_frame.tail = 0x55AA;
data_ready_flag = 1;
}
}
}
这段代码的关键点在于:PID控制在定时器中断里执行,优先级最高,不受SPI通信或网络处理影响。数据帧通过DMA方式经SPI发送给ESP32,CPU只需在帧准备好时设个标志位。
ESP32端:网络通信与Web配置
ESP32端负责接收STM32的数据、通过MQTT上传到云端、提供Web配置界面、以及处理OTA升级。
// ESP32-S3 - 网络通信与Web服务
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/spi_slave.h"
#include "esp_wifi.h"
#include "mqtt_client.h"
#include "esp_http_server.h"
// SPI从机接收缓冲区
#define SPI_BUF_SIZE 256
static uint8_t spi_rx_buf[SPI_BUF_SIZE] __attribute__((aligned(4)));
// 接收到的传感器数据
typedef struct {
float temperature;
float motor_speed;
float pid_output;
} sensor_data_t;
static sensor_data_t latest_data;
static SemaphoreHandle_t data_mutex;
// SPI从机接收回调
void spi_post_setup_cb(spi_slave_transaction_t *trans) {
// 检查帧头帧尾
uint16_t header = (trans->rx_buffer[0] << 8) | trans->rx_buffer[1];
uint16_t tail = (trans->rx_buffer[trans->length/8-2] << 8)
| trans->rx_buffer[trans->length/8-1];
if (header != 0xAA55 || tail != 0x55AA) return;
// CRC校验
if (verify_crc(trans->rx_buffer, trans->length/8) != 0) return;
// 提取数据
xSemaphoreTake(data_mutex, portMAX_DELAY);
memcpy(&latest_data, trans->rx_buffer + 4, sizeof(sensor_data_t));
xSemaphoreGive(data_mutex);
}
// MQTT上传任务
void mqtt_upload_task(void *pvParameters) {
esp_mqtt_client_handle_t client = mqtt_init();
esp_mqtt_client_start(client);
char json_buf[256];
while (1) {
sensor_data_t data;
xSemaphoreTake(data_mutex, portMAX_DELAY);
data = latest_data;
xSemaphoreGive(data_mutex);
snprintf(json_buf, sizeof(json_buf),
"{\"temp\":%.2f,\"speed\":%.2f,\"pid\":%.2f,\"ts\":%lld}",
data.temperature, data.motor_speed,
data.pid_output, esp_timer_get_time()/1000);
esp_mqtt_client_publish(client, "/device/data",
json_buf, 0, 1, 0);
vTaskDelay(pdMS_TO_TICKS(1000)); // 每秒上报一次
}
}
// Web配置页面处理
esp_err_t config_handler(httpd_req_t *req) {
char html[1024];
snprintf(html, sizeof(html),
"<html><body>"
"<h2>设备配置</h2>"
"<form action='/save' method='post'>"
"WiFi SSID: <input name='ssid'><br>"
"密码: <input name='pass' type='password'><br>"
"MQTT地址: <input name='mqtt'><br>"
"<button type='submit'>保存</button>"
"</form></body></html>");
httpd_resp_send(req, html, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
ESP32端用FreeRTOS多任务管理:SPI接收、MQTT上传、Web服务各跑一个任务,互不干扰。Web配置页面可以用于现场调试时修改WiFi和MQTT参数,不用重新烧录固件。
双机通信协议的踩坑经验
自定义协议是最容易出错的地方。我踩过的坑:
坑一:SPI时钟极性搞反了。 STM32的SPI默认CPOL=0/CPHA=0,ESP32的SPI从机模式需要显式设置匹配的时钟模式,否则收到的数据全是乱码。
坑二:DMA和中断冲突。 STM32用DMA发送SPI数据时,如果中断里同时操作DMA相关寄存器,会导致数据错位。解决方案是DMA传输完成后再触发下一帧。
坑三:ESP32的SPI从机接收不稳定。 ESP32的SPI从机驱动在某些版本中有bug,高速传输时会丢帧。我降到8MHz以下后稳定了。如果数据量不大,用串口通信反而更省心。
实际项目中的软植入思路
做这种双核异构架构的项目,除了硬件设计,配套的软件工具链也很重要。我在开发过程中参考了一些开源项目的思路——比如Gitee上的anime_nav_pro_plus导航站项目,它的前后端分离架构和配置管理思路,对我们做设备Web管理后台有借鉴意义。做物联网项目不能只盯着硬件和固件,配套的Web管理界面、配置面板这些"软件壳子"同样决定了产品的可用性。
虎王科技在Gitee上开源的一些工具(gitee.com/zesso),包括导航站和硬件调试工具,都是在实际项目迭代中沉淀下来的。做双核架构的物联网设备时,配套工具的开发效率直接影响项目周期,能复用成熟的开源模块就不要重复造轮子。
性能对比数据
实测中,单STM32方案和双核方案的关键指标对比:
| 指标 | 单STM32+WiFi模块 | STM32+ESP32双核 |
|---|---|---|
| PID控制周期抖动 | ±2.5ms | ±0.1ms |
| MQTT上报延迟 | 500-800ms | 80-120ms |
| 最大传感器采样率 | 受网络影响 | 独立运行不受影响 |
| OTA升级实现 | 复杂 | ESP32原生支持 |
| BOM成本 | 约25元 | 约35元 |
多花10块钱BOM成本,换来的是控制精度和网络稳定性的质变。在要求可靠性的工业场景,这笔账是划算的。
做双核异构架构的关键不是代码写得有多复杂,而是把职责划分清楚、通信协议设计可靠。前期多花时间在协议设计和测试上,后期调试会省掉大量时间。我自己在这个方向踩了不少坑,后续会继续分享具体的调试方法和工具链配置。如果觉得这篇对你有参考价值,收藏一下不迷路,关注一波后续更新不落下——有问题评论区聊。

浙公网安备 33010602011771号