ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SPI、I2C、UART选型与时序调试:STM32F103实战避坑

SPI、I2C、UART选型与时序调试:STM32F103实战避坑 1. 三种接口的定位之争选型错了后面全是坑搞硬件这行SPI、UART、I²C 这三个词几乎是从入行第一天就刻进脑子里的。可真到项目上很多人还是会卡在同一个问题上手上这颗传感器、这颗存储芯片、这块屏到底挂哪条总线选 UART 吧速率不够选 I²C 吧怕它卡总线选 SPI 吧又嫌它占引脚。最后往往是上次那个项目用啥我就用啥能跑通就收工跑不通就开始怀疑人生。我这几年做的板子从 STM32F103 这种经典款到带屏、带 Flash、带一堆传感器的复合板都有SPI、UART、I²C 三种接口的坑基本都踩过一遍。这篇文章想干的事很明确把三种接口的选型逻辑讲透把时序层面真正影响调试的那几个点挖出来再配上一套可以直接照着复现的实操流程——CubeMX 配 SPI DMA 读芯片数据、UART 调试链路打通并落盘日志、I²C 总线死锁恢复。不管你是刚上手画第一块板的学生还是天天跟示波器打交道的工程师看完至少能少熬两个通宵。先说结论性的判断方便你带着框架往下读SPI 是快、独占、线多I²C 是慢、共享、线少UART 是点对点、异步、最省事。这三个定位没有优劣之分只有场景匹配度。真正让人吃亏的从来不是不知道用哪个而是用错了还硬调把时间全耗在了本来就不该走的路上。1.1 从物理层看三根线、两根线、一根线的代价SPI 的物理层是四根线打底SCLK、MOSI、MISO、CS。全双工主机给时钟数据同时在两个方向上跑。每多挂一个从设备就多一根 CS。挂八个从设备光片选就八根线这在小封装 MCU 上是真奢侈。但它的代价换来了收益——没有地址概念、没有仲裁、没有应答时钟给多快数据就跑多快协议开销几乎为零。UART 是两根线TX 和 RX。异步没有时钟线双方靠事先约定好的波特率对时。这个约定是它最大的优点也是最大的软肋优点是线少、跨设备简单一根 USB 转串口就能跟电脑说话软肋是双方时钟必须足够接近偏一点就采样错位整帧数据崩掉。UART 天生只能点对点想挂多个设备就得靠多路串口或者外扩芯片。I²C 是两根线SCL 和 SDA而且都是开漏输出靠上拉电阻把电平拉高。所有设备并联在同一对线上靠 7 位地址区分彼此。这两根线理论上能挂一百多个设备实际受总线电容限制一般也就挂几个到十几个。它的少线是用慢和复杂换来的——开漏意味着上升沿是 RC 充电速率上不去共享总线意味着要处理地址冲突和仲裁。注意画原理图时最常见的低级错误是把 I²C 的 SDA/SCL 当普通推挽 IO 用。开漏输出必须配上拉电阻没有上拉总线永远是低电平芯片连 ACK 都发不出来。1.2 速度、距离、拓扑与功耗的四维权衡选型的时候我一般按四个维度过一遍比单纯看速率靠谱得多。速率上SPI 在 STM32F103 上 SPI1 挂 APB272MHz最高能到 18MHzSPI2 挂 APB136MHz最高 9MHz。I²C 标准模式 100kHz快速模式 400kHzF1 基本就到这。UART 常见 115200高一点到 921600 甚至 2M 也能跑但线路一长就掉链子。所以需要搬大量数据的场景比如读 Flash、刷屏、接高速 ADCSPI 几乎是唯一答案。距离上三个都是板内总线超过几十厘米都要慎重。UART 相对皮实一点配个 RS-485 收发器能拉上千米那是另一套电气标准了。I²C 对总线电容最敏感官方建议总电容不超过 400pF一条长排线走过去很容易就超了。SPI 高速时对走线阻抗和匹配更敏感波形一塌糊涂的时候先看示波器。拓扑上SPI 是星型每个从机一根 CSI²C 是总线型全并联UART 是点对点。这个差异在 PCB 布局阶段就决定了你能不能省引脚。功耗上I²C 因为开漏加外部上拉静态时总有电流从电阻流向被拉低的线功耗比想象中大SPI 的 CS 拉高后从机进休眠整体更干净。低功耗产品里这点经常被忽略实测能差出几百微安。1.3 一张选型对照表直接抄维度SPII²CUART信号线4 根起SCLK/MOSI/MISO/CS2 根SCL/SDA2 根TX/RX拓扑星型每从机一根 CS总线型并联共享点对点典型速率1M~18M100k / 400k9.6k~921.6k双工全双工半双工全双工寻址无靠 CS 片选7 位地址无应答机制无有 ACK/NACK无可选校验引脚开销高低低典型器件Flash、LCD、ADC、无线模块传感器、EEPROM、IO 扩展GPS、蓝牙、模组、调试口这张表我建议直接贴在工位上。选型时对着过一遍多数纠结五分钟内就能定。2. 把时序图看懂调试就赢了一半我见过太多人调不通接口就换芯片、换板子、怀疑买到假货最后发现是 CPOL/CPHA 配错了或者波特率算错了 0.5%。接口调试的 90% 问题根子都在时序上。这一节把三种接口时序里最容易出错的地方逐个拆开。2.1 SPI的四种模式CPOL和CPHA到底怎么配SPI 的四种模式来自两个参数CPOL时钟极性和 CPHA时钟相位。CPOL 决定 SCK 空闲时是高还是低CPOL0 空闲低电平CPOL1 空闲高电平。CPHA 决定数据在哪个时钟边沿被采样CPHA0 在第一个边沿采样CPHA1 在第二个边沿采样。组合起来就是四种模式Mode 0CPOL0CPHA0。SCK 空闲低上升沿采样下降沿输出。这是最常见的一种绝大多数 SPI Flash 和传感器默认用它。Mode 1CPOL0CPHA1。SCK 空闲低下降沿采样上升沿输出。Mode 2CPOL1CPHA0。SCK 空闲高下降沿采样。Mode 3CPOL1CPHA1。SCK 空闲高上升沿采样。用的人也很多尤其是一些 ADC 和无线芯片。怎么从手册里读出该用哪个手册一般有两种写法。一种是直接写支持 SPI Mode 0/3那就照抄。另一种是描述边沿行为比如数据在 SCK 上升沿被采样在下降沿被更新。这时候要翻译采样在上升沿且 SCK 空闲状态决定了这是第几个边沿。如果芯片没说空闲电平通常默认空闲低那就是 CPOL0、CPHA0即 Mode 0。提示CPOL 配错波形看起来会整体反相数据全错CPHA 配错波形形状对但采样点偏了一个边沿表现为数据稳定地错位一位。示波器抓 SCK 和数据线量一下采样点落在数据眼图正中还是边缘一眼就能定位是哪个参数的问题。还有一个容易被忽略的点First Bit 顺序。多数器件是 MSB First但确实有 LSB First 的。这个配错的结果是数据整体比特反转比如 0x01 读成 0x80非常好认。2.2 UART波特率误差怎么算为什么差3%就乱码UART 没有时钟线接收端靠检测起始位的下降沿来对齐然后在位周期中间采样。理想情况下接收端在第 8、9 位一帧第 0 位是起始位附近采样留出的容错余量大概是半个位周期。STM32 的波特率计算公式是USARTDIV fCK / (16 × BaudRate) BRR round(USARTDIV) 实际波特率 fCK / (16 × USARTDIV整数化后的值)以 STM32F103 为例USART1 挂 APB2fCK72MHz。要 115200USARTDIV 72000000 / (16 × 115200) 39.0625 BRR 的整数部分 39小数部分 0.0625 × 16 1 实际波特率 72000000 / (16 × (39 1/16)) 115200误差为 0。这就是为什么 72MHz 主频配 115200 特别稳。反过来如果你用的是 8MHz 晶振跑 115200USARTDIV 8000000 / (16 × 115200) 4.3403 取整数部分 4小数部分 0.3403×16 5.44 → 取 5 实际 8000000 / (16 × (4 5/16)) 111111 误差 (111111 - 115200)/115200 ≈ -3.5%这个误差已经超标了接收端采样点会逐渐偏移到位的边缘跑几帧就开始出错。解决办法是换合适的晶振频率或者降波特率到 9600误差会小很多。经验阈值误差在 ±2% 以内安全2%~3% 勉强超过 3% 基本必乱码。而且这个误差是收发双方累积的如果对端也不准两边一叠加就更玄。所以调试串口出问题时第一件事不是换线是算一遍波特率误差。2.3 I²C的开漏输出、上拉电阻与时钟拉伸I²C 的三条铁律开漏输出、上拉电阻、ACK 应答。开漏输出意味着任何设备都只能把线拉低不能主动拉高。高电平完全靠上拉电阻充上去所以上升沿是个 RC 充电过程波形是圆弧而不是陡峭的方波。这也是 I²C 速率上不去的根本原因。时钟拉伸Clock Stretching是 I²C 独有的机制从机如果处理不过来可以把 SCL 拉低不放强制主机等着。很多 MCU 的硬件 I²C 外设对这个机制支持不好表现为通信卡死。STM32F1 的硬件 I²C 在遇到某些从机尤其是反复拉伸时钟的传感器时会出现总线挂死这是老生常谈的问题。还有一点I²C 的 ACK 是接收方在第九个时钟周期把 SDA 拉低。很多新手用示波器抓波形看到第九个脉冲后 SDA 没被拉低就以为是数据错了其实是从机没应答根因可能是地址写错、器件没上电、或者总线死锁。3. STM32F103实操CubeMX配置SPIDMA读取芯片数据理论讲完了接下来是最实用的部分。我用 STM32F103 CubeMX 演示一遍 SPI 配 DMA 读芯片数据的完整流程这套方法适用于读 Flash、读传感器、读 ADC换一下指令字节就能复用。3.1 CubeMX里这8个参数决定成败打开 CubeMX选好 STM32F103C8 或者你手上的型号配好时钟树外部晶振 8MHzPLL 倍频到 72MHz。然后进 SPI1 配置参数设置值说明ModeFull-Duplex Master全双工主机模式Hardware NSS SignalDisable用软件控制 CS后面细说Data Size8 Bits绝大多数器件是 8 位First BitMSB First除非手册特别说明Prescaler872MHz/8 9MHz先用低速调通再提速Clock Polarity (CPOL)Low对应 Mode 0Clock Phase (CPHA)1 Edge对应 Mode 0CRC CalculationDisabled一般用不上开了反而增加开销Prescaler 这一项调试阶段我强烈建议先降速。9MHz 甚至 4.5MHz 先跑通确认数据对了再一步步往上提到 18MHz。如果一上来就 18MHz 跑不通你根本分不清是协议问题还是信号完整性问题。接着配 DMA。在 DMA Settings 里点 Add选 SPI1_RXMode 选 Normal单次或 Circular循环适合持续采集Data Width 都选 BytePriority 选 High。然后在 NVIC Settings 里把对应的 DMA 通道中断打开。最后配一个 GPIO 作为 CS比如 PA4设为 Output Push-Pull初始电平 High片选空闲时是高电平。3.2 DMA接收代码落地与数据错位的根因CubeMX 生成的初始化代码不用动我们只需要加几行自己的逻辑。先定义缓冲区和标志位#define SPI_RX_SIZE 64 uint8_t spi_rx_buf[SPI_RX_SIZE]; volatile uint8_t spi_rx_done 0; #define CS_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET) #define CS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET)启动 DMA 接收void SPI_Start_DMA_Read(void) { spi_rx_done 0; CS_LOW(); HAL_SPI_Receive_DMA(hspi1, spi_rx_buf, SPI_RX_SIZE); }接收完成回调void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi-Instance SPI1) { CS_HIGH(); /* 必须在数据收完之后再抬 CS */ spi_rx_done 1; } }如果你要读某个寄存器标准做法是发两个字节命令 空字节用全双工收发uint8_t Read_Reg(uint8_t reg) { uint8_t tx[2] { reg | 0x80, 0x00 }; /* 读命令最高位置1 */ uint8_t rx[2] { 0 }; CS_LOW(); HAL_SPI_TransmitReceive(hspi1, tx, rx, 2, 100); CS_HIGH(); return rx[1]; }这里面第一个大坑是CS 拉高的时机。用HAL_SPI_TransmitReceive时函数返回说明两个字节都收发完了此时抬 CS 没问题。但用 DMA 时函数是立即返回的如果你在启动 DMA 后马上抬 CS从机会认为传输结束后面收的数据全是垃圾。必须在RxCpltCallback里抬 CS这是数据错位最常见的根因。第二个坑是DMA 传输长度和实际数据长度不匹配。有些器件读命令发完之后还得等一小段时间才有数据出来这时候需要中间插入延时或者用两个独立的HAL_SPI_Transmit和HAL_SPI_Receive分开处理。第三个坑是SPI 的 RX 和 TX 是同一个时钟驱动。即使你只想读数据也得给时钟而给时钟就会同时发送数据。所以只读的场景必须给 TX 送空字节0x00 或 0xFF看器件要求不能啥都不发。3.3 硬件片选和软件片选怎么选CubeMX 里 Hardware NSS Signal 有两个选项Disable软件控制和 Hardware NSS Output硬件自动控制。软件片选是我 90% 场景下的选择。原因很简单可控。你可以精确控制 CS 拉低到第一个时钟之间的时间、最后一个时钟到 CS 拉高之间的时间而这两个时间在时序要求严格的器件比如某些 Flash 要求 tSLCH、tCHSH 有最小值里非常关键。另外多从机场景下软件片选能让你灵活切换。硬件片选适合严格的单主机单从机、速度极高、CPU 不想介入的场景。它的优点是 NSS 由硬件自动拉低拉高时序精确到时钟周期缺点是灵活性差而且 STM32F1 的硬件 NSS 在某些配置下会有奇怪的行为比如需要配置 SSM/SSI 位否则会误进从机模式。经验如果发现 SPI 明明配了主机模式时钟却不输出先看 NSS 的配置。硬件 NSS 模式下如果 NSS 引脚被拉低SPI 会认为有别的设备在选中自己于是退出主机模式SCLK 停止输出。这是个非常隐蔽的坑多数人第一次遇到会以为是 SPI 外设坏了。4. UART链路打通从驱动安装到调试日志落盘UART 是调试阶段用得最多的接口。板子一出问题第一反应就是接个串口看它有没有在跑。但这条链路看着简单坑一点不少。4.1 USB转串口芯片的驱动坑板子上的 UART 是 TTL 电平3.3V电脑上是 USB中间要有个桥接芯片。市面上常见的几款芯片特点常见问题CH340便宜国产兼容性好老系统上驱动签名问题部分版本连不上CP2102稳定原厂驱动完善假货多便宜的模块可能是翻新片CP2104支持更低功耗带 GPIO驱动和 CP2102 不通用别装错FT232R老牌稳定性好驱动要认准原厂第三方驱动常出问题装驱动的顺序很关键先装驱动再插模块。反过来的话系统会先按默认的未知设备处理之后即使装了驱动也可能要手动在设备管理器里更新。装完之后在设备管理器里看端口号正常应该显示 USB-SERIAL CH340 (COMx) 或者 Silicon Labs CP210x USB to UART Bridge (COMx)。如果设备管理器里出现黄色感叹号八成是驱动没匹配上。这时候别急着换模块先卸载设备勾选删除驱动程序软件拔掉模块重装驱动再插上。Windows 的驱动缓存很顽固不彻底删干净会一直用错的那个。4.2 串口助手参数与乱码排查顺序串口调试助手的参数必须和固件完全一致波特率、数据位通常 8、停止位通常 1、校验位通常 None、流控通常 None。这五项里有任何一项不一致收到的都是乱码。乱码排查我有个固定顺序从高概率到低概率核对波特率。90% 的乱码是波特率不对。先在助手和固件两边都确认一遍数值。算一遍波特率误差。按 2.2 节的公式算超过 3% 就是配置问题换晶振或降速。看 TX/RX 是不是接反了。板子 TX 接模块 RX板子 RX 接模块 TX交叉接。接反的表现通常是完全收不到数据而不是乱码。确认共地。USB 转串口模块和板子必须共地不共地的时候电平参考不一样收到的数据是随机的。这是新手最容易忽略的一条。看电平是否匹配。3.3V 的板子接 5V 的模块或者反过来可能收不到或者烧芯片。用万用表量一下空闲时 TX 的电平正常应该和供电电压一致。如果收到的数据是能看懂一部分中间夹杂乱码那多半是波特率接近但不完全一致或者有电源干扰。这种情况把波特率降一档通常能解决。4.3 调试信息同时打印和存盘的做法调试的时候经常遇到这种情况屏幕上刷得飞快问题一闪而过想回看却没了。所以同时打印到终端和写入日志文件是个刚需。最省事的办法是给调试串口加一层封装。在固件里定义#include stdarg.h #include stdio.h void log_printf(const char *fmt, ...) { char buf[256]; va_list ap; va_start(ap, fmt); int n vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); if (n 0) { HAL_UART_Transmit(huart1, (uint8_t *)buf, n, 100); } }所有调试输出都走log_printf这样做的好处是后面想加时间戳、加等级INFO/WARN/ERR、加模块名都只改一个函数。电脑这一侧用串口助手自带的保存日志功能也行但更灵活的是写个几行的 Python 脚本同时打印和写文件import serial import datetime port COM7 baud 115200 logname uart_%s.log % datetime.datetime.now().strftime(%Y%m%d_%H%M%S) ser serial.Serial(port, baud, timeout1) f open(logname, a, encodingutf-8, errorsreplace) print(logging to, logname) while True: data ser.read(128) if not data: continue text data.decode(utf-8, errorsreplace) print(text, end) f.write(text) f.flush()f.flush()这一句很关键不加的话 Python 会攒一批才写盘程序崩了日志就丢了。加上之后每收到一段就立刻落盘断电都不怕。提示在 Keil 里调试的时候如果你想在 Watch 窗口看到结构体变量的实时值记得把编译优化等级设成 -O0 或者 -Og。开 -O2 之后局部变量可能被优化进寄存器Watch 窗口显示 cannot read memory会让人误以为程序跑飞了。另外涉及中断里改、主循环里读的变量一定要加volatile否则优化后主循环可能永远读到缓存里的旧值。5. I²C总线疑难上拉计算、地址冲突与总线死锁I²C 是所有接口里看起来最简单、出问题最缠人的一个。两根线一接代码一调运气好跑通运气不好就是无尽的卡死。5.1 上拉电阻不是随手焊一个4.7k很多人画 I²C 电路上拉电阻直接从参考设计里抄个 4.7k。这个值在多数场景下没问题但它是算出来的不是拍脑袋定的。上拉电阻有两个约束一个是下限不能太小一个是上限不能太大。下限由灌电流决定。开漏输出的器件把线拉低时电流从上拉电阻灌进芯片。这个电流不能超过器件的 IOL 能力一般 3mA。公式Rmin (VDD - VOL_max) / IOL_max3.3V 供电VOL 取 0.4VIOL 取 3mARmin (3.3 - 0.4) / 0.003 ≈ 967Ω所以上拉电阻不能小于约 1kΩ。上限由上升时间决定。I²C 标准规定标准模式100kHz上升时间不超过 1000ns快速模式400kHz不超过 300ns。上升时间公式tr ≈ 0.847 × R × C其中 C 是总线总电容包括引脚电容、走线电容、器件电容官方建议不超过 400pF。快速模式下R ≤ 300ns / (0.847 × 400pF) ≈ 885Ω也就是说如果总线电容真的到了 400pF 上限快速模式的上拉电阻得小于 900Ω4.7k 根本不够。实际项目中总线电容通常只有几十 pF比如 100pFR ≤ 300ns / (0.847 × 100pF) ≈ 3542Ω这时候 4.7k 就超了得用 2.2k 或 3.3k。所以结论是跑 400kHz 时4.7k 只有在总线电容很小小于约 75pF的情况下才安全。板子上一堆器件挂上去很容易就超了。我自己的做法是100kHz 用 4.7k 没问题400kHz 起步用 2.2k走线长或者挂的设备多就用 1.5k同时用示波器量上升沿确保在 300ns 以内。5.2 总线被拉死之后的恢复手法I²C 最经典的故障是总线死锁某个从机在传输过程中被打断比如主机复位了它还在等下一个时钟于是死死把 SDA 拉低不放。这时候主机重新初始化 I²C发现 SDA 一直是低连起始条件都发不出去整个总线就废了。标准恢复手法是手动发 9 个时钟脉冲让从机把剩下的位发完然后补一个 STOP 条件。代码可以这么写void I2C_Bus_Recover(void) { GPIO_InitTypeDef g {0}; /* 先把 I2C 外设停掉引脚切成开漏输出 */ HAL_I2C_DeInit(hi2c1); g.Pin GPIO_PIN_6 | GPIO_PIN_7; /* SCLPB6, SDAPB7 */ g.Mode GPIO_MODE_OUTPUT_OD; g.Pull GPIO_PULLUP; g.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, g); /* 先确保 SDA 释放 */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET); /* 打 9 个时钟 */ for (int i 0; i 9; i) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET); HAL_Delay(1); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET); HAL_Delay(1); } /* 手动产生 STOP: SCL 高时 SDA 由低变高 */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET); HAL_Delay(1); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET); HAL_Delay(1); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET); HAL_Delay(1); /* 重新初始化 I2C 外设 */ HAL_I2C_Init(hi2c1); }这段代码我基本每个用硬件 I²C 的项目都会加上放在HAL_I2C_ErrorCallback里或者上电初始化时先跑一遍能省掉大量莫名其妙就不通信了的现场问题。另外找设备地址的时候别一个个猜。写个扫描循环从 0x01 扫到 0x7F哪个地址有 ACK 就是有设备void I2C_Scan(void) { for (uint8_t addr 1; addr 0x80; addr) { if (HAL_I2C_IsDeviceReady(hi2c1, addr 1, 2, 10) HAL_OK) { log_printf(I2C found: 0x%02X\r\n, addr); } } }扫描一遍两秒钟就知道总线上挂了谁、地址是多少。地址冲突两个器件地址一样也会在这里暴露出来——表现为扫描到的设备数量比实际挂的少这时候就得靠器件上的地址选择引脚A0/A1/A2来改地址或者用 I²C 多路复用器分总线。6. 问题速查表与几条用血换来的经验6.1 高频问题速查表现象最可能的原因排查动作SPI 完全没有波形主机模式没进、NSS 配置错查 NSS 配置切软件片选SPI 数据整体错位一位CPHA 配反换 CPHA 重试SPI 数据整体反相CPOL 配反换 CPOL 重试SPI 数据比特反转First Bit 顺序错切 LSB FirstSPI DMA 收数据全乱CS 抬早于传输完成回调里再抬 CSUART 全是乱码波特率不匹配或误差超 3%核对参数、算误差UART 完全收不到TX/RX 接反或没共地交叉接线、检查地线UART 偶发错帧电源干扰或线太长降速、缩短线、加滤波I²C 扫描不到设备没上拉、供电没上量 SDA/SCL 空闲电平I²C 通信一会儿就卡死总线死锁、时钟拉伸跑 9 时钟恢复程序I²C 高速下偶发 NACK上拉太大、上升沿太慢减小上拉、示波器量上升时间6.2 几个反常识的经验第一条SPI 不是越快越好。我刚开始做项目的时候觉得 18MHz 能配就配 18MHz结果接一个便宜的传感器数据时对时错。后来降到 4.5MHz 一切正常查手册发现那个器件的最高时钟只有 10MHz而且对上升沿有要求。速度提上去之前先把手册的时序参数表翻出来对照一遍别想当然。信号完整性也是同理长走线上 18MHz 的方波可能已经变成一条斜线了用示波器看一眼就知道。第二条I²C 的硬件外设不一定比软件模拟靠谱。STM32F1 的硬件 I²C 在某些从机上有已知的卡死问题错误标志位卡住、总线状态机跑飞很多人最后改用 GPIO 模拟时序反而稳定。这不是说硬件外设不好而是说当你在硬件 I²C 上折腾超过半天还没进展时及时切换到软件模拟是个理性的选择别死磕。第三条调试信息永远要留后路。板子上只留一个 UART 口固件里只留一个 printf一旦 UART 挂了就完全瞎眼。我的习惯是至少留两条调试通道一条串口打日志一条用 GPIO 打时间戳关键节点翻转电平示波器一抓就知道时序两条互补成本几乎为零。第四条换个芯片试试应该是最后的手段。接口调不通95% 是配置问题、时序问题、硬件连接问题只有 5% 是真的器件坏了。我见过太多人换了三片芯片最后发现是 CPOL 配错。调试的正确顺序永远是先算参数、再看波形、再查连接、最后才怀疑器件。最后分享一个我一直在用的小习惯每调通一个接口就把这次的配置参数CPOL/CPHA、时钟分频、上拉阻值、波特率、地址记在一个自己的清单里注明器件型号和日期。下次遇到同类器件直接查清单五分钟搞定比重新翻手册快得多。这份清单攒两三年之后价值比任何一本书都高因为它全是你在真实板子上验证过的参数。
返回列表