回顾:
1.select
目的:利用select/poll能够对多个设备进行同时的监听;
应用程序调用select/poll,对应底层驱动的接口都是同一个poll.
select系统调用实现过程:
1.应用程序调用select,首先调用C库的select
2.保存系统调用号,触发软中断
3.最终到达内核的sys_select
4.sys_select:
根据select指定的文件描述符,通过文件描述符获取对应的设备的file对象指针;
把被监听设备的驱动poll函数挨个调用一遍,如果设备都不可用,设备驱动的poll函数都返回0;
判断是否设备可用,是否超时,是否接收到信号;
如果都不成立,进程进入休眠状态;
一旦设备可用,必然产生中断,在中断处理函数中唤醒休眠的主进程,之前的休眠状态结束(poll_schedule_timeout函数返回);再次把设备驱动的poll函数挨个调用一遍,此时有一个设备的poll函数返回值应该不为0(设备可用);
返回用户空间;
5.底层驱动的poll主要完成等待队列实现进程休眠过程的一部分内容:
调用poll_wait将当前进程添加到驱动的等待队列头中
判断设备是否可用,如果不可用,返回0,如果可用返回非0
**********************************************************
2.混杂设备
主设备号为10;
通过次设备号分区;
字符设备;
struct miscdevice;
分配初始化混杂设备对象;
misc_register;
misc_deregster;
**********************************************************
3.ADC/DAC
ADC:模拟信号转数字信号的硬件;
DAC:数字信号转模拟信号的硬件;
衡量ADC、DAC的工作参数:分辨率
**********************************************************
I2C总线:
1.掌握I2C总线的硬件特性:
定义:两线式串行总线;
作用:用于连接CPU和外设,CPU和外设通过I2C总线进行数据的交互!
对象:CPU和外设,CPU由称master,外设由称slave;
"两线式":CPU和外设之间的通信接口需要2根信号线,一根是时钟控制信号线SCL,一根是数据传输信号线SDA;
SCL时钟控制信号只能由CPU发起,控制权交与CPU;
SDA数据信号线CPU和外设都能进行操作和控制!
"串行":CPU和外设之间传输数据是一个时钟周期传输一个bit位,如果CPU给外设写入0x55,需要8个时钟周期;
"一个时钟周期传输一个bit":CPU和外设之间传输一个bit位,必须要通过时钟控制信号线来实现双方的数据发和收!比如,CPU在时钟的高电平向数据线写入数据,设备在同周期的低电平从数据线上读取数据!
"总线":这两根信号线上可以挂接很多的外设,也可以挂接很多的CPU,一般总线只有一个CPU。如果有多个CPU,I2C总线具有仲裁机制来实现同步访问!
SCL和SDA分别会接一个上拉电阻,这两根信号线的默认状态为高电平状态!
一般如果CPU或者外设配置GPIO为输出口,就等于CPU或者外设控制了GPIO获取控制权;如果配置为输入口,就等于释放控制权!
MSB:高位
LSB:低位
问:CPU如果通过这两根线找到要访问的具体设备?
答:以上三个问题的答案在I2C总线协议中!
总线协议相关内容(看芯片手册):
1.START信号,起始信号:CPU如果要访问总线,必须CPU首先向总线上发送一个START起始信号;此信号由CPU发起;SCL为高电平,SDA由高电平向低电平跳变,产生START信号;
2.STOP信号,结束信号:CPU如何结束访问总线,CPU需要向总线发送一个STOP信号;此信号由CPU发起;SCL为高电平,SDA由低电平向高电平跳变,产生STOP信号;
3.设备地址:用于标识外设在I2C总线上的唯一性!同一个I2C总线上外设,每一个外设都有唯一的设备地址;如果CPU要访问某一个外设,CPU只需要在总线上发送这个外设的设备地址即可,发送完毕,如果外设存在于总线上,外设会给CPU一个反馈信号,就可以进行后续的数据访问;
外设的设备地址如何确定:一般有芯片厂家和原理图共同决定,
这里以三个外设为例,看看它们的设备地址分别是什么,电可擦除存储器AT24C02,温度传感器LM77,背光灯控制芯片ADP8860:
AT24C02,EEPROM的设备地址:
1010A2A1A0R/W:
1010:高4bit位,芯片厂家定义;
A2A1A0:原理图上A2A1A0都接GND=》A2A1A0=000
R:=1,表示CPU读取外设
W:=0,表示CPU写外设
通过以上信息,得到:
读设备地址:10100001=》0xA1
写设备地址:10100000=>0xA0
设备地址(7位,不算读写位,地址右移1位,高位补0)=》
最终设备地址=01010000=>0x50
LM77温度传感器设备地址:
10010A1A0:如果A1A0都接地:A1A0=00
读设备地址:10010001=>0x91
写设备地址:10010000=>0x90
设备地址:01001000=>0x48
ADP8860背光灯控制芯片设备地址:
0101010x:
读设备地址:01010101=>0x55
写设备地址:01010100=>0x54
设备地址:00101010=>0x2a
ACK信号:反馈应答信号,如果CPU发送完设备地址,并且外设进行响应,此时给CPU发送一个ACK应答信号,告诉CPU,外设存在于总线上;如果在数据读写过程中,也可以通过ACK信号指示数据的读写过程是否正常!有效的ACK信号为低电平(数据线),无效的ACK信号为高电平!
I2C数据传输,从高位开始发送,一次的数据传输为一字节!
I2C传输速度:100KHz,400KHz,3.4M
问:CPU如果找到了具体的某个外设,那么CPU和这个外设是如何通过这两根信号线完成数据的最终交互呢?
答:以三个芯片为例,了解掌握I2C的数据交互过程:
以温度传感器LM77为例:
1.CPU发送START信号;
2.CPU发送设备地址
3.CPU发送读写位
4.设备如果正常的存在于总线上,设备给CPU发送一个ACK信号
5.根据芯片手册进行数据的读写操作,其中会涉及到ACK信号,这个ACK信号和4步骤的ACK信号意义不太一样!
6.CPU发送STOP信号,接触此次的数据交互
ADP8860芯片的访问:
ADP8860芯片内部有一组寄存器,但是这些寄存器CPU不能像访问GPIO一样直接去访问寄存器地址,原因ADP8860并没有直接连接到CPU的4G地址空间中,需要间接得利用I2C总线访问芯片内部的寄存器地址;
例如现在把数据0xaa写入芯片内部寄存器0x55这个地址;
1.CPU发送START信号
2.CPU发送设备地址
3.CPU发送读写位,如果是写,这个bit为0
4.ADP8860如果正常存在于总线上,设备返回一个ACK信号给CPU,低电平有效
5.CPU发送访问的内部寄存器的地址0x55
6.设备获取CPU要访问的寄存器以后,设备给CPU发送一个ACK信号,告诉CPU可以继续访问;
7.CPU发送要写入的数据0xaa
8.设备将数据0xaa写入到内部寄存器0x55,然后设备给CPU一个ACK信号,告诉CPU写入成功;
9.CPU发送STOP信号,结束这次的寄存器的写入操作
电可擦除存储器EEPROM(at24c02)访问过程:
AT24C02的容量为256字节,地址编址:0~255
将数据0xaa写入到内部存储地址0x55地方去,CPU要访问内部地址的操作过程和ADP8860相似!
1.CPU发送START信号
2.CPU发送设备地址
3.CPU发送写:0
4.设备如果存在于总线上,那么设备给CPU发送一个ACK应答信号,告诉CPU,我在总线上!
4.CPU发送要操作的地址:0x55
5.设备接收到这个要操作的这个地址,设备给CPU发送一个ACK信号,告诉CPU,可以访问这个地址!
6.CPU发送要写入的数据0xaa
7.设备接收到这个要写入的数据,并且将数据写入到对应的地址0x55中,设备给CPU发送一个ACK信号,告诉CPU,写入成功!
8.CPU发送一个STOP信号,停止数据的此次访问!
随机读:就是读任何一个地址里的数据即可,例如读取0x55地址空间中的数据信息
问:SDA和SCL如何搭配使用?
答:
如果CPU或者设备要向数据线上写入数据,应该在SCL为低电平写入数据;如果CPU或者设备从数据线上获取数据,应该在同周期的高电平去读取数据!
总结:
I2C外设的具体如何操作,关键看芯片手册即可!
AT24C02的硬件特性:
EEPROM,电可擦除存储器;
容量:2Kbit,256字节;
传输速度:100KHz、400KHz
写周期:5ms
分页:1页为8字节,如果按页写,最多只能一次写8字节,如果写9字节,第9字节的数据会把第一字节的数据进行覆盖;
地址编址:0~255
设备地址:0x50
用户需求:
存储软件和硬件版本号到EEPROM中:
软件版本号:SYYMMDDXY->S14101700
硬件版本号:HYYMMDDXY->H14101700
EEPROM存储器地址规划:
软件版本号的地址范围:0x0~0x9
硬件版本号的地址范围:0x10~0x19
测试:
./at24c02_test w 0x55 0xaa //将数据0xaa写入地址0x55
./at24c02_test r 0x55 //读0x55地址中的数据
驱动设计:
1.采用GPIO模拟I2C时序来实现I2C总线硬件操作!
2.采用操作I2C控制器来实现I2C总线硬件操作!
3.采用linux内核I2C驱动框架来实现I2C硬件操作!
采用方法1来实现驱动:参考代码gpio_emu_i2c
GPIO模拟I2C时序的本质的就是利用软件操作GPIO的状态,来实现I2C总线的时序要求!
SDA->GPD1_0
SCL->GPD1_1