树莓派用mmap操作GPIO前须确认SoC型号对应物理地址并获/dev/mem权限:Pi4为0xfe200000,Pi3为0x3f200000,Pi Zero/1为0x20200000;需sudo或setcap、禁用gpio-bcm2835模块、用O_SYNC打开/dev/mem、offset页对齐、volatile指针及MAP_SHARED。

树莓派上用 mmap 操作 GPIO 前必须确认硬件地址和权限
树莓派的 GPIO 寄存器物理地址不是固定的,不同型号差异很大,而且 Linux 内核默认禁止用户空间直接访问。不查清 SoC 型号和寄存器基址就硬 mmap,mmap 会返回 MAP_FAILED,errno 通常是 EPERM 或 EACCES。
以 Raspberry Pi 4(BCM2711)为例,GPIO 寄存器起始物理地址是 0xfe200000;Pi 3(BCM2837)是 0x3f200000;Pi Zero/1 是 0x20200000。这些值必须从官方文档或 /proc/cpuinfo + SoC 手册交叉验证,不能靠网上旧教程抄。
操作前需确保:
- 用
sudo运行程序,或给当前用户添加mem设备访问权限(如sudo setcap cap_sys_rawio+ep ./gpio_test) - 关闭内核的 GPIO 驱动占用(禁用
gpio-bcm2835模块,否则寄存器可能被锁定) - 使用
/dev/mem而非 /dev/gpiomem —— 后者只开放部分寄存器,不支持 GPLEV/GPSET/GPCLR 等状态寄存器直接读写
正确打开 /dev/mem 并 mmap GPIO 寄存器区域
open("/dev/mem", O_RDWR | O_SYNC) 必须带 O_SYNC,否则 CPU 缓存可能导致写入不立即生效,引脚电平无响应。mmap 的 offset 要对齐到内存页边界(通常 4KB),所以实际 offset 是寄存器基址向下取整到 4096 的倍数,长度至少覆盖所需寄存器范围(如 4KB 足够)。
示例代码关键段(Pi 4):
int fd = open("/dev/mem", O_RDWR | O_SYNC);
if (fd == -1) { perror("open /dev/mem"); return -1; }
// BCM2711 GPIO base: 0xfe200000 → page-aligned offset = 0xfe200000
volatile uint32_t *gpio_map = (uint32_t*)mmap(
nullptr, 4096,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd,
0xfe200000
);
if (gpio_map == MAP_FAILED) { perror("mmap"); return -1; }
注意:volatile 修饰指针必不可少,否则编译器可能优化掉重复读写;MAP_SHARED 是必须的,MAP_PRIVATE 对设备内存无效。
设置 GPIO 方向与输出电平的操作顺序不能错
BCM 系列 GPIO 没有方向寄存器,靠写 GPFSEL(Function Select)寄存器配置引脚模式。每个引脚占 3 bit,值为 001 表示输出,000 表示输入。直接位操作比全字写入更安全,避免误改相邻引脚。
输出高/低电平也不用写数据寄存器,而是用 GPSET(置位)和 GPCLR(清零)寄存器 —— 它们是写 1 有效、写 0 无操作的“脉冲式”寄存器,天生线程安全且不会干扰其他引脚。
典型操作链:
- 查清目标引脚编号(如 GPIO 16)→ 计算在
GPFSEL中的 bit 位置:16 / 10 = 1(组号),16 % 10 = 6(组内偏移),对应GPFSEL1的 bit 18–20 - 用位掩码设置:例如
gpio_map[1] = (gpio_map[1] & ~0x700000) | (0x1 (设 GPIO16 为输出) - 置高:写
GPSET0(偏移 0x0028)对应位;置低:写GPCLR0(偏移 0x002c)对应位
寄存器偏移查 BCM2835 ARM Peripherals 手册 Table 6,不要凭记忆硬编码。
常见崩溃点:未同步、越界访问、忘记 unmap
最隐蔽的问题是没加内存屏障。虽然 O_SYNC 和 volatile 能解决大部分时序问题,但在高频翻转(如 PWM 模拟)时,仍建议在关键写之后插入 __sync_synchronize() 或 std::atomic_thread_fence(std::memory_order_seq_cst)。
另一个高频坑:寄存器数组索引误用。比如把 GPSET0 当作 gpio_map[0],其实它的偏移是 0x0028,对应 gpio_map[0x0028/4] = gpio_map[10] —— 错一位就写到别的寄存器甚至触发总线错误。
最后,munmap(gpio_map, 4096) 和 close(fd) 必须成对调用,否则多次运行后可能耗尽 /dev/mem 句柄或引发内核警告。调试时可用 cat /proc/$(pidof your_app)/maps | grep mem 确认映射是否残留。
寄存器映射这事,地址、权限、时序、边界,四者错一不可用,没有“差不多就行”的余地。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











