使用asp.net core 3.0 搭建智能小車2


  上一篇中我們把基本的運行環境搭建完成了,這一篇中,我們實戰通過樹莓派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來看效果吧:

  今天超聲波模塊就弄完了.下一章把紅外避障和電機驅動上,讓它跑起來

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM