回顾:
1.linux内核I2C驱动实现
GPIO模拟I2C的时序来实现;
直接操作I2C控制器来实现;
采用linux内核的I2C驱动框架来实现;
linux内核I2C驱动框架:
应用层:open,read,write,ioctl访问I2C外设
eeprom.addr //外设的片内地址
eeprom.data //外设访问的数据
--------------------------------------------------
I2C设备驱动
i2c_client->i2c_board_info
i2c_driver
1.获取用户要访问的片内地址和数据
2.I2C设备驱动只关心设备操作的数据信息,不关心数据是如何通过硬件传输的;
3.利用内核提供的SMBUS接口将数据信息丢给底层的I2C总线驱动,由I2C总线驱动来实现I2C硬件传输;
________________________________________________
SMBUB接口:
本质:就是I2C设备驱动和I2C总线驱动的桥梁
目标:给I2C设备驱动使用!用于将I2C设备驱动获取的数据信息(设备地址,片内地址,数据)丢给I2C总线驱动
----------------------------------------------------------
I2C总线驱动
1.I2C总线驱动不关心数据的信息,只关注硬件的I2C总线数据的传输;
2.数据信息来自I2C设备驱动
3.操作的硬件是I2C控制器甚至用GPIO模拟的I2C控制器(i2c-gpio.c);
2.一线式总线,DS18B20
一根数据线;无时钟线和其他控制信号线
总线;
三线,二线;
上拉电阻;
一线式总线数据传输协议:以DS18B20数字温度传感器为例
1.DS18B20硬件特性;
2.操作步骤:三步骤
三步骤的操作关键涉及数据的传输问题,由于没有时钟线,只能在数据线上做文章(时序);
*********************************************************
linux老式字符设备驱动注册和卸载:
注册字符设备:
int register_chrdev(intmajor, char *name, struct file_operations *fops);
卸载字符设备:
intunregister_chrdev(int majro, char *name);
以上方法仅仅是注册字符设备和卸载字符设备,设备文件还需单独创建!
**********************************************************
linux内核input子系统:
管理的硬件设备对象:键盘,鼠标,触摸屏,按键,游戏摇杆,compass传感器,温度传感器等,只是前5个设备是标准的输入设备!
设备 有效数据信息(用户关心)
键盘,按键 键值和按键的状态(按下或者松开)
触摸屏 X,Y绝对坐标和触摸屏的状态(按下,松 开)
鼠标 X,Y相对坐标,左右两个按键值和按键的 状态,还有滚轮的方向和滚轮的按下,松 开
compass传感器 X,Y值
温度传感器 温度值
总结:有效的数据信息最终来自硬件设备本身!
linux内核input子系统的软件分层框架:
应用层:app1 app2
open,read,write,ioctl...
设备文件:由核心层创建
按键设备文件/dev/input/event0或者/dev/event0
触摸屏设备文件/dev/input/event1或者/dev/event1
鼠标的设备文件/dev/input/event2或者/dev/event2
...
------------------------------------------------
核心层:drivers/input/input.c
作用:它是应用层和底层硬件设备驱动的桥梁,可以做标准
用户访问设备不是直接,而是间接(由核心层做中转);
任务:
1.核心层要给应用层提供一个统一的访问硬件设备的操作接口,也就代表如果这个硬件按照input子系统的软件框架来实现,那么所有的应用程序访问这个设备,最终都会调用核心层的这个接口;
核心层提供的统一操作接口如下:
static conststruct file_operations evdev_fops = {
.owner =THIS_MODULE,
.read =evdev_read,//读设备
.write =evdev_write,//写设备
.poll =evdev_poll, //监听设备
.open =evdev_open,//打开设备
.release =evdev_release,//关闭设备
.unlocked_ioctl = evdev_ioctl, //控制设备
};
明确:所有的应用程序访问input子系统的设备,最终都会调用evdev_fops提供的函数!
2.
核心层给底层设备驱动提供相关函数和一个结构体用于将底层硬件设备硬件信息注册到核心层去,供核心层的操作接口去使用!
明确:这里说的“硬件信息”:是用户最终要关心的硬件信息,例如如果是按键,这个硬件信息是键值和按键的状态,如果是触摸屏,这个硬件信息是坐标和触摸屏的操作状态,不关心这个硬件信息来自于具体的硬件,不关心硬件如何连接和触发!底层硬件的具体操作例如申请GPIO,注册中断,中断处理函数等等都有驱动来实现。
结构体:
structinput_dev;
函数:
intinput_register_device(struct input_dev *dev);
intinput_event(struct input_dev *dev,
int type, int code, int value);
总结:底层设备驱动利用上面的结构体和函数就可以将硬件的有效数据信息注册到核心层;
----------------------------------------------------
设备驱动层:只关注硬件
1.关注实际的硬件信息:使用哪个GPIO,使用哪个中断,使用哪个物理地址等
2.GPIO申请,中断的申请,地址映射等
3.利用核心层提供的结构体和函数注册有效的数据信息到核心层
4.每当硬件上发生变化(产生中断),底层驱动获取有效的硬件信息,将有效的信息信息提交给核心层去
----------------------------------------------------
按键设备驱动 触摸屏设备驱动 鼠标设备驱动 ...
(input_dev) input_dev input_dev
总结:一个硬件设备对应一个struct input_dev,在用户空间也对应一个设备文件/dev/input/eventx(x=0,1,2,...)
问:如何利用内核提供的input子系统框架,实现一个input子系统的按键驱动?
答:关键底层驱动围绕着一个结构体和两个函数;
一个结构体:
struct input_dev{
const char*name;//指示当前硬件设备名称
//最终给用户上报的事件类型,间接告诉用户将来这个事件对应的有效的数据信息是什么(例如按键值或者坐标),注意在使用input_dev时,一定要将上报的事件类型进行初始化,给核心层使用,将来核心层会检查这个位图对应的位置是否进行被初始化。内核常用的事件有如下:
#define EV_KEY 0x01 //按键类事件
#define EV_REL 0x02//相对位移坐标事件
#define EV_ABS 0x03//绝对位移坐标事件
#define EV_MSC 0x04//混杂设备事件
#define EV_REP 0x14 //重复类事件
unsigned longevbit[BITS_TO_LONGS(EV_CNT)];
例如:set_bit(EV_KEY, evbit); //上报按键类事件
//按键类事件中的哪些按键值需要进行上报,如果要指定某个上报的按键,需要将按键对应的位图位置值1,将来核心层会检查
unsigned longkeybit[BITS_TO_LONGS(KEY_CNT)];
例如:set_bit(KEY_UP, keybit); //将来要上报KEY_UP键值
//绝对位移坐标事件中的哪些信息(坐标信息)需要上报,如果要上报某个坐标,需要将对应的位图位置置1,核心层会检查
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];
例如:set_bit(ABS_X, absbit); //将来要上报X坐标值
set_bit(ABS_Y,absbit); //将来要上报Y坐标值
};
结构体如何使用?
1.分配struct input_dev对象,代表一个硬件设备
struct input_dev *input =input_allocate_device();
2.初始化input_dev对象
input->name = "tarena"; //指定设备的名称
set_bit(EV_ABS, input->evbit);//将来会上报绝对位移坐标 事件
set_bit(ABS_X, input->absbit);//将来会上报X坐标值
set_bit(ABS_Y, input->absbit);//将来会上报Y坐标值
set_bit(ABS_PRESSURE, input->absbit);//将来上报压力 值
set_bit(EV_KEY, input->evbit);//将来上报触摸屏的操作状 态
set_bit(BTN_TOUCH,input->keybit);//触摸屏上报的键值
3.向核心层注册input_dev对象
input_register_device(input_dev);
注意再注意:
3.1一旦注册input_dev对象成功,核心层会为这个设备在用户空间创建一个对应的设备文件(/dev/input/eventx)。
3.2本质是向核心层注册硬件设备将来要上报的有效数据信息
4.卸载input_dev对象
input_unregister_device(input); //删除设备文件
5.释放input_dev对象
input_free_device(input);
6.如何将有效的数据信息上报给核心层呢?
利用input_event函数
intinput_event(struct input_dev *dev,
int type, int code, int value);
功能:一旦硬件设备驱动发现设备或者数据可用,底层驱动利用此函数可以将设备的有效数据信息上报给核心层。例如,如果设备驱动的中断处理函数被内核调用,表明硬件有操作,产生中断,此时即可上报数据信息;
参数:
dev:指向分配的input_dev对象
type:上报的事件类型,例如EV_KEY,EV_REP...
code:KEY_1,KEY_2,ABS_X,ABS_Y...
value:
如果是按键:value的值为:按下为1,松开为0
如果是坐标:坐标值
7.核心层获取底层驱动上报的数据信息以后,如何将数据信息递交给用户呢,这里用户和核心层使用同一个结构体来实现数据的传递:
structinput_event {
struct timeval time; //事件触发时间
__u16 type; //事件类型,EV_KEY,EV_ABS
__u16 code; //对应事件类型的位图位置,键值
__s32 value;//按键状态或者坐标值
};
用户使用结构体需包含头文件#include <linux/input.h>
实验步骤:
1.insmodbtn_drv.ko
2.cat/proc/interrupts //查看中断的注册信息
3.cat/proc/bus/input/devices //查看按键的设备文件
I: Bus=0000 Vendor=0000 Product=0000Version=0000
N:Name="tarena_button"
P: Phys=
S:Sysfs=/devices/virtual/input/input3
U: Uniq=
H: Handlers=kbdevent3 (最终设备文件为/dev/event3或者/dev/input/event3)
B: EV=100003
B: KEY=1680 0 00
4.读取按键信息(事件类型,键值,按键状态)
./btn_test/dev/event3 或者./btn_test/dev/input/event3
5.运行贪吃蛇或者俄罗斯方块游戏
PC编译:
/opt/project/qt/bin/qmake-project
/opt/project/qt/bin/qmake
make
添加QT对开发板按键的支持:
vim/opt/rootfs/etc/profile中添加:
exportQWS_KEYBOARD="TTY:/dev/event3" //注 意/dev/event3或者/dev/input/event3是按键的设备文件
重启开发板
运行游戏,通过上下左右来操作
//查看触摸屏的设备文件
hexdump/dev/input/event0开始一个个试,按屏幕看打印
别忘记修改profile文件指定触摸屏的设备文件
QT中文乱码解决方法:
vim main.cpp添加如下语句:
#include<QTextCodec>
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("gb2312"));
QTextCodec::setCodecForLocale(QTextCodec::codecForName("gb2312"));
********************************************************************
按键硬件设计:
独立式按键,例如100按键,需要100个IO
缺点:浪费IO资源,优点:软件操作简单
矩阵式按键,例如100按键,需要20个IO(10*10)
缺点:软件操作复杂,优点:省IO资源
按键去抖动:
产生的原因:按键本身就是机械结构
如何去抖动:
硬件去抖动
滤波电路,成本高
软件去抖动
抖动产生的触发沿下降沿和上升沿之间的时间间隔为一个经验值5~10ms
单片机:采用忙延时,for循环
linux内核:采用定时器实现(struct timer_list)