当前位置: 首页 > news >正文

Linux驱动开发笔记(十九)——IIC(AP3216C驱动+MPU6050驱动)

视频:第22.1讲 Linux IIC驱动实验-Linux IIC适配器驱动框架简析_哔哩哔哩_bilibili
资料:《【正点原子】I.MX6U开发指南V1.81.pdf》六十一章


目录

一、IIC设备驱动

1.1 两个重要结构体

1.1.1 i2c_client

1.1.2 i2c_driver

注册

注销

示例

1.2 IIC设备的信息描述

1.2.1 无设备树

1.2.2 有设备树

1.3 设备驱动的匹配

1.4 数据收发处理

二、IIC控制器 / 适配器驱动

2.1 i2c_adapter

注册i2c_adapter

删除i2c_adapter

2.2 i2c_algorithm

三、AP3216C驱动

3.1 修改设备树

3.1.1 配置引脚

3.1.2 配置设备节点

3.2 文件结构

3.3 Makefile

 3.4 ap3216creg.h

3.5 驱动代码

3.5.1 驱动框架

3.5.2 读写AP3216C的寄存器数据

3.5.2.1 读数据:

3.5.2.2 写数据:

3.5.3 完整的驱动代码ap3216c.c

3.6 应用程序 

四、MPU6050驱动

4.1 设备树

4.1.1 配置IIC引脚

4.1.2 配置设备节点

4.2 文件结构

4.3 Makefile

 4.4 mpu6050reg.h MPU6050的各种寄存器

4.4.1 读取数据用到的寄存器

4.4.2 初始化用到的寄存器

4.5 驱动代码

4.5.1 框架

4.5.2 读写MPU6050寄存器函数

4.5.2.1 写寄存器

4.3.2.2 读寄存器

4.5.2.3 .read函数

4.5.3 MPU6050初始化

4.5.4 完整驱动代码

4.6 应用程序

4.7 测试

这目录也是又臭又长了


一、IIC设备驱动

1.1 两个重要结构体

        设备驱动主要看两个重要结构体:i2c_client和i2c_driver。
        这两个结构体都定义在include/linux/i2c.h。

1.1.1 i2c_client

        i2c_client表示IIC设备。

/*** struct i2c_client - represent an I2C slave device* @flags: I2C_CLIENT_TEN indicates the device uses a ten bit chip address;*	I2C_CLIENT_PEC indicates it uses SMBus Packet Error Checking* @addr: Address used on the I2C bus connected to the parent adapter.* @name: Indicates the type of the device, usually a chip name that's*	generic enough to hide second-sourcing and compatible revisions.* @adapter: manages the bus segment hosting this I2C device* @dev: Driver model device node for the slave.* @irq: indicates the IRQ generated by this device (if any)* @detected: member of an i2c_driver.clients list or i2c-core's*	userspace_devices list* @slave_cb: Callback when I2C slave mode of an adapter is used. The adapter*	calls it to pass on slave events to the slave driver.** An i2c_client identifies a single device (i.e. chip) connected to an* i2c bus. The behaviour exposed to Linux is defined by the driver* managing the device.* struct i2c_client表示一个I2C从设备* @flags: I2C_CLIENT_TEN表示该设备使用10位芯片地址*         I2C_CLIENT_PEC表示该设备使用SMBus的数据包错误校验* @addr: 连接到父适配器的I2C总线上使用的地址* @name: 指示设备类型,通常是一个芯片名称;该名称应足够通用,*        以便屏蔽第二供货商和兼容修订版本的差异* @adapter: 管理承载此I2C设备的总线适配器* @dev: 该从设备在驱动模型中的设备节点* @irq: 该设备产生的中断号(如果有)* @detected: 属于i2c_driver.clients链表或i2c-core的userspace_devices链表的成员* @slave_cb: 当适配器处于I2C从模式时使用的回调。适配器调用它,以将从设备事件传递给从设备驱动** 一个i2c_client标识连接到某条I2C总线的单个设备(即芯片),其在Linux中暴露的行为由管理该设备的驱动程序定义*/
struct i2c_client {unsigned short flags;		/* div., see below		*/unsigned short addr;		/* chip address - NOTE: 7bit	芯片地址——7位地址 *//* addresses are stored in the	_LOWER_ 7 bits地址存放在低7位 */char name[I2C_NAME_SIZE];struct i2c_adapter *adapter;	/* the adapter we sit on  所在的适配器*/struct device dev;		/* the device structure	设备结构体 */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
};

        i2c_client结构体不需要我们手动创建。在设备树里添加IIC芯片,系统在解析设备树时会自动添加对应的i2c_client。他的功能是方便我们通过其获取设备IIC信息。

1.1.2 i2c_driver


/*** struct i2c_driver - represent an I2C device driver* @class: What kind of i2c device we instantiate (for detect)* @attach_adapter: Callback for bus addition (deprecated)* @probe: Callback for device binding* @remove: Callback for device unbinding* @shutdown: Callback for device shutdown* @alert: Alert callback, for example for the SMBus alert protocol* @command: Callback for bus-wide signaling (optional)* @driver: Device driver model driver* @id_table: List of I2C devices supported by this driver* @detect: Callback for device detection* @address_list: The I2C addresses to probe (for detect)* @clients: List of detected clients we created (for i2c-core use only)** The driver.owner field should be set to the module owner of this driver.* The driver.name field should be set to the name of this driver.** For automatic device detection, both @detect and @address_list must* be defined. @class should also be set, otherwise only devices forced* with module parameters will be created. The detect function must* fill at least the name field of the i2c_board_info structure it is* handed upon successful detection, and possibly also the flags field.** If @detect is missing, the driver will still work fine for enumerated* devices. Detected devices simply won't be supported. This is expected* for the many I2C/SMBus devices which can't be detected reliably, and* the ones which can always be enumerated in practice.** The i2c_client structure which is handed to the @detect callback is* not a real i2c_client. It is initialized just enough so that you can* call i2c_smbus_read_byte_data and friends on it. Don't do anything* else with it. In particular, calling dev_dbg and friends on it is* not allowed.*** struct i2c_driver - 表示一个 I2C 设备驱动* @class: 我们实例化的IIC设备的类型(用于检测)* @attach_adapter: (已弃用)* @probe: 设备匹配时调用  △ △ △* @remove: 设备移除时调用  △ △ △* @shutdown: 设备关机时调用* @alert: 警报回调函数,例如SMBus警报协议* @command: 用于总线范围信号(可选)的回调函数* @driver: device_driver结构体  △ △ △*     driver.owner 此驱动的所有者,一般都是THIS_MODULE  △ △ △*     driver.name  此驱动的名称  △ △ △*     driver.of_match_table  虽然英文注释中没写,但这个也挺重要,用于设备树匹配  △ △ △* @id_table: 该驱动支持的IIC设备列表。在无设备树时使用此列表进行匹配  △ △ △* @detect: 设备检测的回调函数* @address_list: 要探测的IIC地址(用于检测)* @clients: 我们创建的已检测客户端列表(仅供i2c-core使用)*** 对于自动设备检测,@detect和@address_list都必须定义* 还应设置 @class,否则只会创建通过模块参数强制指定的设备* detect函数在成功检测后必须填写传入的i2c_board_info结构中的name字段,也可以填写flags字段** 如果未定义@detect,驱动程序对于已枚举的设备仍能正常工作,只是检测到的设备不会被支持。* 这对许多无法可靠检测的 I2C/SMBus 设备来说是预期的,对于那些在实际使用中总能被枚举的设备也同样适用** 传递给@detect回调函数的i2c_client结构并不是真正的i2c_client,* 它只被初始化到足以调用i2c_smbus_read_byte_data等函数的程度,* 不要对它进行其他操作。特别是不允许在其上调用dev_dbg等函数。*/
struct i2c_driver {unsigned int class;/* Notifies the driver that a new bus has appeared. You should avoid* using this, it will be removed in a near future.*/int (*attach_adapter)(struct i2c_adapter *) __deprecated;/* Standard driver model interfaces */int (*probe)(struct i2c_client *, const struct i2c_device_id *);int (*remove)(struct i2c_client *);/* driver model interfaces that don't relate to enumeration  */void (*shutdown)(struct i2c_client *);/* Alert callback, for example for the SMBus alert protocol.* The format and meaning of the data value depends on the protocol.* For the SMBus alert protocol, there is a single bit of data passed* as the alert response's low bit ("event flag").*/void (*alert)(struct i2c_client *, unsigned int data);/* a ioctl like command that can be used to perform specific functions* with the device.*/int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);struct device_driver driver;const struct i2c_device_id *id_table;/* Device detection callback for automatic device creation */int (*detect)(struct i2c_client *, struct i2c_board_info *);const unsigned short *address_list;struct list_head clients;
};
注册
// 定义在drivers/i2c/i2c-core.c
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
// owner: 一般都是THIS_MODULE
// driver:要注册的i2c_driver
// return:0成功;负值失败

