树莓派(五)驱动开发

这篇具有很好参考价值的文章主要介绍了树莓派(五)驱动开发。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、初识驱动

1.简介

Linux下的设备通常可分为三类:

  1. 字符设备
  2. 块设备
  3. 网络设备

常见字符设备有鼠标、键盘、串口、控制台等。

常见的块设备有各种硬盘、flash磁盘、RAM磁盘等。

在Linux里一个网络设备也可以叫做一个网络接口,如eth0,应用程序是通过Socket而不是设备节点来访问网络设备,在系统里根本就不存在网络设备节点。网络接口没有像字符设备和块设备一样的设备号,只有一个唯一的名字,如eth0、eth1等,而这个名字也不需要与设备文件节点对应。

在Linux中,所有设备都以文件的形式存放在/dev目录下,都是通过文件的方式进行访问,设备节点是Linux内核对设备的抽象,一个设备节点就是一个文件。应用程序通过一组标准化的调用执行访问设备,这些调用独立于任何特定的驱动程序,而驱动程序负责将这些标准调用映射到实际硬件。

2.涉及到的概念

设备节点:Linux下被创建在/dev目录,是连接内核与用户层的枢纽,就是设备是接到对应哪种接口的哪个ID 上。 相当于硬盘的inode一样的东西,记录了硬件设备的位置和信息。

设备号包含主设备好和次设备号。

主设备号:驱动程序在初始化时,会注册它的驱动及对应主设备号到系统中,这样当应用程序访问设备节点时,系统就知道它所访问的驱动程序了。你可以通过/proc/devices文件来查看系统设备的主设备号。

次设备号:驱动程序遍历设备时,每发现一个它能驱动的设备,就创建一个设备对象,并为其分配一个次设备号以区分不同的设备。这样当应用程序访问设备节点时驱动程序就可以根据次设备号知道它说访问的设备了。

驱动程序:设备驱动程序(device driver),简称驱动程序(driver),是一个允许高级(High level)计算机软件(computer software)与硬件(hardware)交互的程序,这种程序建立了一个硬件与硬件,或硬件与软件沟通的界面,经由主板上的总线(bus)或其它沟通子系统(subsystem)与硬件形成连接的机制,这样的机制使得硬件设备(device)上的数据交换成为可能。想象平时我们说的写驱动,例如点led灯的驱动,就是简单的io操作。

系统调用:应用程序运行在用户空间,而 Linux 驱动属于内核的一部分,因此驱动运行于内核空间。当我们在用户空间想要实现对内核的操作,必须使用一个叫做 "系统调用" 的方法来实现,因为用户空间不能直接对内核进行操作,而 "系统调用" 相当于是用户程序和内核之间的接口,通过这个接口,用户程序就能安全而又受控地访问底层操作系统,从而实现对底层驱动的操作。open、close、write 和 read 等这些函数是有 C 库提供的,在 Linux 系统中,系统调用作为 C 库的一部分。

3.彼此的关联

应用程序是运行在用户空间的,而驱动位于内核的,设备又是具体的硬件层面,三者是如何联系在一起的呢?

用户程序调用C库open函数,会触发软中断,然后通过系统调用进入内核态,系统调用中也有与C库open函数对应的sys_open函数,然后sys_open通过设备名称访问到设备节点,而创建设备节点时是需要指定主设备号和次设备号的,那么顺着设备号就可以找到驱动程序和相应设备,驱动程序中也有属于自己的drv_open等函数,最后通过这些函数来指挥对应设备硬件。

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

驱动开发大致可分为两步:  

  1. 添加驱动:编写驱动程序,然后加载到内核
  2. 调用驱动:用户空间open后,调用驱动程序

编写驱动程序时,要准备好设备名称、设备号、设备驱动函数(通过操作寄存器去操作IO口) 

