【玩转RT-Thread】I2C(内核学习)

本文涉及的产品
数据传输服务 DTS,同步至DuckDB 3个月
简介: 【玩转RT-Thread】I2C(内核学习)

一、i2c协议

由飞利浦公司开发,支持设备间的短距离通信。i2c通信需要的引脚少,硬件实现简单、可扩展性强,被广泛应用在系统内多个集成电路(IC)间的通信。

二、i2c物理层

  • i2c通信总线可连接多个i2c通信设备,支持多个通信主机和多个通信从机。i2c通信只需要两条双向总线——SDA(串行数据线)和SCL(串行时钟线)。
    SDA:用于传输数据
    SCL:用于同步数据收发
  • 每个连接到总线的设备都有一个独立地址,共7bit,主机正是利用该地址对设备进行访问
  • i2c支持多主控,任何时间点都只能有一个主控。

  • i2c器件的SDA引脚和SCL引脚是开漏电路(参照资料)形式,因此,SDA和SCL总线都需要连接上拉电阻(参照资料),当总线空闲时,两条总线均为高电平。
  • 各器件的SDA和SCL信号线在总线上都是线与关系。(即连接到总线上的任意器件输出低电平都会将总线信号拉低)

三、i2c协议层

协议层定义了i2c的通信协议。一个完整的i2c数据传输包含开始信号,器件地址,读写控制,器件内访问地址,有效数据,应答信号和结束信号。

1.i2c总线的位传输

数据传输:当SCL位高电平时,SDA必须保持稳定,SDA上传1位数据。

数据改变:当SCL为低电平时,SDA才可以改变电平

i2c位传输时序图

2.i2c总线的开始和结束信号

开始信号:SCL 为高电平时,主机将SDA 拉低,表示数据传输即将开始。

结束信号:在SDA 为低电平时,主机将SCL 拉高并保持高电平,然后在将SDA 拉高,表示传输结束。

3.i2c应答信号

  • 主机发送完每一个字节数据后,释放SDA(保持高电平),被寻址的接收器在成功接收到每一个字节后,必须产生一个应答ACK(从机将SDA拉低,使它在这个时钟脉冲的高电平期间保持稳定的低电平)
  • 从机接收不到数据或通信故障时,从机必须使SDA保持高电平,主机产生一个结束信号终止传输或者产生新的传输。

4.i2c总线的仲裁机制

  • SDA的仲裁也是建立在总线具有线与逻辑功能的原理上的。
  • 节点在发送1位数据后,比较总线上所呈现的数据与自己发送的是否一致。是,继续发送;否则,退出竞争。
  • SDA的仲裁可以保证i2c总线系统在多个主节点上同时企图控制总线时通信正常进行而且数据不丢失(总线系统通过仲裁只允许一个主节点可以继续占据总线)
  • 当SCL为高电平时,仲裁在SDA上发生。在其他主机发送低电平时,发送高电平的主机将会断开它的数据传输级,因为总线上的电平是线与连接。

四、访问i2c总线设备

一般情况下MCU 的I2C 器件都是作为主机和从机通讯,在RT-Thread 中将I2C 主机虚拟为I2C 总线设备,I2C 从机通过I2C 设备接口和I2C 总线通讯,相关接口如下所示:

函数 描述
rt_device_find() 根据I2C 总线设备名称查找设备获取设备句柄
rt_i2c_transfer() 传输数据

五、查找i2c总线设备

在使用I2C 总线设备前需要根据I2C 总线设备名称获取设备句柄,进而才可以操作I2C 总线设备,查找设备函数如下所示,

rt_device_t rt_device_find(const char* name);
参数 描述
name i2c总线设备名称
返回
设备句柄 查找到对应设备将返回相应的设备句柄
RT-NULL 没有找到相应的设备对象

一般情况下,注册到系统的I2C 设备名称为i2c0 ,i2c1 等,使用示例如下所示:

#define AHT10_I2C_BUS_NAME "i2c1" /* 传感器连接的I2C总线设备名称*/
struct rt_i2c_bus_device *i2c_bus; /* I2C总线设备句柄*/
/* 查找I2C总线设备, 获取I2C总线设备句柄*/
i2c_bus = (struct rt_i2c_bus_device *)rt_device_find(name);

