ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

GD32H759 RT-Thread SPI驱动开发:DMA配置与工控稳定性实战

GD32H759 RT-Thread SPI驱动开发:DMA配置与工控稳定性实战 1. 为什么在GD32H759上跑RT-Thread还要死磕SPI拿到GD32H759这块片子的时候我第一反应是外设资源真够猛的——Cortex-M7内核跑到480MHz带一堆定时器、CAN-FD、以太网MAC还有好几个SPI接口。但真正把它塞进工控板子跑起来才发现SPI这块儿要是不吃透后面接ADC、接编码器、接Flash存储处处都是坑。这篇东西是我在GD32H759 RT-Thread环境下折腾SPI的完整记录。核心想解决几个问题RT-Thread的SPI设备框架怎么跟GD32H7的硬件SPI对接、DMA怎么配才不丢数据、片选到底用硬件还是软件、工控场景下怎么保证长时间通信不翻车。适合已经能把RT-Thread跑起来、但一接SPI外设就各种玄学问题的兄弟参考。如果你还在纠结SPI时序图怎么看我也会用生活化的方式把它讲清楚保证看完能直接上手改代码。先说结论GD32H759的SPI外设本身不复杂难的是RT-Thread那套设备驱动模型跟它之间的适配层以及工控现场对稳定性的变态要求。下面我按实际调试顺序从框架设计一路讲到问题排查。2. 整体方案设计与RT-Thread SPI框架拆解2.1 为什么选RT-Thread的SPI设备框架而不是裸机裸机写SPI很简单初始化寄存器、拉片选、发数据、等标志位几十行搞定。但工控项目里SPI总线上往往挂好几个设备——一个W25Q64存参数、一个MT6816磁编码器读位置、可能还有个外部ADC采模拟量。裸机这么写下去每个设备的时序、片选、速率都得手动管代码很快就成一团乱麻。RT-Thread的SPI设备框架本质上是给你抽了一层“总线-设备”模型。SPI总线注册一次挂在上面的设备各自注册框架帮你管片选、管传输队列、管DMA。我选它的核心理由有三个一是多设备共存时片选不会打架二是DMA传输可以跟线程调度配合不阻塞其他任务三是换芯片平台时上层应用代码基本不用动。注意RT-Thread的SPI框架分两层底层是spi_ops里的configure和transfer上层是rt_spi_send、rt_spi_recv这些API。很多人只调上层API结果底层transfer没实现好DMA配了也不工作。2.2 GD32H759的SPI外设特点与选型考量GD32H759的SPI跟STM32H7系列很像支持全双工、半双工、 simplex模式FIFO深度16级支持TI模式和Motorola模式。工控场景我一般用Motorola模式CPOL和CPHA根据从机手册来定。速率方面SPI时钟从APB总线分频出来GD32H759的APB2能跑到120MHzSPI最高能到60MHz左右但实际工控板子上走线长了我一般压到10MHz到20MHz之间稳定优先。这里有个选型细节GD32H759有几个SPI接口SPI0和SPI1挂APB2SPI2挂APB1。如果同时要用多个SPI尽量把高速设备挂SPI0/SPI1低速的挂SPI2避免总线争抢。另外DMA请求映射要查手册每个SPI的TX和RX对应哪个DMA通道是固定的配错了DMA根本不触发。2.3 硬件片选与软件片选的取舍这个问题热词里也有人问我直接说我的做法工控场景优先用软件片选。原因很简单硬件片选虽然省CPU但片选时序跟数据之间的间隔是硬件固定的有些从机对片选建立时间和保持时间要求比较怪硬件片选调不了。软件片选就是普通GPIO拉低拉高之间可以插延时灵活得多。但软件片选有个坑RT-Thread的SPI框架默认会帮你管片选如果你在rt_spi_configure里指定了CS引脚框架会在传输前后自动拉。我一般把CS当普通GPIO自己控制在transfer函数外面手动拉这样时序完全可控。代价是每次传输多几条GPIO操作指令对480MHz的M7来说可以忽略。3. 核心细节解析与实操要点3.1 SPI模式与时序参数的确定方法SPI时序图看着晕其实就四个参数CPOL决定空闲时时钟是高还是低CPHA决定数据在第一个边沿还是第二个边沿采样。组合起来四种模式工控器件里Mode 0和Mode 3最常见。W25Q64用Mode 0MT6816用Mode 3这个必须查手册配错了读出来全是0xFF或者0x00。我实际调试时的做法是先拿逻辑分析仪抓一下从机手册里标称的时序确认CPOL和CPHA然后在RT-Thread的rt_spi_configure里设RT_SPI_MODE_0或RT_SPI_MODE_3。速率先设低比如1MHz通了再往上加。有一次我直接上20MHz结果MT6816读出来角度跳变降到5MHz就稳了后来查是板子上拉电阻太大导致边沿变缓。3.2 DMA配置的关键参数与计算过程DMA是SPI高速传输的命根子。GD32H759的DMA跟STM32类似有多个通道每个通道有优先级、数据宽度、地址增量这些参数。配SPI DMA时外设地址固定内存地址递增数据宽度一般设8位或16位跟SPI数据帧大小一致。传输长度怎么算比如我要读W25Q64的4KB数据SPI数据帧8位那DMA传输数量就是4096。但注意RT-Thread的SPI框架里transfer函数的length参数是消息个数每个消息可能包含多个字节。我一般把DMA配置成循环模式关掉传输完成中断里给信号量线程等信号量再继续。提示GD32H759的SPI DMA有个细节TX和RX要同时配即使你只发不收RX的DMA也得开着否则FIFO溢出会出错。我踩过这个坑只配TX DMA发了几百字节后SPI直接卡死。3.3 RT-Thread SPI设备注册与驱动对接RT-Thread里注册SPI总线用rt_spi_bus_register需要传一个struct rt_spi_bus和struct rt_spi_ops。ops里至少实现configure和transfer。configure里根据rt_spi_configuration结构体设置CPOL、CPHA、数据位宽、速率。transfer里根据rt_spi_message链表逐个处理每个message有send_buf、recv_buf、length、cs_take、cs_release这些字段。我的做法是在transfer里判断有没有DMA有DMA走DMA路径没DMA走查询路径。查询路径简单但占CPU适合小数据量。DMA路径复杂但效率高适合大数据块。两条路径都实现上层根据数据量自动选。4. 实操过程与核心环节实现4.1 环境搭建与基础工程配置先确保RT-Thread的BSP能跑起来串口能打印。然后打开SPI驱动框架在rtconfig.h里确认RT_USING_SPI和RT_USING_SPI_MSD如果用文件系统是开的。GD32H759的BSP里一般有现成的SPI驱动但可能只实现了查询模式DMA要自己加。我习惯先写个最小测试注册SPI总线配一个W25Q64设备读ID。ID读对了说明时序和片选没问题再往下做DMA。读ID的代码很简单发0x9F收3个字节对比0xEF4018之类的值。4.2 SPI总线注册与设备挂载代码实现总线注册的代码大概长这样static struct rt_spi_bus spi_bus0; static struct gd32_spi spi0; rt_err_t gd32_spi_configure(struct rt_spi_device *device, struct rt_spi_configuration *cfg) { struct gd32_spi *spi device-bus-parent.user_data; // 根据cfg-mode设置CPOL/CPHA // 根据cfg-max_hz计算分频系数 // 根据cfg-data_width设置数据帧大小 return RT_EOK; } rt_uint32_t gd32_spi_transfer(struct rt_spi_device *device, struct rt_spi_message *message) { // 遍历message链表 // 对每个message拉片选、发数据、收数据、释放片选 return length; } static struct rt_spi_ops gd32_spi_ops { .configure gd32_spi_configure, .transfer gd32_spi_transfer, }; int rt_hw_spi_init(void) { // 使能SPI时钟、GPIO时钟、DMA时钟 // 配置GPIO复用 // 注册总线 rt_spi_bus_register(spi_bus0, spi0, gd32_spi_ops); spi_bus0.parent.user_data spi0; return 0; }设备挂载用rt_spi_bus_attach_device传设备名、总线名、片选引脚。片选引脚如果是软件控制传RT_NULL自己在传输时拉GPIO。4.3 DMA传输通道配置与中断处理DMA配置我单独写了个函数在transfer里判断数据长度超过阈值比如32字节就走DMA。配置步骤使能DMA时钟、设置外设地址为SPI数据寄存器、设置内存地址为发送缓冲区、设置传输方向、设置数据宽度、设置优先级、使能传输完成中断。中断处理里给信号量线程等信号量。这里有个细节DMA传输完成中断里要清标志位还要关DMA通道否则下次传输会出错。我一般传输完成后把DMA通道disable下次传输前再enable。void DMA0_Channel0_IRQHandler(void) { if (dma_interrupt_flag_get(DMA0, DMA_CH0, DMA_INT_FLAG_FTF)) { dma_interrupt_flag_clear(DMA0, DMA_CH0, DMA_INT_FLAG_FTF); dma_channel_disable(DMA0, DMA_CH0); rt_sem_release(spi_dma_sem); } }4.4 实际读写W25Q64与MT6816的测试记录W25Q64测试读ID成功擦除扇区写数据读回来对比全对。速率从1MHz加到20MHz读4KB数据耗时从约32ms降到约1.6msDMA效果明显。MT6816测试这是个磁编码器SPI Mode 3读角度寄存器。一开始读出来数据跳变逻辑分析仪一看CS拉低到第一个时钟沿之间只有几十纳秒MT6816要求至少100ns。在CS拉低后加了rt_hw_us_delay(1)问题解决。速率最后定在5MHz再高数据就不稳。5. 常见问题与排查技巧实录5.1 SPI通信无响应的排查思路先查片选用万用表量CS引脚在传输时有没有拉低。再查时钟逻辑分析仪看SCK有没有波形。如果CS和SCK都正常但没数据查MISO有没有被从机驱动。有一次我忘了配MISO的GPIO复用一直读0xFF查了半天。还有个隐蔽问题RT-Thread的SPI框架里如果configure函数返回错误框架不会报错直接继续传输但参数没设进去。我习惯在configure里加打印确认参数真的设进去了。5.2 DMA传输数据错位的几种原因数据错位最常见的原因是DMA内存地址没递增或者数据宽度设错了。比如SPI数据帧8位DMA设成16位数据就错位了。还有DMA优先级太低被其他DMA打断也会错位。我一般把SPI DMA优先级设成高。另一个原因是FIFO阈值。GD32H759的SPI FIFO有阈值设置DMA请求在FIFO达到阈值时触发。如果阈值设得不对DMA传输的数据量会跟预期不符。我一般把TX FIFO阈值设成空RX FIFO阈值设成满这样DMA请求最及时。5.3 工控现场长距离SPI通信的稳定性处理工控现场SPI走线可能几十厘米甚至通过排线到另一个板子。这种场景下速率必须降我一般不超过5MHz。另外要加串联电阻一般22欧到33欧抑制反射。CS和SCK线上可以加小电容但别太大否则边沿变缓。还有个经验长距离SPI通信时软件片选比硬件片选稳。因为软件片选可以在CS拉低后延时等信号稳定了再发时钟。硬件片选没这个灵活性。5.4 常见问题速查表现象可能原因排查方法解决措施读数据全0xFFMISO未配置或从机未驱动量MISO引脚电平配置GPIO复用检查从机供电读数据全0x00时钟未输出或从机未响应逻辑分析仪看SCK检查SPI使能位检查片选数据错位DMA宽度或地址增量错误查DMA配置寄存器设对数据宽度内存地址递增高速时数据跳变信号完整性差示波器看边沿降速率加串联电阻DMA不触发DMA通道映射错误查手册DMA请求映射表改用正确的DMA通道传输卡死FIFO溢出或DMA未关查SPI状态寄存器同时配TX和RX DMA传输后关DMA提示逻辑分析仪是调SPI的必备工具没有它基本靠猜。我用的是一款几百块的国产逻辑分析仪抓SPI时序足够。6. 工控场景下的SPI性能优化与扩展思考6.1 多设备共存时的总线仲裁与片选管理一条SPI总线上挂多个设备时片选管理是核心。我的做法是每个设备独立CS传输时只拉对应设备的CS其他保持高。RT-Thread框架里如果每个设备注册时都指定自己的CS引脚框架会自动管。但我还是喜欢自己管因为有些设备需要特殊的片选时序。多设备共存还有个速率问题。不同设备支持的SPI速率不同比如W25Q64能到50MHzMT6816只能到10MHz。RT-Thread的configure是按设备调的所以每个设备可以设不同速率框架在切换设备时会重新配置SPI。这个机制很好用但要注意切换设备时的片选间隔别让上一个设备的CS还没拉高就配了下一个设备的参数。6.2 SPI与RT-Thread线程调度的配合工控项目里SPI传输往往在独立线程里做比如一个线程专门读编码器一个线程专门写Flash。RT-Thread的SPI框架在传输时会拿总线锁所以多线程同时访问SPI不会打架但会串行化。如果两个线程都频繁访问SPI要考虑优先级和传输量避免低优先级线程饿死。我一般把SPI传输放在中等优先级线程里传输时如果走DMA线程会挂起等信号量CPU让给其他线程。这样即使SPI传输量大也不影响高优先级任务。6.3 从查询模式到DMA模式的平滑迁移很多兄弟一开始用查询模式后来数据量大了想换DMA但改起来怕出问题。我的做法是保留两条路径在transfer里根据长度自动选。查询模式代码不动DMA模式新增。这样小数据量走查询大数据量走DMA迁移平滑风险低。迁移时注意DMA的缓冲区要对齐GD32H759的DMA对内存地址有对齐要求一般4字节对齐。我习惯用rt_malloc分配缓冲区然后手动对齐或者直接用静态数组加__attribute__((aligned(4)))。6.4 后续可以扩展的方向这套SPI框架跑通后可以往上叠更多东西。比如接SPI接口的ADC做多通道模拟量采集接SPI Flash做文件系统用RT-Thread的DFS接SPI屏幕做本地显示。每个扩展都是在这套框架上加设备底层不用动。还有个方向是SPI从机模式GD32H759支持SPI从机可以用在跟其他主控通信的场景。不过从机模式对时序要求更严调试起来更麻烦我目前还没在工控项目里用过。我在实际项目里踩过的最大坑是DMA和查询模式混用时忘了在DMA传输前关查询模式的中断结果两个路径同时操作SPI寄存器数据全乱。后来在transfer入口加了状态判断确保同一时间只有一个路径在跑。这个细节手册上不会写但实际调试中很容易遇到。
返回列表