当前位置: 首页 > news >正文

25. 嵌入式通信基石:SPI协议工作原理、模式选择与CW32F030硬件SPI应用详解

嵌入式通信基石:SPI协议工作原理、模式选择与CW32F030硬件SPI应用详解

大家好,我是地文星。最近在项目里用CW32F030驱动一个SPI接口的OLED屏和一个Flash芯片,发现很多刚开始接触嵌入式通信的朋友对SPI协议有点“发怵”,特别是那四种工作模式,经常搞混。今天,我就结合CW32F030这款MCU的硬件SPI,带大家彻底搞懂SPI。咱们不堆砌术语,就从一个工程师的实际使用角度,聊聊SPI到底是什么、怎么配、怎么用。

1. SPI到底是什么?一主多从的“同步对话”

咱们先从最根本的说起。SPI,全称Serial Peripheral Interface,翻译过来就是“串行外设接口”。它是一种同步串行的通信协议。你可以把它想象成两个人(主设备和从设备)在打电话,而且这个电话很高级,允许双方同时说话(全双工)。

核心角色:

  • 主设备 (Master):通常是咱们的微控制器(比如CW32F030)。它是对话的发起者和节奏控制者,负责产生时钟信号,决定什么时候开始和结束通信。
  • 从设备 (Slave):比如传感器、存储器(Flash)、显示屏驱动芯片等。它们被动等待主设备的“呼叫”,然后根据主设备的时钟节奏来收发数据。

一个主设备可以“呼叫”多个从设备,这就是“一主多从”。想象一下主设备是一个项目经理,他可以通过点名(片选信号)来和不同的组员(从设备)单独开会。

通信过程(打个比方):假设主设备(MCU)想读取从设备(传感器)的数据,同时也要给它发个命令。

  1. 主设备先“点名”(拉低对应从设备的片选线CS),说:“小王(传感器),现在开始跟我对话。”
  2. 主设备开始有节奏地拍桌子(产生时钟SCLK),一拍一下。
  3. 在第一拍,主设备通过MOSI线把自己要发的命令(一个比特位)告诉从设备;同时,从设备也通过MISO线把自己要发的数据(一个比特位)告诉主设备。
  4. 如此反复,一拍传一位,直到一个字节(通常8位)或者更长的数据传完。
  5. 主设备“散会”(拉高片选线CS),对话结束。

关键点:因为发送(MOSI)和接收(MISO)是两条独立的线,所以主从设备可以同时收发数据,这就是“全双工”。效率很高。

2. SPI的“四线制”硬件接口

SPI通信最少需要4根线,这也是它最经典的模式。咱们用一张表来清晰说明:

信号线常见别称方向(主设备视角)说明
SCLKSCK, CLK输出时钟线。由主设备产生,是所有数据收发的节奏基准。就像乐队的指挥,大家都要跟着它的拍子走。
MOSISDO, DO, SI输出主设备输出,从设备输入。主设备通过这根线把数据发送给从设备。
MISOSDI, DI, SO输入主设备输入,从设备输出。主设备通过这根线接收从设备发来的数据。
CSNSS, SS输出片选线。主设备通过控制这根线的电平(通常是拉低有效)来选中要和它通信的特定从设备。每个从设备都需要独占一根CS线连接到主设备。

注意:有些器件为了节省引脚,会使用“三线制”(比如只有SCLK, CS和一根双向数据线),或者“两线制”(比如只有时钟和数据,依靠特定协议寻址)。但理解“四线制”是掌握SPI的基础。

3. SPI的四种工作模式:时钟极性与相位

这是SPI最让人困惑,也最关键的地方。模式选择不对,通信绝对失败。别怕,咱们拆开看。

模式由两个参数决定:时钟极性 (CPOL或CKPL)时钟相位 (CPHA或CKPH)

  • 时钟极性 (CPOL):它定义的是时钟线SCLK在空闲状态(即没有传输数据时)的电平
    • CPOL = 0:空闲时,SCLK是低电平。
    • CPOL = 1:空闲时,SCLK是高电平。
  • 时钟相位 (CPHA):它定义的是数据在时钟的哪个边沿被采样(读取),在哪个边沿被更新(切换)
    • CPHA = 0:数据在第一个时钟边沿被采样,在第二个边沿更新。
    • CPHA = 1:数据在第二个时钟边沿被采样,在第一个边沿更新。

