驴友花雕 发表于 4 天前

【Arduino 动手做】使用TTGO T-Beam 的 ESP32 舵机机器人

本帖最后由 驴友花雕 于 2025-6-3 09:45 编辑

Arduino是一个开放源码的电子原型平台,它可以让你用简单的硬件和软件来创建各种互动的项目。Arduino的核心是一个微控制器板,它可以通过一系列的引脚来连接各种传感器、执行器、显示器等外部设备。Arduino的编程是基于C/C++语言的,你可以使用Arduino IDE(集成开发环境)来编写、编译和上传代码到Arduino板上。Arduino还有一个丰富的库和社区,你可以利用它们来扩展Arduino的功能和学习Arduino的知识。

Arduino的特点是:
1、开放源码:Arduino的硬件和软件都是开放源码的,你可以自由地修改、复制和分享它们。
2、易用:Arduino的硬件和软件都是为初学者和非专业人士设计的,你可以轻松地上手和使用它们。
3、便宜:Arduino的硬件和软件都是非常经济的,你可以用很低的成本来实现你的想法。
4、多样:Arduino有多种型号和版本,你可以根据你的需要和喜好来选择合适的Arduino板。
5、创新:Arduino可以让你用电子的方式来表达你的创意和想象,你可以用Arduino来制作各种有趣和有用的项目,如机器人、智能家居、艺术装置等。





驴友花雕 发表于 4 天前

【Arduino 动手做】使用TTGO T-Beam 的舵机机器人

我一直在尝试使用不同的 ESP32 开发板,最近我订购了一款 TTGO T-Beam 开发板,它配有一个电池插座,可以添加您自己的 18650 Lipo,这确实消除了构建小型机器人的一些电源调节复杂性,因为它已经配备了电池和充电器电路。

然而,要直接从该电路板驱动某些东西,它需要一些低功率的东西,所以我决定添加一些我已经拥有一段时间的连续旋转伺服器。

我在这里使用的 ESP32 开发板具有很多功能,包括 Lora 无线电和 GPS,这些功能在未来可能会有用,但您也可以获得没有这些附加功能的 ESP32 开发板,这会使开发板更小一些,并且仍然配备 18650 电池座。

那么让我们开始讨论构建。



驴友花雕 发表于 4 天前

【Arduino 动手做】使用TTGO T-Beam 的 ESP32 舵机机器人

## 补给品

4 x 连续旋转伺服器

4 个适合伺服器的轮子

如果您想添加,则需要 1 条 5 x Neopixels 条带。

1 个 ESP32,理想情况下内置可充电电池,或带有外部电池的 ESP32。

我是从 Lilygo Aliexpress 购买的,发货速度比我预期的要快得多,我用的那个可以在这里找到 https://www.aliexpress.com/item/32967228739.html?spm=a2g0s.9042311.0.0.206f4c4dXaqAU9

1 小块有机玻璃,可切割和钻孔以形成底盘。

1 小块 veroboard

一些电线,我使用了一个迷你 jst 插座作为连接器,但这可以只是焊接。

4 x 伺服接头,因此您只需将伺服器插入连接器 veroboard

一些塑料电路板支架。



驴友花雕 发表于 4 天前

【Arduino 动手做】使用TTGO T-Beam 的 ESP32 舵机机器人

## 步骤1:构建底盘

我想要一个真正的基本底盘,任何人都可以使用有机玻璃或塑料制作,甚至可以使用旧的塑料午餐盒或外卖盒。

我切了一块比 ESP32 板稍宽但长度大致相同的有机玻璃,然后标记了想要添加 4 个孔的位置,以便使用电路板支架安装 ESP32。

连接伺服器

我把伺服电机的方位都调整到一致,这样接线后它们就能朝同一个方向驱动。我用了一些塑料胶水把它们固定到位,还加了一些支架来辅助固定。

我钻了一些孔,让伺服器电线穿过底盘底座,这样它们就可以插入我使用的小型 veroboard,稍后我会详细介绍。

我尽可能地将伺服器多余的线路捆扎起来,并使用几根小扎带将它们固定到位。

掩盖一切

最后一步,我用一块和第一块一样大小的有机玻璃盖住了所有部件。我钻了几个孔,用来安装额外的支架,并用支架螺丝固定所有部件。

我很惊讶这个一次性组装起来的重量竟然如此之轻,比我上周制作的基于马达的重量轻得多。











驴友花雕 发表于 4 天前

【Arduino 动手做】使用TTGO T-Beam 的 ESP32 舵机机器人

## 步骤2:制作定制的Veroboard

我想做一个小电路板,可以把 ESP32 芯片插上去,需要的时候也方便取下。所以我按照照片里的样子做了个小电路板,还加了一些排针,方便插上伺服电机,之后再插上 Neopixel 灯条。

我还添加了 2 个小型 jst 插座,以便我可以使用它们从 ESP32 供电并提供伺服信号连接。