编写驱动程序的流程:

  1. 确定主设备号,也可以让内核分配
  2. 定义自己的file_operations结构体
  3. 实现对应的drv_open/drv_read/drv_write等函数,填入file_operations结构体
  4. 把file_operations结构体告诉内核:register_chrdev
  5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
  6. 有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev
  7. 其他完善:提供设备信息,自动创建设备节点:class_create, device_create

register_chrdev()

register_chrdev函数是Linux设备驱动程序中非常重要的函数,它用于动态地分配主设备号和次设备号,并将主设备号和设备驱动程序所用的file_operations结构体绑定在一起。

函数原型:

int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);
  • major表示主设备号,如果它被设置为0,则表示系统自动分配主设备号
  • name表示设备名称
  • fops为设备驱动程序的操作函数指针,它包含了所有与设备相关的操作函数

返回值:

当register_chrdev函数成功被调用时,它会返回分配的主设备号,否则会返回一个负数,表示分配失败。当主设备号被成功分配后,你就可以在file_operations结构体中使用这个主设备号了。

以下是驱动程序:

#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";   //模块名,或者设备节点名称,位于/dev目录下
 
//pin4_read函数
//static int pin4_read(struct file *file,char __user *buf,size_t count, loff_t *ppos)
//{
//    printk("pin4_write\n");
//    return 0;
//}
 
//pin4_open函数,实现对应的open函数,然后填入file_operations结构体
static int pin4_open(struct inode *inode,struct file *file)
{
    printk("pin4_open\n");  //内核的打印函数,和printf类似
   
    return 0;
}
 
//pin4_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
	printk("pin4_write\n");
    return 0;
}

//定义自己的file_operations结构体,将之前实现的open、write等函数填入
static struct file_operations pin4_fops = {
 
    .owner = THIS_MODULE,
    .open  = pin4_open,
    .write = pin4_write,
};
 
//初始化驱动函数
int __init pin4_drv_init(void)   //驱动的真正入口
{
 
    int ret;
    devno = MKDEV(major,minor);  //创建设备号

    //把file_operations结构体告诉内核,注册驱动程序
    ret   = register_chrdev(major, module_name,&pin4_fops); 
 
    //由代码通过以下两步,在/dev下自动生成设备文件
    //调用class_create先在/sys/class下创建一个类,类名为myfirstdemo
    pin4_class=class_create(THIS_MODULE,"myfirstdemo");
    //再调用device_create自动创建设备文件,设备名为module_name
    pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name);  //创建设备文件
 
 
    return 0;
}
 
void __exit pin4_drv_exit(void)
{
 
    device_destroy(pin4_class,devno);       //卸载设备
    class_destroy(pin4_class);              //删除类
    unregister_chrdev(major, module_name);  //卸载驱动
 
}
 
module_init(pin4_drv_init);  //入口:内核加载驱动的时候,这个宏会被调用,而真正的驱动入口是它调用的函数
module_exit(pin4_drv_exit);  //出口:卸载驱动程序时,出口函数调用unregister_chrdev
MODULE_LICENSE("GPL v2");

代码补充:

static的作用

内核代码数量庞大,为了防止与其他的文件有变量命名冲突,static限定变量的作用域仅仅只在这个文件。

结构体成员变量的单独赋值

static struct file_operations pin4_fops = {
 
    .owner = THIS_MODULE,
    .open  = pin4_open,
    .write = pin4_write,
};

 这是内核代码中常见的对结构体的操作方式,单独给指定结构体元素赋值,其他不管。

 注意:在keil的编译工具环境中不允许这样写,linux可以。

结构体file_operations 

在SourceInsight中查看结构体file_operations,可以发现很多的函数指针,这些函数名跟系统上层文件的操作差不多。

树莓派(五)驱动开发,linux

如果上层想要实现read,就复制过来,按照格式改一改。

上层对应底层,上层想要用read,底层就要有read的支持。

三、交叉编译驱动程序

打开虚拟机,进入Linux源码目录下字符设备驱动目录/driver/char。将上述驱动框架复制到这里,创建名为pin4driver.c。