六、数据传输

获取到I2C 总线设备句柄就可以使用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[] 待传输的消息数组指针
num 消息数组的元素个数
返回 -
- -
消息数组的元素个数 成功
错误码 失败
  • 和SPI 总线的自定义传输接口一样,I2C 总线的自定义传输接口传输的数据也是以一个消息为单位。
  • 参数msgs[] 指向待传输的消息数组,用户可以自定义每条消息的内容,实现I2C 总线所支持的2 种不同的数据传输模式。如果主设备需要发送重复开始条件,则需要发送2 个消息。
    !!! note “注意事项” 此函数会调用rt_mutex_take(), 不能在中断服务程序里面调用,会导致assertion报错。

I2C 消息数据结构原型如下:

struct rt_i2c_msg
{
rt_uint16_t addr; /* 从机地址*/
rt_uint16_t flags; /* 读、写标志等*/
rt_uint16_t len; /* 读写数据字节数*/
rt_uint8_t *buf; /* 读写数据缓冲区指针 */
}
  • 从机地址addr:支持7 位和10 位二进制地址,需查看不同设备的数据手册。
  • 标志flags 可取值为以下宏定义,根据需要可以与其他宏使用位运算“|” 组合起来使用。
    !!! note “注意事项” RT-Thread I2C 设备接口使用的从机地址均不包含读写位,读写位控制需修改标志flags。
#define RT_I2C_WR 0x0000 /* 写标志*/
#define RT_I2C_RD (1u << 0) /* 读标志*/
#define RT_I2C_ADDR_10BIT (1u << 2) /* 10 位地址模式*/
#define RT_I2C_NO_START (1u << 4) /* 无开始条件*/
#define RT_I2C_IGNORE_NACK (1u << 5) /* 忽视NACK */
#define RT_I2C_NO_READ_ACK (1u << 6) /* 读的时候不发送ACK */

使用示例如下所示:

#define AHT10_I2C_BUS_NAME "i2c1" /* 传感器连接的I2C总线设备名称*/
#define AHT10_ADDR 0x38 /* 从机地址*/
struct rt_i2c_bus_device *i2c_bus; /* I2C总线设备句柄*/
/* 查找I2C总线设备, 获取I2C总线设备句柄*/
i2c_bus = (struct rt_i2c_bus_device *)rt_device_find(name);
/* 读传感器寄存器数据*/
static rt_err_t read_regs(struct rt_i2c_bus_device *bus, rt_uint8_t len, rt_uint8_t
*buf)
{
  struct rt_i2c_msg msgs;
  msgs.addr = AHT10_ADDR; /* 从机地址*/
  msgs.flags = RT_I2C_RD; /* 读标志*/
  msgs.buf = buf; /* 读写数据缓冲区指针 */
  msgs.len = len; /* 读写数据字节数*/
  /* 调用I2C设备接口传输数据*/
  if (rt_i2c_transfer(bus, &msgs, 1) == 1)
  {
    return RT_EOK;
  }
  else
  {
    return -RT_ERROR;
  }
}

七、I2C 总线设备使用示例

I2C 设备的具体使用方式可以参考如下示例代码,示例代码的主要步骤如下:

  1. 首先根据I2C 设备名称查找I2C 名称,获取设备句柄,然后初始化aht10 传感器。
  2. 控制传感器的2 的函数为写传感器寄存器write_reg() 和读传感器寄存器read_regs()
    这两个函数分别调用了rt_i2c_transfer() 传输数据。读取温湿度信息的函数read_temp_humi() 则是调用这两个函数完成功能。
