首页 > 编程语言 >基于总线设备驱动模型的按键读取驱动程序

基于总线设备驱动模型的按键读取驱动程序

时间:2024-05-08 12:23:19浏览次数:26  
标签:__ 驱动程序 引脚 int button 总线 buttons include 读取

本次实验基于总线设备驱动模型实现按键驱动程序的编写,给上层应用程序提供检测按键是否按下的操作接口,上层应用根据按键是否按下控制led的亮灭。所以上层应用程序会同时使用led和按键的驱动接口,但是对于下层驱动而言,这二者是分离的,因此只需要专注于编写按键驱动程序就可以了。
在正点原子imx6ull开发板上按键原理图如下:

按键KEY0接到的引脚名字是uart1_cts,查找芯片参考手册第28章(GPIO章节),找到下面这个表

从表中找到GPIO1_IO18这一项,写着UART1_CTS_B,跟原理图上引脚名一致,说明这是按键Key0对应的引脚。我们要读取按键是否按下,因此我们需要把UART1_CTS_B这个引脚也即GPIO1_IO18设置为GPIO功能(GPIO功能代指普通的输入输出功能)。与led驱动程序的步骤类似,我们需要查看GPIO章节中有关引脚的原理图,其涉及到IOMUXC控制器、CMM时钟控制器,以及相关的GPIO控制器。
(1)CCM时钟控制器

在CCM控制器章节找到CCGR1寄存器的CG13位域控制GPIO1模块的时钟使能,接着在CCM章节的寄存器映射小节找到寄存器地址(20C_406Ch)及各位域的配置方式

(2)IOMUC复用控制器
在IOMUXC控制器章节找到复用寄存器IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B(20E_008Ch)

(2)GPIO控制器
需要将GPIO设置为输入功能,因此涉及GDIR和PSR寄存器,其中GDIR将GPIO配置为输入,PSR寄存器用于读取引脚输入状态

在GPIO章节找到上表可以查看到GPIO1相关的寄存器

这里插入话题GPIO是什么,为什么叫GPIO:
GPIO全称是General Purpose Input / Output 通用目的输入输出。既然是通用目的(General Purpose),那说明它可以用于不同的功能,比如有些引脚输出能力强,可以作为输出,这时候它就被用作最一般的输出功能,但是并不意味着它只能做普通输入输出引脚,它也可以复用为其它功能(具体查看芯片手册)。又比如本次实验中按键接到的引脚名字叫UART1_CTS,说明它的主要功能是用于UART串口,但是不代表不能用于输入,本实验就用它作为按键输入。芯片就是通过输入/输出数据来提供功能的,所有引脚都是输入/输出数据,也因此才叫IO,但是同一个引脚可能可以被复用为多种功能,比如UART,PWM,SPI,因此它是通用的,也即General Purpose,所以合起来才叫GPIO。更细致地分类解释的话,所有的引脚都具有输入/输出功能,它们都属于GPIO,但是如果某一个引脚只是单纯的用于输入/输出,比如LED的引脚GPIO1_IO03,这个引脚具有较强的输出能力,一般就用于输出,并且这个引脚属于GPIO1组的第3个IO引脚,那么这个引脚就命名为GPIO1_IO03;再比如按键Key0的引脚一般情况下输出能力较弱,会作为UART串口功能,那么这个引脚就命名UART1_CTS,但是它实际上是GPIO1组的第18个IO引脚,因此它是GPIO1_IO18,但是命名为UART1_CTS。

分析完硬件和寄存器部分后,开始写驱动代码,分为三部分:设备树、下层驱动代码,上层驱动框架

1、设备树
在根节点下添加如下节点

点击查看代码
mykey0 {
			compatible = "key0_driver";
			pin = <GROUP_PIN(1, 18)>;
			pin_reg = <
					0x20C406C	/*CCM_CCGR1*/
					0x20E008C	/*IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B*/
					0x209C004	/*GPIO1_GDIR*/
					0x209C008	/*GPIO1_PSR*/	
			>;
		};
