0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

详细讲解RT-Thread I2C设备驱动框架及相关函数

RTThread物联网操作系统 来源:未知 作者:李倩 2018-03-29 10:52 次阅读

本应用笔记以驱动I2C接口的6轴传感器MPU6050为例,说明了如何使用I2C设备驱动接口开发应用程序,并详细讲解了RT-Thread I2C设备驱动框架及相关函数。

1 本文的目的和结构

1.1 本文的目的和背景

I2C(或写作i2c、IIC、iic)总线是由Philips公司开发的一种简单、双向二线制(时钟SCL、数据SDA)同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息,是半导体芯片使用最为广泛的通信接口之一。RT-Thread中引入了I2C设备驱动框架,I2C设备驱动框架提供了基于GPIO模拟硬件控制器的2种底层硬件接口。

1.2 本文的结构

本文首先描述了RT-Thread I2C设备驱动框架的基本情况,然后详细描述了I2C设备驱动接口,并使用I2C设备驱动接口编写MPU6050的驱动程序,并给出了在正点原子STM32F4探索者开发板上验证的代码示例。

2 I2C设备驱动框架简介

在使用MCU进行项目开发的时候,往往需要用到I2C总线。一般来说,MCU带有I2C控制器(硬件I2C),也可以使用MCU的2个GPIO自行编写程序模拟I2C总线协议实现同样的功能。

RT-Thread提供了一套I/O设备管理框架,它把I/O设备分成了三层进行处理:应用层、I/O设备管理层、底层驱动。I/O设备管理框架给上层应用提供了统一的设备操作接口和I2C设备驱动接口,给下层提供的是底层驱动接口。应用程序通过I/O设备模块提供的标准接口访问底层设备,底层设备的变更不会对上层应用产生影响,这种方式使得应用程序具有很好的可移植性,应用程序可以很方便的从一个MCU移植到另外一个MCU。

本文以6轴惯性传感器MPU6050为例,使用RT-Thread I2C设备驱动框架提供的GPIO模拟I2C控制器的方式,阐述了应用程序如何使用I2C设备驱动接口访问I2C设备。

图2-1 RT-Thread I2C设备驱动框架

3 运行I2C设备驱动示例代码

3.1 示例代码软硬件平台

正点原子STM32F4探索者开发板

GY-521 MPU-6050模块

MDK5

RT-Thread 源码

正点原子探索者STM32F4 开发板的MCU是STM32F407ZGT6,本示例使用USB串口(USART1)发送数据及供电,使用SEGGER JLINK连接JTAG调试。

本次实验用的GY521模块是一款6轴惯性传感器模块,板载MPU6050。我们使用开发板的PD6(SCL)、PD7(SDA)作为模拟I2C管脚,用杜邦线将GY521模块的SCL硬件连接到PD6、SDA连接到PD7、GND连接到开发板的GND、VCC连接到3.3V。

图3.1-1 正点原子开发板

图3.1-2 GY521模块

本文基于正点原子STM32F4探索者开发板,给出了底层I2C驱动(GPIO模拟方式)的添加方法和I2C设备的具体应用示例代码(以驱动MPU6050为例),包含寄存器读、写操作方法。由于RT-Thread上层应用API的通用性,因此这些代码不局限于具体的硬件平台,用户可以轻松将它移植到其它平台上。

3.2 启用I2C设备驱动

使用env工具命令行进入 rt-threadspstm32f4xx-HAL 目录,然后输入menuconfig命令进入配置界面。

配置shell使用串口1:选中Using UART1,进入RT-Thread Kernel ---> Kernel Device Object菜单,修改the device name for console为uart1。

进入RT-Thread Components ---> Device Drivers菜单,选中 Using I2C device drivers,本示例使用GPIO模拟I2C,因此还要开启 Use GPIO to simulate I2C。

图3.2-1 使用menuconfig开启i2c

