4、微控制器选型与接口:STM32/ESP32选型对比、SPI/I2C/UART接口时序、DMA传输配置
好,咱们进入第四讲。这一讲我打算聊聊微控制器的选型,还有那几个绕不开的通信接口。说实话,做叶片智能传感器,MCU就是整个系统的大脑。选对了,事半功倍;选错了,后面全是坑。
我个人习惯,在项目启动前会花至少一周时间做MCU选型评估。为什么?因为一旦PCB打样回来,再想换芯片,那代价可就大了。你想想看,重新画板、重新调试、重新做EMC测试……嗯,都是泪。
4.1 STM32 vs ESP32:选型对比
这两款芯片,我估计做嵌入式的兄弟都不陌生。一个来自ST,一个来自乐鑫。它们各有各的脾气。
| 对比维度 | STM32(以F4/H7系列为例) | ESP32(以ESP32-S3为例) |
|---|---|---|
| 内核架构 | Cortex-M4/M7,单核或双核 | Xtensa LX7,双核 |
| 主频 | 最高240MHz(H7可达480MHz) | 最高240MHz |
| SRAM | 192KB~1MB | 512KB(可外扩PSRAM) |
| Flash | 512KB~2MB(内置) | 16MB(外置Flash,通过SPI映射) |
| WiFi/BLE | 无(需外挂) | 内置WiFi 4 + BLE 5.0 |
| ADC精度 | 12位/16位(H7有16位) | 12位(有衰减,实际约10位) |
| 典型功耗 | 运行:50~200mA,待机:<10μA | 运行:80~300mA,深度睡眠:<5μA |
| 开发工具 | Keil/IAR/STM32CubeIDE | ESP-IDF/Arduino/MicroPython |
| 价格(批量) | ¥15~60 | ¥10~25 |
我在项目中遇到过这样一个情况:客户要求做一款叶片振动监测传感器,需要采集三轴加速度数据,然后通过WiFi上传到云端。一开始我选了STM32F407,结果发现还得外挂一个ESP8266做WiFi模块。板子面积大了,功耗也上去了。后来我换成了ESP32,一颗芯片搞定所有事。说白了,如果你需要无线连接,ESP32是更省心的选择。
但如果你对实时性要求极高,比如要做高速数据采集(>1Msps),或者需要运行复杂的控制算法,那STM32的Cortex-M7内核和更成熟的RTOS生态会是更好的选择。我做过一个叶片裂纹检测项目,用STM32H743做FFT分析,效果就很稳。
选型核心原则:
- 需要无线连接?→ 优先ESP32
- 需要高精度ADC或强实时控制?→ 优先STM32
- 成本敏感且功能简单?→ 考虑ESP32
- 工业级可靠性要求?→ STM32更成熟
4.2 SPI/I2C/UART接口时序
这三个接口,是传感器和MCU之间沟通的桥梁。我见过太多工程师因为时序没搞对,折腾了好几天。其实没那么玄乎,咱们一个一个说。
4.2.1 SPI接口
SPI,全称Serial Peripheral Interface,四线制(MOSI、MISO、SCLK、CS)。它的特点是全双工、速度快。我一般用SPI来连接高速ADC或显示屏。
时序上,SPI有四种模式,由CPOL(时钟极性)和CPHA(时钟相位)决定。说白了就是:
- CPOL=0:空闲时SCLK为低电平
- CPOL=1:空闲时SCLK为高电平
- CPHA=0:在第一个时钟沿采样数据
- CPHA=1:在第二个时钟沿采样数据
我曾经踩过一个坑:用STM32的SPI1去驱动一个OLED屏,死活不显示。查了半天,发现是模式配错了。传感器手册上写的是Mode 3(CPOL=1, CPHA=1),我配成了Mode 0。嗯,改了一行代码,好了。
避坑指南:SPI通信前,务必确认主从设备的模式一致。我建议用逻辑分析仪抓一下波形,一目了然。
4.2.2 I2C接口
I2C,两线制(SCL、SDA),半双工。它的优势是引脚少,可以挂多个设备。但速度比SPI慢,标准模式100kHz,快速模式400kHz。
I2C的时序核心是起始条件、停止条件和应答信号。起始条件是SCL高电平时,SDA从高变低;停止条件是SCL高电平时,SDA从低变高。每个字节后面跟一个ACK位。
我记得有一次调试一个温湿度传感器,I2C通信总是超时。后来发现是上拉电阻选大了,导致信号上升沿太慢。换成4.7kΩ的电阻,问题解决。你想想看,I2C是开漏输出,上拉电阻的取值很关键。总线电容越大,电阻就要越小。
小技巧:I2C总线上挂多个设备时,每个设备的地址不能冲突。我习惯在原理图阶段就把地址分配表列出来,省得后面打架。
4.2.3 UART接口
UART,异步串口,两线制(TX、RX)。它不需要时钟线,靠波特率来同步。常用的波特率有9600、115200、921600等。
UART的时序比较简单:空闲时总线为高电平,起始位为低电平,然后是数据位(通常8位),可选的校验位,最后是停止位(高电平)。
做叶片传感器时,我经常用UART来和GPS模块或4G模块通信。有一点要注意:UART的波特率误差不能太大,一般要求小于2%。如果MCU和模块的时钟源精度不同,就可能出现乱码。我遇到过用内部RC振荡器跑115200波特率,结果丢包率高达10%。换成外部晶振后,一切正常。
4.3 DMA传输配置
DMA,Direct Memory Access,直接存储器访问。它的作用是在没有CPU干预的情况下,在外设和内存之间搬运数据。说白了,就是让CPU从繁琐的数据搬运中解放出来,去做更重要的事。
我为什么强调DMA?因为叶片传感器需要连续采集数据。如果每次都用CPU去读ADC或SPI的数据,CPU会被频繁中断,导致系统响应变慢。用DMA的话,数据自动搬到内存里,CPU只需要在缓冲区满时处理一下就行。
以STM32为例,配置DMA的步骤大致如下:
// 以STM32 HAL库为例,配置SPI DMA传输
// 1. 使能DMA时钟
__HAL_RCC_DMA1_CLK_ENABLE();
// 2. 配置DMA句柄
DMA_HandleTypeDef hdma_spi1_rx;
hdma_spi1_rx.Instance = DMA1_Stream2;
hdma_spi1_rx.Init.Channel = DMA_CHANNEL_3;
hdma_spi1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_spi1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_spi1_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_spi1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_spi1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_spi1_rx.Init.Mode = DMA_CIRCULAR; // 循环模式,持续采集
hdma_spi1_rx.Init.Priority = DMA_PRIORITY_HIGH;
HAL_DMA_Init(&hdma_spi1_rx);
// 3. 将DMA句柄关联到SPI
__HAL_LINKDMA(&hspi1, hdmarx, hdma_spi1_rx);
// 4. 启动SPI DMA接收
HAL_SPI_Receive_DMA(&hspi1, buffer, BUFFER_SIZE);
// 5. 在DMA传输完成中断中处理数据
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
{
if(hspi->Instance == SPI1)
{
// 数据已自动存入buffer,可以处理了
process_sensor_data(buffer, BUFFER_SIZE);
}
}
这里我用了循环模式(DMA_CIRCULAR),这样DMA会不停地往缓冲区里填数据。当缓冲区满了,它会自动从头开始覆盖。配合双缓冲技术,可以做到零拷贝数据采集。
DMA配置要点:
- 方向:外设到内存(采集)、内存到外设(输出)、内存到内存(搬运)
- 模式:普通模式(一次传输)、循环模式(持续传输)
- 数据宽度:字节、半字、字,要与外设寄存器宽度匹配
- 优先级:高优先级DMA可以打断低优先级的传输
ESP32的DMA配置思路类似,但用的是ESP-IDF的API。我简单说一下:
// ESP32 SPI DMA配置示例
spi_bus_config_t bus_cfg = {
.mosi_io_num = 11,
.miso_io_num = 12,
.sclk_io_num = 13,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 4096,
};
spi_bus_initialize(SPI2_HOST, &bus_cfg, SPI_DMA_CH_AUTO); // 自动分配DMA通道
spi_device_handle_t spi_handle;
spi_device_interface_config_t dev_cfg = {
.clock_speed_hz = 10 * 1000 * 1000, // 10MHz
.mode = 0,
.spics_io_num = 10,
.queue_size = 1,
.flags = SPI_DEVICE_HALFDUPLEX,
};
spi_bus_add_device(SPI2_HOST, &dev_cfg, &spi_handle);
// 发起DMA传输
spi_transaction_t trans = {
.length = 8 * BUFFER_SIZE,
.rx_buffer = buffer,
};
spi_device_transmit(spi_handle, &trans); // 内部使用DMA
ESP32的DMA是自动管理的,你只需要在初始化时指定SPI_DMA_CH_AUTO,剩下的交给底层驱动。这一点比STM32省事不少。
注意:DMA虽然好用,但也不是万能的。如果DMA和CPU同时访问同一块内存,可能会产生数据一致性问题。我建议用__DSB()指令或内存屏障来保证顺序。另外,DMA描述符要放在SRAM中,不能放在Flash里。
4.4 本章知识体系
下面这张图,是我梳理的本章核心逻辑。你可以把它当作一个选型和配置的决策树。
这张图把本章的核心逻辑串起来了。从MCU选型开始,到接口时序,再到DMA配置,每一步都有对应的决策依据。你可以在实际项目中对照这张图来走流程。
好了,这一讲的内容就到这里。MCU选型没有绝对的好坏,只有合不合适。接口时序看着复杂,多抓几次波形就熟了。DMA配置虽然步骤多,但配好了能省下大量CPU资源。嗯,这些经验都是我在项目里一点点攒出来的,希望对你有帮助。
公众号:蓝海资料掘金营,微信deep3321