在上面的设备树节点中,compatible用于匹配驱动,其余的pin和pin_reg是自定义属性,保存引脚的相关寄存器信息,可以在驱动代码里面的probe函数里读取设备树中的属性信息来获取寄存器地址。

2、包含platform_driver结构体的代码
这部分就是下层涉及具体硬件的操作函数,除了填充platform_driver结构体的内容外,还要向上层提供具体设备的操作函数,比如硬件的初始化、控制,同时在probe函数中调用上层接口为每一个匹配的设备注册文件视图。具体的代码如下:

头文件button_rsc.h

点击查看代码
#ifndef __BUTTON__RSC__H
#define __BUTTON__RSC__H

#define GROUP_PIN(g, p) ((g << 16) | (p))   //转换成引脚编号
#define PIN(x)  (x & 0xffff)    //获取引脚属于哪一个
#define GROUP(x)    ((x >> 16) & 0xffff)    //获取引脚属于哪一组

struct button_resource{
    int group_pin;  //[31:16]存放组,[15:0]存放
    //定义变量存放涉及到的寄存器的地址
    unsigned int CCM_CCGR1;
    unsigned int IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B;
    unsigned int GDIR;
    unsigned int DR; 
    unsigned int PSR;
};

struct button_registers_after_map{
    unsigned int* CCM_CCGR1;
    unsigned int* IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B;
    unsigned int* GDIR;
    unsigned int* DR; 
    unsigned int* PSR;
};

#endif

头文件button_opr.h

点击查看代码
#ifndef __BUTTON_OPR__H
#define __BUTTON_OPR__H

struct button_operations{
    int (*init) (unsigned int which);    //初始化引脚,which表示引脚编号
    int (*read) (unsigned int which);   //读取引脚电平状态,which表示引脚编号
};

#endif

头文件button_drv.h

点击查看代码
#ifndef __BUTTON_DRV_H__
#define __BUTTON_DRV_H__

#include "button_opr.h" //如果在board_button.c中button_drv头文件的包含在button_opr前面的话,这里就需要先包含button_opr.h,否则会有警告,因为struct button_operations定义在button_opr.h
void button_device_register(int minor);
void button_device_unregister(int minor);
void register_button_operations(struct button_operations *button_opr);

#endif

board_button.c文件

点击查看代码
/*
下层按键驱动程序,实现具体的硬件操作
(1)实现按键GPIO引脚的操作函数,包括初始化函数、读取按键状态函数,通过结构体指针形式向上层注册
(2)实现platform_driver结构体,从设备树生成的platform_device获取寄存器资源,并提供匹配和卸载时的probe和remove函数,还有其他属性
(3)实现驱动入口/出口函数
*/

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include "asm/delay.h"
#include "linux/ioport.h"
#include "linux/mod_devicetable.h"
#include "linux/of.h"
#include "linux/platform_device.h"
#include "asm/io.h"
#include <linux/delay.h>

#include "button_rsc.h"
#include "button_drv.h"
#include "button_opr.h"

int cur_counts = 0;
struct button_resource g_buttons[10];
struct button_registers_after_map g_buttons_map_addr[10];


/*
构造操作函数结构体,为上层驱动框架提供实体操作函数
*/
int button_init(unsigned int which)    //初始化引脚,which表示引脚编号
{
    /*根据which初始化GPIO,其中which是次设备号,也是g_buttons中保存资源结构体的下标*/
    unsigned int val;
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    //地址映射
    if(!g_buttons_map_addr[which].CCM_CCGR1)
    {
        g_buttons_map_addr[which].CCM_CCGR1 = ioremap(g_buttons[which].CCM_CCGR1, 4);
        g_buttons_map_addr[which].IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B = ioremap(g_buttons[which].IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B, 4);
        g_buttons_map_addr[which].GDIR = ioremap(g_buttons[which].GDIR, 4);
        g_buttons_map_addr[which].PSR = ioremap(g_buttons[which].PSR, 4);
    }

    //使能时钟
    val = *g_buttons_map_addr[which].CCM_CCGR1;
    val |= (0x3 << 26);
    *(g_buttons_map_addr[which].CCM_CCGR1) = val;

    /*
    配置GPIO1_IO18为GPIO模式
    */
    val = *g_buttons_map_addr[which].IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B;
    val &= (~0xf);  //低四位先清零
    val |= (0x5);   //设置为GPIO模式
    *(g_buttons_map_addr[which].IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B) = val;

    /*
    设置GPIO1_IO18为输出
    */
    *(g_buttons_map_addr[which].GDIR) &= ~(0x1 << 18);
    return 0;
}

