GD32H759 RT-Thread SPI驱动实战:从ST7789到多设备工控应用
发布时间:2026/9/28 19:49:34来源:尧图网络
1. 从零搭建GD32H759的SPI驱动为什么我选择RT-Thread的设备框架拿到GD32H759这块片子的时候我第一反应是——这玩意儿性能真够猛的。Cortex-M7内核跑到480MHz配上RT-Thread这种成熟的RTOS做工业控制场景里的高速数据采集和显示刷新理论上完全够用。但实际动手把SPI跑通、跑稳中间踩的坑比我想象的多。这篇内容主要聊的是GD32H759在RT-Thread下驱动SPI外设的完整过程核心场景是驱动ST7789这款常见的TFT液晶屏。为什么选ST7789因为它便宜、资料多、SPI接口简单非常适合用来验证SPI链路的正确性。但SPI本身在工控场景里的用途远不止驱动屏幕——ADC采样、Flash存储、编码器读取、多机通信都离不开它。所以把这套SPI驱动框架搭好后面换任何SPI从设备都是改改参数的事。适合谁看如果你手上有GD32H7系列的板子正在用RT-Thread做项目或者你之前只用过STM32的HAL库点屏、读Flash想迁移到GD32平台那这篇内容能帮你省掉不少查手册和调试的时间。我会从SPI的硬件配置讲起到RT-Thread设备框架的注册、DMA的接入、片选的控制方式再到实际驱动ST7789时遇到的时序问题全部按我实际操作的顺序展开。先说结论GD32H759的SPI外设和STM32H7系列在寄存器层面有相似之处但细节差异不少尤其是时钟配置和DMA请求映射直接照搬STM32的代码大概率跑不起来。RT-Thread的SPI设备框架本身很成熟但GD32的BSP包需要自己补齐一些东西。下面我一步步拆。2. SPI在工控场景里的角色与GD32H759的硬件特性2.1 为什么工控项目里SPI出场率这么高做工业控制的人都知道板子上要挂的东西太多了——传感器、存储器、显示屏、隔离芯片每个都需要和主控通信。I2C速度慢、UART点对点、CAN适合长距离多节点而SPI的优势在于速度快、全双工、协议简单、几乎不占CPU。像ST7789这种屏幕刷一帧320x240的RGB565图像数据量是153600字节用SPI跑到50MHz理论上30ms左右能刷完实际加上命令开销大概40-50ms对于大多数工控HMI场景完全够用。GD32H759的SPI外设支持最高时钟频率是内核时钟的一半也就是240MHz。当然实际能不能跑到这个频率取决于你的PCB走线、从设备支持的最高速率、以及信号完整性。我实测在板级走线10cm以内、不加额外缓冲的情况下跑到60MHz是稳定的再往上就需要示波器看眼图了。2.2 GD32H759的SPI资源分布GD32H759一共有6个SPI外设其中SPI0、SPI1、SPI2、SPI3、SPI4、SPI5。注意GD32的命名和STM32不同STM32叫SPI1、SPI2GD32从SPI0开始编号。每个SPI支持全双工/半双工/单工模式主从模式可配8位或16位数据帧硬件CRC校验DMA请求TI模式SSI其中SPI0和SPI1的时钟源来自APB2SPI2到SPI5来自APB1。在RT-Thread的BSP里我们需要确认每个SPI的挂载总线和时钟使能位。GD32H759的时钟树比F4系列复杂不少APB1和APB2的预分频器配置直接影响SPI的输入时钟。我一般建议如果只是驱动屏幕或者Flash用SPI1或SPI2就够了把SPI0留给需要最高速率的场景。因为SPI0在APB2上通常APB2的频率更高。2.3 RT-Thread的SPI设备框架长什么样RT-Thread把SPI抽象成了四层结构SPI总线设备对应一个物理SPI控制器比如spi0、spi1SPI从设备挂在总线上的具体设备比如spi10表示SPI1上的设备0SPI消息一次传输的抽象包含发送缓冲区、接收缓冲区、长度SPI配置模式、速率、位宽等参数这种分层的好处是你的应用代码只操作从设备换总线或者换片选引脚时上层逻辑不用大改。但代价是初始化流程比裸机多几步第一次用容易搞混。3. 硬件设计与SPI模式选型的关键决策3.1 引脚分配与片选方式的选择GD32H759的SPI引脚是复用的需要先配置GPIO的复用功能。以SPI1为例常用引脚组合是信号引脚复用编号说明SCKPB13AF5时钟MISOPB14AF5主入从出MOSIPB15AF5主出从入CSPB12软件控制片选这里有个关键决策用硬件片选还是软件片选硬件片选是SPI外设自动控制的传输开始拉低结束拉高。优点是时序精确不占CPU。缺点是片选引脚固定不能随便换而且多从设备时管理麻烦。软件片选是自己用GPIO控制传输前拉低传输后拉高。优点是灵活任何GPIO都行多从设备时逻辑清晰。缺点是需要手动管理如果忘了拉高从设备会一直以为在通信。我在工控项目里一律用软件片选。原因很简单工业现场经常需要多个SPI从设备共享总线硬件片选的数量有限而且调试时用逻辑分析仪抓波形软件片选的状态一目了然。ST7789的CS引脚我就接在PD3上用GPIO控制。注意用软件片选时CS拉低和第一个SCK边沿之间要留至少一个时钟周期的建立时间。ST7789要求CS建立时间最小10nsGD32的GPIO翻转速度在100MHz以上这个条件很容易满足但如果你在CS和SCK之间加了RC滤波就要重新算。3.2 SPI模式的选择ST7789为什么用Mode 3SPI有四种模式由CPOL时钟极性和CPHA时钟相位组合决定模式CPOLCPHA采样边沿Mode 000第一个边沿上升Mode 101第二个边沿下降Mode 210第一个边沿下降Mode 311第二个边沿上升ST7789的数据手册明确写了支持Mode 0和Mode 3。我选Mode 3也就是CPOL1、CPHA1。为什么因为在Mode 3下SCK空闲时是高电平第一个边沿是下降沿数据在下降沿改变在上升沿采样。这种模式下从设备有更充裕的建立时间尤其在高速率下信号完整性更好。实际配置时RT-Thread的SPI配置结构体里mode字段填RT_SPI_MODE_3它等价于RT_SPI_CPOL | RT_SPI_CPHA。3.3 时钟频率的计算与实测GD32H759的SPI时钟来自APB总线经过一个预分频器。假设SPI1挂在APB2上APB2时钟配置为240MHzSPI的预分频系数可以选择2、4、8、16、32、64、128、256。我最初想跑60MHz计算方式SPI时钟 APB2时钟 / 预分频系数 60MHz 240MHz / 4所以预分频系数选4。但实际配置后发现屏幕花屏用示波器看SCK波形发现上升沿有明显的过冲和振铃。后来降到30MHz预分频系数8波形干净了屏幕也正常了。这里有个经验GD32H7的SPI输出驱动能力比STM32H7弱一些如果你的走线比较长或者没有阻抗匹配不要一上来就拉满。先用低速跑通再逐步提高同时用示波器观察波形。4. RT-Thread下SPI驱动的完整实现过程4.1 在BSP中使能SPI和DMART-Thread的GD32 BSP通常放在bsp/gd32/gd32h759-xxx目录下。首先要在rtconfig.h里打开SPI和DMA的宏#define RT_USING_SPI #define BSP_USING_SPI1 #define BSP_USING_SPI1_DMA #define BSP_SPI1_TX_DMA_CHANNEL 0 #define BSP_SPI1_RX_DMA_CHANNEL 1GD32H759的DMA请求映射和STM32不同SPI1的TX和RX分别对应DMA0的通道0和通道1。这个映射关系在GD32H7的用户手册里有表格一定要查清楚填错了DMA不会报错但数据就是不动。然后在board.c的rt_hw_board_init里确保SPI1的时钟已经使能rcu_periph_clock_enable(RCU_SPI1); rcu_periph_clock_enable(RCU_DMA0);4.2 注册SPI总线设备RT-Thread的SPI驱动注册分两步先注册总线再注册从设备。总线注册的代码在drv_spi.c里核心是填充struct rt_spi_bus和struct gd32_spi结构体。GD32的SPI寄存器操作和STM32类似但寄存器名字不同比如STM32的SPI_CR1在GD32里叫SPI_CTL0。static const struct gd32_spi_config spi_config[] { { .spi_periph SPI1, .clock RCU_SPI1, .dma_tx DMA0, .dma_tx_ch 0, .dma_rx DMA0, .dma_rx_ch 1, }, };注册函数rt_hw_spi_init();这一步完成后系统里会出现spi1这个总线设备用list_device命令能看到。4.3 挂载ST7789从设备从设备的注册需要指定总线名字、片选引脚、配置参数struct rt_spi_device *spi_dev; spi_dev rt_spi_device_create(spi10, spi1, RT_NULL); rt_spi_configure(spi_dev, cfg);其中cfg是struct rt_spi_configurationstruct rt_spi_configuration cfg; cfg.data_width 8; cfg.mode RT_SPI_MODE_3 | RT_SPI_MSB; cfg.max_hz 30000000;片选引脚用GPIO控制在传输前后手动拉低拉高rt_pin_mode(PD3, PIN_MODE_OUTPUT); rt_pin_write(PD3, PIN_HIGH); rt_pin_write(PD3, PIN_LOW); rt_spi_transfer(spi_dev, cmd, RT_NULL, 1); rt_pin_write(PD3, PIN_HIGH);注意rt_spi_transfer是阻塞式的传输完成才返回。如果你在中断里调用要用rt_spi_transfer_message的非阻塞版本或者干脆用DMA信号量。4.4 DMA的接入与传输完成回调ST7789刷屏时数据量大用DMA能解放CPU。RT-Thread的SPI框架支持DMA但需要驱动层实现spi_transfer的回调。GD32的DMA配置步骤使能DMA时钟配置DMA通道的源地址、目的地址、传输长度配置传输方向、数据宽度、优先级使能DMA通道使能SPI的DMA请求位关键代码dma_deinit(DMA0, DMA_CH0); dma_single_data_mode_init(DMA0, DMA_CH0, dma_init_struct); dma_channel_subperipheral_select(DMA0, DMA_CH0, DMA_SUBPERI0); dma_channel_enable(DMA0, DMA_CH0); spi_dma_enable(SPI1, SPI_DMA_TRANSMIT);传输完成后DMA产生中断在中断里释放信号量通知应用层可以发下一包数据。我实测下来用DMA刷320x240的屏幕CPU占用率从纯轮询的70%降到5%以下效果非常明显。5. ST7789驱动适配中的时序陷阱与调试记录5.1 初始化序列的延时要求ST7789的上电初始化序列里有几条命令后面必须跟延时比如SLPOUT0x11后面要等120msDISPON0x29后面要等20ms。这些延时不能用rt_thread_delay因为初始化通常在系统启动阶段调度器还没跑起来。我用的是rt_hw_us_delay基于DWT计数器的微秒级延时。static void st7789_write_cmd(uint8_t cmd) { rt_pin_write(PD3, PIN_LOW); rt_pin_write(PD4, PIN_LOW); // DC0 表示命令 rt_spi_transfer(spi_dev, cmd, RT_NULL, 1); rt_pin_write(PD3, PIN_HIGH); } static void st7789_write_data(uint8_t data) { rt_pin_write(PD3, PIN_LOW); rt_pin_write(PD4, PIN_HIGH); // DC1 表示数据 rt_spi_transfer(spi_dev, data, RT_NULL, 1); rt_pin_write(PD3, PIN_HIGH); }5.2 花屏问题的排查过程第一次点亮屏幕时显示的是随机噪点。排查步骤检查SPI波形用逻辑分析仪抓SCK、MOSI、CS发现数据位是对的但CS在传输结束后没有立即拉高多了一个SCK周期。原因是rt_spi_transfer返回后SPI的移位寄存器可能还没空需要等SPI_STAT的TRANS位清零。检查模式配置确认CPOL1、CPHA1和ST7789手册一致。检查复位时序ST7789的RES引脚需要至少10us的低电平复位我最初只给了1us导致内部寄存器没复位干净。检查电源3.3V供电正常但背光升压电路有问题导致屏幕亮度异常误以为是数据问题。最终解决在CS拉高前加一个while(spi_i2s_flag_get(SPI1, SPI_FLAG_TRANS) SET);确保传输完成。复位延时改成100us。花屏消失。5.3 刷屏速度的优化初始版本用单字节传输刷一屏要200ms以上。优化措施改用DMA传输一次发一整行数据把st7789_write_data改成批量写减少CS翻转次数提高SPI时钟到30MHz优化后刷一屏约45ms对于工控HMI的刷新率要求通常10-20fps完全够用。优化阶段传输方式SPI时钟刷屏时间初始单字节轮询10MHz220ms中期批量轮询30MHz80ms最终DMA批量30MHz45ms6. 常见问题速查与避坑经验6.1 SPI通信失败的排查清单现象可能原因排查方法无波形时钟未使能查RCU寄存器波形但数据错模式不对对比CPOL/CPHA数据偶尔错时序余量不足降速测试DMA不传输请求映射错查DMA通道表CS不拉高传输未完成查TRANS标志6.2 我踩过的三个坑第一个坑GD32的SPI使能顺序。GD32要求先配置CTL0寄存器再使能SPIEN位。如果先使能再配置参数不生效。STM32的HAL库帮你处理了这个顺序但RT-Thread的GD32驱动里需要自己注意。第二个坑DMA传输长度限制。GD32H7的DMA单次传输长度最大65535刷一屏153600字节需要分3次。我在驱动里做了自动分包应用层不用管。第三个坑RT-Thread的SPI从设备名冲突。如果你注册了两个从设备都叫spi10系统不会报错但第二个会覆盖第一个。命名规则是spi 总线号 设备号比如spi10、spi11。6.3 关于SPI硬件片选和软件片选的补充有朋友问过硬件片选能不能用能用但有限制。GD32的硬件片选引脚是固定的SPI1的NSS是PB12SPI2的NSS是PB12复用冲突。如果你多个SPI同时用硬件片选引脚会打架。所以我还是推荐软件片选灵活且可控。提示软件片选时如果从设备多建议用74HC138这类译码器扩展片选节省GPIO。7. 从ST7789延伸到其他SPI设备的移植思路这套SPI驱动框架搭好后换其他SPI设备就是改配置的事。比如换成W25Q64 Flash模式改成Mode 0速率可以拉到60MHz命令格式不同但rt_spi_transfer的调用方式一样换成MT6816磁编码器模式Mode 3速率10MHz需要定时读取可以放在定时器线程里核心思想是把SPI总线配置和从设备配置分离。总线配置在BSP里固定从设备配置在应用层按需修改。这样一套驱动能覆盖大部分工控场景。我在实际项目里一块GD32H759板子上同时挂了ST7789屏幕、W25Q64 Flash、MT6816编码器三个设备共享SPI1总线用不同的CS引脚区分。RT-Thread的SPI框架天然支持这种多从设备场景只要注册三个从设备各自配置好参数就行。唯一需要注意的是多从设备共享总线时每次传输前要确保其他从设备的CS是高电平否则会互相干扰。最后分享一个小技巧调试SPI时逻辑分析仪比示波器好用。示波器只能看两路信号逻辑分析仪能同时抓SCK、MOSI、MISO、CS四路还能直接解码SPI协议省去手动数位的麻烦。我用的是一款8通道的USB逻辑分析仪配合开源软件抓SPI时序非常方便。
网站建设高端定制企业官网