IMX6ULL按键控制LED灯亮灭(基于韦东山嵌入式Linux教程)

目录

一、编写驱动程序的三种方法

1.1 传统方法

1.2 总线设备驱动模型

1.3 设备树

二、IMX6ULL按键控制LED灯亮灭(实现部分)

2.1 按键驱动部分(button_drv_source)

2.1.1 硬件层(chip_xxx_gpio.c)

2.1.2 中间层(xxx_drv.c)

2.2 led驱动部分(led_drv_source)

2.2.1 硬件层(chip_xxx_gpio.c)

2.2.2 中间层(led_drv.c)

2.3 编写应用层程序实现按键操作led亮灭

三、编译源码,测试


一、编写驱动程序的三种方法

1.1 传统方法

       在传统方法中,资源和驱动都放在一个文件里面,使用哪个引脚,怎么操作引脚都写死在一个文件中,这种方法缺点就是扩展性差,每次修改引脚时都要重新编译。

1.2 总线设备驱动模型

在总线设备驱动模型中,我们把资源和驱动分离开来,如果需要修改引脚,我们只需要单独修改编译资源部分,而不需要编译驱动部分,增加了可扩展性,这种方法的缺点就是会产生一大堆资源文件在内核中,不方便管理。

1.3 设备树

       在引入设备树后,我们的资源统一放到设备树中管理,修改资源只需要更改设备树文件,极大的方便资源的管理。

二、IMX6ULL按键控制LED灯亮灭(实现部分)

       我所采用的是前面介绍的传统方法来实现,为了方便拓展,我也分了两层来实现,一层是chip_xxx_gpio.c,作为硬件层,主要用来配置引脚资源和操作相应寄存器;另一层是xxx_drv.c,作为应用层和硬件层的中间层,向上(应用层)提供驱动接口(open,read,write等等),向下(硬件层)调用硬件操作。下面是我的代码的结构,源码放在文章末尾。

2.1 按键驱动部分(button_drv_source)

2.1.1 硬件层(chip_xxx_gpio.c)

       首先是硬件层,这部分涉及到具体的gpio的操作以及引脚的配置,所以我们要从板子的原理图,以及芯片手册开始看起(这两个文件我会一起放在源码中)。

在板子的原理图中(100ask_imx6ull_v1.1.pdf文件),我们先通过目录找到GPIO部分:

 在右边我们可以看到两个按键的原理图,我们使用的是左边的按键:

从原理图上可以看到,我们可以通过第5组GPIO模块的第1个引脚(GPIO5_01)来获得引脚电平,并且可以知道按键未按下时引脚为高电平,按下时引脚为低电平。

在原理图上得到gpio引脚信息后,接下来我们就可以通过芯片手册(IMX6ULLRM.pdf文件)来配置各个模块。我们主要操作的模块有三个:

在这里插入图片描述

首先是CCM模块,这个模块主要用来控制时钟,哪组 GPIO用哪个 CCM_CCGR 寄存器来设置,请看上图红框部分(如GPIO5使用CCGR1[CG15]来控制)。

我们可以到芯片手册里面找到CCGR1寄存器的位置(手册Chapter 18​: Clock Controller Module (CCM) --> CCM Memory Map/Register Definition --> CCM下 ),可以看到30-31位是gpio5的时钟使能位,并且找到了寄存器的物理地址(0x20C406C):

CCM_CCGR 寄存器中某 2 位的取值含义如下:

在这里插入图片描述

① 00:该 GPIO 模块全程被关闭。
② 01:该 GPIO 模块在 CPU run mode 情况下是使能的;在 WAIT 或 STOP 模式下,关闭。
③ 10:保留。
④ 11:该 GPIO 模块全程使能。

因为我们肯定要gpio模块全程使能,所以要初始化CCM_CCGR1模块的30-31位为11,初始化代码如下:

/* 声明指针 */
static volatile unsigned int *CCM_CCGR1; 

/* 初始化指针,指针指向CCM_CCGR1寄存器,ioremap的作用是将物理地址映射到虚拟地址上 */
CCM_CCGR1 = ioremap(0x20C406C, 4);

/* 使能GPIO5时钟*/
*CCM_CCGR1 |= (3<<30); //30-31位置1

 其次是IOMUXC模块,用于控制引脚的复用,这里我们找到控制gpio5_1引脚的寄存器(手册Chapter 32​: IOMUX Controller (IOMUXC) --> IOMUXC SNVS Memory Map/Register Definition --> IOMUXC_SNVS下):

所以我们需要初始化寄存器IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1的低四位MUX_MODE为0101,初始化代码如下:

/* 声明指针 */
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1;

/* 让指针指向寄存器 */
IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1 = ioremap(0x229000C, 4);

/* 初始化寄存器低四位为0101*/
*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1 &= ~(0xf); //低四位清0
*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1 |= 0x5; //低四位MUX_MODE置0101

最后就是gpio5对应的寄存器了,下面是gpio模块内部框图:

 我们暂时只需要用到三个寄存器,GDIR(用于设置方向)、DR(设置输出电平)、PSR(读取输入电平),由于每个模块的寄存器地址都是连续的,并且每个相隔4字节,所以我们可以用一个结构体来存放所有寄存器地址:

 初始化代码如下:

/* gpio寄存器,因为每个gpio模块的寄存器地址连续,且各个寄存器之间相隔4B */
struct imx6ull_gpio_registers {
    volatile unsigned int DR; //gpio data register
    volatile unsigned int GDIR; //gpio direction register
    volatile unsigned int PSR; //gpio pad status register
    volatile unsigned int ICR1; //gpio interrupt configuration register1
    volatile unsigned int ICR2; //gpio interrupt configuration register2
    volatile unsigned int IMR; //gpio interrupt mask register
    volatile unsigned int ISR; //gpio interrupt status register
    volatile unsigned int EDGE_SEL; //gpio edge select register
};


/* 指针指向第一个寄存器的地址 */
gpio5_registers = ioremap(0x20AC000, sizeof(struct imx6ull_gpio_registers));

/* 3. 设置gpio5_1引脚为input,因为我们要读取引脚数据*/
gpio5_registers->GDIR &= ~(1<<1); //将GDIR寄存器的第一位置0

在知道怎么初始化寄存器后,我们就可以开始编写硬件层的代码了,硬件层代码如下:

chip_button_gpio.h

#ifndef _CHIP_BUTTON_GPIO_H
#define _CHIP_BUTTON_GPIO_H

/*gpio寄存器,因为每个gpio模块的寄存器地址连续,且各个寄存器之间相隔4B*/
struct imx6ull_gpio_registers {
    volatile unsigned int DR; //gpio data register
    volatile unsigned int GDIR; //gpio direction register
    volatile unsigned int PSR; //gpio pad status register
    volatile unsigned int ICR1; //gpio interrupt configuration register1
    volatile unsigned int ICR2; //gpio interrupt configuration register2
    volatile unsigned int IMR; //gpio interrupt mask register
    volatile unsigned int ISR; //gpio interrupt status register
    volatile unsigned int EDGE_SEL; //gpio edge select register
};

//对button的操作
struct button_operations {
        int (*init)  (int which);
        int (*read) (int which);
};


static int button_gpio_init(int which);
static int button_gpio_read(int which);

#endif

 chip_button_gpio.c 

#include <linux/platform_device.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 <asm/io.h>
#include "chip_button_gpio.h"
#include "button_drv.h"


/* enable GPIO5 clock*/
static volatile unsigned int *CCM_CCGR1; 

/* set GPIO5_IO01 as GPIO */
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1;

/* GPIO registers */
static struct imx6ull_gpio_registers *gpio5_registers;


/*提供给上一层的接口*/
static struct button_operations button_opr = {
	.init = button_gpio_init,
	.read = button_gpio_read,
};

/*初始化button,which指定哪一个button*/
static int button_gpio_init(int which){
	/* 将物理地址映射到虚拟地址上 */
	CCM_CCGR1 = ioremap(0x20C406C, 4);
    IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1 = ioremap(0x229000C, 4);
    gpio5_registers = ioremap(0x20AC000, sizeof(struct imx6ull_gpio_registers));

	/* 默认就是第0个按键 */
	if (which == 0){
        	/* 1. enable GPIO5 clock*/
	        *CCM_CCGR1 |= (3<<30); //30-31位置1

	        /* 2. set GPIO5_IO01 as GPIO*/
		    *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1 &= ~(0xf); //低四位清0
            *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1 |= 0x5; //低四位MUX_MODE置0101

            /* 3. set GPIO5_IO01 as input*/
            gpio5_registers->GDIR &= ~(1<<1); //将GDIR寄存器的第一位置0
    }
	/* 后面如果想使用多个按键的话可以用if else判断 */	
	return 0;
}

/*读取引脚电平,查看按键是否按下,0表示未按下,1表示按下,-1表示出错*/
static int button_gpio_read(int which){
	//将psr寄存器的第一位状态返回,该位即为引脚状态。按下按钮时为0,未按下时为1
	return (gpio5_registers->PSR & (1<<1)) ? 1 : 0;
}