int button_read(unsigned int which)  //读取引脚电平状态,which表示引脚编号
{
    // printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    if(!(*(g_buttons_map_addr[which].PSR) & (1 << 18)))
    {
        //udelay(10000);  //延时10ms  参数太大,udelay不允许,改为mdelay
        mdelay(10);
        if(!(*(g_buttons_map_addr[which].PSR) & (1 << 18)))
        return 0;
    }
    return 1;
}


struct button_operations button_oprs = {
    .init = button_init,
    .read = button_read,
};

int board_button_probe(struct platform_device *pdev)
{
    struct device_node *np;
    int err;
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    //获取device_node结构体
    np = pdev->dev.of_node;
    if(!np)
    {
        printk("device_node error\n");
        return -1;
    }

    //获取寄存器信息
    err = of_property_read_u32(np, "pin", &g_buttons[cur_counts].group_pin);
    err = of_property_read_u32_index(np, "pin_reg", 0, &g_buttons[cur_counts].CCM_CCGR1);
    err = of_property_read_u32_index(np, "pin_reg", 1, &g_buttons[cur_counts].IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B);
    err = of_property_read_u32_index(np, "pin_reg", 2, &g_buttons[cur_counts].GDIR);
    err = of_property_read_u32_index(np, "pin_reg", 3, &g_buttons[cur_counts].PSR);

    //创建设备节点,使用上层驱动提供的函数
    button_device_register(cur_counts);

    //节点数加1
    cur_counts++;

    return 0;
}

int board_button_remove(struct platform_device *pdev)
{
    int i;
    int err;
    struct device_node* np;
    int button_pin;
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    //获取device_node结构体
    np = pdev->dev.of_node;
    err = of_property_read_u32(np, "pin", &button_pin);
    for(i = 0; i < cur_counts; i++)
    {
        if(g_buttons[i].group_pin == button_pin)
        {
            button_device_unregister(i);
            g_buttons[i].group_pin = -1;
            break;
        }
    }

    for(i = 0; i < cur_counts; i++)
    {
        if(g_buttons[i].group_pin != -1)
        {
            break;
        }
    }

    if(i == cur_counts)
    {
        cur_counts = 0;
    }

    return 0;
}


static struct of_device_id mykeys[] = {
    {
        .compatible = "key0_driver"
    },
    {},
};

struct platform_driver button_drv = {
    .probe = board_button_probe,
    .remove = board_button_remove,
    .driver = {
        .name = "key",
        .of_match_table = mykeys,
    },
};

static int __init button_driver_init(void)
{
    int err;
    err = platform_driver_register(&button_drv);    //注册平台驱动
    //调用上层接口注册、向上层驱动提供操作函数
    register_button_operations(&button_oprs);
    return 0;
}

static void __exit button_driver_exit(void)
{
    platform_driver_unregister(&button_drv);
}

module_init(button_driver_init);
module_exit(button_driver_exit);

MODULE_LICENSE("GPL");

3、上层驱动框架

点击查看代码
/*
按键驱动程序上层框架,主要实现:
(1)file_operations结构体内容实现
(2)添加驱动入口/出口函数,在入口函数注册驱动,在出口函数撤销驱动
(3)提供下层驱动向上层注册的接口:创建设备节点、删除设备节点、下层向上层注册按键相关的操作函数
*/

