linux驱动之二、AM4378按键驱动(while1轮询方式,中断方式见下一节)

序言:
本文记录以rico board开发板为硬件平台,主控芯片为AM4378,实现按键驱动代码的过程。
同上一篇博客:【linux驱动之一、LED驱动】思路相同,步骤如下:
1、写驱动框架
2、看电路图找到按键所在GPIO口
3、看芯片手册,看怎样将GPIO口设置成输入模式,都需要配置哪些寄存器,以及每个寄存器的地址
4、完善驱动代码,填充到对应的接口中
5、写测试应用程序
6、验证功能

附:github工程连接:https://github.com/Warrior-Asa/key_drv_while1/blob/master/key_drv.c

一、写驱动框架

代码如下:

/* 头文件 */
#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 <linux/device.h>

/* 设备号 & class */
int major;
static struct class *key_drv_class;

/* 配置寄存器为输入模式 */
static int key_drv_open(struct inode *inode, struct file *file)
{
    return 0;
}

/* 读取寄存器的值,并返回给用户层 */
static ssize_t key_drv_read(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    return 0;
}

/* 结构体 */
static struct file_operations key_drv_fops = {
    .owner = THIS_MODULE,
    .open  = key_drv_open,
    .read  = key_drv_read,
};

/* 驱动入口函数 */
static int key_drv_init(void)
{
    major = register_chrdev(0, "key_drv", &key_drv_fops);

    key_drv_class = class_create(THIS_MODULE, "class_key_drv");
    device_create(key_drv_class, NULL, MKDEV(major, 0), NULL, "device_button"); /* 根据udev机制,该接口调用后会在/dev/目录下生成设备,名字为:device_button,上层应用程序要操作设备的话,需要open该设备名称,即:fd = open("/dev/device_button", O_RDWR); */

    return 0;
}

/* 驱动出口函数 */
static void key_drv_exit(void)
{
    unregister_chrdev(major, "key_drv"); /* 卸载驱动程序,告诉内核 */

    device_destroy(key_drv_class, MKDEV(major, 0));
    class_destroy(key_drv_class);
}

/* 声明 */
module_init(key_drv_init);
module_exit(key_drv_exit);
MODULE_LICENSE("Dual BSD/GPL");

二、查看电路图,找GPIO口

看下图,可以看到sw4对应GPIO口为:GPIO5_13
在这里插入图片描述
在这里插入图片描述

三、查看芯片手册,找寄存器&配置方法

3.1 GPIO口输入模式

查看芯片手册第28.3.4.3章节,可以看到如下描述:
在这里插入图片描述
大致意思是:当我们将GPIO口配置成输入模式的话,需要将GPIO_OE寄存器对应位设置成1,然后从GPIO_DATAIN寄存器对应位就可以读出从外部捕获的电平高低值了。
继续往下看,查找这两个寄存器的详细描述。

3.2 GPIO_OE寄存器

如下图:可以看到,需要将该寄存器对应位配置成1,则表示相应的GPIO口为input功能。另外:偏移地址为:0x134
在这里插入图片描述

3.3 GPIO_DATAIN寄存器

没什么好说的,只读寄存器,看清地址,配置好以后只需要读该寄存器即可。偏移地址为0x138
在这里插入图片描述

3.4 使用宏定义寄存器地址

(1)查看GPIO5基地址(找memory map之类的章节):可以看到GPIO5起始地址为:0x48322000
在这里插入图片描述
(2)宏定义地址如下:

/* 寄存器地址 */
#define GPIO5_BASE_ADDR   0x48322000
#define GPIO5_OE_ADDR     ((GPIO5_BASE_ADDR) + 0x134)
#define GPIO5_DATAIN_ADDR ((GPIO5_BASE_ADDR) + 0x138)

四、完善驱动代码(完整代码)

#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 <linux/device.h>

/* 寄存器地址 */
#define GPIO5_BASE_ADDR   0x48322000
#define GPIO5_OE_ADDR     ((GPIO5_BASE_ADDR) + 0x134)
#define GPIO5_DATAIN_ADDR ((GPIO5_BASE_ADDR) + 0x138)

