精华内容
下载资源
问答
  • my_button_dev = device_create(my_button_cls,NULL,MKDEV(major,0),NULL,"mybutton"); /* 1.4映射gpio的物理地址为虚拟地址,一个是控制寄存器地址,一个是数据寄存器地址 */ gpfconf = (volatile unsigned long...

    本篇是linux下按键设备驱动,采用的中断法,也是属于字符设备类的驱动,一起来动手吧。下面的话,老朋友可以跳过了直接从《需求描述》章节看起,新朋友可以试着看看。

     

    前言

    在嵌入式行业,有很多从业者。我们工作的主旋律是拿开源代码,拿厂家代码,完成产品的功能,提升产品的性能,进而解决各种各样的问题。或者是维护一个模块或方向,一搞就是好几年。

     

    时间长了,中年润发现我们对从零开始编写驱动、应用、算法、系统、协议、文件系统等缺乏经验。没有该有的广度和深度。中年润也是这样,工作了很多年,都是针对某个问题点修修补补或者某个模块的局部删删改改。很少有机会去独自从零开始编写一整套完整的代码。

     

    当然,这种现状对于企业来说是比较正常的,可以降低风险。但是对于员工本身,如果缺乏必要的规划,很容易工作多年却还是停留在单点的层面,而丧失了提升到较高层面的机会。随着时间的增长很容易丧失竞争力。

     

    另外,根据中年润的经验,绝大多数公司对于0-5年经验从业者的定位主要是积极的问题解决者。而对于5-10经验从业者的定位主要是积极的系统规划者和引领者。在这种行业规则下,中年润认为,每个从业者都应该问自己一句,“5年后,我是否具备系统化把控软件的能力呢?”。

     

    当前的这种行业现状,如果我们不做出一点改变,是没有办法突破的。有些东西,仅仅知道是不够的,还需要深思熟虑的思考和必要的训练,简单来说就是要知行合一。

     

    也许有读者会有疑惑?这不就是重复造轮子么?我们确实是在重复造轮子,因为别人会造轮子那是别人的能力,我们自己会造轮子是我们自己的能力。在行业中,有太多的定制化需求是因为轮子本身有原生性缺陷,我们无法直接使用,或者需要对其进行改进,或者需要抽取开源代码的主体思想和框架,根据公司的需要定制自己的各项功能。设想,如果我们具备这种能力,必然会促使我们在行业中脱颖而出,而不是工作很多年一直在底层搬砖。底层搬砖没什么不好,问题是当有更廉价更激情的劳动力涌进来的时候,我们这些老的搬砖民工也就失去了价值。我们不会天天重复造轮子,我们需要通过造几个轮子使得自己具备造轮子的能力,从而更好的适应这个环境,适应这个世界。

     

    针对当前行业现状,中年润经过深思熟虑,想为大家做点实实在在的事情,希望能够帮助大家在巩固基础的同时提升系统化把控软件的能力。当然,中年润的水平也有限,有些观点也只是一家之谈,希望大家独立思考,谨慎采用,如果写的有错误或者不对的地方还请读者们批评斧正,我们一起共同进步。

     

    在这里简单介绍下中年润,中年润现在就职于一家大型国际化公司,工作经验6年,硕士毕业。曾经担任过组内的项目主管,项目经理,也曾经组建过新团队,带领大家冲锋陷阵。在工作中,有做的不错的地方,也有失误的地方,有激情的时刻,也有失落的时刻。现在偏安一隅,专心搞技术,目前个人规划的技术方向是嵌入式和AI基础设施建设,以及嵌入式和AI的融合发展。

     

    最后,说了这么多,中年润希望,在未来的日子里和未知的领域里,你我同行,为我们的美好生活而努力奋斗。

     

    总体目标

    本篇文章的目标是介绍如何从自顶向下从零编写linux下的按键字符设备驱动(采用中断法)。着力从总体思路,需求端,分析端,实现端,详尽描述一个完整需求的开发流程,是中年润多年经验的提炼,希望读者能够有所收获。最后的实战目标,请读者尽量完成,这样读者才能形成自己的思路。

    本示例采用arm920架构,天祥电子生产的tx2440a开发板,核心为三星的s3c2440。Linux版本为2.6.31,是已经移植好的版本。编译器为arm920t-eabi-4.1.2.tar。

     

    总体思路

    总体思路是严格遵循需求的开发流程来,不遗漏任何思考环节。读者在阅读时请先跟中年润的思路走一遍,然后再抛弃中年润的思路,按照自己的思路走一遍,如果遇到困难请先自己思考,实在不会再来参考中年润的思路和实现。

     

    中年润在写代码的的总体思路如下:

    需求描述—能够详细完整的描述一个需求。

    需求分析—根据需求描述,提取可供实现的功能,需要有定量或者定性的指标。(从宏观上确定需要什么功能)。

    需求分解—根据需求分析,考虑要实现需求所需要做的工作(根据宏观确定的功能,拆分成小的可单独实现的功能)。

    编写思路—根据需求分解从总体上描述应该如何编写代码,(解决怎么在宏观上实现)。

    详细步骤—根据编写思路,落实具体步骤,(解决怎么在微观上实现)。

    编写框架—根据编写思路,实现总体框架(实现编写思路里主体框架,细节内容留在具体代码里编写)。

    具体代码—根据编写框架和详细步骤,编写每一个函数里所需要实现的小功能,主要是实现驱动代码,测试代码。

    Makefile—用来编译驱动代码。

    目录结构—用来说明当完成编码后的结果。

    测试步骤—说明如何对驱动进行测试,主要是加载驱动模块,执行测试代码。

    执行结果—观察执行结果是否符合预期。

    结果总结—回顾本节的知识点,api,结构体。

    实战目标—说明如何根据本文档训练。

    请大家尽量按照自顶向下的学习思路来学习和实战,因为我们所有工作的动力都是我们心中的需求。这些步骤仅仅是我们达到目标所要走过的路。目录起到提纲挈领的重要作用,写的时候要实时看下提纲,看有没有偏离自己的方向。

     

    需求描述

    使用linux提供的字符设备api接口,编写一个按键字符设备驱动和一个测试代码,能够在输入./button命令后,按下按键时,在串口输出按下了哪个按键。同时用户不希望用户线程占用太多的cpu资源。

     

    需求分析

    对于按键字符设备驱动来说,测试代码就是我们的用户,因此我们可以通过分析测试代码的逻辑来分析我们的需求。

     

    首先梳理用户的工作流程,用户的工作流程如下:

    1用户调用open系统调用打开/dev/mybutton设备节点,获取fd

    2用户拿到fd之后,调用read系统调用读取fd中的值                                                                     

    3如果没有数据,希望用户进程睡眠,不希望大量占用cpu资源

    4如果有数据,直接返回数据

    5如果按下或松开按键,在中断中保存当前的数据,并唤醒用户线程,用户线程直接拿数据。

     

    根据用户的工作流程,我们可以梳理出驱动所要做的工作,下图说明了驱动所要做的主体,跟用户层是一一对应的。

    梳理完用户工作流程后,我们再来看下用户进程和中断之间的交互,这样对于为什么编写某些函数会有直观的感受。

    需求分解

    根据《需求分析》和用户的操作,我们需要做以下几件工作。

    1用户调用open系统调用打开/dev/mybutton设备节点,获取fd

    1.3   需要提供一个设备节点/dv/mybutton,

    1.4   需要提供一个操作设备节点的open函数

    2用户拿到fd之后,调用read系统调用读取fd中的值

    2.1 需要提供操作设备节点的read函数,用来将读取的数据返回     

    3如果没有数据,希望用户进程睡眠,不希望大量占用cpu资源

    3.1 需要能判断当前有无数据,需要在无数据时进入休眠状态

    4如果有数据,直接返回数据

    4.1 需要能判断当前有无数据,需要在有数据时能立即返回数据,并标记当前数据状态为无

    4.2 需要一个有无数据的标志,在产生数据时标记有,在读取完毕后标记无

    5如果按下或松开按键,在中断中保存当前的数据,并唤醒用户线程,用户线程直接拿数据。

    5.1 需要一个中断处理函数

    5.2 需要能够检测当前按下的是哪个按键

    5.3 需要在中断中保存数据,并标记当前数据状态为有

    5.4 需要唤醒用户进程

    5.5 在中断函数中保存数据后,read函数能够直接操作被保存的数据并返回给用户

     

    用一张图来总结上述驱动所要做的工作,如下图所示:

    编写思路

    编写思路主要用来搭建代码框架,解决在宏观上如何用代码实现驱动的功能。

    确定目标:实现需求分解中的所有函数

    Init函数,exit函数,open函数,read函,close函数,中断处理函数(在详细步骤中编写)

     

    确定基本思路:

    0搭建基础框架

    0.1编写代码框架,头文件

    0.2编写空的出口函数

    0.3编写空的入口函数

    0.4修饰出口函数,修饰入口函数,声明LICENSE

     

    在入口函数中所做的工作如下

    1入口函数

    1.1注册一个字符设备,名字为s3c_button

    1.1.1定义file_operations结构体

    1.1.2编写空的打开函数,关闭函数,读函数

    1.2创建一个类,名字为button-int,(决定了/sys/class下目录的名字)

    1.3创建一个设备节点,名字为mybutton(决定了/dev下文件的名字)

    1.4映射gpio的物理地址为虚拟地址,一个是控制寄存器地址,一个是数据寄存器地址

     

    在出口函数中所作的工作如下

    2出口函数

    2.1卸载字符设备

    2.2删除设备节点

    2.3销毁这个类

    2.4取消地址映射

     

    将上述步骤编写完成就得到了《编写框架》章节中的代码

     

    详细步骤

    详细步骤主要用来在代码框架里填充必要的细节代码,解决在微观上如何用代码实现驱动各个小功能。

     

    0搭建基础框架

    0.1编写代码框架,头文件

    0.2编写空的出口函数

    0.3编写空的入口函数

    0.4修饰出口函数,修饰入口函数,声明LICENSE

     

    在入口函数中所做的工作如下

    1入口函数

    1.1注册一个字符设备,名字为s3c_button

    1.1.1定义file_operations结构体

    1.1.2编写空的打开函数,关闭函数,读函数

    1.2创建一个类,名字为button-int,(决定了/sys/class下目录的名字)

    1.3创建一个设备节点,名字为mybutton(决定了/dev下文件的名字)

    1.4映射gpio的物理地址为虚拟地址,一个是控制寄存器地址,一个是数据寄存器地址

     

    在出口函数中所作的工作如下

    2出口函数

    2.1卸载字符设备

    2.2删除设备节点

    2.3销毁这个类

    2.4取消地址映射

     

    3编写file_operations里的操作函数

    3.1编写打开函数,注册中断和中断处理函数

    3.2编写关闭函数,释放中断

    3.3编写读函数,读取按下的键值

    3.4编写中断处理函数,判断当前是哪个按键按下,以及当前是按下还是松开

    所有函数的基本流程图如下图示所示:

    编写框架

    根据编写思路,实现总体框架(实现编写思路里主体框架,细节内容留在具体代码里编写)。

    /* 本文件名字为button_drv_int_skel.c*/
    /* 本文件是依照button-中断法驱动<编写思路>章节编写,本文件
     * 的目的是编写代码框架,不做具体细节的编写
     */
    
    /* 0.1编写代码框架,头文件 */
    #include <linux/module.h>
    #include <linux/ioport.h>
    #include <linux/io.h>
    #include <linux/platform_device.h>
    #include <linux/init.h>
    #include <linux/serial_core.h>
    #include <linux/serial.h>
    #include <linux/irq.h>
    #include <asm/irq.h>
    #include <asm/io.h>
    #include <asm/uaccess.h>
    #include <mach/hardware.h>
    #include <mach/regs-gpio.h>
    #include <mach/gpio-fns.h>
    #include <plat/regs-serial.h>
    
    volatile unsigned long * gpfconf;
    volatile unsigned long * gpfdat;
    static struct class *my_button_cls;
    static struct device * my_button_dev;
    static int major;
    
    /* 1.1.2编写空的打开函数 */
    static int button_open (struct inode * inode, struct file * filep)
    {
    	return 0;	
    }
    
    /* 1.1.2编写空的关闭函数 */
    static int button_release (struct inode * inode, struct file * filep)
    {
    	return 0;
    }
    
    /* 1.1.2编写空的读函数 */
    static ssize_t button_read (struct file * filep, char __user * buff, size_t size, loff_t * pos)
    {
    	return 0;
    }
    
    /* 1.1.1定义file_operations结构体 */
    static struct file_operations button_fops = {
    	.owner  = THIS_MODULE,
    	.open   = button_open,
    	.read   = button_read,
    	.release  = button_release,
    	
    };
    
    /* 0.3编写空的入口函数 */
    static int my_button_init(void)
    {
    	int ret = 0;
    	/* 1.1注册一个字符设备,名字为s3c_button */
    	major = register_chrdev(0,"s3c_button",&button_fops);
    	/* 1.2创建一个类,名字为button-int,(决定了/sys/class下目录的名字) */
    	my_button_cls = class_create(THIS_MODULE,"button-int");
    	/* 1.3创建一个设备节点,名字为mybutton(决定了/dev下文件的名字) */
    	my_button_dev = device_create(my_button_cls,NULL,MKDEV(major,0),NULL,"mybutton");
    	/* 1.4映射gpio的物理地址为虚拟地址,一个是控制寄存器地址,一个是数据寄存器地址 */
    	gpfconf = (volatile unsigned long *)ioremap(0x56000050,16);	
    	//获得数据寄存器地址
    	gpfdat  = gpfconf + 1;
    
    	return ret;
    }
    
    /* 0.2编写空的出口函数 */
    static void my_button_exit(void)
    {
    	/* 2.1卸载字符设备 */
    	unregister_chrdev(major," s3c_button");
    	/* 2.2删除设备节点 */
    	device_unregister(my_button_dev);
    	/* 2.3销毁这个类 */
    	class_destroy(my_button_cls);
    	/* 2.4取消地址映射 */
    	iounmap(gpfconf);
    	return;
    }
    
    /* 0.4修饰出口函数,修饰入口函数,声明LICENSE */
    module_init(my_button_init);
    module_exit(my_button_exit);
    MODULE_LICENSE("GPL");
    

    驱动代码

    根据《编写框架》《详细步骤》章节,编写每一个函数里所需要实现的小功能。

    /* 本文件名字为button_drv_int.c*/
    /* 本文件是依照button-中断法驱动<详细步骤>章节编写,本文件
     * 的目的是编写具体操作函数,不做框架介绍
     */
    
    /* 0.1编写代码框架,头文件 */
    #include <linux/module.h>
    #include <linux/ioport.h>
    #include <linux/io.h>
    #include <linux/platform_device.h>
    #include <linux/init.h>
    #include <linux/serial_core.h>
    #include <linux/serial.h>
    #include <linux/irq.h>
    #include <asm/irq.h>
    #include <asm/io.h>
    #include <asm/uaccess.h>
    #include <mach/hardware.h>
    #include <mach/regs-gpio.h>
    #include <mach/gpio-fns.h>
    #include <plat/regs-serial.h>
    
    volatile unsigned long * gpfconf;
    volatile unsigned long * gpfdat;
    static struct class *my_button_cls;
    static struct device * my_button_dev;
    static int major;
    
    DECLARE_WAIT_QUEUE_HEAD(button_waitq);
    
    //引脚描述符
    struct pin_desc{
    	int pin;//代表是哪个引脚
    	int key_val;//代表是按下还是松开
    };
    
    //引脚描述符,按引脚的不同定义不同的值,在中断中进行处理
    struct pin_desc pin_desc[4] = 
    {
    	{S3C2410_GPF4_EINT4,0x1},
    	{S3C2410_GPF5_EINT5,0x2}, 
    	{S3C2410_GPF6_EINT6,0x3},
    	{S3C2410_GPF7_EINT7,0x4},
    };
    
    //标志是否进入中断
    static int do_press_loos;
    
    /* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */
    /* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */
    /* 主要是为了区分是按下还是松开 */
    static unsigned char key_val;
    
    irqreturn_t button_irq(int irq, void * devid)
    {
    	int pin_val;
    	struct pin_desc * pin_readed;
    	/*
    	 *	1 获取当前引脚的电平值
    	 *	2 根据电平值判断当前是按下还是松开
    	 *		松开为高电平,返回0x8x
    	 *		按下为低电平,返回0x0x
    	 *	3 标记已有数据
    	 *	4 唤醒处在内核态的用户进程
    	 */		
    	pin_readed = (struct pin_desc *)devid;
    	//获取某个引脚是高还是低
    	pin_val = s3c2410_gpio_getpin(pin_readed->pin);
    		
    	if (pin_val) //松开是高电平
    	{
    		key_val = 0x80 | pin_readed->key_val;
    	}
    	else		//按下为低电平
    	{
    		//把当前按键的键值给一个全局静态变量,在read函数里给用户
    		key_val = pin_readed->key_val;
    	}
    	
    	//标记中断已经触发
    	do_press_loos = 1;
    
    	//唤醒用户的读进程
    	wake_up_interruptible(&button_waitq);
    
    	return IRQ_RETVAL(IRQ_HANDLED);
    }
    
    /* 1.1.2编写空的打开函数 */
    static int button_open (struct inode * inode, struct file * filep)
    {
    	/* 3.1编写打开函数,注册中断和中断处理函数 */
    	//request_irq里已经帮忙将GPF4-GPF7设置成中断引脚了
    	
    	int ret;
    	/* 注册外部中断4,类型为下降沿中断,名字为S1,
    	   中断处理函数为button_irq
    	   传入中断处理函数中的参数为pin_desc[0] */
    	ret = request_irq(IRQ_EINT4,button_irq,IRQ_TYPE_EDGE_FALLING,"S1",
    						(void *)&pin_desc[0]);
    	if (ret)
    		goto errout;
    	ret = request_irq(IRQ_EINT5,button_irq,IRQ_TYPE_EDGE_FALLING,"S2",
    						(void *)&pin_desc[1]);
    	if (ret)
    		goto errout;
    	ret = request_irq(IRQ_EINT6,button_irq,IRQ_TYPE_EDGE_FALLING,"S3",
    						(void *)&pin_desc[2]);
    	if (ret)
    		goto errout;
    	ret = request_irq(IRQ_EINT7,button_irq,IRQ_TYPE_EDGE_FALLING,"S4",
    						(void *)&pin_desc[3]);
    	if (ret)
    		goto errout;
    
    	return 0;
    errout:
    	return ret;	
    }
    
    /* 1.1.2编写空的关闭函数 */
    static int button_release (struct inode * inode, struct file * filep)
    {
    	/* 3.2编写关闭函数,释放中断 */
    	free_irq(IRQ_EINT4, &pin_desc[0]);
    	free_irq(IRQ_EINT5, &pin_desc[1]);
    	free_irq(IRQ_EINT6, &pin_desc[2]);
    	free_irq(IRQ_EINT7, &pin_desc[3]);
    	return 0;
    }
    
    /* 1.1.2编写空的读函数 */
    static ssize_t button_read (struct file * filep, char __user * buff, size_t size, loff_t * pos)
    {
    	int ret = 0;
    	/* 3.3编写读函数,读取按下的键值,以及当前是按下还是松开 */
    	if (size != 1)
    		return -EINVAL;
    	//根据do_press_loos判断,如果没有中断,直接进入休眠
    	wait_event_interruptible(button_waitq, do_press_loos);
    	//如果被唤醒拷贝数据到用户空间
    	ret = copy_to_user(buff, &key_val, sizeof(key_val));
    	if (ret) {
    		return -EFAULT;
    	}
    	//读取数据完毕后需要将标志位清零,表示暂时无数据可读
    	do_press_loos = 0;
    	return 1;	
    }
    
    /* 1.1.1定义file_operations结构体 */
    static struct file_operations button_fops = {
    	.owner  = THIS_MODULE,
    	.open   = button_open,
    	.read   = button_read,
    	.release  = button_release,
    	
    };
    
    /* 0.3编写空的入口函数 */
    static int my_button_init(void)
    {
    	int ret = 0;
    	/* 1.1注册一个字符设备,名字为s3c_button */
    	major = register_chrdev(0,"s3c_button",&button_fops);
    	/* 1.2创建一个类,名字为button-int,(决定了/sys/class下目录的名字) */
    	my_button_cls = class_create(THIS_MODULE,"button-int");
    	/* 1.3创建一个设备节点,名字为mybutton(决定了/dev下文件的名字) */
    	my_button_dev = device_create(my_button_cls,NULL,MKDEV(major,0),NULL,"mybutton");
    	/* 1.4映射gpio的物理地址为虚拟地址,一个是控制寄存器地址,一个是数据寄存器地址 */
    	gpfconf = (volatile unsigned long *)ioremap(0x56000050,16);	
    	//获得数据寄存器地址
    	gpfdat  = gpfconf + 1;
    
    	return ret;
    }
    
    /* 0.2编写空的出口函数 */
    static void my_button_exit(void)
    {
    	/* 2.1卸载字符设备 */
    	unregister_chrdev(major," s3c_button");
    	/* 2.2删除设备节点 */
    	device_unregister(my_button_dev);
    	/* 2.3销毁这个类 */
    	class_destroy(my_button_cls);
    	/* 2.4取消地址映射 */
    	iounmap(gpfconf);
    	return;
    }
    
    /* 0.4修饰出口函数,修饰入口函数,声明LICENSE */
    module_init(my_button_init);
    module_exit(my_button_exit);
    MODULE_LICENSE("GPL");
    

    测试代码

    测试代码编写思路如下:

    1打开/dev/mybutton文件

    2读取获得的值

    3打印获得的值

     

    测试代码如下:

    /* 本文件是button_test.c,是根据button-中断法驱动的
      * <测试代码>章节编写,主要任务是用来测试
      * button 驱动
      */
    
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    
    /* button 驱动,中断法实验
      */
    int main(int argc, char **argv)
    {
    	int fd;
    	unsigned char key_val;
    	int cnt;
    	
    	fd = open("/dev/mybutton", O_RDWR);
    	if (fd < 0)
    	{
    		printf("can't open!\n");
    	}
    
    	while (1)
    	{
    		read(fd, &key_val, 1);
    		printf("key_val = 0x%x\n", key_val);
    	}
    	
    	return 0;
    }
    

    Makefile

    KERN_DIR = /home/linux/tools/linux-2.6.31_TX2440A
    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_int.o
    

    目录结构

    代码编写完成的目录结构如下所示。直接执行make即可生成.ko文件。

    .

    ├── button_drv_int.c(驱动代码)

    ├── button_drv_int_skel.c(驱动框架代码)

    ├── button_test.c(驱动测试代码)

    └── Makefile(用来编译驱动代码)

     

    测试步骤

    0 在linux下的makefile +180行处配置好arch为arm,cross_compile为arm-linux-(或者arm-angstrom-linux-gnueabi-)

    1 在menuconfig中配置好内核源码的目标系统为s3c2440

    2 在pc上将驱动程序编译生成.ko,命令:make

    3 在pc上将测试程序编译生成elf可执行文件,生成的button就是我们所要使用的命令。

    编译:arm-angstrom-linux-gnueabi-gcc button_test.c -o button_test

    4 挂载nfs,这样就可以在开发板上看到pc端的.ko文件和测试文件

    mount -t nfs -o nolock,vers=2 192.168.0.105:/home/linux/nfs_root  /mnt/nfs

    5 insmod button_drv.ko,加载按键的驱动模块

    6执行命令./button,依次按下四个按键,观察串口的打印信息。

     

    执行结果

    执行完./button后,依次按下四个按键,串口的日志如下,按下一个按键一次,会出现很多次打印。

    [root@TX2440A 4-button-int]# insmod button_drv_int.ko

    [root@TX2440A 4-button-int]# lsmod

    button_drv_int 3356 0 - Live 0xbf000000

    [root@TX2440A 4-button-int]# ./button_test

    key_val = 0x4

    key_val = 0x4

    key_val = 0x3

    key_val = 0x2

    key_val = 0x2

    key_val = 0x2

    key_val = 0x2

    key_val = 0x2

    key_val = 0x2

    key_val = 0x1

    key_val = 0x1

    key_val = 0x1

    上述log表明,当前的驱动有按键抖动的现象,还需要继续优化。

     

    结果总结

    在本篇文章中,中年润跟读者分享了按键字符设备驱动的编写思路和方法,其中贯穿始终的有几个函数和关键数据结构,它们分别是:

    struct file_operations

    struct class

    struct class_device

     

    register_chrdev

    class_create

    device_create

    unregister_chrdev

    device_destroy

    class_destroy

    ioremap

    iounmap

    DECLARE_WAIT_QUEUE_HEAD

    request_irq

    free_irq

    请读者尽力去了解这些函数的作用,入参,返回值。

     

    问题汇总

    暂无

     

    实战目标

    1请读者根据《需求描述》章节,独立编写需求分析和需求分解。

    2请读者根据需求分析和需求分解,独立编写编写思路和详细步骤。

    3请读者根据编写思路,独立写出编写框架。

    4请读者根据详细步骤,独立编写驱动代码和测试代码。

    5请读者根据《Makefile》章节,独立编写Makefile。

    6请读者根据《测试步骤》章节,独立进行测试。

    7请读者抛开上述练习,自顶向下从零开始再编写一遍驱动代码,测试代码,makefile

    8如果无法独立写出7,请重复练习1-6,直到能独立写出7。

     

    参考资料

    《linux设备驱动开发祥解》

    《TX2440开发手册及代码》

    《韦东山嵌入式教程》

    《鱼树驱动笔记》

    《s3c2440a》芯片手册英文版和中文版

     

    致谢

    感谢在嵌入式领域深耕多年的前辈,感谢中年润的家人,感谢读者。没有前辈们的开拓,我辈也不能站在巨人的肩膀上看世界;没有家人的鼎力支持,我们也不能集中精力完成自己的工作;没有读者的关注和支持,我们也没有充足的动力来编写和完善文章。看完中年润的文章,希望读者学到的不仅仅是如何编写代码,更进一步能够学到一种思路和一种方法。

     

    为了省去驱动开发者搜集各种资料来写驱动,中年润后续有计划按照本模板编写linux下的常见驱动,敬请读者关注。

     

    联系方式

    微信群:自顶向下学嵌入式(可先加微信号:runzhiqingqing, 通过后会邀请入群。)

    微信订阅号:自顶向下学嵌入式  公众号:EmbeddedAIOT

    CSDN博客:中年润  网址:https://blog.csdn.net/chichi123137

    邮箱:834759803@qq.com

    QQ群:766756075

    更多原创文章请关注微信公众号。另外,中年润还代理销售韦东山老师的视频教程,欢迎读者点击下面二维码购买。在中年润这里购买了韦东山老师的视频教程,除了能得到韦东山官方的技术支持外,还能获得中年润细致入微的技术和非技术的支持和帮助。欢迎选购哦。

    公众号二维码如下图

    入群小助手二维码如下图

    中年润代理销售的韦东山视频购买地址如下图

    如果略有所获,欢迎赞赏,您的支持对中年润无比重要

    展开全文
  • Button-查询法 本篇是linux下按键设备驱动,采用查询法,也是属于字符设备类的驱动,一起来动手吧。下面的话,老朋友可以跳过了直接从《需求描述》章节看起,新朋友可以试着看看。 特别说明:本系列教程可以配套...

    Button-查询法

    本篇是linux下按键设备驱动,采用查询法,也是属于字符设备类的驱动,一起来动手吧。下面的话,老朋友可以跳过了直接从《需求描述》章节看起,新朋友可以试着看看。

    特别说明:本系列教程可以配套《韦东山视频教程二期》,是韦老师教程的有益补充。

    Csdn地址如下:

    https://blog.csdn.net/chichi123137/article/details/89741831

     

    前言

    在嵌入式行业,有很多从业者。我们工作的主旋律是拿开源代码,拿厂家代码,完成产品的功能,提升产品的性能,进而解决各种各样的问题。或者是维护一个模块或方向,一搞就是好几年。

     

    时间长了,中年润发现我们对从零开始编写驱动、应用、算法、系统、协议、文件系统等缺乏经验。没有该有的广度和深度。中年润也是这样,工作了很多年,都是针对某个问题点修修补补或者某个模块的局部删删改改。很少有机会去独自从零开始编写一整套完整的代码。

     

    当然,这种现状对于企业来说是比较正常的,可以降低风险。但是对于员工本身,如果缺乏必要的规划,很容易工作多年却还是停留在单点的层面,而丧失了提升到较高层面的机会。随着时间的增长很容易丧失竞争力。

     

    另外,根据中年润的经验,绝大多数公司对于0-5年经验从业者的定位主要是积极的问题解决者。而对于5-10经验从业者的定位主要是积极的系统规划者和引领者。在这种行业规则下,中年润认为,每个从业者都应该问自己一句,“5年后,我是否具备系统化把控软件的能力呢?”。

     

    当前的这种行业现状,如果我们不做出一点改变,是没有办法突破的。有些东西,仅仅知道是不够的,还需要深思熟虑的思考和必要的训练,简单来说就是要知行合一。

     

    也许有读者会有疑惑?这不就是重复造轮子么?我们确实是在重复造轮子,因为别人会造轮子那是别人的能力,我们自己会造轮子是我们自己的能力。在行业中,有太多的定制化需求是因为轮子本身有原生性缺陷,我们无法直接使用,或者需要对其进行改进,或者需要抽取开源代码的主体思想和框架,根据公司的需要定制自己的各项功能。设想,如果我们具备这种能力,必然会促使我们在行业中脱颖而出,而不是工作很多年一直在底层搬砖。底层搬砖没什么不好,问题是当有更廉价更激情的劳动力涌进来的时候,我们这些老的搬砖民工也就失去了价值。我们不会天天重复造轮子,我们需要通过造几个轮子使得自己具备造轮子的能力,从而更好的适应这个环境,适应这个世界。

     

    针对当前行业现状,中年润经过深思熟虑,想为大家做点实实在在的事情,希望能够帮助大家在巩固基础的同时提升系统化把控软件的能力。当然,中年润的水平也有限,有些观点也只是一家之谈,希望大家独立思考,谨慎采用,如果写的有错误或者不对的地方还请读者们批评斧正,我们一起共同进步。

     

    在这里简单介绍下中年润,中年润现在就职于一家大型国际化公司,工作经验6年,硕士毕业。曾经担任过组内的项目主管,项目经理,也曾经组建过新团队,带领大家冲锋陷阵。在工作中,有做的不错的地方,也有失误的地方,有激情的时刻,也有失落的时刻。现在偏安一隅,专心搞技术,目前个人规划的技术方向是嵌入式和AI基础设施建设,以及嵌入式和AI的融合发展。

     

    最后,说了这么多,中年润希望,在未来的日子里和未知的领域里,你我同行,为我们的美好生活而努力奋斗。

     

    总体目标

    本篇文章的目标是介绍如何从自顶向下从零编写linux下的按键字符设备驱动。着力从总体思路,需求端,分析端,实现端,详尽描述一个完整需求的开发流程,是中年润多年经验的提炼,希望读者能够有所收获。最后的实战目标,请读者尽量完成,这样读者才能形成自己的思路。

    本示例采用arm920架构,天祥电子生产的tx2440a开发板,核心为三星的s3c2440。Linux版本为2.6.31,是已经移植好的版本。编译器为arm920t-eabi-4.1.2.tar。

     

    总体思路

    总体思路是严格遵循需求的开发流程来,不遗漏任何思考环节。读者在阅读时请先跟中年润的思路走一遍,然后再抛弃中年润的思路,按照自己的思路走一遍,如果遇到困难请先自己思考,实在不会再来参考中年润的思路和实现。

     

    中年润在写代码的的总体思路如下:

    需求描述—能够详细完整的描述一个需求。

    需求分析—根据需求描述,提取可供实现的功能,需要有定量或者定性的指标。(从宏观上确定需要什么功能)。

    需求分解—根据需求分析,考虑要实现需求所需要做的工作(根据宏观确定的功能,拆分成小的可单独实现的功能)。

    编写思路—根据需求分解从总体上描述应该如何编写代码,(解决怎么在宏观上实现)。

    详细步骤—根据编写思路,落实具体步骤,(解决怎么在微观上实现)。

    编写框架—根据编写思路,实现总体框架(实现编写思路里主体框架,细节内容留在具体代码里编写)。

    具体代码—根据编写框架和详细步骤,编写每一个函数里所需要实现的小功能,主要是实现驱动代码,测试代码。

    Makefile—用来编译驱动代码。

    目录结构—用来说明当完成编码后的结果。

    测试步骤—说明如何对驱动进行测试,主要是加载驱动模块,执行测试代码。

    执行结果—观察执行结果是否符合预期。

    结果总结—回顾本节的知识点,api,结构体。

    实战目标—说明如何根据本文档训练。

    请大家尽量按照自顶向下的学习思路来学习和实战,因为我们所有工作的动力都是我们心中的需求。这些步骤仅仅是我们达到目标所要走过的路。目录起到提纲挈领的重要作用,写的时候要实时看下提纲,看有没有偏离自己的方向。

     

    需求描述

    使用linux提供的字符设备api接口,编写一个按键字符设备驱动和一个测试代码,能够在输入./button命令后,按下按键时,在串口输出按下了哪个按键。

     

    需求分析

    对于按键字符设备驱动来说,测试代码就是我们的用户,因此我们可以通过分析测试代码的逻辑来分析我们的需求。

     

    测试代码中用户的工作流程如下:

    1用户调用open函数打开/dev/mybutton设备节点,获取fd

    2用户拿到fd之后,调用read函数读取fd中的值                                                                            

     

    根据用户的工作流程,我们可以梳理出驱动所要做的工作:

    1用户调用open系统调用打开/dev/mybutton设备节点,获取fd

    1.1驱动需要提供一个设备节点/dv/mybutton

    1.2驱动需要提供一个操作设备节点的open函数

    2用户拿到fd之后,调用read系统调用读取fd中的值

    2.1 驱动需要提供操作设备节点的read函数,用来将读取的按键数据返回     

     

    需求分解

    根据《需求分析》的结果,将宏观确定的功能拆分成小的可单独实现的功能,我们需要做以下几件工作。

    针对测试代码:

    1打开/dev/mybutton设备,获得fd

    2读fd,并显示按下了哪个按键。

     

    针对驱动代码:

    1创建设备节点/dev/mybutton

    2提供打开设备的open函数

    3提供读设备的read函数

     

    另外,我们还需要指定我们要操作的gpio管脚,因此引出4和5

    4需要将按键所对应的gpio管脚的物理地址映射到某个地方,方便在驱动代码中控制

    5需要在读函数中根据按键gpio引脚的高低电平,判断当前是哪个按键按下,并将数据丢给用户。

     

    编写思路

    编写思路主要用来搭建代码框架,解决在宏观上如何用代码实现驱动的功能。

    确定目标:

    1我们需要一个设备节点,存在于/dev/目录下(因此需要在入口函数创建设备)

    2我们需要一个open函数(用来配置gpio为输入模式)

    3我们需要一个read函数(用来返回哪个按键按下)

    4我们需要操作按键所对应的gpio(因此需要映射gpio管脚的地址)

    5我们需要判断按键所对应的gpio管脚的高低电平

     

    确定基本思路:

    0搭建基础框架

    0.1编写代码框架,头文件

    0.2编写空的出口函数

    0.3编写空的入口函数

    0.4修饰出口函数,修饰入口函数,声明LICENSE

     

    在入口函数中所做的工作如下

    1入口函数

    1.1注册一个字符设备,名字为s3c_button

    1.1.1定义file_operations结构体

    1.1.2编写空的打开函数

    1.1.2编写空的读函数

    1.2创建一个类,名字为button,(决定了class下目录的名字)

    1.3创建一个设备节点,名字为mybutton(决定了/dev下目录的名字)

    1.4映射gpio的物理地址为虚拟地址,一个是控制寄存器地址,一个是数据寄存器地址

     

    在出口函数中所作的工作如下

    2出口函数

    2.1卸载字符设备

    2.2删除设备节点

    2.3销毁这个类

    2.4取消地址映射

     

    详细步骤

    详细步骤主要用来在代码框架里填充必要的细节代码,解决在微观上如何用代码实现驱动各个小功能。

     

    0搭建基础框架

    0.1编写代码框架,头文件

    0.2编写空的出口函数

    0.3编写空的入口函数

    0.4修饰出口函数,修饰入口函数,声明LICENSE

     

    在入口函数中所做的工作如下

    1入口函数

    1.1注册一个字符设备,名字为s3c_button

    1.1.1定义file_operations结构体

    1.1.2编写空的打开,读操作函数

    1.2创建一个类,名字为button,(决定了class下目录的名字)

    1.3创建一个设备节点,名字为mybutton(决定了/dev下目录的名字)

    1.4映射gpio的物理地址为虚拟地址,一个是控制寄存器地址,一个是数据寄存器地址

     

    在出口函数中所作的工作如下

    2出口函数

    2.1卸载字符设备

    2.2删除设备节点

    2.3销毁这个类

    2.4取消地址映射

     

    编写操作函数

    3编写file_operations里的操作函数

    3.1编写打开函数

    3.1.1配置GPF4,5,6,7为输入引脚(用来感知按键的gpio引脚)(根据s3c2440 芯片手册配置)

    3.2编写读函数

    3.2.1如果所要读的字节数不对,返回错误

    3.2.2读按键gpio管脚对应的gpio数据寄存器gpfdat

    3.2.3根据所读的数据来判断按下了哪个按键

    3.2.4将数据丢给用户

    3.2.5返回读取到的数据的个数

     

    编写框架

    根据编写思路,实现总体框架(实现编写思路里主体框架,细节内容留在具体代码里编写)。

    /* 本文件名字为button_drv_skel.c*/
    /* 本文件是依照button-查询法驱动<编写思路>章节编写,本文件
      * 的目的是编写代码框架,不做具体细节的编写
      */
     
    /* 本头文件是linux2.6.31内核所提供的,其他版本按需调整 */
    /* 0.1编写代码框架,头文件 */
    #include <linux/module.h>
    #include <linux/ioport.h>
    #include <linux/io.h>
    #include <linux/platform_device.h>
    #include <linux/init.h>
    #include <linux/serial_core.h>
    #include <linux/serial.h>
    #include <asm/irq.h>
    #include <mach/hardware.h>
    #include <plat/regs-serial.h>
    #include <mach/regs-gpio.h>
    #include <asm/uaccess.h>
     
    int major;
    static struct class *buttondrv_class;
    static struct device *buttondrv_class_dev;
    volatile unsigned long *gpfcon;
    volatile unsigned long *gpfdat;
     
    /* 1.1.2编写空的打开函数 */
    static int button_drv_open(struct inode *inode, struct file *file)
    {
           return 0;
    }
     
    /* 1.1.2编写空的读函数 */
    ssize_t button_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
    {
           return 0;
    }
     
    /* 1.1.1定义file_operations结构体 */
    static struct file_operations sencod_drv_fops = {
        .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
        .open   =   button_drv_open,    
           .read       =     button_drv_read,     
    };
     
    /* 0.2编写空的入口函数 */
    static int button_drv_init(void)
    {
           /* 1.1注册一个字符设备,名字为s3c_button */
           major = register_chrdev(0, "button_drv", &sencod_drv_fops);
           /* 1.2创建一个类,名字为button,(决定了class下目录的名字) */
           buttondrv_class = class_create(THIS_MODULE, "button_drv");
           /* 1.3创建一个设备节点,名字为myled(决定了/dev下目录的名字) */
           buttondrv_class_dev = device_create(buttondrv_class, NULL, MKDEV(major, 0), NULL, "buttons"); /* /dev/buttons */
           /* 1.4映射gpio的物理地址为虚拟地址,一个是控制寄存器地址,一个是数据寄存器地址 */
           gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
           gpfdat = gpfcon + 1;
           return 0;
    }
     
    /* 0.3编写空的出口函数 */
    static void button_drv_exit(void)
    {
           /* 2.1 卸载字符设备 */
           unregister_chrdev(major, "button_drv");
           /* 2.2 删除设备节点 */
           device_unregister(buttondrv_class_dev);
           /* 2.3 销毁这个类 */
           class_destroy(buttondrv_class);
           /* 2.4 取消地址映射 */
           iounmap(gpfcon);
    }
     
    /* 0.4修饰出口函数,修饰入口函数,声明LICENSE */
    module_init(button_drv_init);
    module_exit(button_drv_exit);
    MODULE_LICENSE("GPL");
    

     

    驱动代码

    根据《编写框架》《详细步骤》章节,编写每一个函数里所需要实现的小功能。

    /* 本文件名字为button_drv.c*/
    /* 本文件是依照button-查询法驱动<编写思路>章节编写,本文件
      * 的目的是编写代码框架,不做具体细节的编写
      */
     
    /* 本头文件是linux2.6.31内核所提供的,其他版本按需调整 */
    /* 0.1编写代码框架,头文件 */
    #include <linux/module.h>
    #include <linux/ioport.h>
    #include <linux/io.h>
    #include <linux/platform_device.h>
    #include <linux/init.h>
    #include <linux/serial_core.h>
    #include <linux/serial.h>
    #include <asm/irq.h>
    #include <mach/hardware.h>
    #include <plat/regs-serial.h>
    #include <mach/regs-gpio.h>
    #include <asm/uaccess.h>
     
    int major;
    static struct class *buttondrv_class;
    static struct device *buttondrv_class_dev;
    volatile unsigned long *gpfcon;
    volatile unsigned long *gpfdat;
     
    /* 1.1.2编写空的打开函数 */
    static int button_drv_open(struct inode *inode, struct file *file)
    {
           /* 3.1.1配置GPF4,5,6,7为输入引脚 */
           *gpfcon &= ~((0x3<<(2*4)) | (0x3<<(2*5)) | (0x3<<(2*6)) | (0x3<<(2*7)));
           return 0;
    }
     
    /* 1.1.2编写空的读函数 */
    ssize_t button_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
    {
           /* 返回4个引脚的电平 */
           unsigned char key_vals[4];
           int regval;
           unsigned long ret;
           /* 3.2.1如果所要读的字节数不对,返回错误  */
           if (size != sizeof(key_vals))
                  return -EINVAL;
           /* 读GPF4,5,6,7 */
           /* 3.2.2读按键gpio管脚对应的gpio数据寄存器gpfdat */
           regval = *gpfdat;
           /* 3.2.3根据所读的数据来判断按下了哪个按键 */
           key_vals[0] = (regval & (1<<4)) ? 1 : 0;
           key_vals[1] = (regval & (1<<5)) ? 1 : 0;
           key_vals[2] = (regval & (1<<6)) ? 1 : 0;
           key_vals[3] = (regval & (1<<7)) ? 1 : 0;
           /* 3.2.4将数据丢给用户 */
           ret = copy_to_user(buf, key_vals, sizeof(key_vals));
           if ( ret < 0) {
                  printk("copy_to_user failed\n");
                  return -EFAULT;
           }
           /* 3.2.5返回读取到的数据的个数 */
           return sizeof(key_vals);
     
    }
     
    /* 1.1.1定义file_operations结构体 */
    static struct file_operations sencod_drv_fops = {
        .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
        .open   =   button_drv_open,    
           .read       =     button_drv_read,     
    };
     
    /* 0.2编写空的入口函数 */
    static int button_drv_init(void)
    {
           /* 1.1注册一个字符设备,名字为s3c_button */
           major = register_chrdev(0, "s3c_button", &sencod_drv_fops);
           /* 1.2创建一个类,名字为button,(决定了class下目录的名字) */
           buttondrv_class = class_create(THIS_MODULE, "button");
           /* 1.3创建一个设备节点,名字为mybutton(决定了/dev下目录的名字) */
           buttondrv_class_dev = device_create(buttondrv_class, NULL, MKDEV(major, 0), NULL, "mybutton");
           /* 1.4映射gpio的物理地址为虚拟地址,一个是控制寄存器地址,一个是数据寄存器地址 */
           gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
           gpfdat = gpfcon + 1;
           return 0;
    }
     
    /* 0.3编写空的出口函数 */
    static void button_drv_exit(void)
    {
           /* 2.1 卸载字符设备 */
           unregister_chrdev(major, "button_drv");
           /* 2.2 删除设备节点 */
           device_unregister(buttondrv_class_dev);
           /* 2.3 销毁这个类 */
           class_destroy(buttondrv_class);
           /* 2.4 取消地址映射 */
           iounmap(gpfcon);
    }
     
    /* 0.4修饰出口函数,修饰入口函数,声明LICENSE */
    module_init(button_drv_init);
    module_exit(button_drv_exit);
    MODULE_LICENSE("GPL");
    

     

    测试代码

    测试代码编写思路如下:

    1打开/dev/mybutton文件

    2根据读到的值不同,打印不同的值

     

    测试代码如下:

    /* 本文件是button_test.c,是根据button-查询法驱动的
      * <测试代码>章节编写,主要任务是用来测试
      * button 驱动
      */
     
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    int main(int argc, char **argv)
    {
           int fd = 0;
           int cnt = 0;
           int ret = 0;
           unsigned char key_vals[4] = {0};
          
           fd = open("/dev/mybutton", O_RDWR);
           if (fd < 0) {
                  printf("can't open /dev/mybutton!\n");
                  return -1;
           }
           while (1) {
                  ret= read(fd, key_vals, sizeof(key_vals));
                  if (ret < 0) {
                         printf("some thing wrong happend errno is %d\n",ret);
                  } else {
                         if (!key_vals[0] || !key_vals[1] || !key_vals[2] ||
                                !key_vals[3])
                         {
                                printf("%04d key pressed: %d %d %d %d\n",
                                       cnt++, key_vals[0], key_vals[1], key_vals[2],
                                              key_vals[3]);
                         }
                  }
           }
          
           return 0;
    }
    

     

    Makefile

    KERN_DIR = /home/linux/tools/linux-2.6.31_TX2440A
    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
    

     

    目录结构

    代码编写完成的目录结构如下所示。直接执行make即可生成.ko文件。

    .
    ├── button_drv.c(驱动代码)
    ├── button_drv_skel.c(驱动框架代码)
    ├── button_test.c(驱动测试代码)
    └── Makefile(用来编译驱动代码)
    

     

    测试步骤

    0 在linux下的makefile +180行处配置好arch为arm,cross_compile为arm-linux-(或者arm-angstrom-linux-gnueabi-)

    1 在menuconfig中配置好内核源码的目标系统为s3c2440

    2 在pc上将驱动程序编译生成.ko,命令:make

    3 在pc上将测试程序编译生成elf可执行文件,生成的button就是我们所要使用的命令。

    编译:arm-angstrom-linux-gnueabi-gcc button_test.c -o button

    4 挂载nfs,这样就可以在开发板上看到pc端的.ko文件和测试文件

    mount -t nfs -o nolock,vers=2 192.168.0.105:/home/linux/nfs_root  /mnt/nfs

    5 insmod button_drv.ko,加载按键的驱动模块

    6执行命令./button,依次按下四个按键,观察串口的打印信息。

     

    执行结果

    执行完./button后,依次按下四个按键,串口的日志如下,按下一个按键一次,会出现很多次打印。

    按下第一个按键会打印0111,按下第二个会打印1011,按下第三个会打印1101,按下第四个会打印1110。

    0000 key pressed: 0 1 1 1

    0001 key pressed: 0 1 1 1

    0002 key pressed: 0 1 1 1

    ……

    0155 key pressed: 1 0 1 1

    0156 key pressed: 1 0 1 1

    0157 key pressed: 1 0 1 1

    ……

    0310 key pressed: 1 1 0 1

    0311 key pressed: 1 1 0 1

    0312 key pressed: 1 1 0 1

    ……

    0465 key pressed: 1 1 1 0

    0466 key pressed: 1 1 1 0

    0467 key pressed: 1 1 1 0

     

    结果总结

    在本篇文章中,中年润跟读者分享了led字符设备驱动的编写思路和方法,其中贯穿始终的有几个函数和关键数据结构,它们分别是:

    struct file_operations
    struct class
    struct class_device
     
    register_chrdev
    class_create
    device_create
    unregister_chrdev
    device_destroy
    class_destroy
    ioremap
    iounmap
    

     

    请读者尽力去了解这些函数的作用,入参,返回值。

     

    问题汇总

    暂无

     

    实战目标

    1请读者根据《需求描述》章节,独立编写需求分析和需求分解。

    2请读者根据需求分析和需求分解,独立编写编写思路和详细步骤。

    3请读者根据编写思路,独立写出编写框架。

    4请读者根据详细步骤,独立编写驱动代码和测试代码。

    5请读者根据《Makefile》章节,独立编写Makefile。

    6请读者根据《测试步骤》章节,独立进行测试。

    7请读者抛开上述练习,自顶向下从零开始再编写一遍驱动代码,测试代码,makefile

    8如果无法独立写出7,请重复练习1-6,直到能独立写出7。

     

    参考资料

    《linux设备驱动开发祥解》

    《TX2440开发手册及代码》

    《韦东山嵌入式教程》

    《鱼树驱动笔记》

    《s3c2440a》芯片手册英文版和中文版

     

    致谢

    感谢在嵌入式领域深耕多年的前辈,感谢中年润的家人,感谢读者。没有前辈们的开拓,我辈也不能站在巨人的肩膀上看世界;没有家人的鼎力支持,我们也不能集中精力完成自己的工作;没有读者的关注和支持,我们也没有充足的动力来编写和完善文章。看完中年润的文章,希望读者学到的不仅仅是如何编写代码,更进一步能够学到一种思路和一种方法。

     

    为了省去驱动开发者搜集各种资料来写驱动,中年润后续有计划按照本模板编写linux下的常见驱动,敬请读者关注。

     

    联系方式

    微信群:自顶向下学嵌入式(可先加微信号:runzhiqingqing, 通过后会邀请入群。添加时请注明来自哪个平台)

    微信订阅号:自顶向下学嵌入式  公众号:EmbeddedAIOT

    CSDN博客:中年润  网址:https://blog.csdn.net/chichi123137

    邮箱:834759803@qq.com

    QQ群:766756075

    更多原创文章请关注微信公众号。另外,中年润还代理销售韦东山老师的视频教程,欢迎读者咨询。在中年润这里购买了韦东山老师的视频教程,除了能得到韦东山官方的技术支持外,还能获得中年润细致入微的技术和非技术的支持和帮助。欢迎选购哦。

                      学习交流群

     

                       入群小助手

    展开全文
  • linux驱动篇-Input-button

    2019-04-19 23:00:12
    本篇是linux下input子系统下的按键(button/key)驱动,一起来动手吧。下面的话,老鸟可以跳过了直接从《需求描述》章节看起,新手可以试着看看。

    Input-button

    本篇是linux下input子系统下的按键(button/key)驱动,一起来动手吧。下面的话,老鸟可以跳过了直接从《需求描述》章节看起,新手可以试着看看。

     

    前言

    在嵌入式行业,有很多从业者。我们工作的主旋律是拿开源代码,拿厂家代码,完成产品的功能,提升产品的性能,进而解决各种各样的问题。或者是维护一个模块或方向,一搞就是好几年。

    时间长了,中年润发现我们对从零开始编写驱动、应用、算法、系统、协议、文件系统等缺乏经验。没有该有的广度和深度。中年润也是这样,工作了很多年,都是针对某个问题点修修补补或者某个模块的局部删删改改。很少有机会去独自从零开始编写一整套完整的代码。

    当然,这种现状对于企业来说是比较正常的,可以降低风险。但是对于员工本身,如果缺乏必要的规划,很容易工作多年却还是停留在单点的层面,而丧失了提升到较高层面的机会。随着时间的增长很容易丧失竞争力。

    另外,根据中年润的经验,绝大多数公司对于0-5年经验从业者的定位主要是积极的问题解决者。而对于5-10经验从业者的定位主要是积极的系统规划者和引领者。在这种行业规则下,中年润认为,每个从业者都应该问自己一句,“5年后,我是否具备系统化把控软件的能力呢?”。

    当前的这种行业现状,如果我们不做出一点改变,是没有办法突破的。有些东西,仅仅知道是不够的,还需要深思熟虑的思考和必要的训练,简单来说就是要知行合一。

     

    也许有读者会有疑惑?这不就是重复造轮子么?我们确实是在重复造轮子,因为别人会造轮子那是别人的能力,我们自己会造轮子是我们自己的能力。在行业中,有太多的定制化需求是因为轮子本身有原生性缺陷,我们无法直接使用,或者需要对其进行改进,或者需要抽取开源代码的主体思想和框架,根据公司的需要定制自己的各项功能。设想,如果我们具备这种能力,必然会促使我们在行业中脱颖而出,而不是工作很多年一直在底层搬砖。底层搬砖没什么不好,问题是当有更廉价更激情的劳动力涌进来的时候,我们这些老的搬砖民工也就失去了价值。我们不会天天重复造轮子,我们需要通过造几个轮子使得自己具备造轮子的能力,从而更好的适应这个环境,适应这个世界。

     

    针对当前行业现状,中年润经过深思熟虑,想为大家做点实实在在的事情,希望能够帮助大家在巩固基础的同时提升系统化把控软件的能力。当然,中年润的水平也有限,有些观点也只是一家之谈,希望大家独立思考,谨慎采用,如果写的有错误或者不对的地方还请读者们批评斧正,我们一起共同进步。

    在这里简单介绍下中年润,中年润现在就职于一家大型国际化公司,工作经验6年,硕士毕业。曾经担任过组内的项目主管,项目经理,也曾经组建过新团队,带领大家冲锋陷阵。在工作中,有做的不错的地方,也有失误的地方,有激情的时刻,也有失落的时刻。现在偏安一隅,专心搞技术,目前个人规划的技术方向是嵌入式和AI基础设施建设,以及嵌入式和AI的融合发展。

     

    最后,说了这么多,中年润希望,在未来的日子里和未知的领域里,你我同行,为我们的美好生活而努力奋斗。

     

    总体目标

    本篇文章的目标是介绍如何利用linux下的input子系统自顶向下从零编写按键(button/key)驱动。着力从总体思路,需求端,分析端,实现端,详尽描述一个完整需求的开发流程,是中年润多年经验的提炼,希望读者能够有所收获。最后的实战目标,请读者尽量完成,这样读者才能形成自己的思路。

    本示例采用arm920架构,天祥电子生产的tx2440a开发板,核心为三星的s3c2440。Linux版本为2.6.31,是已经移植好的版本。编译器为arm920t-eabi-4.1.2.tar。

     

    总体思路

    总体思路是严格遵循需求的开发流程来,不遗漏任何思考环节。读者在阅读时请先跟中年润的思路走一遍,然后再抛弃中年润的思路,按照自己的思路走一遍,如果遇到困难请先自己思考,实在不会再来参考中年润的思路和实现。

     

    中年润在写代码的的总体思路如下:

    需求描述—能够详细完整的描述一个需求。

    需求分析—根据需求描述,提取可供实现的功能,需要有定量或者定性的指标。(从宏观上确定需要什么功能)。

    需求分解—根据需求分析,考虑要实现需求所需要做的工作(根据宏观确定的功能,拆分成小的可单独实现的功能)。

    编写思路—根据需求分解从总体上描述应该如何编写代码,(解决怎么在宏观上实现)。

    详细步骤—根据编写思路,落实具体步骤,(解决怎么在微观上实现)。

    编写框架—根据编写思路,实现总体框架(实现编写思路里主体框架,细节内容留在具体代码里编写)。

    具体代码—根据编写框架,编写每一个函数里所需要实现的小功能,主要是实现驱动代码,测试代码。

    Makefile—用来编译驱动代码。

    目录结构—用来说明当完成编码后的结果。

    测试步骤—说明如何对驱动进行测试,主要是加载驱动模块,执行测试代码。

    执行结果—观察执行结果是否符合预期。

    结果总结—回顾本节的思路,知识点,api,结构体。

    实战目标—说明如何根据本文档训练。

    请大家尽量按照自顶向下的学习思路来学习和实战,因为我们所有工作的动力都是我们心中的需求。这些步骤仅仅是我们达到目标所要走过的路。目录起到提纲挈领的重要作用,写的时候要实时看下提纲,看有没有偏离自己的方向。

     

    需求描述

    使用input子系统接口,编写一个按键驱动,能够让四个按键分别表达L,S,ENTER(回车换行),LEFTSHIFT(左shift)。

     

    需求分析

    根据《需求描述》,从宏观上提取可供实现的功能,我们需要做以下几件工作。

    1需要使用input子系统接口

    2需要能够检测按键的状态来判断是哪个按键按下或者松开

    3需要通过四个按键的按下来上报L,S,ENTER,LEFTSHIFT4个事件

     

    需求分解

    根据《需求分析》的结果,将宏观确定的功能拆分成小的可单独实现的功能,我们需要做以下几件工作。

    1需要注册一个input dev结构

    2需要注册检测按键状态的中断函数

    3需要在按下时,利用input api将按键的事件上报

    4按键按下时一般会有抖动,需要通过定时器来延时消抖

     

    编写思路

    编写思路主要用来搭建代码框架,解决在宏观上如何用代码实现驱动的功能。

    input驱动的核心是注册input设备,并注册检测按键状态的中断处理函数。

     

    0搭建基础框架

    0.1编写代码框架,头文件,修饰出口函数,修饰入口函数,声明LICENSE

    0.2构造和定义基础数据结构

    0.2.1构造引脚描述符数据结构

    0.2.2重新定义引脚号码

    0.2.3定义引脚描述符

     

    在入口函数中所做的工作如下

    1入口函数

    1.1 分配一个input_dev 结构体

    1.2 设置

    1.2.1 设置能产生哪类事件

    1.2.2 设置能产生这类事件的哪些事件: L,S,ENTER,LEFTSHIT

    1.3 注册input_dev

    1.4 硬件相关操作

    1.4.1 注册一个定时器

    1.4.2 注册中断及中断处理函数

     

    在出口函数中所作的工作如下

    2出口函数

    2.1 释放中断

    2.2 删除定时器

    2.3 卸载input设备

    2.4 释放input设备

     

    详细步骤

    详细步骤主要用来在代码框架里填充必要的细节代码,解决在微观上如何用代码实现驱动各个小功能。

    Input按键驱动的总体核心是注册input设备,并注册按键中断处理函数。

     

    0搭建基础框架

    0.1编写代码框架,头文件,修饰出口函数,修饰入口函数,声明LICENSE

    0.2构造和定义基础数据结构

    0.2.1构造引脚描述符数据结构

    0.2.2重新定义引脚号码,就是GPIO口

    0.2.3定义引脚描述符,定义按键所需要的资源

     

    在入口函数中所做的工作如下:

    1入口函数

    1.1 分配一个input_dev 结构体

    1.2 设置

    1.2.1 设置能产生哪类事件

    1.2.2 设置能产生这类事件的哪些事件: L,S,ENTER,LEFTSHIT

    1.3 注册input_dev

    1.4 硬件相关操作

    1.4.1 注册一个定时器

    1.4.1.1编写定时器处理函数

    1.4.2 注册中断及中断处理函数

    1.4.2.1编写中断处理函数

     

    在出口函数中所作的工作如下

    2出口函数

    2.1 释放中断

    2.2 删除定时器

    2.3 卸载input设备

    2.4 释放input设备

     

    编写框架

    根据编写思路,实现总体框架(实现编写思路里主体框架,细节内容留在具体代码里编写)。

     

     /* 本文件名字为input_button_skel.c*/
    /* 本文件是依照input 驱动<编写思路>章节编写,本文件
      * 的目的是编写代码框架,不做具体细节的编写
      */
    /* 本头文件是linux2.6.31内核所提供的,其他版本按需调整 */
     
    /* 0.1编写代码框架,头文件,出入口函数,声明LICENSE */
    #include <linux/module.h>
    #include <linux/ioport.h>
    #include <linux/io.h>
    #include <linux/platform_device.h>
    #include <linux/init.h>
    #include <linux/serial_core.h>
    #include <linux/serial.h>
    #include <linux/irq.h>
    #include <asm/irq.h>
    #include <asm/io.h>
    #include <asm/uaccess.h>
    #include <mach/hardware.h>
    #include <mach/regs-gpio.h>
    #include <mach/gpio-fns.h>
    #include <plat/regs-serial.h>
    #include <linux/input.h>
    /* 本文件的编写思路如下,请参考中年润所写的input驱动文档 */
    /* 需求分析 --- 需求分解 --- 编写框架*/
     
    /* 0.2构造和定义基础数据结构 */
    /* 0.2.1构造引脚描述符数据结构 */
    /* 引脚描述符 */
    struct pin_desc{
           int irqnum;
           char *name;
           int pin;
           int key_val;
    };
     
    /*按引脚的不同定义不同的值,在中断中进行处理
    *要自己重新定义下面几个宏,配合s3c2410_gpio_getpin函数使用
    */
    /* 0.2.2重新定义引脚号码 */
    #define S3C2410_GPF4 S3C2410_GPF(4)
    #define S3C2410_GPF5 S3C2410_GPF(5)
    #define S3C2410_GPF6 S3C2410_GPF(6)
    #define S3C2410_GPF7 S3C2410_GPF(7)
     
    static struct timer_list button_timer;
    static struct pin_desc *irq_pindesc;
    static struct input_dev * button_inputdev;
     
    /* 0.2.3定义引脚描述符 */
    /* 定义按键所需要的中断号,名字,gpio引脚,按键值*/
    struct pin_desc pin_desc[4] =
    {
     
    };
     
    /* 定时器函数 */
    void button_timer_func(unsigned long data)
    {
     
    }
     
    /* 按键中断函数 */
    irqreturn_t button_irq(int irq, void * devid)
    {
           return IRQ_RETVAL(IRQ_HANDLED);
    }
     
    /* 1入口函数 */
    static int my_button_init(void)
    {
           int ret = 0;
           int i = 0;
           /* 1.1 分配一个input_dev 结构体 */
           button_inputdev = input_allocate_device();
           /* 1.2 设置*/
           /* 1.2.1 设置能产生哪类事件 EV_KEY*/
           set_bit(EV_KEY,button_inputdev->evbit);
           set_bit(EV_REP,button_inputdev->evbit);
           /* 1.2.2 设置能产生这类事件的哪些事件: L,S,ENTER,LEFTSHIFT */
           set_bit(KEY_L,button_inputdev->keybit);
           set_bit(KEY_S,button_inputdev->keybit);
           set_bit(KEY_ENTER,button_inputdev->keybit);
           set_bit(KEY_LEFTSHIFT,button_inputdev->keybit);
           /* 1.3 注册input_dev */
           ret = input_register_device(button_inputdev);
           if (ret != 0)
           {
                  printk("input_register_device failed\n");
           }
           /* 1.4 硬件相关操作 */
           /* 1.4.1 定时器相关操作 */
           init_timer(&button_timer);
           //buttons_timer.expires  = 0;
           button_timer.function = button_timer_func;
           add_timer(&button_timer);
           /* 1.4.2 注册中断及中断处理函数 */
           for (i = 0; i < 4; i ++){ 
                  ret = request_irq(pin_desc[i].irqnum, button_irq, IRQ_TYPE_EDGE_BOTH, pin_desc[i].name,(void *)&pin_desc[i]);
                  if (ret != 0)
                  {
                         printk("request button irq failed error code %d\n",ret);
                  }
           }
     
           return ret;
    }
     
    /* 2出口函数 */
    static void my_button_exit(void)
    {
           int i = 0;
           /* 2.1 释放中断 */
           for (i = 0; i < 4; i ++){
                  free_irq(pin_desc[i].irqnum,&pin_desc[i]);
           }
           /* 2.2 删除定时器 */
           del_timer(&button_timer);
           /* 2.3 卸载input设备 */
           input_unregister_device(button_inputdev);
           /* 2.4 释放input设备 */
           input_free_device(button_inputdev);
           return;
    }
     
    /* 0.1编写代码框架,头文件,修饰出口函数,修饰入口函数,声明LICENSE */
    module_init(my_button_init);
    module_exit(my_button_exit);
    MODULE_LICENSE("GPL");
    

     

    驱动代码

    /* 本文件名字为input_button.c*/
    /* 本文件是依照input 驱动<详细步骤>章节编写,本文件
      * 的目的是编写具体代码,不介绍框架
      */
    /* 本头文件是linux2.6.31内核所提供的,其他版本按需调整 */
     
    /* 0.1编写代码框架,头文件,出入口函数,声明LICENSE */
    #include <linux/module.h>
    #include <linux/ioport.h>
    #include <linux/io.h>
    #include <linux/platform_device.h>
    #include <linux/init.h>
    #include <linux/serial_core.h>
    #include <linux/serial.h>
    #include <linux/irq.h>
    #include <asm/irq.h>
    #include <asm/io.h>
    #include <asm/uaccess.h>
    #include <mach/hardware.h>
    #include <mach/regs-gpio.h>
    #include <mach/gpio-fns.h>
    #include <plat/regs-serial.h>
    #include <linux/input.h>
    /* 本文件的编写思路如下,请参考中年润所写的input驱动文档 */
    /* 需求分析 --- 需求分解 --- 编写框架--编写代码*/
     
    /* 0.2构造和定义基础数据结构 */
    /* 0.2.1构造引脚描述符数据结构 */
    /* 引脚描述符 */
    struct pin_desc{
           int irqnum;
           char *name;
           int pin;
           int key_val;
    };
     
    /*按引脚的不同定义不同的值,在中断中进行处理
    *要自己重新定义下面几个宏,配合s3c2410_gpio_getpin函数使用
    */
    /* 0.2.2重新定义引脚号码,就是GPIO口 */
    #define S3C2410_GPF4 S3C2410_GPF(4)
    #define S3C2410_GPF5 S3C2410_GPF(5)
    #define S3C2410_GPF6 S3C2410_GPF(6)
    #define S3C2410_GPF7 S3C2410_GPF(7)
     
    static struct timer_list button_timer;
    static struct pin_desc *irq_pindesc;
    static struct input_dev * button_inputdev;
     
    /* 0.2.3定义引脚描述符,定义按键所需要的资源 */
    /* 定义按键所需要的中断号,名字,gpio引脚,按键值*/
    struct pin_desc pin_desc[4] =
    {
           {IRQ_EINT4,"S1",S3C2410_GPF4,KEY_L},
           {IRQ_EINT5,"S2",S3C2410_GPF5,KEY_S},
     
           {IRQ_EINT6,"S3",S3C2410_GPF6,KEY_ENTER},
           {IRQ_EINT7,"S4",S3C2410_GPF7,KEY_LEFTSHIFT},
    };
     
    /* 1.4.1.1编写定时器处理函数 */
    /* 定时器函数 */
    void button_timer_func(unsigned long data)
    {
           int pin_val;
           struct pin_desc * pin_readed;
           pin_readed = irq_pindesc;
           if (!pin_readed)
                  return;
          
           /*获取某个引脚是高还是低,需要配合上面定义的宏使用*/
           pin_val = s3c2410_gpio_getpin(pin_readed->pin);
           if (pin_val) /*松开是高电平*/
           {
                  /*上报事件,0表示松开,最后一个参数: 0-松开, 1-按下*/
                  input_event(button_inputdev, EV_KEY, pin_readed->key_val, 0);
           }
           else        /* 按下为低电平*/
           {
                  /*上报事件,1表示按下,最后一个参数: 0-松开, 1-按下*/
                  input_event(button_inputdev, EV_KEY, pin_readed->key_val, 1);
           }
           /* 上报同步事件*/
           input_sync(button_inputdev);     
     
    }
     
    /* 1.4.2.1编写中断处理函数 */
    /* 按键中断函数 */
    irqreturn_t button_irq(int irq, void * devid)
    {
           /*记录传入的参数值,利用定时器延时,10ms后调用定时器中断处理函数
           *如果有按键抖动会多次进入按键中断函数,会多次修改调用定时器中断
           *的时机,最后的效果就是只调用了一次定时器中断处理函数
           *HZ代表1s,HZ/100是10ms
           *mod_timer第二个参数应该是jiffies+你想要延时的时间
           */
           irq_pindesc = (struct pin_desc *)devid;
           mod_timer(&button_timer,jiffies + HZ / 100);
           return IRQ_RETVAL(IRQ_HANDLED);
    }
     
    /* 1入口函数 */
    static int my_button_init(void)
    {
           int ret = 0;
           int i = 0;
           /* 1.1 分配一个input_dev 结构体 */
           button_inputdev = input_allocate_device();
           /* 1.2 设置*/
           /* 1.2.1 设置能产生哪类事件 EV_KEY*/
           set_bit(EV_KEY,button_inputdev->evbit);
           set_bit(EV_REP,button_inputdev->evbit);
           /* 1.2.2 设置能产生这类事件的哪些事件: L,S,ENTER,LEFTSHIFT */
           set_bit(KEY_L,button_inputdev->keybit);
           set_bit(KEY_S,button_inputdev->keybit);
           set_bit(KEY_ENTER,button_inputdev->keybit);
           set_bit(KEY_LEFTSHIFT,button_inputdev->keybit);
           /* 1.3 注册input_dev */
           ret = input_register_device(button_inputdev);
           if (ret != 0)
           {
                  printk("input_register_device failed\n");
           }
           /* 1.4 硬件相关操作 */
           /* 1.4.1 定时器相关操作 */
           init_timer(&button_timer);
           //buttons_timer.expires  = 0;
           button_timer.function = button_timer_func;
           add_timer(&button_timer);
           /* 1.4.2 注册中断及中断处理函数 */
           for (i = 0; i < 4; i ++){ 
                  ret = request_irq(pin_desc[i].irqnum, button_irq, IRQ_TYPE_EDGE_BOTH, pin_desc[i].name,(void *)&pin_desc[i]);           
                  if (ret != 0)
                  {
                         printk("request button irq failed error code %d\n",ret);
                  }
           }
     
           return ret;
    }
     
    /* 2出口函数 */
    static void my_button_exit(void)
    {
           int i = 0;
           /* 2.1 释放中断 */
           for (i = 0; i < 4; i ++){
                  free_irq(pin_desc[i].irqnum,&pin_desc[i]);
           }
           /* 2.2 删除定时器 */
           del_timer(&button_timer);
           /* 2.3 卸载input设备 */
           input_unregister_device(button_inputdev);
           /* 2.4 释放input设备 */
           input_free_device(button_inputdev);
           return;
    }
     
    /* 0.1编写代码框架,头文件,修饰出口函数,修饰入口函数,声明LICENSE */
    module_init(my_button_init);
    module_exit(my_button_exit);
    MODULE_LICENSE("GPL");
    

     

    测试代码

    本示例无需测试代码,可以通过相关命令测试。

     

    Makefile

    KERN_DIR = /home/linux/tools/linux-2.6.31_TX2440A
    all:
           make -C $(KERN_DIR) M=`pwd` modules
    clean:
           make -C $(KERN_DIR) M=`pwd` modules clean
           rm -rf modules.order
    obj-m += input_button.o
    

    如果名字不同,请更换obj-m后面.o的名字。

     

    目录结构

    代码编写完成的目录结构如下所示。直接执行make即可生成.ko文件。

    .
    ├── input_button.c
    ├── input_button_skel.c
    └── Makefile
    

     

    测试步骤

    0 在linux下的makefile +180行处配置好arch为arm,cross_compile为arm-linux-(arm-angstrom-linux-gnueabi-)

    1 在menuconfig中配置好内核源码的目标系统为s3c2440

    2 在pc上将驱动程序编译生成.ko,命令:make

    3 挂载nfs,这样就可以在开发板上看到pc端的.ko文件和测试文件

    mount -t nfs -o nolock,vers=2 192.168.0.105:/home/linux/nfs_root  /mnt/nfs

    4 insmod input_button.ko

    input: Unspecified device as /class/input/input0

    5 cat /dev/tty1

    6依次按下四个按键,观察现象

     

    执行结果

    [root@TX2440A 8input-button]# cat /dev/tty1

    lllllss

    ls

    ls

    LS

    ls

    LS

    llllllllllllllllll

    ssssssssssss

    单次按下,会有输出l,s,ls,配合板子的SHIFT键,会有输出L,S,LS

    按着不动,会重复输出l或者s

     

    [root@TX2440A 8input-button]# ls /dev/event*

    /dev/event0

    event0代表着input设备

     

    结果总结

    在本篇文章中,中年润跟读者分享了input按键驱动的编写思路和方法,其中贯穿始终的有几个函数和关键数据结构。它们分别是:

    struct input_dev

    struct timer_list

    s3c2410_gpio_getpin

    input_event

    input_sync

    input_allocate_device

    set_bit

    input_register_device

    init_timer

    request_irq

    free_irq

    del_timer

    input_unregister_device

    input_free_device

    宏S3C2410_GPF()

    请读者尽力去了解这些函数的作用,入参,返回值。

     

    实战目标

    1请读者根据《需求描述》章节,独立编写需求分析和需求分解。

    2请读者根据需求分析和需求分解,独立编写编写思路和详细步骤。

    3请读者根据编写思路,独立写出编写框架。

    4请读者根据详细步骤,独立编写驱动代码和测试代码。

    5请读者根据《Makefile》章节,独立编写Makefile。

    6请读者根据《测试步骤》章节,独立进行测试。

    7请读者抛开上述练习,自顶向下从零开始再编写一遍驱动代码,测试代码,makefile

    8如果无法独立写出7,请重复练习1-6,直到能独立写出7。

     

    参考资料

    《linux设备驱动开发祥解》

    《TX2440开发手册及代码》

    《韦东山嵌入式教程》

    《鱼树驱动笔记》

    《s3c2440a》芯片手册英文版和中文版

     

     

    致谢

    感谢在嵌入式领域深耕多年的前辈,感谢中年润的家人,感谢读者。没有前辈们的开拓,我辈也不能站在巨人的肩膀上看世界;没有家人的鼎力支持,我们也不能集中精力完成自己的工作;没有读者的关注和支持,我们也没有充足的动力来编写和完善文章。看完中年润的文章,希望读者学到的不仅仅是如何编写代码,更进一步能够学到一种思路和一种方法。

     

    为了省去驱动开发者搜集各种资料来写驱动,中年润后续有计划按照本模板编写linux下的常见驱动,敬请读者关注。

     

    联系方式

    微信群:见文章最底部,因微信群有效期只有7天,感兴趣的同学可以加下。微信群里主要是为初学者答疑解惑,也可以进行技术和非技术的交流。如果微信群失效了,大家可以加qq群,我会在qq群里分享微信群的二维码。同时也欢迎和中年润志同道合的中年人尤其是中年码农的加入。

    微信订阅号:自顶向下学嵌入式

    公众号微信:EmbeddedAIOT

    CSDN博客:chichi123137

    CSDN博客网址:https://blog.csdn.net/chichi123137

    QQ邮箱:834759803@qq.com

    QQ群:766756075

    更多原创文章请关注微信公众号。另外,中年润还代理销售韦东山老师的视频教程,欢迎读者咨询。在中年润这里购买了韦东山老师的视频教程,除了能得到韦东山官方的技术支持外,还能获得中年润细致入微的技术和非技术的支持和帮助。欢迎大家选购哦。

    展开全文
  • 更新时间:2020年10月21日 17:19:57 作者:Red&&Black 这篇文章主要为大家详细介绍了Android实现简单计时器功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考...xml vers...

     更新时间:2020年10月21日 17:19:57   作者:Red&&Black  

    这篇文章主要为大家详细介绍了Android实现简单计时器功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

    本文实例为大家分享了Android实现简单计时器的具体代码,供大家参考,具体内容如下

    布局

    在res/layout 下进行布局

    
    <?xml version="" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout
     xmlns:android=""
     xmlns:tools=""
     xmlns:app=""
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     tools:context=".MainActivity">
    
    
     <LinearLayout
      android:orientation="vertical"
      android:layout_width="match_parent"
      android:layout_height="match_parent" tools:layout_editor_absoluteY="0dp"
      tools:layout_editor_absoluteX="0dp">
     <TextView
      android:text="00:00:00"
      android:textSize="60sp"
      android:gravity="center"
      android:layout_width="match_parent"
      android:layout_height="wrap_content" android:id="@+id/timeView"/>
     <Button
      android:text="start"
      android:onClick="onClickStart"
      android:layout_width="match_parent"
      android:layout_height="wrap_content" android:id="@+id/start"/>
     <Button
      android:text="stop"
      android:onClick="onClickStop"
      android:layout_width="match_parent"
      android:layout_height="wrap_content" android:id="@+id/stop"/>
     <Button
      android:text="reset"
      android:onClick="onClickReset"
      android:layout_width="match_parent"
      android:layout_height="wrap_content" android:id="@+id/reset"/>
     </LinearLayout>
    
    </android.support.constraint.ConstraintLayout>

    MainActivity

    
    package com.test;
    
    import android.os.Handler;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.View;
    
    import android.widget.TextView;
    
    public class MainActivity extends AppCompatActivity {
    
    
     private int seconds = 0;
     private boolean running = false; //计时状态
     private boolean wasRunning = false; //保存running的状态
    
     //app进入后台,暂停计时
     @Override
     protected void onStop() {
     ();
     wasRunning = running;
     running = false;
     }
    
     //重新进入app,开始计时
     @Override
     protected void onStart() {
     ();
     if(wasRunning) running = true;
     }
    
     //失去焦点(如分屏),暂停计时
     @Override
     protected void onPause() {
     ();
     wasRunning = running;
     running = false;
     }
    
     //获得焦点,重新开始计时
     @Override
     protected void onResume() {
     ();
     if(wasRunning) running = true;
     }
    
     @Override
     protected void onCreate(Bundle savedInstanceState) {
     (savedInstanceState);
     setContentView();
     //获取保存的状态
     if(savedInstanceState!=null){
      seconds = ("seconds");
      running = ("running");
      wasRunning = ("wasRunning");
     }
     runTime();
     }
    
     /**
     *保存状态
     */
     @Override
     public void onSaveInstanceState(Bundle saveInstanceState) {
    
     (saveInstanceState);
     ("seconds",seconds);
     ("running",running);
     ("wasRunning",wasRunning);
     }
     /**
     * 响应button的onClick事件
     * 方法名和onClick的值一致
     */
     public void onClickStart(View button){
     running = true;
     }
     public void onClickStop(View button){
     running = false;
     }
     public void onClickReset(View button){
     running = false;
     seconds = 0;
     }
    
     /**
     * 注意 ui线程不能被堵塞,因此不能在ui线程中调用sleep方法
     * 只允许ui线程更新界面,不能在后台线程更新界面
     *
     * ** 使用ui线程的Handler定时更新 **
     * 将任务封装到 Runnable的run方法中 ,通过Handler的
     * post(立即提交任务)或postDelayed(实现定时调度)方法提交到ui线程
     */
     private void runTime(){
     final Handler handler = new Handler();
     (new Runnable() {
      @Override
      public void run() {
      final TextView textView = findViewById();
      int hour = seconds /3600%24;
      int minute = seconds%3600/60;
      String time = ("%02d:%02d:%02d",hour,minute,seconds%60);
      (time);
      if(running) seconds++;
      Delayed(this,1000);
      }
      }
     );
    
     }
    }

    测试

    46199b99e4d37c02a6c59a4cf7903890.png

    完成

    以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

    展开全文
  • 一直想写点东西,但不知道写什么,一直在学Android开发,正好借此机会练练写作,呵呵,长话短说,今天学习Android的Button控件和TextView控件,什么??你还不会建立Android开发平台?那麻烦您去百度或是Google一下吧. Button...
  • mount -t nfs -o nolock,vers=2 192.168.0.103:/home/linux/nfs_root /mnt   执行步骤如下: insmod second_drv.ko 加载模块 ./second 执行用户程序 执行结果如下 该程序会出现按键抖动,检测...
  • 本篇是linux下按键设备驱动,采用的中断法和poll机制,也是属于字符设备类的驱动,一起来动手吧。... major = register_chrdev(0,"button-poll",&button_fops); /* 1.2 创建一个类 */ my_button_cls = class_create...
  • How to add a custom button

    2020-12-26 18:55:20
    <div><p>Como adicionar um botão customizado na versão 3.0 do angular-froala? <p>In the previous version it was done as follows: <p><code>declare var $: any; $.FroalaEditor.DefineIcon('assinatura...
  • You can config file by this command: ...grep -rl "Could not detect Mac OS X Version from sw_vers output:" /Applications/Appium.app/ Terminal will show : /Applications/Appium.app//Content...
  • shape特效定制很重要,但不经常用容易忘记,所以记录在这里方便以后需要时直接来看笔记,本人不喜欢背代码,只喜欢活学活用! 实现效果如下图中按钮的样式,有边框描边,有中间背景颜色过渡渐变,有四个...xml vers...
  • JS演示图论汇总

    千次阅读 多人点赞 2020-01-11 11:23:16
    edges[DFS.LocateEg(vers[cur], vers[j])].setStatus(1); DrawGraph(); Note(DFS.msg()); document.getElementById('NextBtn').disabled = false; }); return; } this.curV = this.vstack.pop(); if (this....
  • } 在probe中的gpio_keys_setup_key函数分析: static int gpio_keys_setup_key(struct platform_device *pdev, struct input_dev *input, struct gpio_button_data *bdata, const struct gpio_keys_...
  • You can config file by this command: ...grep -rl "Could not detect Mac OS X Version from sw_vers output:" /Applications/Appium.app/ Terminal will show : /Applications/Appium.app//Co...
  • 字符设备驱动概述

    2016-03-04 19:11:36
    在JZ2440的环境中使用mount -t nfs -o nolock,vers=2 192.168.2.102:/work /mnt/by_work进行挂接 而可以挂接的目录在虚拟机设备上的/etc/exports文件中 驱动程序只有有了设备节点才可以使用 创建根文件系统...
  • JEECG-BOOT环境搭建:2.1.1版

    万次阅读 2019-11-05 20:09:04
    liumiaocn:~ liumiao$ sw_vers ProductName: Mac OS X ProductVersion: 10.14 BuildVersion: 18A391 liumiaocn:~ liumiao$ MySQL liumiaocn:~ liumiao$ mysql --version mysql Ver 8.0.11 for osx10.13 on x86_...
  • ng-alain新版尝试

    2021-03-19 23:48:56
    环境 liumiao@liumiaodeMacBook-Pro alain-project % sw_vers ProductName: macOS ProductVersion: 11.1 BuildVersion: 20C69 liumiao@liumiaodeMacBook-Pro alain-project % 步骤1: 安装nvm liumiao@...
  • IF_WDY_MD_BUTTON_ROW_ITEM IF_WDY_MD_CONTEXTUAL_PANEL_ITM /IWBEP/IF_MGW_VOCAN_TERM IF_DLV_OLC_ALV_GRID GET_WINDOW_MAXIMIZE IF_DO_CDL_RQ_ACTION_REQUEST IF_DO_CDL_RQ_FUNCTION_REQUEST IF_WDY_MD_...
  • &mini2440_button_device, &s3c_device_nand, &s3c_device_sdi, &s3c_device_iis, &uda1340_codec, &mini2440_audio, &samsung_asoc_dma, }; 然后再搜索:mini2440_devices,找到: platform_add_...
  • Ubuntu 意外死机 (Linux Crash/Hang)解决以Intel Bay Trail/J1900/N2940 为例,通常是由于linux kernel和硬件兼容性问题导致:查询网址:https://bugzilla.kernel.org/点开对应问题,就可以看到问题,和一些解决...
  • 使用 global key 一键启动应用程序

    千次阅读 2017-08-26 12:14:11
    busybox mount -t nfs -o nolock,vers=2 192.168.1.170:/work/nfs_root /data/nfs 拷贝apk文件覆盖到system/framework里面的文件: cp framework-res.apk /system/framework/ 把原来上一章那个模拟按键的那个...
  • noter-源码

    2021-03-19 04:48:50
    :rocket: Versão1.0 :check_mark_button: •• • • • • :magnifying_glass_tilted_right:维萨尔杰拉尔注释和注释的网络结构。 Utilizando或melhor没有后端com Node Js e Express,没有前端com React JS,...
  • 使用ST05查找BADI

    千次阅读 2013-10-22 16:32:34
    Push button "Multiple selections" button behind field Objects Fill:  V_EXT_IMP  and  V_EXT_ACT Push button "Copy (F8)" Fill Operations:  OPEN Push button Enter See the result...
  • 一些常用的网页特效

    千次阅读 2008-01-09 14:53:00
    11. <input type=button value=查看网页源代码 onclick="window.location = "view-source:"+ " http://www.pconline.com.cn ""> 12.删除时确认 (confirm("确实要删除吗?"))location="boos.asp?&areyou=删除&page...
  • 如何查找BAdi

    2008-03-21 16:41:00
    Push button "Multiple selections" button behind field Objects Fill: V_EXT_IMP and V_EXT_ACT Push button "Copy (F8)" Fill Operations: OPEN Push button Enter See the result: Interpreting the...
  • 动态窗口法

    2019-12-12 11:32:06
    1 """ 2 version2.0,增加环境动态 3 vers...
  • Oracle 10g RAC On Linux Using NFS

    千次阅读 2010-03-18 23:52:00
    http://inthirties.com:90/thread-918-3-1.htmlThis article describes the installation of Oracle 10g release 2 (10.2.0.1) RAC on Linux (Oracle Enterprise Linux 4.5) using NFS to provide the
  • Eclipse paho实现的MQTT Java客户端

    千次阅读 2017-12-07 16:10:32
    button_disconnect.setActionCommand("disconnect"); button_disconnect.setBounds(263, 66, 93, 23); contentPane.add(button_disconnect); JLabel label = new JLabel("订阅主题:"); label.setBounds...
  • XEditText:XEditText-源码

    2021-04-22 14:47:19
    XEditText 包装EditText常用用法。 特征 要清除所有文本内容,只需在右侧单击一下即可。 清晰的可绘制对象是可定制的。 ... 切换可绘制对象也是可... implementation ' com.xw.repo:xedittext-androidx:${LATEST_VERS
  • jQuery 事件方法

    2016-09-19 09:23:24
    Query 事件方法 ...$("button#demo").click() 上面的例子将触发 id="demo" 的 button 元素的 click 事件。 绑定实例: $("button#demo").click(function(){$("img").hide()}) 上面的例子会在点击 id="de
  • minigui成功移植到ubuntu64位平台

    千次阅读 2016-09-06 20:49:20
    1.pc系统ubuntu14LTS 64bit,同时在32位ubuntu16.04上经过了测试,官方的所有范例程序都能运行。 2.过两天会写份详细的移植教程,现在只是将移植好的文件上传到我的csdn下载,大家可以免费下载。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 530
精华内容 212
关键字:

buttonvers