修改Makefile文件

运行以下指令,进行配置,使得工程编译时可以编译这个文件。

vi Makefile

模仿这些文件的编译方式,以编译成模块的方式编译pin4driver.c。

将以下配置加入Makefile。

obj-m     += pin4driver.o

树莓派(五)驱动开发,linux

 进行模块编译

之前编译内核的时候用的是这个命令。

ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make -j4 zImage modules dtbs

 现在只需进行模块编译,不需要生成zImage,dtbs文件。

所以,回到源码目录执行以下指令。

ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make -j4 modules

执行完编译命令后,回到/driver/char目录,会发现多了一些文件如下图。树莓派(五)驱动开发,linux 

 把 .ko 文件发送到树莓派

运行以下指令发送 .ko 文件。

scp pin4driver.ko pi@192.168.10.112:/home/pi

再写一个应用程序 pin4test.c 来测试驱动。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
 
int main()
{
	int fd;
	fd = open("/dev/pin4",O_RDWR);
	if(fd < 0){
		printf("open failed\n");
		perror("reson");
	}else{
		printf("open success\n");
	}
	fd = write(fd,'1',1);//写一个字符'1',写一个字节
	return 0;
}

 使用交叉编译工具进行编译。

arm-linux-gnueabihf-gcc pin4test.c -o pin4test

 将生成文件发送到树莓派。

scp pin4test pi@192.168.10.112:/home/pi

 四、树莓派装载驱动

运行以下命令进行装载。

sudo insmod pin4driver.ko

运行这个指令,就是在底层运行初始化驱动函数。

可以通过 lsmod 指令来查看内核挂载的驱动。树莓派(五)驱动开发,linux

或者直接到 /dev 目录下查看。

ls /dev

 如果要卸载驱动就运行以下指令。

sudo rmmod pin4driver

运行应用程序 pin4test 

sudo ./pin4test

检查是否成功:demsg指令可以查看内核打印信息

用dmesg命令显示内核缓冲区信息,并通过管道筛选与pin4相关信息。

dmesg | grep pin4

可以看到这两个打印信息,说明内核的printk已经被成功调用,我们已经成功完成了上层对内核的调用 !  

树莓派(五)驱动开发,linux

 五、参考博文

01linux驱动学习--基本驱动框架 - 码农教程 (manongjc.com)

Linux驱动device_create创建字符设备文件-CSDN博客

class_create()函数应用_class_create函数-CSDN博客

register_chrdev函数详解_笔记大全_设计学院 (python100.com)

嵌入式Linux驱动复习(1)hello驱动_can't open file /dev/hello-CSDN博客

【Linux】基于框架编写驱动代码、驱动代码编译和测试-CSDN博客

树莓派(十一)树莓派驱动开发入门:从读懂框架到自己写驱动(上)-CSDN博客

嵌入式Linux驱动复习(3)设备节点、驱动、最简单的LED驱动编写_.write = led_write什么意思-CSDN博客 文章来源地址https://www.toymoban.com/news/detail-784414.html

