第九讲 Linux I2C子系统及mma8653重力传感器驱动编写
时间:2022-11-10 04:30:01
2、I2C子系统
- 设备、总线、驱动模型:
subsystem | bus | dev | drv |
---|---|---|---|
platform | bus_type | platform_device | platform_driver |
I2C | bus_type | i2c_client | i2c_driver |
- IIC实现时序的三种方法:
办法 | 特点 | 优缺点 |
---|---|---|
gpio模拟I/O口 | 把它放在相应的时间节点上IO口拉高/低 | 思路清晰,操作麻烦 |
控制器 | 配置寄存器 | 操作麻烦,可移植性差 |
IIC子系统 | 内核密封上述方法,并将其安装成函数接口 | 操作简单,移植性好,初次接触难以理解 |
-
IIC子系统的组成
2.1、I2C总线(/driver/i2c/busses/i2c-nxp.c)
- 功能:
- 实现通信
- 实现设备与驱动的分离(使用platform注册一个系统i2c子实现)
2.2、I2C核心层(/driver/i2c/i2c-core.c)
子系统的核心是管理适配器、设备和驱动。主要实现device和driver注册、匹配、回调和注销。
-
注册了i2c总线,注册了dummy Driver
-
提供了adapter、driver、i2c_board_info等待注册/注销方法
-
提供通信方式(send/recv)
-
结构体 统一函数
-
i2c_transfer()
/* *传输数据 */ int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs,int num); struct i2c_msg{ __u16 addr; /* slave address */ __u16 flags; /*写是0,读是1*/ #define I2C_M_TEN 0x0010 /*a ten bit chip address*/ #define I2C_M_RD 0x0001 /* read data, from slave to master */ #define I2C_M_NOSTA 0x4000 #define I2C_M_REV_DIR_ADDR 0x2000 #define I2C_M_IGNORE_NAK 0x1000 #define I2C_M_NO_RD_ACK 0x0800 #define I2C_M_RECV_LEN 0x0400 /*length will be first received byte*/ __u16 len; /* msg length */ __u8 *buf; /* pointer to msg data */ };
-
利用i2c_transfer发送数据只需填充一个i2c_msg即可:
msg[0].addr = addr; /*器件地址*/ msg[0].flags = !I2C_M_RD; /*写标记*/ msg[0].len = count; /*buf大小*/ msg[0].buf = &data; /*一般有两个或多个字节组成,第一个是写的地址,后面是写的数据*/
-
利用i2c_transfer读取数据需填充两个i2c_msg:
msg[0].addr = chip_addr; /*器件地址*/ msg[0].flags = !I2C_M_RD; /*写标记*/ msg[0].len = count; /*buf大小*/ msg[0].buf = &addr; /*器件单元地址*/ msg[1].addr = chip_addr; /*器件地址*/ msg[1].flags = I2C_M_RD; /*读标记*/ msg[1].len = count; /*buf大小*/ msg[1].buf = &buf; /*读取到的数据*/
-
-
单独的读写函数:
int i2c_master_send(struct i2c_client *client, const char *buf, int count);
int i2c_master_recv(struct i2c_client *client, char *buf, int count);
-
smbus方式:
s32 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command)
s32 i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command, u8 value)
-
2.3、I2C适配器adapter
-
每个i2c控制器被抽象成了一个i2c_adapter,完成数据收发任务。
struct i2c_adapter{ struct module*owner; unsigned int class; /* classes to allow probing for */ const struct i2c_algorithm *algo; /*与总线通信相关的传输协议算法*/ void *algo_data; /* data fields that are valid for all devices */ struct rt_mutex bus_lock; int timeout; /* in jiffies */ int retries; struct device dev; /* the adapter device */ int nr; /* i2c端口号*/ char name[48]; struct completion dev_released; struct list_head userspace_client; };
-
传输协议算法结构体:具体设备通信就是用的i2c_algorithm指定的方法。
struct i2c_algorithm{ int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num); int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr, unsigned short flags,\ int size, union i2c_smbus_data *data); /*To determine what the adapter supports*/ u32 (*functionality)(struct i2c_adapter *); }
2.4、IIC驱动I2C_driver
- i2c总线匹配:设备树 > id_tables , 如果设备树和id表都没有提供的话,不会从device_driver中的name匹配
2.4.1、驱动端
-
完成i2c设备的控制功能,被抽象成i2c_driver,再将i2c设备注册成具体的字符、块或网络设备类型为应用提供服务。
-
数据结构体:
struct i2c_driver{
unsigned int class;
int (*attach_adapter)(struct i2c_adapter *);
int (*detach_adapter)(struct i2c_adapter *);
/* Standard driver model interfaces */
int(*probe)(struct i2c_client*, const struct i2c_device_id*);
int(*remove)(struct i2c_client*);
...
struct device_driver driver; //给name成员变量赋值或进行初始化
const struct i2c_device_id *id_table;
...
};
struct i2c_device_id{
//对于不使用设备树的驱动,必须用id_table匹配
char name[I2C_NAME_SIZE];
...
};
- 注册/注销方法:
/* 功能:把i2c_driver结构体加入内核 参数1:i2c_driver结构体指针 返回值:添加成功返回0,失败返回负值 */
int i2c_add_driver(srtuct i2c_driver *driver)
其中:
#define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE,driver)
/* 功能:把i2c_driver结构体从内核中删除 参数1:i2c_driver结构体指针 返回值:空 */
void i2c_del_driver(struct i2c_driver *driver);
2.4.2 驱动端操作流程
- 填充i2c_driver
- i2c_add_driver(相当于register驱动)
- 在probe中建立访问方式
2.5、I2C设备I2C_client
-
i2c设备被抽象成了一个i2c_client,用来描述一个具体的i2c设备,总线下有多少i2c设备就有多少个i2c_client与之对应。
-
数据结构:
struct i2c_client{
unsigned short flags; /* div., see below */
unsigned short addr; /*从机地址*/
char name[I2C_NAME_SIZE]; /*名字*/
struct i2c_adapter *adapter; /* the adapter we sit on */
struct i2c_driver *driver; /* 匹配后由内核赋值 */
struct device dev; /* the device structure */
int irq; /* irq issued by device */
...
};
/*创建i2c设备就是填充:addr、name、adapter这3项*/
提供的方法:
/* *功能:创建并注册一个i2c设备 *参数: *@adap i2c适配器结构体指针 *@info i2c板级信息的结构体指针(里面包含i2c设备地址,以及给这个设备起的名字) *返回值:成功返回i2c_client结构体指针,失败返回NULL */
struct i2c_client *i2c_new_device(struct i2c_adapter *adap,\
struct i2c_board_info const *info);
其中:
struct i2c_board_info{
char type[I2C_NAME_SIZE]; //名字要和驱动里面的id_table一致方可匹配
unsigned short flags;
unsigned short addr; //从机地址
void *platform_data;
struct dev_archdata *archdata;
struct device_node *of_node; //设备树节点
int irq;
};
/* *功能:获得i2c_adapter结构体 *参数: * @nr :第几个adapter(0-2) * 返回值:获得的i2c_adapter结构体指针 */
struct i2c_adapter *i2c_get_adapter(int nr); //nr硬件端口号
/* *功能:把i2c_client结构体从内核中删除 *参数: * @client :i2c_client 结构体指针 * 返回值:无 */
void i2c_unregister_device(struct i2c_client*client) //注销
2.5.1 设备端操作流程
- 在加载函数中:
- 根据设备使用的I2C端口号,获取适配器指针:i2c_get_adapter()
- 跟据I2C设备信息(地址和名字),创建i2c设备信息结构体:struct i2c_board_info
- 根据前面两个步骤得到的指针信息,创建并注册I2C设备(i2c_client):i2c_new_device(相当于register设备)
- 在卸载函数中
- 卸载设备时释放创建的I2C设备:i2c_unregister_device()
2.6、实例演示——重力传感器
-
项目介绍:
加速度传感器mma8653是一款由飞思卡尔研发的加速度传感器,能够提供x,y,z三个轴方向的加速度的数值。当前嵌入式领域中针对加速度和运动的采样和计算需求正在快速增加,小到计步器,大到无人驾驶和无人机,甚至航空航天都需要加速度传感器的应用,本项目将会引导大家进行一个加速度传感器驱动的编写,并使其符合内核的一些相关规范。本项目主要通过i2c子系统及input子系统完成驱动与硬件的命令和信息交互,掌握常见加速度传感器的工作原理。 -
芯片介绍:
**MMA8653:**自动唤醒/休眠模式的自动输出数据速率切换数据输出速率:1.5到800HZ加速度数据实时输出有两个可编程中断引脚可以进行自由落体或运动检测±2G,±4g和±8克动态可选的满量程范围。采用标准I2C通讯接口,从设备工作模式。时钟频率: <800KHZ,SDA保持时间: 0.05~0.90us,停止到启动的总线空闲时间: 1.3us。
-
电路连接图:
-
工作模式
- ACTIVE:指的是mma8653具有一个活跃模式,用于正常工作
- STANDBY:指mma8653具有一个待命模式,暂停采样,用于省电
两种模式可以通过CTRL_REG1寄存器(详见mma8653芯片手册)的最低位进行操作切换。
-
通信时序
-
对mma8653的连续写操作
-
对mma8653的连续读操作
读操作分为两个时序,分别是写时序和读时序,写时序最后不需要stop,紧接着直接发出读时序的start信号即可。
-
-
驱动框架
项目采用了linux内核的i2c子系统,mma8653芯片连接在s5p6818的i2c控制器2上,芯片地址为0b0011101,即0x1d。下图为触摸屏驱动框架:
-
i2c设备(i2c_client)代码
/*mma865x_dev.c*/ #include
#include #include #define I2C_HW_PORT (2) #define MMA865X_ADDR (0x1D) struct i2c_adapter *adp = NULL; struct i2c_client *mma865x_dev = NULL ; struct i2c_board_info mma865x_info = { .type = "bk2101-mma8653", .addr = MMA865X_ADDR, .flags = 0, }; static int __init mma865x_module_init(void) { int ret = 0; adp = i2c_get_adapter(I2C_HW_PORT); if(adp == NULL){ printk(KERN_ERR "i2c_get_adapter faile...\n"); ret = -ENXIO; goto err0; } mma865x_dev = i2c_new_device(adp, &mma865x_info); if(mma865x_dev == NULL){ printk(KERN_ERR "i2c_new_device faile...\n"); ret = -ENXIO; goto err0; } printk(KERN_DEBUG "[-%s-%s-%d-]\n",__FILE__,__func__,__LINE__); return 0; err0: return ret ; } static void __exit mma865x_module_exit(void) { i2c_unregister_device(mma865x_dev); printk(KERN_DEBUG "[-%s-%s-%d-]\n",__FILE__,__func__,__LINE__); } module_init(mma865x_module_init); module_exit(mma865x_module_exit); MODULE_LICENSE("GPL"); -
i2c驱动(i2c_driver)代码
/*mma865x_drv.c*/ #include
#include #include #include #include #define N 64 #define WHO_AM_I (0x0D) //#define TEN_BIT struct i2c_client *mma865x_client = NULL; static signed short x_acc = 0,y_acc = 0,z_acc = 0; void mma8653_write_reg(struct i2c_client *client,char reg_addr,char val) { char w_buf[] = { reg_addr,val}; struct i2c_msg mma_msg[] = { [0] = { .addr = client->addr, .flags= 0, .len = ARRAY_SIZE(w_buf), .buf = w_buf, }, }; i2c_transfer(client->adapter, mma_msg, ARRAY_SIZE(mma_msg)); } char mma8653_read_reg(struct i2c_client *client,char reg_addr) { char r_buf; struct i2c_msg mma_msg[] = { [0] = { .addr = client->addr, .flags= 0, .len = 1, .buf = ®_addr, }, [1] = { .addr = client->addr, .flags= I2C_M_RD, .len = 1, .buf = &r_buf, }, }; i2c_transfer(client->adapter, mma_msg, ARRAY_SIZE(mma_msg)); return r_buf; } void mma8653_read_xyz(struct i2c_client *client,char reg_addr,char r_buf[]) { struct i2c_msg mma_msg[] = { [0] = { .addr = client->addr, .flags= 0, .len = 1, .buf = ®_addr, }, [1] = { .addr = client->addr, .flags= I2C_M_RD, .len = 6, .buf = r_buf, }, }; i2c_transfer(client->adapter, mma_msg, ARRAY_SIZE(mma_msg)); } static int mma865x_open(struct inode *inode, struct file *filp) { printk(KERN_DEBUG "[-%s-%s-%d-]\n",__FILE__,__func__,__LINE__); return 0; } static long mma865x_ioctl(struct file *filp, unsigned int cmd, unsigned long args) { char mma_buf[6] = { 0}; #ifdef TEN_BIT mma8653_read_xyz(mma865x_client,0x01,mma_buf); x_acc = (((mma_buf[0]<<2)|(mma_buf[1]>>6))<<6)/(64); y_acc = (((mma_buf[2]<<2)|(mma_buf[3]>>6))<<6)/(64); z_acc = (((mma_buf[4]<<2)|(mma_buf[5]>>6))<<6)/(64); printk(KERN_INFO "x:%d,y:%d:z:%d\n",x_acc,y_acc,z_acc); #else printk(KERN_INFO "x:%hhd",mma8653_read_reg(mma865x_client,0x01)); printk(KERN_INFO "y:%hhd",mma8653_read_reg(mma865x_client,0x03)); printk(KERN_INFO "z:%hhd",mma8653_read_reg(mma865x_client,0x05)); printk(KERN_INFO "-----------------------------------------\n"); #endif printk(KERN_DEBUG "[-%s-%s-%d-]\n",__FILE__,__func__,__LINE__); return 0; } static int mma865x_release(struct inode *inode, struct file *filp) { printk(KERN_DEBUG "[-%s-%s-%d-]\n",__FILE__,__func__,__LINE__); return 0; } struct file_operations f_op = { .owner = THIS_MODULE, .open = mma865x_open, .unlocked_ioctl = mma865x_ioctl, .release = mma865x_release, }; struct miscdevice misc = { .minor = 255, .name = "bk2101_mma865x", .fops = &f_op, }; int mma865x_probe(struct i2c_client *client, const struct i2c_device_id *idt) { char mma_buf[6]; mma865x_client = client; #ifdef TEN_BIT mma8653_write_reg(client,0x2A,0x1); #else mma8653_write_reg(client,0x2A,0x3元器件数据手册、IC替代型号,打造电子元器件IC百科大全!