/*入口函数,模块一被装载就执行*/
static int chip_button_gpio_drv_init(void){
	//将button_opr传给上一层
    register_button_operations(&button_opr);
	//创建从设备
	button_device_create(0);
	return 0;
}

/*入口函数,模块一被装载就执行*/
static void chip_button_gpio_drv_exit(void){
    //销毁从设备
	button_device_destroy(0);
}

module_init(chip_button_gpio_drv_init);
module_exit(chip_button_gpio_drv_exit);
MODULE_LICENSE("GPL");

其中:

button_operations是提供给上一层(即中间层)的接口,它包含了两个函数,button_gpio_init和button_gpio_read。button_gpio_init函数用于初始化各个寄存器,而button_gpio_read则读取引脚电平,并且前面我们知道按键未按下时引脚为高电平,按下时引脚为低电平。

chip_button_gpio_drv_init和chip_button_gpio_drv_exit两个函数是模块的入口和出口函数,模块被装载(insmod)则执行chip_button_gpio_drv_init,模块被卸载(rmmod)则执行chip_button_gpio_drv_exit。入口函数主要把button_operations传给上一层并且创建子设备,出口函数则销毁子设备。硬件层代码如下:

2.1.2 中间层(xxx_drv.c)

       在linux中,一切皆文件,设备也是作为文件存在于linux里面的,我们在应用层对设备进行各种操作就相当于对文件进行读写操作,所以为了让应用层能够通过对文件的操作来控制设备,我们要提供接口给应用层(如open,read,write等等),比如通过read读取引脚电平(按键是否按下),通过write来让引脚为高电平或者低电平(控制led是否打开)。这些接口通过file_operations结构体来实现,中间层代码如下:

 button_drv.h

#ifndef _BUTTON_DRV_H
#define _BUTTON_DRV_H
	
#include "chip_button_gpio.h"

void button_device_create(int minor);
void button_device_destroy(int minor);
void register_button_operations(struct button_operations *opr);

#endif

 button_drv.c

#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 "button_drv.h"

/*确定主设备号,可以自己指定,也可以设置为0让系统自动分配*/
static int major = 0;
static struct class *button_class;
/*指向button_operations的指针*/
struct button_operations *p_button_opr;

/*创建button设备,minor是从设备号*/
void button_device_create(int minor){
	device_create(button_class, NULL, MKDEV(major, minor), NULL, "lzp_button%d", minor);
}

/*销毁对应的button设备*/
void button_device_destroy(int minor){
	device_destroy(button_class, MKDEV(major, minor));
}

/*获取chip_button_gpio中的button_operations*/
void register_button_operations(struct button_operations *opr){
	p_button_opr = opr;
}

/*使用EXPORT_SYMBOL可以将一个函数以符号的方式导出给其他模块使用*/
EXPORT_SYMBOL(button_device_create);
EXPORT_SYMBOL(button_device_destroy);
EXPORT_SYMBOL(register_button_operations);


/*实现对应的open/read/write等函数,填入file_operations结构体*/
static ssize_t button_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset){
	/*获取次设备号,即read函数想要操作的设备*/
    unsigned int minor = iminor(file_inode(file));
	/*读取按键电平*/
	char level = p_button_opr->read(minor);
	/*将传回给用户空间,不能直接访问buf,需要用到copy_to_user函数*/
	copy_to_user(buf, &level, 1);
	return 0;
}

static int button_drv_open (struct inode *node, struct file *file){
	/*获取次设备号*/
	int minor = iminor(node);
	/*根据次设备号初始化button*/
	p_button_opr->init(minor);

	return 0;
}

/*定义自己button的file_operations结构体*/
static struct file_operations button_drv = {
	.open    = button_drv_open,
	.read    = button_drv_read,
};

/*把file_operations结构体告诉内核:注册驱动程序*/
/*入口函数:安装驱动程序时,就会去调用这个入口函数*/
static int button_init(void){
	int err;
	/*注册字符设备,把file_operations结构体告诉内核,获取主设备号*/
	major = register_chrdev(0, "lzp_button", &button_drv);
	button_class = class_create(THIS_MODULE, "lzp_button_class");
	err = PTR_ERR(button_class);
	if (IS_ERR(button_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "lzp_button");
		return -1;
	}
	return 0;
}


/*出口函数:卸载驱动程序时,就会去调用这个出口函数*/
static void button_exit(void)
{
	class_destroy(button_class);
	unregister_chrdev(major, "lzp_button");
}


module_init(button_init);
module_exit(button_exit);