或使用宏定义:

// 定义在include/linux/i2c.h
// (还是在调用i2c_register_driver,只是能少写一个THIS_MODULE)
#define i2c_add_driver(driver)  i2c_register_driver(THIS_MODULE, driver)
注销
// 定义在drivers/i2c/i2c-core.c
void i2c_del_driver(struct i2c_driver *driver)
// driver:要注销的i2c_driver
示例

(该示例代码来自《【正点原子】I.MX6U开发指南V1.81》六十一章 示例代码61.1.2.4)

/* i2c驱动的probe函数 */
static int xxx_probe(struct i2c_client *client, const struct i2c_device_id *id){…………}/* i2c驱动的remove函数 */ 
static int xxx_remove(struct i2c_client *client){…………}/* 传统匹配方式ID列表 */
static const struct i2c_device_id xxx_id[] = {{"xxx", 0},…………{ /* sentinel */ }
};/* 设备树匹配列表 */
static const struct of_device_id xxx_of_match[] = {{ .compatible = "xxx" },{ /* Sentinel */ }
};/* i2c驱动结构体 */
static struct i2c_driver xxx_driver = {.probe = xxx_probe,.remove = xxx_remove,.driver = {.owner = THIS_MODULE,.name = "xxx",   // 不用于匹配!.of_match_table = xxx_of_match,  // 若定义了of_match_table,表示有设备树,使用of_match_table匹配},.id_table = xxx_id,  // 若没有设备树,使用id_table进行匹配。具体匹配流程详见1.3
};/* 驱动入口函数 */
static int __init xxx_init(void){int ret = 0;ret = i2c_add_driver(&xxx_driver); // 注册i2c_driverreturn ret;
}/* 驱动出口函数 */
static void __exit xxx_exit(void){i2c_del_driver(&xxx_driver);
}module_init(xxx_init);
module_exit(xxx_exit);

1.2 IIC设备的信息描述

1.2.1 无设备树

        无设备树时,需要配置i2c_board_info结构体

// 定义在include/linux/i2c.h
struct i2c_board_info {char		type[I2C_NAME_SIZE];  // IIC设备名    必须设置unsigned short	flags;            //    unsigned short	addr;             // IIC器件地址  必须设置void		*platform_data;struct dev_archdata	*archdata;struct device_node *of_node;struct fwnode_handle *fwnode;int		irq;
};

1.2.2 有设备树

        以mag3110节点为例:

// 定义在arch/arm/boot/dts/imx6ull-alientek-emmc.dts
&i2c1 {clock-frequency = <100000>;pinctrl-names = "default";pinctrl-0 = <&pinctrl_i2c1>;    // 指定i2c1的引脚status = "okay";mag3110@0e {                    // 节点名 @ IIC器件地址compatible = "fsl,mag3110"; // 用于匹配驱动    必须设置reg = <0x0e>;               // IIC器件地址     必须设置position = <2>;};…………
};

1.3 设备驱动的匹配

        设备和驱动的匹配由IIC总线完成:

// 定义在drivers/i2c/i2c-core.c
struct bus_type i2c_bus_type = {.name		= "i2c",.match		= i2c_device_match, // match就是IIC总线的设备/驱动匹配函数.probe		= i2c_device_probe,.remove		= i2c_device_remove,.shutdown	= i2c_device_shutdown,
};

其中,匹配函数i2c_device_match内容如下:

// 定义在drivers/i2c/i2c-core.c// 设备树匹配 -> ACPI匹配 -> name字段匹配
static int i2c_device_match(struct device *dev, struct device_driver *drv){struct i2c_client	*client = i2c_verify_client(dev);struct i2c_driver	*driver;if (!client)return 0;/* Attempt an OF style match */// 设备树设备和驱动的匹配// 比较设备树节点的compatible与和of_device_id中的compatible属性是否一致if (of_driver_match_device(dev, drv))return 1;/* Then ACPI style match */if (acpi_driver_match_device(dev, drv))   // 用于ACPI形式匹配return 1;driver = to_i2c_driver(drv);/* match on an id table if there is one */if (driver->id_table)return i2c_match_id(driver->id_table, client) != NULL;// i2c_match_id用于传统的、无设备树的I2C设备和驱动匹配过程// 比较IIC设备名和i2c_device_id的name是否相等return 0;
}

1.4 数据收发处理

        i2c_transfer函数

int i2c_transfer(struct i2c_adapter *adap,  // 使用的IIC控制器/适配器。i2c_client会保存其对应的i2c_adapterstruct i2c_msg     *msgs,  // 要发送的消息(一个或多个)int      num)   // 消息数量
// return:负值失败;else:发送的msgs数量

其中,i2c_msg定义如下:

struct i2c_msg {__u16 addr;	/* slave address 从机地址 */__u16 flags; // 标志,具体内容如以下define所示// 0 表示 发送数据,这里没定义
#define I2C_M_RD		0x0001	/* read data, from slave to master  from 从机 读数据 to 主机 *//* I2C_M_RD is guaranteed to be 0x0001! */
#define I2C_M_TEN		0x0010	/* this is a ten bit chip address 10位芯片地址 */
#define I2C_M_DMA_SAFE		0x0200	/* the buffer of this message is DMA safe *//* makes only sense in kernelspace *//*  */
#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 本段msg的长度*/__u8 *buf;		/* pointer to msg data 消息数据 */
};

        具体使用可见3.5.2代码。

二、IIC控制器 / 适配器驱动

        (驱动开发只需要完成IIC设备驱动即可,控制器驱动一般由原厂提供,这里只做了解)

        IIC总线驱动重点是IIC适配器/控制器驱动。要用到两个结构体:i2c_adapter和i2c_algorithm。

2.1 i2c_adapter

        Linux内核将SOC的IIC适配器/控制器抽象为i2c_adapter。IIC适配器/控制器驱动的核心就是申请i2c_adapter结构体、初始化、注册。

// 定义在include/linux/i2c.h
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;/* 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;    // 编号char name[48];struct completion dev_released;struct mutex userspace_clients_lock;struct list_head userspace_clients;struct i2c_bus_recovery_info *bus_recovery_info;const struct i2c_adapter_quirks *quirks;
};

        其中的重点就是i2c_algorithm这个结构体。详见2.2。

注册i2c_adapter

使用以下两个函数函数向系统注册i2c_adapter结构体:

// 定义在drivers/i2c/i2c-core.c
// 动态分配一个未被占用的编号(/dev/i2c-*中的X)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
// return:0成功;负值失败
// 定义在drivers/i2c/i2c-core.c
// 需要预先设置i2c_adapter的nr成员
// 内核会尝试以这个编号进行注册;若该编号已被占用会失败
int i2c_add_adapter(struct i2c_adapter *adapter)
// return:0成功;负值失败

删除i2c_adapter

// 定义在drivers/i2c/i2c-core.c
void i2c_del_adapter(struct i2c_adapter *adap)

2.2 i2c_algorithm

        i2c_algorithm就是IIC适配器/控制器与IIC设备通信的方法。

// 定义在include/linux/i2c.h
/*** struct i2c_algorithm - represent I2C transfer method* @master_xfer: Issue a set of i2c transactions to the given I2C adapter*   defined by the msgs array, with num messages available to transfer via*   the adapter specified by adap.* @smbus_xfer: Issue smbus transactions to the given I2C adapter. If this*   is not present, then the bus layer will try and convert the SMBus calls*   into I2C transfers instead.* @functionality: Return the flags that this algorithm/adapter pair supports*   from the I2C_FUNC_* flags.* @reg_slave: Register given client to I2C slave mode of this adapter* @unreg_slave: Unregister given client from I2C slave mode of this adapter** The following structs are for those who like to implement new bus drivers:* i2c_algorithm is the interface to a class of hardware solutions which can* be addressed using the same bus algorithms - i.e. bit-banging or the PCF8584* to name two of the most common.** The return codes from the @master_xfer field should indicate the type of* error code that occurred during the transfer, as documented in the kernel* Documentation file Documentation/i2c/fault-codes.struct i2c_algorithm:表示 I2C 传输方法@master_xfer:向指定的I2C控制器发起一组I2C事务这些事务由msgs数组定义,数组中包含num条可通过adap指定的控制器传输的消息@smbus_xfer:向指定的I2C控制器发起SMBus事务如果该函数未被实现(为NULL),那么I2C总线层会尝试将SMBus调用转换为I2C传输@functionalit:返回该算法/控制器组合所支持的功能标志这些标志来自I2C_FUNC_*宏定义@reg_slave:    将指定的从设备(client)注册到该控制器的I2C从设备模式中@unreg_slave:将指定的从设备(client)从该控制器的I2C从设备模式中注销*/
struct i2c_algorithm {/* If an adapter algorithm can't do I2C-level access, set master_xferto NULL. If an adapter algorithm can do SMBus access, setsmbus_xfer. If set to NULL, the SMBus protocol is simulatedusing common I2C messages 如果适配器/控制器算法无法进行IIC访问,要将master_xfer设为NULL如果适配器/控制器算法可以进行SMBus访问,要设置smbus_xfer如果smbus_xfer为NULL,则SMBus协议会使用常规IIC消息进行模拟master_xfer should return the number of messages successfullyprocessed, or a negative value on error master_xfer应返回成功处理的消息数量,若错误则返回负值*/int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num); // 该函数就是IIC控制器最终进行数据收发的函数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
};

三、AP3216C驱动

