使用C#在.Net5中获取DTH11温湿度传感器数据
本文开发环境为:VS2019 + .Net5 Console App。
使用C#获取树莓派GPIO数据需要安装System.Device.Gpio包,在NuGet中搜索即可找到;DTH11温湿度传感器通过GPIO接口连接在树莓派相关引脚,其数据协议可在购买传感器时获取。在.Net5中获取DTH11温湿度传感器数据,是通过GpioController操作GPIO引脚实现的。
本文的参考文档为《c# 树莓派GPIO读取DTH11温湿度传感器数据》。主要是对GpioController的操作进行了封装,方便获取传感器数据。
另外,可以参考《用树莓派实时监测室内温湿度》,增加对树莓派和DTH11温湿度传感器的认识。
参考DTH11温湿度传感器的Arduino demo,可以了解一些GPIO操作。
需要注意的是,读取传感器数据的C#代码中,将状态设置为低电平时必须使用Thread.Sleep暂停程序,将状态设置为高电平时必须使用WaitMicroseconds方法,将其顺序交换或者使用异步的await Task.Delay都不能正确获取温湿度数据;但是在定时读取数据时,可以使用await Task.Delay语句暂停指定时间。这种情况可能的原因是,C#在操作GPIO时的暂停机制和操作系统级别的暂停机制不太一样。
最后,在发布软件的时候,由于树莓派运行Linux系统(如Raspberry Pi OS或Ubuntu),目标运行时选择“可移植的”即可,当然也可以选择具体的目标运行时。
1、DHT11.cs
/// <summary> /// 温湿度传感器类 /// </summary> // ReSharper disable once InconsistentNaming public class DHT11 { public Action<string> ErrorOccured = (message) => { }; public Action<float, float> DataReceived = (temperature, humidity) => { }; public int ReadDataInterval { set => _readDataInterval = value < _minReadDataInterval ? _minReadDataInterval : value; get => _readDataInterval; } private readonly int _pin; //传感器针脚 private readonly byte[] _dhtData = {0, 0, 0, 0, 0}; //传感器数据 private int _readDataInterval = _minReadDataInterval; //获取传感器数据时间间隔(ms) private static readonly int _minReadDataInterval = 2000; //获取传感器数据最小时间间隔(ms) private readonly GpioController _gpio; private readonly CancellationTokenSource _tokenSource; public DHT11(int pin) { _pin = pin; _tokenSource = new CancellationTokenSource(); _gpio = new GpioController(PinNumberingScheme.Board); } public Tuple<bool, string> Start() { try { if (_gpio.IsPinOpen(_pin)) { return new Tuple<bool, string>(false, $"PinOpen: {_pin}"); } //开启针脚 _gpio.OpenPin(_pin); //启动取数据线程 Task.Run(async () => { await DoWorkAsync(_tokenSource.Token); }); return new Tuple<bool, string>(true, string.Empty); } catch (Exception e) { return new Tuple<bool, string>(false, e.Message); } } public void Stop() { _tokenSource.Dispose(); } private async Task DoWorkAsync(CancellationToken token) { while (!token.IsCancellationRequested) { try { await Task.Delay(ReadDataInterval, token); if (token.IsCancellationRequested) { break; } Console.WriteLine($"{DateTime.Now: hh:mm:ss.fff} ReadData."); if (ReadData()) { var humidity = _dhtData[0] + _dhtData[1] / 100f; //湿度(%) var temperature = _dhtData[2] + _dhtData[3] / 100f; //湿度(℃) DataReceived?.Invoke(temperature, humidity); } else { Console.WriteLine($"{DateTime.Now: hh:mm:ss.fff} Receive no data."); } } catch (Exception e) { ErrorOccured?.Invoke(e.Message); break; } } _gpio.ClosePin(_pin); } private bool ReadData() { for (var i = 0; i < _dhtData.Length; i++) { _dhtData[i] = 0; } //注意:必须先Thread.Sleep(20)、WaitMicroseconds(40), //然后在循环中WaitMicroseconds(1),否则计时无法生效。 _gpio.SetPinMode(_pin, PinMode.Output); _gpio.Write(_pin, PinValue.Low); Thread.Sleep(20); _gpio.Write(_pin, PinValue.High); WaitMicroseconds(40); _gpio.SetPinMode(_pin, PinMode.Input); var lastState = PinValue.High; var j = 0; for (var i = 0; i < 85; i++) { int count = 0; while (_gpio.Read(_pin) == lastState) { count++; WaitMicroseconds(1); if (count == 255) { break; } } lastState = _gpio.Read(_pin); if (count == 255) { break; } if ((i >= 4) && (i % 2 == 0)) { _dhtData[j / 8] <<= 1; if (count > 16) { _dhtData[j / 8] |= 1; } j++; } } return j >= 40 && _dhtData[4] == ((_dhtData[0] + _dhtData[1] + _dhtData[2] + _dhtData[3]) & 0xFF); } private static void WaitMicroseconds(int microseconds) { var until = DateTime.UtcNow.Ticks + microseconds * 10; while (DateTime.UtcNow.Ticks < until) { //Do nothing } } }
2、Main
static void Main(string[] args) { try { var dht = new DHT11(7) {ReadDataInterval = 10000}; dht.ErrorOccured += (message) => { Console.WriteLine($"{DateTime.Now: hh:mm:ss.fff} ErrorOccured: {message}."); }; dht.DataReceived += (temperature, humidity) => { Console.WriteLine( $"{DateTime.Now: hh:mm:ss.fff} DataReceived: 温度 - {temperature}℃, 湿度 - {humidity}%."); }; dht.Start(); Console.ReadKey(); dht.Stop(); } catch (Exception e) { Console.WriteLine($"Main: {e.Message}"); } Console.WriteLine("Main exit"); Console.ReadKey(); }