<meter id="pryje"><nav id="pryje"><delect id="pryje"></delect></nav></meter>
          <label id="pryje"></label>

          新聞中心

          EEPW首頁(yè) > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > 終于搞定了!花30元,DIY了一只機(jī)器狗!

          終于搞定了!花30元,DIY了一只機(jī)器狗!

          作者: 時(shí)間:2024-12-31 來源:嘉立創(chuàng) 收藏

          30元了一只超可愛的機(jī)器狗!

          本文引用地址:http://www.ex-cimer.com/article/202412/465941.htm

          目前,項(xiàng)目已全開源!

          它有哪些功能(第1章)軟硬件怎么設(shè)計(jì)(2-3章)?如何校準(zhǔn)舵機(jī)(4章)?開源資料入口(6章)下文咱們一一了解~

          1.功能&亮點(diǎn)

          • 支持手機(jī)遙控

          • 支持表情、每日天氣、時(shí)間顯示

          • 電路大部分使用插件封裝,成本低廉且易于新手焊接,適用于教學(xué)

          • 本項(xiàng)目不含電池,成本大致為30元,主要費(fèi)用在于舵機(jī)12元,屏幕4.5元,主控4元

          • 支持功能拓展,代碼已完全開源,可以根據(jù)現(xiàn)有代碼邏輯框架,添加更多好玩有趣的功能

          當(dāng)然你也可以完全重構(gòu),使用更好性能的主控

          在軟件上可以添加語(yǔ)音交互,大模型對(duì)話等等

          在硬件上也可以添加避障,測(cè)溫,搭載炮臺(tái)等等

          2.硬件設(shè)計(jì)

          電路由以下部分組成——電源部分、ESP8266主控、外部接口。

          EDA-Robot插件版原理圖

          EDA-Robot插件版PCB圖

          ①硬件參數(shù)

          • 主控:ESP8266,內(nèi)置WIFI功能,通過AP模式遙控

          • OLED顯示屏:0.96寸,可顯示表情、時(shí)鐘、天氣等信息

          • LDO線性穩(wěn)壓器:AMS1117 ,負(fù)責(zé)將8.4V和5V電壓分別轉(zhuǎn)換成5V和3.3V,為舵機(jī)及主控提供電源

          • 舵機(jī):SG-90/MG90,支持180度/360度版本,本文以360度版本為主

          • 供電:14500雙節(jié)電池組,通過LDO降壓穩(wěn)壓器供電

          • OLED顯示屏支持SSD1315,SSD1306驅(qū)動(dòng),該模塊自帶屏幕驅(qū)動(dòng)電路,僅需接口接入即可。

          • 電路設(shè)計(jì)軟件:嘉立創(chuàng)EDA

          ②原理解析

          (1)ADC電量檢測(cè)電路

          修改分壓器適配 8.4V 到 1V

          現(xiàn)在需要適配新的輸入電壓范圍(最大 8.4V)到 ESP8266 的 1.0V ADC 輸入。

          分壓比計(jì)算如下:

          分壓比=1V8.4V=18.4≈0.119分壓比=8.4V1V=8.41≈0.119

          根據(jù)分壓公式:

          R2R1+R2=0.119R1+R2R2=0.119

          假設(shè)保持100k ,計(jì)算 :

          100kR1+100k=0.119R1+100k100k=0.119

          R1+100k=100k0.119≈840kR1+100k=0.119100k≈840k

          R1≈740kΩR1≈740kΩ

          對(duì)于 ,輸出電壓:

          Vout=8.4×100k740k+100k=8.4×100840≈1.0VVout=8.4×740k+100k100k=8.4×840100≈1.0V

          對(duì)于電壓較低時(shí)(如 4.2V),輸出電壓為:

          Vout=4.2×100k740k+100k=4.2×100840≈0.5VVout=4.2×740k+100k100k=4.2×840100≈0.5V

          分壓電路成功將8.4V的輸入電壓,壓縮到0-1V范圍內(nèi)

          (2)外部接口電路

          串口:為方便下載,單獨(dú)引出了IO0及GND接口作為跳帽插入接口,當(dāng)插入跳帽時(shí),IO0被拉低,進(jìn)入下載模式。反之被主控部分電路拉高,進(jìn)入工作模式。

          電池:引出了外部充電拓展接口,VIN與VBAT是開關(guān)接口,VIN與GND接口是外部充電模塊接口。充電模塊選擇滿電電壓大概在8.4V的2串鋰電池充電模塊。

          按鍵:使用IO2和IO15引腳,IO2按鍵按下時(shí)拉低,空閑時(shí)被拉高。但由于IO15必須接下拉電阻,所以這里開關(guān)邏輯與IO2相反,按鍵按下時(shí)拉高,空閑時(shí)被拉低。

          3.軟件代碼

          本章節(jié)只介紹部分比較重要的關(guān)鍵代碼。

          開源網(wǎng)址:
          https://oshwhub.com/course-examples/bot-dog 
          開發(fā)文檔:
          https://wiki.lceda.cn/zh-hans/course-projects/smart-internet/eda-robot/eda-robot-introduce.html

          如何通過手機(jī)【控制】機(jī)器狗?

          為了控制機(jī)器狗,我寫了一個(gè)網(wǎng)頁(yè),你可以直接使用,也可以參考下方了邏輯,自己寫一個(gè),并在此基礎(chǔ)上進(jìn)行拓展。

          ①控制頁(yè)面CSS樣式表

              body {            margin: 0;            padding: 0;            font-family: Arial, sans-serif;
                  }        .container {            max-width: 800px;            margin: 0 auto;            padding: 20px;            text-align: center;
                  }        h1 {            text-align: center;
                  }        button {            display: inline-block;            height: auto;            width: auto;            margin-top: 20px;            padding: 10px 20px;            background-color: deepskyblue;            color: #fff;            border: none;            border-radius: 20px; /* 添加圓角 */
                      text-decoration: none;            line-height: 2; /* 通過調(diào)整line-height的值來調(diào)整文字的垂直位置 */
                      text-align: center; /* 文字居中 */
                      box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2); /* 添加立體感 */
                      transition: all 0.3s ease; /* 添加過渡效果 */
                  }        button:hover {            background-color: skyblue; /* 鼠標(biāo)懸停時(shí)的背景顏色 */
                      transform: translateY(2px); /* 點(diǎn)擊效果 */
                      box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.3); /* 添加更多立體感 */
                  }        .button-grid3 {            display: grid;            grid-template-columns: repeat(3, 1fr);            gap: 10px;            justify-content: center;            align-content: center;            text-align: center;            margin: 20px;
                  }        .button-grid2 {            display: grid;            grid-template-columns: repeat(2, 1fr);            gap: 10px;            justify-content: center;            align-content: center;            text-align: center;            margin: 20px;
                  } .button-grid1 {              display: grid;              border-radius: 20px; /* 添加圓角 */
                        grid-template-columns: repeat(1, 1fr);              justify-content: center;              align-content: center;              text-align: center;              margin: 10px;
                    }

          ②控制頁(yè)面JavaScript代碼

           // 簡(jiǎn)化 AJAX 請(qǐng)求函數(shù)
                  function sendCommand(action) {
                      fetch(`/${action}`)
                          .then(response => response.text())
                          .catch(() => alert('發(fā)送失敗,請(qǐng)檢查設(shè)備連接'));
                  }    
                  function refreshState(url, displayElementId) {
                      fetch(url)
                          .then(response => response.text())
                          .then(data => {                    document.getElementById(displayElementId).innerText = data;
                          });
                  }        function setRefreshInterval(url, displayElementId) {
                      setInterval(() => refreshState(url, displayElementId), 1000);
                  }        const states = [
                   { url: '/batteryVoltage', displayId: 'batteryVoltageDisplay' },
                      { url: '/batteryPercentage', displayId: 'batteryPercentageDisplay' },
                      { url: '/engine1offsetleftpwm', displayId: 'engine1offsetleftpwmDisplay' },
                      { url: '/engine1offsetrightpwm', displayId: 'engine1offsetrightpwmDisplay' },
                      { url: '/engine2offsetleftpwm', displayId: 'engine2offsetleftpwmDisplay' },
                      { url: '/engine2offsetrightpwm', displayId: 'engine2offsetrightpwmDisplay' },
                      { url: '/engine3offsetleftpwm', displayId: 'engine3offsetleftpwmDisplay' },
                      { url: '/engine3offsetrightpwm', displayId: 'engine3offsetrightpwmDisplay' },
                      { url: '/engine4offsetleftpwm', displayId: 'engine4offsetleftpwmDisplay' },
                      { url: '/engine4offsetrightpwm', displayId: 'engine4offsetrightpwmDisplay' }
                  ];
                  states.forEach(state => setRefreshInterval(state.url, state.displayId));

          ③控制頁(yè)面HTML代碼

          <div>
              <h1>EDA-Robot遙控臺(tái)</h1>
              <p>本項(xiàng)目基于ESP8266主控開發(fā)</p>
                      <div style="display:flex;justify-content:center">
                      <p>電壓:<span id="batteryVoltageDisplay">0</span></p>
                      <p>電量:<span id="batteryPercentageDisplay">0</span></p>
                  </div>
              <div style="background-color:papayawhip">
                  <h3>運(yùn)動(dòng)控制</h3>
                  <div style="display:flex;justify-content:center">
                      ↑        </div>
                  <div style="display:flex;justify-content:center">
                      ←
                      →        </div>
                  <div style="display:flex;justify-content:center">
                      ↓        </div>
                  <div>
                      抬左手
                      抬右手
                      坐下
                      趴下
                      自由模式開
                      自由模式關(guān)        </div>
              </div>
              
              <div style="background-color:limegreen">
                  <h3>表情控制</h3>
                  <div>
                      開心
                      生氣
                      難受
                      好奇
                      喜歡
                      錯(cuò)誤
                      暈        </div>
              </div>
              
              <div style="background-color:orange">
                  <h3>聯(lián)網(wǎng)功能</h3>
                  <div>
                      時(shí)間
                      天氣        </div>
              </div></div>

          控制頁(yè)面的代碼是存放在FS文件系統(tǒng)中的,這里主要看AJAX請(qǐng)求函數(shù),這部分的請(qǐng)求與下一小節(jié)的頁(yè)面路由監(jiān)聽代碼相對(duì)應(yīng),我們通過點(diǎn)擊頁(yè)面按鈕觸發(fā)請(qǐng)求。

          這里進(jìn)行了一些簡(jiǎn)化操作,避免html過長(zhǎng)過大導(dǎo)致html加載和響應(yīng)緩慢,這可能導(dǎo)致esp8266無(wú)法正確顯示頁(yè)面。

          如何讓機(jī)器狗【運(yùn)行起來】?給它注入點(diǎn)賽博靈魂~~

          ④頁(yè)面路由監(jiān)聽

          void handleWiFiConfig()
          {    // 啟動(dòng)服務(wù)器
              server.on("/left90", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                  actionstate = 10;  // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/right90", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                  actionstate = 11;  // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/front", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                  actionstate = 1;  // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/back", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 actionstate = 4;   // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/left", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 actionstate = 2;  // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/right", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 actionstate = 3;  // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/toplefthand", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                  actionstate = 5;   // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/toprighthand", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                  actionstate = 6;  // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/sitdown", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                  actionstate = 8;  // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/lie", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                actionstate = 7; 
                  request->send(200, "text/plain", "Front function started"); });    // server.on("/dance", HTTP_GET, [](AsyncWebServerRequest *request)
              //           {
              //     actionstate = 7;  // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
              //     request->send(200, "text/plain", "Front function started"); });
              server.on("/free", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                freestate=true;
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/offfree", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                freestate=false;
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/histate", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                  emojiState = 0;  // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/angrystate", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                  emojiState = 1;   // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/errorstate", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 emojiState = 2;  // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/engine1offsetleftpwm", HTTP_GET, [](AsyncWebServerRequest *request)
                        { request->send(200, "text/plain", String(engine1offsetleftpwm)); });
              server.on("/engine2offsetleftpwm", HTTP_GET, [](AsyncWebServerRequest *request)
                        { request->send(200, "text/plain", String(engine2offsetleftpwm)); });
              server.on("/engine3offsetleftpwm", HTTP_GET, [](AsyncWebServerRequest *request)
                        { request->send(200, "text/plain", String(engine3offsetleftpwm)); });
              server.on("/engine4offsetleftpwm", HTTP_GET, [](AsyncWebServerRequest *request)
                        { request->send(200, "text/plain", String(engine4offsetleftpwm)); });
              server.on("/engine1offsetrightpwm", HTTP_GET, [](AsyncWebServerRequest *request)
                        { request->send(200, "text/plain", String(engine1offsetrightpwm)); });
              server.on("/engine2offsetrightpwm", HTTP_GET, [](AsyncWebServerRequest *request)
                        { request->send(200, "text/plain", String(engine2offsetrightpwm)); });
              server.on("/engine3offsetrightpwm", HTTP_GET, [](AsyncWebServerRequest *request)
                        { request->send(200, "text/plain", String(engine3offsetrightpwm)); });
              server.on("/engine4offsetrightpwm", HTTP_GET, [](AsyncWebServerRequest *request)
                        { request->send(200, "text/plain", String(engine4offsetrightpwm)); });
              server.on("/engine4offsetrightpwm", HTTP_GET, [](AsyncWebServerRequest *request)
                        { request->send(200, "text/plain", String(engine4offsetrightpwm)); });
                            server.on("/batteryVoltage", HTTP_GET, [](AsyncWebServerRequest *request)
                        { request->send(200, "text/plain", String(batteryVoltage)); });     
              server.on("/batteryPercentage", HTTP_GET, [](AsyncWebServerRequest *request)
                        { request->send(200, "text/plain", String(batteryPercentage)); });  
              server.on("/speed", HTTP_GET, [](AsyncWebServerRequest *request)
                        { request->send(200, "text/plain", String(speed)); });
              server.on("/speedup", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 speed++;  // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/speeddown", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
              speed--;
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/engine1offsetrightpwmup", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 engine1offsetrightpwm++;  // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/engine1offsetrightpwmdown", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 engine1offsetrightpwm--;
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/engine1offsetleftpwmup", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 engine1offsetleftpwm++;  // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/engine1offsetleftpwmdown", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 engine1offsetleftpwm--;
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/engine2offsetrightpwmup", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 engine2offsetrightpwm++;  // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/engine2offsetrightpwmdown", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 engine2offsetrightpwm--;
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/engine2offsetleftpwmup", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 engine2offsetleftpwm++;  // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/engine2offsetleftpwmdown", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 engine2offsetleftpwm--;
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/engine3offsetrightpwmup", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 engine3offsetrightpwm++;  // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/engine3offsetrightpwmdown", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 engine3offsetrightpwm--;
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/engine3offsetleftpwmup", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 engine3offsetleftpwm++;  // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/engine3offsetleftpwmdown", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 engine3offsetleftpwm--;
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/engine4offsetrightpwmup", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 engine4offsetrightpwm++;  // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/engine4offsetrightpwmdown", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 engine4offsetrightpwm--;
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/engine4offsetleftpwmup", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 engine4offsetleftpwm++;  // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/engine4offsetleftpwmdown", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 engine4offsetleftpwm--;
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/speedup", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 speed++;  // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/speeddown", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 speed--;
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/dowhatstate", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 emojiState = 3;  // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/lovestate", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 emojiState = 4;  // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/sickstate", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 emojiState = 5;  // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/yunstate", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 emojiState = 6; 
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/time", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 emojiState = 8; 
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/weather", HTTP_GET, [](AsyncWebServerRequest *request)
                        {
                 emojiState = 7;  // 設(shè)置標(biāo)志,執(zhí)行舵機(jī)動(dòng)作
                  request->send(200, "text/plain", "Front function started"); });
              server.on("/connect", HTTP_POST, [](AsyncWebServerRequest *request)
                        {        // 獲取POST參數(shù):ssid、pass、uid、city、api
                  String ssid = request->getParam("ssid", true)->value();        String pass = request->getParam("pass", true)->value();        String uid = request->getParam("uid", true)->value();        String city = request->getParam("city", true)->value();        String api = request->getParam("api", true)->value();        // 打印接收到的參數(shù)
                  Serial.println(ssid);
                  Serial.println(pass);        // 保存WiFi信息到JSON文件
                  DynamicJsonDocument doc(1024);
                  doc["ssid"] = ssid;
                  doc["pass"] = pass;
                  doc["uid"] = uid;
                  doc["city"] = city;
                  doc["api"] = api;
                  fs::File file = SPIFFS.open(ssidFile, "w");  // 打開文件進(jìn)行寫入
                  if (file) {
                      serializeJson(doc, file);  // 將JSON內(nèi)容寫入文件
                      file.close();  // 關(guān)閉文件
                  }        // 更新全局變量
                  useruid = uid;
                  cityname = city;
                  weatherapi = api;        // 開始連接WiFi
                  WiFi.begin(ssid.c_str(), pass.c_str());        // 發(fā)送HTML響應(yīng),告知用戶正在連接
                  request->send(200, "text/html", "<h1>Connecting...</h1>"); });
              server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
                        {        // 檢查SPIFFS文件系統(tǒng)中是否存在index.html文件
                  if (SPIFFS.exists("/index.html")) {
                      fs::File file = SPIFFS.open("/index.html", "r");  // 打開index.html文件
                      if (file) {
                          size_t fileSize = file.size();  // 獲取文件大小
                          String fileContent;                // 逐字節(jié)讀取文件內(nèi)容
                          while (file.available()) {
                              fileContent += (char)file.read();
                          }
                          file.close();  // 關(guān)閉文件
                          // 返回HTML內(nèi)容
                          request->send(200, "text/html", fileContent);                return;
                      }
                  }        // 如果文件不存在,返回404錯(cuò)誤
                  request->send(404, "text/plain", "File Not Found"); });
              server.on("/control.html", HTTP_GET, [](AsyncWebServerRequest *request)
                        {        // 檢查SPIFFS文件系統(tǒng)中是否存在index.html文件
                  if (SPIFFS.exists("/control.html")) {
                      fs::File file = SPIFFS.open("/control.html", "r");  // 打開index.html文件
                      if (file) {
                          size_t fileSize = file.size();  // 獲取文件大小
                          String fileContent;                // 逐字節(jié)讀取文件內(nèi)容
                          while (file.available()) {
                              fileContent += (char)file.read();
                          }
                          file.close();  // 關(guān)閉文件
                          // 返回HTML內(nèi)容
                          request->send(200, "text/html", fileContent);                return;
                      }
                  }        // 如果文件不存在,返回404錯(cuò)誤
                  request->send(404, "text/plain", "File Not Found"); });
              server.on("/engine.html", HTTP_GET, [](AsyncWebServerRequest *request)
                        {        // 檢查SPIFFS文件系統(tǒng)中是否存在index.html文件
                  if (SPIFFS.exists("/engine.html")) {
                      fs::File file = SPIFFS.open("/engine.html", "r");  // 打開index.html文件
                      if (file) {
                          size_t fileSize = file.size();  // 獲取文件大小
                          String fileContent;                // 逐字節(jié)讀取文件內(nèi)容
                          while (file.available()) {
                              fileContent += (char)file.read();
                          }
                          file.close();  // 關(guān)閉文件
                          // 返回HTML內(nèi)容
                          request->send(200, "text/html", fileContent);                return;
                      }
                  }        // 如果文件不存在,返回404錯(cuò)誤
                  request->send(404, "text/plain", "File Not Found"); });
              server.on("/setting.html", HTTP_GET, [](AsyncWebServerRequest *request)
                        {        // 檢查SPIFFS文件系統(tǒng)中是否存在index.html文件
                  if (SPIFFS.exists("/setting.html")) {
                      fs::File file = SPIFFS.open("/setting.html", "r");  // 打開index.html文件
                      if (file) {
                          size_t fileSize = file.size();  // 獲取文件大小
                          String fileContent;                // 逐字節(jié)讀取文件內(nèi)容
                          while (file.available()) {
                              fileContent += (char)file.read();
                          }
                          file.close();  // 關(guān)閉文件
                          // 返回HTML內(nèi)容
                          request->send(200, "text/html", fileContent);                return;
                      }
                  }        // 如果文件不存在,返回404錯(cuò)誤
                  request->send(404, "text/plain", "File Not Found"); });    // 啟動(dòng)服務(wù)器
              server.begin();
          };

          這部分的代碼較長(zhǎng),是所有WebServer的頁(yè)面路由監(jiān)聽,與頁(yè)面中按鈕觸發(fā)的url對(duì)應(yīng),這里的url務(wù)必檢查仔細(xì),如果不能對(duì)應(yīng)就無(wú)法監(jiān)聽到頁(yè)面請(qǐng)求是否觸發(fā),硬件也無(wú)法做出對(duì)應(yīng)的響應(yīng)。

          另外,在/connect下還添加了寫入信息到FS文件系統(tǒng)中的功能,只要每次開機(jī)執(zhí)行讀取就不需要重復(fù)配置網(wǎng)絡(luò)信息了。

          ⑤讀取FS系統(tǒng)保存的json文件

          void loadWiFiConfig(){    if (SPIFFS.begin())
              {
                  fs::File file = SPIFFS.open(ssidFile, "r");        if (file)
                  {            DynamicJsonDocument doc(1024);
                      DeserializationError error = deserializeJson(doc, file);            if (!error)
                      {
                          String ssid = doc["ssid"];
                          String pass = doc["pass"];
                          String uid = doc["uid"];
                          String city = doc["city"];
                          String api = doc["api"];
                          useruid = uid;
                          cityname = city;
                          weatherapi = api;
                          WiFi.begin(ssid.c_str(), pass.c_str());                // 嘗試連接WiFi,最多等待10秒
                          unsigned long startAttemptTime = millis();                while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 5000)
                          {
                              delay(500);
                          }                // 如果連接失敗,打印狀態(tài)
                          if (WiFi.status() != WL_CONNECTED)
                          {
                              Serial.println("WiFi connection failed, starting captive portal...");
                              handleWiFiConfig(); // 啟動(dòng)強(qiáng)制門戶
                          }                else
                          {
                              Serial.println("WiFi connected");
                              timeClient.begin();
                          }
                      }
                      file.close();
                  }
              }
          }

          前面我們講了在/connect路由監(jiān)聽下,添加了將信息保存的FS文件系統(tǒng),那么,這里的loadWiFiConfig()方法就是讀取FS文件系統(tǒng)的Json文件,并將數(shù)據(jù)同步到全局變量之中,這樣就不需要每次開機(jī)進(jìn)入配置頁(yè)面配網(wǎng)了,程序會(huì)自動(dòng)加載上次配網(wǎng)保存的信息,極為方便。

          ⑥運(yùn)動(dòng)狀態(tài)

          switch (actionstate)
              {    case 0 /* constant-expression */:        /* code */
                  break;    case 1:
                  front(); // 執(zhí)行一次舵機(jī)動(dòng)作
                  actionstate = 0;        break;    case 2:        left(); // 執(zhí)行一次舵機(jī)動(dòng)作
                  actionstate = 0;        break;    case 3:        right(); // 執(zhí)行一次舵機(jī)動(dòng)作
                  actionstate = 0;        break;    case 4:
                  back(); // 執(zhí)行一次舵機(jī)動(dòng)作
                  actionstate = 0;        break;    case 5:
                  toplefthand(); // 執(zhí)行一次舵機(jī)動(dòng)作
                  actionstate = 0;        break;    case 6:
                  toprighthand(); // 執(zhí)行一次舵機(jī)動(dòng)作
                  actionstate = 0;        break;    case 10:
                  left90(); // 執(zhí)行一次舵機(jī)動(dòng)作
                  actionstate = 0;        break;    case 11:
                  right90(); // 執(zhí)行一次舵機(jī)動(dòng)作
                  actionstate = 0;        break;    case 7:
                  lie(); // 執(zhí)行一次舵機(jī)動(dòng)作
                  actionstate = 0;        break;    case 8:
                  sitdown(); // 執(zhí)行一次舵機(jī)動(dòng)作
                  actionstate = 0;        break;    case 9:
                  emojiState = random(0, 7); // 執(zhí)行一次舵機(jī)動(dòng)作
                  actionstate = 0;        break;    default:        break;
              }

          運(yùn)動(dòng)狀態(tài)代碼與前面的路由監(jiān)聽對(duì)應(yīng),之所以沒有把動(dòng)作函數(shù)直接寫入路由監(jiān)聽的代碼,這是因?yàn)闀?huì)導(dǎo)致頁(yè)面響應(yīng)過久,導(dǎo)致頁(yè)面無(wú)法加載或者觸發(fā)程序死機(jī)然后重啟。

          為了避免這個(gè)情況發(fā)生,我們通過actionstate變量定義運(yùn)動(dòng)狀態(tài),然后再loop函數(shù)中判斷。

          這里選擇的是switch,而并沒有使用if-else,理論上對(duì)應(yīng)順序較長(zhǎng)的數(shù)據(jù)switch性能略好,看個(gè)人喜歡,其實(shí)都可以用。

          ⑦前進(jìn)運(yùn)動(dòng)

          void front()
          {    //+30C 2/3
              servo2.writeMicroseconds(1500 + speed + engine2offsetleftpwm);    servo3.writeMicroseconds(1500 - speed - engine3offsetleftpwm);    delay(500-runtime);    servo2.writeMicroseconds(1500);    servo3.writeMicroseconds(1500);    //-30C 1/4
              servo1.writeMicroseconds(1500 - speed - engine1offsetrightpwm);    servo4.writeMicroseconds(1500 + speed + engine4offsetrightpwm);    delay(500-runtime);    servo1.writeMicroseconds(1500);    servo4.writeMicroseconds(1500);    // 0C 2/3
              servo2.writeMicroseconds(1500 - speed - engine2offsetrightpwm);    servo3.writeMicroseconds(1500 + speed + engine3offsetrightpwm);    delay(500-runtime);    servo2.writeMicroseconds(1500);    servo3.writeMicroseconds(1500);    // 0C 1/4
              servo1.writeMicroseconds(1500 + speed + engine1offsetleftpwm);    servo4.writeMicroseconds(1500 - speed - engine4offsetleftpwm);    delay(500-runtime);    servo1.writeMicroseconds(1500);    servo4.writeMicroseconds(1500);    //+30C 1/4
              servo1.writeMicroseconds(1500 + speed + engine1offsetleftpwm);    servo4.writeMicroseconds(1500 - speed - engine4offsetleftpwm);    delay(500-runtime);    servo1.writeMicroseconds(1500);    servo4.writeMicroseconds(1500);    //-30C 2/3
              servo2.writeMicroseconds(1500 - speed - engine2offsetrightpwm);    servo3.writeMicroseconds(1500 + speed + engine3offsetrightpwm);    delay(500-runtime);    servo2.writeMicroseconds(1500);    servo3.writeMicroseconds(1500);    // 0C 1/4
              servo1.writeMicroseconds(1500 - speed - engine1offsetrightpwm);    servo4.writeMicroseconds(1500 + speed + engine4offsetrightpwm);    delay(500-runtime);    servo1.writeMicroseconds(1500);    servo4.writeMicroseconds(1500);    // 0C 2/3
              servo2.writeMicroseconds(1500 + speed + engine2offsetleftpwm);    servo3.writeMicroseconds(1500 - speed - engine3offsetleftpwm);    delay(500-runtime);    servo2.writeMicroseconds(1500);    servo3.writeMicroseconds(1500);
          }

          ⑧ADC電量檢測(cè)

          // 對(duì) ADC 數(shù)據(jù)多次采樣并計(jì)算平均值float getAverageAdcVoltage() {  long totalAdcValue = 0;  // 多次采樣
            for (int i = 0; i < numSamples; i++) {
              totalAdcValue += analogRead(A0); // 讀取 ADC 數(shù)據(jù)
              delay(10); // 每次采樣間隔 10ms
            }  // 計(jì)算平均 ADC 值
            float averageAdcValue = totalAdcValue / (float)numSamples;  // 將 ADC 值轉(zhuǎn)換為電壓
            return (averageAdcValue / 1023.0) * 1.0; // ESP8266 的參考電壓為 1.0V}// 計(jì)算電池電量百分比的函數(shù)int mapBatteryPercentage(float voltage) {  if (voltage <= minVoltage) return 0;   // 小于等于最小電壓時(shí),電量為 0%
            if (voltage >= maxVoltage) return 100; // 大于等于最大電壓時(shí),電量為 100%
            // 根據(jù)線性比例計(jì)算電量百分比
            return (int)((voltage - minVoltage) / (maxVoltage - minVoltage) * 100);
          }

          與小車不同,機(jī)器狗不能像小車那樣簡(jiǎn)單控制電機(jī)正反轉(zhuǎn),實(shí)現(xiàn)前進(jìn)后退,這里需要觀察四足動(dòng)物,進(jìn)行一些仿生模擬,用舵機(jī)模擬四足動(dòng)物前進(jìn)時(shí)的四足變化情況。下一章,我們就講這個(gè)!

          4.舵機(jī)校準(zhǔn)

          如何讓機(jī)器狗麻溜滴【走起來】且不順拐?

          機(jī)器小狗使用360度舵機(jī),其拓展性高,但不像180度舵機(jī)那樣,可以直接控制旋轉(zhuǎn)角度,所有我們需要進(jìn)行舵機(jī)校準(zhǔn),確保舵機(jī)轉(zhuǎn)速,角度均合適。

          說明:刷入程序的舵機(jī)校準(zhǔn)數(shù)據(jù)并不是通用的,這要根據(jù)自己的舵機(jī)情況進(jìn)行調(diào)整。

          精確校準(zhǔn)

          接著,請(qǐng)按一下步驟進(jìn)行精調(diào)。

          1.將所有腳固定到相同角度。 2.滑到校準(zhǔn)頁(yè)的底部,點(diǎn)擊4次‘電機(jī)左轉(zhuǎn)90度’。 3.找到轉(zhuǎn)動(dòng)大于360度或小于360度的腳,進(jìn)行舵機(jī)補(bǔ)償。

          修改程序重新燒錄

          記錄下認(rèn)為合理的各個(gè)電機(jī)補(bǔ)償值,修改程序的補(bǔ)償定義,重新刷入程序,當(dāng)然,不重新輸入也可以,這個(gè)值是立即生效的。

          但是為了能快速響應(yīng),避免重復(fù)刷寫降低壽命,所以不會(huì)保存到FS文件系統(tǒng),下次重啟也不會(huì)被保留。

          當(dāng)然啦!其實(shí)更推薦使用180度版本,因?yàn)槠渥詭尬黄?,為了便于大?a class="contentlabel" href="http://www.ex-cimer.com/news/listbylabel/label/DIY">DIY,原工程中,已開源了180度舵機(jī)的版本。可前往原工程查看!

          開源網(wǎng)址:
          https://oshwhub.com/course-examples/bot-dog 

          開發(fā)文檔:
          https://wiki.lceda.cn/zh-hans/course-projects/smart-internet/eda-robot/eda-robot-introduce.html



          關(guān)鍵詞: DIY 電子狗

          評(píng)論


          相關(guān)推薦

          技術(shù)專區(qū)

          關(guān)閉
          看屁屁www成人影院,亚洲人妻成人图片,亚洲精品成人午夜在线,日韩在线 欧美成人 (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();