一、通信协议基础概念:从底层到应用层的 “金字塔” 结构
在嵌入式开发中,通信协议可分为 底层硬件协议(解决 “如何传数据”)和 应用层协议(解决 “传什么数据”)。本文将深入解析 6 大底层协议(RS232、RS485、SPI、IIC、CAN),并补充 UART/USART/ 串口 的深度对比,最后介绍工业常用的 MODBUS 协议。
1. 通信基础概念详解
(1)双工模式
- 单工通信:数据仅能单向传输(如遥控器与电视)。
- 半双工通信:数据可双向传输但不能同时进行(如对讲机)。
- 全双工通信:数据可同时双向传输(如电话)。
(2)并行与串行传输
特性 | 并行通信 | 串行通信 |
---|---|---|
数据传输 | 多根线同时传输多个数据位 | 单根线逐位传输数据位 |
速度 | 快(如并口打印机) | 慢(如 USB) |
距离 | 短(米级) | 长(千米级) |
成本 | 高(多根线) | 低(单根线) |
典型应用 | 早期计算机总线(如 ISA) | 现代接口(如 SPI、IIC、UART) |
(3)同步与异步通信
- 同步通信:
- 需时钟线(如 SPI 的 SCLK),数据传输与时钟同步。
- 特点:高速(如 SPI 可达 50Mbps+),但硬件复杂。
- 异步通信:
- 无时钟线,依赖起始位(0)和停止位(1)同步(如 UART)。
- 特点:硬件简单,适合低速场景。
二、六大底层硬件协议深度对比
1. UART/USART/ 串口:最熟悉的 “异步通信三兄弟”
1.1 基础概念与核心定义
-
串口(Serial Port)
- 定义:广义指所有串行通信接口,狭义通常特指基于 UART 硬件的异步串口(如常见的 TTL 电平串口)。
- 核心作用:实现设备间逐位传输数据,仅需 1~2 根信号线(TX 发送、RX 接收),成本低、易部署,是嵌入式开发中最基础的通信方式。
-
UART(Universal Asynchronous Receiver-Transmitter)
- 定义:通用异步收发器,硬件电路,实现异步串行通信,不支持同步时钟信号。
- 核心特性:异步通信,收发双方各自独立时钟,靠约定波特率同步;数据以 “帧” 为单位传输,每帧包含起始位、数据位、校验位、停止位。
-
USART(Universal Synchronous Asynchronous Receiver-Transmitter)
- 定义:通用同步异步收发器,UART 的 “增强版”,支持异步和同步两种模式。
- 核心特性:异步模式兼容 UART;同步模式可外接时钟线(SCLK),用于高速同步通信(如配合 SPI 协议),功能更灵活。
1.2 硬件接口与物理层
特性 | UART / 串口(TTL 电平) | USART(同步模式) |
---|---|---|
信号线 | TX(发送)、RX(接收)、GND(共地) | TX、RX、SCLK(同步时钟)、GND |
电平标准 | 3.3V/5V TTL(高电平 1,低电平 0) | 可兼容 TTL/CMOS,同步模式需额外时钟线 |
典型接线 | TX 接对方 RX,RX 接对方 TX,共地 | 异步时同 UART;同步时需 SCLK 同步时钟 |
1.3 工作原理:从 “帧格式” 到数据收发
1.3.1 异步通信(UART/USART 异步模式)
-
帧格式(以 8 位数据位为例):
起始位(1位,0) → 数据位(5~8位,低位先传) → 校验位(0/1位,奇/偶/无) → 停止位(1/1.5/2位,1)
- 示例:发送字符‘A’(ASCII 码 0x41,二进制 01000001),无校验位,1 位停止位:
帧结构为0(起始) + 1000001(数据,低位先传即1000001→1000001,注意低位是第一位) + 1(停止)
。
- 示例:发送字符‘A’(ASCII 码 0x41,二进制 01000001),无校验位,1 位停止位:
-
波特率:每秒传输的比特数(如 9600bps 表示每秒传 9600 位),收发双方必须一致(误差 < 5%),常见值:9600、19200、115200。
-
工作流程(以 STM32 单片机为例):
-
初始化配置(代码示例,注释解释):
// 步骤1:开启GPIO和USART时钟(假设使用USART1,对应PA9/TX、PA10/RX) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); // 步骤2:配置TX引脚为复用推挽输出,RX为浮空输入 GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9; // TX引脚PA9 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出(发送数据) GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10; // RX引脚PA10 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入(接收数据) GPIO_Init(GPIOA, &GPIO_InitStruct); // 步骤3:配置USART参数(异步模式) USART_InitTypeDef USART_InitStruct; USART_InitStruct.USART_BaudRate = 115200; // 波特率 USART_InitStruct.USART_WordLength = USART_WordLength_8b; // 8位数据位 USART_InitStruct.USART_StopBits = USART_StopBits_1; // 1位停止位 USART_InitStruct.USART_Parity = USART_Parity_No; // 无校验位 USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件流控 USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 收发模式 USART_Init(USART1, &USART_InitStruct); // 步骤4:使能USART USART_Cmd(USART1, ENABLE);
参数解析:
USART_BaudRate
:波特率,决定数据传输速度。USART_WordLength
:数据位长度(5~8 位),需与对方一致。USART_StopBits
:停止位长度,长停止位可容忍时钟误差(如多机通信)。USART_Parity
:校验位(奇 / 偶 / 无),简单错误检测,非必须。
-
数据发送:
- 单字符发送:调用
USART_SendData(USART1, data)
,等待发送完成标志USART_FLAG_TC
(避免覆盖发送)。while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); // 等待上一帧发送完成 USART_SendData(USART1, 'A'); // 发送字符'A'
- 字符串发送:循环调用单字符发送函数,逐字节发送。
- 单字符发送:调用
-
数据接收:
- 轮询方式:检测接收缓冲区非空标志
USART_FLAG_RXNE
,读取数据寄存器USART_ReceiveData(USART1)
。 - 中断方式(推荐,适合实时性场景):使能接收中断
USART_IT_RXNE
,在中断服务函数中读取数据。// 使能接收中断 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 中断服务函数 void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { uint8_t data = USART_ReceiveData(USART1); // 读取接收到的数据 // 处理数据(如存入缓冲区) } }
- 轮询方式:检测接收缓冲区非空标志
-
1.3.2 同步通信(USART 同步模式)
- 核心区别:外接同步时钟线(SCLK),收发双方共享同一时钟,数据在时钟边沿同步传输,适合高速、长距离(需时钟稳定)。
- 应用场景:极少单独使用,常作为 SPI 协议的底层驱动(SPI 本质是同步串行协议,USART 同步模式可配置为 SPI 兼容模式)。
1.4 典型应用场景
-
UART / 串口(TTL 电平):
- 嵌入式设备与电脑通信(通过 CH340/PL2303 模块转换为 USB),如单片机调试(打印日志、接收指令)。
- 低速外设连接,如蓝牙模块(HC-05)、WiFi 模块(ESP8266)、传感器(部分串口传感器)。
- 示例:Arduino 通过 SoftwareSerial 库模拟 UART,与蓝牙模块通信。
-
USART:
- 需同步的高速场景(如配合 SPI),但更常见是作为增强型 UART 使用(异步模式为主)。
- 多协议兼容:STM32 等单片机的 USART 外设可配置为 UART、LIN、IrDA 等多种模式。
1.5 优缺点对比
优点 | 缺点 |
---|---|
硬件简单(仅需 TX/RX/GND) | 异步模式依赖波特率严格一致(误差敏感) |
成本低,适合短距离(TTL 电平≤10m) | 无硬件流控时易丢包(需软件握手) |
协议简单,上手快(帧格式固定) | 同步模式需额外时钟线,应用较少 |
支持全双工(TX/RX 独立,可同时收发) | 数据无应答机制,可靠性依赖上层协议 |
1.6 与其他协议的核心差异
- 对比 RS232/RS485:UART/USART 是逻辑层协议(定义数据格式),RS232/RS485 是物理层标准(定义电平、接口、传输距离)。
- UART(TTL 电平)可通过 MAX232 芯片转换为 RS232 电平(±12V),延长传输距离至 15m。
- 通过 MAX485 芯片转换为 RS485 差分信号,支持多节点、长距离(1200m)、抗干扰。
- 对比 SPI/IIC:UART 是异步、全双工;SPI 是同步、全双工(需时钟和片选线);IIC 是同步、半双工(单总线双向)。
- 对比 CAN:UART 无纠错和仲裁机制,适合简单场景;CAN 是高可靠、多主总线,用于汽车电子等严苛环境。
1.7 避坑指南
- 波特率不一致:必现乱码,调试时先用串口助手固定波特率(如 115200),逐步排查。
- 电平不匹配:3.3V 设备与 5V 设备直连可能烧芯片,需用电平转换模块(如电阻分压)。
- 停止位 / 校验位错误:收发双方帧格式必须完全一致(数据位、停止位、校验位),否则接收错位。
- 中断处理不当:接收中断需及时读取数据,避免缓冲区溢出(可配合 DMA 或环形缓冲区)。
1.8 拓展:UART 的 “进阶玩法”
- 硬件流控(RTS/CTS):通过请求发送(RTS)和清除发送(CTS)信号控制数据流,避免缓冲区溢出,适合高速通信。
- 多机通信:利用 UART 的空闲总线检测(IDLE 中断),实现主从模式(如 1 个主机带多个从机,通过地址帧寻址)。
- 与 MODBUS 结合:MODBUS-RTU 协议基于 UART,定义了数据帧格式和功能码(如 0x03 读取寄存器),是工业控制中最常用的串口应用层协议。
总结:一句话分清三兄弟
- 串口:泛指标称,底层通常是 UART 硬件。
- UART:纯异步,靠波特率同步,最简串行通信方案。
- USART:异步为主,支持同步,功能更全(如可配置为 SPI 兼容模式),单片机中最常用。
理解 UART/USART 是掌握其他串行协议的基础,后续将继续对比 RS232/RS485、SPI/IIC/CAN 等协议,帮你构建完整的嵌入式通信知识体系。
2. RS232:PC 串口的 “老大哥”(异步、单端)
2.1 物理层核心特性:电平、距离与信号特性
-
独特的负逻辑电平体系
RS232 采用 负逻辑电平标准,与微控制器的 TTL/CMOS 电平完全不同:- 逻辑 1:电压范围 -3V ~ -15V(高电平代表逻辑 1,反常识设计)
- 逻辑 0:电压范围 +3V ~ +15V(低电平代表逻辑 0)
- 未定义区:-3V ~ +3V(信号处于此区间时,接收端可能无法正确解析)
为什么这样设计? 早期为兼容电话线路的差分传输,通过负逻辑减少噪声干扰,但与现代数字电路(0-3.3V/5V 电平)完全不兼容,必须依赖 MAX232/SP3232 等电平转换芯片。
-
短距离通信的局限性
- 传输距离:最大有效距离 15 米,实际应用中通常控制在 5 米内以保证稳定性。
- 信号缺陷:采用 单端信号传输(仅一根信号线,以公共地为参考),易受电磁干扰(EMI)和地电位差影响。距离越长,信号衰减越明显(如 10 米外信号幅值可能下降 30%)。
2.2 硬件连接:PC 与嵌入式设备的 “翻译官”
- 三要素:电平转换 + 共地 + 引脚对应
PC(DB9 接口) MAX232 芯片 单片机 ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ TX(2 脚,-3~-15V)→ R1IN(13 脚) │ → TXD(TTL 3.3V)│ │ RX(3 脚,-3~-15V)← T1OUT(14 脚) │ ← RXD(TTL 3.3V)│ │ GND(5 脚,地) ── GND(6 脚)─────┼── GND(地) │ └───────────────┘ └───────────────┘ └───────────────┘
- 关键步骤解析
- 电平转换芯片选型
- 5V 系统选 MAX232(内部电荷泵将 5V 转为 ±12V 满足 RS232 电平)。
- 3.3V 系统选 MAX3232(3.3V 供电,兼容 3.3V TTL 电平)。
- 引脚功能对应
- PC 的 TX(发送)接 MAX232 的 R1IN(接收输入):PC 发送 RS232 电平,经芯片转为 TTL 电平给单片机。
- PC 的 RX(接收)接 MAX232 的 T1OUT(发送输出):单片机发送 TTL 电平,经芯片转为 RS232 电平给 PC。
- 共地连接:必须将 PC、MAX232、单片机的 GND 短接,否则地电位差会导致信号解析错误(如地电位差 2V 会被误判为逻辑 0)。
- 电平转换芯片选型
2.3 典型应用场景:嵌入式调试黄金搭档
-
场景 1:单片机串口调试
- 操作流程:
- 单片机 TXD 接 MAX232 的 DI,RXD 接 RO。
- MAX232 连接 PC 的 USB 转 RS232 接口(如 CH340 芯片)。
- 串口助手设置波特率 115200、8 数据位、1 停止位、无校验。
- 代码示例(STM32 发送数据到 PC):
// 初始化 USART1 为 TTL 电平模式(默认已为 TTL,无需额外转换) USART_InitTypeDef USART_InitStruct; USART_InitStruct.USART_BaudRate = 115200; USART_InitStruct.USART_WordLength = USART_WordLength_8b; USART_Init(USART1, &USART_InitStruct); USART_Cmd(USART1, ENABLE); // 发送字符串 void USART_SendString(char *str) { while (*str) { USART_SendData(USART1, *str++); while (!USART_GetFlagStatus(USART1, USART_FLAG_TXE)); // 等待发送缓冲区空 } } // 调用:USART_SendString("Hello, RS232!\r\n");
- 操作流程:
-
场景 2:工业设备点对点通信
- 应用案例:老式数控机床通过 RS232 连接上位机,传输加工程序(波特率 9600,需手动配置两端参数一致)。
2.4 避坑指南:三大常见问题解决
问题现象 | 可能原因 | 解决方法 |
---|---|---|
串口助手无数据显示 | 电平转换芯片供电错误 | 测量 MAX232 的 VCC 引脚是否为 5V/3.3V(对应系统电压) |
数据乱码 | 波特率 / 数据位 / 停止位不匹配 | 检查串口助手与设备的参数设置(如是否为 8-N-1 格式) |
芯片发热烧毁 | 引脚接反(如 TX 接 RX) | 对照电路图,确保 PC TX 接芯片 R1IN,PC RX 接芯片 T1OUT |
3. RS485:工业级 “多节点” 串口(异步、差分)
3.1 物理层升级:差分信号如何实现抗干扰?
-
差分传输核心原理
- 信号定义:通过两根信号线(A 线、B 线)的电压差表示逻辑值:
- 逻辑 1:A - B ≥ +200mV(A 线电压高于 B 线)
- 逻辑 0:A - B ≤ -200mV(B 线电压高于 A 线)
- 抗干扰优势:共模干扰(如地噪声、电磁辐射)同时影响 A、B 线,差分接收器仅检测压差。
-
长距离传输能力
- 波特率与距离关系:
波特率 1200bps 9600bps 115200bps 10Mbps 最大距离 1200 米 1200 米 400 米 15 米 - 工业场景首选:煤矿、工厂等恶劣环境,距离 500 米以上时优先选择 RS485。
- 波特率与距离关系:
-
3.2 多节点组网:从硬件到协议的完整方案
-
硬件组网三要素
- 半双工控制(DE/RE 引脚)
- DE(Driver Enable):高电平使能发送器(A/B 线输出差分信号),低电平进入高阻态。
- RE(Receiver Enable):低电平使能接收器(读取 A/B 线信号),高电平禁止接收(部分芯片与 DE 共用引脚)。
- 终端电阻(120Ω 关键作用)
- 作用:匹配传输线特性阻抗,消除信号反射(长距离传输时,未匹配的信号会在末端反射,导致数据错误)。
- 安装位置:总线两端各接一个 120Ω 电阻(并联在 A、B 线之间),短距离(<300 米)可省略。
- 线缆选择:使用 屏蔽双绞线(减少电磁干扰),A 线用红色,B 线用黑色(行业通用配色)。
- 半双工控制(DE/RE 引脚)
-
典型网络拓扑
主机(PLC)────A/B────从机 1────A/B────从机 2────A/B────终端电阻(120Ω) ↓ 从机 3(T 型连接,支线 <10米)
-
3.3 代码实现:STM32 控制 RS485 收发器(带收发切换)
#include "stm32f10x.h" // 定义 RS485 控制引脚(DE 高电平发送,低电平接收) #define RS485_DE_GPIO_PORT GPIOA #define RS485_DE_PIN GPIO_Pin_11 // 初始化 RS485 接口 void RS485_Init(void) { // 使能 GPIO 和 USART 时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); // 配置 TX 引脚(PA9)为复用推挽输出 GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽,由 USART 控制发送 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); // 配置 RX 引脚(PA10)为浮空输入 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入,高阻态接收 GPIO_Init(GPIOA, &GPIO_InitStruct); // 配置 DE 控制引脚(PA11)为推挽输出 GPIO_InitStruct.GPIO_Pin = RS485_DE_PIN; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出,控制收发模式 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); GPIO_ResetBits(RS485_DE_GPIO_PORT, RS485_DE_PIN); // 初始化为接收模式 // 初始化 USART1(与从机参数一致) USART_InitTypeDef USART_InitStruct; USART_InitStruct.USART_BaudRate = 9600; // 波特率 USART_InitStruct.USART_WordLength = USART_WordLength_8b; // 8 位数据位 USART_InitStruct.USART_StopBits = USART_StopBits_1; // 1 位停止位 USART_InitStruct.USART_Parity = USART_Parity_No; // 无校验位 USART_Init(USART1, &USART_InitStruct); USART_Cmd(USART1, ENABLE); } // 发送数据函数(自动切换收发模式) void RS485_SendData(uint8_t *data, uint16_t len) { GPIO_SetBits(RS485_DE_GPIO_PORT, RS485_DE_PIN); // 置高 DE,进入发送模式 for (uint16_t i = 0; i < len; i++) { USART_SendData(USART1, data[i]); // 发送数据 while (!USART_GetFlagStatus(USART1, USART_FLAG_TXE)); // 等待发送缓冲区空 } while (!USART_GetFlagStatus(USART1, USART_FLAG_TC)); // 等待发送完成(避免多帧重叠) GPIO_ResetBits(RS485_DE_GPIO_PORT, RS485_DE_PIN); // 置低 DE,恢复接收模式 } // 接收数据(中断方式,需配置 NVIC) void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_RXNE)) { // 接收缓冲区非空中断 uint8_t data = USART_ReceiveData(USART1); // 读取数据 // 存入接收缓冲区(如环形缓冲区)或处理数据 } }
- 代码核心逻辑
- 收发切换:发送前置高 DE,使能发送器;发送完成后置低 DE,避免占用总线。
- 状态标志检测:
USART_FLAG_TXE
表示发送缓冲区空,USART_FLAG_TC
表示整帧发送完成,确保数据不丢失。
-
3.4 应用层协议:Modbus-RTU 与 RS485 的黄金组合
-
Modbus-RTU 帧格式(基于 RS485)
[设备地址(1B)] [功能码(1B)] [数据起始地址(2B)] [数据长度(2B)] [数据( nB)] [CRC 校验(2B)]
- 设备地址:0x01~0x7F(唯一标识从机,0x00 为广播地址)。
- 功能码:0x03 读寄存器,0x06 写单个寄存器(具体见 Modbus 协议文档)。
-
通信流程(主机读取从机寄存器)
- 主机发送查询帧:
01 03 00 00 00 01 CRC_H CRC_L
(读取从机 01 的 0x0000 寄存器)。 - 从机响应:
01 03 02 DATA1 DATA2 CRC_H CRC_L
(返回 2 字节数据)。
- 主机发送查询帧:
-
3.5 工程实践:三大核心问题解决方案
- 节点冲突:同一时间多设备发送
- 解决:采用 单主多从模式(主机发起,从机响应),从机发送前检测总线空闲(如通过监测 A/B 线压差)。
- 信号反射:数据乱码或丢失
- 解决:长距离(>300 米)必须安装终端电阻,使用示波器测量信号波形,调整电阻值(典型 120Ω,根据线缆特性阻抗调整)。
- 地电位差:烧毁芯片或通信中断
- 解决:添加 信号隔离模块(如光耦隔离 + DC-DC 隔离电源),切断地环路,避免共模电压超标(RS485 允许共模电压 -7V~+12V)。
-
总结:RS232 vs RS485,选型决策表
场景 RS232 RS485 传输距离 <15 米(短距离调试) 1200 米(长距离工业场景) 节点数 2 个(点对点) 32+ 个(单主多从) 抗干扰能力 弱(单端信号) 强(差分信号) 典型应用 单片机与 PC 调试、老式设备连接 工业仪表组网、传感器集群 必备硬件 MAX232 电平转换芯片 RS485 收发器(如 SP485)、终端电阻 软件要点 无需收发切换,直接透传 DE/RE 引脚控制收发模式 通过理解 RS232 和 RS485 的物理层差异、硬件连接和应用场景,新手可快速掌握工业通信的基础,后续结合 Modbus 等协议可实现更复杂的设备互联。记住:RS232 是入门调试的桥梁,RS485 是工业组网的核心,两者相辅相成,共同构成嵌入式通信的基石。
- 信号定义:通过两根信号线(A 线、B 线)的电压差表示逻辑值:
4. SPI:高速外设的 “专属通道”(同步、全双工)
4.1 物理层:四根线构建高速通道
-
核心信号线(必知必会)
信号名 全称 方向 功能描述 SCLK 同步时钟线 主→从 由主机生成时钟信号,控制数据传输节奏(频率可达 50Mbps+)。 MOSI 主出从入 主→从 主机发送数据,从机接收(Master Out, Slave In)。 MISO 主入从出 从→主 从机发送数据,主机接收(Master In, Slave Out)。 CS(NSS) 片选线 主→从 低电平有效,主机通过拉低对应从机的 CS 线选中目标设备(每个从机独立 CS)。 硬件连接示例(单主机双从机):
plaintext
主机(STM32) 从机1(W25Q64) 从机2(SD卡) SCLK ────────────── SCLK SCLK MOSI ────────────── MOSI MOSI MISO ────────────── MISO MISO CS1 ────────────── CS — CS2 ────────────── — CS
- 关键:每个从机必须有独立的 CS 线,主机通过切换 CS 电平选择通信对象。
-
传输特性
- 全双工:主机和从机可同时发送 / 接收数据(MOSI 和 MISO 独立传输)。
- 高速率:波特率可达系统时钟的 1/2(如 STM32 时钟 72MHz 时,SPI 速率可达 36MHz),适合传输大量数据(如图像、音频)。
4.2 通信模式:时钟极性与相位的组合魔法
4.3.2 数据收发:同步交换机制
4.4 典型应用场景
4.5 优缺点与适用场景
优点 | 缺点 | 最佳场景 |
---|---|---|
高速率(50Mbps+) | 无硬件应答机制(依赖软件确认) | 高速数据传输(存储、图像) |
全双工同步通信 | 多从机需独立 CS 线(引脚资源紧张) | 点对点或少量从机组网 |
协议简单(无复杂握手) | 无总线仲裁(多主机易冲突) | 主从架构明确的系统(如单片机 + 外设) |
4.6 新手避坑与拓展
4.7 与其他协议对比(核心差异)
特性 | SPI | I2C | UART |
---|---|---|---|
同步 / 异步 | 同步(需时钟线) | 同步(SCL 时钟) | 异步(波特率同步) |
双工模式 | 全双工 | 半双工 | 全双工(TX/RX 独立) |
从机选择 | 独立 CS 线 | 7/10 位地址(SDA) | 无(点对点) |
典型速率 | 10Mbps~50Mbps | 400kHz~3.4MHz | 115200bps~1Mbps |
总线仲裁 | 无(依赖主机控制 CS) | 有(SDA 竞争检测) | 无 |
总结:SPI 为何是高速外设首选?
掌握 SPI 的核心在于理解时钟时序(CPOL/CPHA)和片选逻辑,从简单的 Flash 读写开始,逐步拓展到多设备组网,是进入高速串行通信的关键一步。
编辑
分享
SPI总线的优缺点有哪些?
SPI与IIC总线的区别是什么?
详细介绍一下SPI总线的拓展知识
-
四大模式(由 CPOL 和 CPHA 定义)
模式 CPOL(空闲电平) CPHA(采样边沿) 数据采样时机 典型应用(示例) 0 0(低电平) 0(第一个边沿) 时钟上升沿 W25Q64(默认模式) 1 0(低电平) 1(第二个边沿) 时钟下降沿 部分传感器(需匹配从机时序) 2 1(高电平) 0(第一个边沿) 时钟下降沿 少数高速外设 3 1(高电平) 1(第二个边沿) 时钟上升沿 兼容模式 0 的从设备 如何选择? 查看从设备数据手册(如 W25Q64 要求 CPOL=0/1 且 CPHA=0/1,故模式 0 或 3 可用)。
-
时序图解析(以模式 0 为例)
-
- 空闲时 SCLK=0,数据在第一个上升沿(时钟从 0→1)时被从机采样,主机在下降沿(1→0)时更新数据。
-
4.3 代码实现:STM32 驱动 SPI 外设(以 W25Q64 为例)
-
4.3.1 初始化步骤(分三步走)
-
GPIO 配置(设置复用功能)
void SPI_GPIO_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA时钟 GPIO_InitTypeDef GPIO_InitStruct; // SCK(PA5)和MOSI(PA7)设为复用推挽输出 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽,由SPI控制 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); // MISO(PA6)设为浮空输入 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 高阻态接收从机数据 GPIO_Init(GPIOA, &GPIO_InitStruct); // 片选线(如PC7)设为普通输出(软件控制) GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT_PP; GPIO_Init(GPIOC, &GPIO_InitStruct); GPIO_SetBits(GPIOC, GPIO_Pin_7); // 初始拉高,不选中从机 }
参数解释:
GPIO_Mode_AF_PP
:复用推挽,允许 SPI 外设控制引脚输出时钟和数据。GPIO_Mode_IN_FLOATING
:浮空输入,避免固定电平干扰从机数据读取。
-
SPI 外设配置(核心参数)
void SPI_Init_Config(void) { SPI_InitTypeDef SPI_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); // 使能SPI1时钟 SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // 全双工模式 SPI_InitStruct.SPI_Mode = SPI_Mode_Master; // 主机模式 SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; // 8位数据帧 SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low; // 时钟空闲低电平(模式0/1) SPI_InitStruct.SPI_CPHA = SPI_PHASE_1EDGE; // 第一个边沿采样(模式0/2) SPI_InitStruct.SPI_NSS = SPI_NSS_Soft; // 软件片选(通过GPIO控制CS) SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; // 波特率=系统时钟/4(如72MHz→18MHz) SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB; // 高位先行(如0x55先传最高位1) SPI_Init(SPI1, &SPI_InitStruct); SPI_Cmd(SPI1, ENABLE); // 使能SPI外设 }
关键参数:
SPI_NSS_Soft
:不使用硬件片选,通过软件控制 GPIO 作为 CS 线(灵活适配多从机)。SPI_BaudRatePrescaler
:分频系数越大,波特率越低(高速设备选小系数,如_2;低速设备选大系数,如_256)。
-
片选控制(软件管理从机)
#define W25Q64_CS_GPIO GPIOC #define W25Q64_CS_PIN GPIO_Pin_7 #define SELECT_SLAVE() GPIO_ResetBits(W25Q64_CS_GPIO, W25Q64_CS_PIN) // 拉低CS选中从机 #define DESELECT_SLAVE() GPIO_SetBits(W25Q64_CS_GPIO, W25Q64_CS_PIN) // 拉高CS取消选中
-
单字节读写(核心函数)
uint8_t SPI_TransmitReceive(uint8_t data) { while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); // 等待发送缓冲区空 SPI_I2S_SendData(SPI1, data); // 发送数据到缓冲区 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); // 等待接收缓冲区非空 return SPI_I2S_ReceiveData(SPI1); // 返回从机发送的数据 }
原理:SPI 是 “数据交换协议”,主机发送数据时,从机同时返回数据(即使从机无需响应,也需发送 dummy 数据以维持时钟)。
-
页编程示例(向 W25Q64 写入 256 字节)
void W25Q64_PageProgram(uint32_t addr, uint8_t *data, uint8_t len) { SELECT_SLAVE(); // 选中从机 SPI_TransmitReceive(W25Q64_PAGE_PROGRAM); // 发送页编程指令(0x02) SPI_TransmitReceive((addr >> 16) & 0xFF); // 发送24位地址高8位 SPI_TransmitReceive((addr >> 8) & 0xFF); // 中8位 SPI_TransmitReceive(addr & 0xFF); // 低8位 for (int i=0; i<len; i++) { SPI_TransmitReceive(data[i]); // 发送数据字节 } DESELECT_SLAVE(); // 取消选中 W25Q64_WaitBusy(); // 等待写入完成(检测状态寄存器) }
- 高速存储设备:
- FLASH 芯片(如 W25Q64):通过 SPI 读取 / 写入固件,利用高速率减少等待时间。
- SD 卡:使用 SPI 模式(非 SDIO 模式)初始化,适合低速单片机控制。
- 传感器通信:
- 加速度计 / 陀螺仪(如 ADXL345):实时传输姿态数据,全双工模式确保双向配置与数据读取。
- 显示驱动:
- OLED/LCD 控制器(如 SSD1306):通过 SPI 发送像素数据,高位先行格式适配屏幕缓存。
-
常见问题解决
- 数据错位:检查 CPOL/CPHA 是否与从机匹配(用逻辑分析仪抓取时钟与数据波形)。
- 片选未释放:确保每次通信后拉高 CS,避免多从机同时响应导致总线冲突。
- 波特率过高:高速通信时需匹配从机支持的最大速率(查看数据手册,如 W25Q64 最大 50MHz)。
-
拓展知识:SPI 变种
- Dual SPI:使用 MOSI 和 MISO 同时传输 2 位数据,速率翻倍(如 QSPI Flash)。
- Quad SPI:支持 4 位数据传输(IO0~IO3),进一步提升带宽(适用于大容量存储)。
- 半双工模式:关闭 MISO 或 MOSI,用于单工通信(如只发送不接收的传感器)。
- 速度为王:同步时钟驱动,支持超高波特率,适合 Flash、SD 卡等海量数据传输。
- 简单直接:无需复杂协议,四根线实现全双工,主机完全掌控通信节奏。
- 灵活适配:通过 CS 线轻松扩展多从机,软件配置四种模式兼容不同外设。
5. IIC(I²C):低速外设的 “双线极简方案”(同步、开漏)
5.1 物理层:两根线撑起极简通信
-
核心信号线(开漏结构是关键)
信号名 全称 方向 特性描述 SCL 同步时钟线 双向 主机生成时钟,从机同步数据;开漏输出,需外接上拉电阻(3.3V/5V,阻值 4.7kΩ~10kΩ)。 SDA 数据线 双向 传输数据,开漏输出,支持 “线与” 特性(多主机冲突时,低电平优先)。 硬件连接(以 SHT30 为例):
STM32 (软件模拟IIC) SHT30 PB6(SCL) ───────────── SCL(上拉至3.3V) PB7(SDA) ───────────── SDA(上拉至3.3V) GND ──────────────── VSS(共地)
- 上拉电阻作用:确保总线空闲时为高电平,开漏输出只能拉低,拉高需依赖上拉电阻。
-
传输特性
- 速度模式:标准模式 100kHz(低速外设,如 RTC)、快速模式 400kHz(传感器)、高速模式 3.4MHz(少用)。
- 总线负载:总电容≤400pF,超过会导致信号上升沿变缓,需缩短线缆或减少从机数量。
5.2 通信时序:从 “起始” 到 “停止” 的完整流程
- 四大核心信号(时序图是关键)
-
起始信号(Start)
- 条件:SCL 为高电平时,SDA 由高→低跳变。
- 作用:通知从机 “开始通信”,总线从空闲状态(SCL=SDA=1)转入忙状态。
- 代码实现(软件模拟):
void IIC_Start() { SDA_OUT(); // 设置SDA为输出 SCL_H; SDA_H; // 先拉高双线 delay_us(5); SDA_L; // SCL高电平时拉低SDA,产生起始信号 delay_us(5); SCL_L; // 拉低SCL,准备发送数据 }
-
停止信号(Stop)
- 条件:SCL 为高电平时,SDA 由低→高跳变。
- 作用:通知从机 “通信结束”,总线返回空闲状态。
- 代码实现:
void IIC_Stop() { SDA_OUT(); SCL_L; SDA_L; // 先拉低双线 delay_us(5); SCL_H; // 拉高SCL delay_us(5); SDA_H; // SCL高电平时拉高SDA,产生停止信号 delay_us(5); }
-
应答信号(ACK)
- 定义:接收方在接收到 8 位数据后,第 9 个时钟周期拉低 SDA(ACK)或保持高电平(NACK)。
- 主机接收时需释放 SDA(设置为输入),检测从机是否拉低 SDA:
uint8_t IIC_WaitACK() { SDA_IN(); // SDA设为输入 SCL_H; delay_us(1); uint8_t ack = !SDA_READ; // 读取SDA电平,0=ACK,1=NACK SCL_L; return ack; }
-
数据传输
- 每个字节先发最高位(MSB),SDA 在 SCL 低电平期间变化,高电平期间保持稳定。
- 发送字节示例:
void IIC_SendByte(uint8_t data) { SDA_OUT(); for (int i=7; i>=0; i--) { SDA_WRITE = (data >> i) & 1; // 从最高位开始发送 SCL_H; delay_us(1); // 高电平期间数据有效 SCL_L; delay_us(1); // 低电平期间允许数据变化 } }
-
5.3 数据帧格式:从机地址 + 读写控制 + 数据 + 应答
-
完整传输流程(以 SHT30 读温度为例)
- 主机发送起始信号 → 2. 发送从机地址(7 位)+ 写位(0) → 3. 等待 ACK →
- 发送寄存器地址(如 0x2C) → 5. 等待 ACK → 6. 发送起始信号(重复起始) →
- 发送从机地址(7 位)+ 读位(1) → 8. 等待 ACK → 9. 接收 16 位温度数据 → 10. 发送 NACK → 11. 发送停止信号
从机地址计算:
- SHT30 的 ADDR 引脚接地时,7 位地址为 0x44,写操作地址为
0x44<<1 | 0=0x88
,读操作地址为0x44<<1 | 1=0x89
。
-
多主机仲裁
- 当多个主机同时发送数据时,SDA 线会根据 “线与” 原则仲裁:若主机 1 发 1,主机 2 发 0,最终 SDA 为 0,主机 1 检测到冲突后自动退出发送,确保唯一主机控制总线。
5.4 典型应用:软件模拟 IIC 驱动 SHT30(避坑指南)
-
步骤 1:GPIO 初始化(软件模拟,STM32 为例)
void IIC_GPIO_Init() { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; // 开漏输出需软件模拟 GPIO_InitStruct.GPIO_OType = GPIO_OType_OD; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; // 内部上拉(也可外接上拉电阻) GPIO_Init(GPIOB, &GPIO_InitStruct); SCL_H; SDA_H; // 初始化为高电平 }
-
步骤 2:发送单字节并等待 ACK(核心函数)
uint8_t IIC_WriteByte(uint8_t data) { IIC_SendByte(data); // 发送8位数据 return IIC_WaitACK(); // 返回应答状态(0=成功,1=失败) }
-
步骤 3:读取温湿度数据(SHT30 为例)
void SHT30_ReadData(float *temp, float *humidity) { uint8_t data[6]; IIC_Start(); if (!IIC_WriteByte(0x88)) { // 写地址0x88(0x44<<1+0) IIC_WriteByte(0x2C); // 发送高重复性测量命令0x2C IIC_WriteByte(0x06); IIC_Stop(); delay_ms(15); // 等待转换完成 IIC_Start(); if (!IIC_WriteByte(0x89)) { // 读地址0x89(0x44<<1+1) data[0] = IIC_ReadByte(1); // 读温度MSB,发送NACK data[1] = IIC_ReadByte(1); // 读温度LSB data[2] = IIC_ReadByte(1); // 读CRC(可忽略) data[3] = IIC_ReadByte(0); // 读湿度MSB,发送ACK data[4] = IIC_ReadByte(0); // 读湿度LSB data[5] = IIC_ReadByte(1); // 读CRC,发送NACK IIC_Stop(); // 数据转换公式(参考SHT30手册) *temp = ((((uint16_t)data[0] << 8) | data[1]) / 65535.0) * 175 - 45; *humidity = ((((uint16_t)data[3] << 8) | data[4]) / 65535.0) * 100; } } }
5.5 优缺点与适用场景
优点 | 缺点 | 最佳场景 |
---|---|---|
极简硬件(仅两根线) | 速度较慢(最高 3.4MHz,常用 400kHz) | 低速传感器(SHT30、MPU6050)、RTC、EEPROM |
支持多主机仲裁 | 总线负载敏感(电容≤400pF) | 少量外设组网(≤8 个从机) |
地址自动分配(7 位地址) | 软件模拟时序复杂(需精准延时) | 对引脚资源敏感的场景(如单片机) |
5.6 新手避坑与进阶技巧
-
常见问题解决
- 应答失败(ACK=1):
- 检查从机地址是否正确(是否左移一位 + 读写位)。
- 确认从机供电正常,SDA/SCL 上拉电阻是否接入(硬件模拟时必须外接)。
- 数据错位:
- 确保软件模拟时延时精准(参考从机时序要求,如 SHT30 要求 SCL 高 / 低电平时间≥1μs)。
- 使用逻辑分析仪抓取时序,对比从机数据手册。
- 应答失败(ACK=1):
-
进阶技巧
- 硬件 IIC vs 软件模拟:
- 硬件 IIC(如 STM32 的 I2C1)适合高速场景,但需注意兼容性问题(部分单片机硬件 IIC 有 BUG)。
- 软件模拟灵活可靠,适合低速设备(如传感器),但需用
__nop()
或精准延时函数保证时序。
- 多从机地址冲突:
- 确保每个从机地址唯一(通过 ADDR 引脚设置,如 SHT30 的 ADDR 接 VDD/VSS 选择 0x44/0x45)。
- 硬件 IIC vs 软件模拟:
5.7 与其他协议对比(核心差异)
特性 | IIC | SPI | UART |
---|---|---|---|
信号线 | 2 根(SCL+SDA) | 4 根(SCK+MOSI+MISO+CS) | 2 根(TX+RX) |
同步方式 | 同步(SCL 时钟) | 同步(SCK 时钟) | 异步(波特率同步) |
双工模式 | 半双工(SDA 双向) | 全双工 | 全双工 |
地址机制 | 7/10 位地址(自动仲裁) | 独立 CS 线(主机控制) | 无(点对点) |
典型应用 | 低速传感器、RTC | 高速存储、SD 卡 | 调试串口、模块通信 |
总结:IIC 为何是低速外设首选?
- 极简设计:两根线实现多设备通信,节省引脚资源,适合传感器、存储等低速场景。
- 总线仲裁:自动解决多主机冲突,无需额外硬件逻辑。
- 灵活适配:软件模拟兼容所有单片机,硬件 IIC 提升速度,满足不同需求。
掌握 IIC 的关键在于理解开漏结构、时序细节和应答机制,从简单的传感器读取开始,逐步尝试多从机通信,是进入嵌入式设备互联的重要一步。
6. CAN:汽车级 “高可靠” 总线(异步、差分)
6.1 物理层:差分传输构建抗干扰基石
6.1.1 差分信号与总线结构
CAN 总线采用两根差分信号线 CAN_H 和 CAN_L,通过电压差传输信号,抗干扰能力极强,适合恶劣工业环境和汽车电子场景。
- 显性电平(逻辑 0):CAN_H ≈ 3.5V,CAN_L ≈ 1.5V,压差 2V(总线被主动驱动)。
- 隐性电平(逻辑 1):CAN_H ≈ CAN_L ≈ 2.5V,压差 0V(总线由上拉电阻保持高阻态)。
两种网络结构:
- 闭环结构(ISO 11898,高速短距离)
- 两端接 120Ω 终端电阻,消除信号反射,支持最高 1Mbps,最远 40 米(如汽车动力系统)。
- 典型硬件:STM32 + TJA1040 收发器,适用于高速实时控制。
- 开环结构(ISO 11519-2,低速长距离)
- 两端接 2.2kΩ 电阻,支持最高 125kbps,最远 10km(如工业传感器网络)。
6.1.2 硬件连接三要素
主机(STM32) CAN收发器(TJA1040) 从机(传感器)
PA11(RX) ────── Rx引脚 CAN_L
PA12(TX) ────── Tx引脚 CAN_H
GND ──────────── GND GND
- CAN 控制器:MCU 片上外设(如 STM32 CAN1),处理协议逻辑。
- CAN 收发器:转换 TTL 电平与 CAN 差分电平,如 TJA1040(闭环)、SN65HVD230(开环)。
- 终端电阻:长距离必须接入,短距离(<10 米)可临时省略但需测试稳定性。
6.2 核心特性:多主仲裁与可靠传输
6.2.1 多主仲裁:ID 越小优先级越高
- 非破坏性仲裁:多个节点同时发送时,逐位比较 ID,显性电平(0)优先,隐性电平(1)退出发送。
- 示例:节点 A(ID=0x123)和节点 B(ID=0x456)同时发送,前 11 位中某一位 A 发 0、B 发 1,B 检测到总线状态与自身发送不一致,立即停止,A 继续发送。
- 数据帧 vs 遥控帧:同 ID 时,数据帧的 RTR 位为显性(0),优先级高于遥控帧(RTR=1)。
- 标准帧 vs 扩展帧:标准帧(11 位 ID)的 IDE 位为显性(0),比扩展帧(29 位 ID,IDE=1)优先级高。
6.2.2 错误检测与自动重发
- 5 种错误类型:位错误、填充错误、CRC 错误、格式错误、ACK 错误。
- 错误处理:
- 检测到错误的节点发送 错误帧(主动错误标志 = 6 个显性位,被动错误标志 = 6 个隐性位)。
- 所有节点接收错误帧后,发送节点自动重发数据,确保可靠传输(如汽车刹车信号必须正确到达)。
6.3 帧结构:数据帧详解(最常用场景)
6.3.1 数据帧 7 大段(标准格式为例)
[帧起始(1位显性)] → [仲裁段(11位ID+RTR位)] → [控制段(6位)] → [数据段(0-8字节)] → [CRC段(16位)] → [ACK段(2位)] → [帧结束(7位隐性)]
- 仲裁段:11 位 ID 决定优先级,ID 越小优先级越高(如刹车信号 ID=0x001 优先于温度信号 ID=0x100)。
- 控制段:DLC 字段表示数据长度(0-8 字节,CAN 单次最多传 8 字节,确保实时性)。
- ACK 段:接收方在 ACK 槽发送显性位(0)表示成功接收,发送方检测到 ACK 后继续传输。
6.3.2 扩展帧 vs 标准帧
特性 | 标准帧 | 扩展帧 |
---|---|---|
ID 长度 | 11 位 | 29 位 |
适用场景 | 中小规模网络(≤30 节点) | 复杂系统(如汽车多 ECU) |
优先级 | 高于同 ID 扩展帧 | 低于同 ID 标准帧 |
6.4 代码实现:STM32 驱动 CAN 总线(从初始化到数据发送)
6.4.1 硬件准备(STM32F103 为例)
- 所需工具:STM32CubeMX(辅助配置)、CANalyzer(总线抓包调试)。
- 关键引脚:
- PA12(CAN_TX):复用推挽输出,连接收发器 Tx 引脚。
- PA11(CAN_RX):浮空输入,连接收发器 Rx 引脚。
6.4.2 初始化步骤(逐行解析)
#include "stm32f10x.h"
#define CAN_TX_PIN GPIO_Pin_12
#define CAN_RX_PIN GPIO_Pin_11
#define CAN_PERIPH CAN1
void CAN_InitSetup(void) {
GPIO_InitTypeDef GPIO_InitStruct;
CAN_InitTypeDef CAN_InitStruct;
CAN_FilterInitTypeDef CAN_FilterStruct;
// 1. 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); // 使能CAN1时钟
// 2. 配置GPIO为CAN复用功能
// TX引脚:复用推挽输出
GPIO_InitStruct.GPIO_Pin = CAN_TX_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽,由CAN控制器驱动
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// RX引脚:浮空输入
GPIO_InitStruct.GPIO_Pin = CAN_RX_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 高阻态接收差分信号
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 3. 配置CAN核心参数(波特率1Mbps)
CAN_InitStruct.CAN_Mode = CAN_Mode_Normal; // 正常模式(非测试模式)
CAN_InitStruct.CAN_SJW = CAN_SJW_1tq; // 同步跳转宽度:1个时间量子
CAN_InitStruct.CAN_BS1 = CAN_BS1_6tq; // 时间段1:6个时间量子
CAN_InitStruct.CAN_BS2 = CAN_BS2_3tq; // 时间段2:3个时间量子
CAN_InitStruct.CAN_Prescaler = 6; // 预分频器:72MHz/(6*(6+3+1))=1Mbps
CAN_Init(CAN_PERIPH, &CAN_InitStruct);
// 4. 配置滤波器(接收所有ID,简化示例)
CAN_FilterStruct.CAN_FilterNumber = 0; // 滤波器0
CAN_FilterStruct.CAN_FilterMode = CAN_FilterMode_IdMask; // 掩码模式,接收所有ID
CAN_FilterStruct.CAN_FilterScale = CAN_FilterScale_32bit; // 32位过滤(ID+掩码各16位,全0表示不过滤)
CAN_FilterInit(&CAN_FilterStruct);
CAN_FilterCmd(0, ENABLE); // 使能滤波器0
// 5. 使能CAN控制器
CAN_Cmd(CAN_PERIPH, ENABLE);
}
6.4.3 发送数据帧(标准帧,ID=0x123,数据 = 0x55 0xAA)
void CAN_SendDataFrame(uint16_t std_id, uint8_t* data, uint8_t len) {
CanTxMsg TxMsg;
uint8_t mailbox;
// 配置消息参数
TxMsg.StdId = std_id; // 标准ID:0x123(11位有效,实际取低11位)
TxMsg.IDE = CAN_ID_STD; // 标准帧
TxMsg.RTR = CAN_RTR_DATA; // 数据帧(非遥控帧)
TxMsg.DLC = len; // 数据长度:2字节
for (int i=0; i<len; i++) {
TxMsg.Data[i] = data[i]; // 填充数据
}
// 发送并等待成功
mailbox = CAN_Transmit(CAN_PERIPH, &TxMsg);
while (CAN_TransmitStatus(CAN_PERIPH, mailbox) != CAN_TxStatus_Ok); // 阻塞直到发送完成
}
// 主函数调用示例
int main() {
CAN_InitSetup();
uint8_t send_data[] = {0x55, 0xAA};
CAN_SendDataFrame(0x123, send_data, 2);
while(1);
}
6.5 典型应用场景
6.5.1 汽车电子(核心场景)
- 场景:发动机 ECU、刹车系统、仪表盘通过 CAN 总线互联。
- 示例:刹车踏板被踩下时,刹车 ECU 发送 ID=0x001 的紧急信号(低 ID 高优先级),所有节点立即响应,确保刹车指令优先传输。
6.5.2 工业自动化(长距离抗干扰)
- 场景:工厂传感器(温度、压力)组网,距离 1000 米,波特率 50kbps。
- 优势:差分信号抗电磁干扰,自动重发确保数据不丢失,适合钢铁厂、煤矿等恶劣环境。
6.5.3 医疗设备(高精度同步)
- 场景:多参数监护仪,各模块(心率、血氧)通过 CAN 总线同步数据。
- 特点:8 字节数据段刚好容纳实时生理参数,CRC 校验确保数据准确,适合医疗设备对可靠性的严苛要求。
6.6 新手避坑与优化技巧
6.6.1 常见问题排查
问题现象 | 可能原因 | 解决方法 |
---|---|---|
数据乱码 / 丢帧 | 终端电阻缺失或阻值错误 | 总线两端添加 120Ω 电阻(闭环)或 2.2kΩ 电阻(开环) |
节点无法通信 | 波特率不匹配 | 检查所有节点的CAN_Prescaler 、BS1 、BS2 参数,确保一致 |
多个节点同时发送失败 | ID 冲突(同 ID 不同帧类型) | 分配唯一 ID,数据帧 ID 避免与遥控帧重复 |
6.6.2 波特率计算(关键公式)
波特率 = 系统时钟 / (预分频器 × (BS1 + BS2 + 1))
示例(STM32 72MHz):
预分频器=6,BS1=6tq,BS2=3tq
波特率 = 72MHz / (6 × (6+3+1)) = 1Mbps
6.6.3 硬件优化
- 线缆选择:高速场景用屏蔽双绞线(减少 EMI),低速长距离用非屏蔽双绞线。
- 收发器选型:闭环选 TJA1040(高速),开环选 SN65HVD230(低速),确保与网络结构匹配。
6.7 与其他协议对比(核心差异)
特性 | CAN | RS485 | SPI | I2C |
---|---|---|---|---|
信号类型 | 差分(抗干扰最强) | 差分(半双工) | 单端(同步时钟) | 单端(开漏上拉) |
多主支持 | 支持(仲裁机制) | 单主多从 | 单主多从(CS 线) | 多主(仲裁通过 SDA) |
数据长度 | 0-8 字节(固定) | 任意(需自定义) | 任意(同步传输) | 任意(单字节应答) |
典型场景 | 汽车、工业控制 | 仪表组网 | 高速存储(Flash) | 低速传感器(SHT30) |
错误处理 | 自动重发 + CRC 校验 | 无(依赖上层协议) | 无 | 无 |
总结:为什么 CAN 是工业级首选?
- 可靠性:差分传输 + 仲裁机制 + 错误重发,适合高容错场景(如汽车刹车系统)。
- 灵活性:支持多主架构,适配不同规模网络(从小型传感器到汽车多 ECU 集群)。
- 实时性:ID 优先级确保关键数据优先传输,满足工业控制对延迟的严格要求。
掌握 CAN 总线的关键在于理解差分信号原理、仲裁机制和帧结构,从简单的单节点通信开始,逐步尝试多节点组网,结合逻辑分析仪调试,可快速掌握工业级通信设计的核心能力。
三、应用层协议:MODBUS—— 工业设备的 “通用语言”
MODBUS 是运行在底层协议上的应用层协议,定义了数据格式和功能码,常见于工业场景。它通过统一的数据交互规则,实现不同厂商设备的互联互通。
3.1 MODBUS 与 RS 系列协议的本质区别
3.1.1 物理层与协议层的定位差异
维度 | RS232/RS485 | MODBUS |
---|---|---|
协议层级 | 物理层(定义电气特性、传输介质) | 应用层(定义数据格式、通信规则) |
核心功能 | 实现信号传输(如电平转换、抗干扰) | 实现数据交互(如寄存器读写、设备控制) |
典型场景 | 硬件连接(如 PC 串口、工业总线) | 设备通信(如 PLC 与传感器、仪表) |
兼容性 | 仅支持特定物理接口(如 RS485 差分信号) | 可运行于 RS485/RS232/Ethernet 等多种物理层 |
示例:
- RS485 负责将数据以差分信号形式传输(如 A/B 线压差 ±2V)。
- MODBUS 则定义数据帧格式(如从机地址、功能码、校验),确保主从设备理解数据含义。
3.1.2 数据传输方式对比
特性 | RS232 | RS485 | MODBUS RTU |
---|---|---|---|
信号类型 | 单端信号(依赖 GND 参考) | 差分信号(A/B 线压差) | 基于 RS485/RS232 的二进制数据帧 |
传输距离 | 15 米内(抗干扰弱) | 1200 米 @100Kbps(抗干扰强) | 受限于物理层(如 RS485 距离) |
节点数量 | 1 对 1(点对点) | 32 个从机(需终端电阻) | 主从模式(1 主 + N 从) |
典型应用 | 设备调试(如单片机串口) | 工业组网(如 PLC 与仪表) | 工业自动化(如数据采集、控制) |
示例:
- RS485 网络中,MODBUS 主站通过地址 0x01 向从站发送读寄存器命令(功能码 0x03),从站返回数据并附加 CRC 校验。
3.2 MODBUS 核心知识点详解
3.2.1 两种通信模式
-
MODBUS-RTU(基于 RS485/TTL-UART)
- 传输效率:二进制数据,1 帧可携带 256 字节,适合高速场景。
- 帧结构:
[从机地址(1B)][功能码(1B)][数据长度(2B)][数据内容(nB)][CRC校验(2B)]
- 应用:工业设备本地通信(如 PLC 与变频器)。
-
MODBUS-TCP(基于以太网)
- 传输效率:数据封装在 TCP 包中,支持远程监控。
- 帧结构:
[MBAP头(7B)][功能码(1B)][数据内容(nB)]
- 应用:跨网络通信(如工厂上位机与远程设备)。
示例:
- MODBUS-RTU 读寄存器命令:
01 03 00 00 00 03 05 CB
(从机地址 1,读 3 个寄存器,CRC 校验 0x05CB)。 - MODBUS-TCP 读寄存器命令:
00 01 00 00 00 06 01 03 00 00 00 03
(MBAP 头 + 功能码 + 地址)。
3.2.2 功能码体系(核心操作指令)
功能码 | 名称 | 描述 | 示例 |
---|---|---|---|
0x01 | 读线圈状态 | 读取开关量输出(如继电器状态) | 读取从机地址 2 的 00001-00008 线圈状态 |
0x03 | 读保持寄存器 | 读取可写寄存器(如传感器测量值) | 读取从机地址 3 的 40001-40003 寄存器(共 3 个) |
0x06 | 写单个寄存器 | 写入单个寄存器(如设置参数) | 向从机地址 1 的 40002 寄存器写入 0x1234 |
0x10 | 写多个寄存器 | 写入连续寄存器块(如批量设置参数) | 向从机地址 5 的 40001-40005 寄存器写入 5 个数据 |
代码示例:
# 使用 minimalmodbus 库读取保持寄存器
import minimalmodbus
instrument = minimalmodbus.Instrument('/dev/ttyUSB0', 1) # 端口、从机地址
instrument.serial.baudrate = 9600 # 波特率
instrument.serial.bytesize = 8 # 数据位
instrument.serial.parity = 'N' # 校验位
instrument.serial.stopbits = 1 # 停止位
data = instrument.read_registers(0, 3) # 读取 40001-40003 寄存器
print("读取结果:", data)
3.3 MODBUS 与 RS485 协同工作机制
3.3.1 硬件连接规范
-
线缆选择:
- 推荐使用双绞屏蔽线(如 Belden 3107A),屏蔽层单端接地。
- 长距离(>500 米)需加 120Ω 终端电阻(跨接 A/B 线)。
-
组网拓扑:
- 总线型拓扑(菊花链连接),避免星型结构。
- 节点间距 ≤10 米,分支长度 ≤0.5 米。
示例:
- 工业现场中,PLC 通过 RS485 总线连接 10 台仪表,每台仪表地址唯一(1-10),终端电阻接在总线两端。
3.3.2 通信流程与时序
-
主从通信流程:
-
时序要求:
- 波特率匹配:主从设备波特率偏差需 <0.5%。
- 帧间隔:需满足 3.5 字符周期的空闲时间(如 9600bps 时约 3.6ms)。
示例:
- 主站发送命令后,若 100ms 内未收到响应,则判定超时并重新发送。
3.4 常见误区与排错指南
3.4.1 常见误区
-
MODBUS 只能用 RS485 传输:
- 实际支持 RS232/RS485/Ethernet 等 7 种物理层。
- 示例:MODBUS-TCP 可通过以太网实现跨网段通信。
-
RS485 只能跑 MODBUS 协议:
- RS485 仅是物理层,可自定义协议(如自定义数据帧格式)。
- 示例:某些工业设备使用私有协议通过 RS485 传输。
3.4.2 故障排查步骤
-
硬件层:
- 检查线缆通断(万用表测 A/B 线电阻,正常应为 120Ω)。
- 测量静态电压(A/B 线压差约 2-3V)。
-
协议层:
- 确认功能码是否正确(如写线圈用 0x05,而非 0x06)。
- 校验 CRC 或 LRC 校验值(工具:ModbusPal、ModbusPoll)。
示例:
- 主站发送命令后从站无响应,排查发现从站地址设置错误(主站发 0x01,从站实际地址 0x02)。
3.5 实际应用场景对比
场景 | 推荐方案 | 优势 |
---|---|---|
短距离调试(如单片机与 PC) | RS232 + MODBUS-RTU | 接线简单(3 根线),成本低 |
工业多设备组网 | RS485 + MODBUS-RTU | 抗干扰强,支持 32 个从机 |
远程监控(跨网段) | MODBUS-TCP | 支持 IP 网络,传输距离无限制 |
高可靠性汽车电子 | CAN 总线 + 自定义协议 | 多主仲裁,抗干扰能力极强 |
示例:
- 智能建筑中,空调控制器通过 RS485 + MODBUS-RTU 连接到 PLC,实现集中监控。
3.6 总结:MODBUS 与 RS 系列的协同关系
层级 | RS232/RS485 | MODBUS |
---|---|---|
物理层 | 提供电气标准(如差分信号) | 无(依赖底层物理层) |
协议层 | 无(仅传输原始数据) | 定义数据格式、功能码、校验规则 |
应用层 | 无(仅硬件连接) | 实现设备通信(如寄存器读写) |
关键结论:
- RS232/RS485 是 “高速公路”,MODBUS 是 “交通规则”。
- 选择 RS485 + MODBUS-RTU 可兼顾长距离、抗干扰和协议通用性。
- MODBUS-TCP 适合需要远程访问的复杂系统。
通过以上对比,可快速掌握 MODBUS 与 RS 系列协议的核心差异及实际应用技巧。
四、七大协议核心对比表(含 UART/USART)
特性 | UART | USART | RS232 | RS485 | SPI | IIC | CAN | MODBUS |
---|---|---|---|---|---|---|---|---|
同步方式 | 异步 | 异步 + 同步 | 异步 | 异步 | 同步 | 同步 | 异步 | 应用层协议 |
物理层 | TTL | TTL | 单端信号 | 差分信号 | 单端信号 | 开漏输出 | 差分信号 | 多样(RS485/RS232 / 以太网等) |
线缆数量 | 2(TX/RX) | 3(TX/RX/CTS/RTS) | 3(TX/RX/GND) | 2(A/B) | 4(CLK/MOSI/MISO/CS) | 2(SDA/SCL) | 2(CAN_H/CAN_L) | 依底层协议 |
传输距离 | 短(cm 级) | 短(cm 级) | 15 米 | 1200 米 @100Kbps | cm 级 | 10 米 | 10km@5Kbps | 依底层协议 |
多设备 | 点对点 | 点对点 | 点对点 | 1 主多从 | 1 主多从 | 多主多从 | 多主多从 | 1 主多从 |
典型速度 | 115200bps | 1Mbps+ | 115200bps | 10Mbps@100 米 | 50Mbps+ | 400kHz | 1Mbps@40 米 | 依底层协议 |
抗干扰 | 弱 | 弱 | 弱 | 强 | 中等 | 弱 | 极强 | 依底层协议 |
应用场景 | 调试 | 高速外设 | 串口设备 | 工业仪表 | 高速外设 | 低速外设 | 汽车电子 | 工业通信 |
对比表核心参数解析
1. 同步方式
- UART/USART/RS232/RS485/CAN:异步通信,无需时钟线,依赖波特率同步。
- SPI/IIC:同步通信,需时钟线(CLK/SCL)。
- MODBUS:应用层协议,依赖底层物理层同步方式。
2. 物理层
- TTL:3.3V/5V 电平(如单片机串口)。
- 差分信号:RS485/CAN 通过 A/B 线压差传输,抗干扰强。
- 开漏输出:IIC 需上拉电阻,支持多主设备。
3. 传输距离
- RS485:1200 米 @100Kbps(工业场景首选)。
- CAN:10km@5Kbps(汽车电子长距离通信)。
- SPI:cm 级(高速但抗干扰弱)。
4. 多设备支持
- RS485:1 主 32 从(需终端电阻)。
- IIC:多主多从(通过地址仲裁)。
- CAN:多主多从(非破坏性仲裁)。
5. 典型速度
- SPI:50Mbps+(如高速 Flash 通信)。
- USART:1Mbps+(同步模式下)。
- IIC:400kHz(低速外设控制)。
五、如何选择?
1. 按 “距离 + 设备数量” 选底层协议
- 短距离(cm 级)、高速外设:SPI(如连接 Flash)。
- 短距离(米级)、低速多设备:IIC(如传感器组网)。
- 中距离(100 米级)、多设备:RS485+MODBUS(如工业仪表)。
- 长距离(km 级)、高可靠:CAN 总线(如汽车 ECU)。
- PC 调试、点对点:UART+RS232/TTL(如单片机连电脑)。
2. 按 “同步需求” 选 UART/USART
- 纯异步场景:选 UART(或 USART 配置为 UART 模式)。
- 需同步高速传输:选 USART 的同步模式(加 CLK 线)。
3. 按 “行业场景” 选协议
- 工业自动化:RS485+MODBUS(性价比高)或 CAN(高可靠)。
- 智能家居:IIC(传感器)+UART(控制面板)。
- 汽车电子:CAN 总线(ECU 控制)。
- 5G 工业通信:DTU 模块(如腾讯云 TD210)实现串口数据与 IP 协议转换。
六、关键技术深度解析
1. 双工模式的工程实现
- 半双工:
- RS485:通过 DE/RE 引脚切换收发状态(如 MAX485 芯片)。
- 代码示例:
// 发送数据时 DE = 1; // 使能发送 USART_SendData(USART1, data); while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); DE = 0; // 禁止发送,切换为接收
- 全双工:
- SPI:主机和从机同时收发数据(如 STM32 的 SPI 外设)。
- 特性:发送一个字节必接收一个字节,硬件自动完成同步。
2. 差分信号与开漏输出的原理
- 差分信号:
- RS485/CAN:通过 A/B 线压差传输数据,抗共模干扰。
- 优势:长距离传输(如 RS485 达 1200 米),抗干扰能力强。
- 开漏输出:
- IIC:SDA/SCL 需外接上拉电阻,实现 “线与” 仲裁。
- 原理:高阻态表示 1,低电平表示 0,多主机竞争时自动降权。
3. 仲裁机制的工程实现
- CAN 总线:
- 非破坏性仲裁:ID 越小优先级越高,总线冲突时低 ID 帧优先发送。
- 示例:节点 A(ID=0x123)与节点 B(ID=0x456)同时发送,ID 逐位比较,0x123 优先级更高。
- IIC 总线:
- 线与仲裁:多主机竞争时,先拉低 SDA 的主机获得总线控制权。
- 示例:主机 A 发送 “101”,主机 B 发送 “100”,仲裁时主机 B 获胜。
七、总结:从 “接线” 到 “协议” 的核心逻辑
- UART/USART 是逻辑协议,定义数据格式(异步 / 同步),需搭配物理层(如 RS232/TTL)。
- RS232/RS485 是物理层标准,解决 “如何传”(电平、线缆),常与 UART 结合。
- SPI/IIC 是单片机外设常用的同步总线,适合短距离高速 / 低速设备。
- CAN 是工业级高可靠总线,适合多主多从、长距离场景。
- MODBUS 是应用层协议,让不同设备 “说同一种语言”,常跑在 RS485/CAN/TCP 上。
掌握这些协议的核心差异,就能在嵌入式开发中根据需求灵活选择,从硬件接线到软件协议实现一气呵成!建议从单片机外设(如 SPI Flash、IIC 传感器)开始实践,逐步深入工业级总线(RS485/CAN)和 MODBUS 协议,实现从入门到进阶的跨越。