MODULE_LICENSE("GPL");

2.2 led驱动部分(led_drv_source)

由于led驱动源码整体架构和button几乎一模一样,只是一些引脚和寄存器的操作不同,比如在led硬件层中我们要写DR寄存器来操作led的亮灭,写入低电平则led亮起,写入高电平则led熄灭,对应的gpio引脚也不一样。

这里我就不进一步细讲,直接贴代码了,如果有不懂的地方可以评论区交流。

2.2.1 硬件层(chip_xxx_gpio.c)

chip_led_gpio.h

#ifndef _CHIP_LED_GPIO_H
#define _CHIP_LED_GPIO_H

/*gpio寄存器,因为每个gpio模块的寄存器地址连续,且各个寄存器之间相隔4B*/
struct imx6ull_gpio_registers {
    volatile unsigned int DR; //gpio data register
    volatile unsigned int GDIR; //gpio direction register
    volatile unsigned int PSR; //gpio pad status register
    volatile unsigned int ICR1; //gpio interrupt configuration register1
    volatile unsigned int ICR2; //gpio interrupt configuration register2
    volatile unsigned int IMR; //gpio interrupt mask register
    volatile unsigned int ISR; //gpio interrupt status register
    volatile unsigned int EDGE_SEL; //gpio edge select register
};

//对led的操作
struct led_operations {
        int (*init)  (int which);
        int (*ctl) (int which, int status);
};


static int led_gpio_init(int which);
static int led_gpio_write(int which, int status);

#endif

chip_led_gpio.c

#include <linux/platform_device.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 <asm/io.h>
#include "chip_led_gpio.h"
#include "led_drv.h"


/* enable GPIO5 clock*/
static volatile unsigned int *CCM_CCGR1; 

/* set GPIO5_IO03 as GPIO */
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;

/* GPIO5 registers */
static struct imx6ull_gpio_registers *gpio5_registers;


/*提供给上一层的接口*/
static struct led_operations led_opr = {
	.init = led_gpio_init,
	.ctl = led_gpio_write,
};

/*初始化led,which指定哪一个led*/
static int led_gpio_init(int which){
	/* 将物理地址映射到虚拟地址上 */
	CCM_CCGR1 = ioremap(0x20C406C, 4);
    IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x2290014, 4);
    gpio5_registers = ioremap(0x20AC000, sizeof(struct imx6ull_gpio_registers));

	/* 默认就是第0个按键 */
	if (which == 0){
        	/* 1. enable GPIO5 clock*/
	        *CCM_CCGR1 |= 0x11; //30-31位置11

	        /* 2. set GPIO5_IO01 as GPIO*/
		    *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~(0xf); //低四位清0
            *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= 0x5; //低四位MUX_MODE置0101

            /* 3. set GPIO5_IO01 as input*/
            gpio5_registers->GDIR |= (1<<3); //将GDIR寄存器的第三位置1,表示输入
    	}
	/* 后面如果想使用多个按键的话可以用if else判断 */	
	return 0;
}

/*写DR寄存器,写0灯亮,写1灯灭*/
static int led_gpio_write(int which, int status){
	if(status == 1){
		//status为1则让灯亮,DR第三位置0
		gpio5_registers->DR &= ~(1<<3); 
	}else{
		//status为0则让灯灭,DR第三位置1
		gpio5_registers->DR |= (1<<3);
	}
	return 0;
}

/*入口函数,模块一被装载就执行*/
static int chip_led_gpio_drv_init(void){
	//将led_opr传给上一层
    register_led_operations(&led_opr);
	//创建从设备
	led_device_create(0);
	return 0;
}

/*入口函数,模块一被装载就执行*/
static void chip_led_gpio_drv_exit(void){
    //销毁从设备
	led_device_destroy(0);
}

module_init(chip_led_gpio_drv_init);
module_exit(chip_led_gpio_drv_exit);
MODULE_LICENSE("GPL");

2.2.2 中间层(led_drv.c)

led_drv.h

#ifndef _LED_DRV_H
#define _LED_DRV_H
	
#include "chip_led_gpio.h"

void led_device_create(int minor);
void led_device_destroy(int minor);
void register_led_operations(struct led_operations *opr);

#endif

led_drv.c 

#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 "led_drv.h"

/*确定主设备号,可以自己指定,也可以设置为0让系统自动分配*/
static int major = 0;
static struct class *led_class;
/*指向led_operations的指针*/
struct led_operations *p_led_opr;

/*创建led设备,minor是从设备号*/
void led_device_create(int minor){
	device_create(led_class, NULL, MKDEV(major, minor), NULL, "lzp_led%d", minor);
}