我切割了电路板底面上的一条铜轨,这样每个伺服器的信号引脚就不同了,然后我使用一个小的线连接器通过一条轨道上的电线将其移动,这样两个 jst 引脚就可以连接到一侧或另一侧。

由于飞行器左右两侧各有两个伺服电机,我用电路板将左右两侧的两个伺服电机连接起来,这样我就可以用一个伺服电机连接左右两侧的伺服电机了。我这里只是把左右两侧的电机连接起来,以减少所需的接线数量。

我允许 Vcc 和 GND 连接通过铜轨连接到整个 veroboard,但是我切断了信号线,这样我就可以独立控制我想要驱动的不同侧面。










驴友花雕 发表于 4 天前

【Arduino 动手做】使用TTGO T-Beam 的 ESP32 舵机机器人

## 步骤3:接线

这里的接线图显示了连接以及如何使用尽可能少的电线连接伺服器和 Neopixel 条。



驴友花雕 发表于 4 天前

【Arduino 动手做】使用TTGO T-Beam 的 ESP32 舵机机器人

## 步骤4:整合

将所有东西连接好后,我安装了定制的 veroboard,并将 ESP32 添加到底盘,一切都很顺利。

大部分接线都是隐藏的,可以轻松添加侧面和顶部以完全封闭 ESP32。





驴友花雕 发表于 4 天前

【Arduino 动手做】使用TTGO T-Beam 的 ESP32 舵机机器人

## 步骤5:控制和测试

我想要一些简单的控制,于是在https://randomnerdtutorials.com/这个网站上找到了一个很好的示例,教你如何运行网络服务器并显示控制按钮,这样就能让机器人小车四处行驶。我修改了这个示例,用伺服器代替了电机,并添加了代码来使用 Neopixel 灯带,同时在 OLED 屏幕上显示我需要连接的 IP 地址,这样才能控制机器人。







驴友花雕 发表于 4 天前

【Arduino 动手做】使用TTGO T-Beam 的 ESP32 舵机机器人

## 步骤6:ESP32的代码

我附上了代码,您可以根据自己的需求进行修改。所有代码均来自 randomnerdtutorials,它们构成了我这里所有代码的基础。我强烈建议您购买他们关于 ESP32 的课程,它会引导您了解使用 ESP32 的许多复杂原理,并提供一些非常好的示例项目。

我希望这对其他尝试快速使用 ESP32 进行机器人技术的人有所帮助。

#include <SPI.h>
#include <WiFi.h>
#include <Wire.h>
#include <Servo.h>
#include <Adafruit_NeoPixel.h>
#include "SSD1306.h"
//#include "images.h"

#ifdef __AVR__
#include <avr/power.h>
#endif
static const int servoPin = 25; //works with TTGO
static const int servoPin2 = 33; // works with TTGO

#define PIN 14 // Neopixel works with TTGO
Adafruit_NeoPixel strip = Adafruit_NeoPixel(5, PIN, NEO_GRB + NEO_KHZ800);

SSD1306 display(0x3c, 21, 22);
Servo servo1;
Servo servo2;

//network credentials
const char* ssid   = "your ssid here";
const char* password = "your password here";

// Set web server port number to 80
WiFiServer server(80);

// Variable to store the HTTP request
String header;

// Decode HTTP GET value
String valueString = String(5);
int pos1 = 0;
int pos2 = 0;

void setup() {
Serial.begin(115200);
servo1.attach(servoPin);
servo2.attach(servoPin2);

   //Display something on Oled if required
display.init();
display.flipScreenVertically();
//display.setFont(ArialMT_Plain_10);
display.setFont(ArialMT_Plain_16);
//display.setFont(ArialMT_Plain_24);
display.clear();
display.setTextAlignment(TEXT_ALIGN_LEFT);


// This is for Trinket 5V 16MHz, you can remove these three lines if you are not using a Trinket
#if defined (__AVR_ATtiny85__)
    if (F_CPU == 16000000) clock_prescale_set(clock_div_1);
#endif
// End of trinket special code

strip.begin();
strip.show(); // Initialize all pixels to 'off'

// Connect to Wi-Fi network with SSID and password
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
    display.drawString(5, 20, "STARTING..");
    delay(500);
    display.clear();
    Serial.print(".");
}
// Print local IP address and start web server
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
//Display IP address in Oled
display.drawString(0, 0, "IP: ");
display.drawString(20, 0,(WiFi.localIP().toString()));
display.setFont(ArialMT_Plain_24);
display.drawString(5, 20, "READY");
display.display();

server.begin();
}

