驱动程序开发:新字符设备驱动之LED点灯

news/2024/12/21 22:57:07/

驱动程序开发:新字符设备驱动之LED点灯

newchrled.c

/**	根据linux内核的程序查找所使用函数的对应头文件。*/
#include <linux/module.h>	// MODULE_LICENSE,MODULE_AUTHOR
#include <linux/init.h>		// module_init,module_exit
#include <linux/kernel.h>	// printk
#include <linux/fs.h>		// struct file_operations
#include <linux/uaccess.h>	// copy_to_user,copy_from_user
#include <linux/io.h>		//ioremap,iounmap
#include <linux/cdev.h>		//struct cdev,cdev_init,cdev_add,cdev_del
#include <linux/device.h>	//class/* 定义设备名字 */
#define NEWCHRLED_NAME 	"newchrled"
/* 定义设备的个数 */
#define NEWCHRLED_COUNT		1/********************************* 1.1、寄存器物理地址定义 ********************************/
#define CCM_CCGR1_BASE				(0x020C406C)	//对应IO的时钟寄存器地址
#define SW_MUX_GPIO1_IO03_BASE		(0x020E0068)	//对应IO的复用寄存器地址
#define SW_PAD_GPIO1_IO03_BASE		(0x020E02F4)	//对应IO的电气属性寄存器地址
#define GPIO1_GDIR_BASE				(0x0209C004)	//对应IO的输出方向寄存器地址
#define GPIO1_DR_BASE				(0x0209C000)	//对应IO的输出电平寄存器地址
/***************************************************************************************/
/****** 物理地址映射后定义虚拟地址的指针,其类型是根据ioremap函数返回值类型定义的 ******/
static void __iomem *IMX6U_CCM_CCGR1; 	//对应IO的时钟寄存器的映射虚拟地址
static void __iomem *SW_MUX_GPIO1_IO03; //对应IO的复用寄存器的映射虚拟地址
static void __iomem *SW_PAD_GPIO1_IO03; //对应IO的电气属性寄存器的映射虚拟地址
static void __iomem *GPIO1_GDIR;		//对应IO的输出方向寄存器的映射虚拟地址
static void __iomem *GPIO1_DR;			//对应IO的输出电平寄存器的映射虚拟地址
/*********************************************************************************************/#define LEDOFF	0	//关灯
#define LEDON	1	//开灯/* LED设备结构体 */
struct newchrled_dev {struct cdev cdev; 		/* 注册设备结构体 */dev_t devid;	 		/* 设备号 */int major;		 		/* 主设备号 */int minor;		 		/* 次设备号 */struct class *class;	/* 类 */struct device *device;	/* 设备 */
};/* 定义一个LED设备结构体变量 */
struct newchrled_dev newchrled;/************** 6.1 开/关灯的函数 **************/
void led_switch(u8 state)
{u32 val;					//操作的是32位的寄存器if(state == LEDON) {/* 开灯 */val = readl(GPIO1_DR);	//读取寄存器val &= ~(1 << 3);		//清零writel(val, GPIO1_DR);	//写入寄存器} else if(state == LEDOFF) {/* 关灯 */val = readl(GPIO1_DR);	//读取寄存器val |= 1 << 3;		writel(val, GPIO1_DR);	//写入寄存器		}
}
/*********************************************//******************************* 4.1 打开设备文件 ********************************/
static int newchrled_open(struct inode *inde, struct file *filp)
{/**	在 open 函数里面设置好私有数据以后,在 write、 read、 close 等函数中直接读取 private_data即可得到设备结构体。*/filp->private_data = &newchrled;	//将定义打驱动设备结构体变为私有类return 0;
}
/******************************* 4.2 关闭设备文件 *******************************/
static int newchrled_release(struct inode *inode, struct file *file)
{/* 定义下面直接引用设备结构体变量的指针,相当于提取私有类的属性一样 */struct newchrled_dev *dev = (struct newchrled_dev *)file->private_data;return 0;
}
/****************************** 4.3 向设备文件写数据 ******************************/
static ssize_t newchrled_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos)
{/*********** 6.0 应用程序写入参数控制LED ***********/int ret;		//保存调用函数返回值u8 databuf[1];	//保存应用程序写入打数据ret = copy_from_user(databuf, buf, count);	//将应用程序传入过来打buf数据写入驱动程序databuf里if(ret < 0) {printk("write kernel failed!\r\n");return -EFAULT;}/* 判断开关灯 */led_switch(databuf[0]);/*************************************************/return 0;
}
/******************** 4.0 设备操作集合 ********************/
static const struct file_operations newchrled_fops = {.owner   = THIS_MODULE,.write 	 = newchrled_write,.open 	 = newchrled_open,.release = newchrled_release,
};
/********************************************************//******************************* 1.1 驱动模块入口函数 ************************************/
static int __init newchrled_init(void)
{int ret = 0;	//保存调用函数打返回值打临时变量	unsigned int val = 0;		//操作寄存器是保存的临时变量printk("newchrled_init\r\n");/***************** 5.0 初始化led,先地址映射 *****************/IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);/***********************************************************//****** 5.2 始化时钟,一般操作寄存器使用读改写操作步骤 ******/val = readl(IMX6U_CCM_CCGR1);	//读取寄存器val &= ~(3 << 26);	//bit26:27清零val |= 3 << 26;		//bit26:27置一writel(val, IMX6U_CCM_CCGR1);	//写入寄存器,时钟writel(0x5,SW_MUX_GPIO1_IO03);		//复用writel(0x10B0,SW_PAD_GPIO1_IO03);	//电气属性val = readl(GPIO1_GDIR);	//读取寄存器val |= 1 << 3;		writel(val, GPIO1_GDIR);	//写入寄存器,输出方向/* 开灯 */val = readl(GPIO1_DR);	//读取寄存器val &= ~(1 << 3);		//清零writel(val, GPIO1_DR);	//写入寄存器/***********************************************************//**************************** 2.0 分配字符设备号 *****************************/newchrled.major = 0;	//手动清零,表示由系统分配设备号if(newchrled.major) {/* 将主设备号和次设备号整合成设备号 */newchrled.devid = MKDEV(newchrled.major,0);/* 使用给定的设备号进行注册设备 */ret = register_chrdev_region(newchrled.devid, NEWCHRLED_COUNT, NEWCHRLED_NAME);} else {/* 没有给定设备号,系统自动进行注册设备号 */alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_COUNT, NEWCHRLED_NAME);newchrled.major = MAJOR(newchrled.devid);newchrled.minor = MINOR(newchrled.devid);}if(ret < 0) {goto failed_devid;}//打印出设备号的主次设备号printk("newchrled major:%d , minor:%d\r\n",newchrled.major,newchrled.minor);/**************************************************************************//***************************** 3.0 注册字符设备 *****************************///newchrled.cdev.owner = THIS_MODULE;		//cdev结构体内部会自动实现,无需手动实现cdev_init(&newchrled.cdev, &newchrled_fops);	//初始化cdev结构体变量,也就是将newchrled_fops设备操作集合传入cdev结构体内部的操作集合中ret = cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_COUNT);	//向Linux系统添加字符设备(cdev结构体变量)if(ret < 0) {goto failed_cdev;}/**************************************************************************/
/******************************************* 7-8步骤: 自动创建设备节点 *******************************************//*********** 7.0 创建一个类,该类是创建节点的必要参数之一 ***********/newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);	//创建类if (IS_ERR(newchrled.class)) {ret = PTR_ERR(newchrled.class);goto failed_class;}/*************************************************************//******************************* 8.0 创建设备,即可实现自动创建节点 *******************************/newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);if (IS_ERR(newchrled.device)) {ret = PTR_ERR(newchrled.device);goto failed_device;}/*********************************************************************************************/
/***************************************************************************************************************/return 0;/* 创建设备失败,需要将前面注册的设备号、注册的字符设备、创建打类给释放掉 */
failed_device:class_destroy(newchrled.class);
/* 创建类失败,需要将前面注册的设备号、注册的字符设备给释放掉 */
failed_class:cdev_del(&newchrled.cdev);
/* 注册字符设备失败,说吗分配设备号成功,因此需要先释放设备号 */
failed_cdev:unregister_chrdev_region(newchrled.devid,NEWCHRLED_COUNT);
/* 分配设备号失败 */
failed_devid:printk("newchrled chrdev_region err!\r\n");return ret;}
/**************************************************************************************/
/* 1.2 驱动模块出口函数 */
static void __exit newchrdev_exit(void)
{u32 val = 0;printk("newchrdev_exit\r\n");/************************* 关灯 ************************/val = readl(GPIO1_DR);	//读取寄存器val |= 1 << 3;		writel(val, GPIO1_DR);	//写入寄存器/********************************************************//******************** 5.1 注销地址映射 **********************/iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO03);iounmap(SW_PAD_GPIO1_IO03);iounmap(GPIO1_GDIR);iounmap(GPIO1_DR);/***********************************************************//******************* 3.1 注销字符设备 *******************/cdev_del(&newchrled.cdev);	//从Linux内核中删除相应的字符设备/*****************************************************//******************* 2.1 注销设备号 ********************/unregister_chrdev_region(newchrled.devid,NEWCHRLED_COUNT);/*****************************************************//* 删除创建的节点 */device_destroy(newchrled.class, newchrled.devid);/************************* 7.1 摧毁类 *************************/class_destroy(newchrled.class);		//摧毁类/*************************************************************/
}/*** 1.0 注册加载和卸载驱动模块 ***/
module_init(newchrled_init);
module_exit(newchrdev_exit);
/******************************/
/********* 1.3 驱动许可证 *********/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("djw");
/*********************************/

