上一篇中我们把基本的运行环境搭建完成了,这一篇中,我们实战通过树莓派B+连接HC-SR04超声波测距传感器,用c# GPIO控制传感器完成距离测定,并将距离显示在网页上.
1.HC-SR04接线
传感器如下图:
HC-SR04 模块可以测量 3cm – 4m 的距离,精确度可以达到 3mm.这个模块包括 超声波发射器、超声波接收器和控制电路三部分.该传感器有4个引脚:
VCC, 超声波模块电源脚,接5V电源即可
Trig, 超声波发送脚
Echo,超声波接收检测脚
GND,接地
1.1HC-SR04超声波模块工作原理:
(1) 树莓派向 Trig 脚发送一个至少 10us 的脉冲信号。
(2) HC-SR04 接收到信号,开始发送超声波,并把 Echo置为高电平,然后准备接收返回的超声波
(3) HC-SR04 接收到返回的超声波,把 Echo 置为低电平。
(4) Echo 高电平持续的时间就是超声波从发射到返回的时间间隔。
(5) 计算距离:距离(单位:m) = (start - end) * 声波速度 / 2
1.2 接线
4 个引脚由 2 个电源引脚(Vcc 、GND)和 2 个控制引脚(Trig、Echo)组成。
Vcc 和 Gnd 接 5v DC 电源,但不推荐用独立电源给它供电,应使用树莓派的 GPIO 口输出 5v 和 Gnd 给它供电。不然会影响这个模块的运行。
Trig 引脚用来接收来自树莓派的控制信号。接任意 GPIO 口。
Echo 引脚用来发送测距结果给树莓派。接任意 GPIO 口。
对应树莓派40pin引脚对照表:
我这里把Trlg接到23,Echo接到24,VCC接到5V,(BCM编码方式)GND接GND:
这样线就接好了.开始编码阶段.
2.c# 程序
打开上一章建立的空项目首先在Models新建一个类 SiteConfig:
public class SiteConfig { ////// 超声波控制端 默认23 /// public int TriggerPin { get; set; } /// /// 超声波接收端 默认24 /// public int EchoPin { get; set; } }
之后在 appsettings.json 中添加 这个节点:
"SiteConfig": { "TriggerPin": 23, "EchoPin": 24, }
在 Startup.cs 类的ConfigureServices 方法添加
services.Configure(Configuration.GetSection("SiteConfig"));
这样通过依赖注入我们就可以使用我们配置的变量了.这些无关紧要的东西写完后,我们在项目中新建文件夹 Playground 并在这个文件下建立Ultrasonic文件夹:
在Ultrasonic里面建立三个文件 IHcsr04Client.cs,Hcsr04Client.cs,Hcsr04ReadEventArgs.cs
IHcsr04Client:
public interface IHcsr04Client { event EventHandlerOnDataAvailable; void Start(); void Stop(); }
Hcsr04Client:
public class Hcsr04Client : IHcsr04Client { private readonly int _echo; private readonly int _trigger; private int _lastMeasurment = 0; public const int NoObstacleDistance = -1; private readonly object _locker = new object(); private readonly GpioController _controller; private readonly Stopwatch _timer = new Stopwatch(); public event EventHandlerOnDataAvailable; public bool IsRunning { get; set; } public Hcsr04Client(IOptions option, GpioController controller) { _echo = option.Value.EchoPin; _trigger = option.Value.TriggerPin; _controller = controller; } public void Start() { lock (_locker) { IsRunning = true; if (!_controller.IsPinOpen(_echo)) _controller.OpenPin(_echo, PinMode.Input); if (!_controller.IsPinOpen(_trigger)) { _controller.OpenPin(_trigger, PinMode.Output); _controller.Write(_trigger, PinValue.Low); } Task.Run(() => PerformContinuousReads()); } } public void Stop() { lock (_locker) { IsRunning = false; if (_controller.IsPinOpen(_trigger)) _controller.ClosePin(_trigger); if (_controller.IsPinOpen(_echo)) _controller.ClosePin(_echo); } } private void PerformContinuousReads() { while (IsRunning) { var sensorData = RetrieveSensorData(); if (!IsRunning) continue; OnDataAvailable?.Invoke(this, sensorData); Thread.Sleep(200); } } private Hcsr04ReadEventArgs RetrieveSensorData() { try { _timer.Reset(); while (Environment.TickCount - _lastMeasurment < 60) { Thread.Sleep(TimeSpan.FromMilliseconds(Environment.TickCount - _lastMeasurment)); } _controller.Write(_trigger, PinValue.High); // trigger上高电平 Thread.Sleep(TimeSpan.FromMilliseconds(0.01)); // 持续一段时间,发出足够的脉冲 _controller.Write(_trigger, PinValue.Low); // 设置低电平 if (!GpioEX.WaitForValue(_controller, _echo, PinValue.Low)) // echo等待低电平结束,记录时间 throw new TimeoutException(); _lastMeasurment = Environment.TickCount; _timer.Start(); if (!GpioEX.WaitForValue(_controller, _echo, PinValue.High)) // echo等待高电平结束,记录时间 throw new TimeoutException(); _timer.Stop(); TimeSpan elapsed = _timer.Elapsed; var distance = elapsed.TotalMilliseconds / 2.0 * 34.3; return new Hcsr04ReadEventArgs(distance); } catch { return Hcsr04ReadEventArgs.CreateInvalidReading(); } } }
Hcsr04ReadEventArgs:
public class Hcsr04ReadEventArgs : EventArgs { internal Hcsr04ReadEventArgs(double distance) { Distance = distance; } private Hcsr04ReadEventArgs(bool isValid) : this(Hcsr04Client.NoObstacleDistance) { IsValid = isValid; } ////// 读数是否有效. /// public bool IsValid { get; } = true; /// /// 是否检测到任何障碍物. /// public bool HasObstacles => Distance != Hcsr04Client.NoObstacleDistance; /// /// 获取到障碍物的实际距离,以厘米为单位. /// public double Distance { get; } internal static Hcsr04ReadEventArgs CreateInvalidReading() => new Hcsr04ReadEventArgs(false);
最后在注入到服务:
services.AddSingleton();
代码都非常简单,也有相关注释.大家看一眼就懂了.之后就是网网页上展示了.
先弄一个Controller,在Controllers文件新建一个Hcsr04Controller和CarController的控制器:
public class Hcsr04Controller : Controller { public static string iscsb = "stop"; private readonly IHcsr04Client _hcsr04; private readonly IHubContext_chatHub; public Hcsr04Controller(IHcsr04Client hcsr04, IHubContext chatHub) { _hcsr04 = hcsr04; _chatHub = chatHub; } public async Task Hcsr04On() { _hcsr04.OnDataAvailable += async (s, e) => { if (!e.IsValid) { await _chatHub.Clients.All.SendAsync("ReceiveMessage", "1", "声波没有返回,被折射掉了."); } else if (e.HasObstacles) { await _chatHub.Clients.All.SendAsync("ReceiveMessage", "1", $"距离:{e.Distance:N2}cm."); } else { await _chatHub.Clients.All.SendAsync("ReceiveMessage", "1", "未检测到障碍物."); } }; _hcsr04.Start(); iscsb = "start"; await _chatHub.Clients.All.SendAsync("ReceiveMessage", "50", "开启超声波通知."); return Content("超声波打开"); } public async Task Hcsr04Off() { _hcsr04.Stop(); iscsb = "stop"; await _chatHub.Clients.All.SendAsync("ReceiveMessage", "51", "超声波关闭通知."); return Content("超声波关闭"); } }
我这里面使用了signalR,方便数据到达时候在web上面展示,SignalR的内容这里就不说了,就是简单的使用.
CarController就什么都不用改了.之后新建一个视图:
代码:
@{ ViewData["Title"] = "智能小车控制面板"; } @section Css{ "~/css/car.css" rel="stylesheet" /> }class="text-center">class="display-4">控制面板
温度:"wd">35°C 湿度:"sd">50%
- class="Switch">
- "checkbox" name="Storage" id="csb" onclick="KZCSB(this)" /> 超声波
- "checkbox" name="Storage2" id="bz" onclick="KZBZ(this)" /> 红外避障
- "checkbox" name="Storage2" id="wf" onclick="KZWIFIYK(this)" checked="checked" /> WiFi遥控
- "checkbox" name="Storage2" id="hw" onclick="KZHWYK(this)" /> 红外遥控
"text-align:center;margin-top:10px;">超声波数据
"csbdata" style="color:Red;text-align:center">
"text-align:center;margin-top:10px;">红外避障数据
"bzdata" style="color:Red;text-align:center">
编码阶段就完成了,没啥含量,简单粗暴,能用就行.现在我们把代码生成完成把生成的一堆文件都ftp到我们树莓派的目录上面.
在树莓派上我们要重启我们的站点才能生效:
sudo systemctl restart kestrel-carapp.service
之后在浏览器输入树莓派的IP来看效果吧:
今天超声波模块就弄完了.下一章把红外避障和电机驱动上,让它跑起来