IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 嵌入式知识框架之五 - 编写驱动程序的框架 -> 正文阅读

[嵌入式]嵌入式知识框架之五 - 编写驱动程序的框架

1.什么是驱动程序?

驱动程序是对硬件的具体操作,比如让LED的亮灭,在LCD上显示一个像素等,设计到对GPIO或者硬件的设置。

2.驱动程序如何加载进内核?

linux设备驱动是以内核模块的形式出现,Linux系统整体架构庞大,组价非常多,但是需要什么功能就重新编译内核,费时费力。linux提供一种模块机制,1.模块本身可以不被编译进入内核,控制内核映像的大小;2.一旦模块加载,就和编译进内核是一样的。

linux提供了模块程序结构

module_init(使用insmod调用)

module_exit(使用rmmod调用)

模块许可证声明(GPL)

模块参数(可选)

模块导出符号(可选)

模块作者等信息声明(可选)

?3.驱动如何编写

框架+成员函数?

4. 框架是什么?

首先,驱动最终通过与文件操作相关的系统调用或C库函数(本质也基于系统调用)被访问,而设备驱动的结构最终也是为了迎合提供给应用程序员的API。
其次,驱动工程师在设备驱动中不可避免地会与设备文件系统打交道,包括从Linux 2.4内核的devfs文件系统到Linux 2.6以后的udev。

?Linux中文件操作的系统调用,包括创建、打开、读写和关闭文闭等操作

int creat(const char *filename, mode_t mode);
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int read(int fd, const void *buf, size_t length);
int write(int fd, const void *buf, size_t length);
int lseek(int fd, offset_t offset, int whence);
int close(int fd);

C库同样有以上操作,自行查看,推荐使用C库,C库最后也是调用系统调用,使用C库可以使程序与系统无关,利于移植程序。

?应用程序,VFS,设备驱动关系

按键驱动的例子:

1.注册模块

/* 这两行指定驱动程序的初始化函数和卸载函数 */
module_init(sixth_drv_init);
module_exit(sixth_drv_exit);

/* 描述驱动程序的一些信息,不是必须的 */
MODULE_AUTHOR("http://www.100ask.net");             // 驱动程序的作者
MODULE_DESCRIPTION("S3C2410/S3C2440 BUTTON Driver");   // 一些描述信息
MODULE_LICENSE("GPL");                              // 遵循的协议

?当编译好驱动文件.ko文件时,使用insmod命令就会调用module_init()函数,使用rmmod会调用module_exit()函数,module_license是必须的内容。其余的信息时可选项。

2.init模块的sixth_drv_init是一个函数的名,或者说函数符号。来看这个函数的内容有什么:

static int sixth_drv_init(void)
{
	init_timer(&buttons_timer);
	buttons_timer.function = buttons_timer_function;
	//buttons_timer.expires  = 0;
	add_timer(&buttons_timer); 

	major = register_chrdev(0, "sixth_drv", &sencod_drv_fops);

	sixthdrv_class = class_create(THIS_MODULE, "sixth_drv");

	/* 为了让mdev根据这些信息来创建设备节点 */
	sixthdrv_class_dev = class_device_create(sixthdrv_class, NULL, MKDEV(major, 0), NULL, "buttons"); /* /dev/buttons */

	gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
	gpfdat = gpfcon + 1;

	gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
	gpgdat = gpgcon + 1;

	return 0;
}

最基础的功能有
1.获得一个设备号major,将该设备注册进入设备的数组中,以动态获取设备号的形式
2.创建一个class类:/dev下创建一个文件夹
3.再class类下面注册一个节点?:在上面那个文件夹下创建一个文件,linux下一切皆文件,这个文件就是创建的设备。
4.初始化,把要操作的寄存器物理地址ioremap到虚拟空间

带双引号是名称,无关紧要,自己起,来看取址符号后面的内容,

3.sencod_drv_fops,这里是一个结构体的名字:

static struct file_operations sencod_drv_fops = {
    .owner   =  THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open    =  sixth_drv_open,     
	.read	 =	sixth_drv_read,	   
	.release =  sixth_drv_close,
	.poll    =  sixth_drv_poll,
	.fasync	 =  sixth_drv_fasync,
};

这个结构体里里面东西就是驱动的真正内容。open通常是对引脚进行配置,或者设置中断,在终中断里面做引脚的设置工作;read,通常是读取某个DATA寄存器的值,返回给用户程序;write通常是给data寄存器里面写一个值。

4.中断函数如何使用:

/******************发生中断时调用的中断函数***********************/
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
	struct pin_desc * pindesc = (struct pin_desc *)dev_id;
	unsigned int pinval;
	
	pinval = s3c2410_gpio_getpin(pindesc->pin);

	if (pinval)
	{
		/* 松开 */
		key_val = 0x80 | pindesc->key_val;
	}
	else
	{
		/* 按下 */
		key_val = pindesc->key_val;
	}

    ev_press = 1;                  /* 表示中断发生了 */
    wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程 */

	
	return IRQ_RETVAL(IRQ_HANDLED);
}


/*********************open函数中的中断函数***************************/
request_irq(IRQ_EINT0,  buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);

1.参数1是将哪个引脚设置为中断模式
2.参数2是中断函数,由自己定义
3.触发方式,这里是双边缘触发
4.中断的名称,中断进程中会显示这里的S2
5.调用中断函数是传入的值,自己定义,这样可以使用同样的函数,区分不同的中断。

总结下框架包括:注册模块,初始化模块,分配file_operation结构体并写成员函数(驱动的主要内容),卸载模块

5.把自己写的驱动融合进现有的驱动框架

?输入子系统框架:

比如scanf();tty1;而不是打开一个你的/dev/first_dev这样的操作。

核心层在drives/input.c

input.c:

做了以下事情:

1.创建设备号,注册设备,创建节点
????????class_register();
????????register_chrdev();
2.定义file_operation结构体
? ? ? ? 定义成员函数open(是一个中转的过程),owner
?

//源码:
/* 注册模块 */
subsys_initcall(input_init);
module_exit(input_exit);
//fiel_operation结构体
static const struct file_operations input_fops = {
	.owner = THIS_MODULE,
	.open = input_open_file,
};

输入子系统分为两层,最上层是核心层,里面同样是注册函数等,注册的file_operation结构体中的成员函数只有open函数,非常简单,但open函数只是一个中转,open函数中,会根据设备节点的传进来的次设备号,在一个数组中,找到一个input_handler结构体,然后调用其中的open函数,并且把这个文件的fop指向input_handler里面的fop,以后要读或者写,都是调用这个input_handler里面的write/read函数。那么这个数组是如何被构建的?都是一些纯软件的input_handler,通过调用上层input.c中的input_register_handler来组成的这个表。纯软件的肯定不够,还需要硬件的部分才能控制硬件,硬件部分同样有一个input_dev结构体,会向上注册到到input_dev_list链表中,每次调用注册函数,会同时调用list_for_each_entry()函数下的input_attach_handler()函数,该函数通过他自己的input_match_device()函数来匹配软硬的ID,如果匹配成功,将会建立“连接”

连接:

1.分配一个handle结构体,最后注册这个结构体到一个链表中。
2.其成员dev和handler分别指向软件和硬件结构体的地址。
? ?input_handle.dev = input_dev; ?// 指向左边的input_dev
?? input_handle.handler = input_handler; ?// 指向右边的input_handler
3.软硬两部分结构体中的成员都有h_list,他们都将指向这个handle结构体

实现桥式连接,handle是中间结构体,两边的结构体中都有成员指向他。

所以在往后写驱动程序的时候只需要分配一个input_dev结构体(不同的设备结构体不同),然后注册到对应的链表中去。比如:fb_info结构体,注册register_frambuffer,不需要再进行创建设备节点等操作。

总结:类似上面的框架在linux中有很多,一般步骤都是:1.分配结构体,2.设置;3.注册;4.硬件操作相关。

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-07-30 12:54:10  更:2021-07-30 12:55:20 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/8 13:13:46-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码