ledAPP.c

/**	头文件可以通过linux手册查找,man no <xx>*	1是普通的命令*	2是系统调用,如open,write之类的(通过这个,至少可以很方便的查到调用这个函数,需要加什么头文件)*	3是库函数,如printf,fread*	4是特殊文件,也就是/dev下的各种设备文件*	5是指文件的格式,比如passwd, 就会说明这个文件中各个字段的含义*	6是给游戏留的,由各个游戏自己定义*	7是附件还有一些变量,比如向environ这种全局变量在这里就有说明*	8是系统管理用的命令,这些命令只能由root使用,如ifconfig*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>/** argc:应用程序参数个数 * argv[]:具体打参数内容,字符串形式 * ./ledAPP <filename> <0:1> 0为关,1为开* ./ledAPP /dev/led 0   表示关灯* ./ledAPP /dev/led 1   表示开灯*/
int main(int argc, char *argv[])
{int fd, ret;		//fd保存调用文件操作函数的文件描述符的临时变量,ret保存调用函数返回值临时变量char *filename;		//保存文件名信息的指针变量unsigned char databuf[1];	//保存终端输入的开关灯变量/* 判断终端输入的参数个数是否为3个 */if(argc != 3) {printf("ERROR USAGE!\r\n");return -1;}/* 保存第二个输入的参数,文件名信息,此参数是保存对应创建文件节点的绝对路径的 */filename = argv[1];fd = open(filename,O_RDWR);		//使用读写方式打开文件if(fd < 0) {printf("file %s open failed!\r\n",filename);return -1;}/* 保存第三个输入参数,也就是led的状态参数 */databuf[0] = atoi(argv[2]);/* 将led的状态参数写入fd设备文件中,其会在驱动程序中以调用函数buf形参写入驱动程序的变量中 */ret = write(fd, databuf, sizeof(databuf));	if(ret < 0) {printf("LED control failed!\r\n");close(fd);		//如果写入操作失败了,那么就关闭设备文件return -1;}close(fd);			//程序运行完成,关闭设备文件return 0;
}

