linux驱动开发架构

驱动模型

最近开始开发驱动,现总结通用驱动开发模型如下
驱动整体模型:
在这里插入图片描述
添加一个设备,多数需要用户空间下发指令等操作。那么有两个问题:

  1. kernel如何控制设备
  2. 用户空间如何和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 的,在实际开发中个人也有实现,代码不在写该篇博客的电脑中,就借用了大神代码。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值