ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

STM32 硬件 SPI 实战:全双工上 MHz,给 I2C 那些慢设备提速

STM32 硬件 SPI 实战:全双工上 MHz,给 I2C 那些慢设备提速 硬件 I2C 能把一堆传感器安顿好但 I2C 满打满算 400k顶多应付温湿度、气压这种慢变量。等我要往 TFT 屏刷像素、从 SPI Flash 烧程序、读 IMU 的裸数据流400k 直接跪屏一闪一闪跟抽风似的。SPI 一上场就不一样全双工、推到十几 MHz、没有地址仲裁那套开销字节一个接一个往外蹦。这篇讲怎么把 SPI 用稳别让 CPOL/CPHA 和片选把你自己绕晕。协议人话版SPI 四根线SCK时钟、MOSI主发从收、MISO从发主收、CS/SS片选。时钟由主机产生数据在 SCK 边沿一位位移进移出。靠两根数据线所以它才能全双工发的同时就能收。和 I2C 最大区别SPI没有从机地址靠 CS 脚一个一个挑从机也不用上拉电阻推挽输出直接驱动速率上限高得多。代价是每多一个从机就多一根 CS 线线数蹭蹭涨。CPOL / CPHA 四种模式这是 SPI 最容易栽的地方。两个参数决定在时钟的哪个边沿采样数据CPOL空闲时 SCK 是高还是低。CPHA在第一个边沿还是第二个边沿采样。组合出 4 种模式Mode 0~3。从机 datasheet 写 Mode 0你就配 Mode 0差一个模式数据整字节错位。别凭感觉配先翻从机手册的 “SPI Mode” 那一行。CubeMX 怎么配SPI1 → Mode 选Full-Duplex Master全双工主机。参数Clock Polarity (CPOL)/Clock Phase (CPHA)对照从机手册填。Baud Rate PrescalerSPI 时钟 APB 时钟 / 分频。84MHz 的 APB2 上选/4就是 21MHz新手先/8或/16跑通。Data Size8 bits默认或 16 bits某些 DAC/ADC 用 16 位帧。First BitMSB First默认和绝大多数器件一致。NSS选Software软件片选。这样 CS 脚你自己用普通 GPIO 控制灵活度最高HAL 不会自动帮你拉 CS。引脚SCK/MOSI/MISO 自动分配CS 脚单独设一个 GPIO 输出如 PA4。代码全双工收发是核心SPI 是移位寄存器发一位的同时收一位。想读数据你就得发 dummy 把 SCK 跑起来从机才往 MISO 吐。#defineCS_PINGPIO_PIN_4#defineCS_PORTGPIOAstaticinlinevoidcs_low(void){HAL_GPIO_WritePin(CS_PORT,CS_PIN,GPIO_PIN_RESET);}staticinlinevoidcs_high(void){HAL_GPIO_WritePin(CS_PORT,CS_PIN,GPIO_PIN_SET);}// 读 W25Q 这类 SPI Flash发命令3字节地址再收数据voidspi_flash_read(uint32_taddr,uint8_t*out,uint32_tlen){uint8_tcmd[4]{0x03,(addr16)0xFF,(addr8)0xFF,addr0xFF};cs_low();HAL_SPI_Transmit(hspi1,cmd,4,100);// 发读命令地址HAL_SPI_Receive(hspi1,out,len,100);// 发 dummy 把时钟跑起来收数据cs_high();}注意上面分两步Transmit发命令Receive其实是在发 dummyHAL 内部填 0xFF产生 SCK 同时收 MISO。只想收也必须发东西否则从机没时钟、不吐数据——这是 SPI 新手最常懵的一点。单次收发最简单用TransmitReceiveuint8_ttx0xAA,rx0;HAL_SPI_TransmitReceive(hspi1,tx,rx,1,100);// 发的同时收 1 字节DMA 版刷屏、连续搬大块时省 CPUuint8_tframe[1024];cs_low();HAL_SPI_Transmit_DMA(hspi1,frame,1024);// 完成进 HAL_SPI_TxCpltCallback// 收也要 DMA 就再加一条 SPI1_RX 流全双工需 TX、RX 两个流都开多从机共享总线多个 SPI 从机可以共用 SCK/MOSI/MISO但每个从机一根独立 CS。谁要通信就拉谁的 CS 低谈完拉高。绝不能同时拉低两个 CS。// 三个从机各一根 CSCS1PA4, CS2PA5, CS3PA6voidtalk_to(uint8_tdev){cs_high_all();switch(dev){case1:HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);break;case2:HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_RESET);break;case3:HAL_GPIO_WritePin(GPIOA,GPIO_PIN_6,GPIO_PIN_RESET);break;}// ... 传数据 ...cs_high_all();}10 个真实翻车点1. CPOL/CPHA 模式对不上。★ 头号坑。从机 datasheet 写 Mode 0CPOL0, CPHA0你图省事默认配成 Mode 3数据整字节错位读回来全是乱码。拿到任何 SPI 器件第一步翻手册 SPI Mode 那行照着配。2. 软件片选忘了拉低。NSS 设成 Software 后HAL 的Transmit/Receive不会自动拉 CS。你没cs_low()就直接发从机压根没被选中总线静悄悄。每次传输前cs_low()、之后cs_high()当成仪式。3. 多从机 CS 没释放干净。上一个从机谈完忘了cs_high()下一个又cs_low()另一个结果两个 CS 同时低——两个从机一起往 MISO 驱动数据打架总线冲突。封装成拉高全部→只拉低目标的talk_to()最稳。4. 速率超规格。分频算的 21MHz但线长超过 10cm 或负载重SCK 振铃、建立保持时间不够偶发错字节。先降一档/8验证线短再往上提。5. 想只读却用 Transmit。SPI 收数据必须有 SCK而 SCK 只在发的时候跑。你只调HAL_SPI_Transmit想读从机没被时钟驱动啥也收不到。只读请用TransmitReceive或ReceiveHAL 会填 dummy 发时钟。6. 读 SPI Flash 没等 WIP 忙。Flash 写完/擦完内部要忙一阵状态寄存器 WIP 位为 1。你立刻读读到的可能是旧值或乱的。写/擦后先轮询Read Status Register等 WIP 清 0 再读。7. DMA 只开一个方向。SPI 全双工TX 和 RX 是两个独立 DMA 流。想全双工 DMA 必须 TX、RX 流都配只开 TX 收不到只开 RX 没时钟。单工场景纯发屏只开 TX 没问题。完成回调是HAL_SPI_TxCpltCallback/HAL_SPI_TxRxCpltCallback不是 UART 那套。8. 16 位帧高低字节错。有些 DAC/ADC 一帧 16 位。你Data Size配 16bit但发的数组是uint8_t[2]HAL 按 16 位搬会把两个字节当成一个数高低字节反掉。16 位帧必须用uint16_t数组。9. MSB/LSB first 不一致。默认 MSB First绝大多数器件也这么要。少数器件某些老 LCD 控制器要 LSB First你没改First Bit数据整串反的。翻手册确认。10. 半双工/三线器件错配成全双工。有些 SPI 器件是三线半双工MISO/MOSI 合一或双线/四线那是 QSPI另算。你按全双工配了四根线MISO 悬空读到的就是随机噪声。先数清楚器件到底是几根线。完整例程骨架intmain(void){HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_SPI1_Init();cs_high_all();uint8_tid[3];cs_low();uint8_tcmd0x9F;// Read JEDEC IDHAL_SPI_Transmit(hspi1,cmd,1,100);HAL_SPI_Receive(hspi1,id,3,100);cs_high();// id[0..2] 厂商/内存类型/容量while(1){/* ... 刷屏 / 读写 Flash ... */}}适合TFT 屏、SPI Flash/SD 卡、IMU 裸数据流、高速 ADC/DAC 这类要速率、要全双工、不在乎多几根线的大块数据传输。不适合板子上器件多又不想多占引脚的慢速控制面那种用 I2C 更省线。铁律三条CPOL/CPHA 严格照从机手册配、软件片选每次传输前后必须拉低拉高、只读操作也要发时钟用 Receive/TransmitReceive。把这三条焊死SPI 基本一次跑通。SPI 解决了快但它是点对点、靠 CS 挨个轮询的器件一多 CS 线爆炸而且没有应答机制、传错了你不知道。要一堆节点平等地挂在两条差分线上、自带仲裁和校验、错了能重发就得上 CAN。进阶玩法CAN 怎么在嘈杂的现场总线里稳住。
返回列表