回顾:
1.mmap:
作用:就是将设备的物理地址映射到用户的虚拟内存空间上;
read,write,ioctl三个系统调用函数,如果涉及数据的访问,必然要经过两次的数据拷贝:用户空间,内核空间,硬件。
mmap的使用将2次的数据操作变成1次,大大的提高了设备数据的访问效率!
mmap系统调用过程:
1.应用程序mmap
2.C库的mmap
3.sys_mmap:
内核到当前进程的3G的MMAP内存映射区中找一块空闲的内存区域;一旦找到,内核使用struct vm_area_struct来描述这块空闲的内存区域的信息;
4.调用底层驱动的mmap
5.remap_pfn_range进行将物理地址映射到内核帮你找的虚拟内存进行地址映射!
2.linux应用编程高级IO:IO多路监听select/poll
案例:一个应用程序访问多个设备
方案1:串行
方法2:多线程
方案3:select.
select函数的功能:
利用此函数能够监听多个设备,监听设备是否可读,是否可写,或者是否有异常,如果监听的设备都不可用(即不可读也不可写也无异常),那么select就会让主进程进入休眠状态;监听的设备中只要有一个设备(或者监听的设备)可用(可读或者可写或者有异常)都会唤醒休眠的进程;
分析以上的总结内容:
1.多个设备就代表着有多个驱动程序;每一个设备都对应一个驱动;
2.设备不可用代表着应用程序会到内核空间操作硬件设备,只有在内核空间才有权限访问硬件设备;
3.主进程休眠代表主进程进入内核空间进行休眠,在内核空间必将利用等待队列机制让主进程休眠;
4.等待队列需要有一个等待队列头,唤醒休眠进程,使用wake_up,这个函数只需传递一个等待队列头即可唤醒休眠进程;只要有一个设备可用都会唤醒休眠的进程,这句话的潜台词是说一个主进程分别被添加到被监听的设备对应的驱动程序定义的等待队列头中!
5.select函数引起的主进程休眠,假如底层驱动也有对应的select函数,那只需要利用等待队列机制让主进程在底层驱动的select函数进行休眠操作即可!
**********************************************************
select系统调用过程:
1.应用程序调用select,首先调用C库的select函数实现;
2.C库的select保存select系统调用号到R7,调用SVC或者SWI触发软中断,至此由用户空间陷入内核空间,ARM的工作模式由用户模式转变为SVC管理模式;
3.跳转到内核准备好的异常向量表的入口地址,根据R7保存的系统调用,以它为索引在系统调用表中找到对应的实现函数sys_select
4.sys_select要完成:
1.把被监听的设备对应驱动程序的poll函数挨个调用一遍,
被监听的设备都不可用,它们的驱动的poll函数都返回0;
2.判断是否是驱动主动唤醒,还是超时唤醒,还是接收到信号唤醒;
3.如果即没有驱动主动唤醒,也没有超时唤醒,没有接收到信号,sys_select调用poll_schedule_timeout主动让进程进入休眠
4.假设被监听的设备中,有一个设备可用(可读或者可写或者异常,硬件通过中断来判断),都会唤醒休眠的主进程;
5.sys_select的poll_schedule_timeout函数返回,不再休眠
6.再次把被监听的设备驱动的poll函数挨个调用一遍,此时可用的设备对应的驱动poll函数会返回非0;
7.if (ret || time_out || ...) //ret = 1,立即返回到用户空间,
返回值为ret值
总结:
1.明确本来应该底层驱动的poll函数利用等待队列机制让进程休眠,但是等待队列休眠9步骤并不都是驱动的poll来编写,有一部分是有内核sys_select来实现;
2.驱动poll函数完成如下内容即可:
1.调用poll_wait,将当前进程添加到驱动定义的等待队列头中
2.根据设备是否可用,决定返回0还是非0
3.明确:监听机制,底层poll函数不是必须的,如果要监听设备还可以使用多线程机制也能够完成监听;但是如果要使用select/poll监听设备,驱动必须有poll实现!
案例:给之前的按键驱动添加被监听机制,并且采用平台总线的软件实现!
在linux下,有多个文件时,调用多个文件中的函数时!
ctags 看源码:
ctags -R * //做源码数据库,生成tags文件(将该目录下的所有文件做成源码数据库,生成tags文件)
vim btn_drv.c //tags文件在哪个目录下,就需要在哪个目录进行打开文件操作
如何实现跳转(如何查看调用的另一个文件中的函数或者结构体、变量的实现):
1.光标移动到跳转的地方(函数的调用处)
2.ctrl + ] (右方括号)跳转
3.ctrl + t 返回
**************************************************************************************
linux内核混杂设备:
1.混杂设备:主设备号已经被内核进行分配,主设备号为10,通过次设备号区分的一类字符设备!
特点:
还是字符设备;
主设备号为10;
通过次设备号来区分设备个体;
2.linux内核如何描述混杂设备
struct miscdevice {
int minor;
const char *name;
const struct file_operations *fops;
};
minor:次设备号,用于区分各个混杂设备,如果让内核帮你分配一个次设备号,可以指定为MISC_DYNAMIC_MINOR;
name:设备文件名,混杂设备文件名内核会帮你创建;
fops:给混杂设备提供的访问硬件的方法,并且将这些方法提供用户使用;
3.如何实现一个混杂设备驱动
分配初始化一个混杂设备对象:
struct file_operations led_fops = {
...
};
struct miscdevice led_misc = {
.minor= MISC_DYNAMIC_MINOR, //动态分配
.name= "myled" , //dev/myled
.fops= &led_fops
};
注册混杂设备:misc_register(&led_misc);
卸载混杂设备:misc_deregister(&led_misic);
案例:利用混杂设备驱动来实现LED
GPIO操作方法:
1.利用GPIO库函数
2.ioremap寄存器,然后再操作寄存器
**********************************************************
模拟信号:声音,电压,电流,温度,湿度,压力,速度;
数字信号:0,1这些数字量组成的信号
数字系统不能直接处理模拟信号,需要将模拟信号转换成对应的数字信号;
不管是数字信号还是模拟信号都是描述的同一个实物!
信号之间的转换:
AD:模拟信号转数字信号过程;
DA:数字信号转成模拟信号过程;
ADC:将模拟信号转数字信号的硬件单元;
DAC:将数字信号转模拟信号的硬件单元;
案例:手机的录音和放音
录音:就是将声音模拟信号转成数字信号的过程;手机必须有ADC硬件单元;
放音:就是将声音的数字信号转成模拟信号的过程;手机必须有DAC硬件单元;
手机的ADC和DAC都是集成在声卡芯片(AUDIO CODEC)中.
S5PV210自带ADC的硬件特性,芯片手册1978页:
模拟输入通道有10路:AIN0~AIN9,同一时刻只能转换一路模拟信号;这10个IO不能复用,只能做INPUT;
工作频率:最大是5MHz,时钟源是PCLK=66MHz,注意需要做降频!
ADC转换器一旦开启信号的转换,转换过程需要时间,一旦转换结束,ADC硬件给CPU产生一个中断信号,通知CPU转换结束
衡量ADC的工作参数指标:分辨率,自带ADC的分辨率为10位或者12位;
如果分辨率采用10位,就是代表将模拟信号转成数字信号以后,有效的数字量的位数为10位;
如果分辨率采用12位,就是代表将模拟信号转成数字信号以后,有效的数字量的位数为12位;
模拟输入电压的范围为0~3.3V;
注意:最大的模拟输入电压为3.3V,
如果分辨率为10位,那么每一个bit位对应的模拟电压是:
3.3/(1 << 10) = 3.22mV
如果分辨率为12,那么每一个bit位对应的模拟电压是:
3.3/(1 << 12) = 0.81mV
问:
如果现在已知一个10bit的转换以后的数字量为0011000000,请问这个数字量对应的模拟量值为多少?
模拟量的值 =0011000000 * 3.32mV
ADC操作涉及的寄存器:
寄存器组的基地址:0xE1700000
ADC控制寄存器:
bit16->配置分辨率,0:10位,1:12位
bit15->判断是否转换结束,0:转换进行中,1:转换结束
结论:判断ADC转换结束的方法:中断,轮询查询bit15
bit14->使能分频,0:不使能,1:使能
bit6-bit13:设置分频系数,如果ADC的工作频率为3.3MHZ,这个寄存器的值为66/3.3 - 1=19
bit0:启动ADC转换,1:表示启动ADC
ADC数据寄存器:保存ADC的转换结果
bit0~bit11:共12bit,范围0~0xFFF
注意普通的ADC转换的有效值:
分辨率为10位:data = 数据寄存器的值&0x3ff;
分辨率为12位: data = 数据寄存器的值&0xfff;
ADC中断清除寄存器:清ADC的中断,中断处理函数中清
bit0:写0或者1清中断
写任何值都可清除中断!
ADC模拟信号通过选择寄存器:选择模拟输入通道
ADC操作步骤:
1.设置正常的工作频率,3.3MHz
2.设置分辨率,12位
3.设置模拟输入通道,AIN1
4.启动硬件ADC,硬件ADC开始对模拟电压信号进行转换
5.ADC转换结束产生中断信号
6.中断处理函数中清中断,唤醒休眠的进程
7.进程读取转换以后的数字量
软件实现:
1.用户需求
在QT图形界面上,每隔5秒数动态刷新显示电压值
在QT图形界面上,配置ADC的分辨率和模拟输入通道
2.驱动的设计:
1.对用户提供的接口:
read:读转换以后的电压值;
启动ADC
判断是否转换结束,如果没有,进入休眠
读取转换以后的数字量
上报到用户空间
ioctl:配置分辨率和模拟输入通道
2.采用混杂设备驱动实现方法+platform
3.寄存器地址要进行ioremap
4.注册中断处理函数:IRQ_ADC中断号
5.由于ADC的处理速度慢于CPU(应用程序的读取速度),所以在ADC没有转换结束时,进程进入休眠状态(等待队列机制)
6.在ADC操作之前,还要设置ADC的默认工作参数