IMX6平台下基于mmap的GPIO置位/清零及LCD显示问题咨询
i.MX6平台GPIO mmap映射与LCD显示伪影问题解决
问题背景
因sysfs操作GPIO速度过慢,在i.MX6平台尝试通过mmap直接内存访问实现GPIO置位/清零,当前使用的GPIO信息:
cat /sys/kernel/debug/gpio gpiochip4: GPIOs 128-159, parent: platform/20ac000.gpio, 20ac000.gpio: gpio-136 ( |sysfs ) out lo gpio-137 ( |sysfs ) out lo gpio-138 ( |sysfs ) out lo gpio-139 ( |sysfs ) out lo gpio-140 ( |sysfs ) out lo gpio-141 ( |sysfs ) out lo gpio-142 ( |sysfs ) out lo gpio-143 ( |sysfs ) out lo gpio-144 ( |sysfs ) out lo gpio-145 ( |sysfs ) out lo
已实现物理地址0x020ac000到虚拟地址的映射,代码如下:
#define GPIO_BASE_ADDR_4 (0x020ac000) volatile unsigned *gpio_reg; void* gpio_map; int mem_fd; mem_fd = open("/dev/mem", O_RDWR | O_SYNC); if(mem_fd < 0) { printf("ERROR could not open gpio mapping"); exit(1); } gpio_map = mmap(NULL, getpagesize(), (PROT_READ | PROT_WRITE), MAP_SHARED, mem_fd, GPIO_BASE_ADDR_4); close(mem_fd); if(gpio_map == MAP_FAILED) { printf("ERROR could not open gpio mapping"); exit(1); } gpio_reg = (volatile unsigned *)gpio_map;
最初的置位/清零函数:
void clear(int gpioNr) { *(gpio_reg+0x190) = 1<<(gpioNr-128); } void set(int gpioNr) { *(gpio_reg+0x194) = 1<<(gpioNr-128); }
程序以root权限执行,初始通过sysfs导出GPIO并设为输出模式。后续修改了置位/清零函数:
void clear(int gpioNr) { int reg_index = (gpioNr - 128) / 32; int bit_pos = (gpioNr - 128) % 32; gpio_reg[reg_index] &= ~(1 << bit_pos); } void set(int gpioNr) { int reg_index = (gpioNr - 128) / 32; int bit_pos = (gpioNr - 128) % 32; gpio_reg[reg_index] |= (1 << bit_pos); }
修改后GPIO输出有所改善,但驱动NHD-14432WG LCD时约50-70%区域显示正常,其余区域有伪影;此前用sysfs控制显示正常但刷新极慢。
问题分析与解决方案
1. 地址映射正确性验证
- 物理地址对齐:i.MX6的GPIO基地址
0x020ac000是4KB对齐的(末尾三位为0),mmap使用getpagesize()(通常4KB)映射没问题,不存在对齐问题。 - 映射范围:i.MX6单个GPIO控制器的寄存器空间为0x400字节(从BASE到BASE+0x3FF),
getpagesize()(4KB=0x1000)完全覆盖所需寄存器区域,范围足够。 - 映射参数:
MAP_SHARED和PROT_READ|PROT_WRITE参数正确,保证内核与用户空间共享映射,且具备读写权限。
2. GPIO寄存器使用正确性
i.MX6的GPIO寄存器设计中:
- 初始函数错误点:
*(gpio_reg+0x190)是按4字节偏移计算的,0x190字节偏移对应寄存器索引为0x190/4=0xA4,并非正确的置位/清零寄存器。i.MX6的GPIO置位寄存器是GPIOx_DR_SET(偏移0x14),清零寄存器是GPIOx_DR_CLEAR(偏移0x18),而非直接操作数据寄存器GPIOx_DR(偏移0x00)。 - 修改后函数的问题:直接对
GPIOx_DR寄存器做读改写操作存在竞争风险——若内核或其他程序同时操作该GPIO组,读和写之间的间隙会导致数据丢失。此外,i.MX6的GPIO数据寄存器在推挽输出模式下直接操作有效,但正确的置位/清零应使用专门的SET/CLEAR寄存器,避免读改写操作。
正确的置位/清零函数:
// i.MX6 GPIO SET寄存器偏移0x14,CLEAR寄存器偏移0x18 // 按unsigned int索引,每个寄存器占4字节 #define GPIO_DR_SET 0x14/4 #define GPIO_DR_CLEAR 0x18/4 void clear(int gpioNr) { int bit_pos = gpioNr - 128; gpio_reg[GPIO_DR_CLEAR] = 1 << bit_pos; } void set(int gpioNr) { int bit_pos = gpioNr - 128; gpio_reg[GPIO_DR_SET] = 1 << bit_pos; }
3. LCD显示伪影解决方案
显示伪影的核心原因是GPIO操作时序不达标或竞争条件导致的电平异常:
- 时序问题:sysfs操作慢但时序稳定,mmap操作快但可能未满足LCD的建立/保持时间要求。需在GPIO电平切换后添加短延时(使用
usleep(),根据LCD datasheet调整,比如1-10us)。 - 竞争风险:直接操作
GPIOx_DR的读改写会引发不可预测的电平跳变,换成SET/CLEAR寄存器可避免该问题。 - 信号完整性:检查GPIO引脚的硬件连接,确保走线无过长、干扰情况,必要时添加上拉/下拉电阻。
- 内存屏障:在GPIO操作前后添加内存屏障,确保硬件及时响应寄存器写入:
#include <stdlib.h> #include <stdint.h> #define dmb() __asm__ __volatile__ ("dmb" : : : "memory") void set(int gpioNr) { int bit_pos = gpioNr - 128; dmb(); gpio_reg[GPIO_DR_SET] = 1 << bit_pos; dmb(); }
总结
- 地址映射本身无问题,范围和对齐均符合要求;
- 需使用i.MX6专用的GPIO SET/CLEAR寄存器,避免直接操作数据寄存器的读改写;
- 调整GPIO操作时序、添加内存屏障、排查硬件连接可解决LCD伪影问题。
内容的提问来源于stack exchange,提问作者codeshredder726b
相关产品推荐
相关产品推荐

