简化的驱动框架
话说前面章节讲到了如何利用嵌入式驱动开发的方式进行驱动开发。由于其学习路线相比于裸机开发来说,上手难度稍微大一些,而且代码量也相对来说较多,所以对刚上手的人来说是颇有难度的。本章节,我们将以一个类似于Hello World点灯的例子,来讲解在linux下如何进行内核驱动的开发。
工欲善其事,必先利其器,开始之前,我们需要先将驱动开发用到的主体框架搭建一下,这里由于上节讲过,我这里直接贴上来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | #include <linux/init.h> #include <linux/module.h> #include <linux/device.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/gpio.h> #include <linux/string.h> #define LED_MAJOR 200 #define LED_NAME "LED" static int led_open( struct inode *inode, struct file *filep){ printk( "GPIO init \n" ); return 0; } static int led_write( struct file *filep, const char __user *buf, size_t count, loff_t *ppos){ return count; } static int led_release( struct inode *inode, struct file *filep){ printk( "Release !! \n" ); return 0; } static const struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .write = led_write, .release = led_release, }; static int __init led_init( void ){ printk( "led control device init success! \r\n" ); return 0; } static void __exit led_exit( void ){ printk( " led_exit \r\n" ); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE( "GPL v2" ); MODULE_AUTHOR( "CXSR" ); |
可以看到,整个驱动开发框架无非是如下几个东西,首先是module_init和module_exit函数,之后就是file_operations结构体,结构体中有对文件的读,写,打开,关闭等等,我们只需要把我们用到的操作去实现就行了。由于这部分整体比较固化,所以这里我不再赘述了。
由于在荔枝派中,我选择了A1口作为我的控制口,所以我先尝试利用GPIO手动控制了一下,整体控制流程如下:
1 2 3 4 | 1、 进入sys/ class /gpio目录下 2、 执行echo 1 > export命令,可以看出来创建了gpio1的文件夹 3、 进入gpio1文件夹,cat direction查看其值为in,通过vi direction,将其值改为out后, wq保存。 4、 运行 echo 1 > value 命令,则可以设置A1口高电平,设置echo 0 > value 命令,则可以设置A1口低电平。 |
通过如上步骤,我们发现,我们可以控制板子上的LED灯的亮灭了。之所以能这么控制,是因为我们编译生成的根文件中,包含了对GPIO的支持,所以使得我们很容易的进行控制。
LED驱动编码
接下来,我们就开始针对刚才的驱动框架模板,来慢慢的填写我们的内容吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | #include <linux/init.h> #include <linux/module.h> #include <linux/device.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/gpio.h> #include <linux/string.h> /* 主设备号 */ #define LED_MAJOR 200 /* 设备名称 */ #define LED_NAME "LED" /* 操作的引脚 */ #define LED_PIN 1 /* 内核态用户态交互缓冲 */ static char recv_msg[20]; /* 自动创建设备树:类 */ static struct class *led_control_class = NULL; /* 自动创建设备树:设备 */ static struct device *led_control_device = NULL; /* led初始化 */ static int led_open( struct inode *inode, struct file *filep){ printk( "GPIO init \n" ); /* 校验 */ if (!gpio_is_valid(LED_PIN)){ printk( "Error wrong gpio number !!\n" ); return ; } gpio_request(LED_PIN, "led_ctr" ); /* 设置direction为输出 */ gpio_direction_output(LED_PIN,1); /* 初始置为高电平 */ gpio_set_value(LED_PIN,1); return 0; } /* 引脚电平写入 */ static int led_write( struct file *filep, const char __user *buf, size_t count, loff_t *ppos){ int cnt = _copy_from_user(recv_msg, buf, count); if (0 == cnt){ /* 如果用户输入了on标记,代表打开 */ if (0 == memcmp (recv_msg, "on" , 2)){ printk( "LED on! \n" ); gpio_set_value(LED_PIN, 1); } /* 如果用户输入了其他标记,代表关闭 */ else { printk( "LED off! \n" ); gpio_set_value(LED_PIN, 0); } } else { printk( "ERROR occur when writing!!\n" ); return -EIO; } return count; } /* led注销 */ static int led_release( struct inode *inode, struct file *filep){ printk( "Release !! \n" ); gpio_free(LED_PIN); return 0; } /* 设备文件操作对象 */ static const struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .write = led_write, .release = led_release, }; /* 设备操作的初始化 */ static int __init led_init( void ){ int ret = 0; //1. 注册字符设备 ret = register_chrdev(LED_MAJOR, LED_NAME,&led_fops); if (ret <0){ printk( "register fail!\r\n" ); return -EIO; } printk( "register success, major number is %d \r\n" ,ret); /* 2. 自动注册设备:首先创建类 */ led_control_class = class_create(THIS_MODULE, LED_NAME); if (IS_ERR(led_control_class)){ unregister_chrdev(LED_MAJOR, LED_NAME); return -EIO; } /* 3. 自动注册设备:基于类创建设备 */ led_control_device = device_create(led_control_class, NULL, MKDEV(LED_MAJOR,0), NULL,LED_NAME); if (IS_ERR(led_control_device)){ class_destroy(led_control_class); unregister_chrdev(LED_MAJOR, LED_NAME); return -EIO; } printk( "led control device init success! \r\n" ); return 0; } /* 设备注销 */ static void __exit led_exit( void ){ printk( " led_exit \r\n" ); device_destroy(led_control_class, MKDEV(LED_MAJOR,0)); class_unregister(led_control_class); class_destroy(led_control_class); unregister_chrdev(LED_MAJOR,LED_NAME); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE( "GPL v2" ); MODULE_AUTHOR( "CXSR" ); |
由于代码部分,我做了详尽的注释,所以这里不再过多的解释了。这里唯一需要注意的几点就是:
1. 用户态和内核态的数据交换,是需要通过copy_from_user或者copy_to_user来进行。
2. 设备初始化,需要先利用register_chrdev来注册字符设备,之后才能进行设备的创建操作。
3. 自动注册设备树,需要先进行class_create,之后才能进行device_create.
4. insmod和rmmod命令的执行,分别对应led_init函数和led_exit函数。
Makefile制作
既然代码写好了,我们这里就需要来编译生成ko文件才行。我们来手写一个Makefile文件。
1 2 3 4 5 6 7 8 9 10 11 12 | KERNELDIR := /home/scy/linux-mi/linux-f1c100s-480272lcd-test/ CURRENT_PATH := $(shell pwd) obj-m := led1.o build: kernel_modules kernel_modules: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm clean: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean |
可以看到,整个文件内容比较少,其中KERNELDIR代表linux内核源码的目录,CURRENT_PATH则代表当前路径,kernel_modules则代表我们mak命令要执行的内容,需要注意的是,如果你的板子是ARM架构的,这里要加上ARCH=arm,如果你在编译内核的时候,有用到自己的交叉编译链,那么这里也需要指定一下CROSS_COMPILE,否则可能会因为内核编译方式不一样,导致生成的ko文件,不能被板子上的系统所加载。
执行make命令,可以看到如下熟悉的输出。
之后,我们就可以看到led1.ko文件被生成了。
开发板上跑起来
由于ko文件已经被生成,所以这里我们拿下tf卡,然后通过如下的命令,将我们的led1.ko,拷贝到media目录中去:
1 2 3 4 5 | sudo mkdir /mnt/sdb2 //创建一个临时目录 sudo mount /dev/sdb2 /mnt/sdb2 //将sdb2挂载到此临时目录 sudo cp led1.ko /mnt/sdb2/media //拷贝到sdb2/media目录下 sudo sync sudo umount /dev/sdb2 |
拷贝完毕后,利用sudo minicom启动串口监听,之后去media目录,安装我们的驱动看看:
可以看到,驱动被成功的安装了,可以用lsmod命令看一下:
可以看到驱动成功的被加载了。那么这时候,我们通过cat /proc/devices命令看看字符设备是否成功注册:
这里我们也可以清晰的看到模块被注册完毕了。
之后我们利用rmmod led1.ko命令卸载下看看:
可以看到模块被成功卸载,同时再利用cat /proc/devices命令执行,发现字符设备已被注销掉了。
我们这里重新将驱动加载上,然后执行如下命令:
1 2 3 4 | #关闭led echo off > /dev/LED #打开led echo on > /dev/LED |
通过交换执行命令,我们发现板子上的led不时的亮灭,整体驱动成功!
参考资料:
纯代码点灯方式 :https://www.cnblogs.com/y4247464/p/12379992.html
这篇参考文章也不错(此人写的一系列文章都可以的) :https://www.cnblogs.com/y4247464/p/12370190.html
vscode搭建内核开发环境 :https://blog.mxslly.com/archives/170.html
linux设备驱动程序--gpio控制 :https://www.cnblogs.com/downey-blog/p/10501709.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
2012-06-22 Winforms SkinFramework
2012-06-22 遇到一个好站点:PInvoke for DotNet
2012-06-22 在Win7下无法安装Silverlight的解决办法