在前面章节我们通过点亮LED完成了最基础的IO驱动,但是有两个问题:
下面我们就要针对这两点内容修改程序
驱动程序修改
驱动程序主要是修改write函数,思路就是先编写一个函数,用来实现LED的状态切换
#define LEDOFF 0 #define LEDON 1 static void led_switch(u8 sta) { u32 val = 0; if(sta == LEDON){ val = readl(IMX6UL_GPIO1_DR); val &= ~(1<<3); writel(val,IMX6UL_GPIO1_DR); } else if(sta == LEDOFF){ val = readl(IMX6UL_GPIO1_DR); val |= (1<<3); writel(val,IMX6UL_GPIO1_DR); } }
函数的逻辑很简单没有什么可讲的,就是根据不动的参数(sta)来讲GPIO对应的为置零或置1。所以就不再写注释了。然后在write的时候调用这个函数就行了
/** * @brief 改变LED状态 * * @param file * @param buf * @param count * @param ppos * @return ssize_t */ static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { int ret = 0; unsigned char databuf[1]; //待写入的参数 ret = copy_from_user(databuf,buf,count); //获取从用户空间传递来的参数 if (ret == 0){ led_switch(databuf[0]); //根据参数改变LED状态 } else{ printk("kernelwrite err!\r\n"); return -EFAULT; } }
整个函数也没什么可说的,直接调用函数就行。
应用程序构成
应用程序这块也没什么好说的
/** * @file app.c * @author your name (you@domain.com) * @brief 驱动应用程序 * @version 0.1 * @date 2022-03-27 * * @copyright Copyright (c) 2022 * */ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #define LEDOFF 0 #define LEDON 1 /** * @brief * * @param argc //参数个数 * @param argv //参数 * @return int */ int main(int argc,char *argv[]) { char *filename; //文件名 filename = argv[1]; //文件名为命令行后第二个参数(索引值为1) int ret = 0; //初始化操作返回值 int f = 0; //初始化文件句柄 int action = atoi(argv[2]); //读写标志:0为关灯,1为开灯 unsigned char databuf[1]; databuf[0]= atoi(argv[2]); if(argc != 3){ //输入参数个数不为3,提示输入格式错误 printf("input format error!\r\n"); } f = open(filename, O_RDWR); //打开文件 if(f < 0){ printf("file open error\r\n"); return -1; } ret = write(f,databuf,1); //文件写入数据 if(ret<0){ //写入错误,输出信息并关闭文件 printf("LED control failed\r\n"); close(f); return -1; } close(f); //关闭文件 return 0; }
这里主要就是有个点要注意一下:在程序最后调用了close函数,对应的是驱动里的release函数,在上一章写驱动的时候灯的熄灭我们把代码放在了模块卸载的时候,还有一种方法是放在文件close的过程里。但是在配合应用程序使用的时候,由于我们在应用程序最后都使用了close命令,每次点亮LED闪一下就被close里的代码灭了。这一点一定要注意。
这个应用程序和前面讲框架时候的应用程序有点区别:前面那个我们用第3个参数0或1来分别表示读和写;而在这个应用程序中0和1都是写,只不过代表要写入不同的值。
最后把完整的驱动代码放在下面
/** * @file led.c * @author your name (you@domain.com) * @brief led点亮程序模块测试 * @version 0.1 * @date 2022-04-04 * * @copyright Copyright (c) 2022 * */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/io.h> #define DEV_MAJOR 200 //设备号 #define DEV_NAME "LED" //设备名称 // static char writebuf[100]; // static char readbuf[100]; /** * @brief 寄存器物理地址 * */ #define CCM_CCGR1_BASE (0X020C406C) #define SW_MUX_GPIO1_IO03_BASE (0X020E0068) #define SW_PAD_GPIO1_IO03_BASE (0X020E02F4) #define GPIO1_GDIR_BASE (0X0209C004) #define GPIO1_DR_BASE (0X0209C000) /** * @brief 内存映射后虚拟内存地址 * */ static void __iomem *IMX6UL_CCM_CCGR1; static void __iomem *IMX6UL_SW_MUX_GPIO1_IO03; static void __iomem *IMX6UL_SW_PAD_GPIO1_IO03; static void __iomem *IMX6UL_GPIO1_DR; static void __iomem *IMX6UL_GPIO1_GDIR; #define LEDOFF 0 #define LEDON 1 // /** // * @brief 打开设备文件 // * // * @return int // */ static int led_open(struct inode *inode, struct file *filp) { printk("dev open!\r\n"); return 0; } /** * @brief 关闭设备文件 * * @return int */ static int led_release(struct inode *inode, struct file *filp) { printk("dev release!\r\n"); return 0; } /** * @brief 读设备文件数据 * * @param filp * @param buf * @param count * @param ppos * @return ssize_t */ static ssize_t led_read(struct file *filp, __user char *buf, size_t count, loff_t *ppos) { int ret = 0; printk("dev read data!\r\n"); if (ret == 0){ return 0; } else{ printk("kernel read data error!"); return -1; } } static void led_switch(u8 sta) { u32 val = 0; if(sta == LEDON){ val = readl(IMX6UL_GPIO1_DR); val &= ~(1<<3); writel(val,IMX6UL_GPIO1_DR); } else if(sta == LEDOFF){ val = readl(IMX6UL_GPIO1_DR); val |= (1<<3); writel(val,IMX6UL_GPIO1_DR); } } /** * @brief 改变LED状态 * * @param file * @param buf * @param count * @param ppos * @return ssize_t */ static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { int ret = 0; unsigned char databuf[1]; //待写入的参数 ret = copy_from_user(databuf,buf,count); //获取从用户空间传递来的参数 if (ret == 0){ led_switch(databuf[0]); //根据参数改变LED状态 } else{ printk("kernelwrite err!\r\n"); return -EFAULT; } } /** * @brief 字符设备操作操作集 * */ static struct file_operations led_fops= { .owner = THIS_MODULE, .open = led_open, .release = led_release, .read = led_read, .write = led_write, }; /** * @brief 初始化 * * @return int */ static int __init led_init(void) { int ret = 0; unsigned int val = 0; /*led初始化*/ //获取地址映射 IMX6UL_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE,4); IMX6UL_SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4); IMX6UL_SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4); IMX6UL_GPIO1_DR = ioremap(GPIO1_DR_BASE, 4); IMX6UL_GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4); printk("ioremap finished!\r\n"); //时钟初始化 val = readl(IMX6UL_CCM_CCGR1); //读取CCM_CCGR1的值 val &= ~(3<<26); //清除bit26、27 val |= (3<<26); //bit26、27置1 writel(val, IMX6UL_CCM_CCGR1); printk("CCM init finished!\r\n"); /*GPIO初始化*/ writel(0x5, IMX6UL_SW_MUX_GPIO1_IO03); writel(0x10B0, IMX6UL_SW_PAD_GPIO1_IO03); printk("GPIO SW init finished!\r\n"); val = readl(IMX6UL_GPIO1_GDIR); val |= 1<<3; //bit3=1,设置为输出 writel(val, IMX6UL_GPIO1_GDIR); printk("GPIO GDIR init finished!\r\n"); val = readl(IMX6UL_GPIO1_DR); val &= ~(1<<3); writel(val,IMX6UL_GPIO1_DR); printk("device init!\r\n"); //字符设备注册 ret = register_chrdev(DEV_MAJOR, DEV_NAME, &led_fops); if(ret < 0 ){ printk("device init failed\r\n"); return -EIO; } return 0; } /** * @brief 卸载 * */ static void __exit led_exit(void) { unsigned int val = 0; //关闭led val = readl(IMX6UL_GPIO1_DR); val |= (1<<3); writel(val ,IMX6UL_GPIO1_DR); //取消地址映射 iounmap(IMX6UL_CCM_CCGR1); iounmap(IMX6UL_SW_MUX_GPIO1_IO03); iounmap(IMX6UL_SW_PAD_GPIO1_IO03); iounmap(IMX6UL_GPIO1_DR); iounmap(IMX6UL_GPIO1_GDIR); //字符设备注销 unregister_chrdev(DEV_MAJOR,DEV_NAME); printk("device exit\r\n"); } module_init(led_init); //模块加载 module_exit(led_exit); //模块卸载 MODULE_LICENSE("GPL"); MODULE_AUTHOR("ZeqiZ");led.c
写在后面
通过这个点亮LED的过程我们主要了解一下数据的写入、文件的操作、寄存器地址映射等方法,主要是要我们有个大概的思路,后面的操作中我们应该不太常用这种方式来写驱动。但是整个流程一定要清楚!