前言
什么是I2C?
I2C,一种总线结构。I2C串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL。所有接到I2C总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。
为了避免总线信号的混乱,要求各设备连接到总线的输出端时必须是漏极开路(OD)输出或集电极开路(OC)输出。设备上的串行数据线SDA接口电路应该是双向的,输出电路用于向总线上发送数据,输入电路用于接收总线上的数据。而串行时钟线也应是双向的,作为控制总线数据传送的主机,一方面要通过SCL输出电路发送时钟信号,另一方面还要检测总线上的SCL电平,以决定什么时候发送下一个时钟脉冲电平;作为接受主机命令的从机,要按总线上的SCL信号发出或接收SDA上的信号,也可以向SCL线发出低电平信号以延长总线时钟信号周期。总线空闲时,因各设备都是开漏输出,上拉电阻Rp使SDA和SCL线都保持高电平。任一设备输出的低电平都将使相应的总线信号线变低,也就是说:各设备的SDA是“与”关系,SCL也是“与”关系。(来源:搜狗百科)
在 Raspberry Pi 的引脚中,引出了一组 I2C 接口,其内部总线 ID 为 1,引脚中的 GPIO 2 为 SDA,GPIO 3 为 SCL(如下图所示)。至于 I2C-0,它用于 Raspberry Pi 内部的 GPIO 扩展器、相机、显示器等其他设备。Raspberry Pi 的 I2C 引脚中内置了一个 1.8 kΩ 的上拉电阻,这意味着在一般情况下使用 I2C 总线时不必再连接一个额外的上拉电阻。
传感器接线
这里采用SHT3x系列的温湿度传感器作为示例,SHT3x湿度传感器系列包括低成本版本SHT30、标准版本SHT31,以及高端版本SHT35。与DHT系列温湿度传感器相似,SHT3x系列温湿度传感器使用同样广泛。不过SHT3x系列不同于DHT12/22之处在于SHT3x系列使用IIC通信且传感器体积更小。
接线
程序代码
通过Visual Studio 2019创建一个控制台应用程序,然后再Nuget包管理器安装包System.Device.Gpio。然后编写下面的代码:
static void Main(string[] args)
{
try
{
I2cConnectionSettings settings = new I2cConnectionSettings(1, (byte)I2cAddress.AddrLow);
I2cDevice device = I2cDevice.Create(settings);
using Sht3x sensor = new Sht3x(device);
while (true)
{
// read temperature (℃)
double temperature = sensor.Temperature;
// read humidity (%)
double humidity = sensor.Humidity;
Console.WriteLine($"Temperature: {temperature.ToString("0.0")} ℃, Humidity: { humidity.ToString("0.0")} %");
Thread.Sleep(1500);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
SHTx.cs
/// <summary>
/// 来源于 iot.Device.Sht3x
/// https://github.com/dotnet/iot/tree/master/src/devices/Sht3x
/// </summary>
class Sht3x : IDisposable
{
private I2cDevice _i2cDevice;
// CRC const
private const byte CRC_POLYNOMIAL = 0x31;
private const byte CRC_INIT = 0xFF;
#region prop
/// <summary>
/// SHT3x Resolution
/// </summary>
public Resolution Resolution { get; set; }
private double _temperature;
/// <summary>
/// SHT3x Temperature
/// </summary>
public double Temperature
{
get
{
ReadTempAndHumi();
return _temperature;
}
}
private double _humidity;
/// <summary>
/// SHT3x Relative Humidity (%)
/// </summary>
public double Humidity { get { ReadTempAndHumi(); return _humidity; } }
private bool _heater;
/// <summary>
/// SHT3x Heater
/// </summary>
public bool Heater
{
get => _heater;
set
{
SetHeater(value);
_heater = value;
}
}
#endregion
/// <summary>
/// Creates a new instance of the SHT3x
/// </summary>
/// <param name="i2cDevice">The I2C device used for communication.</param>
/// <param name="resolution">SHT3x Read Resolution</param>
public Sht3x(I2cDevice i2cDevice, Resolution resolution = Resolution.High)
{
_i2cDevice = i2cDevice;
Resolution = resolution;
Reset();
}
/// <summary>
/// Cleanup
/// </summary>
public void Dispose()
{
_i2cDevice?.Dispose();
_i2cDevice = null;
}
/// <summary>
/// SHT3x Soft Reset
/// </summary>
public void Reset()
{
Write(Register.SHT_RESET);
}
/// <summary>
/// Set SHT3x Heater
/// </summary>
/// <param name="isOn">Heater on when value is true</param>
private void SetHeater(bool isOn)
{
if (isOn)
Write(Register.SHT_HEATER_ENABLE);
else
Write(Register.SHT_HEATER_DISABLE);
}
/// <summary>
/// Read Temperature and Humidity
/// </summary>
private void ReadTempAndHumi()
{
Span<byte> writeBuff = stackalloc byte[] { (byte)Register.SHT_MEAS, (byte)Resolution };
Span<byte> readBuff = stackalloc byte[6];
_i2cDevice.Write(writeBuff);
// wait SCL free
Thread.Sleep(20);
_i2cDevice.Read(readBuff);
// Details in the Datasheet P13
int st = (readBuff[0] << 8) | readBuff[1]; // Temp
int srh = (readBuff[3] << 8) | readBuff[4]; // Humi
// check 8-bit crc
bool tCrc = CheckCrc8(readBuff.Slice(0, 2), readBuff[2]);
bool rhCrc = CheckCrc8(readBuff.Slice(3, 2), readBuff[5]);
if (tCrc == false || rhCrc == false)
{
return;
}
// Details in the Datasheet P13
_temperature = Math.Round(st * 175 / 65535.0 - 45, 1);
_humidity = Math.Round(srh * 100 / 65535.0, 1);
}
/// <summary>
/// 8-bit CRC Checksum Calculation
/// </summary>
/// <param name="data">Raw Data</param>
/// <param name="crc8">Raw CRC8</param>
/// <returns>Checksum is true or false</returns>
private bool CheckCrc8(ReadOnlySpan<byte> data, byte crc8)
{
// Details in the Datasheet P13
byte crc = CRC_INIT;
for (int i = 0; i < 2; i++)
{
crc ^= data[i];
for (int j = 8; j > 0; j--)
{
if ((crc & 0x80) != 0)
crc = (byte)((crc << 1) ^ CRC_POLYNOMIAL);
else
crc = (byte)(crc << 1);
}
}
return crc == crc8;
}
private void Write(Register register)
{
byte msb = (byte)((short)register >> 8);
byte lsb = (byte)((short)register & 0xFF);
Span<byte> writeBuff = stackalloc byte[] { msb, lsb };
_i2cDevice.Write(writeBuff);
// wait SCL free
Thread.Sleep(20);
}
}
/// <summary>
/// SHT3x I2C Address
/// </summary>
public enum I2cAddress : byte
{
/// <summary>
/// ADDR (pin2) connected to logic low (Default)
/// </summary>
AddrLow = 0x44,
/// <summary>
/// ADDR (pin2) connected to logic high
/// </summary>
AddrHigh = 0x45
}
/// <summary>
/// SHT3x Resolution (No Clock Stretching)
/// </summary>
public enum Resolution : byte
{
/// <summary>High resolution</summary>
High = 0x00,
/// <summary>Medium resolution</summary>
Medium = 0x0B,
/// <summary>Low resolution</summary>
Low = 0x16
}
/// <summary>
/// SHT3x Register
/// </summary>
internal enum Register : ushort
{
SHT_MEAS = 0x24,
SHT_RESET = 0x30A2,
SHT_HEATER_ENABLE = 0x306D,
SHT_HEATER_DISABLE = 0x3066
}
IIC通讯步骤
通过程序,可以简单归纳IIC的通讯步骤。首先通过I2cConnectionSettings指定I2C设备的ID,树莓派中I2C设备ID默认为1,然后指定起始地址。之后就可以通过Create方法创建一个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);
效果图
参考链接
文章评论