# 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 ... **框架图** image-20200901213737995 在应用程序里面想要控制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轴旋转的角速度 **硬件原理图** image-20200903171355402 - 图上的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 image-20230521170613574 1. 最后的值直接参照官方的设置,设备树中其他的 pinctrl 子节点的配置就是很好的一个参考,如果有需要再参考 IM6ULL 用户手册对个别参数进行修改。这里设备树中没有其他i2c设置的引脚属性值,只能查用户手册,配置为0x4001b8b0 image-20230521171418181 image-20230521172626415 image-20230521172756244 image-20230521172834062 image-20230521172917363 初始化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`可以看到输出三轴信息,将开发板倾斜某一角度,打印的数值不同,得到运动物体的姿势