一、SPI 通信协议概述
SPI(Serial Peripheral Interface)是一种同步串行通信协议,由摩托罗拉公司提出,在嵌入式系统中广泛应用于微控制器与各种外设之间的数据传输。它采用主从模式,通常包含四条信号线:
SCK(Serial Clock):时钟信号线,由主设备产生,用于同步数据传输。其频率决定了数据传输的速率,在不同的应用场景下,可根据外设的要求灵活设置,例如对于一些低速的传感器,可能使用较低的 SCK 频率,如几百 kHz;而对于高速的存储设备或显示屏,SCK 频率可能会达到几十 MHz。
MOSI(Master Out Slave In):主设备输出从设备输入数据线,主设备通过这条线将数据发送给从设备。
MISO(Master In Slave Out):主设备输入从设备输出数据线,从设备通过此线向主设备返回数据,实现双向通信。
CS(Chip Select):片选信号线,用于选择特定的从设备。当主设备与多个从设备连接时,通过拉低相应从设备的 CS 引脚来选中该设备进行通信,未被选中的从设备处于未激活状态,不参与数据传输,这样可以在同一总线上连接多个 SPI 设备,节省微控制器的引脚资源。
SPI 协议的数据传输模式通过时钟极性(CPOL)和时钟相位(CPHA)来定义,共有四种模式(0,0)、(0,1)、(1,0)、(1,1),这决定了数据在时钟的上升沿还是下降沿采样以及何时改变数据。例如,在模式(0,0)下,SCK 空闲时为低电平,数据在上升沿采样,主设备在下降沿改变数据输出;而在模式(1,1)中,SCK 空闲为高电平,数据在下降沿采样,主设备在上升沿改变数据。不同的外设可能支持不同的 SPI 模式,因此在实际应用中需要根据外设的要求正确设置。
二、GD32 系列芯片与 SPI 接口
GD32 系列微控制器作为国产芯片的优秀代表,基于 ARM Cortex-M 内核,拥有丰富的产品线,如 GD32F1、GD32F3、GD32F4 等系列,各系列在性能、资源配置和功耗等方面有所差异,以满足不同应用场景的需求。
SPI 接口作为 GD32 芯片的重要外设之一,具有以下特点和优势:
高速传输能力:部分 GD32 芯片的 SPI 接口能够支持较高的数据传输速率,例如 GD32F4 系列,其 SPI 最高波特率可达几十 MHz,满足如高速 ADC 数据采集、高速存储设备读写等对速度要求苛刻的应用场景。
灵活的配置选项:可以通过软件对 SPI 的工作模式(主从模式)、数据格式(8 位或 16 位等)、CPOL 和 CPHA 等参数进行详细配置,方便与各种不同类型的 SPI 外设进行连接和通信,无论是简单的传感器还是复杂的通信模块,都能轻松适配。
多 SPI 接口支持:一些高端的 GD32 芯片提供多个 SPI 接口,允许同时连接多个 SPI 外设,实现并行数据传输,提高系统的数据吞吐量和处理效率,例如在一个同时使用 SPI 接口的显示屏、Flash 存储器和无线通信模块的系统中,多个 SPI 接口可以避免数据传输冲突,使各外设独立高效地工作。
三、GD32 SPI 引脚配置与初始化
引脚复用功能设置
GD32 的 SPI 引脚通常与 GPIO 引脚复用,在使用 SPI 之前,需要配置相应的 GPIO 复用功能寄存器,将其从默认的 GPIO 功能切换到 SPI 功能。以 GD32F4 系列为例,假设使用 SPI1,其 MOSI、MISO、SCK 和 CS 引脚可能分别对应于特定的 GPIO 端口和引脚号,如 PA7、PA6、PA5 和 PA4。通过设置 GPIO 复用功能寄存器(如 AFRL 和 AFRH 寄存器),将这些引脚配置为 SPI1 的相应功能,使它们能够正确地传输 SPI 信号。
引脚电气特性配置
除了引脚功能复用,还需对引脚的电气特性进行配置,包括输出速度、上下拉电阻等。对于 SCK 引脚,较高的输出速度可以满足高速数据传输需求,但也会增加电磁干扰(EMI)。因此,在实际配置时,需要根据系统的工作频率和对 EMI 的敏感度进行权衡,一般可通过 GPIO 输出速度寄存器(如 OSPEEDR 寄存器)设置合适的速度值。对于 MOSI 和 MISO 引脚,合理设置上下拉电阻(通过 PUPDR 寄存器)可以确保在没有数据传输时引脚处于稳定的电平状态,避免出现浮空导致的信号不稳定问题,例如在连接某些外部设备时,可能需要根据其电气特性设置上拉电阻以保证数据传输的可靠性。
SPI 初始化代码示例
// 使能 SPI1 时钟
rcu_periph_clock_enable(RCU_SPI1);
// 配置 SPI1 工作模式为主模式,数据格式为 8 位,CPOL = 0,CPHA = 0,波特率预分频值为 8
spi_init(SPI1, SPI_MODE_MASTER, SPI_DFF_8BIT, SPI_CPOL_LOW, SPI_CPHA_1EDGE);
spi_baudrate_prescaler_config(SPI1, SPI_PSC_8);
// 使能 SPI1 接口
spi_enable(SPI1);
这段代码首先使能 SPI1 的时钟,然后通过 spi_init 函数配置 SPI1 的工作模式、数据格式、时钟极性和相位等参数,最后使用 spi_enable 函数使能 SPI1 接口,完成 SPI1 的初始化设置,使其能够进行数据传输操作。
四、SPI 驱动程序开发
数据传输函数实现
发送数据函数:
// 发送一个字节数据
void SPI_SendByte(SPI_TypeDef* SPIx, uint8_t data)
{
// 等待发送缓冲区为空
while(RESET == spi_i2s_flag_get(SPIx, SPI_FLAG_TBE));
// 将数据写入 SPI 数据寄存器,启动数据发送
spi_i2s_data_transmit(SPIx, data);
}
该函数首先通过查询 SPI_FLAG_TBE 标志位等待 SPI 发送缓冲区为空,然后将待发送的数据写入 SPIx 的数据寄存器,SPI 硬件会自动按照配置的时钟和数据格式将数据逐位移出到 MOSI 引脚进行发送。
接收数据函数:
// 接收一个字节数据
uint8_t SPI_ReceiveByte(SPI_TypeDef* SPIx)
{
// 等待接收缓冲区非空
while(RESET == spi_i2s_flag_get(SPIx, SPI_FLAG_RBNE));
// 从 SPI 数据寄存器读取接收到的数据
return spi_i2s_data_receive(SPIx);
}
此函数通过查询 SPI_FLAG_RBNE 标志位等待 SPI 接收缓冲区有数据,然后从 SPIx 的数据寄存器读取接收到的数据并返回,在发送数据的同时,SPI 硬件会将从 MISO 引脚接收到的数据逐位移入到数据寄存器中,从而实现数据的接收。
\2. 中断驱动的数据传输(可选)
在一些对实时性要求较高或需要高效利用 CPU 资源的应用中,可以利用 SPI 中断来实现数据传输。首先需要配置 SPI 的中断使能寄存器,例如使能发送完成中断和接收完成中断:
// 使能 SPI1 发送完成中断和接收完成中断
spi_i2s_interrupt_enable(SPI1, SPI_I2S_INT_TBE | SPI_I2S_INT_RBNE);
1
2
然后编写中断服务函数,在中断服务函数中处理数据的发送和接收操作。例如:
void SPI1_IRQHandler(void)
{
if(spi_i2s_interrupt_flag_get(SPI1, SPI_I2S_INT_TBE))
{
// 发送下一个数据
spi_i2s_data_transmit(SPI1, next_data_to_send);
}
if(spi_i2s_interrupt_flag_get(SPI1, SPI_I2S_INT_RBNE))
{
// 读取接收到的数据
received_data = spi_i2s_data_receive(SPI1);
// 处理接收到的数据,例如存储到缓冲区或进行进一步的计算
process_received_data(received_data);
}
}
这样,当 SPI 完成一次数据发送或接收时,会产生中断,在中断服务函数中可以及时进行下一次数据的发送或对接收到的数据进行处理,而不需要 CPU 持续轮询标志位,提高了系统的响应速度和效率,尤其适用于多任务处理的系统环境。
五、SPI 在实际项目中的应用案例
连接 SPI 接口的传感器(以 MPU6050 为例)
硬件连接:MPU6050 是一款常用的六轴运动传感器,包含加速度计和陀螺仪。它通过 SPI 接口与 GD32 连接,其 VDD 和 GND 分别连接到 GD32 的电源和地,SCK、MOSI、MISO 分别连接到 GD32 的相应 SPI 引脚(如上述提到的 SPI1 的对应引脚),CS 引脚连接到 GD32 的一个 GPIO 引脚用于片选控制。
驱动开发与数据读取:首先对 SPI 进行初始化配置,然后通过向 MPU6050 的寄存器写入相应的命令来启动传感器并配置其工作模式,例如设置采样率、量程等。之后,通过 SPI 读取传感器的加速度和角速度数据。以下是一个简单的读取 MPU6050 数据的函数示例:
void MPU6050_ReadData(SPI_TypeDef* SPIx, int16_t* accel_data, int16_t* gyro_data)
{
// 片选 MPU6050
GPIO_ResetBits(GPIOA, GPIO_PIN_4);
// 发送读取加速度数据的寄存器地址
SPI_SendByte(SPIx, 0x3B);
// 读取三个轴的加速度数据
accel_data[0] = (SPI_ReceiveByte(SPIx) << 8) | SPI_ReceiveByte(SPIx);
accel_data[1] = (SPI_ReceiveByte(SPIx) << 8) | SPI_ReceiveByte(SPIx);
accel_data[2] = (SPI_ReceiveByte(SPIx) << 8) | SPI_ReceiveByte(SPIx);
// 发送读取陀螺仪数据的寄存器地址
SPI_SendByte(SPIx, 0x43);
// 读取三个轴的陀螺仪数据
gyro_data[0] = (SPI_ReceiveByte(SPIx) << 8) | SPI_ReceiveByte(SPIx);
gyro_data[1] = (SPI_ReceiveByte(SPIx) << 8) | SPI_ReceiveByte(SPIx);
gyro_data[2] = (SPI_ReceiveByte(SPIx) << 8) | SPI_ReceiveByte(SPIx);
// 取消片选
GPIO_SetBits(GPIOA, GPIO_PIN_4);
}
通过定期调用该函数,可以获取 MPU6050 的实时运动数据,这些数据可用于姿态解算、运动检测等应用,如在四轴飞行器飞行控制系统中,用于稳定飞行姿态;在智能手环中,用于计步和运动分析等。
使用 SPI 接口的显示屏(以 ILI9341 为例)
硬件连接:ILI9341 是一款常用的 TFT 显示屏控制器,通过 SPI 接口与 GD32 通信。其连接方式与其他 SPI 设备类似,将 VCC、GND 连接到电源和地,SCK、MOSI、CS 等引脚连接到 GD32 的 SPI 引脚,同时还需要连接一些控制引脚,如 RESET、DC(数据 / 命令选择)等到 GD32 的 GPIO 引脚。
驱动实现与图形显示:首先初始化 SPI 和相关的 GPIO 引脚,然后通过 SPI 向 ILI9341 发送一系列的命令和数据来初始化显示屏,设置显示模式、颜色格式、分辨率等参数。之后,就可以通过 SPI 发送像素数据来显示图形和文字。例如,以下是一个简单的在显示屏上绘制一个矩形的函数:
void ILI9341_DrawRectangle(SPI_TypeDef* SPIx, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)
{
// 设置绘制矩形的命令
uint8_t command[] = {0x2A, 0x00, x1 >> 8, x1 & 0xFF, x2 >> 8, x2 & 0xFF};
// 发送命令
GPIO_ResetBits(GPIOB, GPIO_PIN_5); // DC 引脚拉低表示发送命令
for(int i = 0; i < sizeof(command); i++)
{
SPI_SendByte(SPIx, command[i]);
}
GPIO_SetBits(GPIOB, GPIO_PIN_5); // DC 引脚拉高表示发送数据
// 设置颜色数据
uint8_t data[] = {color >> 8, color & 0xFF};
// 绘制矩形的每一行
for(uint16_t y = y1; y < y2; y++)
{
// 设置行地址命令
uint8_t row_command[] = {0x2B, 0x00, y >> 8, y & 0xFF};
GPIO_ResetBits(GPIOB, GPIO_PIN_5);
for(int i = 0; i < sizeof(row_command); i++)
{
SPI_SendByte(SPIx, row_command[i]);
}
GPIO_SetBits(GPIOB, GPIO_PIN_5);
// 发送一行的颜色数据
for(uint16_t x = x1; x < x2; x++)
{
for(int i = 0; i < sizeof(data); i++)
{
SPI_SendByte(SPIx, data[i]);
}
}
}
}
通过这样的函数,可以在显示屏上绘制各种图形和界面,用于显示系统状态、菜单、图像等信息,广泛应用于工业控制终端、智能仪表、手持设备等领域。
六、SPI 应用中的优化与调试技巧
时序优化
时钟频率调整:根据连接的外设特性,合理设置 SPI 的时钟频率(波特率)。如果外设对时钟频率有明确的要求,如某些高速 ADC 要求特定的 SPI 时钟频率以保证数据同步采集,应严格按照其要求设置。对于一些对速度不太敏感但对稳定性要求较高的外设,适当降低时钟频率可以减少数据传输错误,提高系统的可靠性。可以通过修改 SPI 波特率预分频值来调整时钟频率,例如在 GD32 中使用 spi_baudrate_prescaler_config 函数进行设置,并通过示波器观察 SPI 时钟信号的波形,确保其符合预期的频率和稳定性。
CPOL 和 CPHA 设置优化:对于不同的 SPI 外设,正确设置时钟极性(CPOL)和时钟相位(CPHA)至关重要。如果设置不正确,可能导致数据采样错误,无法正常通信。在连接新的外设时,需要仔细查阅其数据手册,确定正确的 CPOL 和 CPHA 设置,并通过实验验证数据传输的正确性。可以先使用简单的测试代码,如发送固定的数据并检查接收的数据是否正确,来验证 SPI 时序设置的合理性。
错误检测与处理
数据校验机制:在数据传输过程中,引入数据校验机制,如 CRC(循环冗余校验)。在发送数据时,计算数据的 CRC 值并一同发送,接收端对接收到的数据重新计算 CRC 值并与发送的 CRC 值进行比较。如果 CRC 校验不通过,说明数据可能在传输过程中出现错误,此时可以采取重传数据、标记错误等措施。例如,对于重要的配置数据或传感器测量数据的传输,使用 CRC 校验可以确保数据的完整性和准确性,提高系统的可靠性。
超时检测:在进行 SPI 操作时,设置超时检测机制,防止由于外设故障或其他原因导致的通信长时间阻塞。例如,在发送数据后,启动一个定时器,如果在规定的时间内没有完成数据传输(未收到相应的标志位或中断信号),则判定为超时错误,并进行相应的错误处理,如复位 SPI 接口、重新初始化通信或发出错误提示信号,以便系统能够及时采取措施恢复正常运行。
调试工具的使用
逻辑分析仪:使用逻辑分析仪可以捕获 SPI 总线上的信号,包括 SCK、MOSI、MISO 和 CS 等信号的波形,直观地观察数据传输的过程。通过分析波形,可以检查数据的正确性、时序是否符合要求以及是否存在信号干扰等问题。例如,在调试 SPI 连接的 Flash 存储器时,如果写入或读取数据出现错误,可以通过逻辑分析仪查看命令和数据的传输过程,判断是 SPI 时序设置问题还是 Flash 本身的故障导致的错误。
调试器单步调试:利用 GD32 的调试接口(如 JTAG 或 SWD)和调试工具(如 Keil、IAR 等集成开发环境),对 SPI 驱动代码进行单步调试。在调试过程中,可以查看寄存器
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/pigliuxu/article/details/144725144
|
|