# IMX-Card
**Repository Path**: llolle/imx-card
## Basic Information
- **Project Name**: IMX-Card
- **Description**: A Computer Card Based On I.MX6ULL
- **Primary Language**: C
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2023-08-01
- **Last Updated**: 2023-08-01
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# IMX-Card
IMX-Card是一款基于I.MX6ULL开发板开发的运动传感卡片电脑,该项目从NXP的U-Boot和Linux内核改动并移植过来,配合mpu6050的空间运动信号采集以获取算出运动物体的姿势
## 涉及技术
uboot移植、kernel移植、rootfs移植、IIC、Linux驱动框架
## IIC驱动框架
**iic物理总线**
- SCL:时钟线,数据收发同步
- SDA:数据线,具体数据
支持一主多从,各设备地址独立,标准模式传输速率为100kbit/s,快速模式为400kbit/s
**常见iic设备**
- eeprom
- 触摸芯片
- 温湿度传感器
- 姿态传感器mpu6050
...
**框架图**
在应用程序里面想要控制i2c物理设备本质上也是通过字符设备驱动来进行控制的,这里的i2c总线是i2c驱动总线,也是将i2c设备(i2c_client)和i2c驱动(i2c_driver)剥离开,往linux里面注册设备或者注册驱动时就会根据i2c总线的匹配规则来进行配对,如果成功配对i2c驱动就会执行里面的probe函数,然后在probe函数里面创建字符设备驱动,这和platform平台总线是相类似的。在现在的Linux里面i2c设备不需要自己手动注册,设备树机制和platform相配合,对于设备树里面设备节点会被解析成platform总线里面平台设备,进一步将平台设备转化成i2c设备,然后注册到i2c总线上
设备驱动创建成功,我们还需要实现设备的文件操作接口 (file_operations),file_operations 中会使用到内核中 i2c 核心函数 (i2c 系统已经实现的函数,专门开放给驱动工程师使用)。使用这些函数会涉及到 i2c 适配器,也就是 i2c 控制器。由于 ic2 控制器有不同的配置,所有 linux 将每一个 i2c控制器抽象成 i2c 适配器对象。这个对象中存在一个很重要的成员变量——Algorithm,Algorithm中存在一系列函数指针,这些函数指针指向真正硬件操作代码。
- I2C核心
提供I2C总线驱动和设备驱动的注册方法、注销方法、I2C通信硬件无关代码
- I2C 总线驱动
主要包含I2C硬件体系结构中适配器(iic控制器)的控制,用于I2C 读写时序
主要数据结构:I2C_adapter、i2c_algorithm
- I2C设备驱动
通过I2C适配器与CPU交换数据
主要数据结构:i2c_driver和i2c_client
**核心数据结构**
**i2c_adapter**
i2c_ 适配器对应一个 i2c 控制器,是用于标识物理 i2c 总线以及访问它所需的访问算法的结构
include/linux/i2c.h
```c
struct i2c_adapter {
struct module *owner;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;
...
};
```
- 对应一个IIC控制器
- 相关API
- int i2c_add_adapter(struct i2c_adapter *adapter)
注册一个i2c_adapter ,系统分配编号
- int i2c_add_numbered_adapter(struct i2c_adapter *adapter)
注册一个i2c_adapter ,自己指定编号
- void i2c_del_adapter(struct i2c_adapter * adap)
注销一个i2c_adapter
**i2c_algorithm**
include/linux/i2c.h
```c
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, char read_write,
u8 command, int size, union i2c_smbus_data *data);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);
#if IS_ENABLED(CONFIG_I2C_SLAVE)
int (*reg_slave)(struct i2c_client *client);
int (*unreg_slave)(struct i2c_client *client);
#endif
};
```
- 对应一套具体的通信方法
- master_xfer:产生I2C通信时序
**struct i2c_client**
include/linux/i2c.h
```c
struct i2c_client {
unsigned short flags; /* div., see below */
unsigned short addr; /* chip address - NOTE: 7bit */
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter; /* the adapter we sit on */
//继承了device结构体,因为所有设备都要符合Linux设备驱动模型
struct device dev; /* the device structure */
int init_irq; /* irq set at initialization */
int irq; /* irq issued by device */
struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
i2c_slave_cb_t slave_cb; /* callback for slave mode */
#endif
};
```
- addr:i2c设备地址
**struct i2c_driver**
一个i2c驱动可以供多个i2c设备来使用,struct i2c_driver由自己构建
include/linux/i2c.h
```c
struct i2c_driver {
unsigned int class;
/* Standard driver model interfaces */
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
...
struct device_driver driver;
const struct i2c_device_id *id_table;
...
}
```
- id_table:i2c总线传统匹配方式,没有设备树时i2c总线就是根据id_table来进行i2c设备和i2c驱动的匹配
- probe:i2c设备和i2c驱动匹配后,回调该函数指针
- 相关API
- int i2c_add_driver (struct i2c_driver *driver)
注册一个i2c_driver
- void i2c_del_driver(struct i2c_driver *driver)
注销一个i2c_driver
**I2C 总线驱动分析**
**i2c总线注册**
drivers/i2c/i2c-core-base.c
```c
static int __init i2c_init(void)
{
int retval;
...
retval = bus_register(&i2c_bus_type);
if (retval)
return retval;
is_registered = true;
...
retval = i2c_add_driver(&dummy_driver);
if (retval)
goto class_err;
if (IS_ENABLED(CONFIG_OF_DYNAMIC))
WARN_ON(of_reconfig_notifier_register(&i2c_of_notifier));
if (IS_ENABLED(CONFIG_ACPI))
WARN_ON(acpi_reconfig_notifier_register(&i2c_acpi_notifier));
return 0;
...
}
```
**i2c总线定义**
```c
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
```
**i2c设备和i2c驱动匹配规则**
```c
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
/* Attempt an OF style match */
if (i2c_of_match_device(drv->of_match_table, client))
return 1;
/* Then ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
driver = to_i2c_driver(drv);
/* Finally an I2C match */
if (i2c_match_id(driver->id_table, client))
return 1;
return 0;
}
```
- of_driver_match_device:设备树匹配方式
- 比较 I2C 设备节点的 compatible 属性和 of_device_id 中的 compatible 属性,相等说明匹配,就会回调执行i2c_driver里面的probe函数
- acpi_driver_match_device : ACPI 匹配方式
- i2c_match_id:i2c总线传统匹配方式(没有设备树时匹配)
- 比较 I2C设备名字和 i2c驱动的id_table->name 字段是否相等,相等说明匹配
**设备树节点**
arch/arm/boot/dts/imx6ull.dtsi
```c
i2c1: i2c@21a0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
reg = <0x21a0000 0x4000>;
interrupts = ;
clocks = <&clks IMX6UL_CLK_I2C1>;
status = "disabled";
};
```
**i2c_imx_probe()函数**
drivers/i2c/busses/i2c-imx.c
```c
static int i2c_imx_probe(struct platform_device *pdev)
{
const struct of_device_id *of_id = of_match_device(i2c_imx_dt_ids,
&pdev->dev);
struct imx_i2c_struct *i2c_imx;
struct resource *res;
struct imxi2c_platform_data *pdata = dev_get_platdata(&pdev->dev);
void __iomem *base;
int irq, ret;
dma_addr_t phy_addr;
...
//获取适配器设备节点里面的register属性值
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
//寄存器地址的物理地址映射到虚拟地址
base = devm_ioremap_resource(&pdev->dev, res);
phy_addr = (dma_addr_t)res->start;
i2c_imx = devm_kzalloc(&pdev->dev, sizeof(*i2c_imx), GFP_KERNEL);
...
i2c_imx->adapter.algo = &i2c_imx_algo;
...
i2c_imx->base = base;
...
ret = i2c_add_numbered_adapter(&i2c_imx->adapter);
...
}
```
**i2c_imx_algo结构体变量**
drivers/i2c/busses/i2c-imx.c
```c
static const struct i2c_algorithm i2c_imx_algo = {
.master_xfer = i2c_imx_xfer,
.functionality = i2c_imx_func,
};
```
- i2c_imx_xfer:iic通信函数,负责通讯时数据收发
- i2c_imx_func:查询iic通信协议类型
**i2c_imx_func()函数**
drivers/i2c/busses/i2c-imx.c
```c
static u32 i2c_imx_func(struct i2c_adapter *adapter)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL
| I2C_FUNC_SMBUS_READ_BLOCK_DATA;
}
```
**i2c_imx_xfer()函数**
drivers/i2c/busses/i2c-imx.c
```c
static int i2c_imx_xfer(struct i2c_adapter *adapter,
struct i2c_msg *msgs, int num)
{
unsigned int i, temp;
int result;
bool is_lastmsg = false;
bool enable_runtime_pm = false;
struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter);
...
//让i2c控制器发出i2c的启动信号
result = i2c_imx_start(i2c_imx);
...
//发送数据
for (i = 0; i < num; i++) {
if (i == num - 1)
is_lastmsg = true;
if (i) {
dev_dbg(&i2c_imx->adapter.dev,
"<%s> repeated start\n", __func__);
temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
temp |= I2CR_RSTA;
imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
//判断i2c是否忙碌
result = i2c_imx_bus_busy(i2c_imx, 1);
if (result)
//忙碌跳转fail0
goto fail0;
}
...
//判断当前信息是要求读还是写
if (msgs[i].flags & I2C_M_RD)
result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg);
else {
//要求写时
if (i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD)
result = i2c_imx_dma_write(i2c_imx, &msgs[i]);
else
result = i2c_imx_write(i2c_imx, &msgs[i]);
}
if (result)
goto fail0;
}
fail0:
/* Stop I2C transfer */
//让i2c控制器发送停止信号
i2c_imx_stop(i2c_imx);
...
}
```
## IIC框架使用的“万能”驱动
**i2c_add_adapter()函数**
drivers/i2c/i2c-core-base.c
注册一个i2c适配器
```c
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adapter)
```
- i2c_add_adapter是系统自动帮助设置设备器编号
- i2c_add_numbered_adapter自己设置适配器编号
adapter->nr:适配器的编号
参数:
- adapter:i2c物理控制器对应的适配器
返回值:
- 成功:0
- 失败:负数
**i2c_add_driver()宏**
include/linux/i2c.h
注册一个i2c驱动
```c
#define i2c_add_driver(driver) \ i2c_register_driver(THIS_MODULE, driver)
```
**i2c_register_driver()函数**
drivers/i2c/i2c-core-base.c
注册一个i2c驱动
````c
int i2c_register_driver(struct module *owner,
struct i2c_driver *driver)
````
参数:
- owner: :一般为 THIS_MODULE
- driver:要注册的 i2c_driver.
返回值:
- 成功:0
- 失败:负数
**i2c_transfer()函数**
该函数核心是adap->algo->master_xfer(adap,msgs,num),也就是适配器中具体的通信方法
drivers/i2c/i2c-core-base.c
收发iic消息
```c
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
```
参数:
- adap :所使用的 I2C 适配器,i2c_client 会保存其对应的 i2c_adapter
- msgs:I2C 要发送的一个或多个消息
- num :消息数量,也就是 msgs 的数量
返回值:
- 成功:发送的msgs 的数量
- 失败:负数
**i2c_msg结构体**
include/uapi/linux/i2c.h
描述一个iic消息
```c
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
#define I2C_M_RD 0x0001 /* read data, from slave to master */
/* I2C_M_RD is guaranteed to be 0x0001! */
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
#define I2C_M_DMA_SAFE 0x0200 /* the buffer of this message is DMA safe */
/* makes only sense in kernelspace */
/* userspace buffers are copied anyway */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
#define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};
```
- addr:iic设备地址
- flags:消息传输方向和特性。
- I2C_M_RD:表示读取消息
- 0:表示发送消息
- len:消息数据的长度
- buf:消息缓冲区
**i2c_master_send()函数**
include/linux/i2c.h
封装了i2c_transfer_buffer_flags函数,发送一个i2c消息
```c
static inline int i2c_master_send(const struct i2c_client *client,
const char *buf, int count)
{
return i2c_transfer_buffer_flags(client, (char *)buf, count, 0);
};
```
**i2c_master_recv()函数**
封装了i2c_transfer_buffer_flags函数,接收一个i2c消息
include/linux/i2c.h
```c
static inline int i2c_master_recv(const struct i2c_client *client,
char *buf, int count)
{
return i2c_transfer_buffer_flags(client, buf, count, I2C_M_RD);
};
```
**i2c_transfer_buffer_flags()函数**
drivers/i2c/i2c-core-base.c
发送一个i2c消息
```c
int i2c_transfer_buffer_flags(const struct i2c_client *client, char *buf,
int count, u16 flags)
{
int ret;
struct i2c_msg msg = {
.addr = client->addr,
.flags = flags | (client->flags & I2C_M_TEN),
.len = count,
.buf = buf,
};
ret = i2c_transfer(client->adapter, &msg, 1);
/*
* If everything went ok (i.e. 1 msg transferred), return #bytes
* transferred, else error code.
*/
return (ret == 1) ? count : ret;
}
```
**"万能"的i2c驱动--i2c-dev.c分析**
drivers/i2c/i2c-dev.c
- 内核集成i2c_dev驱动模块,开机自动加载
- 为每个i2c_adapter生成一个设备文件,通过该设备文件间接使用IIC核心函数收发数据
- 注册i2c总线的通知函数,解决加载顺序问题(需要先加载适配器,然后才加载万能i2c驱动)。在万能i2c驱动里面会给总线注册一个通知函数,有通知函数到时候注册i2c适配器时通知函数就会回调被执行,然后在通知函数里面为i2c适配器生成设备文件
**i2c_dev_init()函数**
万能i2c驱动的入口函数
drivers/i2c/i2c-dev.c
```c
static int __init i2c_dev_init(void)
{
int res;
printk(KERN_INFO "i2c /dev entries driver\n");
//申请设备号
res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c");
if (res)
goto out;
//创建i2c-dev的类,到时候在sys下面可以看到i2c-dev的类
i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
if (IS_ERR(i2c_dev_class)) {
res = PTR_ERR(i2c_dev_class);
goto out_unreg_chrdev;
}
...
//注册通知函数,i2c_bus_type是i2c的总线
res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
if (res)
goto out_unreg_class;
/* Bind to already existing adapters right away */
i2c_for_each_dev(NULL, i2cdev_attach_adapter);
return 0;
out_unreg_class:
class_destroy(i2c_dev_class);
out_unreg_chrdev:
unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS);
out:
printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
return res;
}
```
**i2cdev_notifier定义**
```c
static struct notifier_block i2cdev_notifier = {
.notifier_call = i2cdev_notifier_call,
};
```
**i2cdev_notifier_call()函数**
```c
static int i2cdev_notifier_call(struct notifier_block *nb, unsigned long action,
void *data)
{
struct device *dev = data;
switch (action) {
//添加设备事件
case BUS_NOTIFY_ADD_DEVICE:
return i2cdev_attach_adapter(dev, NULL);
//删除设备事件
case BUS_NOTIFY_DEL_DEVICE:
return i2cdev_detach_adapter(dev, NULL);
}
return 0;
}
```
**I2C_MAJOR**
include/linux/i2c-dev.h
```c
#define I2C_MAJOR 89 /* Device major number */
```
**I2C_MINORS**
drivers/i2c/i2c-dev.c
```c
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define I2C_MINORS MINORMASK
```
**i2c_for_each_dev()函数**
drivers/i2c/i2c-core-base.c
```c
int i2c_for_each_dev(void *data, int (*fn)(struct device *, void *))
{
int res;
mutex_lock(&core_lock);
res = bus_for_each_dev(&i2c_bus_type, NULL, data, fn);
mutex_unlock(&core_lock);
return res;
}
```
**bus_for_each_dev()函数**
drivers/base/bus.c
```c
int bus_for_each_dev(struct bus_type *bus, struct device *start,
void *data, int (*fn)(struct device *, void *))
{
struct klist_iter i;
struct device *dev;
int error = 0;
if (!bus || !bus->p)
return -EINVAL;
klist_iter_init_node(&bus->p->klist_devices, &i,
(start ? &start->p->knode_bus : NULL));
while (!error && (dev = next_device(&i)))
error = fn(dev, data);
klist_iter_exit(&i);
return error;
}
```
**i2cdev_attach_adapter()函数**
drivers/i2c/i2c-dev.c
```c
static int i2cdev_attach_adapter(struct device *dev, void *dummy)
{
struct i2c_adapter *adap;
struct i2c_dev *i2c_dev;
int res;
if (dev->type != &i2c_adapter_type)
return 0;
adap = to_i2c_adapter(dev);
i2c_dev = get_free_i2c_dev(adap);
if (IS_ERR(i2c_dev))
return PTR_ERR(i2c_dev);
cdev_init(&i2c_dev->cdev, &i2cdev_fops);
i2c_dev->cdev.owner = THIS_MODULE;
res = cdev_add(&i2c_dev->cdev, MKDEV(I2C_MAJOR, adap->nr), 1);
if (res)
goto error_cdev;
/* register this i2c device with the driver core */
i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
MKDEV(I2C_MAJOR, adap->nr), NULL,
"i2c-%d", adap->nr);
if (IS_ERR(i2c_dev->dev)) {
res = PTR_ERR(i2c_dev->dev);
goto error;
}
pr_debug("i2c-dev: adapter [%s] registered as minor %d\n",
adap->name, adap->nr);
return 0;
error:
cdev_del(&i2c_dev->cdev);
error_cdev:
put_i2c_dev(i2c_dev);
return res;
}
```
**i2cdev_fops定义**
drivers/i2c/i2c-dev.c
```c
static const struct file_operations i2cdev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = i2cdev_read,
.write = i2cdev_write,
.unlocked_ioctl = i2cdev_ioctl,
.compat_ioctl = compat_i2cdev_ioctl,
.open = i2cdev_open,
.release = i2cdev_release,
};
```
- 在用户空间使用read和write函数一次只能发送接收一条消息,如果要同时发送接收多条i2c消息,这时候使用i2cdev_fops里面的i2cdev_ioctl接口
**i2cdev_open()函数**
drivers/i2c/i2c-dev.c
```c
static int i2cdev_open(struct inode *inode, struct file *file)
{
unsigned int minor = iminor(inode);
struct i2c_client *client;
struct i2c_adapter *adap;
adap = i2c_get_adapter(minor);
if (!adap)
return -ENODEV;
client = kzalloc(sizeof(*client), GFP_KERNEL);
if (!client) {
i2c_put_adapter(adap);
return -ENOMEM;
}
snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);
client->adapter = adap;
file->private_data = client;
return 0;
}
```
**i2cdev_read()函数**
drivers/i2c/i2c-dev.c
```c
static ssize_t i2cdev_read(struct file *file, char __user *buf, size_t count,
loff_t *offset)
{
char *tmp;
int ret;
struct i2c_client *client = file->private_data;
if (count > 8192)
count = 8192;
tmp = kmalloc(count, GFP_KERNEL);
if (tmp == NULL)
return -ENOMEM;
pr_debug("i2c-dev: i2c-%d reading %zu bytes.\n",
iminor(file_inode(file)), count);
ret = i2c_master_recv(client, tmp, count);
if (ret >= 0)
ret = copy_to_user(buf, tmp, count) ? -EFAULT : ret;
kfree(tmp);
return ret;
}
```
**i2cdev_write()函数**
drivers/i2c/i2c-dev.c
```c
static ssize_t i2cdev_write(struct file *file, const char __user *buf,
size_t count, loff_t *offset)
{
int ret;
char *tmp;
struct i2c_client *client = file->private_data;
if (count > 8192)
count = 8192;
tmp = memdup_user(buf, count);
if (IS_ERR(tmp))
return PTR_ERR(tmp);
pr_debug("i2c-dev: i2c-%d writing %zu bytes.\n",
iminor(file_inode(file)), count);
ret = i2c_master_send(client, tmp, count);
kfree(tmp);
return ret;
}
```
## MPU6050
空间运动传感器芯片,可以绑定在一些运动的物体上,根据芯片反馈回来的数据可以算出运动物体的姿势
- 3轴加速度:xyz轴加速度值
- 3轴角速度:绕着xyz轴旋转的角速度
**硬件原理图**
- 图上的i2c地址是0xD0,用二进制表示为b1101 0000,MPU6050 的 slave 七位字长,所以右移一位
- 查看 MPU 芯片手册我们可以知道,MPU6050 的 slave 地址为 b110100X,七位字长,最低有效位 X 由 AD0 管脚上的逻辑电平决定(AD0是8号引脚接地)。从原理图上可以看到,AD0 接地,则地址为 b1101000,也就是 0x68。
## IIC驱动读取mpu6050数据
控制芯片需要先初始化UART4_RXD和UART4_RXD引脚,并且初始化时需要将这两个引脚复用为i2c控制器引脚,可以直接利用pinctrl子系统帮助初始化引脚
**步骤**
1、添加设备树节点和iomuxc的子节点
**设备树节点**
在iomux节点下添加新的引脚组:
**iomuxc子节点**
```
pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
>;
};
```
- 通过fsl,pins属性声明了引脚组,在引脚组里有两个引脚,分别是开发板上的MX6UL_PAD_UART4_TX_DATA和MX6UL_PAD_UART4_RX_DATA,也就是对应原理图上的UART4_RXD和UART4_RXD引脚,然后用pinctrl子系统来把这两个引脚复用为i2c1控制器的相关引脚(所以写MX6UL_PAD_UART4_TX_DATA__I2C1_SCL),最后一个值(0x4001b8b0)是用来设置引脚io属性
fsl,pins属性如何设置的:
1. 宏定义先查原理图提取需要的引脚的名字UART4_RXD和UART4_RXD,然后再./arch/arm/boot/dts/imx6ul-pinfunc.h文件(ctrl+p)中找到对应的宏定义(ctrl+f搜索UART4_RX)复用为I2C
1. 最后的值直接参照官方的设置,设备树中其他的 pinctrl 子节点的配置就是很好的一个参考,如果有需要再参考 IM6ULL 用户手册对个别参数进行修改。这里设备树中没有其他i2c设置的引脚属性值,只能查用户手册,配置为0x4001b8b0
初始化UART4_RXD和UART4_RXD引脚后需要对i2c控制器进行初始化,因为UART4_RXD和UART4_RXD引脚i2c通讯时序是依赖开发板的i2c控制器来产生的,所以在设备树上也需要设置i2c控制器相关的属性,也就是在设备树下添加新节点:
**i2c1子节点**
```
&i2c1{
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
i2c_mpu6050@68 {
compatible = "fire,i2c_mpu6050";
reg = <0x68>;
status = "okay";
};
};
```
- clock-frequency:设置i2c控制器时钟频率,这里为100k
- pinctrl-names:设备节点的使用引脚的状态,默认
- pinctrl-0:默认状态下引脚组设置为pinctrl_i2c1,也就是iomuxc子节点新定义的节点引脚组
- status:设置i2c1节点状态是ok状态代表想要马上使用i2c控制器
- i2c_mpu6050:i2c1节点下创建了i2c_mpu6050子节点,compatible属性设置为fire,i2c_mpu6050,reg属性和status属性都被设置。
对于i2c1节点下面的子节点i2c_mpu6050等Linux系统加载设备树之后就会被解析成i2c_client结构体,也就是i2c设备。在上一节i2c_client是我们自己来动态申请内存生成的,现在有设备树之后,不希望手动生成i2c_client结构体,这样只需要在i2c1节点下面定义子节点,到时候这些子节点就会被自动转化为i2c_client结构体,compatible属性值就会变成i2c_client结构体里name的值即i2c设备的名字,reg属性值会被转化为i2c设备的设备地址,status属性是给i2c驱动使用的,设置为ok即可,写i2c驱动就根据status的状态值来决定i2c是否正常工作。
i2c设备地址为什么是0x68?在MPU-6000.6050中文资料中slave地址为b110100x
在设备树中加入节点而不使用设备树插件,因为Linux里面的pinctrl子系统对设备树插件的支持不够完善。设备树文件中追加i2c1子节点,iomuxc子节点中加入pinctrl_i2c1。
2、编写驱动程序
1. 入口函数中调用i2c_add_driver来给Linux系统注册i2c驱动
```c
static int __init mpu6050_driver_init(void)
{
int ret;
pr_info("mpu6050_driver_init\n");
ret = i2c_add_driver(&mpu6050_driver);
return ret;
}
```
2. 定义i2c总线设备结构体
```c
/*定义i2c总线设备结构体*/
struct i2c_driver mpu6050_driver = {
.probe = mpu6050_probe,
.remove = mpu6050_remove,
.id_table = gtp_device_id,
.driver = {
.name = "fire,i2c_mpu6050",
.owner = THIS_MODULE,
.of_match_table = mpu6050_of_match_table,
},
};
```
- 这个i2c驱动名字是fire,i2c_mpu6050,和设备树节点里面i2c_mpu6050的compatible属性的值相等的,i2c_mpu6050子节点转化成的i2c_client结构体是和i2c_driver结构体进行匹配的,配对成功就会执行probe函数指针指向的mpu6050_probe函数
```c
/*----------------平台驱动函数集-----------------*/
static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = -1; //保存错误状态码
printk(KERN_EMERG "\t match successed \n");
/*---------------------注册 字符设备部分-----------------*/
//采用动态分配的方式,获取设备编号,次设备号为0,
//设备名称为rgb-leds,可通过命令cat /proc/devices查看
//DEV_CNT为1,当前只申请一个设备编号
ret = alloc_chrdev_region(&mpu6050_devno, 0, DEV_CNT, DEV_NAME);
if (ret < 0)
{
printk("fail to alloc mpu6050_devno\n");
goto alloc_err;
}
//关联字符设备结构体cdev与文件操作结构体file_operations
mpu6050_chr_dev.owner = THIS_MODULE;
cdev_init(&mpu6050_chr_dev, &mpu6050_chr_dev_fops);
// 添加设备至cdev_map散列表中
ret = cdev_add(&mpu6050_chr_dev, mpu6050_devno, DEV_CNT);
if (ret < 0)
{
printk("fail to add cdev\n");
goto add_err;
}
/*创建类 */
class_mpu6050 = class_create(THIS_MODULE, DEV_NAME);
/*创建设备 DEV_NAME 指定设备名,*/
device_mpu6050 = device_create(class_mpu6050, NULL, mpu6050_devno, NULL, DEV_NAME);
mpu6050_client = client;
return 0;
add_err:
// 添加设备失败时,需要注销设备号
unregister_chrdev_region(mpu6050_devno, DEV_CNT);
printk("\n error! \n");
alloc_err:
return -1;
}
```
- 这部分和前面的注册字符设备一样,重点看文件操作接口file_operation
```c
/*字符设备操作函数集*/
static struct file_operations mpu6050_chr_dev_fops =
{
.owner = THIS_MODULE,
.open = mpu6050_open,
.read = mpu6050_read,
.release = mpu6050_release,
};
/*字符设备操作函数集,.release函数实现*/
static int mpu6050_release(struct inode *inode, struct file *filp)
{
// printk("\n mpu6050_release \n");
return 0;
}
```
```c
/*字符设备操作函数集,open函数实现*/
static int mpu6050_open(struct inode *inode, struct file *filp)
{
// printk("\n mpu6050_open \n");
/*向 mpu6050 发送配置数据,让mpu6050处于正常工作状态*/
mpu6050_init();
return 0;
}
```
- 调用了mpu6050_init函数
```c
/*初始化i2c
*返回值,成功,返回0。失败,返回 -1
*/
static int mpu6050_init(void)
{
int error = 0;
/*配置mpu6050电源管理,0x00,正常启动*/
error += i2c_write_mpu6050(mpu6050_client, PWR_MGMT_1, 0X00);
/*设置MPU6050的采样频率*/
error += i2c_write_mpu6050(mpu6050_client, SMPLRT_DIV, 0X07);
/*设置数字低通滤波器和帧同步引脚采样*/
error += i2c_write_mpu6050(mpu6050_client, CONFIG, 0X06);
/*设置量程和 X、Y、Z 轴加速度自检*/
error += i2c_write_mpu6050(mpu6050_client, ACCEL_CONFIG, 0X01);
if (error < 0)
{
/*初始化错误*/
printk(KERN_DEBUG "\n mpu6050_init error \n");
return -1;
}
return 0;
}
```
- 反复调用i2c_write_mpu6050函数发送配置信息给mpu6050,在mpu6050芯片上面有很多不同的寄存器地址的,每个不同的寄存器地址都是配置芯片不同属性,寄存器地址开头用宏定义,寄存器的值是在芯片手册上来确定的,对于每个寄存器地址要设置什么值在数据手册上面都有说明
- ```c
/*通过i2c 向mpu6050写入数据
*mpu6050_client:mpu6050的i2c_client结构体。
*address, 数据要写入的地址,
*data, 要写入的数据
*返回值,错误,-1。成功,0
*/
static int i2c_write_mpu6050(struct i2c_client *mpu6050_client, u8 address, u8 data)
{
int error = 0;
u8 write_data[2];
struct i2c_msg send_msg; //要发送的数据结构体
/*设置要发送的数据*/
write_data[0] = address;
write_data[1] = data;
/*发送 iic要写入的地址 reg*/
send_msg.addr = mpu6050_client->addr; //mpu6050在 iic 总线上的地址
send_msg.flags = 0; //标记为发送数据
send_msg.buf = write_data; //写入的首地址
send_msg.len = 2; //reg长度
/*执行发送*/
error = i2c_transfer(mpu6050_client->adapter, &send_msg, 1);
if (error != 1)
{
printk(KERN_DEBUG "\n i2c_transfer error \n");
return -1;
}
return 0;
}
```
- 调用i2c_transfer函数,i2c_transfer用来收发一个或者多个i2c消息,传递进来的i2c_client结构体*mpu6050_client里面addr变量值就是设备树子节点i2c_mpu6050里面reg属性的值,消息的flags的值设置为0也就是标记为发送数据,发送的buf内容设置为write_data数组的内容,数组前面填充了address和data变量值,这两个变量值是函数传递进来的;最后是设置消息的长度send_msg.len = 2;,因为write_data的0和1有消息即这条消息有两个字节。
```c
/*字符设备操作函数集,.read函数实现*/
static ssize_t mpu6050_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
char data_H;
char data_L;
int error;
//保存mpu6050转换得到的原始数据
short mpu6050_result[6];
/*读取3轴加速度原始值*/
//读取x轴加速度的高8位
i2c_read_mpu6050(mpu6050_client, ACCEL_XOUT_H, &data_H, 1);
//读取x轴加速度的低8位
i2c_read_mpu6050(mpu6050_client, ACCEL_XOUT_L, &data_L, 1);
//高8位与低8位合起来变成十六进制数
mpu6050_result[0] = data_H << 8;
mpu6050_result[0] += data_L;
i2c_read_mpu6050(mpu6050_client, ACCEL_YOUT_H, &data_H, 1);
i2c_read_mpu6050(mpu6050_client, ACCEL_YOUT_L, &data_L, 1);
mpu6050_result[1] = data_H << 8;
mpu6050_result[1] += data_L;
i2c_read_mpu6050(mpu6050_client, ACCEL_ZOUT_H, &data_H, 1);
i2c_read_mpu6050(mpu6050_client, ACCEL_ZOUT_L, &data_L, 1);
mpu6050_result[2] = data_H << 8;
mpu6050_result[2] += data_L;
/*读取3轴角速度原始值*/
i2c_read_mpu6050(mpu6050_client, GYRO_XOUT_H, &data_H, 1);
i2c_read_mpu6050(mpu6050_client, GYRO_XOUT_L, &data_L, 1);
mpu6050_result[3] = data_H << 8;
mpu6050_result[3] += data_L;
i2c_read_mpu6050(mpu6050_client, GYRO_YOUT_H, &data_H, 1);
i2c_read_mpu6050(mpu6050_client, GYRO_YOUT_L, &data_L, 1);
mpu6050_result[4] = data_H << 8;
mpu6050_result[4] += data_L;
i2c_read_mpu6050(mpu6050_client, GYRO_ZOUT_H, &data_H, 1);
i2c_read_mpu6050(mpu6050_client, GYRO_ZOUT_L, &data_L, 1);
mpu6050_result[5] = data_H << 8;
mpu6050_result[5] += data_L;
// printk("AX=%d, AY=%d, AZ=%d \n",(int)mpu6050_result[0],(int)mpu6050_result[1],(int)mpu6050_result[2]);
// printk("GX=%d, GY=%d, GZ=%d \n \n",(int)mpu6050_result[3],(int)mpu6050_result[4],(int)mpu6050_result[5]);
/*将读取得到的数据拷贝到用户空间*/
error = copy_to_user(buf, mpu6050_result, cnt);
if(error != 0)
{
printk("copy_to_user error!");
return -1;
}
return 0;
}
```
- mpu6050_read函数就是应用层调用read函数所对应底层的接口
- 调用了i2c_read_mpu6050
- 调用copy_to_user将mpu6050_result数组的值返回到用户空间的buf里面
- ```c
/*通过i2c 向mpu6050写入数据
*mpu6050_client:mpu6050的i2c_client结构体。
*address, 要读取的地址,
*data,保存读取得到的数据
*length,读长度
*返回值,错误,-1。成功,0
*/
static int i2c_read_mpu6050(struct i2c_client *mpu6050_client, u8 address, void *data, u32 length)
{
int error = 0;
u8 address_data = address;
struct i2c_msg mpu6050_msg[2];
/*设置读取位置msg*/
mpu6050_msg[0].addr = mpu6050_client->addr; //mpu6050在 iic 总线上的地址
mpu6050_msg[0].flags = 0; //标记为发送数据
mpu6050_msg[0].buf = &address_data; //写入的首地址
mpu6050_msg[0].len = 1; //写入长度
/*设置读取位置msg*/
mpu6050_msg[1].addr = mpu6050_client->addr; //mpu6050在 iic 总线上的地址
mpu6050_msg[1].flags = I2C_M_RD; //标记为读取数据
mpu6050_msg[1].buf = data; //读取得到的数据保存位置
mpu6050_msg[1].len = length; //读取长度
error = i2c_transfer(mpu6050_client->adapter, mpu6050_msg, 2);
if (error != 2)
{
printk(KERN_DEBUG "\n i2c_read_mpu6050 error \n");
return -1;
}
return 0;
}
```
- 定义了两条消息,并调用i2c_transfer发送两条消息,第一条消息是发送要读取的寄存器地址,第二条消息是读取i2c消息,flags为0代表发送数据,发送数据的内容是要读取的mpu6050寄存器的地址;第二条消息的标志是I2C_M_RD表示要读取数据,这时候i2c_transfer读取到的数据返回到mpu6050_msg中第一个元素里面的buf成员变量,buf成员变量初始化为data的变量值。
- 调用此函数后得到的值就记录到*data上
3、编写应用程序进行测试
1. 调用open函数打开设备文件I2C1_mpu6050
2. 调用read函数读取12字节数据
3. printf打印数据
4. close关闭文件
4、编译设备树、应用程序、驱动程序
1. debian系统里默认加载一部分插件这些插件一部分使用到i2c控制器,为了不干扰实验结果需要先屏蔽这部分插件,使用nano编辑器打开boot目录下的uEnv.txt文件,这个文件主要是控制设备插件的使用,需要屏蔽i2c词条的和lcd词条的,因为lcd液晶屏的触摸芯片也使用到i2c,重启开发板
2. insmod驱动程序,替换设备树
## 验证测试
使用命令`/mnt/App`执行应用程序,这里出现了可能是共享文件夹同步问题,先将应用程序拷贝到当前文件夹~里,重新执行`./App`可以看到输出三轴信息,将开发板倾斜某一角度,打印的数值不同,得到运动物体的姿势