<>驱动模型

最近开始开发驱动,现总结通用驱动开发模型如下
驱动整体模型:

添加一个设备,多数需要用户空间下发指令等操作。那么有两个问题:

* kernel如何控制设备
* 用户空间如何和kernel中的驱动交互
问题1:
kernel中有各种总线,设备挂载在总线上,驱动通过kernel总线提供的接口初始化控制设备。
问题2:
kernel中提供文件设备驱动,在驱动中增加一个文件设备,如字符设备、proc、sys等文件设备。

基于以上两个问题,驱动包含两部分


<>开发设备驱动

<>系统端驱动开发步骤

1、阅读设备相关的规格书、demo
2、确定设备挂载总线、文件交互设备
3、参照demo,编写驱动代码,代码包含两部分:设备树添加结点、逻辑代码

注意: 设备树结点中的字段可以是标准的(内核已有代码解析),也可以包含自定义的(设备驱动逻辑代码解析)。

<>设备端基于单片机驱动开发

设备端硬件形态有两种:
1、设备端有一个独立的小系统,通过一个单片机运行
2、设备端仅由电子原件和电路图实现

形态1:
一般情况下,该部分代码,设备厂商已提供,无需系统端负责开发。系统端通过总线和设备进行交互。

形态2:
设备端上电后,驱动实现初始化,控制设备端寄存器,配置设备以满足对设备功能、数据的需求

<>应用场景


以上描述的驱动开发架构模式,常用于应用级驱动开发。一些控制器类型,如i2c控制器、spi总线控制器等kernel中框架层的,一般不需要文件系统设备的存在,只需要开发设备驱动即可;为debug方便,一般也会搭配文件系统设备,方便命令行查看设备状态。

在开发中,可以根据实际需求决定。

<>DEMO

<>需求描述

给一个i2c编写驱动,i2c提供一些接口给userspace使用

