详细到吐血

您所在的位置:网站首页 树莓派开发难吗 详细到吐血

详细到吐血

2023-12-04 06:59| 来源: 网络整理| 查看: 265

师承陈立臣

目录 README 一、驱动初步认知 为什么要学会写驱动? 设备号的两个作用? 区分硬件 索引驱动在驱动链表中的位置 从open到设备,从上层到底层,经历了什么? 二、基于内核驱动框架编写驱动代码流程 1.编写上层应用代码 2.根据上层需求修改内核驱动框架代码 代码补充解读 static的作用 结构体成员变量的单独赋值 结构体`file_operations` 手动生成设备 3.在Ubuntu上交叉编译(很重要) 驱动框架的模块编译并发送至树莓派 ①修改Makefile ②进行模块编译 ③把`.ko`文件发送至树莓派 上层代码的编译并发送至树莓派 使用交叉编译工具进行编译 发送至树莓派 4.树莓派装载驱动并运行 ①树莓派装载驱动 ②运行上层代码 ③增加访问权限再运行 是否执行成功:`demsg`指令查看内核打印信息 三、3个地址的介绍 微机总线地址 物理地址 虚拟地址 简单了解地址框图与内核的页表映射 四、实战:操作IO口输出高 / 低电平 1.芯片手册导读 General Purpose I/O (GPIO)板块 捕捉信息 配置引脚 输入 / 输出 配置引脚输出是 0 还是 1 清除 0 / 1 状态 整理关键内容 2.配置3个主要的寄存器地址 ①在原来框架的基础上,添加寄存器的定义 弄清楚寄存器的分组 volatile的使用 ②配置寄存器的地址 分别找到几个IO寄存器的物理地址(非常易错) 弄清楚GPIO的物理地址(真实地址) 根据偏移值,弄清楚寄存器的物理地址(真实地址) 物理地址转换为虚拟地址:`ioremap`函数 3.进行功能配置 ①在函数pin4_open中配置pin4为输出引脚 运用与(&) / 或(|)运算进行位操作 ==与运算给指定位==(14、13)==赋值0==,其他不变 ==或运算==给指定位(12)==赋值1== ②在函数pin4_write中配置pin4输出 0 / 1 获取上层write函数的值:`copy_from_user`函数 根据值来操作IO口 4.解除虚拟地址映射 退出程序卸载驱动的时候,解除映射:`iounmap`函数 5.完整代码 内核驱动框架 上层应用程序 6.交叉编译并发送至树莓派 ①树莓派上卸载之前的pin4驱动,删除上层应用文件和.ko文件 ②框架和上层应用程序在Ubuntu中进行交叉编译并发送至树莓派 7.装载驱动 8.运行上层应用文件 驱动成功运行 驱动运行失败:学会调试 奇怪的问题 五、其他 简单了解:DMA(direct memory access)(直接存储器访问) md5sum检查两个文件是否完全一样

README

emmm一不小心写了这么长的篇幅,建议配合目录一起看,从目录点击对应知识点,对知识体系和结构有整体的认识,阅读的时候才不会感到吃力。 在这里插入图片描述

一、驱动初步认知 为什么要学会写驱动?

树莓派开发简单是因为有厂家提供的wiringPi库,实现超声波,实现继电器操作,做灯的点亮…都非常简单。

但未来做开发时,不一定都是用树莓派,则没有wiringPi库可以用。但只要能运行Linux,linux的标准C库一定有。

学会根据标准C库编写驱动,只要能拿到linux内核源码,拿到芯片手册,电路图…就能做开发。

用树莓派学习的目的不仅是为是体验其强大便捷的wiringPi库,更要通过树莓派学会linux内核开发,驱动编写等,做一个属于自己的库。

设备号的两个作用? 区分硬件

linux一切皆为文件,其设备管理同样是和文件系统紧密结合。在目录/dev下都能看到鼠标,键盘,屏幕,串口等设备文件,硬件要有相对应的驱动,那么open怎样区分这些硬件呢?

依靠文件名与设备号。在/dev下ls -l可以看到

在这里插入图片描述

索引驱动在驱动链表中的位置

设备号又分为:主设备号用于区别不同种类的设备;次设备号区别同种类型的多个设备。

内核中存在一个驱动链表,管理所有设备的驱动。 驱动开发无非以下两件事:

编写完驱动程序,加载到内核 用户空间open后,调用驱动程序

驱动插入到链表的位置(顺序)由设备号检索。

从open到设备,从上层到底层,经历了什么? 用户层调用open产生一个软中断(中断号是0x80),进入内核空间调用sys_callsys_call。 sys_callsys_call真正调用的是sys_open,去内核的驱动链表根据主设备号与次设备号找到相关驱动函数。 调用驱动函数里面的open,去设置IO口引脚电平。

(对应下图的粉色笔迹)

在这里插入图片描述

二、基于内核驱动框架编写驱动代码流程

目的是用简单的例子展示从用户空间到内核空间的整套流程。

1.编写上层应用代码

在上层访问一个设备跟访问普通的文件没什么区别。试写一个简单的open和write去操作设备"pin4"。

#include #include #include #include int main() { int fd; fd = open("/dev/pin4",O_RDWR); if(fd printf("open success\n"); } fd = write(fd,'1',1);//写一个字符'1',写一个字节 return 0; }

根据上面提到的驱动认知,有个大致的概念,以open为例子: 上层open→sys_call→sys_open→内核驱动链表节点→执行节点里的open

当然,没有装载驱动的话这个程序执行一定会报错。只有在内核装载了驱动并且在/dev下生成了“pin4”这样一个设备才能运行。

接下来介绍最简单的字符设备驱动框架。

2.根据上层需求修改内核驱动框架代码

所谓框架,就是定死的东西,基本的语句必须要有,少一个都不行。

虽然有这么多的代码,但核心运行的就两个printk。

#include //file_operations声明 #include //module_init module_exit声明 #include //__init __exit 宏定义声明 #include //class devise声明 #include //copy_from_user 的头文件 #include //设备号 dev_t 类型声明 #include //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"


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3