什么是 I2C 总线

I2C 总线(Inter-Integrated Circuit Bus)是设备与设备间通信方式的一种。它是一种串行通信总线,由飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展[1]。I2C 总线包含两根信号线,一根为信号线 SDA ,另一根为时钟线 SCL 。总线上可以挂载多个设备,以 7 位 I2C 地址为例,总线上最多可以挂载 27 - 1 个设备,即 127 个,地址 0x00 不用(类似于网络中的广播地址)。I2C 还包括一个子集叫 SMBus (System Management Bus),是 1995 年由 Intel 提出的[2]。为什么说是子集,是因为 SMBus 是 I2C 的简化版,电气特性和传输速率等方面上略有不同。下图展示了一个 I2C 主设备和三个 I2C 从设备的示意图,总线上只能有一个主设备,而通常情况下你的主机(如 Raspberry Pi,Arduino)就是主设备,传感器为从设备。

图1:I2C 设备

I2C 总线也并不是那么完美。因为 I2C 只有两根信号线,与 SPI 的四根信号线相比,传输速率上并不占优,而且数据在同一时间内只能向一个方向传输。但反过来看,I2C 总线的最大优点是只需要占用两个 IO 接口,在单片机等 IO 接口数量较少的设备上也算是一种优势吧。

在 Raspberry Pi 的引脚中,引出了一组 I2C 接口,其内部总线 ID 为 1,引脚中的 GPIO 2 为 SDA,GPIO 3 为 SCL(如下图所示)。至于 I2C-0,它用于 Raspberry Pi 内部的 GPIO 扩展器、相机、显示器等其他设备。Raspberry Pi 的 I2C 引脚中内置了一个 1.8 kΩ 的上拉电阻,这意味着在一般情况下使用 I2C 总线时不必再连接一个额外的上拉电阻。

Raspberry Pi B+/2B/3B/3B+/Zero 引脚图

相关类

I2C 操作的相关类位于 System.Device.I2c 命名空间下。

I2cConnectionSettings

I2cConnectionSettings 类位于 System.Device.I2c 命名空间下,表示 I2C 设备的连接设置。

public sealed class I2cConnectionSettings
{
// 构造函数
// busId 是 I2C 总线的内部 ID,在 Raspberry Pi 上只能填 1
// deviceAddress 是要连接设备的 I2C 地址
public I2cConnectionSettings(int busId, int deviceAddress);
}

I2cDevice

I2cDevice 是一个抽象类,通过单例模式创建具体的对象。具体实现是通过两个内部类 UnixI2cDeviceWindows10I2cDevice ,分别代表 Unix 和 Windows10 下的 I2C 控制器。

public abstract partial class I2cDevice : IDisposable
{
// 创建 I2cDevice 对象
public static I2cDevice Create(I2cConnectionSettings settings); // 从从设备中读取一段数据,数据长度由 Span 的长度决定
public abstract void Read(Span<byte> buffer);
// 从从设备中读取一个字节的数据
public abstract byte ReadByte(); // 向从设备中写入一段数据,通常 Span 中的第一个数据为要写入数据的寄存器的地址
public abstract void Write(ReadOnlySpan<byte> data);
// 向从设备中写入一个字节的数据,通常这个字节为寄存器的地址
public abstract void WriteByte(byte data); // 向从设备中写入一段数据,然后从该设备中读取一段数据
// 该方法多用于 SMBus 协议
public abstract void WriteRead(ReadOnlySpan<byte> writeBuffer, Span<byte> readBuffer);
}

I2C 总线的通信步骤

在开始实验之前,首先说明一下 I2C 总线的读取和写入的步骤。因为 .NET 帮我们封装好了一些操作方法,这大大简化了 I2C 的操作难度,即使你没有丰富的硬件知识也可以顺利的操作硬件,所以我们不必像开发单片机一样去研究设备之间通信的时序图(当然,如果通信出现错误的话还是需要用时序图帮助判断)。

