9. GPIO子系统
1. 基础知识
什么是GPIO?
• 定义:
GPIO(General-Purpose Input/ Output,通用输入输出)是一种灵活的硬件接口,允许开发者通过编程控制其工作模式(输入或输出),实现以下功能:
输出模式:主动控制引脚电平(高/低),用于驱动外部设备(如点亮LED、控制继电器)。
输入模式:读取外部信号的电平状态(如检测按键是否按下、传感器触发信号)。
• 典型应用场景:
控制简单外设:LED、蜂鸣器、继电器。
读取外部信号:按键、开关、传感器(如红外、温湿度)。
实现通信协议:作为I2C、SPI的片选(CS)信号,或自定义低速协议。
核心概念
1. 方向(Direction)
• 输入模式(Input):
引脚作为“信号接收端”,用于检测外部设备的状态。
示例:读取按键是否被按下(按键按下时引脚电平变化)。
• 输出模式(Output):
引脚作为“信号发送端”,主动控制外部设备。
示例:设置引脚电平为高,点亮LED。
2. 电平(Level)
物理电平:
• 高电平:实际电压值(如3.3V或5V,取决于硬件设计)。
• 低电平:接近0V的电压(如0V)。
逻辑电平:
• 逻辑1:对应高电平(1
)。
• 逻辑0:对应低电平(0
)。
逻辑值 | 物理电平(典型值) | 应用场景 |
---|---|---|
1 |
3.3V / 5V | 点亮LED、使能设备 |
0 |
0V | 关闭LED、禁用设备 |
3. 有效极性(Active Polarity)
作用:定义“有效状态”对应的逻辑电平,简化硬件设计。
GPIO_ACTIVE_HIGH(高有效):
◦ 有效状态 = 逻辑1
(高电平)。
◦ 示例:LED正极接GPIO,负极接地。当GPIO输出1
时,LED点亮。GPIO_ACTIVE_LOW(低有效):
◦ 有效状态 = 逻辑0
(低电平)。
◦ 示例:LED正极接电源,负极接GPIO。当GPIO输出0
时,LED点亮。
• 关键区别:
// 设备树中定义极性:
gpios = <&gpio0 5 GPIO_ACTIVE_HIGH>; // 高有效
gpios = <&gpio0 5 GPIO_ACTIVE_LOW>; // 低有效
// 驱动代码中设置电平:
gpiod_set_value(led, 1); // 无论极性如何,均表示“激活设备”
• 若极性为GPIO_ACTIVE_LOW
,gpiod_set_value(1)
会将物理电平设为0
,从而点亮LED。
为什么需要有效极性?
• 硬件设计灵活性:
某些电路需要低电平触发(如共阳极LED),通过配置极性可避免修改代码逻辑。
• 代码统一性:
驱动代码只需关注“激活/关闭设备”的逻辑值(1
或0
),无需关心具体电平。
总结
• GPIO是连接软件与硬件的桥梁,通过方向、电平、极性控制外部设备。
• 输入模式用于检测信号,输出模式用于驱动设备。
• 有效极性将逻辑值与物理电平解耦,简化开发和硬件设计。
4. 设备树配置
4.1 关闭系统默认的GPIO占用
在设备树中禁用系统自带的心跳灯(避免GPIO冲突):
// 文件:rk3568-evb.dtsi
&leds {
work_led: work {
status = "disabled"; // 关闭心跳灯
};
};
4.2 添加LED设备节点
在根节点下添加自定义LED配置:
// 文件:rk3568-atk-evb1-ddr4-v10.dtsi
/ {
gpioled {
compatible = "alientek,led"; // 驱动匹配标识
led-gpio = <&gpio0 RK_PC0 GPIO_ACTIVE_HIGH>; // GPIO0_C0引脚,高电平有效
status = "okay";
};
};
参数解析:
&gpio0
:GPIO控制器为GPIO0组。RK_PC0
:引脚号为C0(对应索引号16)。GPIO_ACTIVE_HIGH
:高电平激活LED。
4.3 验证设备树
编译并烧写设备树后,检查节点是否生效:
cat /proc/device-tree/gpioled/status # 应输出"okay"
5. 驱动代码实现
5.1 完整驱动代码(gpioled.c)
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#define GPIOLED_NAME "gpioled"
#define LED_ON 1
#define LED_OFF 0
struct gpioled_dev {
struct device_node *nd; // 设备树节点
int led_gpio; // GPIO编号
};
static struct gpioled_dev gpioled;
// 文件操作函数:写操作控制LED
static ssize_t led_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt) {
u8 val;
if (copy_from_user(&val, buf, cnt)) // 从用户空间获取值
return -EFAULT;
gpio_set_value(gpioled.led_gpio, val); // 设置GPIO电平
return cnt;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.write = led_write,
};
// 驱动初始化
static int __init led_init(void) {
int ret;
// 1. 获取设备树节点
gpioled.nd = of_find_node_by_path("/gpioled");
if (!gpioled.nd) {
printk("Failed to find gpioled node!\n");
return -ENODEV;
}
// 2. 解析GPIO编号
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
if (gpioled.led_gpio < 0) {
printk("Invalid GPIO: %d\n", gpioled.led_gpio);
return -EINVAL;
}
// 3. 申请GPIO资源
ret = gpio_request(gpioled.led_gpio, "led-gpio");
if (ret) {
printk("GPIO %d request failed!\n", gpioled.led_gpio);
return ret;
}
// 4. 配置为输出模式,初始低电平
gpio_direction_output(gpioled.led_gpio, LED_OFF);
// 5. 注册字符设备
ret = register_chrdev(0, GPIOLED_NAME, &fops);
if (ret < 0) {
gpio_free(gpioled.led_gpio);
printk("Failed to register chrdev!\n");
return ret;
}
printk("LED driver loaded. GPIO: %d\n", gpioled.led_gpio);
return 0;
}
// 驱动卸载
static void __exit led_exit(void) {
gpio_set_value(gpioled.led_gpio, LED_OFF); // 关闭LED
gpio_free(gpioled.led_gpio); // 释放GPIO
unregister_chrdev(0, GPIOLED_NAME); // 注销设备
printk("LED driver unloaded.\n");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
5.2 关键代码解析
设备树交互:
of_find_node_by_path
:通过路径查找设备树节点。of_get_named_gpio
:从设备树属性解析GPIO编号。
GPIO操作:
gpio_request
:申请GPIO资源。gpio_direction_output
:设置为输出模式并初始化电平。
用户空间接口:
led_write
:接收用户指令(1
或0
)控制LED亮灭。
6. 编译与测试
6.1 编译驱动
使用Makefile编译驱动:
obj-m += gpioled.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
执行编译:
make
6.2 加载驱动并测试
insmod gpioled.ko # 加载驱动
echo 1 > /dev/gpioled # 点亮LED
echo 0 > /dev/gpioled # 熄灭LED
rmmod gpioled # 卸载驱动
6.3 验证GPIO状态
gpioinfo gpiochip0 # 查看GPIO0所有引脚状态
gpioget gpiochip0 16 # 读取GPIO0_C0电平(应为0或1)
7. 常见问题排查
GPIO申请失败:
检查设备树是否禁用系统默认占用(如心跳灯)。
使用
dmesg
查看内核日志,确认冲突信息。
电平反向问题:
修改设备树的
GPIO_ACTIVE_HIGH/LOW
属性。硬件检查:确认LED正负极接线正确。
驱动加载失败:
确保设备树节点
compatible
值与驱动匹配。检查GPIO编号是否正确(
of_get_named_gpio
返回值)。
总结
通过设备树配置GPIO引脚并编写对应驱动,可实现灵活的硬件控制。关键步骤包括:
设备树配置:禁用冲突节点,定义GPIO属性。
驱动开发:解析设备树、申请GPIO、实现用户接口。
调试验证:使用
gpioinfo
/gpioget
工具确保硬件与软件一致。
下一步可扩展功能:添加中断处理、PWM控制或结合其他外设(如按键)实现复杂交互。 可参考https://blog.guoxiaozhong.cn/archives/1706491900645#2-2-gpio%E5%AD%90%E7%B3%BB%E7%BB%9FAPI%E5%87%BD%E6%95%B0