新字符设备驱动开发步骤:
第一步:1、先调用module_init注册加载与module_exit卸载驱动模块的函数。2、编写出驱动模块入口(加载)函数与驱动模块出口(卸载)函数。3、调用MODULE_LICENSE函数实现驱动许可证。
第二步:1、实现register_chrdev_region|alloc_chrdev_region注册设备号。2、实现unregister_chrdev_region注销设备号。
第三步:1、cdev_init&cdev_add注册字符设备。2、cdev_del注销字符设备。
第四步:1、实现file_operations设备操作集合及open、release、write、read等操作函数。
第五步:1、使用ioremap将物理地址映射为虚拟地址。2、使用iounmap将虚拟地址给卸载掉。3、配置寄存器的虚拟地址(LED初始化)。
第六步:1、在write函数编写控制寄存器(LED)程序。2、编写LED判断状态点灯函数。
第七步:1、使用class_create创建一个类。2、使用class_destroy摧毁一个类。
第八步:1、使用device_create创建设备。2、使用device_destroy摧毁设备(先摧毁设备后再去摧毁其内部定义的类参数)。

实验操作步骤:
1、将驱动程序进行make操作,编译生成xxx.ko文件
2、使用交叉编译器将应用程序编译成可执行文件,如:我这里使用 arm-linux-gnueabihf-gcc xxxAPP.c -o xxxAPP
3、将xxx.ko和xxxAPP两个文件拷贝到存放驱动模块的目录中。
4、打开开发板并使用Linux系统选择通过TFTP从网络启动和使用NFS挂载网络根文件系统。
5、在PC机的串口终端中先输入depmod,后输入modprobe xxx.ko加载驱动文件,最后输入lsmod查看当前系统中存在的模块。
6、在PC机的串口终端中输入cat /proc/devices查看当前系统的所有设备的对应的设备号及名称。
7、在PC机的串口终端中输入./xxxApp /dev/xxx 1或./xxxApp /dev/xxx 2指令来使用应用程序对驱动程序进行读写等操作。
8、在PC机的串口终端中输入rmmod xxx.ko卸载驱动模块。


