原创首发于优快云,转载请注明出处,谢谢!https://blog.youkuaiyun.com/weixin_46959681/article/details/117962761
文章目录
为什么要学习驱动编程?
作为一个合格的开发人员,学习驱动编程有以下理由:
- 以51、STM32等单片机或者树莓派进简单应用的开发,如实现 LED点灯、超声波的测距、继电器的电路控制轻而易举,因为生产厂家都已经把所需要的库文件打包封装实现了,但在未来的工作中不一定有相应的库文件;
- 工作开发中的硬件平台只要能运行Linux系统,则一定有标准的C库文件存在;
- Linux内核的源码是通用的,具备芯片手册和电路图,就可在标准的C库文件基础进行开发;
- LInux内核是每一个程序员都应该追逐的圣杯 。
用户态上层的应用测试代码 pin4Test.c
上层测试代码: pin4Test.c
/* pin4Test.c */
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd;
fd = open("/dev/pin4",O_RDWR);
if(fd < 0){
perror("Open failed:");
}else{
printf("Open success.\n");
}
write(fd,'3',1);
return 0;
}
基于驱动框架编写引脚驱动文件 pin4Driver.c
底层驱动代码: pin4Driver.c
/* pin4Driver.c */
#include <linux/fs.h> //file_operations 声明
#include <linux/module.h> //module_init module_exit 声明
#include <linux/init.h> //__init __exit 宏定义声明
#include <linux/device.h> //class devise 声明
#include <linux/uaccess.h> //copy_from_user 的头文件
#include <linux/types.h> //设备号 dev_t 类型声明
#include <asm/io.h> //ioremap iounmap 的头文件
static struct class *pin4_class;
static struct device *pin4_class_dev;
static dev_t devno; //设备号
static int major =231; //主设备号
static int minor =0; //次设备号
static char *module_name="pin4"; //模块名
//函数pin4_open
static int pin4_open(struct inode *inode,struct file *file)
{
printk("pin4_open\n");
return 0;
}
//函数pin4_write
static ssize_t pin4_write(struct file *file1,const char __user *buf,size_t count, loff_t *ppos)
{
printk("pin4_write\n");
return 0;
}
static struct file_operations pin4_fops = {
.owner = THIS_MODULE,
.open = pin4_open,
.write = pin4_write,
};
int __init pin4_drv_init(void)
{
int ret;
//1.创建设备号(主设备、次设备号)。
devno = MKDEV(major,minor);
printk("drive init succeed\n");
//2.注册驱动:通知内核,将该驱动加入到驱动链表。
ret = register_chrdev(major,module_name,&pin4_fops);
//3.加载驱动代码文件时自动操作路径“/root/dev”下生成驱动设备。
pin4_class = class_create(THIS_MODULE,"myfirstdriver");
//4.创建设备文件
pin4_class_dev = device_create(pin4_class,NULL,devno,NULL,module_name);
return 0;
}
void __exit pin4_drv_exit(void)
{
//1.销毁设备
device_destroy(pin4_class,devno);
//2.销毁类文件。
class_destroy(pin4_class);
//3.卸载驱动。
unregister_chrdev(major, module_name);
}
//整体驱动框架文件的初始化入口。
module_init(pin4_drv_init);
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");
注意:
- 内核代码的编写与上层代码的编写有不同之处。内核里的文件代码是一个异常巨大的结构,若你希望在内核里的驱动链表里添加驱动文件,那就必须遵循链表的操作规则,即 驱动框架。
- 上层与底层的代码实际上是一一对应的,上层
opne("/dev/pin4",O_RDWR)
底层一定有pin4_open
,操作函数read、write同理。- 对于其他底层的驱动文件也是类似的,open、read、write都是被放在大的结构体之后才加入到驱动链表中。
- 不同于系统的上层文件,底层的驱动框架文件中对于变量的声明有大量的
static
。之所以大量充斥着该函数,是因为整个内核文件数量庞大足足有上万个之多,容易与其他文件的函数命名冲突,函数static
限定了变量的作用域仅在该文件中。
交叉编译两个文件并将文件传送至树莓派
- 引脚驱动文件编译。 将引脚的驱动文件放在系统的字符驱动文件目录下 home/xxx/linux-rpi-4.14.y/drivers/char,直接 vi 修改引导配置文件 Makefile,在文件中直接插入字段 obj-m += pin4Driver.o(前者代表编译的方式,后者代表编译生成的文件。)
- 模块化编译。 在第一步的前提下跳转回到系统源码目录下 home/xxx/linux-rpi-4.14.y/ ,输入指令进行交叉编译:ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make -j4 modules。等待编译完成后再次打开字符设备驱动文件,编译成功则目录底下会生成新的文件
pin4Driver.ko
【“j4”表示使用4个核进行编译工作,“modules”表示生成的驱动模块。相比于笔者关于树莓派内核编译的那篇文章中,此处并不需要生成配置文件“dtbs”、内核文件“zImage”,故而只需要一个参数。】 - 上层应用测试代码编译。上层的文件处理比较简单,直接对其进行交叉编译处理即可,输入指令:arm-linux-gnueabihf-gcc pin4Test.c -o pin4Test。
- 文件传送至树莓派。使用 scp 命令将新生成的文件传送至树莓派内,指令:
scp pin4Test pi@树莓派的IP地址:/home/pi
scp pin4Driver.ko pi@树莓派的IP地址:/home/pi
在树莓派内加载驱动并进行检测
- 驱动加载。输入指令:sudo insmod pin4Driver.ko。查看驱动文件是否加载成功有两个方面:
输入指令:ls /dev/pin4 -l ,查看设备文件下是否有设备文件;
输入指令:lsmod ,查看内核是否挂载设备驱动文件 pin4Driver.ko。 - 设备权限设置。输入指令:sudo chmod 666 /dev/pin4
【666 ,表示所有人都可以访问该设备。】 - 测试。在树莓派主目录 pi@home 下运行文件 ./pin4Test。注意,此时上层界面是没有任何显示的(你也可以往测试文件中 pin4Test.c 加入少许调试信息),真正的运行现场是在内核空间。输入指令
dmesg | grep pin4
,若显示下图中的调试信息则说明在内核中调用函数 printk() 成功,完成了从最上层到最底层的逻辑调用。
注意:步骤相当繁琐,笔者不排除读者的每一步操作都出现小毛病,这需要自己去排查。
思绪
在只有模拟电路和数字电路时,远古工程师们是如何进行这些操作的呢?
参考资料
文章更新时间记录
- “用户态上层的应用测试代码 pin4Test.c”一节完成。 「2021.6.18 11:51」
- "基于驱动框架编写引脚驱动文件 pin4Driver.c"一节完成。 「2021.6.18 12:07」
- “为什么学习驱动编程?”一节完成。 「2021.6.19 17:02」
- “在树莓派内加载驱动文件并进行检测”一节完成。 「2021.6.23 12:44」
P.S.1 后续对树莓派重新进行刷机。 「2021.6.23 12:46」