把这两个参数组合起来,就得到了4种模式(模式0-3)。我习惯这样记:

模式0 (CPOL=0, CPHA=0)

  • 空闲时时钟为低
  • 数据在时钟上升沿被采样(因为第一个边沿是从低到高的上升沿)。
  • 这是最常用的模式之一。

模式1 (CPOL=0, CPHA=1)

  • 空闲时时钟为低。
  • 数据在时钟下降沿被采样(第二个边沿是从高到低的下降沿)。

模式2 (CPOL=1, CPHA=0)

  • 空闲时时钟为高
  • 数据在时钟下降沿被采样(第一个边沿是从高到低的下降沿)。

模式3 (CPOL=1, CPHA=1)

  • 空闲时时钟为高。
  • 数据在时钟上升沿被采样(第二个边沿是从低到高的上升沿)。
  • 这也是非常常用的模式。

怎么选模式?完全取决于你的从设备!在从设备的数据手册(Datasheet)里,一定会写明它支持哪种SPI模式。比如,我用的那个SPI Flash芯片就要求用模式0或模式3。如果手册没写,可以优先尝试模式0或模式3。

4. CW32F030的硬件SPI外设实战

理解了原理,咱们上手实操。CW32F030内置的硬件SPI外设功能挺全的,能帮我们省去用GPIO模拟时序(软件SPI)的麻烦,效率和稳定性都好很多。

4.1 主要特性一览

根据资料,CW32F030的SPI支持以下特性,咱们挑重点说:

  • 主/从模式:既可以当主机发号施令,也可以当从机听令行事。
  • 多种通信模式:支持标准的全双工,也支持单线半双工(省一根线)和单工。
  • 数据帧灵活:数据长度可以是4位到16位,不只是8位。数据位序(先传最高位MSB还是最低位LSB)也可以设置。
  • 时钟可配置:CPOL和CPHA自由配置,支持上述所有4种模式。
  • 速度够快:当主机时,最高速度可以达到系统外设时钟(PCLK)的一半。
  • 多主机支持:多个MCU的SPI可以组网。
  • 支持DMA:这个太有用了!传输大量数据时(比如刷屏、读写大块Flash),可以让DMA来搬运数据,CPU解放出来干别的活,效率倍增。
  • 丰富的中断:收发完成、错误等都有中断标志,方便事件驱动编程。

4.2 基础配置步骤(主机模式,全双工)

假设我们要把CW32F030配置为SPI主机,以模式0(CPOL=0, CPHA=0)与一个从设备通信。以标准库函数为例,步骤一般是这样的:

第一步:开启时钟任何外设使用前,必须先给它“上电”,也就是开启对应的时钟。

RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_SPI1, ENABLE); // 使能SPI1的时钟 // 同时也要开启对应GPIO端口的时钟,例如SPI1的引脚可能在PA5, PA6, PA7 RCC_AHBPeriphClockCmd(RCC_AHBPERIPH_GPIOA, ENABLE);

第二步:配置SPI所用到的GPIO引脚把对应的GPIO引脚设置为复用功能,并配置好上下拉和速度。

GPIO_InitTypeDef GPIO_InitStructure; // 以SPI1为例:PA5-SCK, PA6-MISO, PA7-MOSI GPIO_InitStructure.Pin = GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7; GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; // 复用推挽输出 GPIO_InitStructure.Pull = GPIO_PULLUP; // 上拉,增强驱动,根据实际需要调整 GPIO_InitStructure.Speed = GPIO_SPEED_HIGH; // 高速,SPI通信通常要求较高 GPIO_Init(GPIOA, &GPIO_InitStructure); // 片选CS引脚通常用普通GPIO手动控制,这里配置一个GPIO(例如PA4)为输出 GPIO_InitStructure.Pin = GPIO_PIN_4; GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出 GPIO_InitStructure.Pull = GPIO_PULLUP; GPIO_InitStructure.Speed = GPIO_SPEED_HIGH; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_SetBits(GPIOA, GPIO_PIN_4); // 初始化为高电平(不选中)

第三步:配置SPI外设参数这是核心,设置工作模式、数据格式、速率等。

