GD32F103系列教程—(SPI软件实现与硬件配置优化篇)

张开发
2026/5/20 14:11:30 15 分钟阅读
GD32F103系列教程—(SPI软件实现与硬件配置优化篇)
1. GD32F103 SPI通信基础与硬件配置SPISerial Peripheral Interface是嵌入式系统中常用的同步串行通信协议以其高速、全双工的特点被广泛应用在存储器、传感器等外设连接中。GD32F103系列MCU内置了硬件SPI控制器最高支持18MHz通信速率。在实际项目中我遇到过不少开发者对SPI的四种工作模式选择感到困惑这里先给大家做个通俗解释想象SPI通信就像两个人面对面传球MOSI和MISO两条数据线SCK时钟信号就是两人约定的拍手节奏。CPOL决定拍手前的初始状态0手放开1手拍合CPHA决定在拍手的哪个时刻传球0拍手瞬间传1等拍完再传。这样组合起来就形成了四种模式模式0手放开准备CPOL0拍手瞬间传CPHA0模式1手放开准备CPOL0等拍完再传CPHA1模式2手拍合准备CPOL1拍手瞬间传CPHA0模式3手拍合准备CPOL1等拍完再传CPHA1硬件配置第一步是时钟使能这个步骤很多新手容易遗漏。GD32的每个外设都有独立的时钟门控必须手动开启。我曾调试过一个SPI不工作的案例最后发现是AFIO时钟没开。具体代码如下rcu_periph_clock_enable(RCU_GPIOA); // 使能GPIOA时钟 rcu_periph_clock_enable(RCU_AF); // 使能复用功能时钟 rcu_periph_clock_enable(RCU_SPI0); // 使能SPI0时钟引脚配置是第二个关键点。GD32F103的SPI引脚是固定的不能随意映射。根据我的项目经验推荐配置如下表引脚功能引脚号工作模式输出速率SCKPA5复用推挽输出50MHzMOSIPA7复用推挽输出50MHzMISOPA6浮空输入-CSPA4推挽输出50MHz对应的初始化代码示例// SCK和MOSI配置 gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_5 | GPIO_PIN_7); // MISO配置 gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_6); // CS配置软件控制 gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4); gpio_bit_set(GPIOA, GPIO_PIN_4); // 初始置高2. SPI参数配置与工作模式优化配置SPI外设时需要特别注意结构体参数的初始化顺序。我在早期项目中曾遇到配置不生效的问题后来发现是没调用spi_struct_para_init()进行默认值初始化。推荐按以下步骤操作spi_parameter_struct spi_init_struct; spi_i2s_deinit(SPI0); // 复位SPI0 spi_struct_para_init(spi_init_struct); // 必须调用 // 全双工模式 spi_init_struct.trans_mode SPI_TRANSMODE_FULLDUPLEX; // 模式3CPOL1, CPHA1 spi_init_struct.clock_polarity_phase SPI_CK_PL_HIGH_PH_2EDGE; // 软件NSS管理 spi_init_struct.nss SPI_NSS_SOFT; // 高位优先 spi_init_struct.endian SPI_ENDIAN_MSB; // 主模式 spi_init_struct.device_mode SPI_MASTER; // 8位数据帧 spi_init_struct.frame_size SPI_FRAMESIZE_8BIT; // 预分频设置系统时钟72MHz时 spi_init_struct.prescale SPI_PSC_8; // 9MHz SPI时钟 spi_init(SPI0, spi_init_struct); spi_enable(SPI0); // 最后使能SPI时钟预分频的实用技巧GD32F103的SPI时钟源自APB2总线最高72MHz通过预分频系数可以得到不同速率。实测中发现当SPI时钟超过12MHz时需要缩短布线长度。常用配置如下目标频率预分频值实际频率18MHzSPI_PSC_418MHz9MHzSPI_PSC_89MHz4.5MHzSPI_PSC_164.5MHz2.25MHzSPI_PSC_322.25MHz硬件优化经验在PCB布局时SCK信号线要尽量短避免与其他高频信号平行走线如果通信距离超过10cm建议在MOSI/MISO上加33Ω串联电阻对于电机控制等干扰强的场景可以在SPI线上加10pF对地电容3. SPI数据收发软件实现在非DMA模式下SPI数据传输需要手动管理。这里分享一个经过实战检验的收发函数uint16_t spi_transfer(uint16_t data) { // 等待发送缓冲区空 while(RESET spi_i2s_flag_get(SPI0, SPI_FLAG_TBE)); // 写入数据会同时触发接收 spi_i2s_data_transmit(SPI0, data); // 等待接收完成 while(RESET spi_i2s_flag_get(SPI0, SPI_FLAG_RBNE)); return spi_i2s_data_receive(SPI0); // 返回接收数据 }实际应用中的坑点连续传输时建议在函数开头先读取一次DR寄存器清空残留数据从设备响应慢时需要增加超时判断如下示例多字节传输时CS信号管理很关键改进版带超时的收发函数#define SPI_TIMEOUT 10000 int spi_transfer_safe(uint16_t tx_data, uint16_t *rx_data) { uint32_t timeout SPI_TIMEOUT; // 清空接收缓冲区 if(spi_i2s_flag_get(SPI0, SPI_FLAG_RBNE)) (void)spi_i2s_data_receive(SPI0); // 发送等待 while((RESET spi_i2s_flag_get(SPI0, SPI_FLAG_TBE)) (--timeout)); if(!timeout) return -1; spi_i2s_data_transmit(SPI0, tx_data); timeout SPI_TIMEOUT; while((RESET spi_i2s_flag_get(SPI0, SPI_FLAG_RBNE)) (--timeout)); if(!timeout) return -1; *rx_data spi_i2s_data_receive(SPI0); return 0; }多设备管理技巧 当系统中有多个SPI设备时建议采用如下架构为每个设备封装独立的驱动函数在函数内部统一管理CS信号使用互斥锁保护共享SPI总线示例代码void flash_write_enable(void) { spi_bus_lock(); // 获取总线锁 gpio_bit_reset(GPIOA, GPIO_PIN_4); // CS拉低 spi_transfer(0x06); // 发送写使能命令 gpio_bit_set(GPIOA, GPIO_PIN_4); // CS拉高 spi_bus_unlock(); // 释放总线锁 }4. 性能优化与故障排查软件优化手段使用查表法替代实时计算CRC如果启用CRC功能对频繁调用的SPI函数添加__inline修饰将SPI中断优先级设置为最高如果需要用中断实测对比数据优化方式传输1KB时间(us)提升幅度原始实现1250-内联函数11805.6%循环展开(8字节)102018.4%预取数据89028.8%常见故障排查表现象可能原因解决方法能发不能收MISO引脚配置错误检查GPIO输入模式设置偶尔数据错误时钟速率过高降低SPI时钟频率CS信号抖动软件CS控制时序问题在传输前后增加1us延时从设备无响应工作模式不匹配用逻辑分析仪确认CPOL/CPHA长时间运行后异常总线冲突检查多设备共享时的互斥逻辑硬件调试建议首先用万用表检查所有SPI线路的通断用示波器观察SCK、MOSI、CS信号波形如果怀疑信号完整性问题尝试降低时钟频率测试对于电平问题检查从设备的VCC电压是否匹配在最近的一个工业传感器项目中我们遇到SPI间歇性通信失败的问题。最终发现是电机干扰导致SCK信号出现振铃。通过在SCK线上增加22Ω电阻并缩短走线长度解决了问题。这也提醒我们SPI硬件设计不能只看逻辑正确还要考虑电磁兼容性。

更多文章