/*销毁对应的led设备*/
void led_device_destroy(int minor){
	device_destroy(led_class, MKDEV(major, minor));
}

/*获取chip_led_gpio中的led_operations*/
void register_led_operations(struct led_operations *opr){
	p_led_opr = opr;
}

/*使用EXPORT_SYMBOL可以将一个函数以符号的方式导出给其他模块使用*/
EXPORT_SYMBOL(led_device_create);
EXPORT_SYMBOL(led_device_destroy);
EXPORT_SYMBOL(register_led_operations);


/*实现对应的open/read/write等函数,填入file_operations结构体*/
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset){
	char status;
	/*获取次设备号,即read函数想要操作的设备*/
    unsigned int minor = iminor(file_inode(file));
	/*读取用户传来的status*/
	copy_from_user(&status, buf, 1);
	/*操作led*/
	p_led_opr->ctl(minor, status);
	return 0;
}

static int led_drv_open (struct inode *node, struct file *file){
	/*获取次设备号*/
	int minor = iminor(node);
	/*根据次设备号初始化button*/
	p_led_opr->init(minor);

	return 0;
}

/*定义自己led的file_operations结构体*/
static struct file_operations led_drv = {
	.open    = led_drv_open,
	.write    = led_drv_write,
};

/*把file_operations结构体告诉内核:注册驱动程序*/
/*入口函数:安装驱动程序时,就会去调用这个入口函数*/
static int led_init(void){
	int err;
	/*注册字符设备,把file_operations结构体告诉内核,获取主设备号*/
	major = register_chrdev(0, "lzp_led", &led_drv);
	led_class = class_create(THIS_MODULE, "lzp_led_class");
	err = PTR_ERR(led_class);
	if (IS_ERR(led_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "lzp_led");
		return -1;
	}
	return 0;
}


/*出口函数:卸载驱动程序时,就会去调用这个出口函数*/
static void led_exit(void)
{
	class_destroy(led_class);
	unregister_chrdev(major, "lzp_led");
}


module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");

2.3 编写应用层程序实现按键操作led亮灭

       编写好驱动程序后我们就要开始使用驱动程序提供的接口了,可以看到对设备的操作和对文件的操作基本一样(open、read、write),这里我只使用的简单的查询模式来实现按键控制led亮灭(用一个while循环一直读取按键的电平,如果检测到按键按下则使led亮起,如果检测到按键松开则熄灭led),应用层代码如下:

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

/*
 * ./button_led_test /dev/lzp_button0 /dev/lzp_led0
 */
int main(int argc, char **argv)
{
	int fd_button;
	int fd_led;
	char level;
	int status;
	
	/* 1. 判断参数 */
	if (argc != 3) {
		return -1;
	}

	/* 2. 打开文件 */
	if ((fd_button = open(argv[1], O_RDWR)) == -1){//打开button
		printf("can not open file %s\n", argv[1]);
		return -1;
	}
	if ((fd_led = open(argv[2], O_RDWR)) == -1){//打开led
        printf("can not open file %s\n", argv[2]);
        return -1;
    }

	/* 3. 读文件 */
	while(1){
		read(fd_button, &level, 1);
		if(level == 0){
			//如果按键按下
			status = 1;
			write(fd_led, &status, 1);
		}else{
			//如果按键松开
            status = 0;
            write(fd_led, &status, 1);
		}
	}
	
	close(fd_led);
	close(fd_button);
	
	return 0;
}

三、编译源码,测试

编译之前记得把makefile里面的KERN_DIR换成你自己的内核目录路径: 

在目录下执行make指令,执行完后会把按键和led的ko文件都复制到当前目录,需要用到的几个文件就是下图红线框出来的几个(button_drv.ko,chip_button_gpio.ko,led_drv.ko,chip_led_gpio.ko,button_led_test):

 连上板子,挂载nfs,装载模块,这里要注意装载模块顺序,先装载中间层(xxx_drv.ko),再装载硬件层(chip_xxx_gpio.ko):

查看button驱动和led设备文件是否存在:

 最后测试使用button_led_test可执行文件测试:

 可以看到程序卡住了,因为我们写了一个while循环,这个时候我们可以去按下按键(板子右上角的按键):

按下(绿色led亮起):

 松开(led熄灭):

至此,imux6ull按键控制led亮灭的实现结束,代码写的有点糙,希望大佬轻喷,如果有需要的话我后面还可以把设备树也加到里面去(大家也可以基于我这个结构去扩展)。

最后,附上代码链接:gitee地址

  • 5
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值