张高兴的 .NET Core IoT 入门指南:(三)使用 I2C 进行通信
什么是 i2c 总线
i2c 总线(inter-integrated circuit bus)是设备与设备间通信方式的一种。它是一种串行通信总线,由飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展。i2c 总线包含两根信号线,一根为信号线 sda ,另一根为时钟线 scl 。总线上可以挂载多个设备,以 7 位 i2c 地址为例,总线上最多可以挂载 27 - 1 个设备,即 127 个,地址 0x00 不用(类似于网络中的广播地址)。i2c 还包括一个子集叫 smbus (system management bus),是 1995 年由 intel 提出的[2]。为什么说是子集,是因为 smbus 是 i2c 的简化版,电气特性和传输速率等方面上略有不同。下图展示了一个 i2c 主设备和三个 i2c 从设备的示意图,总线上只能有一个主设备,而通常情况下你的主机(如 raspberry pi,arduino)就是主设备,传感器为从设备。
图源:wikipedia
注意
system.device.gpio 目前并不支持 i2c repeated,i2cdevice 类尚未提供 writeread() 方法,部分设备可能无法正常通信。
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 和 system.device.i2c.drivers 命名空间下。
i2cconnectionsettings
i2cconnectionsettings
类位于 system.device.i2c 命名空间下,表示 i2c 设备的连接设置。
public sealed class i2cconnectionsettings { // 构造函数 // busid 是 i2c 总线的内部 id,在 raspberry pi 上只能填 1 // deviceaddress 是要连接设备的 i2c 地址 public i2cconnectionsettings(int busid, int deviceaddress); }
unixi2cdevice 和 windows10i2cdevice
unixi2cdevice
和 windows10i2cdevice
类位于 system.device.i2c.drivers 命名空间下。两个类均派生自抽象类 i2cdevice,分别代表 unix 和 windows10 下的 i2c 控制器,使用时按照所处的平台有选择的进行实例化。这里以 unixi2cdevice
类为例说明。
public class unixi2cdevice : i2cdevice { // 构造函数 // 需要传入一个 i2cconnectionsettings 对象 public unixi2cdevice(i2cconnectionsettings settings); // 方法 // 从从设备中读取一段数据,数据长度由 span 的长度决定 public override void read(span<byte> buffer); // 从从设备中读取一个字节的数据 public override byte readbyte(); // 向从设备中写入一段数据,通常 span 中的第一个数据为要写入数据的寄存器的地址 public override void write(readonlyspan<byte> data); // 向从设备中写入一个字节的数据,通常这个字节为寄存器的地址 public override void writebyte(byte data); }
i2c 总线的通信步骤
在开始实验之前,首先说明一下 i2c 总线的读取和写入的步骤。因为 .net 帮我们封装好了一些操作方法,这大大简化了 i2c 的操作难度,即使你没有丰富的硬件知识也可以顺利的操作硬件,所以我们不必像开发单片机一样去研究设备之间通信的时序图(当然,如果通信出现错误的话还是需要用时序图帮助判断)。
读取
-
向从设备写入要读取的寄存器的地址
这类似于数组的指针,需要先定位到相应的位置才能读取。通常地址是一位的,只需要调用
writebyte()
方法即可,但也有特殊情况,比如两个字节的地址或者命令+地址时,就需要调用write()
方法。 -
读取从设备中的数据
定位完成后就可以向从设备请求数据了。如果要读取一个字节的数据,那么就调用
readbyte()
方法,如果要读取多个字节,首先需要实例化一个byte 数组
,通过调用read()
方法来读取多个数据,读取的数据取决于数组的长度。比如要读取 8 个字节的数据,代码如下:c# span<byte> readbuffer = stackalloc byte[8]; sensor.read(readbuffer);
写入
写入一般用于配置从设备的寄存器。因为你不可能只向从设备写入寄存器的地址吧,所以通常会调用 write()
方法。比如向地址为 0x01 的寄存器写入一个字节的数据,代码如下:
span<byte> writebuffer = stackalloc byte[] { 0x01, 0xff }; sensor.write(writebuffer);
温湿度传感器读取实验
本实验选用的传感器为奥松的 dht12。主要考虑到这个传感器读取非常简单,不用配置,价格便宜,很适合用来练手。数据手册地址: 。
提示
数据手册(datasheet)是电子元件的使用说明书,包括介绍、电气特性、通信协议、性能等方面的内容。拿到数据手册时我们应该关注什么?
1. 关注该元件的通信协议。有些设备支持多种通信协议,如本实验用到的 dht12 不仅支持 i2c,还支持 1-wire 协议。选择合适的通信协议进行编程。
2. 关注打算使用的通信协议的细节。比如 i2c 总线,你需要关注元件的地址、各个寄存器的地址、最大传输速率等等。
3. 关注该元件的通信的细节。有些设备的通信很简单,并不需要拐弯抹角,但还有一些设备需要发送一些额外的命令。比如你在发送完寄存器地址后还需要紧接着发送一段命令,用于决定是读还是写该寄存器,返回数据时是按字节(byte)返回还是按字(word)返回等。
4. 关注各个寄存器的作用和配置。数据手册中基本上都会把每个寄存器逐条列出,注意细节即可。
传感器图像
硬件需求
名称 | 数量 |
---|---|
dht12 | x1 |
4.7 kω 电阻 | x2 |
杜邦线 | 若干 |
电路
- scl - scl
- sda - sda
- vcc - 5v
- gnd - gnd
如果你的 dht12 是裸板的话需要像电路图中一样给 sda 和 scl 加上上拉电阻。
代码
- 打开 visual studio ,新建一个 .net core 控制台应用程序,项目名称为“dht12”。
- 引入 system.device.gpio nuget 包。
-
新建类 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">i2cdevice,如 unixi2cdevice 和 windows10i2cdevice</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; } } }
-
在 program.cs 中,将主函数代码替换如下:
static void main(string[] args) { i2cconnectionsettings settings = new i2cconnectionsettings(1, dht12.defaulti2caddress); unixi2cdevice device = new unixi2cdevice(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); } } }
发布、拷贝、更改权限、运行
效果图
备注
下一篇文章将谈谈 spi 的使用。
上一篇: Nodejs第2天
下一篇: Socket.IO打造基础聊天室
推荐阅读