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)想读取从设备(传感器)的数据,同时也要给它发个命令。
- 主设备先“点名”(拉低对应从设备的片选线CS),说:“小王(传感器),现在开始跟我对话。”
- 主设备开始有节奏地拍桌子(产生时钟SCLK),一拍一下。
- 在第一拍,主设备通过MOSI线把自己要发的命令(一个比特位)告诉从设备;同时,从设备也通过MISO线把自己要发的数据(一个比特位)告诉主设备。
- 如此反复,一拍传一位,直到一个字节(通常8位)或者更长的数据传完。
- 主设备“散会”(拉高片选线CS),对话结束。
关键点:因为发送(MOSI)和接收(MISO)是两条独立的线,所以主从设备可以同时收发数据,这就是“全双工”。效率很高。
2. SPI的“四线制”硬件接口
SPI通信最少需要4根线,这也是它最经典的模式。咱们用一张表来清晰说明:
| 信号线 | 常见别称 | 方向(主设备视角) | 说明 |
|---|---|---|---|
| SCLK | SCK, CLK | 输出 | 时钟线。由主设备产生,是所有数据收发的节奏基准。就像乐队的指挥,大家都要跟着它的拍子走。 |
| MOSI | SDO, DO, SI | 输出 | 主设备输出,从设备输入。主设备通过这根线把数据发送给从设备。 |
| MISO | SDI, DI, SO | 输入 | 主设备输入,从设备输出。主设备通过这根线接收从设备发来的数据。 |
| CS | NSS, 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配置思路:
- 配置DMA通道,将内存地址(你的数据数组)设置为源地址(对于发送)或目标地址(对于接收),将SPI的数据寄存器地址设置为目标地址(对于发送)或源地址(对于接收)。
- 设置传输的数据量。
- 使能SPI的DMA发送和/或接收请求。
- 启动DMA传输。
- 等待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协议理顺。记住关键:四根线、主从同步、模式看从设备手册。多动手写代码、调时序,遇到问题用逻辑分析仪抓一下波形,对照时序图看,很快就能掌握。