http://www.ppmy.cn/news/364241.html

相关文章

Linux驱动开发 - LED驱动

驱动程序 #include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> …

Linux驱动_新LED驱动

一、新字符驱动原理 1、以前的字符驱动使用register_chedev注册字符设备&#xff0c;需要手动指定主设备号 2、新版字符型驱动有两种方式注册字符设备&#xff1a;alloc_chrdev_region(自动申请设备号&#xff09;和register_chrdev_region(手动申请主设备号)。两种申请方式的注…

一、LED驱动电路

该专栏笔记整理于哔哩哔哩up主 硬件工程师桥 &#xff0c;个人感觉up主视频质量还是很高的&#xff0c;对硬件感兴趣的小伙伴可以戳蓝字关注一波~ 文档中原理图部分&#xff08;不包含对原视频截图中涉及的原理图&#xff09;统一使用 立创EDA 进行绘制 同时&#xff0c;由于个…

linux设备驱动--LED驱动

linux设备驱动--LED驱动 最近正在学习设备驱动开发&#xff0c;因此打算写一个系列博客&#xff0c;即是对自己学习的一个总结&#xff0c;也是对自己的一个督促&#xff0c;有不对&#xff0c;不足&#xff0c;需要改正的地方还望大家指出&#xff0c;而且希望结识志同道合的朋…

【立创开源】RY3730-带充电的恒流驱动LED照明灯

如题所示&#xff0c;作者为了把手上的RY3730恒流驱动芯片和IP2312锂电池充电芯片用完&#xff0c;就做了这么一个板子&#xff0c;甚至顺便把之前打的亚克力板也用完了( 开源地址 RY3730-带充电的恒流驱动LED照明灯 - 嘉立创EDA开源硬件平台 嘉立创EDA开源硬件平台&#xff…

LED驱动程序--可拓展的LED驱动程序

LED驱动程序–可拓展的LED驱动程序 一.把驱动拆分成通用的框架&#xff08;leddrv.c&#xff09;&#xff0c;具体的硬件操作&#xff08;board_X.c&#xff09; 实现一个通用的框架leddrv.c和 具体的硬件操作。想要操作那个硬件就将相应的框架和硬件操作一起编译成对应的驱动…

i.MX 6ULL 驱动开发 五:LED 驱动

一、原理分析 i.MX6ULL裸机开发 一&#xff1a;LED_lqonlylove的博客-CSDN博客 二、pinctrl 子系统和gpio子系统基本概念 Linux 驱动开发 二十一&#xff1a;pinctrl子系统和gpio子系统基本概念_lqonlylove的博客-CSDN博客_设备树gpio 三、linux 源码对于 pinctrl 子系统的…

LED灯驱动介绍

一&#xff1a;简单字符设备驱动开发一般分为下述几个步骤&#xff1a; 第一步&#xff1a; 先调用module_init注册加载与module_exit卸载驱动模块的函数。 编写出驱动模块入口&#xff08;加载&#xff09;函数与驱动模块出口&#xff08;卸载&#xff09;函数。 调用MODULE_L…