夸克之书

  • 首页
  • 科普
  • 笔记
  • .NET/C#
  • 物联网
  • 算法
  • Linux
  • 树莓派
夸克之内,别有洞天
  1. 首页
  2. 树莓派
  3. 正文

树莓派.Net Core Iot入门系列篇(3):I2C

2019-09-23 7131点热度 1人点赞 0条评论

前言

什么是I2C?

I2C,一种总线结构。I2C串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL。所有接到I2C总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。

为了避免总线信号的混乱,要求各设备连接到总线的输出端时必须是漏极开路(OD)输出或集电极开路(OC)输出。设备上的串行数据线SDA接口电路应该是双向的,输出电路用于向总线上发送数据,输入电路用于接收总线上的数据。而串行时钟线也应是双向的,作为控制总线数据传送的主机,一方面要通过SCL输出电路发送时钟信号,另一方面还要检测总线上的SCL电平,以决定什么时候发送下一个时钟脉冲电平;作为接受主机命令的从机,要按总线上的SCL信号发出或接收SDA上的信号,也可以向SCL线发出低电平信号以延长总线时钟信号周期。总线空闲时,因各设备都是开漏输出,上拉电阻Rp使SDA和SCL线都保持高电平。任一设备输出的低电平都将使相应的总线信号线变低,也就是说:各设备的SDA是“与”关系,SCL也是“与”关系。(来源:搜狗百科)

%title插图%num

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

%title插图%num

传感器接线

这里采用SHT3x系列的温湿度传感器作为示例,SHT3x湿度传感器系列包括低成本版本SHT30、标准版本SHT31,以及高端版本SHT35。与DHT系列温湿度传感器相似,SHT3x系列温湿度传感器使用同样广泛。不过SHT3x系列不同于DHT12/22之处在于SHT3x系列使用IIC通信且传感器体积更小。

SHT30中文资料下载
%title插图%num
SHT31传感器

接线

%title插图%num
接线图

程序代码

通过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);

效果图

%title插图%num
运行结果

参考链接

(1)张高兴的 .NET Core IoT 入门指南:(三)使用 I2C 进行通信

本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可
标签: 暂无
最后更新:2020-12-13

afirefish

这个人很懒,什么都没留下

打赏 点赞
< 上一篇
下一篇 >

文章评论

您需要 登录 之后才可以评论
放松一下
https://www.quarkbook.com/wp-content/uploads/2021/05/凤凰传奇-海底(Live).flac
分类
  • .NET/C#
  • Linux
  • 树莓派
  • 物联网
  • 科普
  • 笔记
  • 算法
  • 默认
最新 热点 随机
最新 热点 随机
在代码中判断龙芯新旧世界平台 Windows获取固定后缀的IPv6地址 目前为止,你可能找不到第二台支持志强的1L小主机(P350 Tiny+W-1350+ECC+双NVME+PCIE扩展)!!! iKuai(爱快)实现成都移动IPTV IPoE拨号 Linux EXT4分区误删除后数据恢复 C#连接到巴法云
在代码中判断龙芯新旧世界平台
单机Docker搭建FastDFS Debian 9开启Google BBR ESXi 6.7U3离线驱动封装 C# HTTP请求参数转实体类 23种常见的设计模式(7):原型模式 夸克之内,别有洞天
最近评论
Eagle 发布于 7 个月前(10月21日) 参考博主教程成功搞定了成都移动IPTV组播转单播,电脑、手机都可以播放了。但目前有个问题,原IPTV...
rundoze 发布于 9 个月前(08月31日) 牛逼
cc21216695 发布于 2 年前(09月27日) 试了一下,加入启动项也无效,压根没有用
afirefish 发布于 3 年前(11月28日) 非常感谢,非常棒!
》随缘《 发布于 3 年前(11月20日) 最新【一键处理】方法: https://github.com/MrXhh/VSTools/rele...
书签
  • 打赏
  • 毒鸡汤
  • 米店
  • 金鱼直播间

COPYRIGHT © 2023 quarkbook.com. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

蜀ICP备15036129号-9

登录
注册|忘记密码?