/*
* 程序清单: 这是一个I2C 设备使用例程
* 例程导出了i2c_aht10_sample 命令到控制终端
* 命令调用格式: i2c_aht10_sample i2c1
* 命令解释: 命令第二个参数是要使用的I2C总线设备名称, 为空则使用默认的I2C总线设备
* 程序功能: 通过I2C 设备读取温湿度传感器aht10 的温湿度数据并打印
*/
#include <rtthread.h>
#include <rtdevice.h>
#define AHT10_I2C_BUS_NAME "i2c1" /* 传感器连接的I2C总线设备名称*/
#define AHT10_ADDR 0x38 /* 从机地址*/
#define AHT10_CALIBRATION_CMD 0xE1 /* 校准命令*/
#define AHT10_NORMAL_CMD 0xA8 /* 一般命令*/
#define AHT10_GET_DATA 0xAC /* 获取数据命令*/
static struct rt_i2c_bus_device *i2c_bus = RT_NULL; /* I2C总线设备句柄*/
static rt_bool_t initialized = RT_FALSE; /* 传感器初始化状态*/
/* 写传感器寄存器*/
static rt_err_t write_reg(struct rt_i2c_bus_device *bus, rt_uint8_t reg, rt_uint8_t*data)
{
  rt_uint8_t buf[3];
  struct rt_i2c_msg msgs;
  buf[0] = reg; //cmd
  buf[1] = data[0];
  buf[2] = data[1];
  msgs.addr = AHT10_ADDR;
  msgs.flags = RT_I2C_WR;
  msgs.buf = buf;
  msgs.len = 3;
  /* 调用I2C设备接口传输数据*/
  if (rt_i2c_transfer(bus, &msgs, 1) == 1)
  {
    return RT_EOK;
  }
  else
  {
    return -RT_ERROR;
  }
}
/* 读传感器寄存器数据*/
static rt_err_t read_regs(struct rt_i2c_bus_device *bus, rt_uint8_t len, rt_uint8_t*buf)
{
  struct rt_i2c_msg msgs;
  msgs.addr = AHT10_ADDR;
  msgs.flags = RT_I2C_RD;
  msgs.buf = buf;
  msgs.len = len;
  /* 调用I2C设备接口传输数据*/
  if (rt_i2c_transfer(bus, &msgs, 1) == 1)
  {
    return RT_EOK;
  }
  else
  {
    return -RT_ERROR;
  }
}
static void read_temp_humi(float *cur_temp, float *cur_humi)
{
  rt_uint8_t temp[6];
  write_reg(i2c_bus, AHT10_GET_DATA, 0); /* 发送命令*/
  rt_thread_mdelay(400);
  read_regs(i2c_bus, 6, temp); /* 获取传感器数据*/
  /* 湿度数据转换*/
  *cur_humi = (temp[1] << 12 | temp[2] << 4 | (temp[3] & 0xf0) >> 4) * 100.0 / (1<< 20);
  /* 温度数据转换*/
  *cur_temp = ((temp[3] & 0xf) << 16 | temp[4] << 8 | temp[5]) * 200.0 / (1 << 20)- 50;
}
static void aht10_init(const char *name)
{
  rt_uint8_t temp[2] = {0, 0};
  /* 查找I2C总线设备, 获取I2C总线设备句柄*/
  i2c_bus = (struct rt_i2c_bus_device *)rt_device_find(name);
  if (i2c_bus == RT_NULL)
  {
    rt_kprintf("can't find %s device!\n", name);
  }
  else
  {
    write_reg(i2c_bus, AHT10_NORMAL_CMD, temp);
    rt_thread_mdelay(400);
    temp[0] = 0x08;
    temp[1] = 0x00;
    write_reg(i2c_bus, AHT10_CALIBRATION_CMD, temp);
    rt_thread_mdelay(400);
    initialized = RT_TRUE;
  }
}
static void i2c_aht10_sample(int argc, char *argv[])
{
  float humidity, temperature;
  char name[RT_NAME_MAX];
  humidity = 0.0;
  temperature = 0.0;
  if (argc == 2)
  {
    rt_strncpy(name, argv[1], RT_NAME_MAX);
  }
  else
  {
    rt_strncpy(name, AHT10_I2C_BUS_NAME, RT_NAME_MAX);
  }
  if (!initialized)
  {
    /* 传感器初始化*/
    aht10_init(name);
  }
  if (initialized)
  {
    /* 读取温湿度数据*/
    read_temp_humi(&temperature, &humidity);
    rt_kprintf("read aht10 sensor humidity : %d.%d %%\n", (int)humidity, (int)
    (humidity * 10) % 10);
    if( temperature >= 0 )
    {
      rt_kprintf("read aht10 sensor temperature: %d.%d°C\n", (int)temperature,
      (int)(temperature * 10) % 10);
    }
    else
    {
      rt_kprintf("read aht10 sensor temperature: %d.%d°C\n", (int)temperature,
      (int)(-temperature * 10) % 10);
    }
  }
  else
  {
    rt_kprintf("initialize sensor failed!\n");
  }
}
/* 导出到msh 命令列表中*/
MSH_CMD_EXPORT(i2c_aht10_sample, i2c aht10 sample);


