(TODO)
在树莓派 3B 上,直接通过操作寄存器控制 GPIO26 的 LED,而不使用 `gpio` 子系统和 `pinctrl` 子系统,可以通过直接访问 **Broadcom BCM2837 SoC** 的 GPIO 寄存器来实现。我们将通过内核模块,手动访问 GPIO 寄存器,并控制 GPIO26 的输出来点亮 LED。
### 1. 了解树莓派 GPIO 寄存器
树莓派的 GPIO 控制器有以下重要的寄存器:
- **GPIO Function Select Registers (`GPFSELx`)**:用于配置引脚的功能(输入、输出或其他外围功能)。
- **GPIO Pin Output Set Registers (`GPSETx`)**:用于设置 GPIO 引脚的输出(置位)。
- **GPIO Pin Output Clear Registers (`GPCLRx`)**:用于清除 GPIO 引脚的输出(复位)。#### 树莓派 3B 的 GPIO26 对应的寄存器:
- **GPIO26** 对应于:
- **GPFSEL2**:GPIO26 的功能选择寄存器(用于设置 GPIO26 为输出模式)。
- **GPSET0**:用于将 GPIO26 置为高电平(点亮 LED)。
- **GPCLR0**:用于将 GPIO26 置为低电平(关闭 LED)。#### GPIO 基地址:
在树莓派 3B(BCM2837)上,**外设寄存器**的基地址为 `0x3F000000`。GPIO 寄存器位于基地址的偏移 `0x200000` 处,因此 GPIO 基地址为:
```
GPIO_BASE = 0x3F200000
```### 2. 编写内核模块:直接操作寄存器控制 LED
下面的代码将通过直接访问 GPIO 寄存器控制 GPIO26 上的 LED。我们将手动设置 GPIO26 为输出模式,并控制它的电平。
#### `my_gpio_led.c`
```c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/io.h> // 用于内存映射IO
#include <linux/delay.h> // 延迟函数#define BCM2837_PERI_BASE 0x3F000000 // 树莓派 3B 外设基地址
#define GPIO_BASE (BCM2837_PERI_BASE + 0x200000) // GPIO 基地址#define GPFSEL2 (gpio_base + 0x08) // GPFSEL2 控制 GPIO 20-29
#define GPSET0 (gpio_base + 0x1C) // GPSET0 控制 GPIO 0-31 的置位
#define GPCLR0 (gpio_base + 0x28) // GPCLR0 控制 GPIO 0-31 的清除#define GPIO_PIN 26 // 使用 GPIO 26 控制 LED
static void __iomem *gpio_base; // GPIO寄存器的映射基地址
// 初始化 GPIO26 为输出模式
static void gpio_init(void) {
unsigned int reg_val;// 设置 GPIO26 为输出模式
reg_val = ioread32(GPFSEL2); // 读取 GPFSEL2 的值
reg_val &= ~(7 << 18); // 清除 GPIO26 的 FSEL 位 (三位控制每个 GPIO)
reg_val |= (1 << 18); // 设置 GPIO26 为输出 (001 = 输出)
iowrite32(reg_val, GPFSEL2); // 写回 GPFSEL2
}// 点亮 LED (设置 GPIO26 输出高电平)
static void led_on(void) {
iowrite32((1 << GPIO_PIN), GPSET0); // 设置 GPIO26
}// 关闭 LED (设置 GPIO26 输出低电平)
static void led_off(void) {
iowrite32((1 << GPIO_PIN), GPCLR0); // 清除 GPIO26
}// 模块加载函数
static int __init my_led_init(void) {
pr_info("LED GPIO Module loaded\n");// 映射 GPIO 基地址
gpio_base = ioremap(GPIO_BASE, 0x100); // 映射寄存器地址
if (!gpio_base) {
pr_err("Failed to map GPIO base address\n");
return -EBUSY;
}// 初始化 GPIO26 为输出模式
gpio_init();// 点亮 LED
led_on();
pr_info("LED on (GPIO26)\n");return 0;
}// 模块卸载函数
static void __exit my_led_exit(void) {
// 关闭 LED
led_off();
pr_info("LED off (GPIO26)\n");// 解除 GPIO 基地址映射
if (gpio_base)
iounmap(gpio_base);pr_info("LED GPIO Module unloaded\n");
}module_init(my_led_init);
module_exit(my_led_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Direct GPIO control for LED on Raspberry Pi 3B");
```### 3. 编写 `Makefile`
编写一个 `Makefile` 文件用于编译内核模块:
```makefile
obj-m += my_gpio_led.oall:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modulesclean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
```### 4. 编译和加载内核模块
1. **编译模块**:
在 `my_gpio_led.c` 文件和 `Makefile` 所在目录下,执行以下命令进行编译:```bash
make
```2. **加载模块**:
使用 `insmod` 将编译好的内核模块加载到内核中:```bash
sudo insmod my_gpio_led.ko
```3. **检查内核日志**:
使用 `dmesg` 查看模块加载时的内核日志:```bash
dmesg | tail
```日志应显示 "LED on (GPIO26)",表明 GPIO26 上的 LED 已经点亮。
4. **卸载模块**:
当你想关闭 LED 并卸载模块时,使用以下命令:```bash
sudo rmmod my_gpio_led
```再次使用 `dmesg` 查看日志,应该显示 "LED off (GPIO26)",表示 LED 已关闭。
### 5. 解释代码逻辑
- **`ioremap` 和 `iounmap`**:这些函数用于将物理地址映射到虚拟地址空间,允许内核模块访问设备的寄存器。我们通过 `ioremap` 将 GPIO 基地址映射到内存。
- **寄存器操作**:
- `GPFSEL2`:设置 GPIO26 为输出模式。
- `GPSET0`:将 GPIO26 设置为高电平,点亮 LED。
- `GPCLR0`:将 GPIO26 设置为低电平,关闭 LED。
- **点亮和关闭 LED**:通过 `led_on()` 和 `led_off()` 函数直接操作 GPIO26 的寄存器。### 6. 总结
在这个例子中,我们跳过了 Linux 的 `gpio` 和 `pinctrl` 子系统,直接操作树莓派 3B 的 GPIO 寄存器,通过 `ioremap` 将寄存器映射到内存,并直接控制 GPIO26 的电平来点亮和关闭 LED。
这种方法提供了更低级的硬件访问,但需要更多的寄存器级别的控制。如果您需要对 GPIO 进行更复杂的操作,通常建议使用 `gpio` 子系统和 `pinctrl` 子系统。