退出menuconfig配置界面并保存配置,在env命令行输入scons --target=mdk5 -s命令生成mdk5工程,新工程名为project。使用MDK5打开工程,修改MCU型号为STM32F407ZGTx,修改调试选项为J-LINK。

图3.2-2 修改MCU

图3.2-3 修改调试选项

2.编译工程后下载程序至开发板运行。在终端PuTTY(打开对应端口,波特率配置为115200)输入list_device命令可以看到名为i2c2的设备,设备类型是I2C Bus,说明I2C设备驱动添加成功了。如图所示:

图3.2-4使用list_device命令查看i2c总线

3.3 运行示例代码

将I2C示例代码(请回复AN0003,下载示例代码)里的main.c拷贝到 t-threadspstm32f4xx-HALapplications目录,替换原有的main.c。drv_mpu6050.c、drv_mpu6050.h拷贝到 t-threadspstm32f4xx-HALdrivers目录,并将它们添加到工程中对应分组。如图所示:

图3.3-1 添加驱动

本例使用GPIO PD6作为SCL、GPIO PD7作为SDA,I2C总线名字是i2c2,读者可根据需要修改drv_i2c.c件中如下参数以适配自己的板卡,确保drv_mpu6050.c中定义的宏MPU6050_I2C_BUS_NAME与drv_i2c.c中的宏I2C_BUS_NAME相同。本示例需要将drv_i2c.c默认驱动端口GPIOB改为GPIOD,如下图所示:

图3.3-2 drv_i2c.c中的i2c板级配置

连接好MPU6050模块和开发板,编译工程并下载程序至开发板,复位MCU,终端PuTTY会打印出读取到的MPU6050传感器数据,依次是温度,三轴加速度,三轴角速度:

图3.3-3 终端打印信息

4 I2C设备驱动接口详解

按照前文的步骤,相信读者能很快的将RT-Thread I2C设备驱动运行起来,那么如何使用I2C设备驱动接口开发应用程序呢?

RT-Thread I2C设备驱动目前只支持主机模式,使用RT-Thread I2C设备驱动需要使用menuconfig工具开启宏RT_USING_DEVICE和RT_USING_I2C,如果要使用GPIO模拟I2C还需开启宏RT_USING_I2C_BITOPS。

使用I2C设备驱动的大致流程如下:

用户可以在msh shell输入list_device命令查看已有的I2C设备,确定I2C设备名称。

查找设备使用rt_i2c_bus_device_find()或者rt_device_find(),传入I2C设备名称获取i2c总线设备句柄。

使用rt_i2c_transfer()即可以发送数据也可以接收数据,如果主机只发送数据可以使用rt_i2c_master_send(),如果主机只接收数据可以使用rt_i2c_master_recv()。

接下来本章将详细讲解I2C设备驱动接口的使用。

4.1 查找设备

应用程序要使用已经由操作系统管理的I2C设备需要调用查找设备函数,找到I2C设备后才可以对该设备进行信息传送。

函数原型:struct rt_i2c_bus_device *rt_i2c_bus_device_find(const char *bus_name)

参数 描述
bus_name I2C设备名称

函数返回:I2C设备存在则返回I2C设备句柄,否则返回RT_NULL。

本文示例代码底层驱动drv_mpu6050.c中mpu6050_hw_init()查找设备源码如下:

#define MPU6050_I2CBUS_NAME "i2c2" /* I2C设备名称,必须和drv_i2c.c注册的I2C设备名称一致 */ static struct rt_i2c_bus_device *mpu6050_i2c_bus; /* I2C设备句柄 */ ... ... ... ... int mpu6050_hw_init(void) { rt_uint8_t res; mpu6050_i2c_bus = rt_i2c_bus_device_find(MPU6050_I2CBUS_NAME); /*查找I2C设备*/ if (mpu6050_i2c_bus == RT_NULL) { MPUDEBUG("can't find mpu6050 %s device ",MPU6050_I2CBUS_NAME); return -RT_ERROR; } ... ... ... ... }