        由于我用的mini版并不带AP3216C传感器,因此最终的代码我也不确定是否完全正确,只能说确实能读数据了(虽然都是0)这里只记录一下编写流程。
        后面的MPU6050代码倒是肯定能跑。

3.1 修改设备树

       打开《IMX6ULL_ALPHA_V2.2(底板原理图).pdf》,搜索AP3216C,可以看到AP3216C使用的是I2C1:

       那么我们就要在i2c1下添加ap3216c节点。imx6ull.dtsi下描述的是SOC硬件资源,imx6ull-alientek-emmc.dts是板级资源,因此我们在imx6ull-alientek-emmc.dts的&i2c1下添加ap3216c节点。

3.1.1 配置引脚

        i2c1配置如下:

&i2c1 {clock-frequency = <100000>;pinctrl-names = "default";pinctrl-0 = <&pinctrl_i2c1>;status = "okay";mag3110@0e {compatible = "fsl,mag3110";reg = <0x0e>;position = <2>;};fxls8471@1e {compatible = "fsl,fxls8471";reg = <0x1e>;position = <0>;interrupt-parent = <&gpio5>;interrupts = <0 8>;};
};

       时钟频率、name、status都不用改,只需要看看pinctrl_i2c1是否符合要求。

       AP3216C使用的是I2C1_SCL、I2C1_SDA。去搜索这两个,可以在第5页找到:

        因此现在需要去配置&i2c1中的pinctrl-0,将UART4复用为I2C1。

        pinctrl_i2c1的内容如下:

		pinctrl_i2c1: i2c1grp {fsl,pins = <MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0  // 定义在arch/arm/boot/dts/imx6ul-pinfunc.hMX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0>;};

        巧了,刚好配置的就是UART4复用为I2C1,就不需要改了。

3.1.2 配置设备节点

        在《【正点原子】阿尔法Linux开发板(A盘)-基础资料\06、硬件资料\01、芯片资料》中可以找到《AP3216C.pdf》,其中在IIC部分可以找到ap3216c的7位从设备地址(slave address)为0x1e:

        这里直接告诉我们是7位器件地址为0x1E。有的手册可能会写“读地址为0xAB”(也就是1010 1011共8位),其中最低位bit0就是读写位(0写 1读),高7位bit1~7为器件地址。在配置设备树节点时,我们就需要把最低位的读写位去掉,也就是0xAB >> 1 = 0x55,不要直接把0xAB写上去了。

        现在就可以完成ap3216c的设备树节点:

	ap3216c@1e {      // name@器件地址compatible = "alientek,ap3216c"; // 因为是自己实验,随便写一个就行了,写驱动时保持一致即可reg = <0x1e>; // 器件地址};

         还有一件事。i2c1下面还有个fxls8471节点,它的器件地址也是0x1e。同一个总线上,不能存在两个拥有相同器件地址的设备,reg属性必须唯一。因此我们注释掉fxls8471节点。
        在实际使用中,如果确实有两个设备器件地址一样,需要放到不同的iic下面去。
        (其实有重复的器件地址也是不会报错的,但是只有一个能正确注册。在启动时,内核会根据设备树节点自动生成i2c_client(1.1.1),如果地址未被占用则注册成功。那么我们只要把ap3216c节点写到fxls8471节点的前面就可以正常注册,而fxls8471无法注册。虽然但是 最好还是把fxls8471注释掉)

       现在修改好设备树,编译、复制即可:

make dtbs
sudo cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb /.../tftpboot/

        现在打开开发板,看看是否正确注册了0x1e:

ls /sys/bus/i2c/devices/        # 应能看到0-001e
cd /sys/bus/i2c/devices/0-001e/
cat name                       # 应能看到ap3216c

3.2 文件结构

22_IIC (工作区)
├── .vscode
│   ├── c_cpp_properties.json
│   └── settings.json
├── 22_iic.code-workspace
├── Makefile
├── ap3216creg.h
├── ap3216c.c
└── ap3216cAPP.c

3.3 Makefile

CFLAGS_MODULE += -wKERNELDIR := /....../linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek  # 内核路径
# KERNELDIR改成自己的 linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek文件路径(这个文件从正点原子“01、例程源码”中直接搜,cp到虚拟机里面)CURRENT_PATH := $(shell pwd)	# 当前路径obj-m := ap3216c.o			    # 编译文件build: kernel_modules			# 编译模块kernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean	

 3.4 ap3216creg.h

        将用到的AP3216C的寄存器地址都保存到ap3216creg.h中。

        在【正点原子】阿尔法Linux开发板(A盘)-基础资料/06、硬件资料/01、芯片资料/中可以找到《AP3216C.pdf》。第12页可以找到相关寄存器地址:

整理为代码写入到ap3216creg.h中:

#define AP3216C_SYSTEMCONG   0x00    /* 配置寄存器    */ 
#define AP3216C_INTSTATUS    0X01    /* 中断状态寄存器 */ 
#define AP3216C_INTCLEAR     0X02    /* 中断清除寄存器 */ 
#define AP3216C_IRDATALOW    0x0A    /* IR数据低字节  */ 
#define AP3216C_IRDATAHIGH   0x0B    /* IR数据高字节  */ 
#define AP3216C_ALSDATALOW   0x0C    /* ALS数据低字节 */ 
#define AP3216C_ALSDATAHIGH  0X0D    /* ALS数据高字节 */ 
#define AP3216C_PSDATALOW    0X0E    /* PS数据低字节  */ 
#define AP3216C_PSDATAHIGH   0X0F    /* PS数据高字节  */ 

3.5 驱动代码

3.5.1 驱动框架

先搭个框架出来,主要包括了:
        驱动入口/出口函数,负责注册/注销i2c_driver;
        初始化i2c_driver,以及编写对应的匹配表、probe和remove等函数

// 无设备树匹配表   与刚才设备树节点的compatible写成一样的即可
static struct i2c_device_id ap3216c_id[] = {{"alientek,ap3216c"},{/*sentinel*/},
};
// 有设备树匹配表   需要与刚才设备树节点的compatible保持一致
static struct of_device_id ap3216c_of_match[] = {{ .compatible = "alientek,ap3216c" }, {/*sentinel*/},
};// --i2c驱动结构体 及其函数---------------------------------------------------------------static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id){return 0;
}
static int ap3216c_remove(struct i2c_client *client){return 0;
}
// i2c_driver
static struct i2c_driver ap3216c_driver{ // 依照1.1.2注释,把最重要的几个写上.probe = ap3216c_probe,.remove = ap3216c_remove,.driver = {.owner = THIS_MODULE,.name = "ap3216c",.of_match_table = of_match_ptr(ap3216c_of_match),},.id_table = ap3216c_id,
}// ---驱动入口/出口-----------------------------------------------------------------// 驱动入口
static int __init ap3216c_init(void){int ret = 0;ret = i2c_add_driver(&ap3216c_driver); // 注册i2c_driverreturn ret;
}
// 驱动出口
static void __exit ap3216c_exit(void){i2c_del_driver(&ap3216c_driver); // 注销i2c_driver
}
module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");

3.5.2 读写AP3216C的寄存器数据

3.5.2.1 读数据:

读取ap3216c寄存器数据流程如下,分为4步:
        1、主机发送 器件地址,最后一位是0(Write),表示主机将要发送 目标从机地址
        2、主机发送 要读的从机寄存器地址
        3、主机再发一次 器件地址,不过此时最后一位是1(Read),表示主机要接收的 目标从机地址
        4、总线将读取到的从机寄存器数据发送给主机
简化一下,就是:①主机发送要读取的寄存器地址,②主机读取返回来的目标数据

综上,可以编写读数据函数:

// 从AP3216C寄存器读数据
// 依照图26.1.1.6的流程,主机需要:
//      ①发送要读取的寄存器地址(发送模式,发送的内容是寄存器地址)
//      ②接收目标寄存器的数据  (接收模式,接收的内容是目标寄存器的数据)
// reg:要读取的寄存器的地址    val:存储接收到的数据   len:接收的数据的长度
static int ap3216c_read_regs(struct ap3216c_dev_struct *dev, u8 reg, void *val, int len){struct i2c_client *client = (struct i2c_client*)dev->private_data;struct i2c_msg msg[2]; // i2c_msg详见1.4// msg[0]发送:要读取的从机寄存器的首地址(主机发送)// msg[1]接收:读取到的目标寄存器的数据(主机接收)// ①发送目标寄存器的地址(发送模式,发送的内容是目标寄存器的地址)msg[0].addr = client->addr; // 从机地址(AP3216C)msg[0].flags = 0; // 发送。flag详见1.4msg[0].buf = &reg;// 发送内容:要读取的寄存器地址msg[0].len = 1;   // 要发送的数据长度为1(目标寄存器地址长度为1)(AP3216C的寄存器都是8位)// ②接收目标寄存器的数据(接收模式,接收的内容是目标寄存器的数据)msg[1].addr = client->addr; // 从机地址(AP3216C)msg[1].flags = I2C_M_RD;    // 接收。flag详见1.4msg[1].buf = val;           // 读取到的数据保存到valmsg[1].len = len;           // 要读取的数据长度return i2c_transfer(client->adapter, msg, 2); // 发送
}

最后再封装一下:

// 读取AP3216C一个寄存器
// 对ap3216c_read_regs封装
// reg 要读取的寄存器地址
static unsigned char ap3216c_read_reg(struct ap3216c_dev_struct *dev, u8 reg){u8 data = 0;ap3216c_read_regs(dev, reg, &data, 1);// 可以自己加上错误处理return data;
}

        读数据详细流程可见《【正点原子】I.MX6U开发指南V1.81》26.1部分:

3.5.2.2 写数据:

        写数据就不需要4步了。先发送目标从机地址,再一次性发送(目标寄存器地址+要写入的数据),要写输入的数据DATA可以为多个字节。

// 向AP3216C寄存器写数据
// 因为ap3216c的寄存器都是8位的,因此buf可以直接用u8类型
// reg:要写的寄存器地址    buf:要写的数据    len:要写的数据的长度
static int ap3216c_write_regs(struct ap3216c_dev_struct *dev, u8 reg, u8 *buf, int len){ struct i2c_client *client = (struct i2c_client*)dev->private_data;struct i2c_msg msg; // i2c_msg详见1.4u8 b[256];b[0] = reg;     // 目标寄存器地址存到b[0]memcpy(&b[1], buf, len); // 要发送的数据存到b[1~len]msg.addr = client->addr; // 从机地址(AP3216C)msg.flags = 0; // 发送。flag详见1.4msg.buf = b;// 发送内容:要读取的寄存器地址msg.len = len + 1;   // 要发送的数据长度(len) + 寄存器地址长度(1)return i2c_transfer(client->adapter, &msg, 1); // 这里要写成&msg!不是msg
}

封装一下:

// 向AP3216C一个寄存器写一个字节的数据
// 对ap3216c_write_regs封装
// reg 要写数据的寄存器地址     data 要写的数据
static void ap3216c_write_reg(struct ap3216c_dev_struct *dev, u8 reg, u8 data){u8 buf = 0;buf = data;ap3216c_write_regs(dev, reg, &buf, 1);// 可以自己加上错误处理
}

        写数据详细流程可见《【正点原子】I.MX6U开发指南V1.81》26.1部分:

3.5.3 完整的驱动代码ap3216c.c

接下来完善代码:
        增加设备结构体

        增加操作集函数,即对应的open、read、release函数

        完善probe函数(注册设备号,注册字符设备cdev,自动创建设备节点class device)

        增加ap3216c寄存器读写函数

#include<linux/module.h>
#include<linux/input.h>
#include<linux/i2c.h>
#include<linux/cdev.h>
#include<linux/device.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<linux/string.h>
#include<linux/delay.h>
#include"ap3216creg.h"#define AP3216C_CNT 1
#define AP3216C_NAME "ap3216c"//=== 设备结构体 =====================================================================struct ap3216c_dev_struct {int major; // 主设备号int minor; // 次设备号dev_t devid; // 设备号struct cdev cdev;struct class *class;struct device *device;void *private_data; // 保存i2c_client// 手册上指明,环境光传感器ALS有16位分辨率(0~65535),IR和PS有10位分辨率(0~1023)。因此这里统一设为shortunsigned short ir, als, ps; // 红外强度  环境光强度  接近距离
};
static struct ap3216c_dev_struct ap3216c_dev;//=== 读写寄存器函数 =====================================================================// 从AP3216C寄存器读数据
// 依照图26.1.1.6的流程,主机需要:
//      ①发送要读取的寄存器地址(发送模式,发送的内容是寄存器地址)
//      ②接收目标寄存器的数据  (接收模式,接收的内容是目标寄存器的数据)
// reg:要读取的寄存器的地址    val:存储接收到的数据   len:接收的数据的长度
static int ap3216c_read_regs(struct ap3216c_dev_struct *dev, u8 reg, void *val, int len){struct i2c_client *client = (struct i2c_client*)dev->private_data;struct i2c_msg msg[2]; // i2c_msg详见1.4// msg[0]发送:要读取的从机寄存器的首地址(主机发送)// msg[1]接收:读取到的目标寄存器的数据(主机接收)// ①发送目标寄存器的地址(发送模式,发送的内容是目标寄存器的地址)msg[0].addr = client->addr; // 从机地址(AP3216C)ssmsg[0].flags = 0; // 发送。flag详见1.4msg[0].buf = &reg;// 发送内容:要读取的寄存器地址msg[0].len = 1;   // 要发送的数据长度为1(目标寄存器地址长度为1)(AP3216C的寄存器都是8位)// ②接收目标寄存器的数据(接收模式,接收的内容是目标寄存器的数据)msg[1].addr = client->addr; // 从机地址(AP3216C)msg[1].flags = I2C_M_RD;    // 接收。flag详见1.4msg[1].buf = val;           // 读取到的数据保存到valmsg[1].len = len;           // 要读取的数据长度return i2c_transfer(client->adapter, msg, 2); // 发送
}// 向AP3216C寄存器写数据
// 因为ap3216c的寄存器都是8位的,因此buf可以直接用u8类型
// reg:要写的寄存器地址    buf:要写的数据    len:要写的数据的长度
static int ap3216c_write_regs(struct ap3216c_dev_struct *dev, u8 reg, u8 *buf, int len){ struct i2c_client *client = (struct i2c_client*)dev->private_data;struct i2c_msg msg; // i2c_msg详见1.4u8 b[256];b[0] = reg;     // 目标寄存器地址存到b[0]memcpy(&b[1], buf, len); // 要发送的数据存到b[1]msg.addr = client->addr; // 从机地址(AP3216C)msg.flags = 0; // 发送。flag详见1.4msg.buf = b;// 发送内容:要读取的寄存器地址msg.len = len + 1;   // 要发送的数据长度(len) + 寄存器地址长度(1)return i2c_transfer(client->adapter, &msg, 1); // 这里要写成&msg!不是msg
}// 读取AP3216C一个寄存器
// 对ap3216c_read_regs封装
// reg 要读取的寄存器地址
static unsigned char ap3216c_read_reg(struct ap3216c_dev_struct *dev, u8 reg){u8 data = 0;ap3216c_read_regs(dev, reg, &data, 1);// 可以自己加上错误处理return data;
}// 向AP3216C一个寄存器写一个字节的数据
// 对ap3216c_write_regs封装
// reg 要写数据的寄存器地址     data 要写的数据
static void ap3216c_write_reg(struct ap3216c_dev_struct *dev, u8 reg, u8 data){u8 buf = 0;buf = data;ap3216c_write_regs(dev, reg, &buf, 1);// 可以自己加上错误处理
}// 从AP1632C读取完整的数据
void ap3216c_readdata(struct ap3216c_dev_struct *dev){unsigned char buf[6];unsigned char i =0;for(i=0;i<6;i++){buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW+i);}if(buf[0] & 0x80){ // 数据无效dev->ir = 0;dev->ps = 0;} else {dev->ir = ((unsigned short)buf[1] << 2 | (buf[0] & 0x03));dev->ps = (((unsigned short)buf[5] & 0x3F ) << 4 | (buf[4] & 0x0F));}dev->als = ((unsigned short)buf[3] << 8 | buf[2]);}//=== 操作集函数 =====================================================================static int ap3216c_open(struct inode *inode, struct file *filp){printk("open\r\n");unsigned char value = 0;filp->private_data = &ap3216c_dev;// 初始化AP3216Cap3216c_write_reg(&ap3216c_dev, AP3216C_SYSTEMCONG, 0x4); // 复位mdelay(50);ap3216c_write_reg(&ap3216c_dev, AP3216C_SYSTEMCONG, 0x3); // value = ap3216c_read_reg(&ap3216c_dev, AP3216C_SYSTEMCONG);printk("AP3216C_SYSTEMCONG = %#x\r\n",value);return 0;
}static int ap3216c_release(struct inode *inode, struct file *filp){printk("release\r\n");struct  ap3216c_dev_struct *dev = (struct ap3216c_dev_struct*)filp->private_data; return 0;}static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off){// printk("read\r\n");long err = 0;short data[3];  // 保存 ps als ifstruct ap3216c_dev_struct *dev = (struct ap3216c_dev_struct *)filp->private_data;ap3216c_readdata(dev);data[0]=dev->ir;  // 红外强度data[1]=dev->als; // 环境光强度data[2]=dev->ps;  // 接近距离err = copy_to_user(buf, data, sizeof(data));return 0;
}static const struct file_operations ap3216c_fops = {.owner = THIS_MODULE,.open = ap3216c_open,.read = ap3216c_read,.release = ap3216c_release,
};//===  匹配表  ====================  与刚才设备树节点的compatible写成一样的即可  ===================// 无设备树
static struct i2c_device_id ap3216c_id[] = {{"alientek,ap3216c", 0},{/*sentinel*/},};
// 有设备树
static struct of_device_id ap3216c_of_match[] = {{ .compatible = "alientek,ap3216c" },{/*sentinel*/},};//== i2c_driver结构体 及其函数 =================================================================
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id){printk(" == probe ==\r\n");int ret;ap3216c_dev.major = 0;  // 系统分配// 注册设备号 -------------------------------------if(ap3216c_dev.major){ // 若自定义设备号ap3216c_dev.devid = MKDEV(ap3216c_dev.major,0);ret = register_chrdev_region(ap3216c_dev.devid, AP3216C_CNT, AP3216C_NAME);}else{  // 待系统分配设备号ret = alloc_chrdev_region(&ap3216c_dev.devid, 0, AP3216C_CNT, AP3216C_NAME);ap3216c_dev.major = MAJOR(ap3216c_dev.devid);ap3216c_dev.minor = MINOR(ap3216c_dev.devid);}if(ret<0){printk("chrdev_region error!\r\n");goto fail_devid;}printk("major = %d, minor = %d\r\n",ap3216c_dev.major,ap3216c_dev.minor);// 注册字符设备cdev -------------------------------------ap3216c_dev.cdev.owner = THIS_MODULE;cdev_init(&ap3216c_dev.cdev, &ap3216c_fops);ret = cdev_add(&ap3216c_dev.cdev, ap3216c_dev.devid, AP3216C_CNT);if(ret < 0){printk("cdev_add error!\r\n");goto fail_cdev;}// 自动创建设备节点 -------------------------------------ap3216c_dev.class = class_create(THIS_MODULE, AP3216C_NAME);if(IS_ERR(ap3216c_dev.class)){printk("class create error!\r\n");ret = PTR_ERR(ap3216c_dev.class);goto fail_class;}ap3216c_dev.device = device_create(ap3216c_dev.class, NULL, ap3216c_dev.devid, NULL, AP3216C_NAME);if(IS_ERR(ap3216c_dev.device)){printk("device create error!\r\n");ret = PTR_ERR(ap3216c_dev.device);goto fail_device;}ap3216c_dev.private_data = client;return 0;fail_device:class_destroy(ap3216c_dev.class);
fail_class:cdev_del(&ap3216c_dev.cdev);
fail_cdev:unregister_chrdev_region(ap3216c_dev.devid, AP3216C_CNT);
fail_devid:return ret;
}static int ap3216c_remove(struct i2c_client *client){printk(" == remove ==\r\n");device_destroy(ap3216c_dev.class, ap3216c_dev.devid);  // 摧毁设备class_destroy(ap3216c_dev.class); // 摧毁类cdev_del(&ap3216c_dev.cdev); // 删除字符设备unregister_chrdev_region(ap3216c_dev.devid, AP3216C_CNT); // 注销设备号return 0;
}// i2c_driver
static struct i2c_driver ap3216c_driver = { // 依照2.1.2注释,把最重要的几个写上.probe = ap3216c_probe,.remove = ap3216c_remove,.driver = {.owner = THIS_MODULE,.name = "ap3216c",.of_match_table = of_match_ptr(ap3216c_of_match),},.id_table = ap3216c_id,
};// 驱动入口
static int __init ap3216c_init(void){int ret = 0;ret = i2c_add_driver(&ap3216c_driver); // 注册i2c_driverreturn ret;
}// 驱动出口
static void __exit ap3216c_exit(void){i2c_del_driver(&ap3216c_driver); // 注销i2c_driver
}module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");

