树莓派4 GPIO编程出现Segmentation Fault问题求助
树莓派4 GPIO配置段错误问题排查与修复
问题原因
- 直接访问物理地址无权限:用户空间程序不能直接操作物理内存地址(
0xfe200000),必须通过mmap将GPIO物理内存区域映射到进程的虚拟地址空间后才能合法访问,直接解引用物理地址指针会触发段错误。 - 函数语法错误:
Init_GPIO函数的大括号不匹配,后续的上下拉配置代码跑到了函数体外,属于语法错误,会导致编译或运行异常。 - 位运算逻辑错误:代码中
(valueinput * mask)是错误写法,valueinput本身已经是移位后的位设置值,不需要乘以mask,直接使用valueinput即可。
修复方案
1. 实现物理内存映射
通过mmap从/dev/mem设备中映射GPIO寄存器对应的物理内存区域,步骤如下:
- 打开
/dev/mem设备文件(需要root权限) - 映射足够覆盖GPIO寄存器的内存区域(4KB足够)
- 基于映射后的虚拟地址初始化各个寄存器指针
- 操作完成后释放映射并关闭文件
2. 修正函数语法错误
将上下拉配置代码移到Init_GPIO函数内部,修正大括号匹配问题。
3. 修正位运算逻辑
把(valueinput * mask)改为valueinput,避免无效的位运算。
完整修复代码
#include <stdio.h> #include <stdlib.h> #include <sys/mman.h> #include <fcntl.h> #include <unistd.h> #include <stdint.h> #define GPIO_BASEADDRESS 0xfe200000 #define GPIO_MAP_SIZE 4096 // 映射4KB足够覆盖GPIO寄存器区域 volatile uint32_t *gpio = NULL; volatile uint32_t *GPFSEL1 = NULL; volatile uint32_t *GPFSEL2 = NULL; volatile uint32_t *GPSET0 = NULL; volatile uint32_t *GPCLR0 = NULL; volatile uint32_t *GPLEV0 = NULL; volatile uint32_t *GPIO_PUP_PDN_CNTRL_REG1 = NULL; // 初始化GPIO内存映射 int gpio_map() { int fd = open("/dev/mem", O_RDWR | O_SYNC); if (fd < 0) { perror("Failed to open /dev/mem"); return -1; } gpio = (volatile uint32_t*)mmap(NULL, GPIO_MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO_BASEADDRESS); close(fd); if (gpio == MAP_FAILED) { perror("mmap failed"); return -1; } // 基于映射后的虚拟地址初始化寄存器指针 GPFSEL1 = gpio + 0x04/4; GPFSEL2 = gpio + 0x08/4; GPSET0 = gpio + 0x1c/4; GPCLR0 = gpio + 0x28/4; GPLEV0 = gpio + 0x34/4; GPIO_PUP_PDN_CNTRL_REG1 = gpio + 0xe8/4; return 0; } // 释放GPIO内存映射 void gpio_unmap() { if (gpio != NULL && gpio != MAP_FAILED) { munmap((void*)gpio, GPIO_MAP_SIZE); gpio = NULL; } } static void Init_GPIO(int pin_number, char type, char high_low, char pullup_pulldown) { int valueinput = 0b000 << ((pin_number - 10) * 3); int valueoutput = 0b001 << ((pin_number - 10) * 3); int mask = 0b111 << ((pin_number - 10) * 3); if (pin_number <= 19) { if (type == 'i') { *GPFSEL1 = (*GPFSEL1 & ~mask) | valueinput; } else if (type == 'o') { *GPFSEL1 = (*GPFSEL1 & ~mask) | valueoutput; } else { printf("输入类型未指定,请使用'o'或'i'\n"); } } else if (pin_number > 19 && pin_number <= 29) { mask = 0b111 << ((pin_number - 20) * 3); valueinput = 0b000 << ((pin_number - 20) * 3); valueoutput = 0b001 << ((pin_number - 20) * 3); if (type == 'i') { *GPFSEL2 = (*GPFSEL2 & ~mask) | valueinput; } else if (type == 'o') { *GPFSEL2 = (*GPFSEL2 & ~mask) | valueoutput; } else { printf("输入类型未指定,请使用'o'或'i'\n"); } } if (high_low == 'h') { *GPSET0 = 1 << pin_number; } else if (high_low == 'l') { *GPCLR0 = 1 << pin_number; } else { printf("高低电平未指定,请使用'h'或'l'\n"); } // 上下拉配置修正:适配引脚范围 int pupd_mask, pupd_val; if (pin_number >= 16 && pin_number <= 31) { pupd_mask = 0b11 << ((pin_number - 16) * 2); if (pullup_pulldown == 'p') { pupd_val = 0b01 << ((pin_number - 16) * 2); *GPIO_PUP_PDN_CNTRL_REG1 = (*GPIO_PUP_PDN_CNTRL_REG1 & ~pupd_mask) | pupd_val; } else if (pullup_pulldown == 'n') { pupd_val = 0b10 << ((pin_number - 16) * 2); *GPIO_PUP_PDN_CNTRL_REG1 = (*GPIO_PUP_PDN_CNTRL_REG1 & ~pupd_mask) | pupd_val; } else { printf("上下拉未指定,请使用'p'或'n'\n"); } } else { printf("当前引脚范围不支持此上下拉配置逻辑\n"); } } int main() { // 先完成内存映射 if (gpio_map() != 0) { exit(EXIT_FAILURE); } printf("GPIO映射虚拟地址: %p\n", gpio); Init_GPIO(18, 'i', 'l', 'p'); // 手动配置GPIO18为输入的验证代码 volatile uint32_t valueinput = 0b000 << ((18 - 10) * 3); volatile uint32_t mask = 0b111 << ((18 - 10) * 3); *GPFSEL1 = (*GPFSEL1 & ~mask) | valueinput; // 释放映射 gpio_unmap(); return 0; }
编译与运行说明
- 编译命令:
gcc gpio_config.c -o gpio_config - 必须以root权限运行:
sudo ./gpio_config(访问/dev/mem需要root权限)
内容的提问来源于stack exchange,提问作者Gambling Math
相关产品推荐
相关产品推荐

