C语言mmap实现GPIO模拟I2C时修改单引脚输出其余引脚异常拉低
问题根因
引脚状态互相干扰的核心原因是位操作使用了非实时的寄存器快照值,而非寄存器当前的真实值:
- 代码中
u32 PE_DAT = (*(volatile u32*)(map_base+GPIO_BASE_OFFSET+rPE_DAT));是变量初始化语句,仅在定义该变量的时刻读取了一次PE组数据寄存器的值,后续该变量存储的永远是初始化瞬间的引脚状态,不会自动同步寄存器的变化 - 每次调用
i2cSDA/i2cSCL做位运算时,用的都是这份过期的快照值,完全没有记录另一个引脚此前被修改的状态,写回寄存器时会直接把另一个引脚的状态覆盖为快照中的旧值,最终出现改一个引脚、另一个引脚被意外拉低的现象。 - 复现逻辑和测试现象完全匹配:
- 变量初始化时两个引脚都是低电平,
PE_DAT变量存储值为0x00000000 - 调用
i2cSCL(1)时,基于旧值0x00000000运算得到0x00000800写回寄存器,SCL被拉高,但PE_DAT变量的值没有更新,仍然是0x00000000 - 调用
i2cSDA(1)时,仍然基于旧值0x00000000运算得到0x00001000写回寄存器,SDA被拉高的同时,SCL对应的bit11被写为0,表现为SCL被意外拉低。
- 变量初始化时两个引脚都是低电平,
修正方案
删除提前定义的PE_DAT快照变量,每次修改引脚电平时严格按照「读寄存器实时值→修改对应位→写回寄存器」的流程操作,修正后的代码如下:
void i2cSDA(u8* map_base, bool isHigh) { volatile u32* pe_dat = (volatile u32*)(map_base + GPIO_BASE_OFFSET + rPE_DAT); u32 reg_tmp = *pe_dat; reg_tmp &= ~(1U << 12); // 清除PE12对应位,掩码等价于0xFFFFEFFF if (isHigh) { reg_tmp |= (1U << 12); } *pe_dat = reg_tmp; } void i2cSCL(u8* map_base, bool isHigh) { volatile u32* pe_dat = (volatile u32*)(map_base + GPIO_BASE_OFFSET + rPE_DAT); u32 reg_tmp = *pe_dat; reg_tmp &= ~(1U << 11); // 清除PE11对应位,掩码等价于0xFFFFF7FF if (isHigh) { reg_tmp |= (1U << 11); } *pe_dat = reg_tmp; }
额外优化建议
- 操作寄存器保留位(位13~位31)时,必须保留读取到的原有值,不要强行写入固定值,避免触发硬件未定义行为
- mmap映射完成后需要检查返回值是否为
MAP_FAILED,映射失败时直接访问会触发段错误 - 程序退出前除了关闭
/dev/mem的文件描述符,还需要调用munmap解除内存映射,避免资源泄漏 - 模拟I2C的延时需要根据I2C时钟标准调整,保证时序满足建立、保持时间要求。
内容的提问来源于stack exchange,提问作者Chroluma
相关产品推荐
相关产品推荐

