驱动认知 驱动的编写
1.驱动的认知
打开文件 打开的是 文件名 (存放在/dev下面)
设备号
设备号又分为 主设备号
次设备号
主设备号就像华为手机,苹果手机
次设备号就是主设备号旗下的手机型号华为mate P 等
2.驱动链表
驱动插入到链表的顺序通过设备号进行查改
驱动的链表包括添加,查找
添加:将书写的驱动程序添加到驱动的链表当中
查找:通过设备号找到驱动程序,用户空间进行调用该驱动
分为三个层次 第一个是用户层
第二个是内核态
第三个是硬件层
该图简单的介绍了驱动如何从用户层进入到内核中
用户使用设备名找到设备号,进行调用
VFS虚拟文件系统调用sys_open 通过设备名找到设备号,进入到设备的open函数中进行调用
2.驱动框架解读
内核中printk使用的
- 设备从入口module_init(pin4_drv_init); //1.入口 进入
- 进入到_init函数中
- 首先进行的是创建设备号 通过主设备号和次设备号创建生成设备号devno = MKDEV(major,minor);
- 把写好的主设备号,设备号,创建的结构体写入驱动链表当中
- 注册驱动设备在内核中
- 首先创建生成设备
- 创建生成文件 创建生成文件成功后会在/dev下面生成相应的驱动程序
- 卸载驱动 卸载驱动的顺序是相反的 首先销毁设备文件,销毁生成的设备 最后卸载驱动
i2c_dev_class = class_create(THIS_MODULE, “i2c-dev”); //创建一个名称为i2c-dev的class /sys/class
确实在/sys/class里面看到了下面定义的那个文件名
#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; //pin4 的类
static struct device *pin4_class_dev; //pin4 的设备static dev_t devno; //设备号(设备名)
static int major =231; //主设备号
static int minor =4; //次设备号
static char *module_name="pin4"; //模块名//进行初始化配置引脚的寄存器
volatile unsigned int *GPFSEL0=NULL;
volatile unsigned int *GPSET0 =NULL;
volatile unsigned int *GPCLR0 =NULL;//pin4_open函数
static int pin4_open(struct inode *inode,struct file *file)
{printk("pin4_open\n"); //内核的打印函数和printf类似//配置pin4引脚为输出引脚//通过或运算与运算实现14 13 12 位为001*GPFSEL0&=~(0x6 << 12 ); //左移12位取反以后就变为了001 相与见零为零//所以保障了第14 13 位为0*GPFSEL0|=(0x1<<12); //左移12位取反变成了 001 进行或运算 见一为一// 所以保障了第12位为1 return 0;
}
static ssize_t pin4_read(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{printk("pin4_read\n");return 0;
}
//pin4_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{int userCmd;printk("pin4_write\n");//获取上层write函数的值copy_from_user(&userCmd,buf,count); //获取上层的write的值//根据值来操作io口 低电平或者高电平printk("get value\n");if(userCmd==1){*GPSET0 |= (0x1 << 4); //把引脚4置1 (置一)printk("get 1\n"); }else if(userCmd==0){*GPCLR0 |= (0x1 << 4); //把引脚4置1 (清零)printk("get 0\n"); }else{printk("undo\n");}return 0;
}
//static表示该命名仅在该驱动程序中有效 防止在其他的驱动文件中出现重复命名
//把该结构体放入到内核驱动的链表中
static struct file_operations pin4_fops = {.owner = THIS_MODULE, //内核中书写结构体指定某一位书写.open = pin4_open,.write = pin4_write,.read = pin4_read
};int __init pin4_drv_init(void) //2.真实驱动入口
{int ret;//主设备号 //次设备号devno = MKDEV(major,minor); //3.创建设备号//把上面的结构体 pin4_fops 放入驱动链表当中// 主设备号 设备名 结构体ret = register_chrdev(major, module_name,&pin4_fops); //4.注册驱动 告诉内核,把这个驱动加入到内核驱动的链表中//让代码生成设备的逻辑类 pin4_class=class_create(THIS_MODULE,"myfirstdemo"); //5.让代码在dev中生成设备 (华为手机)pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //创建设备文件 (mate30 ) 设备的类 设备号 模块名//创建完成就会在/dev生成模块名对应的驱动程序//配置 把物理地址转换成虚拟地址return 0;
}void __exit pin4_drv_exit(void) //卸载的时候是反着卸载的
{//1.卸载映射空间
//2.销毁设备文件
//3.销毁生成设备
//4.卸载驱动iounmap(GPFSEL0);iounmap(GPSET0 );iounmap(GPCLR0 ); //卸载映射的虚拟空间device_destroy(pin4_class,devno); //销毁pin4的设备文件class_destroy(pin4_class); //销毁pin4 的生成设备unregister_chrdev(major, module_name); //卸载驱动}module_init(pin4_drv_init); //1.入口 内核加载该驱动时,会启动宏
module_exit(pin4_drv_exit); //6.卸载驱动
MODULE_LICENSE("GPL v2");
3.驱动代码的编译
3.1驱动代码的编译
打开内核文件
进入到 drivers 文件夹下面
创建的是字符设备 因此我们需要进入到char文件夹字符文件
进入到 char文件夹下面创建pin4drivers.c文件
放到哪里改哪里的Makefile
保存完成后修改Makefile进入vi Makefile
书写成编译成为模块的方式
该创建创建的是以模块的方式加载到内核 obj-m m表示的是模块
obj-m += pin4drivers.o (这个名字和创建的pin4drivers名字一样,只是后缀不同)
写完后就配置完成返回到内核源码文件夹 (liunx…)
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules
进行编译编译没有错误生成pin4drivers.ko
传给树莓派
scp ./drivers/char/pin4drivers.ko pi@192.168.43.58:/home/pi
同时通过交叉编译把主程序发送
3.2在树莓派:
- 加载内核驱动
sudo insmod pin4drivers2.ko
- 卸载内核驱动 不需要写.ko 卸载内核驱动
sudo rmmod pin4drivers2
- 查看当前内核的驱动模块
lsmod
- 把dev里面的pin4设置为所有用户可读可写
sudo chmod 666 /dev/pin4
- 查看用户权限
ls -l
- 查看内核驱动
dmesg
可以查看到pin4的驱动
GPIO的寄存器
GPFSELn 输入模式选择寄存器 pin0~pin9 每个GPIO引脚占3位000 = GPIO Pin 9 is an input 001 = GPIO Pin 9 is an output
GPSETn 将指定引脚置一
GPCLRn 将指定位置引脚清零
volatile 的用处
vloatile关键字表示提醒当前编译器该变量是易变的,即每次使用和读取该变量的时候都需要重新向变量地址中读取数据
地址
在编写驱动的时候IO口的驱动地址是在0x3f00 0000加上GPIO的偏移量0x20 0000 因此GPIO的物理地址是从0x3f20 0000开始的在这个基础上通过Linux系统的MMU内存虚拟化管理,映射到虚拟内存
寄存器的偏移地址
寄存器的地址是物理地址加上寄存器的偏移地址寄存器的地址是物理地址需要使用ioremap函数把物理地址转换成虚拟地址
ioremap函数
ioremap(物理地址,长度);
寄存器代码的编写
1.首先在最开始的地方定义全局变量volatile unsigned int * GPFSEL1 =NULL;2.在入口函数处把物理地址转换成虚拟地址GPFSEL1 = (volatile unsigned int *)ioremap(0x3f20004,4); 3.在函数中进行寄存器的按位操作 初始化的pin17引脚*GPFSEL1 &= ~(0x6 << 21) ;//该操作是把23 22位置0 不确定21位当前的状态*GPFSEL1 |= (0x1 << 21); //该操作是不改变其他位的状态,只让21位的值是14.在驱动结束的函数卸载映射的内存空间
4.驱动编写
驱动中有两个函数
read 和write
read
ssize_t (*read)(struct file *filp, char __user *buf, size_t count, lofft *f_pos);
filp:待操作的设备文件file结构体指针
buf:待写入所读取数据的用户空间缓冲区指针
count:待读取数据字节数
f_pos:待读取数据文件位置,读取完成后根据实际读取字节数重新定位
返回:成功实际读取的字节数,失败返回负值内核空间-->用户空间
copy_to_user函数
unsigned long copy_to_user(void *to, const void *from, unsigned long n)
to:目标地址(用户空间)
from:源地址(内核空间)
n:将要拷贝数据的字节数
返回:成功返回0,失败返回没有拷贝成功的数据字节数
write
ssize_t (*write)(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos);
filp:待操作的设备文件file结构体指针
buf:待写入所读取数据的用户空间缓冲区指针
count:待读取数据字节数
f_pos:待读取数据文件位置,写入完成后根据实际写入字节数重新定位
返回:成功实际写入的字节数,失败返回负值用户空间-->内核空间
unsigned long copy_from_user(void *to, const void *from, unsigned long n);
to:目标地址(内核空间)
from:源地址(用户空间)
n:将要拷贝数据的字节数
返回:成功返回0,失败返回没有拷贝成功的数据字节数
驱动认知 驱动的编写相关推荐
- 驱动认知-驱动代码编写与执行
驱动的认知 应用层进行open,read,write驱动程序的时候,linux系统调用过程 基于驱动框架代码编写 寻找一个驱动参考(字符设备驱动) 作为初学者我们可以选择别人写好的驱动作为参考 #in ...
- 《认知觉醒》+《认知驱动》
阅读<认知觉醒>+<认知驱动>,总结 幸福的人各有各的幸福,不幸的人都是同样的不幸
- rust实现一个mysql驱动_使用Rust编写用户态驱动程序
概览 在云计算技术的发展史上,如何提高单个服务器的并发度,一直是热门的研究课题.在20年前,就有著名的"C10K"问题,即如何利用单个服务器每秒应对10K个客户端的同时访问.这么多 ...
- ARM(IMX6U)裸机汇编LED驱动实验——驱动编写、编译链接起始地址、烧写bin文件到SD卡中并运行
参考:Linux之ARM(IMX6U)裸机汇编LED驱动实验–驱动编写 作者:一只青木呀 发布时间: 2020-08-07 09:13:48 网址:https://blog.csdn.net/weix ...
- Linux系统认知——驱动认知
文章目录 一.驱动相关概念 1.什么是驱动 2.被驱动设备分类 3.设备文件的主设备号和次设备号 4.设备驱动整体调用过程 二.基于框架编写驱动代码 1.驱动代码框架 2.驱动代码的编译和测试 三.树 ...
- pinctrl虚拟spi的linux驱动,LinuxSPI驱动.md
--- ![](https://b3logfile.com/bing/20190517.jpg?imageView2/1/w/960/h/540/interlace/1/q/100) SPI 驱动框架 ...
- Aubo i5真机 ros - melodic 版驱动下载 [ 驱动下载 ]
Aubo i5真机melodic 驱动下载 melodic源码下载,官网未更新 ros - melodic 版驱动,此驱动是优化后的,经过测试可正常运行; 链接:https://pan.baidu.c ...
- [设备驱动] 最简单的内核设备驱动--字符驱动
[设备驱动] 最简单的内核设备驱动--字符驱动 概要: x86平台上(linux-2.6.34.14;Linux debian 3.2.0-3-686-pae)编写一个256字节的字符驱动程序.在/ ...
- i.MX283开发板第一个Linux驱动-LED驱动
字符设备驱动开发 字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的.比如我们最常见的点灯.按键.IIC.SPI,LCD ...
最新文章
- HarmonyOS Text超出部分末尾显示...
- 《Typecript 入门教程》 2、访问控制符:public、private、protected、readonly
- 【转自元宝兄】关于delphi Com+调用C# DLL的一点说明
- matlab变量代替语句,MATLAB只是简单地把表达式里的变量名替换成数值,而不给出结果...
- 【数理知识】标量函数、二次型函数、矩阵、正定负定半正定半负定
- Hadoop在Ubuntu下的安装配置(配置成功)
- C语言简洁代码:1006 换个格式输出整数 (15分)
- c语言中文网_在C语言中使用中文字符
- 配置Mac自带的Apache http服务器
- iOS底层探索之多线程(十七)——通过 Swift的Foundation源码分析锁(NSLock、NSCondition、NSRecursiveLock)
- 紫外线杀菌器:Photoscience紫外线杀菌器在食品饮料中的作用
- 前端使用js来获取ip起始和结束地址
- Arduino连接HC05蓝牙模块
- python正弦波叠加方波_电赛初探(一)——正弦波、方波、锯齿波转换
- 栅格数据灰度化并前端转换展示
- 如何在计算机自动开机时选择用户,电脑如何设置自动开机
- 中科大自主招生计算机,中科大自主招生(中科大自主招生试题)
- 编程初学者必备的基础知识
- 【金融量化】CTA策略的构成
- 计算机网络自顶向下方法第四章笔记