SPI_InitTypeDef SPI_InitStructure; SPI_InitStructure.Direction = SPI_DIRECTION_2LINES_FULLDUPLEX; // 双线全双工 SPI_InitStructure.Mode = SPI_MODE_MASTER; // 主机模式 SPI_InitStructure.DataSize = SPI_DATASIZE_8BIT; // 数据帧8位 SPI_InitStructure.CLKPolarity = SPI_POLARITY_LOW; // CPOL=0,空闲时钟低电平 SPI_InitStructure.CLKPhase = SPI_PHASE_1EDGE; // CPHA=0,第一个边沿采样 // 注意:不同库对CPHA的定义可能不同,需查手册。这里“1EDGE”通常对应CPHA=0。 SPI_InitStructure.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32; // 波特率预分频,决定SCLK速度 SPI_InitStructure.FirstBit = SPI_FIRSTBIT_MSB; // 数据传输从最高位(MSB)开始 SPI_InitStructure.CRCCalculation = SPI_CRCCALCULATION_DISABLE; // 禁用CRC校验(通常不用) SPI_InitStructure.CRCPolynomial = 7; // 如果启用CRC,设置多项式 SPI_Init(SPI1, &SPI_InitStructure);

提示BaudRatePrescaler(波特率预分频器)是关键,它决定了SCLK的频率。SCLK频率 = PCLK / 预分频值。比如PCLK是48MHz,预分频选32,那么SCLK就是1.5MHz。一定要确保这个频率不超过从设备支持的最高频率。

第四步:使能SPI外设

SPI_Cmd(SPI1, ENABLE);

4.3 编写收发函数

配置好后,就可以进行数据收发了。一个基础的阻塞式发送接收函数如下:

// 函数:通过SPI1交换一个字节(同时发送和接收) uint8_t SPI1_ReadWriteByte(uint8_t TxData) { // 等待发送缓冲区为空(表示可以写入新的数据) while (SPI_GetFlagStatus(SPI1, SPI_FLAG_TXE) == RESET); // 写入要发送的数据,这会自动启动一次传输 SPI_SendData(SPI1, TxData); // 等待接收缓冲区非空(表示数据已经接收回来) while (SPI_GetFlagStatus(SPI1, SPI_FLAG_RXNE) == RESET); // 读取接收到的数据 return SPI_ReceiveData(SPI1); } // 使用示例:发送命令0x9F(读取器件ID),并接收3个字节的ID uint8_t id_buf[3]; SPI_CS_LOW(); // 拉低片选,选中从设备 SPI1_ReadWriteByte(0x9F); // 发送命令 id_buf[0] = SPI1_ReadWriteByte(0xFF); // 发送哑元数据(0xFF),同时接收第一个ID字节 id_buf[1] = SPI1_ReadWriteByte(0xFF); // 接收第二个ID字节 id_buf[2] = SPI1_ReadWriteByte(0xFF); // 接收第三个ID字节 SPI_CS_HIGH(); // 拉高片选,释放从设备

注意:SPI是全双工,主设备发送的同时也在接收。所以即使你只想读数据,也必须发送点东西(通常是0xFF或0x00,称为“哑元”数据)来产生时钟,从设备才能把数据“推”到MISO线上。

4.4 进阶:使用DMA进行高效传输

当你需要连续发送一大块数据(比如更新显示屏的帧缓冲区)时,用上面的循环一个个字节发效率太低,CPU会被完全占用。这时就该DMA出场了。

DMA配置思路:

  1. 配置DMA通道,将内存地址(你的数据数组)设置为源地址(对于发送)或目标地址(对于接收),将SPI的数据寄存器地址设置为目标地址(对于发送)或源地址(对于接收)。
  2. 设置传输的数据量。
  3. 使能SPI的DMA发送和/或接收请求。
  4. 启动DMA传输。
  5. 等待DMA传输完成中断或查询标志位。

使用DMA后,CPU只需要启动传输,就可以去处理其他任务,等DMA传输完成产生中断时,再来处理后续工作即可,大大提升了系统效率。具体寄存器或库函数的配置,需要参考CW32F030的官方库和例程。

5. SPI的优缺点与应用场景

最后,咱们客观总结一下SPI,方便你在项目中选型。