/* 寄存器别名 */
volatile unsigned long *gpio5_oe = NULL;
volatile unsigned long *gpio5_datain = NULL;

int major;
static struct class *key_drv_class;

/* 配置寄存器为输入模式 */
static int key_drv_open(struct inode *inode, struct file *file)
{
    *gpio5_oe |= (1 << 13);
    return 0;
}

/* 读取寄存器的值,并返回给用户层 */
static ssize_t key_drv_read(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    int regValue;
    unsigned char key_vals[4]; /* 保存引脚电平状态 */

    regValue = *gpio5_datain;  /* 读取当前寄存器的值 */
    key_vals[0] = ((regValue & (1<<13)) ? 1 : 0); /* 判断读取到的电平状态是高还是低,将状态结果保存在key_vals[0]中 */

    copy_to_user(buf, key_vals, sizeof(key_vals)); /* 读取到的高低电平状态上传给用户层 */

    return sizeof(key_vals); /* 返回 */
}

/* 结构体 */
static struct file_operations key_drv_fops = { 
    .owner = THIS_MODULE,
    .open  = key_drv_open,
    .read  = key_drv_read,
};

/* 驱动入口函数 */
static int key_drv_init(void)
{
    major = register_chrdev(0, "key_drv", &key_drv_fops);

    key_drv_class = class_create(THIS_MODULE, "class_key_drv");
    device_create(key_drv_class, NULL, MKDEV(major, 0), NULL, "device_button"); /* 根据udev机制,该接口调用后会在/dev/目录下生成设备,名字为:device_button,上层应用
程序要操作设备的话,需要open该设备名称,即:fd = open("/dev/device_button", O_RDWR); */

    /* 地址映射 */
    gpio5_oe     = (volatile unsigned long *)ioremap(GPIO5_OE_ADDR, 32);
    gpio5_datain = (volatile unsigned long *)ioremap(GPIO5_DATAIN_ADDR, 32);

    return 0;
}

/* 驱动出口函数 */
static void key_drv_exit(void)
{
    unregister_chrdev(major, "key_drv"); /* 卸载驱动程序,告诉内核 */

    device_destroy(key_drv_class, MKDEV(major, 0));
    class_destroy(key_drv_class);

    /* 删除地址映射 */
    iounmap(gpio5_oe);
    iounmap(gpio5_datain);
}

/* 声明 */
module_init(key_drv_init);
module_exit(key_drv_exit);
MODULE_LICENSE("Dual BSD/GPL");

五、写测试应用程序

#include <sys/types.h>                                                                                                                                               
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

int main(int argc, char **argv)
{
    int fd; 
    int cnt = 0; /* 计数 */
    unsigned char key_value[4]; /* 底层驱动代码会将要获取的结果保存在此变量中 */

    fd = open("/dev/device_button", O_RDWR); /* 打开要操作的设备 */
    if (fd < 0) {
        printf("can`t open!\n");
    }   

    while(1) {
        read(fd, key_value, sizeof(key_value)); /* 和驱动代码通信,获取电平高低状态,保存在key_value中 */
        if (key_value[0] == 0) {
            printf("times: %d, button pressed.\r\n", cnt++);
        }   
    }   

    printf("ok\r\n");

    return 0;
}

六、验证功能

按键驱动程序:未使用中断功能,直接使用死循环进行轮训监测。

操作步骤:

1、linux开发环境中执行:

make

—> 生成驱动程序:key_drv.ko

arm-linux-gnueabihf-gcc app_test_key.c -o app_test_key

—>生成测试程序:app_test_key

2、 将上述两个程序拷贝到单板中

3、板卡中执行指令:

insmod key_drv.ko
lsmod
./app_test_key

4、 按下按键,观察现象


有图有真相:
之所以按一次按键会反复打印,是因为用的while1循环执行的原因这样cpu资源也会被耗尽,所以正式开发中不会采用这种方式,一般使用中断的方式,下一节会记录如何使用按键中断的方式来实现捕获外部电平。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值