读取

  1. 向从设备写入要读取的寄存器的地址

    这类似于数组的指针,需要先定位到相应的位置才能读取。通常地址是一位的,只需要调用 WriteByte() 方法即可,但也有特殊情况,比如两个字节的地址或者命令+地址时,就需要调用 Write() 方法。

  2. 读取从设备中的数据

    定位完成后就可以向从设备请求数据了。如果要读取一个字节的数据,那么就调用 ReadByte() 方法,如果要读取多个字节,首先需要实例化一个 byte 数组,通过调用 Read() 方法来读取多个数据,读取的数据取决于数组的长度。比如要读取 8 个字节的数据,代码如下:

    Span<byte> readBuffer = stackalloc byte[8];
    sensor.Read(readBuffer);

写入

写入一般用于配置从设备的寄存器。因为你不可能只向从设备写入寄存器的地址吧,所以通常会调用 Write() 方法。比如向地址为 0x01 的寄存器写入一个字节的数据,代码如下:

Span<byte> writeBuffer = stackalloc byte[] { 0x01, 0xFF };
sensor.Write(writeBuffer);

温湿度传感器读取实验

本实验选用的传感器为奥松的 DHT12。主要考虑到这个传感器读取非常简单,不用配置,价格便宜,很适合用来练手。数据手册地址:https://wenku.baidu.com/view/325b7096eff9aef8941e06f9.html

  提示

数据手册(Datasheet)是电子元件的使用说明书,包括介绍、电气特性、通信协议、性能等方面的内容。拿到数据手册时我们应该关注什么?

1. 关注该元件的通信协议。有些设备支持多种通信协议,如本实验用到的 DHT12 不仅支持 I2C,还支持 1-Wire 协议。选择合适的通信协议进行编程。

2. 关注打算使用的通信协议的细节。比如 I2C 总线,你需要关注元件的地址、各个寄存器的地址、最大传输速率等等。

3. 关注该元件的通信的细节。有些设备的通信很简单,并不需要拐弯抹角,但还有一些设备需要发送一些额外的命令。比如你在发送完寄存器地址后还需要紧接着发送一段命令,用于决定是读还是写该寄存器,返回数据时是按字节(byte)返回还是按字(word)返回等。

4. 关注各个寄存器的作用和配置。数据手册中基本上都会把每个寄存器逐条列出。

传感器图像

硬件需求

名称 数量
DHT12 x1
4.7 kΩ 电阻 x2
杜邦线 若干

电路

  • SDA - SDA (Pin 3)
  • SCL - SCL (Pin 5)
  • VCC - 5V
  • GND - GND

如果你的 DHT12 是裸板的话需要像电路图中一样给 SDA 和 SCL 加上上拉电阻。

使用 Docker 运行示例

示例地址:https://github.com/ZhangGaoxing/dotnet-core-iot-demo/tree/master/src/Dhtxx

docker build -t dht-sample -f Dockerfile .
docker run --rm -it --device /dev/i2c-1 dht-sample