到了这里,关于树莓派(五)驱动开发的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • <Linux开发>驱动开发 -之- Linux RTC 驱动

    <Linux开发>驱动开发 -之- Linux RTC 驱动 交叉编译环境搭建: <Linux开发> linux开发工具-之-交叉编译环境搭建 uboot移植可参考以下: <Linux开发> -之-系统移植 uboot移植过程详细记录(第一部分) <Linux开发> -之-系统移植 uboot移植过程详细记录(第二部分) <Linux开发>

    2024年02月11日
    浏览(38)
  • <Linux开发>驱动开发 -之- Linux LCD 驱动

    <Linux开发>驱动开发 -之- Linux LCD 驱动 交叉编译环境搭建: <Linux开发> linux开发工具-之-交叉编译环境搭建 uboot移植可参考以下: <Linux开发> -之-系统移植 uboot移植过程详细记录(第一部分) <Linux开发> -之-系统移植 uboot移植过程详细记录(第二部分) <Linux开发>

    2024年02月06日
    浏览(43)
  • 【39元linux开发板-ADB远程教程】-[ADB远程终端]-幸狐Luckfox Pico-超越树莓派PICO

    【教程-持续更新】 幸狐Luckfox Pico RV1103 教程合集 【39元linux开发板-ADB远程教程】-[ADB远程终端]-幸狐Luckfox Pico-超越树莓派PIC

    2024年02月10日
    浏览(46)
  • <Linux开发>驱动开发 -之-platform 驱动

    <Linux开发>驱动开发 -之-platform 驱动 交叉编译环境搭建: <Linux开发> linux开发工具-之-交叉编译环境搭建 uboot移植可参考以下: <Linux开发> -之-系统移植 uboot移植过程详细记录(第一部分) <Linux开发> -之-系统移植 uboot移植过程详细记录(第二部分) <Linux开发>

    2024年02月12日
    浏览(60)
  • 香橙派/树莓派 电脑linux 电赛备赛指南-opencv全过程最简安装,ssh,vnc,USB摄像头驱动和配置环境避坑,手把手教学。opencv视觉入门(一)

    老早就想写博客了把之前的项目都记录下来,但是一直碍于每次做完项目都挺累的,于是就偷懒没写,不过每次做完都有总结,现在已经大三了,觉得应该把自己之前学习过程中遇到的经验或者坑写出来,后面会陆陆续续的发出来,今天先来讲一下有关这份博客的前提背景吧

    2024年04月08日
    浏览(51)
  • linux内核网络驱动框架(linux驱动开发篇)

    网络驱动的核心: 1、就是初始化 net_device 结构体中的各个成员变量, 2、然后将初始化完成以后的 net_device 注册到 Linux 内核中 1、网络设备(用net_device结构体) 2、网络设备的操作集( net_device_ops结构体 ) 3、sk_buff结构体 网络是分层的,对于应用层而言不用关系具体的底层是

    2023年04月08日
    浏览(80)
  • Linux驱动开发(十四)---USB驱动开发学习(键盘+鼠标)

    《Linux驱动开发(一)—环境搭建与hello world》 《Linux驱动开发(二)—驱动与设备的分离设计》 《Linux驱动开发(三)—设备树》 《Linux驱动开发(四)—树莓派内核编译》 《Linux驱动开发(五)—树莓派设备树配合驱动开发》 《Linux驱动开发(六)—树莓派配合硬件进行字

    2024年02月08日
    浏览(53)
  • Linux驱动开发12 IIC驱动

            I2C 是很常用的一个串行通信接口,用于连接各种外设、传感器等器件,在裸机篇已经对 I.MX6U 的 I2C 接口做了详细的讲解。本章我们来学习一下如何在 Linux 下开发 I2C 接口器件驱动,重点是学习 Linux 下的 I2C 驱动框架,按照指定的框架去编写 I2C 设备驱动。     

    2024年02月06日
    浏览(44)
  • Linux驱动开发:platform总线驱动

    目录 1、为什么需要platform总线 2、设备端:platform_device 2.1 platform_device结构体 2.2 注册 2.3 注销 3、驱动端:platform_driver 3.1 platform_driver结构体 3.2 注册 3.3 注销 4、总线 4.1 bus_type  4.2 platform_bus_type 5、匹配 5.1 匹配规则,platform_match 5.2 platform_device匹配流程 5.3 platform_driver匹配

    2024年02月03日
    浏览(45)
  • linux驱动的学习 & 驱动开发初识

    在学习驱动和其开发之前,首先要知道 所谓驱动,其对象就是设备 。 1.1 主设备号次设备号: 在Linux中,各种设备都以文件的形式存在 /dev目录下 ,称为 设备文件 。 最上层的应用程序可以打开,关闭,读写这些设备文件,从而完成对设备的操作 。 为了管理这些设备,系统

    2024年02月04日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包