<>添加设备树结点
// SoC上的i2c控制器的地址 i2c@138B0000 { #address-cells = <1>; #size-cells = <0>;
samsung,i2c-sda-delay = <100>; samsung,i2c-max-bus-freq = <20000>; pinctrl-0
=<&i2c5_bus>; pinctrl-names="default"; //
这个一定要okay,其实是对"./arch/arm/boot/dts/exynos4.dtsi +387"处的status = "disabled"的重写,
// 相同的节点的不同属性信息都会被合并,相同节点的相同的属性会被重写 status="okay"; //
设备子节点,/表示板子,它的子节点node1表示SoC上的某个控制器, // 控制器中的子节点node2表示挂接在这个控制器上的设备(们)。68即是设备地址。
// 父结点是一个i2c总线,在此处定义i2c设备,设备i2c客户端自动和总线关联; // 否则,多个总线,设备i2c客户端驱动如何和总线关联?(待学习了解)
mpu6050@68{ // 这个属性就是我们和驱动匹配的钥匙,一个字符都不能错 compatible="invensense,mpu6050"; //
这个属性是从设备的地址,我们可以通过查阅手册"MPU-6050_DataSheet_V3_4"得到 reg=<0x68>; }; };
<>驱动实现
//mpu6050_common.h #define MPU6050_MAGIC 'K' union mpu6050_data { struct {
short x; short y; short z; }accel; struct { short x; short y; short z; }gyro;
unsigned short temp; }; #define GET_ACCEL _IOR(MPU6050_MAGIC, 0, union
mpu6050_data) #define GET_GYRO _IOR(MPU6050_MAGIC, 1, union mpu6050_data)
#define GET_TEMP _IOR(MPU6050_MAGIC, 2, union mpu6050_data) //mpu6050_drv.h
#define SMPLRT_DIV 0x19 //陀螺仪采样率,典型值:0x07(125Hz) #define CONFIG 0x1A
//低通滤波频率,典型值:0x06(5Hz) #define GYRO_CONFIG 0x1B
//陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s) #define ACCEL_CONFIG 0x1C
//加速计自检、测量范围及高通滤波,典型值:0x18(不自检,2G,5Hz) #define ACCEL_XOUT_H 0x3B #define
ACCEL_XOUT_L 0x3C #define ACCEL_YOUT_H 0x3D #define ACCEL_YOUT_L 0x3E #define
ACCEL_ZOUT_H 0x3F #define ACCEL_ZOUT_L 0x40 #define TEMP_OUT_H 0x41 #define
TEMP_OUT_L 0x42 #define GYRO_XOUT_H 0x43 #define GYRO_XOUT_L 0x44 #define
GYRO_YOUT_H 0x45 #define GYRO_YOUT_L 0x46 #define GYRO_ZOUT_H 0x47
//陀螺仪z轴角速度数据寄存器(高位) #define GYRO_ZOUT_L 0x48 //陀螺仪z轴角速度数据寄存器(低位) #define
PWR_MGMT_1 0x6B //电源管理,典型值:0x00(正常启用) #define WHO_AM_I 0x75
//IIC地址寄存器(默认数值0x68,只读) #define SlaveAddress 0x68 //MPU6050-I2C地址寄存器 #define
W_FLG 0 #define R_FLG 1 //mpu6050.c struct mpu6050_pri { struct cdev dev;
struct i2c_client *client; }; struct mpu6050_pri dev; static void
mpu6050_write_byte(struct i2c_client *client,const unsigned char reg,const
unsigned char val) { char txbuf[2] = {reg,val}; struct i2c_msg msg[2] = { [0] =
{ .addr = client->addr, .flags= W_FLG, .len = sizeof(txbuf), .buf = txbuf, },
}; i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); } static char
mpu6050_read_byte(struct i2c_client *client,const unsigned char reg) { char
txbuf[1] = {reg}; char rxbuf[1] = {0}; struct i2c_msg msg[2] = { [0] = { .addr
= client->addr, .flags = W_FLG, .len = sizeof(txbuf), .buf = txbuf, }, [1] = {
.addr = client->addr, .flags = I2C_M_RD, .len = sizeof(rxbuf), .buf = rxbuf, },
}; i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); return rxbuf[0]; }
static int dev_open(struct inode *ip, struct file *fp) { return 0; } static int
dev_release(struct inode *ip, struct file *fp) { return 0; } static long
dev_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) { int res = 0;
union mpu6050_data data = {{0}}; switch(cmd){ case GET_ACCEL: data.accel.x =
mpu6050_read_byte(dev.client,ACCEL_XOUT_L); data.accel.x|=
mpu6050_read_byte(dev.client,ACCEL_XOUT_H)<<8; data.accel.y =
mpu6050_read_byte(dev.client,ACCEL_YOUT_L); data.accel.y|=
mpu6050_read_byte(dev.client,ACCEL_YOUT_H)<<8; data.accel.z =
mpu6050_read_byte(dev.client,ACCEL_ZOUT_L); data.accel.z|=
mpu6050_read_byte(dev.client,ACCEL_ZOUT_H)<<8; break; case GET_GYRO:
data.gyro.x = mpu6050_read_byte(dev.client,GYRO_XOUT_L); data.gyro.x|=
mpu6050_read_byte(dev.client,GYRO_XOUT_H)<<8; data.gyro.y =
mpu6050_read_byte(dev.client,GYRO_YOUT_L); data.gyro.y|=
mpu6050_read_byte(dev.client,GYRO_YOUT_H)<<8; data.gyro.z =
mpu6050_read_byte(dev.client,GYRO_ZOUT_L); data.gyro.z|=
mpu6050_read_byte(dev.client,GYRO_ZOUT_H)<<8; printk("gyro:x %d, y:%d,
z:%d\n",data.gyro.x,data.gyro.y,data.gyro.z); break; case GET_TEMP: data.temp =
mpu6050_read_byte(dev.client,TEMP_OUT_L); data.temp|=
mpu6050_read_byte(dev.client,TEMP_OUT_H)<<8; printk("temp: %d\n",data.temp);
break; default: printk(KERN_INFO "invalid cmd"); break; } printk("acc:x %d,
y:%d, z:%d\n",data.accel.x,data.accel.y,data.accel.z); res = copy_to_user((void
*)arg,&data,sizeof(data)); return sizeof(data); } // 初始化文件系统设备操作接口 struct
file_operations fops = { .open = dev_open, .release = dev_release,
.unlocked_ioctl = dev_ioctl, }; #define DEV_CNT 1 #define DEV_MI 0 #define
DEV_MAME "mpu6050" struct class *cls; dev_t dev_no ; static void
mpu6050_init(struct i2c_client *client) { mpu6050_write_byte(client,
PWR_MGMT_1, 0x00); mpu6050_write_byte(client, SMPLRT_DIV, 0x07);
mpu6050_write_byte(client, CONFIG, 0x06); mpu6050_write_byte(client,
GYRO_CONFIG, 0x18); mpu6050_write_byte(client, ACCEL_CONFIG, 0x0); } static int
mpu6050_probe(struct i2c_client * client, const struct i2c_device_id * id) {
dev.client = client; printk(KERN_INFO "xj_match ok\n"); // 初始化设备文件系统
cdev_init(&dev.dev,&fops);
alloc_chrdev_region(&dev_no,DEV_MI,DEV_CNT,DEV_MAME);
cdev_add(&dev.dev,dev_no,DEV_CNT); // 设备初始化 mpu6050_init(client); /*自动创建设备文件*/
cls = class_create(THIS_MODULE,DEV_MAME);
device_create(cls,NULL,dev_no,NULL,"%s%d",DEV_MAME,DEV_MI); printk(KERN_INFO
"probe\n"); return 0; } static int mpu6050_remove(struct i2c_client * client) {
device_destroy(cls,dev_no); class_destroy(cls);
unregister_chrdev_region(dev_no,DEV_CNT); return 0; } struct of_device_id
mpu6050_dt_match[] = { {.compatible = "invensense,mpu6050"}, {}, }; //
设备驱动注册到总线 struct i2c_device_id mpu6050_dev_match[] = {}; struct i2c_driver
mpu6050_driver = { .probe = mpu6050_probe, .remove = mpu6050_remove, .driver =
{ .owner = THIS_MODULE, .name = "mpu6050drv", .of_match_table =
of_match_ptr(mpu6050_dt_match), }, .id_table = mpu6050_dev_match, };
module_i2c_driver(mpu6050_driver); MODULE_LICENSE("GPL");
在代码实现中把文件系统设备实现和设备驱动实现混合在一起,个人加以分开实现,利于代码重用和设备驱动替换和多方案切换。

<>验证

通过上面的驱动, 我们可以在应用层操作设备文件从mpu6050寄存器中读取原始数据, 应用层如下
int main(int argc, char * const argv[]) { int fd = open(argv[1],O_RDWR);
if(-1== fd){ perror("open"); return -1; } union mpu6050_data data = {{0}};
while(1){ ioctl(fd,GET_ACCEL,&data); printf("acc:x %d, y:%d,
z:%d\n",data.accel.x,data.accel.y,data.accel.z); ioctl(fd,GET_GYRO,&data);
printf("gyro:x %d, y:%d, z:%d\n",data.gyro.x,data.gyro.y,data.gyro.z);
ioctl(fd,GET_TEMP,&data); printf("temp: %d\n",data.temp); sleep(1); } return 0;
}
最终可以获取传感器的原始数据如下

说明: 以上demo是借鉴 https://www.cnblogs.com/xiaojiang1025/p/6500540.html
<https://www.cnblogs.com/xiaojiang1025/p/6500540.html>
的,在实际开发中个人也有实现,代码不在写该篇博客的电脑中,就借用了大神代码。

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信