#include "asm/memory.h"
#include "asm/uaccess.h"
#include "linux/err.h"
#include "linux/export.h"
#include "linux/kdev_t.h"
#include "linux/printk.h"
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/printk.h>
#include <linux/init.h>
#include <asm/io.h>


#include "button_opr.h"
int major;  //主设备号
struct class* button_class; //设备类
struct button_operations* p_button_opr;

void button_device_register(int minor)
{
    device_create(button_class, NULL, MKDEV(major, minor), NULL, "key_%d", minor);
}

void button_device_unregister(int minor)
{
    device_destroy(button_class, MKDEV(major, minor));
}


void register_button_operations(struct button_operations* button_opr)
{
    p_button_opr = button_opr;
}

EXPORT_SYMBOL(button_device_register);
EXPORT_SYMBOL(button_device_unregister);
EXPORT_SYMBOL(register_button_operations);

int button_open(struct inode * node, struct file * file)
{
    int which;
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    which = iminor(node);
    p_button_opr->init(which);
    return 0;
};

ssize_t button_read (struct file *file, char __user *user, size_t count, loff_t *start)
{
    struct inode *node;
    unsigned int which;
    int ret;
    int err;
    // printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

    node = file_inode(file);
    which = iminor(node);
    ret = p_button_opr->read(which);
    err = copy_to_user(user, &ret, 1);

    return ret;
};

int button_close (struct inode *node, struct file *file)
{
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

    return 0;
}

struct file_operations button_opr = {
    .owner = THIS_MODULE,
    .open = button_open, 
    .read = button_read,
    .release = button_close
    
};

static int __init button_init(void)
{
    /*
    驱动入口函数
    (1)注册驱动;(2)创建类
    */
    int err;
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    major = register_chrdev(major, "button_driver", &button_opr);
    button_class = class_create(THIS_MODULE, "button_class");
    err = PTR_ERR(button_class);
    if (IS_ERR(button_class))
    {
        unregister_chrdev(major, "button_driver");
        return -1;
    }

    return 0;
}

static void __exit button_exit(void)
{
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    class_destroy(button_class);
    unregister_chrdev(major, "button_driver");
}

module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");

4、应用程序,按键按下灯亮,再按下灯灭,如此反复

点击查看代码
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char** argv)
{
    int fd_button, fd_led;
    char val;
    char state;

    /*判断参数*/
    if(argc != 3)
    {
        printf("Usage: %s <buton_dev> <led_dev>\n", argv[0]);
    }

    fd_button = open(argv[1], O_RDWR);
    fd_led = open(argv[2], O_RDWR);
    if(fd_button == -1 || fd_led == -1)
    {
        printf("can not open file %s\n", argv[1]);
        return -1;
    }

    /*读文件, 控制led灯*/
    state = 0;
    write(fd_led, &state, 1);
    while(1)
    {
        read(fd_button, &val, 1);
        if(val == 0)
        {
            usleep(10000);
            read(fd_button, &val, 1);
            if(val == 0)
            {
                state = ~state;
            }
            write(fd_led, &state, 1);
            // printf("led state: %d", state);
            sleep(1);
        }

    }
    close(fd_button);
    close(fd_led);


    return 0;
}

至此,按键驱动程序完成。本次驱动的编写与led驱动程序类似,可以看作是一次巩固。

使用按键控制led亮灭看似很简单的功能,但是以此为基础探究Linux的知识却可以很深入。比如使用中断方式判断按键是否按下、灯和按键是两个事件,可以引申出进程线程,进而基于此实现进程线程的通信、同步,消息机制、互斥等等。而且任何嵌入式设备功能的开发,本质都是操作IO操作寄存器,和点灯、按键没有本质的区别,只不过框架稍微复杂一些,关键在linux的那些知识。
接下来要深入学习pinctrl和gpio子系统,这部分在嵌入式linux开发中使用率很高,属于提供工作效率的工具,应该要掌握,预计花费1-2个月的时间学习掌握。

标签:__,驱动程序,引脚,int,button,总线,buttons,include,读取
From: https://www.cnblogs.com/starstxg/p/18166227