4.2 数据传输

RT-Thread I2C设备驱动的核心API是rt_i2c_transfer(),它传递的消息是链式结构的。可以通过消息链,实现调用一次完成多次数据的收发,此函数既可以用于发送数据,也可以用于接收数据。

函数原型:

rt_size_t rt_i2c_transfer(struct rt_i2c_bus_device *bus, struct rt_i2c_msg msgs[], rt_uint32_t num)

参数 描述
bus I2C总线设备句柄
msgs[] I2C消息数组
num 消息数组的数量

函数返回: 成功传输的消息数组的数量

消息数组msgs[]类型为

struct rt_i2c_msg { rt_uint16_t addr; //从机地址 rt_uint16_t flags; //标志,读、写等 rt_uint16_t len; //读写数据字节数 rt_uint8_t *buf; //读写数据指针 }

addr从机地址支持7位和10位二进制地址(flags |= RT_I2C_ADDR_10BIT)。RT-Thread的I2C设备驱动接口使用的从机地址均为不包含读写位的地址,读写位对应修改flags。

flags标志可选值为i2c.h文件中定义的宏,发送数据赋值 RT_I2C_WR,接收数据赋值RT_I2C_RD,根据需要可以与其他宏使用位运算“|”组合起来使用。

#define RT_I2C_WR 0x0000 #define RT_I2C_RD (1u << 0) #define RT_I2C_ADDR_10BIT  (1u << 2) /* this is a ten bit chip address */ #define RT_I2C_NO_START    (1u << 4) #define RT_I2C_IGNORE_NACK (1u << 5) #define RT_I2C_NO_READ_ACK (1u << 6) /* when I2C reading, we do not ACK */

4.2.1 发送数据

用户可以调用I2C设备驱动接口rt_i2c_master_send()或者rt_i2c_transfer()发送数据。函数调用关系如下:

图4.2.1-1 发送数据函数调用关系

drv_mpu6050.c中的mpu6050_write_reg()函数是MCU向mpu6050寄存器写数据。此函数的实现共有2种,分别调用了I2C设备驱动接口rt_i2c_transfer()和rt_i2c_master_send()实现。

本文示例使用的MPU6050数据手册中提到7位从机地址是110100X,X由芯片的AD0管脚决定,GY521模块的AD0连接到了GND,因此MPU6050作为从机时地址是1101000,16进制形式是0x68。写MPU6050某个寄存器,主机首先发送从机地址MPU6050_ADDR、读写标志 R/W 为 RT_I2C_WR(0 为写,1 为读),然 后主机发送从机寄存器地址reg及数据data。

使用rt_i2c_transfer()发送数据

本文示例代码底层驱动drv_mpu6050.c发送数据源码如下:

#define MPU6050_ADDR 0X68 //写mpu6050单个寄存器 //reg:寄存器地址 //data:数据 //返回值: 0,正常 / -1,错误代码 rt_err_t mpu6050_write_reg(rt_uint8_t reg, rt_uint8_t data) { struct rt_i2c_msg msgs; rt_uint8_t buf[2] = {reg, data}; msgs.addr = MPU6050_ADDR; /* 从机地址 */ msgs.flags = RT_I2C_WR; /* 写标志 */ msgs.buf = buf; /* 发送数据指针 */ msgs.len = 2; if (rt_i2c_transfer(mpu6050_i2c_bus, &msgs, 1) == 1) { return RT_EOK; } else { return -RT_ERROR; } }

以本文示例代码其中一次调用rt_i2c_transfer()发送数据为例,从机MPU6050地址16进制值为0X68,寄存器地址reg 16进制值为0X6B,发送的数据data 16进制值为0X80。示例波形如下图所示,第一个发送的数据是0XD0,第一个数据的高7位是从机地址,最低位是读写位为写(值为0),所以第一个数据为:0X68 << 1|0 = 0XD0,然后依次发送寄存器地址0X6B和数据0X80。

图4.2.1-2 I2C发送数据波形示例

使用rt_i2c_master_send()发送数据

函数原型:

rt_size_t rt_i2c_master_send(struct rt_i2c_bus_device *bus, rt_uint16_t addr, rt_uint16_t flags, const rt_uint8_t *buf, rt_uint32_t count)

参数 描述
bus I2C总线设备句柄
addr 从机地址,不包含读写位
flags 标志,读写标志为写。只支持10位地址选择RT_I2C_ADDR_10BIT
buf 指向发送数据的指针
count 发送数据字节数

函数返回: 成功发送的数据字节数。

此函是对rt_i2c_transfer()的简单封装。

本文示例代码底层驱动drv_mpu6050.c发送数据源码如下:

#define MPU6050_ADDR 0X68 //写mpu6050单个寄存器 //reg:寄存器地址 //data:数据 //返回值: 0,正常 / -1,错误代码 rt_err_t mpu6050_write_reg(rt_uint8_t reg, rt_uint8_t data) { rt_uint8_t buf[2]; buf[0] = reg; buf[1] = data; if (rt_i2c_master_send(mpu6050_i2c_bus, MPU6050_ADDR, 0, buf ,2) == 2) { return RT_EOK; } else { return -RT_ERROR; } }

4.2.2 接收数据

用户可以调用I2C设备驱动接口rt_i2c_master_recv()或者rt_i2c_transfer()接受数据。函数调用关系如下:

图4.2.2-1 接收数据函数调用关系

本文示例代码drv_mpu6050.c中的mpu6050_read_reg()函数是MCU从MPU6050寄存器读取数据,此函数的实现同样有2种方式,分别调用了I2C设备驱动接口rt_i2c_transfer()和rt_i2c_master_recv()实现。

读MPU6050某个寄存器,主机首先发送从机地址MPU6050_ADDR、读写标志 R/W 为 RT_I2C_WR(0 为写,1 为读)、从机寄存器地址reg之后才能开始读设备。然后发送从机地址MPU6050_ADDR、读写标志 R/W 为 RT_I2C_RD(0 为写,1 为读)、保存读取数据指针。

使用rt_i2c_transfer()接收数据

本文示例代码底层驱动drv_mpu6050.c接收数据源码如下:

#define MPU6050_ADDR 0X68 //读取寄存器数据 //reg:要读取的寄存器地址 //len:要读取的数据字节数 //buf:读取到的数据存储区 //返回值: 0,正常 / -1,错误代码 rt_err_t mpu6050_read_reg(rt_uint8_t reg, rt_uint8_t len, rt_uint8_t *buf) { struct rt_i2c_msg msgs[2]; msgs[0].addr = MPU6050_ADDR; /* 从机地址 */ msgs[0].flags = RT_I2C_WR; /* 写标志 */ msgs[0].buf = ® /* 从机寄存器地址 */ msgs[0].len = 1; /* 发送数据字节数 */ msgs[1].addr = MPU6050_ADDR; /* 从机地址 */ msgs[1].flags = RT_I2C_RD; /* 读标志 */ msgs[1].buf = buf; /* 读取数据指针 */ msgs[1].len = len; /* 读取数据字节数 */ if (rt_i2c_transfer(mpu6050_i2c_bus, msgs, 2) == 2) { return RT_EOK; } else { return -RT_ERROR; } }

以本文示例代码其中一次调用rt_i2c_transfer()接收数据为例,从机MPU6050地址16进制值为0X68,寄存器地址reg 16进制值为0X75。示例波形如下图所示,第一个发送的数据是0XD0,第一个数据的高7位是从机地址,最低位是读写位是写(值为0),所以第一个数据值为:0X68 << 1|0 = 0XD0,然后发送寄存器地址0X75。第二次发送的第一个数据为0XD1,读写位是读(值为1),值为:0X68 << 1 | 1 = 0XD1,然后收到读取到的数据0X68。

图4.2.2-2 I2C发送数据波形示例

使用 rt_i2c_master_recv()接收数据

函数原型:

rt_size_t rt_i2c_master_recv(struct rt_i2c_bus_device *bus, rt_uint16_t addr, rt_uint16_t flags, rt_uint8_t *buf, rt_uint32_t count)

参数 描述
bus I2C总线设备句柄
addr 从机地址,不包含读写位
flags 标志,读写标志为读,只支持10位地址选择RT_I2C_ADDR_10BIT
buf 接受数据指针
count 接收数据字节数

函数返回: 成功接收的数据字节数。

此函数是对rt_i2c_transfer()的简单封装,只能读取数据(接收数据)。

本文示例代码底层驱动drv_mpu6050.c接收数据源码如下:

#define MPU6050_ADDR 0X68 //读取寄存器数据 //reg:要读取的寄存器地址 //len:要读取的数据字节数 //buf:读取到的数据存储区 //返回值: 0,正常 / -1,错误代码 rt_err_t mpu6050_read_reg(rt_uint8_t reg, rt_uint8_t len, rt_uint8_t *buf) { if (rt_i2c_master_send(mpu6050_i2c_bus, MPU6050_ADDR, 0, ®, 1) == 1) { if (rt_i2c_master_recv(mpu6050_i2c_bus, MPU6050_ADDR, 0, buf, len) == len) { return RT_EOK; } else { return -RT_ERROR; } } else { return -RT_ERROR; } }

4.3 I2C设备驱动应用

通常I2C接口芯片的只读寄存器分为2种情况,一种是单一功能寄存器,另一种是地址连续,功能相近的寄存器。例如MPU6050的寄存器0X3B、0X3C、0X3D、0X3E、0X3F、0X40依次存放的是三轴加速度X、Y、Z轴的高8位、低8位数据。

本文示例代码底层驱动drv_mpu6050.c使用mpu6050_read_reg()函数读取MPU6050的3轴加速度数据:

#define MPU_ACCEL_XOUTH_REG 0X3B //加速度值,X轴高8位寄存器 //得到加速度值(原始值) //gx,gy,gz:陀螺仪x,y,z轴的原始读数(带符号) //返回值:0,成功/ -1,错误代码 rt_err_t mpu6050_accelerometer_get(rt_int16_t *ax, rt_int16_t *ay, rt_int16_t *az) { rt_uint8_t buf[6], ret; ret = mpu6050_read_reg(MPU_ACCEL_XOUTH_REG, 6, buf); if (ret == 0) { *ax = ((rt_uint16_t)buf[0] << 8) | buf[1];          *ay = ((rt_uint16_t)buf[2] << 8) | buf[3];          *az = ((rt_uint16_t)buf[4] << 8) | buf[5];                return RT_EOK;    }    else    {        return -RT_ERROR;    }       }

5 参考

本文所有相关的API

API 头文件
rt_i2c_transfer() rt-threadcomponentsdriversincludedriversi2c.h
rt_i2c_master_send() rt-threadcomponentsdriversincludedriversi2c.h
rt_i2c_master_recv() rt-threadcomponentsdriversincludedriversi2c.h
mpu6050_hw_init() drv_mpu6050.h
mpu6050_write_reg() drv_mpu6050.h
mpu6050_read_reg() drv_mpu6050.h
mpu6050 _temperature_get() drv_mpu6050.h
mpu6050 _gyroscope_get() drv_mpu6050.h
mpu6050 _accelerometer_get() drv_mpu6050.h

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • I2C
    I2C
    +关注

