上一篇中我們把基本的運行環境搭建完成了,這一篇中,我們實戰通過樹莓派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 { /// <summary> /// 超聲波控制端 默認23 /// </summary> public int TriggerPin { get; set; } /// <summary> /// 超聲波接收端 默認24 /// </summary> public int EchoPin { get; set; } }
之后在 appsettings.json 中添加 這個節點:
"SiteConfig": { "TriggerPin": 23, "EchoPin": 24, }
在 Startup.cs 類的ConfigureServices 方法添加
services.Configure<SiteConfig>(Configuration.GetSection("SiteConfig"));
這樣通過依賴注入我們就可以使用我們配置的變量了.這些無關緊要的東西寫完后,我們在項目中新建文件夾 Playground 並在這個文件下建立Ultrasonic文件夾:
在Ultrasonic里面建立三個文件 IHcsr04Client.cs,Hcsr04Client.cs,Hcsr04ReadEventArgs.cs
IHcsr04Client:
public interface IHcsr04Client { event EventHandler<Hcsr04ReadEventArgs> OnDataAvailable; 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 EventHandler<Hcsr04ReadEventArgs> OnDataAvailable; public bool IsRunning { get; set; } public Hcsr04Client(IOptions<SiteConfig> 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; } /// <summary> /// 讀數是否有效. /// </summary> public bool IsValid { get; } = true; /// <summary> /// 是否檢測到任何障礙物. /// </summary> public bool HasObstacles => Distance != Hcsr04Client.NoObstacleDistance; /// <summary> /// 獲取到障礙物的實際距離,以厘米為單位. /// </summary> public double Distance { get; } internal static Hcsr04ReadEventArgs CreateInvalidReading() => new Hcsr04ReadEventArgs(false);
最后在注入到服務:
services.AddSingleton<IHcsr04Client, Hcsr04Client>();
代碼都非常簡單,也有相關注釋.大家看一眼就懂了.之后就是網網頁上展示了.
先弄一個Controller,在Controllers文件新建一個Hcsr04Controller和CarController的控制器:
public class Hcsr04Controller : Controller { public static string iscsb = "stop"; private readonly IHcsr04Client _hcsr04; private readonly IHubContext<ChatHub> _chatHub; public Hcsr04Controller(IHcsr04Client hcsr04, IHubContext<ChatHub> chatHub) { _hcsr04 = hcsr04; _chatHub = chatHub; } public async Task<IActionResult> 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<IActionResult> Hcsr04Off() { _hcsr04.Stop(); iscsb = "stop"; await _chatHub.Clients.All.SendAsync("ReceiveMessage", "51", "超聲波關閉通知."); return Content("超聲波關閉"); } }
我這里面使用了signalR,方便數據到達時候在web上面展示,SignalR的內容這里就不說了,就是簡單的使用.
CarController就什么都不用改了.之后新建一個視圖:
代碼:
@{ ViewData["Title"] = "智能小車控制面板"; } @section Css{ <link href="~/css/car.css" rel="stylesheet" /> } <div class="text-center"> <h5 class="display-4">控制面板</h5> <p>溫度:<span id="wd">35°C</span> 濕度:<span id="sd">50%</span></p> </div> <ul class="Switch"> <li> <input type="checkbox" name="Storage" id="csb" onclick="KZCSB(this)" /> 超聲波 <label for="csb"><em></em></label> </li> <li> <input type="checkbox" name="Storage2" id="bz" onclick="KZBZ(this)" /> 紅外避障 <label for="bz"><em></em></label> </li> <li> <input type="checkbox" name="Storage2" id="wf" onclick="KZWIFIYK(this)" checked="checked" /> WiFi遙控 <label for="wf"><em></em></label> </li> <li> <input type="checkbox" name="Storage2" id="hw" onclick="KZHWYK(this)" /> 紅外遙控 <label for="hw"><em></em></label> </li> </ul> <div class="control-wrapper"> <div class="control-btn control-top" id="up" onclick="carmove(this,'up')"> <i class="fa fa-chevron-up">∧</i> <div class="control-inner-btn control-inner"></div> </div> <div class="control-btn control-left" id="left" onclick="carmove(this,'left')"> <i class="fa fa-chevron-left"><</i> <div class="control-inner-btn control-inner"></div> </div> <div class="control-btn control-bottom" id="down" onclick="carmove(this,'down')"> <i class="fa fa-chevron-down">∨</i> <div class="control-inner-btn control-inner"></div> </div> <div class="control-btn control-right" id="right" onclick="carmove(this,'right')"> <i class="fa fa-chevron-right">></i> <div class="control-inner-btn control-inner"></div> </div> <div class="control-round" id="pause" onclick="carmove(this,'pause')"> <div class="control-round-inner"> <i class="fa fa-pause-circle">P</i> </div> </div> </div> <div class="c-box"> <div class="c-left"> <h5 style="text-align:center;margin-top:10px;">超聲波數據</h5> <p id="csbdata" style="color:Red;text-align:center"></p> </div> <div class="c-right"> <h5 style="text-align:center;margin-top:10px;">紅外避障數據</h5> <p id="bzdata" style="color:Red;text-align:center"></p> </div> </div> <div> <label class="demo--label"> <input class="demo--radio" type="radio" name="demo-radio" checked="checked" value="0.4"> <span class="demo--radioInput"></span>40 </label> <label class="demo--label"> <input class="demo--radio" type="radio" name="demo-radio" value="0.6"> <span class="demo--radioInput"></span>60 </label> <label class="demo--label"> <input class="demo--radio" type="radio" name="demo-radio" value="0.8"> <span class="demo--radioInput"></span>80 </label> <label class="demo--label"> <input class="demo--radio" type="radio" name="demo-radio" value="1.0"> <span class="demo--radioInput"></span>100 </label> </div> @section Scripts{ <script src="~/js/signalr.js"></script> <script> $(document).ready(function () { if (iscsb=="start") { $("#csb").prop("checked",true); } else { $("#csb").prop("checked", false); } }); // 控制超聲波 function KZCSB(th) { if ($(th).is(':checked')) { $.get("/Hcsr04/Hcsr04On", function (data) { iscsb = 'start'; console.log(data); }); } else { $.get("/Hcsr04/Hcsr04Off", function (data) { $("#csbdata").empty(); iscsb = "stop"; console.log(data); }); } } // signalR 接受傳感器數據 var connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build(); connection.on("ReceiveMessage", function (type, msg) { switch (type) { case "1": $("#csbdata").text(msg); break; case "50": $("#csb").prop("checked", true);break; case "51": $("#csb").prop("checked", false); $("#csbdata").empty();break; } }); connection.start().then(function () { }).catch(function (err) { return console.error(err.toString()); }); connection.onclose(async () => { $("#csbdata").empty(); $("#bzdata").empty(); console.info('監聽到鏈接關閉'); await start(); }); async function start() { try { await connection.start(); console.log("connected"); } catch (err) { console.log(err); setTimeout(() => start(), 5000); // 斷線重連 } }; </script> }
編碼階段就完成了,沒啥含量,簡單粗暴,能用就行.現在我們把代碼生成完成把生成的一堆文件都ftp到我們樹莓派的目錄上面.
在樹莓派上我們要重啟我們的站點才能生效:
sudo systemctl restart kestrel-carapp.service
之后在瀏覽器輸入樹莓派的IP來看效果吧:
今天超聲波模塊就弄完了.下一章把紅外避障和電機驅動上,讓它跑起來