相关文章

  • vue2-事件总线$bus的使用
    作用实现不同组件之间进行通信(非父子关系)。 原理$bus就是vue原型上添加的一个vue实例,用于存储、监听以及触发事件。 实现步骤在main.js文件中注册事件总线Vue.prototype.$bus=newVue();在需要发送信息的组件中发送事件this.$bus.$emit("eventname")//无参......
  • 设备驱动程序简介
    设备驱动程序简介概述:Linux驱动程序的核心:作为一个黑盒子,使某个特定硬件相应相应的内部编程接口,如posix。其需要隐藏体的工作细节。Linux驱动程序编写为什么简单:其是使独立于内核的其他部分而建立的,其是模块化的。驱动程序的作用驱动程序的妥协:在编写驱动程序所需要的时......
  • python读取nc文件
    使用netCDF4库读取nc文件#***1数据读取与处理#打开NetCDF文件GA_id=nc.Dataset('taiwan_GA.nc','r')DOV_E_id=nc.Dataset('taiwan_DOV_E.nc','r')DOV_N_id=nc.Dataset('taiwan_DOV_N.nc','r')VGG_id=nc.Dat......
  • 【Python-Json】自定义类输入json序列化、json的读取与写入
    AI问答Questionjson支持numpy数组么Answer不幸的是,标准的JSON格式不直接支持NumPy数组.JSON是一种用于存储和交换数据的文本格式,它有限的数据类型只包括对象(object)、数组(array)、数字(number)、字符串(string)、布尔值(true/false)、空值(null)等.因此,无法直接将......
  • 读取速度超7300MB/s!佰维 NV7200 2TB SSD评测:不可思议的低温
    一、前言:专注高端的国产SSD最近几年,消费级市场出现了许多颇具实力的存储品牌,佰维(Biwin)就是其中之一。近日,我们快科技收到了佰维NV72002TBSSD,它拥有7200MB/s的顺序读取速度、4K随机读取也有1000KIOPS,非常适合用于游戏存储、AI创作等场景。佰维NV7200系列SSD采用的是联芸......
  • stm32-arduino压力薄膜传感器读取
     https://item.taobao.com/item.htm?id=674959275850&skuId=5150222163940&spm=a1z0d.6639537/tb2.1997196601.3.43b97484vSZsIQ      #include<Arduino.h>#defineDEBUGSerialSerialintsensorPin=A0; //定义传感器的引脚//下面4项内容需要根据实......
  • C# 使用ffmpeg读取监控视频流
    编译环境VisualStudio2022.NetFramework4.7.2x64需要开启允许不安全代码(项目属性->生成->允许不安全代码)之前使用OpenCVSharp写的一个拉流,在服务器上跑不起来。于是换了这个使用FFmpeg.AutoGen的。参考博文:用C#做一个拉流播放器-摇光Summer-博客园(cnblogs.com)......
  • [广东强网杯 2021 团队组]love_Pokemon rce读取绕过
    五一结束了,真快啊!今天看一道RCE绕过类型的题目。上来先看看源码。点击查看代码<?phperror_reporting(0);highlight_file(__FILE__);$dir='sandbox/'.md5($_SERVER['REMOTE_ADDR']).'/';if(!file_exists($dir)){mkdir($dir);}functionDefenderBonus($......
  • 读取文件夹内的数据
    读取文件夹内的数据fromPILimportImageimportosimportnumpyasnp#递归函数,用于获取文件夹内所有PNG图片的文件名defget_png_files(folder_path):png_files=[]forroot,dirs,filesinos.walk(folder_path):forfileinfiles:i......
  • minst数据集的读取、训练和预测
    首先是基于本地mnist图像数据集来进行训练笔记首先是不管是数据集还是标签集,它都接收的是np数组,标签集接收的是int类型关于它的输入数据的格式,n2828,标签的格式不是one—hot(这个看编译模型时的损失函数)。整个流程是:1、处理数据(将其处理为模型需要的格式)。2、网络设计(也就是......