    关注

    27

    文章

    1313

    浏览量

    120441
  • 函数
    +关注

    关注

    3

    文章

    3818

    浏览量

    61075
  • 设备驱动
    +关注

    关注

    0

    文章

    63

    浏览量

    10785

原文标题:【应用笔记】小白也能玩转RT-Thread之I2C设备

文章出处:【微信号:RTThread,微信公众号:RTThread物联网操作系统】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    【先楫HPM5361EVK开发板试用体验】RT-Thread I2C使用

    数据传输并产生时钟信号,从设备被主设备寻址,同一时刻只允许有一个主设备。如下图所示: 一般情况下 MCU 的 I2C 器件都是作为主机和从机通讯,在
    发表于 12-25 23:57

    分析rt-threadI2C设备驱动框架

    简要上一篇分析了RTT的PIN驱动,得到了很多网友的认可,很开心。很多人跟我反映写一些u***,wlan等框架,这个一步一步来,从浅到深。这一篇文章我们来分析rt-threadI2C
    发表于 01-12 06:23

    RT-Thread 的 IO 设备模型框架是由哪些部分组成的呢

    RT-ThreadI/O 设备模型框架是由哪些部分组成的呢?接下来由小编给大家详细介绍一下。1、R
    发表于 03-11 18:17

    【资料】RT-Thread设备框架使用指南