3.6 应用程序 

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/ioctl.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
#include<linux/input.h>#define LEDOFF 0
#define LEDON  1/* * @description    : main主程序 * @param - argc   : argv数组元素个数 * @param - argv   : 具体参数 * @return         : 0 成功; else失败* 调用  ./ap3216cAPP /dev/ap3216c*/ int main(int argc, char *argv[]){if(argc != 2){printf("Error Usage!\r\n");return -1;}int fd, err;char *filename;int data[3];unsigned short ir, ps, als;filename = argv[1];fd = open(filename, O_RDWR);if(fd <0){printf("file %s open failed!\r\n",filename);return -1;}while(1){err = read(fd, data, sizeof(data));if(err == 0){ir = data[0];als = data[1];ps = data[2];printf("AP3216C    ir = %d, als = %d, ps = %d\r\n",ir, ps, als);}usleep(200000); // 延时200ms}err = read(fd, &data, sizeof(data));close(fd);return 0;
}

四、MPU6050驱动

        AP3216C没法验证,也不知道对不对,整个MPU6050的驱动吧。

代码参考了以下内容:

        I2C子系统–mpu6050驱动实验 — [野火]嵌入式Linux驱动开发实战指南

        MPU6050模块 — [野火]STM32模块例程介绍 文档

另外关于MPU6050的手册需要下载:

        六轴传感器模块ATK-MPU6050 — 正点原子资料下载中心 1.0.0 文档

