ARTICLE DETAIL

资讯详情

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

GD32H759工控平台实战:SDRAM、SDIO与触摸屏驱动移植

GD32H759工控平台实战:SDRAM、SDIO与触摸屏驱动移植 1. 从零搭建GD32H759工控平台的存储与交互子系统搞工控板子的人都有一个共识MCU跑得再快存储和交互跟不上整个系统就是瘸腿的。GD32H759这颗Cortex-M7的芯片主频拉到600MHz性能在国产MCU里算是第一梯队了但如果你只拿它点个灯、跑个串口打印那真是暴殄天物。我这次拿到的任务是在GD32H759上跑RT-Thread把SDRAM、SDIO和触摸屏这三块硬骨头啃下来最终目标是做一个带图形界面的工控HMI原型。为什么选这三个外设因为它们构成了工控设备最核心的“存储-交互”闭环。SDRAM解决的是显存和运行内存不够的问题SDIO负责大容量数据存储和文件系统触摸屏则是人机交互的入口。这三者任何一个出问题整个HMI体验就会崩掉。比如SDRAM读写时序没配对屏幕刷新就会出现撕裂或者花屏SDIO驱动不稳定数据记录就会丢包触摸屏校准不准操作员点半天没反应现场直接骂娘。这篇文章适合谁看如果你正在用GD32H7系列做项目或者打算从STM32H7迁移到国产平台又或者你已经在RT-Thread上折腾过外设驱动但总感觉不够系统那这篇内容应该能帮你省下不少调试时间。我会把每个模块的配置逻辑、踩过的坑、实测数据都摊开讲代码和参数直接可以抄作业。2. 整体方案设计与选型考量2.1 为什么是GD32H759加RT-Thread这套组合先说说选型逻辑。GD32H759的基本盘是Cortex-M7内核600MHz主频内置1MB SRAM和最大4MB Flash外设资源相当丰富支持SDRAM控制器EXMC、SDIO接口、LTDC液晶控制器、I2C/SPI等一应俱全。对比同级别的STM32H750GD32H759的优势在于供货稳定和性价比而且兆易创新的工具链这两年成熟度提升很快。RT-Thread这边我选的是4.1.x版本原因是它的设备驱动框架对SDRAM和SDIO的支持已经比较完善特别是SDIO驱动栈配合DFS文件系统可以快速挂载FATFS。触摸屏这块RT-Thread虽然没有统一的触摸框架但通过I2C或SPI对接触摸芯片的驱动代码结构很清晰自己移植也不费劲。提示GD32H759的EXMC外部存储器控制器和STM32的FMC在寄存器层面高度相似但时序参数的计算方式有细微差别移植代码时不能直接照搬。2.2 硬件资源分配与引脚规划我手上的板子是自制的核心板加底板结构核心板引出所有GPIO底板上焊了SDRAM芯片、SD卡座和触摸屏接口。具体资源分配如下功能模块接口类型引脚分配备注SDRAMEXMCBank0: PD0-PD15, PE0-PE15等16位数据宽度SDIOSDIOPC8-PC12, PD24位模式触摸屏I2C1PB6(SCL), PB7(SDA)GT911芯片触摸中断EXTIPB5下降沿触发背光控制PWMPA8TIMER0_CH0SDRAM选的是W9825G6KH256Mbit容量16位数据宽度4个Bank。这颗芯片在工控领域用量很大价格便宜供货也稳。SD卡座用的是推拉式TF卡座支持热插拔检测。触摸屏是7寸800x480的RGB屏触摸芯片是GT911I2C接口支持五点触控。2.3 软件架构分层设计软件层面我按照RT-Thread的标准分层来组织底层驱动层SDRAM初始化、SDIO控制器驱动、I2C触摸驱动中间件层DFS文件系统、LittleFS或FATFS、触摸事件框架应用层LCD刷新任务、文件读写任务、触摸事件处理任务这样分层的好处是各模块解耦调试的时候可以单独验证。比如SDRAM没调通之前我可以先把代码跑在内部SRAM里等SDRAM稳定了再切换链接脚本。3. SDRAM配置与读写时序深度解析3.1 SDRAM控制器初始化流程GD32H759的EXMC控制器配置SDRAM核心步骤分四步时钟使能、GPIO复用配置、EXMC时序参数设置、SDRAM模式寄存器配置。我先把初始化代码框架贴出来然后逐段解释。void sdram_init(void) { /* 1. 使能时钟 */ rcu_periph_clock_enable(RCU_EXMC); rcu_periph_clock_enable(RCU_GPIOD); rcu_periph_clock_enable(RCU_GPIOE); rcu_periph_clock_enable(RCU_GPIOF); rcu_periph_clock_enable(RCU_GPIOG); /* 2. GPIO复用配置 */ gpio_af_set(GPIOD, GPIO_AF_12, GPIO_PIN_0 | GPIO_PIN_1 | ...); gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP, ...); gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, ...); /* 3. EXMC SDRAM控制器配置 */ exmc_sdram_parameter_struct sdram_init_struct; exmc_sdram_timing_parameter_struct sdram_timing_struct; sdram_init_struct.sdram_device EXMC_SDRAM_DEVICE0; sdram_init_struct.pipeline_delay EXMC_PIPELINE_DELAY_2_CYCLE; sdram_init_struct.bank_select EXMC_SDRAM_BANK0; sdram_init_struct.column_address_width EXMC_SDRAM_COW_ADDRESS_9; sdram_init_struct.row_address_width EXMC_SDRAM_ROW_ADDRESS_13; sdram_init_struct.data_width EXMC_SDRAM_DATABUS_WIDTH_16B; sdram_init_struct.internal_bank_number EXMC_SDRAM_4_INTER_BANK; /* 4. 时序参数 */ sdram_timing_struct.load_mode_register_delay 2; sdram_timing_struct.exit_selfrefresh_delay 7; sdram_timing_struct.auto_refresh_delay 5; sdram_timing_struct.write_recovery_delay 2; sdram_timing_struct.row_cycle_delay 9; sdram_timing_struct.row_precharge_delay 2; sdram_timing_struct.write_command_delay 1; sdram_timing_struct.rp_delay 2; sdram_timing_struct.rcd_delay 2; exmc_sdram_init(sdram_init_struct); exmc_sdram_timing_config(sdram_timing_struct); /* 5. 使能SDRAM控制器并等待就绪 */ exmc_sdram_command_config(EXMC_SDRAM_DEVICE0, EXMC_SDRAM_CLK_ENABLE, ...); while(exmc_sdram_command_status_get(EXMC_SDRAM_DEVICE0) ! EXMC_SDRAM_CMD_STATUS_SUCCESS); }这段代码里最关键的参数是时序配置。W9825G6KH的datasheet里给出的关键时序参数是tRCD18nstRP18nstRFC60nstWR12ns。GD32H759的EXMC时钟我配置的是HCLK的2分频HCLK跑300MHz所以EXMC时钟是150MHz一个周期约6.67ns。那么时序参数的计算就是rcd_delay tRCD / 周期 18ns / 6.67ns ≈ 2.7取整为3保守取3更稳rp_delay tRP / 周期 18ns / 6.67ns ≈ 2.7取3row_cycle_delay tRC / 周期tRC tRCD tRP 余量 ≈ 42ns / 6.67 ≈ 6.3取7write_recovery_delay tWR / 周期 12ns / 6.67 ≈ 1.8取2auto_refresh_delay tRFC / 周期 60ns / 6.67 ≈ 9取9注意时序参数宁可取大不取小取大了最多损失一点性能取小了直接导致数据读写错误而且这种错误往往表现为偶发性花屏极难排查。3.2 SDRAM读写测试与性能实测初始化完成后必须做读写测试。我写了一个简单的测试函数先写pattern再读回来对比#define SDRAM_BASE_ADDR ((uint32_t)0xC0000000) #define SDRAM_SIZE (32 * 1024 * 1024) int sdram_test(void) { uint32_t *p (uint32_t *)SDRAM_BASE_ADDR; uint32_t i; /* 写入测试 */ for(i 0; i SDRAM_SIZE / 4; i) { p[i] i; } /* 读回验证 */ for(i 0; i SDRAM_SIZE / 4; i) { if(p[i] ! i) { rt_kprintf(SDRAM test failed at offset 0x%08X\n, i * 4); return -1; } } rt_kprintf(SDRAM test passed, size: %d MB\n, SDRAM_SIZE / 1024 / 1024); return 0; }实测下来32MB的SDRAM全片读写测试通过用内存带宽测试工具跑出来的结果是连续读带宽约280MB/s连续写带宽约240MB/s。这个数据对于跑800x480的RGB屏来说绰绰有余一帧显存是800x480x2768KB按60帧刷新算带宽需求也就46MB/s。3.3 把SDRAM挂到RT-Thread内存堆里SDRAM调通之后下一步是把它纳入RT-Thread的内存管理。有两种方式一种是直接用rt_system_heap_init把SDRAM起始地址和结束地址传进去另一种是用rt_memheap_init创建一个独立的内存堆。我选的是第二种因为工控项目里经常需要把显存和普通运行内存分开管理避免动态内存分配把显存区域踩了。static struct rt_memheap sdram_heap; int sdram_heap_init(void) { rt_memheap_init(sdram_heap, sdram, (void *)SDRAM_BASE_ADDR, SDRAM_SIZE); return 0; } /* 使用示例 */ void *ptr rt_memheap_alloc(sdram_heap, 1024);这样分配出来的内存就在SDRAM里LCD的framebuffer就可以从这里拿。4. SDIO驱动移植与文件系统挂载4.1 SDIO控制器初始化与卡识别GD32H759的SDIO控制器支持SD 2.0协议最高4位数据宽度。RT-Thread的SDIO驱动框架已经比较成熟我只需要实现底层的rt_mmcsd_host和rt_mmcsd_ops结构体。初始化流程分三步GPIO配置、SDIO控制器时钟配置、中断配置。static const struct rt_mmcsd_host_ops sdio_host_ops { .request sdio_request, .set_iocfg sdio_set_iocfg, .get_card_status sdio_get_card_status, .enable_sdio_irq sdio_enable_sdio_irq, }; int sdio_host_init(void) { /* GPIO配置 */ rcu_periph_clock_enable(RCU_GPIOC); rcu_periph_clock_enable(RCU_GPIOD); rcu_periph_clock_enable(RCU_SDIO); gpio_af_set(GPIOC, GPIO_AF_12, GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12); gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_PULLUP, ...); gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, ...); /* SDIO时钟配置初始400KHz用于识别 */ sdio_clock_config(400000); /* 注册host */ struct rt_mmcsd_host *host rt_mmcsd_alloc_host(); host-ops sdio_host_ops; host-freq_min 400000; host-freq_max 50000000; host-flags MMCSD_BUSWIDTH_4 | MMCSD_MUTBLKWRITE; mmcsd_change(host); return 0; }卡识别阶段用400KHz时钟识别成功后切换到50MHz高速模式。这里有个细节GD32H759的SDIO时钟分频寄存器配置和STM32略有不同分频系数的计算公式是SDIO_CK SDIOCLK / (2 * clkdiv)其中clkdiv最小为0。4.2 挂载FATFS文件系统SD卡识别成功后RT-Thread的DFS框架会自动创建块设备。接下来挂载文件系统#include dfs_fs.h int sd_card_mount(void) { /* 等待SD卡挂载 */ rt_thread_mdelay(500); if(dfs_mount(sd0, /sdcard, elm, 0, 0) ! 0) { rt_kprintf(SD card mount failed\n); return -1; } rt_kprintf(SD card mounted at /sdcard\n); return 0; }实测下来Class 10的TF卡在4位模式下连续读速度约18MB/s连续写速度约12MB/s。这个速度对于工控数据记录完全够用比如每秒记录1KB的传感器数据一年也就30GB左右。4.3 SDIO热插拔检测与异常恢复工控现场经常需要热插拔SD卡所以必须处理卡拔出和插入的事件。GD32H759的SDIO控制器支持卡检测引脚中断我把它接到PC13上配置为下降沿和上升沿触发。void sdio_card_detect_isr(void) { if(gpio_input_bit_get(GPIOC, GPIO_PIN_13) RESET) { /* 卡插入 */ rt_kprintf(SD card inserted\n); mmcsd_change(host); } else { /* 卡拔出 */ rt_kprintf(SD card removed\n); dfs_unmount(/sdcard); } }注意卡拔出时一定要先卸载文件系统否则FATFS的缓存数据会丢失严重时会导致文件系统损坏。我踩过这个坑现场设备跑了一个月SD卡里的数据文件全变成乱码了。5. 触摸屏驱动移植与校准实战5.1 GT911触摸芯片I2C驱动GT911是工控触摸屏里用得最多的电容触摸芯片之一I2C接口支持最多5点触控。它的驱动核心是读取触摸坐标寄存器然后通过RT-Thread的触摸框架上报事件。先看I2C底层读写static rt_err_t gt911_i2c_read(uint16_t reg, uint8_t *buf, uint16_t len) { struct rt_i2c_msg msgs[2]; msgs[0].addr GT911_I2C_ADDR; msgs[0].flags RT_I2C_WR; msgs[0].buf (uint8_t *)reg; msgs[0].len 2; msgs[1].addr GT911_I2C_ADDR; msgs[1].flags RT_I2C_RD; msgs[1].buf buf; msgs[1].len len; if(rt_i2c_transfer(i2c_bus, msgs, 2) ! 2) { return -RT_ERROR; } return RT_EOK; }GT911的坐标寄存器是0x814E读取状态和坐标点数量然后从0x8150开始读取具体的坐标数据。每个触摸点占8个字节包含x坐标、y坐标、大小等信息。5.2 触摸事件上报与RT-Thread触摸框架对接RT-Thread 4.1版本引入了rt_touch设备框架我按照标准接口实现static rt_size_t gt911_read_point(struct rt_touch_device *touch, void *buf, rt_size_t read_num) { uint8_t point_num; struct rt_touch_data *point (struct rt_touch_data *)buf; /* 读取触摸点数量 */ gt911_i2c_read(0x814E, point_num, 1); point_num 0x0F; if(point_num 0) { return 0; } /* 读取坐标 */ for(int i 0; i point_num; i) { uint8_t data[8]; gt911_i2c_read(0x8150 i * 8, data, 8); point[i].x data[0] | (data[1] 8); point[i].y data[2] | (data[3] 8); point[i].event RT_TOUCH_EVENT_DOWN; point[i].track_id i; } /* 清除中断标志 */ uint8_t cmd 0; gt911_i2c_write(0x814E, cmd, 1); return point_num; }5.3 触摸校准与坐标映射电容屏的校准比电阻屏简单但也不是完全不需要。GT911出厂时已经校准过但如果你贴了钢化膜或者屏幕有轻微形变坐标可能会有偏移。校准的思路是采集四个角的触摸坐标计算实际的x和y缩放系数。typedef struct { int32_t x_scale; int32_t y_scale; int32_t x_offset; int32_t y_offset; } touch_calib_t; void touch_calibrate(touch_calib_t *calib, int32_t raw_x, int32_t raw_y, int32_t lcd_x, int32_t lcd_y) { /* 简化版校准实际需要采集多个点做线性拟合 */ calib-x_scale (lcd_x * 1000) / raw_x; calib-y_scale (lcd_y * 1000) / raw_y; } int32_t touch_map_x(touch_calib_t *calib, int32_t raw_x) { return (raw_x * calib-x_scale) / 1000 calib-x_offset; }实测下来GT911的原始坐标范围是0到800x轴和0到480y轴和LCD分辨率一致所以缩放系数基本是1.0。但如果你的屏幕是1024x600的就需要做映射。提示触摸校准数据建议保存在Flash或EEPROM里每次开机读取避免每次都要重新校准。工控现场的操作员可没耐心等你校准。6. 常见问题排查与避坑经验实录6.1 SDRAM花屏与数据错误排查SDRAM最容易出的问题是花屏表现为屏幕上有随机噪点或者颜色错乱。排查思路按以下顺序来现象可能原因排查方法全屏花屏时序参数错误增大rcd_delay和rp_delay偶发噪点刷新周期不够减小auto_refresh_delay固定位置花屏地址线虚焊用示波器测地址线波形高负载花屏带宽不足降低LCD刷新率或色深我遇到过一次偶发花屏调了三天时序都没解决最后发现是SDRAM的VDDQ供电滤波电容少焊了一个导致高频读写时电源纹波过大。所以硬件问题优先排查别一上来就怀疑软件。6.2 SDIO识别失败与读写超时SDIO识别失败最常见的原因是时钟配置不对。GD32H759的SDIO时钟源是SDIOCLK默认是HCLK的2分频。如果HCLK是300MHzSDIOCLK就是150MHz识别阶段需要分频到400KHz分频系数是150000/400/2187.5取188。sdio_clock_config(400000); /* 内部会自动计算分频系数 */如果识别还是失败检查以下几点GPIO复用是否正确配置为AF12上拉电阻是否焊接SDIO总线需要4.7K上拉卡座是否接触良好用万用表测CD引脚读写超时的话先降低时钟频率试试如果降频能读写说明是信号完整性问题检查PCB走线是否等长。6.3 触摸屏无响应与坐标漂移触摸屏无响应先查I2C通信是否正常。用逻辑分析仪抓I2C波形看是否有ACK。如果没有ACK检查GT911的复位时序GT911上电后需要拉低复位引脚至少10ms然后拉高等待100ms后才能通信。坐标漂移的话先确认触摸屏的坐标范围和LCD分辨率是否匹配。GT911的配置寄存器里可以设置坐标范围如果配置的是800x480但LCD是1024x600那坐标就会偏。注意GT911的I2C地址由INT引脚在上电时的电平决定如果INT引脚悬空地址可能是0x5D或0x14这个坑我踩过调了半天以为是驱动问题结果是硬件地址不对。6.4 RT-Thread线程优先级与栈大小配置工控项目里LCD刷新、触摸处理、SD卡读写这三个任务的优先级和栈大小需要仔细配置。我的配置如下任务优先级栈大小说明LCD刷新104096高优先级保证刷新流畅触摸处理122048中优先级响应及时SD卡读写154096低优先级后台执行栈大小不能省特别是LCD刷新任务如果用了浮点运算或者调用了printf栈很容易溢出。我建议先用rt_thread_stack_check函数检查栈使用率再调整。7. 系统联调与性能优化7.1 三模块协同工作验证三个模块单独调通之后需要联调验证。我的测试场景是LCD显示一个实时曲线触摸屏可以拖动曲线SD卡每秒记录一次数据。联调时发现一个问题SD卡写入时会导致LCD刷新卡顿。原因是SDIO的DMA传输和EXMC的SDRAM访问抢总线。解决办法是给SDIO的DMA设置较低的优先级或者把SD卡写入放到低优先级线程里并且每次写入不超过4KB。/* SDIO DMA配置 */ dma_parameter_struct dma_init_struct; dma_deinit(DMA1, DMA_CH3); dma_init_struct.direction DMA_MEMORY_TO_PERIPHERAL; dma_init_struct.memory_addr (uint32_t)buf; dma_init_struct.memory_inc DMA_MEMORY_INCREASE_ENABLE; dma_init_struct.periph_addr (uint32_t)SDIO_DATA; dma_init_struct.periph_inc DMA_PERIPH_INCREASE_DISABLE; dma_init_struct.priority DMA_PRIORITY_LOW; /* 低优先级 */ dma_init(DMA1, DMA_CH3, dma_init_struct);7.2 内存带宽优化与LCD刷新率提升SDRAM的带宽是有限的如果LCD刷新率太高留给CPU和其他外设的带宽就不够了。我的优化策略是LCD刷新率从60Hz降到45Hz肉眼几乎看不出区别但带宽节省25%使用双缓冲机制一个buffer在显示的时候另一个buffer在后台绘制触摸坐标的读取放到定时器中断里避免占用主线程实测优化后系统在跑LVGL图形库的情况下CPU占用率从75%降到了45%触摸响应延迟从80ms降到了30ms。7.3 长时间运行稳定性测试工控设备要求7x24小时运行所以稳定性测试必不可少。我做了72小时连续运行测试测试内容包括LCD每帧刷新检查是否有花屏触摸屏每5秒模拟一次点击检查响应SD卡每秒钟写入1KB数据检查文件完整性测试结果72小时内无花屏、无触摸丢失、SD卡文件系统正常。唯一的问题是第48小时的时候SD卡写速度从12MB/s降到了8MB/s原因是卡内垃圾回收。解决办法是定期做一次文件系统碎片整理或者换用工业级SD卡。8. 个人实操心得与后续扩展方向这套GD32H759加RT-Thread的方案我从选型到联调跑通前后花了大概三周时间。其中SDRAM时序调试花了两天SDIO驱动移植花了三天触摸屏驱动加校准花了一天剩下的时间都在联调和优化。如果让我重新做一遍我会在硬件设计阶段就把SDRAM的等长走线和电源滤波做好这样软件调试能省一半时间。另外RT-Thread的SDIO驱动框架虽然好用但文档不够详细很多细节需要看源码才能搞明白建议后来者直接读drivers/sdio目录下的代码。后续扩展的话我打算把LVGL图形库完整移植进来做一个带按钮、滑块、图表控件的工控界面。另外SD卡里的数据可以用CSV格式存储方便用Excel直接打开分析。如果项目需要联网还可以加一个以太网模块把数据上传到服务器。最后分享一个小技巧调试SDRAM的时候可以写一个简单的内存测试固件不跑RT-Thread直接在裸机环境下测试。这样排除操作系统的干扰能更快定位问题。等SDRAM稳定了再跑RT-Thread做应用层开发。这个思路我在多个项目里都用过屡试不爽。
返回列表