目录
- 一、引子:为什么"软件能模拟 I2C"?
- 二、通信的本质:电平 + 时间 = 协议
- 2.1 一根线只有 0 和 1,意义来自"何时变"
- 2.2 同步与异步:有没有那根时钟线
- 2.3 推挽、开漏、三态:电气层的三种"性格"
- 2.4 我们看到的字符,和线上那串 0/1 是怎么对应起来的?
- 2.5 一张图看懂:字符 → 二进制 → 线上电平
- 三、三大协议一句话定性
- 3.1 UART:异步、点对点、两根线
- 3.2 I2C:同步、半双工、多设备、两根线
- 3.3 SPI:同步、全双工、四根线、最快
- 3.4 对比一览表
- 3.5 三大协议一图对比
- 四、硬件外设 vs 软件模拟:波形由谁产生?
- 4.1 硬件外设:专用电路自动发波形
- 4.2 软件模拟(Bit-Banging):CPU 手工翻 GPIO
- 4.3 为什么 I2C/SPI 适合模拟,UART 不适合
- 4.4 工程选型:能用硬件就别模拟
- 4.5 硬件外设 vs 软件模拟
- 五、驱动的本质:把"我要数据"翻译成"线上怎么动"
- 5.1 驱动 = 分层翻译器
- 5.2 以 BMP280 双传感器为例的四层驱动
- 5.3 好的驱动让上层"忘了"底层是硬件还是软件
- 5.4 四层驱动一图看懂
- 六、CubeMX 工程配置实战(STM32 为例)
- 6.1 UART 配置(以 USART3 接 ESP8266 为例)
- 6.2 I2C 配置(以 I2C1 接双 BMP280 为例)
- 6.3 SPI 配置(以 SPI 接 W25Q128 为例)
- 七、应用场景与选型准则
- 八、高频排坑指南(血泪经验)
- 8.1 I2C 读不到器件
- 8.2 I2C 地址冲突
- 8.3 UART 乱码
- 8.4 SPI 不响应
- 8.5 软件模拟 I2C 偶发失败
- 8.6 EEPROM 写后立刻读
- 8.7 CubeMX 生成代码后引脚复用冲突
摘要
通信的本质,是在约定好的时间把一根线拉高或拉低。UART、I2C、SPI 看似各异,剥到最底层都是"电平+时间"的游戏。本文从"为什么软件能模拟 I2C"这个朴素问题出发,讲透通信协议的本质、硬件外设与软件模拟的取舍、驱动的分层本质,并结合 STM32CubeMX 给出三大协议的工程配置清单与高频排坑指南。
关键词:嵌入式通信、UART、I2C、SPI、CubeMX、驱动分层、开漏上拉、波特率、CPOL/CPHA
一、引子:为什么"软件能模拟 I2C"?
通信,是这个时代最容易被忽略、却又最不可或缺的基础设施。
早上你扫码进地铁,闸机在零点几秒内完成了与后台服务器的握手;手机刷短视频,每一帧画面都经过基站、光纤、CDN 的层层接力;就连你手里这块开发板,也要靠 UART 把调试信息吐到电脑屏幕上。可以说,没有通信,就没有现代生活,更没有现代工业。
而在嵌入式世界里,通信的重要性被放得更大。一个智能家居网关,要同时跟温湿度传感器、门锁、摄像头打交道;一台医疗设备,要把采集到的生理信号实时传给上位机;一辆汽车里,几十个 ECU 通过 CAN 总线协同工作。通信协议,就是这些设备之间唯一的"语言"——协议选得对不对、驱动写得好不好,直接决定整个系统能不能稳定跑起来。
也正因为如此,理解通信的底层原理,才不是一个"可选项",而是嵌入式工程师的必修课。今天这篇文章,我们就从最朴素的问题出发,把通信这件事彻底讲透。
先讲一个我亲眼见过的场景。
新来的同事接手一个老项目,翻到 bsp_iic.c,看到里面写着:
void MyI2C_Start(void)
{
SDA_Write(1);
SCL_Write(1);
delay_us(10);
SDA_Write(0);
delay_us(10);
SCL_Write(0);
}
他愣了半天,转头问我:“这也叫 I2C?I2C 不是 STM32 片上的一个外设吗?”
这个问题问得特别好。它戳中了很多嵌入式工程师心里那个模糊的角落:协议到底是硬件,还是一份说明书?
答案是:协议不是硬件,协议是一份"电平怎么变、变多快"的说明书。CPU 翻 GPIO 能拼出来,专用电路也能拼出来。两者只是"谁来执行这份说明书"的区别。
这篇文章,我们就从"电平 + 时间"这个最朴素的角度,把 UART、I2C、SPI 三大协议、硬件外设与软件模拟的取舍、驱动的分层本质一次讲透,最后落到 STM32CubeMX 的工程配置与血泪排坑。
二、通信的本质:电平 + 时间 = 协议
2.1 一根线只有 0 和 1,意义来自"何时变"
一根导线,在数字世界里只有两种状态:高电平(1)和低电平(0)。单看某一瞬间,这根线什么信息都表达不了——它只是一个电平。
信息的含义,来自时间轴上的排列。
举个最小例子。我跟你约定:
线拉低 10μs,再拉高,表示"START"。
那么当我在某个时刻把线拉低 10μs 再拉高,你就知道:哦,通信开始了。
再约定:
拉低 20μs 表示"0",拉高 20μs 表示"1"。
于是这根线就能传数据了。
你看,所谓协议,本质就是时序 + 电平的排列规则。0 和 1 本身没有意义,意义来自它们"什么时候变、变多快、按什么顺序变"。
💡 这一句话,是理解全文的钥匙。后面所有协议、所有驱动、所有排坑,都是这句话的展开。
2.2 同步与异步:有没有那根时钟线
通信双方要读懂"何时变",就必须有一个共同的时间基准。根据这个基准从哪来,通信分成两大类:
- 同步通信:发送方额外拉一根时钟线(SCL / SCLK),告诉接收方"每个时钟沿,你采样一次"。I2C、SPI 都属于这一类。
- 异步通信:没有时钟线,双方各自按约定的波特率(每秒多少位)硬对齐。UART 属于这一类。
这个区别极其关键,它直接决定了"为什么 UART 难模拟、I2C/SPI 好模拟"——我们到第四章细讲。
2.3 推挽、开漏、三态:电气层的三种"性格"
协议跑在哪根线上,还取决于这根线的电气特性。三种最常见的输出结构:
| 推挽(Push-Pull) | 能主动拉高、也能主动拉低,驱动能力强 | UART 的 TX、SPI 的 MOSI/MISO/SCLK |
| 开漏(Open-Drain) | 只能主动拉低,拉高靠外部上拉电阻 | I2C 的 SCL/SDA(必须上拉!) |
| 三态(Tri-state) | 可输出高/低,也可释放为高阻 | SPI 多从机共享 MISO 时,未选中的从机输出高阻 |
⚠️ 开漏 + 无上拉 = 总线永远拉不高。这是 I2C 读不到器件的第一大元凶,第八章排坑会重点讲。
2.4 我们看到的字符,和线上那串 0/1 是怎么对应起来的?
前面讲了"电平 + 时间 = 协议",但你可能还有一个更朴素的疑问:我在电脑上敲一个字母 A,它怎么就成了线上的一串 0 和 1?这中间到底经历了什么?
答案是:靠一张"翻译表"——字符编码表(Character Encoding)。它规定了"哪个字符对应哪个二进制数"。
2.4.1 字符编码表:字符 ↔ 二进制数的"字典"
最经典的是 ASCII 码(American Standard Code for Information Interchange,美国信息交换标准代码)。它用 7 位二进制(0~127)给 128 个字符编号:
| A | 65 | 1000001 | 0x41 |
| B | 66 | 1000010 | 0x42 |
| a | 97 | 1100001 | 0x61 |
| 0 | 48 | 0110000 | 0x30 |
| \\n(换行) | 10 | 0001010 | 0x0A |
所以,当你在串口助手里发送字符 A 时,它先被查表翻译成二进制 01000001(实际传输常补足 8 位),然后这 8 个 bit 才被 UART 按波特率一位一位地变成 TX 引脚上的电平变化。
2.4.2 这张表定义在哪里?由谁完成翻译?
- 定义:ASCII 表是国际标准,由美国国家标准学会(ANSI)制定,后来被国际标准化组织(ISO)采纳为 ISO/IEC 646 标准。它不依赖任何硬件厂商,是全世界通用的"字典"。
- 翻译执行者:翻译动作发生在软件层,由你的应用程序 + 操作系统 + 编译器/库共同完成。比如你在 C 语言里写 char c = 'A';,编译器把字符字面量 'A' 直接编译成数值 65;你在串口助手里输入 A,是上位机软件查表把它转成 0x41。CPU 和 UART 外设本身完全不认识字符,它们只认二进制数。
2.4.3 从字符到线上波形的完整链路
以 UART 发送一个字符 A 为例,完整流程是:
你在电脑上敲 'A'
↓
① 上位机软件查 ASCII 表:'A' → 0x41(二进制 01000001)
↓
② 软件把 0x41 写入 UART 外设的发送数据寄存器(TDR)
↓
③ UART 外设硬件自动加上起始位(0)、可选的校验位、停止位(1)
↓
④ 外设按波特率把每一位依次输出到 TX 引脚:低电平=0,高电平=1
↓
线上出现一串电平变化:起始位 + 01000001 + 停止位
接收端则完全反过来:RX 引脚收到电平变化 → UART 外设按波特率采样、去掉起始/停止位、拼回 0x41 → 存入接收寄存器 → 软件读出 0x41 → 查 ASCII 表翻译回字符 A → 显示在屏幕上。
2.4.4 中文怎么办?——ASCII 不够用,还有 UTF-8
ASCII 只有 128 个字符,连中文都装不下。所以后来又有了 GB2312/GBK(简体中文编码)、UTF-8(Unicode 的可变长编码)等。UTF-8 用 1~4 个字节表示一个字符,比如汉字"通"的 UTF-8 编码是 E9 80 9A(三个字节)。编码表不同,同一个字符对应的二进制串就不同——这就是为什么串口收到中文乱码时,往往是发送端和接收端用的编码表不一致。
💡 一句话总结:字符编码表就是"字符 ↔ 二进制"的字典,定义在标准里,翻译由软件完成,最终由硬件外设把二进制变成线上电平。 协议管的是"二进制怎么在线上传",编码表管的是"字符怎么变成二进制",两者各司其职。
2.5 一张图看懂:字符 → 二进制 → 线上电平
#mermaid-svg-kcwRx6dPsNuJ090I{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kcwRx6dPsNuJ090I .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kcwRx6dPsNuJ090I .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kcwRx6dPsNuJ090I .error-icon{fill:#552222;}#mermaid-svg-kcwRx6dPsNuJ090I .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kcwRx6dPsNuJ090I .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kcwRx6dPsNuJ090I .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kcwRx6dPsNuJ090I .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kcwRx6dPsNuJ090I .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kcwRx6dPsNuJ090I .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kcwRx6dPsNuJ090I .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kcwRx6dPsNuJ090I .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kcwRx6dPsNuJ090I .marker.cross{stroke:#333333;}#mermaid-svg-kcwRx6dPsNuJ090I svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kcwRx6dPsNuJ090I p{margin:0;}#mermaid-svg-kcwRx6dPsNuJ090I .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-kcwRx6dPsNuJ090I .cluster-label text{fill:#333;}#mermaid-svg-kcwRx6dPsNuJ090I .cluster-label span{color:#333;}#mermaid-svg-kcwRx6dPsNuJ090I .cluster-label span p{background-color:transparent;}#mermaid-svg-kcwRx6dPsNuJ090I .label text,#mermaid-svg-kcwRx6dPsNuJ090I span{fill:#333;color:#333;}#mermaid-svg-kcwRx6dPsNuJ090I .node rect,#mermaid-svg-kcwRx6dPsNuJ090I .node circle,#mermaid-svg-kcwRx6dPsNuJ090I .node ellipse,#mermaid-svg-kcwRx6dPsNuJ090I .node polygon,#mermaid-svg-kcwRx6dPsNuJ090I .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kcwRx6dPsNuJ090I .rough-node .label text,#mermaid-svg-kcwRx6dPsNuJ090I .node .label text,#mermaid-svg-kcwRx6dPsNuJ090I .image-shape .label,#mermaid-svg-kcwRx6dPsNuJ090I .icon-shape .label{text-anchor:middle;}#mermaid-svg-kcwRx6dPsNuJ090I .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kcwRx6dPsNuJ090I .rough-node .label,#mermaid-svg-kcwRx6dPsNuJ090I .node .label,#mermaid-svg-kcwRx6dPsNuJ090I .image-shape .label,#mermaid-svg-kcwRx6dPsNuJ090I .icon-shape .label{text-align:center;}#mermaid-svg-kcwRx6dPsNuJ090I .node.clickable{cursor:pointer;}#mermaid-svg-kcwRx6dPsNuJ090I .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-kcwRx6dPsNuJ090I .arrowheadPath{fill:#333333;}#mermaid-svg-kcwRx6dPsNuJ090I .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kcwRx6dPsNuJ090I .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kcwRx6dPsNuJ090I .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kcwRx6dPsNuJ090I .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kcwRx6dPsNuJ090I .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kcwRx6dPsNuJ090I .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-kcwRx6dPsNuJ090I .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kcwRx6dPsNuJ090I .cluster text{fill:#333;}#mermaid-svg-kcwRx6dPsNuJ090I .cluster span{color:#333;}#mermaid-svg-kcwRx6dPsNuJ090I div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-kcwRx6dPsNuJ090I .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kcwRx6dPsNuJ090I rect.text{fill:none;stroke-width:0;}#mermaid-svg-kcwRx6dPsNuJ090I .icon-shape,#mermaid-svg-kcwRx6dPsNuJ090I .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kcwRx6dPsNuJ090I .icon-shape p,#mermaid-svg-kcwRx6dPsNuJ090I .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-kcwRx6dPsNuJ090I .icon-shape .label rect,#mermaid-svg-kcwRx6dPsNuJ090I .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kcwRx6dPsNuJ090I .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kcwRx6dPsNuJ090I .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kcwRx6dPsNuJ090I :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
字符 'A'
查 ASCII 表
0x41 = 01000001
UART 外设加起始/停止位
TX 引脚电平变化
三、三大协议一句话定性
3.1 UART:异步、点对点、两根线
- 两根线:TX(发送)、RX(接收)
- 异步,无时钟线,靠波特率对齐
- 全双工:TX/RX 同时收发
- 无寻址:点对点,一根线只能接一个对端
- 典型速率:110 ~ 921600 baud
- 典型应用:调试打印、GPS、蓝牙透传、WiFi AT 指令(ESP8266)
3.2 I2C:同步、半双工、多设备、两根线
- 两根线:SCL(时钟)、SDA(数据)
- 同步,有时钟线
- 半双工:同一时刻只能一个方向
- 7-bit / 10-bit 地址寻址,一条总线可挂多个设备
- 典型速率:100kHz(标准)~ 3.4MHz(高速)
- 典型应用:BMP280 气压传感器、SHT30 温湿度、AT24C02 EEPROM、OLED 屏
3.3 SPI:同步、全双工、四根线、最快
- 四根线:SCLK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)
- 同步,有时钟线
- 全双工:MOSI/MISO 同时收发
- CS 片选寻址:每个从机一根 CS,谁被拉低谁应答
- 典型速率:可达 100M+,三大协议中最快
- 典型应用:W25Q128 字库 Flash、ILI9341 LCD、SD 卡、高速 ADC
3.4 对比一览表
| UART | 2(TX/RX) | 异步 | 全双工 | 无(点对点) | 110~921600 baud | 调试、GPS、蓝牙、WiFi AT |
| I2C | 2(SCL/SDA) | 同步 | 半双工 | 7/10-bit 地址 | 100k~3.4M | 传感器、EEPROM、OLED |
| SPI | 4(SCLK/MOSI/MISO/CS) | 同步 | 全双工 | CS 片选 | 可达 100M+ | Flash、LCD、SD 卡、ADC |
3.5 三大协议一图对比
#mermaid-svg-g4e26FzIYUA8otuU{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-g4e26FzIYUA8otuU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-g4e26FzIYUA8otuU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-g4e26FzIYUA8otuU .error-icon{fill:#552222;}#mermaid-svg-g4e26FzIYUA8otuU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-g4e26FzIYUA8otuU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-g4e26FzIYUA8otuU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-g4e26FzIYUA8otuU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-g4e26FzIYUA8otuU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-g4e26FzIYUA8otuU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-g4e26FzIYUA8otuU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-g4e26FzIYUA8otuU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-g4e26FzIYUA8otuU .marker.cross{stroke:#333333;}#mermaid-svg-g4e26FzIYUA8otuU svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-g4e26FzIYUA8otuU p{margin:0;}#mermaid-svg-g4e26FzIYUA8otuU .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-g4e26FzIYUA8otuU .cluster-label text{fill:#333;}#mermaid-svg-g4e26FzIYUA8otuU .cluster-label span{color:#333;}#mermaid-svg-g4e26FzIYUA8otuU .cluster-label span p{background-color:transparent;}#mermaid-svg-g4e26FzIYUA8otuU .label text,#mermaid-svg-g4e26FzIYUA8otuU span{fill:#333;color:#333;}#mermaid-svg-g4e26FzIYUA8otuU .node rect,#mermaid-svg-g4e26FzIYUA8otuU .node circle,#mermaid-svg-g4e26FzIYUA8otuU .node ellipse,#mermaid-svg-g4e26FzIYUA8otuU .node polygon,#mermaid-svg-g4e26FzIYUA8otuU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-g4e26FzIYUA8otuU .rough-node .label text,#mermaid-svg-g4e26FzIYUA8otuU .node .label text,#mermaid-svg-g4e26FzIYUA8otuU .image-shape .label,#mermaid-svg-g4e26FzIYUA8otuU .icon-shape .label{text-anchor:middle;}#mermaid-svg-g4e26FzIYUA8otuU .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-g4e26FzIYUA8otuU .rough-node .label,#mermaid-svg-g4e26FzIYUA8otuU .node .label,#mermaid-svg-g4e26FzIYUA8otuU .image-shape .label,#mermaid-svg-g4e26FzIYUA8otuU .icon-shape .label{text-align:center;}#mermaid-svg-g4e26FzIYUA8otuU .node.clickable{cursor:pointer;}#mermaid-svg-g4e26FzIYUA8otuU .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-g4e26FzIYUA8otuU .arrowheadPath{fill:#333333;}#mermaid-svg-g4e26FzIYUA8otuU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-g4e26FzIYUA8otuU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-g4e26FzIYUA8otuU .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-g4e26FzIYUA8otuU .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-g4e26FzIYUA8otuU .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-g4e26FzIYUA8otuU .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-g4e26FzIYUA8otuU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-g4e26FzIYUA8otuU .cluster text{fill:#333;}#mermaid-svg-g4e26FzIYUA8otuU .cluster span{color:#333;}#mermaid-svg-g4e26FzIYUA8otuU div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-g4e26FzIYUA8otuU .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-g4e26FzIYUA8otuU rect.text{fill:none;stroke-width:0;}#mermaid-svg-g4e26FzIYUA8otuU .icon-shape,#mermaid-svg-g4e26FzIYUA8otuU .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-g4e26FzIYUA8otuU .icon-shape p,#mermaid-svg-g4e26FzIYUA8otuU .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-g4e26FzIYUA8otuU .icon-shape .label rect,#mermaid-svg-g4e26FzIYUA8otuU .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-g4e26FzIYUA8otuU .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-g4e26FzIYUA8otuU .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-g4e26FzIYUA8otuU :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
SPI
SCLK/MOSI/MISO/CS 四根线
同步 · 全双工 · 最快
I2C
SCL/SDA 两根线
同步 · 多设备
UART
TX/RX 两根线
异步 · 点对点
四、硬件外设 vs 软件模拟:波形由谁产生?
这是本文的差异化重点。同样一份"电平+时间"的说明书,可以由两种"执行者"来跑。
4.1 硬件外设:专用电路自动发波形
以 STM32 的 I2C1 为例。你调用:
HAL_I2C_Mem_Read(&hi2c1, 0x76 << 1, 0xF7, 1, data, 3, 100);
这一条命令背后,START、从机地址、寄存器地址、重启、读数据、ACK、STOP——全部由 I2C 外设的硬件状态机自动在 PB6/PB7 上发出。CPU 只负责发起命令,然后等完成标志,期间可以去干别的(配合中断/DMA 更是如此)。
4.2 软件模拟(Bit-Banging):CPU 手工翻 GPIO
而同事的 MyI2C_Start() 呢?它是用 GPIO_WriteBit + delay_us(10) 手工拼波形:
void MyI2C_Start(void)
{
SDA_Write(1);
SCL_Write(1);
delay_us(10);
SDA_Write(0);
delay_us(10);
SCL_Write(0);
}
引脚随便选(PB10/PB11 也行),灵活性极高。但代价是:CPU 全程被占用,而且最怕中断打断——一旦 delay_us 中间被中断插一脚,SCL 低电平被拉长,从机就认为超时了。
4.3 为什么 I2C/SPI 适合模拟,UART 不适合
关键就在第二章说的同步/异步之分:
- I2C/SPI 是同步协议,有时钟线掌控节奏。SCL 拉高多久、拉低多久,只要从机能采到沿,延时长短不致命。CPU 慢一点、快一点,从机都跟着时钟走。
- UART 是异步协议,没有时钟线。接收方靠波特率硬对齐,每一位的时间精度要求极高。CPU 手工翻 GPIO 模拟 UART,稍有抖动就乱码,几乎不可用。
所以你会看到:软件模拟 I2C、软件模拟 SPI 在工程里很常见,但几乎没人软件模拟 UART。
4.4 工程选型:能用硬件就别模拟
什么情况才考虑软件模拟?
- 引脚被占用,硬件外设的固定引脚不够用;
- 芯片上没有对应的硬件外设;
- 需要灵活换脚、快速验证。
除此之外,一律用硬件外设。硬件外设不占 CPU、时序稳定、有中断/DMA 加持,是工程首选。
⚠️ 特别提醒:网上很多软件模拟 I2C 的代码是 SPL 标准库写的(GPIO_WriteBit + delay_us),用的是 PB10/PB11。在你的 HAL 工程里不能直接拷——库不兼容、引脚冲突、SysTick 打架,三座大山等着你。
4.5 硬件外设 vs 软件模拟
#mermaid-svg-cxygQ62h3Ty5N87x{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cxygQ62h3Ty5N87x .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cxygQ62h3Ty5N87x .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cxygQ62h3Ty5N87x .error-icon{fill:#552222;}#mermaid-svg-cxygQ62h3Ty5N87x .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cxygQ62h3Ty5N87x .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cxygQ62h3Ty5N87x .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cxygQ62h3Ty5N87x .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cxygQ62h3Ty5N87x .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cxygQ62h3Ty5N87x .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cxygQ62h3Ty5N87x .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cxygQ62h3Ty5N87x .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cxygQ62h3Ty5N87x .marker.cross{stroke:#333333;}#mermaid-svg-cxygQ62h3Ty5N87x svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cxygQ62h3Ty5N87x p{margin:0;}#mermaid-svg-cxygQ62h3Ty5N87x .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-cxygQ62h3Ty5N87x .cluster-label text{fill:#333;}#mermaid-svg-cxygQ62h3Ty5N87x .cluster-label span{color:#333;}#mermaid-svg-cxygQ62h3Ty5N87x .cluster-label span p{background-color:transparent;}#mermaid-svg-cxygQ62h3Ty5N87x .label text,#mermaid-svg-cxygQ62h3Ty5N87x span{fill:#333;color:#333;}#mermaid-svg-cxygQ62h3Ty5N87x .node rect,#mermaid-svg-cxygQ62h3Ty5N87x .node circle,#mermaid-svg-cxygQ62h3Ty5N87x .node ellipse,#mermaid-svg-cxygQ62h3Ty5N87x .node polygon,#mermaid-svg-cxygQ62h3Ty5N87x .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cxygQ62h3Ty5N87x .rough-node .label text,#mermaid-svg-cxygQ62h3Ty5N87x .node .label text,#mermaid-svg-cxygQ62h3Ty5N87x .image-shape .label,#mermaid-svg-cxygQ62h3Ty5N87x .icon-shape .label{text-anchor:middle;}#mermaid-svg-cxygQ62h3Ty5N87x .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cxygQ62h3Ty5N87x .rough-node .label,#mermaid-svg-cxygQ62h3Ty5N87x .node .label,#mermaid-svg-cxygQ62h3Ty5N87x .image-shape .label,#mermaid-svg-cxygQ62h3Ty5N87x .icon-shape .label{text-align:center;}#mermaid-svg-cxygQ62h3Ty5N87x .node.clickable{cursor:pointer;}#mermaid-svg-cxygQ62h3Ty5N87x .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cxygQ62h3Ty5N87x .arrowheadPath{fill:#333333;}#mermaid-svg-cxygQ62h3Ty5N87x .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cxygQ62h3Ty5N87x .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cxygQ62h3Ty5N87x .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cxygQ62h3Ty5N87x .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cxygQ62h3Ty5N87x .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cxygQ62h3Ty5N87x .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cxygQ62h3Ty5N87x .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cxygQ62h3Ty5N87x .cluster text{fill:#333;}#mermaid-svg-cxygQ62h3Ty5N87x .cluster span{color:#333;}#mermaid-svg-cxygQ62h3Ty5N87x div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cxygQ62h3Ty5N87x .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cxygQ62h3Ty5N87x rect.text{fill:none;stroke-width:0;}#mermaid-svg-cxygQ62h3Ty5N87x .icon-shape,#mermaid-svg-cxygQ62h3Ty5N87x .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cxygQ62h3Ty5N87x .icon-shape p,#mermaid-svg-cxygQ62h3Ty5N87x .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cxygQ62h3Ty5N87x .icon-shape .label rect,#mermaid-svg-cxygQ62h3Ty5N87x .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cxygQ62h3Ty5N87x .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cxygQ62h3Ty5N87x .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cxygQ62h3Ty5N87x :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
同一份协议说明书
硬件外设
软件模拟
专用电路自动发波形
CPU 手工翻 GPIO
不占 CPU · 时序稳定
占 CPU · 怕中断
五、驱动的本质:把"我要数据"翻译成"线上怎么动"
5.1 驱动 = 分层翻译器
驱动是什么?一句话:驱动 = 把"我要数据"翻译成"线上怎么动"的分层翻译器。
上层应用说"我要室内外压差",底层硬件只听得懂"PB6 拉低、PB7 拉高"。中间这一层层翻译,就是驱动。
5.2 以 BMP280 双传感器为例的四层驱动
我做过一个"双 BMP280 测压差"的项目,正好是四层驱动的典型:
应用层(negpress.c) "我要室内外压差"
↓
芯片驱动层(BMP280.c) 分别读 0x76/0x77,先温度后气压刷新 t_fine
↓
总线驱动层(bsp_iic.c) HAL_I2C_Mem_Read 封装
↓
硬件层(I2C1 外设) PB6/PB7 上真实波形
- 应用层:只关心业务,比如"算压差、判断负压是否达标"。
- 芯片驱动层:知道 BMP280 的寄存器地址、校准系数、读时序。它调用总线层,不关心底层是硬件 I2C 还是软件模拟。
- 总线驱动层:把 HAL_I2C_Mem_Read 这类操作封装成 I2C_ReadReg(addr, reg, buf, len)。
- 硬件层:真正的电平翻转。
5.3 好的驱动让上层"忘了"底层是硬件还是软件
分层的威力在于:换底层时,上层代码零改动。
今天我用硬件 I2C1(PB6/PB7),明天我改成软件模拟(PB10/PB11),只要总线驱动层对外接口不变,BMP280.c 和 negpress.c 一行都不用动。这就是"好的驱动让上层忘了底层"。
📌 特别点出 BMP280 的经典坑:读气压前必须先读温度刷新 t_fine(温度补偿系数)。双传感器时,t_fine 必须存在各自的结构体里,否则室内温度会去补偿室外气压——差分结果全错。这个坑我踩过,血泪。
–
5.4 四层驱动一图看懂
#mermaid-svg-xfTFZojX5KpG9Z2o{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-xfTFZojX5KpG9Z2o .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xfTFZojX5KpG9Z2o .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xfTFZojX5KpG9Z2o .error-icon{fill:#552222;}#mermaid-svg-xfTFZojX5KpG9Z2o .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xfTFZojX5KpG9Z2o .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xfTFZojX5KpG9Z2o .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xfTFZojX5KpG9Z2o .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xfTFZojX5KpG9Z2o .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xfTFZojX5KpG9Z2o .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xfTFZojX5KpG9Z2o .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xfTFZojX5KpG9Z2o .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xfTFZojX5KpG9Z2o .marker.cross{stroke:#333333;}#mermaid-svg-xfTFZojX5KpG9Z2o svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xfTFZojX5KpG9Z2o p{margin:0;}#mermaid-svg-xfTFZojX5KpG9Z2o .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-xfTFZojX5KpG9Z2o .cluster-label text{fill:#333;}#mermaid-svg-xfTFZojX5KpG9Z2o .cluster-label span{color:#333;}#mermaid-svg-xfTFZojX5KpG9Z2o .cluster-label span p{background-color:transparent;}#mermaid-svg-xfTFZojX5KpG9Z2o .label text,#mermaid-svg-xfTFZojX5KpG9Z2o span{fill:#333;color:#333;}#mermaid-svg-xfTFZojX5KpG9Z2o .node rect,#mermaid-svg-xfTFZojX5KpG9Z2o .node circle,#mermaid-svg-xfTFZojX5KpG9Z2o .node ellipse,#mermaid-svg-xfTFZojX5KpG9Z2o .node polygon,#mermaid-svg-xfTFZojX5KpG9Z2o .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xfTFZojX5KpG9Z2o .rough-node .label text,#mermaid-svg-xfTFZojX5KpG9Z2o .node .label text,#mermaid-svg-xfTFZojX5KpG9Z2o .image-shape .label,#mermaid-svg-xfTFZojX5KpG9Z2o .icon-shape .label{text-anchor:middle;}#mermaid-svg-xfTFZojX5KpG9Z2o .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xfTFZojX5KpG9Z2o .rough-node .label,#mermaid-svg-xfTFZojX5KpG9Z2o .node .label,#mermaid-svg-xfTFZojX5KpG9Z2o .image-shape .label,#mermaid-svg-xfTFZojX5KpG9Z2o .icon-shape .label{text-align:center;}#mermaid-svg-xfTFZojX5KpG9Z2o .node.clickable{cursor:pointer;}#mermaid-svg-xfTFZojX5KpG9Z2o .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xfTFZojX5KpG9Z2o .arrowheadPath{fill:#333333;}#mermaid-svg-xfTFZojX5KpG9Z2o .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xfTFZojX5KpG9Z2o .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xfTFZojX5KpG9Z2o .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xfTFZojX5KpG9Z2o .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xfTFZojX5KpG9Z2o .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xfTFZojX5KpG9Z2o .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xfTFZojX5KpG9Z2o .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xfTFZojX5KpG9Z2o .cluster text{fill:#333;}#mermaid-svg-xfTFZojX5KpG9Z2o .cluster span{color:#333;}#mermaid-svg-xfTFZojX5KpG9Z2o div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-xfTFZojX5KpG9Z2o .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xfTFZojX5KpG9Z2o rect.text{fill:none;stroke-width:0;}#mermaid-svg-xfTFZojX5KpG9Z2o .icon-shape,#mermaid-svg-xfTFZojX5KpG9Z2o .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xfTFZojX5KpG9Z2o .icon-shape p,#mermaid-svg-xfTFZojX5KpG9Z2o .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xfTFZojX5KpG9Z2o .icon-shape .label rect,#mermaid-svg-xfTFZojX5KpG9Z2o .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xfTFZojX5KpG9Z2o .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xfTFZojX5KpG9Z2o .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xfTFZojX5KpG9Z2o :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
应用层 negpress.c
芯片驱动层 BMP280.c
总线驱动层 bsp_iic.c
硬件层 I2C1 外设
六、CubeMX 工程配置实战(STM32 为例)
6.1 UART 配置(以 USART3 接 ESP8266 为例)
- Mode:Asynchronous
- 波特率:115200
- 数据位:8,校验:None,停止位:1(即 8N1)
- 引脚:TX + RX
- NVIC:开启全局中断(接收"续杯"必须)
- 高速数据流可选 DMA
关键点:
- TX/RX 必须交叉接:STM32 的 TX 接 ESP8266 的 RX,反之亦然。
- 3.3V 与 5V 直连会烧 IO,注意电平匹配。
- 波特率误差要 < 2%,否则长帧必乱码。
6.2 I2C 配置(以 I2C1 接双 BMP280 为例)
- Mode:I2C
- Speed Mode:Standard Mode,100kHz
- 寻址:7-bit
- PB6/PB7 自动分配为 SCL/SDA,开漏输出
- 硬件电路必须外接 4.7kΩ~10kΩ 上拉到 VCC(ST 官方配置界面也提示 enable pull-ups)
关键点:
- 读不到器件,先量 SCL/SDA 空闲是不是高电平。
- 双设备地址 0x76/0x77 靠 SDO 引脚电平区分:SDO 接 GND → 0x76,SDO 接 VCC → 0x77。
6.3 SPI 配置(以 SPI 接 W25Q128 为例)
- CPOL/CPHA 组合成 4 种 Mode,必须与从设备 datasheet 严格匹配(Flash 多为 Mode 0)
- CS 引脚每从机一个,未选中必须高阻
- 关键点:Mode 错 → 设备完全不响应;CS 时序错 → 数据错乱
📌 三大协议 CubeMX 配置清单速查表:
| UART | Asynchronous | 115200 8N1 | TX/RX 交叉、共地 |
| I2C | I2C Standard | 100kHz 7-bit | 上拉电阻、空闲高电平 |
| SPI | SPI Mode 0 | CPOL=0 CPHA=0 | CS 拉低、Mode 匹配 |
七、应用场景与选型准则
- 传感器多、IO 紧 → I2C:一条总线挂 BMP280、SHT30、OLED,只占两根线。
- 屏幕、Flash、高速数据 → SPI:ILI9341 LCD、W25Q128、SD 卡,要的就是速度。
- 调试打印、模块通信、简单透传 → UART:ESP8266、语音模块、GPS,点对点最省心。
选型没有绝对的对错,核心是匹配需求:要省 IO 选 I2C,要速度选 SPI,要简单选 UART。
选型一图流
#mermaid-svg-VcPzusbZWuy4pqtN{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-VcPzusbZWuy4pqtN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VcPzusbZWuy4pqtN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VcPzusbZWuy4pqtN .error-icon{fill:#552222;}#mermaid-svg-VcPzusbZWuy4pqtN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VcPzusbZWuy4pqtN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VcPzusbZWuy4pqtN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VcPzusbZWuy4pqtN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VcPzusbZWuy4pqtN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VcPzusbZWuy4pqtN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VcPzusbZWuy4pqtN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VcPzusbZWuy4pqtN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VcPzusbZWuy4pqtN .marker.cross{stroke:#333333;}#mermaid-svg-VcPzusbZWuy4pqtN svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VcPzusbZWuy4pqtN p{margin:0;}#mermaid-svg-VcPzusbZWuy4pqtN .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-VcPzusbZWuy4pqtN .cluster-label text{fill:#333;}#mermaid-svg-VcPzusbZWuy4pqtN .cluster-label span{color:#333;}#mermaid-svg-VcPzusbZWuy4pqtN .cluster-label span p{background-color:transparent;}#mermaid-svg-VcPzusbZWuy4pqtN .label text,#mermaid-svg-VcPzusbZWuy4pqtN span{fill:#333;color:#333;}#mermaid-svg-VcPzusbZWuy4pqtN .node rect,#mermaid-svg-VcPzusbZWuy4pqtN .node circle,#mermaid-svg-VcPzusbZWuy4pqtN .node ellipse,#mermaid-svg-VcPzusbZWuy4pqtN .node polygon,#mermaid-svg-VcPzusbZWuy4pqtN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VcPzusbZWuy4pqtN .rough-node .label text,#mermaid-svg-VcPzusbZWuy4pqtN .node .label text,#mermaid-svg-VcPzusbZWuy4pqtN .image-shape .label,#mermaid-svg-VcPzusbZWuy4pqtN .icon-shape .label{text-anchor:middle;}#mermaid-svg-VcPzusbZWuy4pqtN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-VcPzusbZWuy4pqtN .rough-node .label,#mermaid-svg-VcPzusbZWuy4pqtN .node .label,#mermaid-svg-VcPzusbZWuy4pqtN .image-shape .label,#mermaid-svg-VcPzusbZWuy4pqtN .icon-shape .label{text-align:center;}#mermaid-svg-VcPzusbZWuy4pqtN .node.clickable{cursor:pointer;}#mermaid-svg-VcPzusbZWuy4pqtN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-VcPzusbZWuy4pqtN .arrowheadPath{fill:#333333;}#mermaid-svg-VcPzusbZWuy4pqtN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VcPzusbZWuy4pqtN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VcPzusbZWuy4pqtN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VcPzusbZWuy4pqtN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-VcPzusbZWuy4pqtN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VcPzusbZWuy4pqtN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-VcPzusbZWuy4pqtN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VcPzusbZWuy4pqtN .cluster text{fill:#333;}#mermaid-svg-VcPzusbZWuy4pqtN .cluster span{color:#333;}#mermaid-svg-VcPzusbZWuy4pqtN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VcPzusbZWuy4pqtN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-VcPzusbZWuy4pqtN rect.text{fill:none;stroke-width:0;}#mermaid-svg-VcPzusbZWuy4pqtN .icon-shape,#mermaid-svg-VcPzusbZWuy4pqtN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VcPzusbZWuy4pqtN .icon-shape p,#mermaid-svg-VcPzusbZWuy4pqtN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-VcPzusbZWuy4pqtN .icon-shape .label rect,#mermaid-svg-VcPzusbZWuy4pqtN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VcPzusbZWuy4pqtN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-VcPzusbZWuy4pqtN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-VcPzusbZWuy4pqtN :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
要省 IO
I2C
要速度
SPI
要简单
UART
八、高频排坑指南(血泪经验)
平时总听说"终于把某某通信调好了"——这句话背后,是无数个深夜对着示波器发呆的工程师。你可能会问:协议不是一套明明白白、白纸黑字写好的方案吗?为什么这么"确定"的东西,还是这么容易出错?
答案恰恰藏在前面几章里:协议是确定的,但"执行协议"的每一环都不确定。
- 电气层不确定:开漏没上拉、电平不匹配、共地没做好、线太长有干扰——协议写得再清楚,线上电平不对,一切白搭。
- 时序层不确定:波特率误差、时钟抖动、中断打断延时、上电时序不对——协议规定了"何时变",但"变"的时机被各种因素扰动。
- 配置层不确定:CPOL/CPHA 配错、地址引脚接错、引脚复用冲突——协议没错,是"人"把参数配错了。
- 理解层不确定:datasheet 没读透、寄存器地址记错、编码表不一致——协议没错,是"人"理解错了。
所以"调通信"调的不是协议本身,而是把协议之外的所有不确定因素,一个个排查、对齐、消除。协议是那条"应该走的路",但路上有坑、有岔路、有信号灯失灵,工程师的工作就是把这些坑一个个填平。下面这些血泪经验,就是最常见的坑。
–
八、高频排坑指南(血泪经验)
8.1 I2C 读不到器件
- 现象:HAL_I2C_Mem_Read 返回 HAL_BUSY 或 HAL_TIMEOUT。
- 原因:开漏输出 + 无上拉 = 总线永远拉不高。
- 解决:先别改代码!用示波器/万用表量 SCL/SDA 空闲是不是高电平。不是高电平,先补 4.7kΩ~10kΩ 上拉。
8.2 I2C 地址冲突
- 现象:明明地址对,就是读不到。
- 原因:A0/A1/A2 哪怕接了一个高,地址就从 0xA0 变 0xA2。
- 解决:核对硬件地址引脚,确认实际地址。
8.3 UART 乱码
- 现象:收到一堆乱码。
- 原因:波特率不匹配、TX/RX 未交叉、未共地、3.3V 直连 5V。
- 解决:逐一排查——先量波特率,再查交叉,再查共地。
8.4 SPI 不响应
- 现象:设备完全不响应,或数据错乱。
- 原因:CPOL/CPHA Mode 配错、CS 没拉低、片选引脚搞混。
- 解决:对照 datasheet 核对 Mode,确认 CS 时序。
8.5 软件模拟 I2C 偶发失败
- 现象:时好时坏,偶发失败。
- 原因:中断打断了 delay_us 时序,SCL 低电平被拉长,从机认为超时。
- 解决:模拟期间关中断,或改用硬件 I2C。
8.6 EEPROM 写后立刻读
- 现象:写完立刻读,读出来是旧数据。
- 原因:AT24C02 内部写周期需 5ms 以上,跨页写入会回卷。
- 解决:写后延时 5ms+,注意页边界。
8.7 CubeMX 生成代码后引脚复用冲突
- 现象:下载一次后芯片锁死,再也连不上。
- 原因:SYS 的 SWD 调试口忘选,被复用成普通 IO。
- 解决:CubeMX 里 SYS → Debug
感谢您的阅读!本号长期深耕电子信息、通信与嵌入式领域的技术分享,如果这篇文章对您有所启发,欢迎点赞、收藏并关注,后续将持续输出更多硬核干货,与您一同成长!
网硕互联帮助中心




评论前必须登录!
注册