void loop(){
colorWipe(strip.Color(0, 0, 255), 100); // Blue
colorWipe(strip.Color(255, 0, 0), 100); // Red
WiFiClient client = server.available();   // Listen for incoming clients

if (client) {                           // If a new client connects,
    Serial.println("New Client.");          // print a message out in the serial port
    String currentLine = "";                // make a String to hold incoming data from the client
    while (client.connected()) {            // loop while the client's connected
      if (client.available()) {             // if there's bytes to read from the client,
      char c = client.read();             // read a byte, then
      Serial.write(c);                  // print it out the serial monitor
      header += c;
      if (c == '\n') {                  // if the byte is a newline character
          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println();
            
            // Controls the motor pins according to the button pressed
            if (header.indexOf("GET /forward") >= 0) {
            Serial.println("Forward");
            servo1.write(170);
            servo2.write(10);
            }else if (header.indexOf("GET /left") >= 0) {
            Serial.println("Left");
            servo1.write(90);
            servo2.write(10);
            }else if (header.indexOf("GET /stop") >= 0) {
            Serial.println("Stop");
            servo1.write(90);
            servo2.write(90);            
            } else if (header.indexOf("GET /right") >= 0) {
            Serial.println("Right");
            servo1.write(170);
            servo2.write(90);   
            } else if (header.indexOf("GET /reverse") >= 0) {
            Serial.println("Reverse");
            servo1.write(10);
            servo2.write(170);         
            }
            // Display the HTML web page
            client.println("<!DOCTYPE HTML><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");
            // CSS to style the buttons
            // Feel free to change the background-color and font-size attributes to fit your preferences
            client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");
            client.println(".button { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; background-color: #4CAF50;");
            client.println("border: none; color: white; padding: 12px 28px; text-decoration: none; font-size: 26px; margin: 1px; cursor: pointer;}");
            client.println(".button2 {background-color: #555555;}</style>");
            client.println("<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js\"></script></head>");
            
            // Web Page      
            client.println("<p><button class=\"button\" onclick=\"moveForward()\">FORWARD</button></p>");
            client.println("<div style=\"clear: both;\"><p><button class=\"button\" onclick=\"moveLeft()\">LEFT </button>");
            client.println("<button class=\"button button2\" onclick=\"stopRobot()\">STOP</button>");
            client.println("<button class=\"button\" onclick=\"moveRight()\">RIGHT</button></p></div>");
            client.println("<p><button class=\"button\" onclick=\"moveReverse()\">REVERSE</button></p>");
            client.println("<p>Motor Speed: <span id=\"motorSpeed\"></span></p>");         
            client.println("<input type=\"range\" min=\"0\" max=\"100\" step=\"25\" id=\"motorSlider\" onchange=\"motorSpeed(this.value)\" value=\"" + valueString + "\"/>");
            
            client.println("<script>$.ajaxSetup({timeout:1000});");
            client.println("function moveForward() { $.get(\"/forward\"); {Connection: close};}");
            client.println("function moveLeft() { $.get(\"/left\"); {Connection: close};}");
            client.println("function stopRobot() {$.get(\"/stop\"); {Connection: close};}");
            client.println("function moveRight() { $.get(\"/right\"); {Connection: close};}");
            client.println("function moveReverse() { $.get(\"/reverse\"); {Connection: close};}");
            client.println("var slider = document.getElementById(\"motorSlider\");");
            client.println("var motorP = document.getElementById(\"motorSpeed\"); motorP.innerHTML = slider.value;");
            client.println("slider.oninput = function() { slider.value = this.value; motorP.innerHTML = this.value; }");
            client.println("function motorSpeed(pos) { $.get(\"/?value=\" + pos + \"&\"); {Connection: close};}</script>");
         
            client.println("</html>");
            
            //Request example: GET /?value=100& HTTP/1.1 - sets PWM duty cycle to 100% = 255
            if(header.indexOf("GET /?value=")>=0) {
            pos1 = header.indexOf('=');
            pos2 = header.indexOf('&');
            valueString = header.substring(pos1+1, pos2);
            //Set motor speed value
            if (valueString == "0") {
               servo1.write(90);
               servo2.write(90);
            }
            else {
                Serial.println(valueString);
            }
            }         
            // The HTTP response ends with another blank line
            client.println();
            // Break out of the while loop
            break;
          } else { // if you got a newline, then clear currentLine
            currentLine = "";
          }
      } else if (c != '\r') {// if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
      }
      }
    }
    // Clear the header variable
    header = "";
    // Close the connection
    client.stop();
    Serial.println("Client disconnected.");
    Serial.println("");
}
}
// Fill the dots one after the other with a color
void colorWipe(uint32_t c, uint8_t wait) {
//for(uint16_t i=0; i<strip.numPixels(); i++) {
for(uint16_t i=1; i<4; i++) {
    strip.setPixelColor(i, c);
    strip.show();
    delay(wait);
}
}

驴友花雕 发表于 4 天前

【Arduino 动手做】使用TTGO T-Beam 的 ESP32 舵机机器人

附录
【Arduino 动手做】使用TTGO T-Beam 的 ESP32 舵机机器人
项目链接:https://www.instructables.com/ESP32-Robot-Using-Servos/
项目作者:伦敦 otterbot
项目代码:https://content.instructables.com/FAX/PATQ/JYMTDSE9/FAXPATQJYMTDSE9.ino







页: [1]
查看完整版本: 【Arduino 动手做】使用TTGO T-Beam 的 ESP32 舵机机器人