为什么需要板级通信协议
MCU 需要和传感器、存储器、显示屏等多种外设通信。不同外设对速度、引脚数、距离、从机数量的要求不同,催生了多种通信协议。理解它们的区别是嵌入式开发的核心技能。
UART (Universal Asynchronous Receiver Transmitter)
UART 是最简单的异步串行通信,只需要 TX 和 RX 两根线。
特点:
- 异步,无需时钟线
- 全双工
- 点对点通信(只有两个设备)
- 需要双方约定波特率
常见波特率: 9600, 115200, 921600
// UART 初始化示例 (STM32 HAL)
UART_HandleTypeDef huart1;
void MX_USART1_UART_Init(void) {
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
HAL_UART_Init(&huart1);
}
// 发送和接收
HAL_UART_Transmit(&huart1, (uint8_t*)"Hello\r\n", 8, 100);
HAL_UART_Receive(&huart1, rx_buffer, 1, 1000);
调试技巧:UART 的时序判断最简单。如果抓到乱码,先检查波特率是否一致。用逻辑分析仪抓取一个字节的波形,验证起始位(低)+数据位+停止位(高)是否符合预期。
I2C (Inter-Integrated Circuit)
I2C 使用 SCL (时钟) 和 SDA (数据) 两根线,支持一主多从通信。
特点:
- 同步,有时钟线
- 半双工
- 多从机(通过 7 位或 10 位地址区分)
- 标准速度 100kHz,快速 400kHz,高速 3.4MHz
通信流程:
- 主机发送 START 条件 (SCL 高时 SDA 下降沿)
- 主机发送 7 位从机地址 + 读写位
- 从机发送 ACK 应答
- 传输数据字节,每字节后跟 ACK/NACK
- 主机发送 STOP 条件 (SCL 高时 SDA 上升沿)
# I2C 扫描从机地址 (MicroPython)
from machine import Pin, I2C
i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=400000)
devices = i2c.scan()
if devices:
print("发现 I2C 设备:")
for addr in devices:
print(f" 0x{addr:02X}")
else:
print("未检测到 I2C 设备")
// I2C 读取温湿度传感器 (Arduino)
#include <Wire.h>
void setup() {
Wire.begin(); // 作为主机
Serial.begin(115200);
Wire.beginTransmission(0x44); // SHT30地址
Wire.write(0x2C);
Wire.write(0x06); // 发送测量命令
Wire.endTransmission();
delay(100);
Wire.requestFrom(0x44, 6);
while (Wire.available()) {
byte data[6];
for (int i = 0; i < 6; i++) {
data[i] = Wire.read();
}
// 处理数据...
}
}
常见问题:总线卡死(SDA 被拉低)。解决方法是给 SCL 发送 9 个脉冲让从机复位,或者检查上拉电阻是否正确(4.7kΩ 典型值)。
SPI (Serial Peripheral Interface)
SPI 使用四线(MOSI, MISO, SCLK, CS)实现高速全双工通信。
特点:
- 同步,全双工
- 一主多从(每个从机一根 CS 片选线)
- 速度可达数十 MHz
- 不需要地址,靠片选信号选择从机
四种模式(CPOL 时钟极性 + CPHA 时钟相位):
| 模式 | CPOL | CPHA | 采样沿 | 说明 |
|---|---|---|---|---|
| 0 | 0 | 0 | 上升沿 | 最常用 |
| 1 | 0 | 1 | 下降沿 | |
| 2 | 1 | 0 | 下降沿 | |
| 3 | 1 | 1 | 上升沿 |
// SPI 读写示例 (ESP-IDF)
#include "driver/spi_master.h"
spi_device_handle_t spi_handle;
void spi_init(void) {
spi_bus_config_t bus_cfg = {
.mosi_io_num = 23,
.miso_io_num = 19,
.sclk_io_num = 18,
.max_transfer_sz = 4096
};
spi_bus_initialize(SPI2_HOST, &bus_cfg, SPI_DMA_CH_AUTO);
spi_device_interface_config_t dev_cfg = {
.mode = 0,
.clock_speed_hz = 5 * 1000 * 1000, // 5MHz
.spics_io_num = 5,
.queue_size = 1
};
spi_bus_add_device(SPI2_HOST, &dev_cfg, &spi_handle);
}
void spi_read_sensor(uint8_t reg, uint8_t *data, size_t len) {
spi_transaction_t t = {
.length = 8 * (1 + len),
.tx_buffer = (uint8_t[]){reg | 0x80, 0}, // 读命令+虚拟字节
.rx_buffer = NULL
};
// SPI 是同时收发,需要从rx数据中提取
spi_device_transmit(spi_handle, &t);
}
三种协议对比
| 特性 | UART | I2C | SPI |
|---|---|---|---|
| 线数 | 2 | 2 | 3+n (每从机一根CS) |
| 传输方式 | 异步 | 同步 | 同步 |
| 双工 | 全双工 | 半双工 | 全双工 |
| 速度 | 最高 ~1Mbps | 最高 ~3.4Mbps | 最高 ~80Mbps |
| 从机数量 | 1:1 | 1:多 (127个) | 1:多 (受限于CS引脚) |
| 抗噪能力 | 中 | 中 | 好(差分SPI更好) |
| 调试难度 | 低 | 中 | 低 |
调试方法总结
必备工具:逻辑分析仪(20MHz以上采样率即可)。
调试步骤:
- 确认电气连接正常(万用表测通断)
- 用逻辑分析仪抓取波形
- 验证起始条件、地址/命令、数据、应答位是否符合协议规范
- 检查时序参数(时钟频率、建立时间、保持时间)是否满足外设数据手册要求
# 逻辑分析仪捕获数据处理(示例)
# 假设已从 PulseView/Saleae 导出 CSV
import csv
def parse_i2c_transaction(csv_file):
with open(csv_file, 'r') as f:
reader = csv.reader(f)
for row in reader:
time_ns, scl, sda = float(row[0]), int(row[1]), int(row[2])
# 根据SCL和SDA的边沿变化解码
# 这里简化处理,实际使用需要完整的协议解析
process_edge(time_ns, scl, sda)
小结
UART 适合双向简单通信、调试输出;I2C 适合连接大量低速传感器(占用引脚少);SPI 适合高速数据采集(显示屏、SD卡、ADC)。选型时先看外设支持的接口,再看 MCU 的外设资源是否够用,最后考虑引脚布局是否方便。