4.1 设备树

        4.1.1 配置IIC引脚

                这里偷懒了,依然使用I2C1的引脚。具体配置详见3.1.1。

        4.1.2 配置设备节点

                下面这张图来自[野火]。

        在(ATK-MPU6050 — 正点原子资料给的网盘资料中的)《MPU-6000 and MPU-6050 Product Specification.pdf》的“9.2 I2C Interface”部分可以看到MPU6050的从机地址:

        The slave address of the MPU-60X0 is b110100X which is 7 bits long. The LSB bit of the 7 bit address is determined by the logic level on pin AD0. This allows two MPU-60X0s to be connected to the same I2C bus. When used in this configuration, the address of the one of the devices should be b1101000 (pin AD0 is logic low) and the address of the other should be b1101001 (pin AD0 is logic high).

        翻译就是:AD0拉低,从机地址为b1101000,即0x68;AD0拉高,从机地址为b1101001,即0x69。图中可以看出,AD0接了下拉电阻,所以默认以0x68作为从机地址。

        将下面这段代码写到imx6ull-alientek-emmc.dts的&i2c1里面:

	// 新增 2025/11/5  MPU6050节点MPU6050@68 {compatible = "alientek,mpu6050"; // 因为是自己实验,随便写一个就行了,写驱动时保持一致即可reg = <0x68>; // 器件地址};

 编译、复制:

make dtbs
sudo cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb /.../tftpboot/

        现在打开开发板,看看是否正确注册了0x68:

ls /sys/bus/i2c/devices/        # 应能看到0-0068
cd /sys/bus/i2c/devices/0-0068/
cat name                       # 应能看到mpu6050

4.2 文件结构

22_IIC_MPU6050 (工作区)
├── .vscode
│   ├── c_cpp_properties.json
│   └── settings.json
├── 22_iic_mpu6050.code-workspace
├── Makefile
├── mpu6050reg.h
├── mpu6050.c
└── mpu6050APP.c

4.3 Makefile

CFLAGS_MODULE += -wKERNELDIR := /....../imx6ull/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek  # 内核路径
# KERNELDIR改成自己的 linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek文件路径(这个文件从正点原子“01、例程源码”中直接搜,cp到虚拟机里面)CURRENT_PATH := $(shell pwd)	# 当前路径obj-m := mpu6050.o			    # 编译文件build: kernel_modules			# 编译模块kernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean	

 4.4 mpu6050reg.h MPU6050的各种寄存器

        本部分的截图来自正点原子资料《MPU-6000 and MPU-6050 Register Map and Descriptions.pdf》。以下简称《pdf》。

4.4.1 读取数据用到的寄存器

        在《pdf》的“3 Register Map”中可以找到陀螺仪、加速剂、温度计的输出数据寄存器:

        MPU6050的加速度计、陀螺仪每个轴的原始测量值都是16位,但是8位寄存器,因此需要两个寄存器来分别保存高/低8位。

#define MPU6050_SLAVE_ADDRESS  (0x68<<1)      // 读地址//加速度数据寄存器
#define ACCEL_XOUT_H   0x3B // X轴
#define ACCEL_XOUT_L   0x3C
#define ACCEL_YOUT_H   0x3D // Y轴
#define ACCEL_YOUT_L   0x3E
#define ACCEL_ZOUT_H   0x3F // Z轴
#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
#define GYRO_ZOUT_L    0x48

4.4.2 初始化用到的寄存器

        依照mpu6050驱动实验 — [野火]的2.5.2.2.3代码,初始化只用了这4个寄存器。
        文字描述太多了,不写了,只标注在《pdf》中的位置。

// 初始化用到的寄存器
#define PWR_MGMT_1     0x6B   // 配置时钟,是否禁用温度计,循环模式等等      详见《pdf》4.30
#define CONFIG         0x1A   // 控制陀螺仪、加速计的带宽,设置陀螺仪输出率(↓间接控制全芯片采样率),设置数字低通滤波    详见《pdf》4.3
#define SMPLRT_DIV     0x19   // 全芯片的采样率 = 陀螺仪输出速率 ÷ (该寄存器的值+1)     详见《pdf》4.2
#define ACCEL_CONFIG   0x1C   // 设置加速计 量程,设备自检,数字高通滤波   详见《pdf》4.5

        把以上两段代码都贴到mpu6050reg.h中。

4.5 驱动代码

4.5.1 框架

        还是一样先搭个框架出来。直接在3.4.1的基础上改即可。

#include<linux/module.h>
#include<linux/input.h>
#include<linux/i2c.h>
#include<linux/cdev.h>
#include<linux/device.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<linux/string.h>
#include<linux/delay.h>
#include"mpu6050reg.h"#define MPU6050_CNT 1
#define MPU6050_NAME "mpu6050"// 设备结构体 ===================================================struct mpu6050_dev_struct {dev_t devid;int major;int minor;struct cdev cdev;struct class *class;struct device *device;
};// 寄存器读写函数 ===================================================static int mpu6050_read_reg(){}
static int mpu6050_write_reg(){}// 操作集 ===================================================
static int mpu6050_open(){}
static int mpu6050_release(){}
static ssize_t mpu6050_read(){}static const struct file_operations mpu6050_fops = {.owner = THIS_MODULE,.open = mpu6050_open,.release = mpu6050_release,.read = mpu6050_read,
};// 匹配表 ===================================================static struct i2c_device_id mpu6050_id[] = {{"alientek,mpu6050"},{/*sentinel*/},
};
static struct of_device_id mpu6050_of_match[] = {{.compatible = "alientek,mpu6050"},{/*sentinel*/},
};// 总线设备结构体 ===================================================
static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id){return 0;
}static int mpu6050_remove(struct i2c_client *client){return 0;
}static struct i2c_driver mpu6050_driver = {.probe = mpu6050_probe,.remove = mpu6050_remove,.driver = {.owner = THIS_MODULE,.name = "mpu6050",.of_match_table = of_match_ptr(mpu6050_of_match),},.id_table = mpu6050_id,
};// 驱动入口/出口 ===================================================static int __init mpu6050_init(void){i2c_add_driver(&mpu6050_driver);return 0;
}static void __exit mpu6050_exit(void){i2c_del_driver(&mpu6050_driver);
}module_init(mpu6050_init);
module_exit(mpu6050_exit);
MODULE_LICENSE("GPL");

4.5.2 读写MPU6050寄存器函数

4.5.2.1 写寄存器

        在《MPU6050参考资料/MPU6050-英文原版数据手册.pdf》9.3部分可以找到时序图:

        作为主机,需要发送:
                起始信号 -> 从机地址+写标志 -> 目标寄存器地址 -> 数据(可连续发送) -> 终止信号
       其顺序与3.5.2.1基本一致。起止信号S和P是IIC控制器自动执行的,不需要像裸机那样还得手动拉电平。

// reg:目标寄存器地址  buf:要写的数据  len:要写的信息的长度
static int mpu6050_write_reg(struct mpu6050_dev_struct *dev, u8 reg, u8 *buf, int len){int err = 0;struct i2c_client *client = (struct i2c_client*)dev->client;struct i2c_msg msg;u8 b[256];b[0] = reg;memcpy(&b[1], buf, len);msg.addr = client->addr;// 从机地址(AP3216C)msg.flags = 0;          // 发送。flag详见1.4msg.buf = b;            // 发送内容:要读取的寄存器地址msg.len = len + 1;      // 要发送的数据长度(len) + 寄存器地址长度(1)err = i2c_transfer(client->adapter, &msg, 1); // 这里要写成&msg!不是msgif(err != 1){printk("mpu6050_write_reg fail!\r\n");return -1;}return 0;
}
4.3.2.2 读寄存器

