linux 串行通信接口驱动框架
在学习linux内核驱动时,不论是看linux相关的书籍,又或者是直接看linux的源码,总是能在linux中看到各种各样的框架,linux内核极其庞杂,linux各种框架理解起来并不容易,如果直接硬着头皮死记硬背,意义也不大。
博主学习东西一直秉持着追本溯源的态度,要弄清一个东西是怎么样的,如果能够了解它的发展,了解它为什么会变成这样,理解起来就非常简单了。抓住主干,沿着线头就可以将整个框架慢慢梳理清楚。
从i2c开始
在嵌入式中,不管是单片机还是单板机,i2c作为板级通信协议是非常受欢迎的,尤其是在传感器的使用领域,其主从结构加上对硬件资源的低要求,稳稳地占据着主导地位。
我们就以i2c协议为例,聊一聊linux内核中串行通信接口框架。
(注:在这篇文章只讨论大致框架,并不涉及具体细节,linux内核驱动部分的框架分得很细,无法全部覆盖,但求建立一个大体的概念)
单片机中的i2c
每个MCU基本上都会集成硬件i2c控制器,在单片机编程中,对于操作硬件i2c控制器,我们只需要操作一些相应的寄存器即可实现数据的收发。
那如果没有硬件i2c控制器或者i2c控制器不够用呢?
事情也不麻烦,我们可以使用两个gpio来软件模拟i2c协议,代码不过几十行,虽然i2c协议本身有一定的复杂性,但是如果仅仅是实现通信,在单片机上还是非常简单的。
单片机中实现i2c程序
我们不妨回想一下,在单片机中编写一个i2c驱动程序的流程:
以sht31(这是一个常用的i2c接口的温湿度传感器)为例,刚入行的新手程序员可能这样写主机程序(伪代码):
int sht31_read_temprature(){ //读取温度值实现函数
设置i2c写寄存器,发送i2c器件地址
设置i2c写寄存器,发送i2c寄存器地址
设置i2c读
temperature = 读取目标器件发回的数据
return temperature;
}
int sht31_read_humidity(){ //读取湿度值实现函数
设置i2c写寄存器,发送i2c器件地址
设置i2c写寄存器,发送i2c寄存器地址
设置i2c读
humidity = 读取目标器件发回的数据
return humidity;
}
....
程序优化
每次读写函数都对硬件i2c的寄存器进行设置,很显然,这样的代码有很多重复的部分,我们可以将重复的读写部分提取出来作为公共函数,写成这样:
array sht31_read_data(sht31数据寄存器地址){
设置i2c写寄存器,发送i2c器件地址
设置i2c写寄存器,发送i2c寄存器地址
设置i2c读
return 读取目标器件发回的数据;
}
所以,上例中的读温湿度就可以写成这样:
array sht31_read_temprature(){
return sht31_read_data(sht31温度数据寄存器地址);
}
array sht31_read_humidity(){
return sht31_read_data(sht31湿度数据寄存器地址);
}
...
经过这一步优化,这个驱动程序就变成了两层:
- i2c硬件操作部分,比如i2c与设备的读写,在同一平台上,硬件读写的寄存器操作都是一致的。
- 设备的操作函数,不同的设备有不同的寄存器,对于存储设备而言就是存取数据,对于传感器而言就是读写传感器数据,需要读写设备时,直接调用第一步中的接口