ARTICLE DETAIL

资讯详情

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

GD32H759 上 RT-Thread SPI 驱动实战:DMA、片选与时序避坑指南

GD32H759 上 RT-Thread SPI 驱动实战:DMA、片选与时序避坑指南 1. 为什么在 GD32H759 上跑 RT-Thread 还要单独聊 SPIGD32H759 这颗片子最近在工控圈子里热度不低Cortex-M7 内核、主频拉到 600MHz、带 TCM 和 Cache、外设资源也够猛很多人拿它替换原来 STM32H7 的方案。但真到项目里跑起来你会发现一个很现实的问题CPU 再快数据进不来出不去也是白搭。而 SPI 恰恰是工控板子上最忙的那条总线——外挂 NOR Flash 存参数、接 ADC 采模拟量、连编码器读位置、驱动以太网控制器甚至跟 FPGA 做高速数据交换全靠它。我在一个多轴运动控制项目里用 GD32H759 RT-Thread 做主轴板子上挂了 W25Q64 存配方、MT6816 磁编码器读电机角度、还有一片 ADC 做电流环采样三条 SPI 链路同时跑。刚开始图省事全用 RT-Thread 的rt_spi_transfer同步接口结果电流环 10kHz 的采样节拍直接被 SPI 阻塞拖垮电机一加速就丢步。后来把 DMA、片选、时钟相位这些细节一个个抠出来才把总线吞吐和实时性同时稳住。这篇就围绕GD32H759 平台下 RT-Thread 的 SPI 驱动实战展开从框架分层、设备注册、DMA 配置到片选时序、常见故障排查把工控场景里真正会踩的坑讲透。适合已经能点亮 RT-Thread、但一上真实外设就各种玄学问题的嵌入式工程师也适合从 STM32 转 GD32 想快速摸清差异的朋友。看完你至少能搞清楚SPI 设备怎么挂到 RT-Thread 设备框架上、DMA 收发为什么容易卡死、硬件片选和软件片选到底怎么选、以及示波器上那些看起来对但就是不通的波形到底错在哪。2. GD32H759 的 SPI 外设与 RT-Thread 驱动框架怎么对上2.1 先搞清楚 GD32H759 的 SPI 硬件底子GD32H759 的 SPI 外设跟 STM32H7 系列思路接近但寄存器细节有差异工控项目里常用的几个特性得先摸清SPI0/SPI1/SPI2/SPI3/SPI4/SPI5 多路其中 SPI0/SPI1 挂在 APB2SPI2~SPI5 挂在 APB1时钟源不同分频系数算出来差别很大。FIFO 深度 16 级支持 8/16 位数据帧工控里读编码器常用 16 位帧一次搞定。硬件 NSS 管理可以配置成输出模式自动拉片选也可以软件控制。DMA 请求映射每个 SPI 都有独立的 TX/RX DMA 通道GD32H7 的 DMAMUX 比老 GD32F4 灵活得多。最高时钟SPI0 在 APB2 上理论能跑到 100MHz 以上但实际外设比如 W25Q64撑死 80MHz得按从设备手册降频。这里有个容易翻车的点GD32H759 开了 Cache 和 TCM 之后DMA 访问的内存区域必须是非 Cache 或者手动维护一致性的。我一开始把 SPI DMA 的收发 buffer 放在普通 SRAM 里CPU 写进去的数据 DMA 读出来是旧的读回来的数据 CPU 又看不到调了半天以为是 SPI 时序问题其实是 Cache 没处理。后来把 buffer 放到RT_NONCACHEABLE段或者用SCB_CleanDCache_by_Addr手动刷问题立刻消失。2.2 RT-Thread 的 SPI 设备框架分了几层RT-Thread 的 SPI 框架是典型的总线-设备模型理解分层是写好驱动的第一步层级作用典型接口应用层业务代码调用rt_spi_send、rt_spi_recv、rt_spi_transfer设备层抽象 SPI 从设备rt_spi_device、rt_spi_configure总线层管理 SPI 控制器rt_spi_bus、rt_spi_bus_attach_device驱动层操作寄存器/DMAstruct rt_spi_ops里的configure、xfer关键点在于总线bus和从设备device是分开注册的。总线对应 GD32H759 的一个 SPI 外设从设备对应挂在它上面的 W25Q64、MT6816 这些具体芯片。一个 bus 上可以挂多个 device各自有不同的 CS 引脚、时钟频率、SPI 模式。我见过不少新手直接把 SPI 初始化写在应用里绕过设备框架结果多个任务同时访问同一条 SPI 就打架。用框架的好处是 RT-Thread 帮你做了总线互斥rt_spi_transfer内部会拿总线锁多任务访问自动串行化。2.3 总线注册与从设备挂载的完整流程在 GD32H759 上标准流程分三步注册 SPI 总线调用rt_hw_spi_init()内部会填充rt_spi_bus结构体绑定ops然后rt_spi_bus_register()。挂载从设备用rt_spi_bus_attach_device()或rt_spi_bus_attach_device_cspin()后者能直接指定 CS 引脚框架自动管理片选。配置设备参数rt_spi_configure()设置模式CPOL/CPHA、数据位宽、时钟频率。这里有个细节值得说rt_spi_bus_attach_device_cspin是带片选管理的版本它会注册一个 PIN 设备作为 CS每次传输前自动拉低、传输后拉高。如果你的板子 CS 接的是普通 GPIO用这个接口最省心。但如果 CS 接的是 SPI 外设自带的硬件 NSS 引脚就得用不带 cspin 的版本然后在configure里打开硬件 NSS。3. SPI 模式、片选与时序工控现场最容易翻车的三件事3.1 SPI 四种模式到底怎么选SPI 的 CPOL时钟极性和 CPHA时钟相位组合出四种模式这是新手第一个大坑。工控外设里W25Q64 Flash模式 0CPOL0, CPHA0空闲低电平第一个边沿采样。MT6816 磁编码器模式 3CPOL1, CPHA1空闲高电平第二个边沿采样。多数 ADC如 AD7606模式 2 或模式 0看具体型号。选错模式的典型症状是读回来的数据整体偏移一位或者全是 0xFF/0x00。因为采样边沿错了MOSI 上的数据在错误的时刻被锁存。我调 MT6816 的时候就栽过手册上写数据在时钟下降沿更新上升沿采样我按模式 0 配读出来角度值一直在跳。后来拿示波器一看MT6816 空闲时 SCLK 是高电平必须配模式 3。改完立刻稳定。提示拿不准模式时先看从设备手册的时序图重点看 SCLK 空闲电平和第一个采样边沿。空闲高电平就是 CPOL1第一个边沿采样就是 CPHA0。3.2 硬件片选和软件片选的真实差异这是工控项目里争论最多的话题之一。两种方式各有适用场景对比项硬件片选NSS 自动软件片选GPIO 手动时序精度高硬件自动拉依赖代码有抖动多从设备需要多个 NSS 或外部译码每个设备一个 GPIO灵活CPU 开销低每次传输要操作 GPIO调试难度波形干净容易忘记拉高导致总线冲突适用场景单从设备、高速传输多从设备、CS 时序有特殊要求工控板子上通常挂多个 SPI 从设备软件片选更常见。但软件片选有个致命细节CS 拉低到第一个 SCLK 之间要有建立时间tSU最后一个 SCLK 到 CS 拉高之间要有保持时间tHD。W25Q64 要求 tSU 至少 5ns看着很小但如果你的 GPIO 操作和 SPI 传输之间没有延时某些情况下会踩线。我的做法是在rt_spi_bus_attach_device_cspin的基础上如果从设备时序苛刻就在xfer回调里手动加几个 NOP 或者用rt_hw_us_delay(1)保证建立时间。实测 W25Q64 在 80MHz 下不加延时也能跑但 MT6816 在 10MHz 下必须加否则偶尔读到错误角度。3.3 时钟频率怎么算才不超从设备上限GD32H759 的 SPI 时钟来自 APB 分频公式是SPI_CLK APB_CLK / (2^(PSC1))其中 PSC 是预分频系数0~7。假设 SPI0 挂在 APB2APB2 时钟 200MHz要得到 10MHz 的 SCLK10MHz 200MHz / (2^(PSC1)) 2^(PSC1) 20 PSC1 ≈ 4.32取整后 PSC3实际 SCLK 200/16 12.5MHz。如果从设备上限是 10MHz就得取 PSC4SCLK 200/32 6.25MHz。工控项目里宁可降频也不要超频。SPI 超频的后果不是立刻坏而是偶发误码在电磁干扰大的现场变频器、伺服驱动器旁边尤其明显。我一般会把计算出来的频率再降一档留 20% 余量。4. DMA 收发配置让 SPI 不再阻塞你的实时任务4.1 为什么工控场景必须上 DMA回到开头那个电流环的例子。10kHz 采样意味着每 100us 要完成一次 SPI 传输。如果一次传输 16 位数据SPI 时钟 10MHz传输时间约 1.6us看着不长。但 RT-Thread 的同步rt_spi_transfer是阻塞的加上任务切换、总线加锁的开销实际占用可能到 5~10us。如果电流环任务周期是 100usSPI 就吃掉了 10% 的 CPU 时间再加上其他任务调度就开始抖。上 DMA 之后CPU 只需要配置 DMA 描述符、启动传输然后就可以去干别的。传输完成通过中断或信号量通知。CPU 占用从 10% 降到接近 0实时性立刻改善。4.2 GD32H759 的 DMA 配置要点GD32H759 用的是 DMAMUX DMA 控制器架构配置步骤比老款复杂使能 DMA 控制器时钟rcu_periph_clock_enable(RCU_DMA0)。配置 DMAMUX 请求源把 SPI 的 TX/RX 请求映射到具体 DMA 通道。配置 DMA 通道方向、数据宽度、地址增量、优先级、传输数量。使能 SPI 的 DMA 请求spi_dma_enable(SPI0, SPI_DMA_TRANSMIT)。启动传输dma_channel_enable()。关键坑点GD32H759 的 DMA 访问 TCM 内存有限制。TCM紧耦合内存虽然快但 DMA 不一定能访问。我一开始把 buffer 放在 TCM 里DMA 死活不工作查手册才发现 TCM 不在 DMA 可访问地址范围内。后来改放到 AXI SRAM 的非 Cache 段问题解决。4.3 RT-Thread 下 DMA 与设备框架的整合RT-Thread 的 SPI 框架本身不强制用 DMA但rt_spi_ops里的xfer回调可以自己实现 DMA 版本。我的做法是static rt_ssize_t gd32_spi_xfer(struct rt_spi_device *device, struct rt_spi_message *message) { struct gd32_spi *spi (struct gd32_spi *)device-bus-parent.user_data; /* 配置片选 */ if (message-cs_take) rt_pin_write(device-cs_pin, PIN_LOW); /* 判断是否用 DMA */ if (message-length DMA_THRESHOLD) { gd32_spi_dma_transfer(spi, message); } else { gd32_spi_poll_transfer(spi, message); } if (message-cs_release) rt_pin_write(device-cs_pin, PIN_HIGH); return message-length; }这里设了个DMA_THRESHOLD短数据传输用轮询更快省去 DMA 配置开销长数据才走 DMA。工控里读 Flash 一页 256 字节走 DMA读编码器 2 字节走轮询实测效率最高。注意DMA 传输完成后必须等SPI_FLAG_TRANS和SPI_FLAG_RX_BUFFER_NOT_EMPTY都清零再拉高 CS。我见过有人 DMA 中断一触发就拉 CS结果最后一个字节还在移位寄存器里没发完从设备收到半个字节。5. 从 W25Q64 到 MT6816三个真实外设的接入实录5.1 W25Q64 Flash 的读写与 DMA 优化W25Q64 是工控板上的常客存配方、日志、固件备份。接入步骤挂载设备rt_spi_bus_attach_device_cspin(flash_dev, spi10, spi0, CS_PIN)。配置模式 0、8 位、时钟 40MHzW25Q64 上限 80MHz留余量。读 ID 验证发 0x9F读回 0xEF4017。读数据用0x03命令页编程用0x02扇区擦除用0x20。DMA 优化点在读大块数据一次读 4KB 固件轮询要几毫秒DMA 只要几百微秒。踩过的坑W25Q64 写之前必须先擦除而且擦除是按扇区4KB的。我有次直接写没擦读回来全是旧数据以为是 SPI 问题查了半天才发现是 Flash 特性。5.2 MT6816 磁编码器的角度读取MT6816 是 14 位磁编码器SPI 模式 3读角度命令是 0x830016 位。关键点必须用 16 位数据帧8 位帧读出来是错的。CS 拉低后要等至少 1us 再发时钟MT6816 需要准备时间。读回的数据高 2 位是状态位低 14 位才是角度。uint16_t mt6816_read_angle(void) { uint16_t cmd 0x8300; uint16_t resp; rt_spi_transfer(mt6816_dev, cmd, resp, 1); return resp 0x3FFF; /* 取低 14 位 */ }实测在 10MHz 下角度更新率能到 100kHz 以上足够伺服环用。5.3 多从设备共存的总线仲裁三条 SPI 链路挂同一条总线时片选管理是核心。RT-Thread 的总线锁保证同一时刻只有一个设备在传输但前提是每个设备都正确配置了 CS。如果某个设备的 CS 忘记拉高其他设备传输时它会误响应导致数据错乱。我的经验是每个从设备的 CS 引脚单独定义宏初始化时统一检查一遍。另外如果两个设备时钟频率差很多比如 Flash 40MHz、编码器 10MHz每次切换设备都要重新rt_spi_configure否则编码器会被 40MHz 时钟打挂。6. 常见问题速查与排查技巧6.1 SPI 通信故障排查表现象可能原因排查方法读回全 0xFFMISO 悬空或从设备未响应查 CS 是否拉低、从设备供电读回全 0x00MOSI 没数据或时钟没输出示波器看 SCLK、MOSI数据偏移一位SPI 模式错误核对 CPOL/CPHA偶发误码时钟超频或干扰降频、加屏蔽、缩短走线DMA 不工作buffer 在 TCM 或 Cache 未刷换非 Cache 段、手动刷 Cache多设备冲突CS 未正确管理检查每个设备 CS 时序6.2 示波器抓波形的三个关键点调 SPI 离不开示波器重点看三处CS 和第一个 SCLK 的间隔小于从设备 tSU 就会丢第一个位。SCLK 空闲电平确认 CPOL 配置对不对。MISO 采样点数据是否在正确的边沿稳定。我一般用四通道示波器同时抓 CS、SCLK、MOSI、MISO一眼就能看出问题在哪。6.3 几个血泪教训不要在中断里调用阻塞式 SPI 传输会拖垮整个系统。用 DMA 信号量。SPI 总线加锁后不要做耗时操作否则其他任务全卡住。从设备手册的时序参数一定要看tSU、tHD、最大时钟这些不是摆设。GD32H759 的 Cache 和 DMA 是天生一对冤家buffer 位置一定要规划好。7. 我在 GD32H759 RT-Thread SPI 实战中的几点体会调 SPI 这件事说到底就是时序、片选、内存三件事。时序对了数据才能正确移位片选对了多设备才能和平共处内存对了DMA 才能跑得欢。GD32H759 性能强但 Cache 和 TCM 带来的内存一致性问题是它跟老款 GD32F4 最大的差异点也是从 STM32 转过来的朋友最容易忽略的地方。我现在做新项目SPI 部分的标准动作是先拿示波器确认波形再上 RT-Thread 设备框架最后根据数据量决定轮询还是 DMA。这套流程走下来基本不会出大问题。如果非要说一个最容易被低估的点那就是片选时序——它不像时钟频率那样显眼但现场偶发故障十有八九跟它有关。
返回列表