优势:

  • 速度快:因为是全双工、点对点、协议简单,所以可以达到很高的时钟频率(几十MHz很常见)。
  • 协议简单:没有像I2C那样的地址、起始停止位、ACK应答等复杂信号,实现和调试都相对容易。
  • 灵活性高:数据帧长度、位序、时钟模式都可调,适应不同器件。

劣势:

  • 占用引脚多:每个从设备都需要一根独立的片选线,当从设备很多时,会占用大量主设备的GPIO。
  • 没有硬件流控/应答:主设备无法知道从设备是否真的收到了数据(除非在应用层自己定义应答协议)。
  • 通信距离短:通常用于板级设备间通信,不适合长距离传输。

典型应用场景:

  • 存储器:SPI Flash(如W25Q系列)、SD卡(SPI模式)。
  • 传感器:很多数字传感器(如气压计BMP280、IMU等)提供SPI接口。
  • 显示屏:小尺寸的OLED、TFT屏常用SPI接口驱动。
  • 无线模块:如NRF24L01、ESP8266/32(SPI接口固件下载与通信)。
  • 音频编解码器数字电位器等。

希望这篇结合了原理和CW32F030实战的文章,能帮你把SPI协议理顺。记住关键:四根线、主从同步、模式看从设备手册。多动手写代码、调时序,遇到问题用逻辑分析仪抓一下波形,对照时序图看,很快就能掌握。

http://www.cnnetsun.cn/news/1347341.html

相关文章:

  • 影墨·今颜镜像国产化适配:昇腾910B/寒武纪MLU370兼容性验证
  • ROS2实战:如何在rviz2中绘制动态多边形(附完整代码)
  • [函数设计实战] 巧用循环与幂运算,高效求解特殊a串数列和
  • 高效掌握MissionPlanner:面向无人机开发者的开源地面控制站指南
  • ESP32+VScode环境配置踩坑实录:解决‘python.exe -m pip无效’的6种方法
  • USB发展史:从1.0到USB4,揭秘万能接口的进化之路
  • 智能抢占:Oracle Cloud ARM服务器自动部署技术指南
  • 从NEU-DET到YOLOv7:实战数据集格式转换与划分全流程解析
  • ElasticSearch深度分页实战:search_after与伪分页的混合策略
  • CogVideoX-2b企业级部署:本地化+隐私安全+离线渲染完整方案
  • 告别printf调试!用SEGGER RTT实现彩色日志+浮点打印的终极指南
  • 【手把手教学】利用Docker-Compose一键部署RuoYi-Cloud微服务集群
  • Qwen3-0.6B-FP8快速入门Git:命令解释与工作流指导
  • 避开这5个坑!Unity背景音乐优化实战(含Audio Mixer配置)
  • 从基准测试到创新:利用生成先验构建鲁棒图像水印以抵御深度编辑攻击
  • 正运动控制器:视觉纠偏与找孔的高效实现
  • OpenCore Legacy Patcher实战:零基础15分钟打造macOS启动盘
  • all-MiniLM-L6-v2参数详解:6层Transformer结构如何平衡精度与效率?
  • Stata实战:工具变量法(IV)处理内生性问题,从原理到操作全解析
  • 智能客服测试实战:从自动化到性能优化的全链路解决方案
  • VMware虚拟机中搭建MogFace-large开发测试环境教程
  • 避坑指南:BERT微调时90%人会遇到的5个典型错误及解决方案
  • 电商运营必备:RMBG-2.0一键移除商品背景,1秒出透明图
  • 期货量化策略验证的核心工具:天勤量化TqSdk历史回测系统全解析
  • OpenAI Whisper-base.en语音识别技术全解析:从部署到生产级应用
  • STM32CubeMX+FreeRTOS实战:如何用Tracealyzer可视化任务调度(附J-Link避坑指南)
  • Meta-Llama-3-8B-Instruct新手入门:vLLM+WebUI环境搭建与快速测试
  • cv_unet_image-colorization从部署到应用:政务档案馆黑白文档智能着色实施路径
  • 从零开始:用C语言模拟中断控制器与CPU交互(含调试技巧)
  • 基于AI多源数据融合的美联储“三重门”困境分析与政策响应研究