在Linux系统中,通过/dev/mem
设备直接操作物理内存控制GPIO是一种底层方法,通常用于用户空间程序直接访问硬件寄存器。以下是分步指南和注意事项:
1. 准备工作
1.1 硬件文档
- 查阅SoC手册(如BCM2835/BCM2837用于树莓派),确定目标GPIO的:
- 物理地址基址(如GPIO0基址:
0x3F200000
) - 寄存器偏移量(如功能选择寄存器、输出设置寄存器等)
- 物理地址基址(如GPIO0基址:
1.2 权限配置
- 运行程序需
root
权限,或修改/dev/mem
权限:sudo chmod 666 /dev/mem
2. 用户空间操作步骤
2.1 打开/dev/mem
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
int fd = open("/dev/mem", O_RDWR | O_SYNC);
if (fd < 0) {
perror("Failed to open /dev/mem");
return -1;
}
2.2 映射物理地址到用户空间
#define GPIO_BASE 0x3F200000 // 示例:BCM2835 GPIO基址
#define PAGE_SIZE 4096
#define BLOCK_SIZE (4 * PAGE_SIZE)
volatile unsigned int *gpio = mmap(
NULL, // 虚拟地址由内核分配
BLOCK_SIZE, // 映射内存块大小
PROT_READ | PROT_WRITE, // 读写权限
MAP_SHARED, // 共享映射
fd, // 文件描述符
GPIO_BASE // 物理地址基址
);
if (gpio == MAP_FAILED) {
perror("mmap failed");
close(fd);
return -1;
}
2.3 操作GPIO寄存器
假设控制GPIO17(物理引脚11):
-
设置GPIO为输出模式:
#define GPFSEL1 (*(gpio + 1)) // 功能选择寄存器1(偏移0x04) GPFSEL1 &= ~(0b111 << (3 * 3)); // 清除GPIO17的配置位(3组,每组3位) GPFSEL1 |= (0b001 << (3 * 3)); // 设置为输出模式(0b001)
-
设置输出电平:
#define GPSET0 (*(gpio + 7)) // 输出设置寄存器(偏移0x1C) #define GPCLR0 (*(gpio + 10)) // 输出清除寄存器(偏移0x28) GPSET0 = (1 << 17); // 设置GPIO17为高电平 // GPCLR0 = (1 << 17); // 设置GPIO17为低电平
2.4 清理资源
munmap((void *)gpio, BLOCK_SIZE);
close(fd);
3. 完整示例代码
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#define GPIO_BASE 0x3F200000
#define PAGE_SIZE 4096
#define BLOCK_SIZE (4 * PAGE_SIZE)
int main() {
int fd = open("/dev/mem", O_RDWR | O_SYNC);
if (fd < 0) {
perror("open /dev/mem");
return 1;
}
volatile unsigned int *gpio = mmap(
NULL, BLOCK_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO_BASE
);
if (gpio == MAP_FAILED) {
perror("mmap");
close(fd);
return 1;
}
// 设置GPIO17为输出
#define GPFSEL1 (*(gpio + 1))
GPFSEL1 &= ~(0b111 << 15); // 清除位15-17(GPIO17配置)
GPFSEL1 |= (0b001 << 15); // 设置为输出模式
// 输出高电平
#define GPSET0 (*(gpio + 7))
GPSET0 = (1 << 17);
sleep(1);
// 输出低电平
#define GPCLR0 (*(gpio + 10))
GPCLR0 = (1 << 17);
munmap((void *)gpio, BLOCK_SIZE);
close(fd);
return 0;
}
4. 注意事项
- 平台依赖性:不同SoC的GPIO寄存器布局不同,需查阅具体手册。
- 权限风险:直接操作硬件可能导致系统崩溃,建议:
- 使用
sudo
运行程序。 - 避免在关键系统上运行未经验证的代码。
- 使用
- 内核驱动推荐:对于内核模块开发,优先使用Linux GPIO子系统:
#include <linux/gpio.h> gpio_request(gpio_num, "my_gpio"); gpio_direction_output(gpio_num, 1);
- 替代方案:使用用户空间库(如
wiringPi
或libgpiod
)简化操作。
5. 扩展建议
- 调试工具:使用
raspi-gpio
(树莓派)或devmem2
查看/修改寄存器。 - 内核模块:若需在内核中操作,使用
ioremap()
和writel()
等函数。
通过上述方法,您可以直接通过/dev/mem
控制GPIO,但需谨慎处理硬件细节和权限问题。