代码

  1. 打开 Visual Studio ,新建一个 .NET Core 控制台应用程序,项目名称为“Dht12”。

  2. 引入 System.Device.Gpio NuGet 包。

  3. 新建类 Dht12,替换如下代码:

    public class Dht12 : IDisposable
    {
    /// <summary>
    /// DHT12 默认 I2C 地址
    /// </summary>
    public const byte DefaultI2cAddress = 0x5C; // 若数据手册中给的是8位的I2C地址要记得右移1位 private I2cDevice _sensor; private double _temperature;
    /// <summary>
    /// DHT12 温度
    /// </summary>
    public double Temperature
    {
    get
    {
    ReadData();
    return _temperature;
    }
    } private double _humidity;
    /// <summary>
    /// DHT12 湿度
    /// </summary>
    public double Humidity
    {
    get
    {
    ReadData();
    return _humidity;
    }
    } /// <summary>
    /// 实例化一个 DHT12 对象
    /// </summary>
    /// <param name="sensor">I2C Device</param>
    public Dht12(I2cDevice sensor)
    {
    _sensor = sensor;
    } private void ReadData()
    {
    Span<byte> readBuff = stackalloc byte[5]; // 数据手册第三页提供了寄存器地址表 // DHT12 湿度寄存器地址
    _sensor.WriteByte(0x00);
    // 连续读取数据
    // 湿度整数位,湿度小数位,温度整数位,温度小数位,校验和
    _sensor.Read(readBuff); // 校验数据,校验方法见数据手册第五页
    // 校验位=湿度高位+湿度低位+温度高位+温度低位
    if ((readBuff[4] == ((readBuff[0] + readBuff[1] + readBuff[2] + readBuff[3]) & 0xFF)))
    {
    // 温度小数位的范围在0-9,所以与上0x7F即可
    double temp = readBuff[2] + (readBuff[3] & 0x7F) * 0.1;
    // 温度小数位第8个bit为1则表示采样得出的温度为负温
    temp = (readBuff[3] & 0x80) == 0 ? temp : -temp; double humi = readBuff[0] + readBuff[1] * 0.1; _temperature = temp;
    _humidity = humi;
    }
    else
    {
    _temperature = double.NaN;
    _humidity = double.NaN;
    }
    }
    }
  4. Program.cs 中,将主函数代码替换如下:

    static void Main(string[] args)
    {
    I2cConnectionSettings settings = new I2cConnectionSettings(1, Dht12.DefaultI2cAddress);
    I2cDevice device = I2cDevice.Create(settings); using (Dht12 dht = new Dht12(device))
    {
    while (true)
    {
    Console.WriteLine($"Temperature: {dht.Temperature.ToString("0.0")} °C, Humidity: {dht.Humidity.ToString("0.0")} %"); Thread.Sleep(2000);
    }
    }
    }
  5. 发布、拷贝、更改权限、运行

效果图

供参考

  1. I2C - Wikipedia:https://en.wikipedia.org/wiki/I²C
  2. I2C source code:https://github.com/dotnet/iot/tree/master/src/System.Device.Gpio/System/Device/I2c
  3. I2C总线 - 百度百科:https://baike.baidu.com/item/I2C总线/918424
  4. SMBus - 百度百科:https://baike.baidu.com/item/SMBus/1190006

