ARTICLE DETAIL

资讯详情

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

GD32H759+RT-Thread驱动ST7789:SPI+DMA刷屏实战与避坑指南

GD32H759+RT-Thread驱动ST7789:SPI+DMA刷屏实战与避坑指南 1. 从点灯到刷屏为什么SPI是工控HMI绕不开的一道坎做GD32H759加RT-Thread这套组合的工控项目走到第6篇基本都会碰到同一个需求把采集到的数据、设备状态、报警信息实时显示到一块彩屏上。这时候SPI就绕不开了。我接触过的工控HMI方案里SPI接口的TFT屏占了绝大多数尤其是ST7789这类驱动芯片便宜、资料多、驱动成熟240x320或者240x240的分辨率做设备面板绰绰有余。但SPI这东西有个特点协议本身简单到十分钟就能讲完可一旦落到具体芯片、具体RT-Thread版本、具体DMA配置上坑就一个接一个。我见过太多人卡在屏幕不亮花屏刷一半卡死这几个问题上最后归结为SPI有问题其实是时序、片选、DMA、RT-Thread设备框架这几层里某一层没对齐。这篇就围绕GD32H759这颗高性能MCU在RT-Thread下把SPI驱动ST7789这条链路彻底讲透。包括SPI外设初始化、RT-Thread的SPI设备框架怎么用、DMA怎么配、片选硬件和软件怎么选、ST7789的初始化序列怎么写、刷屏性能怎么优化以及我在实际调试中踩过的那些坑。适合已经跑通RT-Thread基础工程、准备上屏的工控开发者也适合想搞清楚RT-Thread SPI框架底层逻辑的人。先给个结论GD32H759的SPI外设性能很强配合DMA刷ST7789全屏刷新做到几十毫秒级别完全没问题关键是把配置和框架用对。2. GD32H759的SPI外设到底强在哪配置前必须搞清楚的事2.1 先看硬件资源SPI数量、时钟、引脚复用GD32H759属于GD32H7系列主频能跑到600MHzSPI外设给得很大方多个SPI/I2S接口支持全双工、半双工、单线双向最高时钟能到很高。具体到SPI它支持Motorola标准的四线模式也支持TI的SSI模式还有硬件CRC、硬件片选管理这些高级功能。配置之前第一件事是查数据手册确认三件事这个SPI挂在哪条总线上、时钟源是谁、引脚怎么复用。GD32H7的SPI一般挂在APB总线上时钟源可能来自PLL的某个分频。我习惯先把时钟树理清楚因为SPI的波特率分频是基于外设时钟算的时钟搞错了波特率就不对屏幕要么不亮要么花。引脚复用这块GD32H7的GPIO复用功能比F1系列复杂同一个SPI的SCK、MISO、MOSI、NSS可能分布在不同的GPIO端口上需要查数据手册的复用表。我的做法是先在原理图上确认屏幕接的是哪组引脚然后反查复用表把AF编号记下来。2.2 SPI模式选择ST7789要的是Mode 0还是Mode 3SPI有四种模式由CPOL时钟极性和CPHA时钟相位组合决定。ST7789的数据手册写得很清楚它支持Mode 0和Mode 3。这两个模式的区别在于空闲时SCK是高还是低以及数据在哪个边沿采样。Mode 0CPOL0CPHA0空闲低电平第一个边沿上升沿采样。 Mode 3CPOL1CPHA1空闲高电平第二个边沿上升沿采样。实际用下来ST7789用Mode 0最稳绝大多数例程也是Mode 0。但这里有个坑有些屏幕模组因为PCB走线或者电平转换芯片的原因Mode 0下会出现数据错位换成Mode 3反而正常。所以如果Mode 0花屏别急着怀疑代码先试试Mode 3。配置的时候CPOL和CPHA这两个位在SPI的CTL0寄存器里。GD32的固件库或者RT-Thread的驱动里都有对应的宏直接设就行。2.3 波特率分频不是越快越好SPI时钟频率直接决定刷屏速度但不是越高越好。ST7789的SPI时钟上限一般在几十MHz具体看模组。GD32H759的SPI外设时钟很高分频系数要算准。假设SPI外设时钟是100MHz分频系数选8得到12.5MHz的SCK。这个频率刷240x320的屏全屏刷新大概在几十毫秒。如果分频系数选2SCK到50MHz可能超过ST7789的上限出现花屏或者不响应。我的经验是先用一个保守的频率比如10MHz把屏幕点亮确认驱动逻辑没问题再逐步提高频率测试稳定性。工控场景下稳定性比极限速度重要得多。注意GD32H7的SPI分频系数是2的幂次最小分频是2不是任意整数。算波特率的时候别按线性分频算。2.4 硬件片选和软件片选工控场景怎么选SPI的NSS片选有两种方式硬件片选和软件片选。硬件片选是SPI外设自动控制NSS引脚传输开始拉低结束拉高。优点是时序精准不占CPU。缺点是NSS引脚固定而且多从机场景下管理麻烦。软件片选是用普通GPIO手动控制片选引脚传输前拉低传输后拉高。优点是灵活任意GPIO都能当片选多从机也好管理。缺点是时序靠软件保证如果中间被中断打断片选可能提前拉高导致传输错误。工控场景下我推荐软件片选原因有三个一是屏幕的片选引脚往往和别的功能复用硬件片选不一定能对上二是RT-Thread的SPI框架对软件片选支持更好三是调试方便出问题容易定位。但软件片选有个必须注意的点片选拉低到第一个时钟之间要有足够的建立时间片选拉高到下一个操作之间要有保持时间。ST7789对这两个时间要求不严但有些SPI从机要求很苛刻。我的做法是在片选操作后加几个NOP或者微秒级延时确保时序余量。3. RT-Thread的SPI设备框架别绕过它自己写寄存器3.1 为什么建议用RT-Thread的SPI框架而不是直接操作寄存器很多人从裸机转RT-Thread习惯直接操作SPI寄存器觉得这样可控。但在RT-Thread下绕过设备框架直接操作寄存器会带来几个问题一是和RT-Thread的设备管理冲突二是DMA和中断的处理要自己写三是多线程访问时没有互斥保护。RT-Thread的SPI设备框架把SPI抽象成标准设备提供rt_spi_transfer、rt_spi_send、rt_spi_recv这些API底层自动处理片选、DMA、中断。用框架的好处是代码可移植换芯片只需要改驱动上层应用不动。GD32H759的SPI驱动在RT-Thread的BSP里一般已经有了需要确认的是驱动是否支持DMA、是否支持软件片选配置。如果没有可能需要自己补。3.2 SPI设备注册和查找的完整流程RT-Thread的SPI设备注册分两步硬件SPI外设注册和SPI从设备挂载。硬件SPI注册是在BSP的驱动初始化里完成的一般用rt_hw_spi_init或者类似的函数把SPI外设注册成rt_spi_device。注册的时候要指定SPI总线名称比如spi1。从设备挂载是用rt_spi_bus_attach_device把一个SPI从设备挂到总线上。这个函数需要传片选引脚、SPI模式、数据位宽、波特率这些参数。挂载成功后用rt_device_find找到设备句柄就可以用rt_spi_transfer收发数据了。这里有个容易忽略的点rt_spi_bus_attach_device的片选引脚参数如果是软件片选传的是GPIO引脚编号如果是硬件片选传的是RT_NULL。传错了片选不动作屏幕自然不亮。3.3 rt_spi_transfer的消息结构和使用姿势rt_spi_transfer的核心是struct rt_spi_message这个结构体定义了传输的缓冲区、长度、片选控制、回调等。用的时候要注意几个字段send_buf和recv_buf发送和接收缓冲区可以只发不收也可以只收不发。length传输长度单位是字节。cs_take和cs_release控制传输前后是否操作片选。如果一次传输分多段第一段cs_take1最后一段cs_release1中间段都设0这样片选在整个传输过程中保持有效。next消息链表支持链式传输。ST7789的驱动里命令和参数是分开传输的。命令传输时DC引脚拉低参数传输时DC引脚拉高。用rt_spi_transfer的时候可以把DC引脚的控制放在传输前后或者用消息链表把命令和参数串起来。我一般用消息链表的方式把命令和参数组织成一条链一次rt_spi_transfer_message发出去效率高片选也不会中间抖动。3.4 DMA配置让CPU从刷屏里解放出来GD32H759的SPI支持DMART-Thread的SPI驱动如果配好了rt_spi_transfer会自动走DMA。DMA配置的关键是通道选择和优先级。GD32H7的DMA通道和SPI的对应关系要查手册不同SPI对应的DMA通道不一样。配置的时候要设好源地址、目的地址、传输方向、数据宽度、传输数量。SPI的DMA一般是外设到内存或者内存到外设数据宽度设成字节。DMA配好之后刷屏的时候CPU只需要准备数据缓冲区剩下的交给DMACPU可以去处理别的任务。工控场景下这个很重要因为刷屏往往和采集、通信并行CPU不能被刷屏占死。但DMA有个坑传输完成中断的处理。如果DMA传输完成中断没处理好rt_spi_transfer可能提前返回导致数据没发完就进行下一步。RT-Thread的SPI驱动一般用信号量同步等DMA完成中断释放信号量后才返回。如果自己写驱动这个同步一定要做对。4. ST7789驱动移植从初始化序列到刷屏函数4.1 ST7789的引脚定义和上电时序ST7789模组一般有这些引脚VCC、GND、SCLSCK、SDAMOSI、RES、DC、CS、BLK。有些模组还有MISO但ST7789只写不读MISO可以不接。上电时序很关键。ST7789要求上电后先给RES一个复位脉冲然后延时至少120ms再发初始化命令。RES复位脉冲的宽度要求是至少10us的低电平。我见过有人RES接在GPIO上但忘了拉低屏幕一直不亮查了半天。BLK背光引脚一般接PWM或者直接接高电平。工控场景下建议接PWM可以调亮度也方便做息屏。4.2 初始化序列那些必须按顺序发的命令ST7789的初始化序列是一串命令加参数的组合顺序不能乱。核心命令包括0x11Sleep Out退出睡眠模式发完要延时120ms。0x3APixel Format设置颜色格式一般设0x55表示16位RGB565。0x36Memory Access Control设置扫描方向这个决定屏幕的横竖和镜像。0x21Display Inversion On有些屏需要开反色不然颜色是反的。0x13Normal Display Mode On。0x29Display On开显示。这些命令的顺序和参数不同厂家的屏可能略有差异。我的做法是拿到屏之后先找厂家给的例程把初始化序列抄过来跑通之后再研究每个命令的作用。0x36这个命令特别重要它控制屏幕的扫描方向。参数的不同位控制行地址顺序、列地址顺序、行刷新方向、列刷新方向。设错了屏幕会镜像或者旋转。工控面板一般用竖屏0x36设0x00或者0xC0具体看屏的安装方向。4.3 设置显示窗口刷屏前的必要步骤ST7789刷屏之前要先设置显示窗口用0x2A命令设列地址范围0x2B命令设行地址范围然后发0x2C命令开始写显存。列地址范围是两个字节高字节在前。比如240宽的屏列地址从0到239发0x00 0x00 0x00 0xEF。行地址同理320高的屏发0x00 0x00 0x01 0x3F。设置完窗口后连续发0x2C后面跟的就是像素数据每个像素两个字节RGB565格式。数据会自动填充到窗口区域超出窗口的会绕回。这里有个优化点如果每次刷屏都重设窗口会有额外开销。如果刷的是全屏窗口固定可以只在初始化时设一次后面直接发0x2C加数据。但如果是局部刷新每次都要重设窗口。4.4 刷屏函数的实现和性能实测刷屏函数的核心逻辑是设窗口、发0x2C、发像素数据。用RT-Thread的SPI框架可以这样组织static rt_err_t st7789_set_window(rt_uint16_t x0, rt_uint16_t y0, rt_uint16_t x1, rt_uint16_t y1) { rt_uint8_t cmd; rt_uint8_t data[4]; cmd 0x2A; st7789_write_cmd(cmd, 1); data[0] x0 8; data[1] x0 0xFF; data[2] x1 8; data[3] x1 0xFF; st7789_write_data(data, 4); cmd 0x2B; st7789_write_cmd(cmd, 1); data[0] y0 8; data[1] y0 0xFF; data[2] y1 8; data[3] y1 0xFF; st7789_write_data(data, 4); cmd 0x2C; st7789_write_cmd(cmd, 1); return RT_EOK; }刷全屏的时候把整个framebuffer通过DMA发出去。240x320的屏16位色一帧数据是153600字节。SPI时钟12.5MHz的话理论传输时间约98ms。实际测试下来加上命令开销全屏刷新在110ms左右。如果把SPI时钟提到25MHz能压到55ms左右。工控场景下如果只是刷新局部区域比如数值变化只刷变化的那一块时间可以忽略不计。全屏刷新一般只在切换画面时用。5. 调试实录花屏、不亮、刷一半卡死的排查链路5.1 屏幕完全不亮从供电查到片选屏幕不亮是最常见的问题排查要按顺序来别跳步。第一步查供电。用万用表量VCC和GND确认电压对。ST7789一般3.3V供电有些模组支持5V但逻辑电平还是3.3V。供电不对后面都白搭。第二步查RES复位。用示波器或者逻辑分析仪看RES引脚上电后应该有一个低电平脉冲。如果没有检查GPIO配置和代码。第三步查片选。软件片选的话传输时CS应该拉低。如果CS一直高SPI从机根本不响应。我遇到过CS引脚配成了输入模式一直高电平查了半天。第四步查时钟和数据。用逻辑分析仪抓SCK和MOSI看有没有波形。如果没有检查SPI外设是否使能引脚复用是否配对。第五步查初始化序列。如果前面都正常但屏幕还是不亮可能是初始化序列有问题。拿厂家例程对比逐条命令核对。5.2 花屏时序、模式、数据格式三选一花屏的表现是屏幕有显示但颜色错乱、位置偏移、有噪点。原因一般有三个一是SPI模式不对。Mode 0和Mode 3搞混了数据采样边沿错位。换模式试试。二是数据格式不对。RGB565和RGB888搞混了或者字节序反了。ST7789默认是RGB565每个像素两个字节高字节在前。如果发数据时高低字节反了颜色会偏。三是时序余量不够。SPI时钟太快或者片选建立保持时间不够。降频试试如果降频后正常就是时序问题。我遇到过一次花屏查了半天发现是DMA传输的数据宽度设成了半字但SPI配置的是字节导致数据错位。DMA和SPI的数据宽度必须一致。5.3 刷一半卡死DMA完成中断和信号量刷一半卡死屏幕显示到一半就不动了或者程序卡在rt_spi_transfer里出不来。这个问题的根因一般是DMA完成中断没触发或者信号量没释放。排查步骤先在DMA完成中断里打个断点或者翻转GPIO看中断有没有进。如果没进检查DMA中断使能和NVIC配置。如果进了但程序还是卡检查信号量释放的代码看是不是在中断里用了错误的API。RT-Thread在中断里释放信号量要用rt_sem_release不能用带阻塞的API。如果中断里调用了会导致阻塞的函数系统会卡死。还有一个可能是DMA传输数量设错了。比如要传153600字节但DMA配置里设成了15360传完就停了rt_spi_transfer等不到完成信号一直阻塞。5.4 用逻辑分析仪抓SPI波形的正确姿势逻辑分析仪是调SPI的神器但要用对。抓波形的时候通道接SCK、MOSI、CS、DC四个信号采样率至少是SCK频率的10倍。抓的时候先看CS传输期间应该一直低。如果CS中间有毛刺说明片选控制有问题。再看SCK数一下时钟个数和发送的字节数对一下。最后看MOSI对照ST7789的命令手册看发的命令和数据对不对。我习惯把第一次初始化的波形抓下来存着后面出问题对比着看很快能定位。6. 工控场景下的SPI稳定性优化和性能调优6.1 片选时序余量的计算和实测工控环境电磁干扰大SPI时序余量要留够。片选建立时间CS拉低到第一个SCK边沿和保持时间最后一个SCK边沿到CS拉高是关键。ST7789对这两个时间的要求是纳秒级但实际PCB走线和干扰会让有效时间变短。我的做法是在片选操作后加1us延时用rt_hw_us_delay实现。1us对刷屏速度影响可以忽略但能显著提高稳定性。实测下来不加延时的时候偶尔会出现第一个字节丢失导致花屏。加了1us延时后连续刷几千次没出过错。6.2 SPI时钟频率和刷屏帧率的关系刷屏帧率和SPI时钟频率基本是线性关系。240x320x16位的一帧数据是153600字节需要的SCK周期数是153600x81228800个。SPI时钟12.5MHz的话传输时间约98ms帧率约10fps。25MHz的话约49ms帧率约20fps。工控HMI一般10fps就够用了除非是做动画或者视频。如果要做流畅的界面切换建议SPI时钟至少20MHz配合DMACPU占用率很低。但要注意SPI时钟提高后PCB走线要跟上。SCK和MOSI的走线要等长尽量短远离干扰源。如果走线不好高频下误码率会上升。6.3 双缓冲和局部刷新减少无效传输工控界面往往只有部分区域在变化比如数值、状态灯。全屏刷新浪费带宽用局部刷新可以大幅降低SPI负载。局部刷新的实现是记录变化区域的坐标只设这个区域的窗口只发这个区域的数据。比如一个数值从100变到101只刷新那几十个像素传输时间从几十毫秒降到几十微秒。双缓冲是在内存里维护两个framebuffer一个用于显示一个用于绘制。绘制完成后切换避免绘制过程中的撕裂。但双缓冲占内存240x320x16位两个buffer是307200字节GD32H759的RAM够用但要看具体型号。我的做法是静态界面用局部刷新动态界面用双缓冲加局部刷新兼顾流畅和内存。6.4 中断优先级和RT-Thread线程调度的配合SPI DMA完成中断的优先级要设对。如果设得太低可能被其他中断打断导致传输延迟。如果设得太高可能影响RT-Thread的调度。我的经验是SPI DMA中断优先级设成中等比系统滴答定时器低比普通外设中断高。这样既保证传输及时完成又不影响系统调度。另外刷屏线程的优先级要设得合理。如果刷屏线程优先级太高会抢占采集和通信线程太低的话刷屏不及时界面卡顿。一般设成比采集低、比空闲高就行。7. 我在GD32H759上刷ST7789积累的几条实战心得第一条别一上来就追求高帧率。先把SPI时钟降到10MHz把屏幕点亮把初始化序列跑通再逐步提频。我见过太多人直接上50MHz结果花屏然后花几天时间查代码最后发现是频率超了。第二条逻辑分析仪比printf好用。SPI的问题printf看不出来逻辑分析仪一抓就清楚。几百块的投资能省几天调试时间。第三条RT-Thread的SPI框架要用起来别自己造轮子。框架里的片选管理、DMA同步、多线程互斥都是经过验证的自己写容易出问题。如果框架不支持某个功能优先考虑给框架打补丁而不是绕过框架。第四条ST7789的初始化序列要保存好。不同批次的屏初始化序列可能不一样。我习惯把每个批次的初始化序列存成单独的数组用宏切换换屏的时候不用改代码逻辑。第五条工控场景下屏幕的EMC要重视。SPI走线尽量短加地线隔离必要时加磁珠。我遇到过屏幕在实验室正常到现场就花屏最后发现是变频器干扰加了屏蔽和滤波才好。最后说个性能数据GD32H759跑RT-ThreadSPI时钟25MHzDMA刷ST7789全屏实测刷新时间52msCPU占用率不到5%。这个性能做工控HMI绰绰有余。如果做更复杂的界面可以考虑用GD32H7的LCD控制器或者RGB接口但那是另一个话题了。
返回列表