一、注册字符设备
二、操作硬件
2.1 操作按键
2.2 通用方式实现
2.3 gpiolib实现
三、源码
3.1 通用方式实现
3.2 gpiolib实现
四、测试
本文目标:实现一个按键驱动,通过read函数非阻塞读取按键的状态
一、注册字符设备
首先第一步,先编写字符设备的框架,这一部分在Linux驱动入门(一)字符设备驱动基础中已经做了详细介绍,这里直接给出源码,不做过多的解释
#include #include static dev_t dev_id; int button_open(struct inode *inode, struct file *file) return 0; ssize_t button_read(struct file *file, char __user *data, size_t size, loff_t *loff) /* 读取按键状态 */ /* 将结果放回给应用层 */ return 0; static struct file_operations button_fops = { static __init int button_init(void) /* 分配字符设备 */ /* 设置字符设备 */ /* 注册字符设备 */ /* 创建设备节点 */ return 0; static __exit void button_exit(void) /* 注销字符设备 */ /* 注销注册的设备号 */ module_init(button_init); MODULE_LICENSE('GPL'); 这里介绍一个知识点,用户空间和内核空间不能通过指针直接访问,必须使用内核提供的函数 从用户空间到内核空间使用 static inline long copy_from_user(void *to, static inline long copy_to_user(void __user *to, 上述程序还未涉及到硬件,下面来讲一讲如何操作按键 二、操作硬件 这里我想操作SW5,所以要看SW5接在芯片的哪个引脚,继续在原理图中搜索EINT2 发现EIN2接在GPH0_2引脚上 从原理图上可以看出,当按键没有按下的时候,GPIO为高电平,按下时,GPIO为低电平,所以要看按键是否被按下,只需要查看GPIO是否为低电平 下面查看芯片的datasheet,查看如何读取读取GPIO的高低电平状态 从芯片手册中找到了下面两个寄存器 v GPH0CON设置GPIO的功能,GPH0DAT设置或者读取GPIO的值 要读取GPH0_2引脚的状态,可以这样做 GPH0CON &= (0xF<<8); //将GPH0_2设置为输入模式 int val = GPH0DAT & (1<<2); //读取GPH0_2的状态 2.2 通用方式实现 首先从datasheet中找到寄存器的物理地址 #define GPH0CON_PHY_ADDR 0xE0200C00 static volatile unsigned int *gph0_con = NULL; gph0_con = (volatile unsigned int *)ioremap(GPH0CON_PHY_ADDR, 8); unsigned int cfg; cfg = readl(gph0_con); unsigned int val = readl(gph0_dat); iounmap(gph0_con); gpio_direction_input(S5PV210_GPH0(2)); int val = gpio_get_value(S5PV210_GPH0(2)); gpio_free(S5PV210_GPH0(2)); #include #define GPH0CON_PHY_ADDR 0xE0200C00 static dev_t dev_id; static volatile unsigned int *gph0_con = NULL; int button_open(struct inode *inode, struct file *file) /* 将GPIO设置为输入模式 */ return 0; ssize_t button_read(struct file *file, char __user *data, size_t size, loff_t *loff) ret = copy_to_user(data, &val, sizeof(val)); return 0; static struct file_operations button_fops = { static __init int button_init(void) /* 分配字符设备 */ /* 设置字符设备 */ /* 注册字符设备 */ /* 创建设备节点 */ /* 映射物理地址 */ return 0; static __exit void button_exit(void) /* 注销字符设备 */ /* 注销注册的设备号 */ /* 注销映射的地址 */ module_init(button_init); MODULE_LICENSE('GPL'); #include static dev_t dev_id; int button_open(struct inode *inode, struct file *file) return 0; ssize_t button_read(struct file *file, char __user *data, size_t size, loff_t *loff) ret = copy_to_user(data, &val, sizeof(val)); return 0; static struct file_operations button_fops = { static __init int button_init(void) /* 分配字符设备 */ /* 设置字符设备 */ /* 注册字符设备 */ /* 创建设备节点 */ gpio_request(S5PV210_GPH0(2), 'button'); return 0; static __exit void button_exit(void) /* 注销字符设备 */ /* 注销注册的设备号 */ gpio_free(S5PV210_GPH0(2)); module_init(button_init); MODULE_LICENSE('GPL'); 保存下面Makefile KERN_DIR = /mFile/arm/boardSupport/linux+QT4.8/bsp/kernel/ all: clean: obj-m += button_drv.o 应用测试程序 #include #define BUTTON_DEV '/dev/button' int main(int argc, char* argv[]) while(1) usleep(1000*10); //10ms close(fd); return 0; 执行./a.out,当按下按键的时候,可以看到控制台打印出'button press'
#include
#include
#include
#include
#include
#include
static struct cdev *button_dev;
static struct class *button_class;
{
/* 将GPIO设置为输入模式 */
}
{
int ret;
ret = copy_to_user(data, &val, sizeof(val));
}
.owner = THIS_MODULE,
.open = button_open,
.read = button_read,
};
{
/* 申请设备号 */
alloc_chrdev_region(&dev_id, 1, 1, 'button');
button_dev = cdev_alloc();
cdev_init(button_dev, &button_fops);
cdev_add(button_dev, dev_id, 1);
button_class = class_create(THIS_MODULE, 'button'); //创建类
device_create(button_class, NULL, dev_id, NULL, 'button'); //创建设备节点
}
{
/* 注销设备节点 */
device_destroy(button_class, dev_id);
class_destroy(button_class);
cdev_del(button_dev);
kfree(button_dev);
unregister_chrdev_region(dev_id, 1);
}
module_exit(button_exit);
编译上述驱动程序,就在模块,就会生成/dev/button设备节点,这是我们的按键驱动的基本框架,我们还需要在button_open和button_read中操作硬件来完善驱动程序
const void __user * from, unsigned long n)
从内核空间到用户空间使用
const void *from, unsigned long n)
例如在button_read中使用copy_to_user将结果返还给应用层
2.1 操作按键
首先打开原理图,查看按键
在Linux驱动入门(二)操作硬件中详细地讲解了如何去操作硬件,分别有通用方法和使用gpiolib的方法,下面将介绍这两种方法
通过直接操作寄存器的方式,来操作按键
#define GPH0DAT_PHY_ADDR 0xE0200C04
映射物理地址
static volatile unsigned int *gph0_dat = NULL;
gph0_dat = gph0_con+1;
设置GPH0_2为输入模式
writel(cfg & ~(0xF<<8), gph0_con);
读取GPH0_2的状态
val = val & (1<<2);
取消地址映射
2.3 gpiolib实现
申请GPIO
设置为输入模式
获取GPIO的状态
释放GPIO
三、源码
3.1 通用方式实现
#include
#include
#include
#include
#include
#include
#include
#define GPH0DAT_PHY_ADDR 0xE0200C04
static struct cdev *button_dev;
static struct class *button_class;
static volatile unsigned int *gph0_dat = NULL;
{
unsigned int cfg;
cfg = readl(gph0_con);
writel(cfg & ~(0xF<<8), gph0_con);
}
{
int ret;
unsigned int val = readl(gph0_dat);
val = val & (1<<2);
}
.owner = THIS_MODULE,
.open = button_open,
.read = button_read,
};
{
/* 申请设备号 */
alloc_chrdev_region(&dev_id, 1, 1, 'button');
button_dev = cdev_alloc();
cdev_init(button_dev, &button_fops);
cdev_add(button_dev, dev_id, 1);
button_class = class_create(THIS_MODULE, 'button'); //创建类
device_create(button_class, NULL, dev_id, NULL, 'button'); //创建设备节点
gph0_con = (volatile unsigned int *)ioremap(GPH0CON_PHY_ADDR, 8);
gph0_dat = gph0_con+1;
}
{
/* 注销设备节点 */
device_destroy(button_class, dev_id);
class_destroy(button_class);
cdev_del(button_dev);
kfree(button_dev);
unregister_chrdev_region(dev_id, 1);
iounmap(gph0_con);
}
module_exit(button_exit);
3.2 gpiolib实现
#include
#include
#include
#include
#include
#include
#include
#include
static struct cdev *button_dev;
static struct class *button_class;
{
/* 将GPIO设置为输出模式 */
gpio_direction_input(S5PV210_GPH0(2));
}
{
int ret;
int val = gpio_get_value(S5PV210_GPH0(2));
}
.owner = THIS_MODULE,
.open = button_open,
.read = button_read,
};
{
/* 申请设备号 */
alloc_chrdev_region(&dev_id, 1, 1, 'button');
button_dev = cdev_alloc();
cdev_init(button_dev, &button_fops);
cdev_add(button_dev, dev_id, 1);
button_class = class_create(THIS_MODULE, 'button'); //创建类
device_create(button_class, NULL, dev_id, NULL, 'button'); //创建设备节点
}
{
/* 注销设备节点 */
device_destroy(button_class, dev_id);
class_destroy(button_class);
cdev_del(button_dev);
kfree(button_dev);
unregister_chrdev_region(dev_id, 1);
}
module_exit(button_exit);
四、测试
将上面两个驱动任意一个保存为button_drv.c
make -C $(KERN_DIR) M=`pwd` modules
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
修改内核源码树,执行make,生成button_drv.ko,加载模块
#include
#include
#include
#include
{
int val;
int fd = open(BUTTON_DEV, O_RDONLY);
if(fd < 0)
{
printf('failed to open %sn', BUTTON_DEV);
return -1;
}
{
read(fd, &val, sizeof(val));
if(val == 0)
{
printf('button pressn');
}
}
}
将上述程序保存为button_test.c,执行arm-linux-gcc button_test.c
上一篇:Linux驱动入门(三)Led驱动
下一篇:Linux驱动入门(五)阻塞方式实现按键驱动
推荐阅读最新更新时间:2026-03-25 10:40
- 用于 7VIN 至 16VIN、1.5V 和 1.2V 输出的 LTM4628EV DC/DC 模块稳压器的典型应用电路
- 使用 Analog Devices 的 LTC3728LIGN 的参考设计
- DER-406 - 适用于 A19 灯的 5.76 W 高 PF 非隔离降压-升压型 TRIAC 调光 LED 驱动器
- ADR5045B 5V 输出精密微功率并联模式电压基准的典型应用
- LT3970EDDB-3.42 2.5V 降压转换器的典型应用
- MC78M08BDTG 8V 电流调节器的典型应用
- LT1021DCN8-5 精密电压基准的典型应用
- DER-282 - 100W, 扁平(11 mm), LLC DC-DC转换器
- REF193 低压差开尔文连接电压基准的典型应用电路
- LT3088EM 线性稳压器用于添加软启动的典型应用

Linux技术手册
【2025 DigiKey创意大赛】 学伴智盒 - 代码
智能机械臂
非常经典的关于LLC的杨波博士论文
ASM10DTBD-S664






京公网安备 11010802033920号