相关实践学习
自建数据库迁移到云数据库
本场景将引导您将网站的自建数据库平滑迁移至云数据库RDS。通过使用RDS,您可以获得稳定、可靠和安全的企业级数据库服务,可以更加专注于发展核心业务,无需过多担心数据库的管理和维护。
Sqoop 企业级大数据迁移方案实战
Sqoop是一个用于在Hadoop和关系数据库服务器之间传输数据的工具。它用于从关系数据库(如MySQL,Oracle)导入数据到Hadoop HDFS,并从Hadoop文件系统导出到关系数据库。 本课程主要讲解了Sqoop的设计思想及原理、部署安装及配置、详细具体的使用方法技巧与实操案例、企业级任务管理等。结合日常工作实践,培养解决实际问题的能力。本课程由黑马程序员提供。
目录
相关文章
|
存储 人工智能 算法
【AI系统】计算与调度
本文探讨了计算与调度的概念,特别是在神经网络和图像处理中的应用。通过分离算法定义和计算组织,Halide 等工具能够显著提升图像处理程序的性能,同时保持代码的简洁性和可维护性。文章详细介绍了计算与调度的基本概念、调度树的构建与约束,以及如何通过调度变换优化计算性能。此外,还讨论了自动调优方法在大规模调度空间中的应用,展示了如何通过探索和预测找到最优的调度方案。
361 0
|
编解码 API
ffmpeg.c(4.3.1)源码剖析(一)
ffmpeg.c(4.3.1)源码剖析(一)
441 2
|
10月前
|
自然语言处理 API 语音技术
是时候说点方言了,Qwen-TTS上新!
Qwen-TTS更新支持北京话、上海话和四川话三种中文方言,新增七种中英双语音色。模型基于超300万小时语料训练,合成语音自然流畅,可自动调整韵律与情绪。用户可通过Qwen API便捷调用,体验多语言、多风格的高质量语音生成服务。
1998 1
|
人工智能 自然语言处理 Java
通义零码智能体测评
这是一款强大的AI辅助编程工具,核心功能包括:代码智能生成,基于上下文快速提供行级/函数级代码建议;研发智能问答,解答各类技术问题;AI程序员支持多文件协同修改与任务处理;行间代码生成,实时续写及注释转代码;编码问题解决,涵盖代码优化、问题修复及Java异常排查,全面提升开发效率。
673 4
|
Rust 关系型数据库 Linux
Rainfrog: 轻量级数据库管理工具
【10月更文挑战第3天】
434 0
|
前端开发 Java 数据库
💡Android开发者必看!掌握这5大框架,轻松打造爆款应用不是梦!🏆
在Android开发领域,框架犹如指路明灯,助力开发者加速应用开发并提升品质。本文将介绍五大必备框架:Retrofit简化网络请求,Room优化数据库访问,MVVM架构提高代码可维护性,Dagger 2管理依赖注入,Jetpack Compose革新UI开发。掌握这些框架,助你在竞争激烈的市场中脱颖而出,打造爆款应用。
1594 3
|
物联网 持续交付 开发工具
RT-Thread 学习-Env开发环境搭建(一)
RT-Thread 学习-Env开发环境搭建(一)
736 0
RT-Thread 学习-Env开发环境搭建(一)
|
存储 Linux 程序员
Linux 内核源代码情景分析(一)(上)
Linux 内核源代码情景分析(一)
302 1
|
存储 监控 负载均衡
在Linux中,如何进行集群管理?
在Linux中,如何进行集群管理?
|
监控 Linux 数据处理
lslocks:Linux系统中的锁信息查看利器
`lslocks`是Linux工具,用于查看系统上的文件锁信息,帮助诊断进程同步问题。它显示持有锁的进程、锁类型(如POSIX、flock)和状态。通过简洁的输出,用户能识别死锁和资源争用,优化性能。结合其他命令如`grep`和`awk`可增强分析能力。需适当权限运行,定期监控以预防并发访问问题,处理死锁时要谨慎。