// reg:目标寄存器地址  buf:要写的数据  len:要写的信息的长度
static int mpu6050_write_reg(struct mpu6050_dev_struct *dev, u8 reg, u8 *buf, int len){int err = 0;struct i2c_client *client = (struct i2c_client*)dev->client;struct i2c_msg msg;u8 b[256];b[0] = reg;memcpy(&b[1], buf, len);msg.addr = client->addr;// 从机地址(AP3216C)msg.flags = 0;          // 发送。flag详见1.4msg.buf = b;            // 发送内容:要读取的寄存器地址msg.len = len + 1;      // 要发送的数据长度(len) + 寄存器地址长度(1)err = i2c_transfer(client->adapter, &msg, 1); // 这里要写成&msg!不是msgif(err != 1){printk("mpu6050_write_reg fail!\r\n");dev_err(&dev->client->dev,"i2c write bus=%d addr=0x%02x reg=0x%02x err=%d\n",dev->client->adapter->nr, dev->client->addr, reg, err);return -1;}return 0;
}
4.5.2.3 .read函数

读取MPU6050原始6轴数据,并发送给用户态

// 读取6轴信息,并发送给用户态
static ssize_t mpu6050_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off){char data_H;char data_L;int err;short mpu6050_data[7]; //保存mpu6050的原始数据。 共6个16位数据struct mpu6050_dev_struct *dev = (struct mpu6050_dev_struct*)filp->private_data;struct i2c_client *client = (struct i2c_client*)dev->client;u8 raw[14];  // 连续14个寄存器的原始字节:AxH,AxL,  AyH,AyL,  AzH,AzL,  TempH,TempL,  GxH,GxL,  GyH,GyL,  GzH,GzLsize_t to_copy;int ret;ret = mpu6050_read_reg(dev, ACCEL_XOUT_H, raw, sizeof(raw)); // 从ACCEL_XOUT_H开始连续读取14个字节mpu6050_data[0] = (short)((u16)raw[0]  << 8 | raw[1]);  // Axmpu6050_data[1] = (short)((u16)raw[2]  << 8 | raw[3]);  // Aympu6050_data[2] = (short)((u16)raw[4]  << 8 | raw[5]);  // Az// 跳过中间的温度寄存器mpu6050_data[3] = (short)((u16)raw[8]  << 8 | raw[9]);  // Gxmpu6050_data[4] = (short)((u16)raw[10] << 8 | raw[11]); // Gympu6050_data[5] = (short)((u16)raw[12] << 8 | raw[13]); // Gzerr = copy_to_user(buf, mpu6050_data, cnt); // 发送给用户态if(err!=0){printk("copy to user fail!");return -1;}return 0;
}

4.5.3 MPU6050初始化

        下面这段代码是照着mpu6050驱动实验 — [野火]的2.5.2.2.3的代码改的。寄存器具体配置详见4.4.2。

// MPU6050设备初始化
// 要和MPU6050驱动初始化mpu6050_init区分
static int mpu6050_device_init(struct mpu6050_dev_struct *dev){int err = 0;short val;struct i2c_client *client = (struct i2c_client *)dev->client;val = 0x00;err += mpu6050_write_reg(dev, PWR_MGMT_1, &val,2); // 启动8MHz振荡器,关闭休眠,关闭睡眠和循环模式,不禁用温度计val = 0x06;err += mpu6050_write_reg(dev, CONFIG, &val,2);     // 加速度/陀螺带宽为5Hz,陀螺输出率为1kHz;关闭FSYNC采样val = 0x07;err += mpu6050_write_reg(dev, SMPLRT_DIV, &val,2); // 全芯片采样率 = 陀螺仪输出速率 ÷ (该寄存器的值+1) = 1k ÷ 8 = 125Hzval = 0x00;err += mpu6050_write_reg(dev, ACCEL_CONFIG, &val,2); // 加速度计量程设为±2g,打开加速度计高通滤波if (err < 0){printk("mpu6050_device_init fail\n");return -1;}printk("mpu6050_device_init finished!\r\n");return 0;
}

4.5.4 完整驱动代码