张高兴的 .NET Core IoT 入门指南:(三)使用 I2C 进行通信的更多相关文章

  1. 张高兴的 .NET Core IoT 入门指南:(二)GPIO 的使用

    什么是 GPIO GPIO 是 General Purpose Input Output 的缩写,即"通用输入输出". Raspberry Pi 有两行 GPIO 引脚, Rasp ...

  2. 张高兴的 .NET Core IoT 入门指南:(一)环境配置、Blink、部署

    如何在 Raspberry Pi 的 Raspbian 上构建使用 GPIO 引脚的 IoT 程序?你可能会回答使用 C++ 或 Python 去访问 Raspberry Pi 的引脚.现在,C# 程 ...

  3. 张高兴的 .NET Core IoT 入门指南:(四)使用 SPI 进行通信

    什么是 SPI 和上一篇文章的 I2C 总线一样,SPI(Serial Peripheral Interface,串行外设接口)也是设备与设备间通信方式的一种.SPI 是一种全双工(数据可以两个方向同 ...

  4. 张高兴的 .NET Core IoT 入门指南:(五)PWM 信号输出

    什么是 PWM 在解释 PWM 之前首先来了解一下电路中信号的概念,其中包括模拟信号和数字信号.模拟信号是一种连续的信号,与连续函数类似,在图形上表现为一条不间断的连续曲线.数字信号为只能取有限个数值 ...

  5. 张高兴的 .NET Core IoT 入门指南:(五)串口通信入门

    在开始之前,首先要说明的是串口通信所用到的 SerialPort 类并不包含在 System.Device.Gpio NuGet 包中,而是在 System.IO.Ports NuGet 包中.之所以 ...

  6. 张高兴的 .NET IoT 入门指南:(七)制作一个气象站

    距离上一篇<张高兴的 .NET Core IoT 入门指南>系列博客的发布已经过去 2 年的时间了,2 年的时间 .NET 版本发生了巨大的变化,.NET Core 也已不复存在,因此本系 ...

  7. 张高兴的 .NET IoT 入门指南:(八)基于 GPS 的 NTP 时间同步服务器

    时间究竟是什么?这既可以是一个哲学问题,也可以是一个物理问题.古人对太阳进行观测,利用太阳的投影发明了日晷,定义了最初的时间.随着科技的发展,天文观测的精度也越来越准确,人们发现地球的自转并不是完全一 ...

  8. require.js入门指南(三)

    *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...

  9. kotlin 语言入门指南(三)--编码习惯

    这章主要讲当前 kotlin 的一些编码习惯. 命名 如无疑问,kotlin 的命名风格与以下的java风格一样: --驼峰命名法(不要使用下划线) --类名首字母大写 --方法和属性名首字母小写 - ...

随机推荐

  1. 转:linux下共享库的注意点之-fpic

    转: http://www.cnblogs.com/leo0000/p/5691483.html linux下共享库的注意点之-fpic 在编译共享库必须加上-fpic.这是为什么呢? 首先看一个简单 ...

  2. PHP生成excel(3)

    这一节主要是设置背景颜色.边框.字体大小.表格宽度 效果图 代码如下 <?php header("Content-Type:text/html;charset=utf-8") ...

  3. Linux - Unix环境高级编程(第三版) 代码编译

    Unix环境高级编程(第三版) 代码编译 本文地址:http://blog.csdn.net/caroline_wendy 时间:2014.10.2 1. 下载代码:http://www.apuebo ...

  4. Qt5官方demo解析集13——Qt Quick Particles Examples - Image Particles

    本系列全部文章能够在这里查看http://blog.csdn.net/cloud_castle/article/category/2123873 接上文 Qt5官方demo解析集12--Qt Quic ...

  5. sql建表,建索引注意事项

    建表注意 .建议字段定义为NOT NULL 搜索引擎 MyISAM InnoDB 区别 InnoDB和MyISAM是许多人在使用MySQL时最常用的两个表类型,这两个表类型各有优劣,视具体应用而定.基 ...

  6. intellij IDEA 更新java后不用重启tomcat

    最近项目大了,每次修改后重启都要等和很久,那个煎熬…… 为了解决这个问题,万能的Google  装了这个  JREBEL 5.63最新的 安装步骤: 一.IDEA在线搜索 jrebel  安装 二.破 ...

  7. 使用POCO发送HTTP(S)请求

    POCO GitHub地址https://github.com/pocoproject/poco http_example.cpp #include <iostream> #include ...

  8. Joomla中的Task 和view 深入学习

    [本文转自:梦溪笔记] Joomla 是一个优秀的CMS系统,她可以让你快速的完成一个网站的建设,她提供组件,模块,模板能够满足你大部分的网站需求.而组件在其中举足轻重. 一.基本知识 组件(comp ...

  9. Springboot读取自定义的yml文件中的List对象

    Yml文件(novellist.xml)如下: novellist:   list:     - name: 笑傲江湖       type: 武侠       master: 令狐冲       a ...

  10. Parallels Desktop 设置win网络连接

    目的: 1 虚拟机中的win系统技能访问外网 2 可以和Mac系统互联 首先来实现1,很简单: 打开控制中心对应系统的设置 选择[硬件]->[网络] 源:设置共享网络 到此就达到1目的了: 现在 ...