字符驱动设备之按键驱动
- 硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3)
- 软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
- 参考资料:《嵌入式Linux应用开发手册》
- 开发环境:Linux 2.6.22.6 内核、arm-linux-gcc-3.4.5-glibc-2.3.6工具链
前言
在本片博文中,编写按键驱动,通过查询的方式得到按键信息。
一、框架搭建
1、编写驱动程序
static int button_drv_open(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t button_drv_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
{
return 0;
}
2、告诉内核有这个驱动程序
2.1 构建file_operations()结构体
static struct file_operations button_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = button_drv_open,
.read = button_drv_read,
};
2.2 构建入口函数button_drv_init()
由内核自动分配主设备节点、自动创建设备节点
static struct class *buttondrv_class;
static struct class_device *buttondrv_class_dev;
int major;
static int button_drv_init(void)
{
major = register_chrdev(0, "button_drv", &button_drv_fops); // 注册, 告诉内核
buttondrv_class = class_create(THIS_MODULE, "buttondrv");
buttondrv_class_dev = class_device_create(buttondrv_class, NULL, MKDEV(major, 0), NULL, "button"); /* /dev/button */
return 0;
}
2.3 构建出口函数button_drv_exit()
static void button_drv_exit(void)
{
unregister_chrdev(major, "button_drv"); // 卸载
class_device_unregister(buttondrv_class_dev);
class_destroy(buttondrv_class);
}
3、修饰入口、出口函数并添加模块的许可证声明
module_init(button_drv_init);
module_exit(button_drv_exit);
MODULE_LICENSE("GPL");
4、完整的框架代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
static struct class *buttondrv_class;
static struct class_device *buttondrv_class_dev;
static int button_drv_open(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t button_drv_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
{
return 0;
}
static struct file_operations button_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = button_drv_open,
.read = button_drv_read,
};
int major;
static int button_drv_init(void)
{
major = register_chrdev(0, "button_drv", &button_drv_fops); // 注册, 告诉内核
buttondrv_class = class_create(THIS_MODULE, "buttondrv");
buttondrv_class_dev = class_device_create(buttondrv_class, NULL, MKDEV(major, 0), NULL, "button"); /* /dev/button */
return 0;
}
static void button_drv_exit(void)
{
unregister_chrdev(major, "button_drv"); // 卸载
class_device_unregister(buttondrv_class_dev);
class_destroy(buttondrv_class);
}
module_init(button_drv_init);
module_exit(button_drv_exit);
MODULE_LICENSE("GPL");
5、修改Makefile文件
KERN_DIR = /work/system/linux-2.6.22.6
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += button_drv.o
5、编译验证
- 通过服务器上传到虚拟机,执行
make
- 通过NFS网络系统,把
button_drv.ko
文件上传到开发版的根文件系统 - 加载驱动,观察功能是否可以正常实现。
二、硬件操作
1、查看原理图
由原理图可知
按键S2-EINT0-GPF0 按键S3-EINT2-GPF2
按键S4-EINT11-GPG3 按键S5-EINT19-GPG11
2、查看s3c2440芯片手册
3、代码编写
3.1 代码分布
button_drv_init()
中完成地址映射button_drv_open()
中完成IO口设置为输入button_drv_read()
中完成按键信息的读取
3.2 驱动代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
int major;
static struct class *buttondrv_class;
static struct class_device *buttondrv_class_dev;
volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;
volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;
static int button_drv_open(struct inode *inode, struct file *file)
{
/* 配置GPF0,2为输入 */
*gpfcon &= ~((0x3<<(0*2)) | (0x3<<(2*2)));
/* 配置GPF3、11为输出 */
*gpfcon &= ~((0x3<<(3*2)) | (0x3<<(11*2)));
return 0;
}
static ssize_t button_drv_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
{
int key_values[4]; //0-gdf0, 1-gdf2, 2-gdg3, 3-gdg11
int value;
unsigned long ret = 0;
/* 如果字节数不对,则返回 */
if(nbytes != sizeof(key_values))
return -;EINVAL
/* 读取GPF0,2 */
value = *gpfdat;;
key_values[0] = (value & (1<<0));
key_values[1] = (value & (1<<2));
/* 读取GPG3,11 */
value = *gpgdat;
key_values[2] = (value & (1<<3));
key_values[3] = (value & (1<<11));
/* 数据给到用户?*/
ret = copy_to_user(buf, key_values, sizeof(key_values));
if(ret < 0){
printk("func button_drv_read() err: copy_to_user");
return -EFAULT;
}
return sizeof(key_values);
}
static struct file_operations button_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = button_drv_open,
.read = button_drv_read,
};
static int button_drv_init(void)
{
major = register_chrdev(0, "button_drv", &button_drv_fops); //注册一个字符设备,名字为button_drv
buttondrv_class = class_create(THIS_MODULE, "button_drv"); //创建一个类,名字为buttond_rv(/class/button_drv)
buttondrv_class_dev = class_device_create(buttondrv_class, NULL, MKDEV(major, 0), NULL, "button"); //创建一个设备节点,名为button(/dev/button)
/* 映射物理地址 */
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
gpfdat = gpfcon + 1;
gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
gpgdat = gpfcon + 1;
return 0;
}
static void button_drv_exit(void)
{
unregister_chrdev(major, "button_drv"); // 卸载字符设备
class_device_unregister(buttondrv_class_dev); //删除设备节点
class_destroy(buttondrv_class); //销毁类
/* 取消映射 */
iounmap(gpfcon);
iounmap(gpgcon);
}
module_init(button_drv_init);
module_exit(button_drv_exit);
MODULE_LICENSE("GPL");
3.3 测试代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
/* buttondrvtest on
* buttondrvtest off
*/
int main(int argc, char **argv)
{
int fd;
int ret = 0;
int key_value[4] = {0};
int count = 0;
fd = open("/dev/button", O_RDWR);
if (fd < 0){
printf("can't open!\n");
return -1;
}
while(1){
ret = read(fd, key_value, sizeof(key_value));
if(ret < 0){
printf(" func read() err\n");
}else{
if(!key_value[0] || !key_value[1] || !key_value[2] || !key_value[3]){
printf("%04d key pressed: %d %d %d %d\n",
count++, key_value[0], key_value[1], key_value[2]
, key_value[3]);
}
}
}
return ret;
}
三、烧写验证
- 通过服务器上传到虚拟机,执行
make
。 - 通过NFS网络系统,把
button_drv.ko
文件上传到开发版的根文件系统。 - 加载驱动,观察功能是否可以正常实现。
- 交叉编译
buttondrvtest.c
文件,得到buttondrvtest.o
文件上传到开发版的根文件系统。 - 执行
./buttondrvtest
。
可以看到正常运行。
四、查询方式缺点
1、不确定性高
2、CPU占用率大
可以看到,运行测试程序的时候CPU的占用率到达了恐怖的99%,这是因为测试程序一直在读取键值。