    是合并进行的,如采样和保持,量化和编码在转换过程中是同时实现的。3、RT-Thread设备框架使用指南——I2C总线设备  
    发表于 03-22 16:07

    基于RT-Thread设备框架实现的tca9534软件包

    游戏机)工业自动化gpio有限的处理器产品3 支持情况4 使用说明4.1 依赖RT-Thread 3.0.0+I2C 驱动,tca9534设备使用
    发表于 05-16 16:21

    RT-ThreadI2C设备驱动框架对接如何实现

    RT-Thread中引入了I2C设备设备驱动框架,该驱动
    发表于 05-19 17:11

    将硬件I2C嫁接到RTT原生的模拟I2C驱动框架步骤分享

    。那硬件i2c驱动框架那更不在话下啦。但到了目前最新版的rt-thread OS就是没有,着实令人费解。原作者:07lhluo
    发表于 07-07 16:29

    基于STM32L431RCT6讲解如何使用I2C设备接口及相关软件包

    中。由于 RT-Thread Nano 没有设备驱动框架,所以我们要把 i2c.h 中有关完整版的内容去掉。整理完之后的
    发表于 07-18 11:24

    【原创精选】RT-Thread征文精选技术文章合集

    I2C通讯)STM32L051上使用RT-Thread (四、串口通讯)STM32L051上使用RT-Thread (五、完结篇)2. RT-Th
    发表于 07-26 14:56

    如何使用I2C设备驱动接口开发应用程序

    摘要本应用笔记以驱动 I2C 接口的 6 轴传感器 MPU6050 为例,说明了如何使用 I2C 设备驱动接口开发应用程序,并
    发表于 08-18 10:54

    如何使用RT-Thread的串口设备

    rt_hw_serial_register 决定的,该函数将串口硬件驱动RT-Thread 设备管理
    发表于 10-25 11:05

    详解RT-Thread I2C设备驱动框架相关函数

    摘要本应用笔记以驱动 I2C 接口的 6 轴传感器 MPU6050 为例,说明了如何使用 I2C 设备驱动接口开发应用程序,并
    发表于 10-25 12:02

    如何使用RT-Thread SPI设备驱动

    上运行了SPI设备驱动示例代码。最后详细描述SPI设备驱动框架接口的使用方法及参数取值。SPI
    发表于 10-25 14:20

    RT-Thread文档_I2C 总线设备

    RT-Thread文档_I2C 总线设备
    发表于 02-22 18:35 0次下载
    <b class='flag-5'>RT-Thread</b>文档_<b class='flag-5'>I2C</b> 总线<b class='flag-5'>设备</b>

    浅析RT-Thread设备驱动框架

    RT-Thread 设备框架属于组件和服务层,是基于 RT-Thread 内核之上的上层软件。设备框架
    的头像 发表于 08-07 15:39 1086次阅读