#include<linux/module.h>
#include<linux/input.h>
#include<linux/i2c.h>
#include<linux/cdev.h>
#include<linux/device.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<linux/string.h>
#include<linux/delay.h>
#include"mpu6050reg.h"#define MPU6050_CNT 1
#define MPU6050_NAME "mpu6050"// 设备结构体 ===================================================struct mpu6050_dev_struct {dev_t devid;int major;int minor;struct cdev cdev;struct class *class;struct device *device;struct i2c_client *client;
};
struct mpu6050_dev_struct mpu6050_dev;// mpu6050寄存器读写函数 ===================================================
// reg:目标寄存器地址  data:保存读取到的信息  len:要读取信息的长度
static int mpu6050_read_reg(struct mpu6050_dev_struct *dev, u8 reg, void *data, int len){int err = 0;struct i2c_client *client = (struct i2c_client*)dev->client;struct i2c_msg msg[2];// ①发送:要读取的寄存器的地址msg[0].addr = client->addr; // 从机地址msg[0].flags = 0;           // 发送msg[0].buf = &reg;          // 要发送的数据:目标寄存器地址msg[0].len = 1;             // 发送数据的长度// ②接收:读到的目标寄存器的数据msg[1].addr = client->addr; // 从机地址msg[1].flags = I2C_M_RD;    // 接收msg[1].buf = data;          // 接收的数据保存到datamsg[1].len = len;           // 接收数据的长度   // 发送msgerr = i2c_transfer(client->adapter, msg, 2);if (err!= 2){printk("mpu6050_write_reg fail!\r\n");return -1;}return 0;
}// reg:目标寄存器地址  buf:要写的数据  len:要写的信息的长度
static int mpu6050_write_reg(struct mpu6050_dev_struct *dev, u8 reg, u8 *buf, int len){int err = 0;struct i2c_client *client = (struct i2c_client*)dev->client;struct i2c_msg msg;u8 b[256];b[0] = reg;memcpy(&b[1], buf, len);msg.addr = client->addr;// 从机地址(AP3216C)msg.flags = 0;          // 发送。flag详见1.4msg.buf = b;            // 发送内容:要读取的寄存器地址msg.len = len + 1;      // 要发送的数据长度(len) + 寄存器地址长度(1)err = i2c_transfer(client->adapter, &msg, 1); // 这里要写成&msg!不是msgif(err != 1){printk("mpu6050_write_reg fail!\r\n");dev_err(&dev->client->dev,"i2c write bus=%d addr=0x%02x reg=0x%02x err=%d\n",dev->client->adapter->nr, dev->client->addr, reg, err);return -1;}return 0;
}
// MPU6050设备初始化
// 要和MPU6050驱动初始化mpu6050_init区分
static int mpu6050_device_init(struct mpu6050_dev_struct *dev){int err = 0;short val;struct i2c_client *client = (struct i2c_client *)dev->client;val = 0x00;err += mpu6050_write_reg(dev, PWR_MGMT_1, &val,2); // 启动8MHz振荡器,关闭休眠,关闭睡眠和循环模式,不禁用温度计val = 0x06;err += mpu6050_write_reg(dev, CONFIG, &val,2);     // 加速度/陀螺带宽为5Hz,陀螺输出率为1kHz;关闭FSYNC采样val = 0x07;err += mpu6050_write_reg(dev, SMPLRT_DIV, &val,2); // 全芯片采样率 = 陀螺仪输出速率 ÷ (该寄存器的值+1) = 1k ÷ 8 = 125Hzval = 0x00;err += mpu6050_write_reg(dev, ACCEL_CONFIG, &val,2); // 加速度计量程设为±2g,打开加速度计高通滤波if (err < 0){printk("mpu6050_device_init fail\n");return -1;}printk("mpu6050_device_init finished!\r\n");return 0;
}// 操作集 ===================================================
static int mpu6050_open(struct inode *inode, struct file *filp){printk("==mpu6050 open==\r\n");filp->private_data = &mpu6050_dev;if(mpu6050_device_init(&mpu6050_dev))return -EIO;return 0;
}
static int mpu6050_release(){printk("mpu6050_release\r\n");return 0;
}
// 读取6轴信息,并发送给用户态
static ssize_t mpu6050_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off){char data_H;char data_L;int err;short mpu6050_data[7]; //保存mpu6050的原始数据。 共6个16位数据struct mpu6050_dev_struct *dev = (struct mpu6050_dev_struct*)filp->private_data;struct i2c_client *client = (struct i2c_client*)dev->client;u8 raw[14];  // 连续14个寄存器的原始字节:AxH,AxL,  AyH,AyL,  AzH,AzL,  TempH,TempL,  GxH,GxL,  GyH,GyL,  GzH,GzLsize_t to_copy;int ret;ret = mpu6050_read_reg(dev, ACCEL_XOUT_H, raw, sizeof(raw)); // 从ACCEL_XOUT_H开始连续读取14个字节mpu6050_data[0] = (short)((u16)raw[0]  << 8 | raw[1]);  // Axmpu6050_data[1] = (short)((u16)raw[2]  << 8 | raw[3]);  // Aympu6050_data[2] = (short)((u16)raw[4]  << 8 | raw[5]);  // Az// 跳过中间的温度寄存器mpu6050_data[3] = (short)((u16)raw[8]  << 8 | raw[9]);  // Gxmpu6050_data[4] = (short)((u16)raw[10] << 8 | raw[11]); // Gympu6050_data[5] = (short)((u16)raw[12] << 8 | raw[13]); // Gzerr = copy_to_user(buf, mpu6050_data, cnt); // 发送给用户态if(err!=0){printk("copy to user fail!");return -1;}return 0;
}
static const struct file_operations mpu6050_fops = {.owner = THIS_MODULE,.open = mpu6050_open,.release = mpu6050_release,.read = mpu6050_read,
};
// 匹配表 ===================================================static struct i2c_device_id mpu6050_id[] = {{"alientek,mpu6050"},{/*sentinel*/},
};
static struct of_device_id mpu6050_of_match[] = {{.compatible = "alientek,mpu6050"},{/*sentinel*/},
};// 总线设备结构体 ===================================================
static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id){printk("   == probe ==\r\n");int ret = 0;mpu6050_dev.major = 0; // 待系统分配// 注册设备号if(mpu6050_dev.major){ // 自定义设备号mpu6050_dev.devid = MKDEV(mpu6050_dev.major,0);ret = register_chrdev_region(mpu6050_dev.devid, MPU6050_CNT, MPU6050_NAME);} else { // 系统分配设备号ret = alloc_chrdev_region(&mpu6050_dev.devid, 0, MPU6050_CNT, MPU6050_NAME);mpu6050_dev.major = MAJOR(mpu6050_dev.devid);mpu6050_dev.minor = MINOR(mpu6050_dev.devid);        }if(ret < 0){printk("chrdev region fail!\r\n");goto fail_devid;}printk("major = %d, minor = %d", mpu6050_dev.major, mpu6050_dev.minor);// 注册字符设备 cdevmpu6050_dev.cdev.owner = THIS_MODULE;cdev_init(&mpu6050_dev.cdev, &mpu6050_fops);ret = cdev_add(&mpu6050_dev.cdev, mpu6050_dev.devid, MPU6050_CNT);if(ret < 0){printk("cdev add fail!\r\n");goto fail_cdev;}// 自动创建设备节点mpu6050_dev.class = class_create(THIS_MODULE, MPU6050_NAME);if(IS_ERR(mpu6050_dev.class)){printk("class_create fail!\r\n");ret = PTR_ERR(mpu6050_dev.class);goto fail_class_create;}mpu6050_dev.device = device_create(mpu6050_dev.class,NULL,mpu6050_dev.devid, NULL, MPU6050_NAME);if(IS_ERR(mpu6050_dev.device)){printk("device create fail!\r\n");ret = PTR_ERR(mpu6050_dev.device);goto fail_device_create;}mpu6050_dev.client = client;return 0;fail_device_create:class_destroy(mpu6050_dev.class);
fail_class_create:cdev_del(&mpu6050_dev.cdev);
fail_cdev: unregister_chrdev_region(mpu6050_dev.devid, MPU6050_CNT);
fail_devid:return ret;
}
static int mpu6050_remove(struct i2c_client *client){device_destroy(mpu6050_dev.class, mpu6050_dev.devid);class_destroy(mpu6050_dev.class);cdev_del(&mpu6050_dev.cdev);unregister_chrdev_region(mpu6050_dev.devid, MPU6050_CNT);return 0;
}
static struct i2c_driver mpu6050_driver = {.probe = mpu6050_probe,.remove = mpu6050_remove,.driver = {.owner = THIS_MODULE,.name = "mpu6050",.of_match_table = of_match_ptr(mpu6050_of_match),},.id_table = mpu6050_id,
};
// 驱动入口/出口 ===================================================static int __init mpu6050_init(void){i2c_add_driver(&mpu6050_driver);return 0;
}
static void __exit mpu6050_exit(void){i2c_del_driver(&mpu6050_driver);
}
module_init(mpu6050_init);
module_exit(mpu6050_exit);
MODULE_LICENSE("GPL");

4.6 应用程序

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/ioctl.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
#include<linux/input.h>#define LEDOFF 0
#define LEDON  1/* * @description    : main主程序 * @param - argc   : argv数组元素个数 * @param - argv   : 具体参数 * @return         : 0 成功; else失败* 调用  ./mpu6050APP /dev/mpu6050*/ int main(int argc, char *argv[]){if(argc != 2){printf("Error Usage!\r\n");return -1;}int fd, err;char *filename;short data[6];filename = argv[1];fd = open(filename, O_RDWR);if(fd <0){printf("file %s open failed!\r\n",filename);return -1;}while(1){err = read(fd, data, sizeof(data));if(err == 0){printf("AX=%5d, AY=%5d, AZ=%5d ",(int)data[0],(int)data[1],(int)data[2]);printf("GX=%5d, GY=%5d, GZ=%5d\n",(int)data[3],(int)data[4],(int)data[5]);}usleep(100000); // 100ms}close(fd);return 0;
}

4.7 测试

        MPU6050只需要接四根线即可。我这里用的是imx6ull mini,MPU6050的SDA接42、SCL接43,再接上VCC和GND即可。AD0默认下拉,不用接。

   

# VScode# 设备树编译详见4.1.2,这里就不写了
make
arm-linux-gnueabihf-gcc mpu6050APP.c -o mpu6050APP
sudo cp mpu6050.ko mpu6050APP /home/for/linux/nfs/rootfs/lib/modules/4.1.15/# 串口cd /lib/modules/4.1.15/
depmod
modprobe mpu6050.ko
./mpu6050APP /dev/mpu6050

        然后就能看到数据了,不过是原始数据

http://www.dtcms.com/a/579272.html

相关文章:

  • 要在 WPF 中实现数据表对应实体的属性与 UI 控件的双向绑定,并支持修改通知和 UI 自动更新
  • v4l2 subdev 设备节点和属性创建过程
  • (论文速读)基于图像堆栈的低频超宽带SAR叶簇隐蔽目标变化检测
  • 懒人之家网站模板做网站的实践报告
  • 网站建设去哪网站注册商标
  • 垂直越权和水平越权是什么
  • 过关斩将编程题
  • 做自己看视频的网站wordpress网站备份
  • html制作手机网站做网络课堂的平台有哪些网站
  • 进程一、进程基本概念
  • 供求信息网站开发背景计算机软件开发培训机构
  • 如何做照片ppt模板下载网站微信公众平台开发者工具
  • 营销型网站建设细节东莞市建设厅官方网站
  • 网站开发合同技术目标专门做简历的网站软件
  • 4G车联网终端TBOX知识详解
  • 郑州本地做团购的网站wordpress获取自定义文章分类名
  • 自己做的网站别人怎么上网找到合作在惠州做网站
  • 中国建设银行网站密码是什么做淘宝电商比较厉害的网站
  • RFID 技术赋能汽车零件装配线:智能化升级的关键引擎
  • 1、prometheus基础理论
  • 设计社交网站手机可以建网站吗
  • 响应式网站要多久手机站点cn
  • 网站鼠标悬停动态效果古田路9号设计网站
  • 突破 GEO 优化瓶颈:孟庆涛引领的 AI 时代营销新范式
  • 网站模版编辑器企业简介范文
  • 基于Vue的智慧楼宇报修平台设计与实现066z15wb(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • php网站开发套模板步骤张家界城乡建设网站
  • 一元云购网站建设教程wordpress侧边栏插件
  • 惠州城市建设建筑网站cms网站开发网站模板
  • 18年手机网站加强机关网站内容建设