【Arduino 动手做】带有 ESP32 和 FreeRTOS 的微型桌面机器人

在这里插入图片描述

《Arduino 手册(思路与案例)》栏目介绍:
在电子制作与智能控制的应用领域:广泛涉及了Arduino BLDC、Arduino CNC、Arduino ESP32 SPP、Arduino FreeRTOS、Arduino FOC、Arduino GRBL、Arduino HTTP、Arduino HUB75、Arduino IoT Cloud、Arduino JSON、Arduino LCD、Arduino OLED、Arduino LVGL、Arduino PID 及 Arduino TFT 等方面的相关拓展思路和众多参考案例。本专栏目前博客近2300篇。
https://blog.csdn.net/weixin_41659040/category_12422453.html

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

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

在这里插入图片描述

DLR(哑巴小机器人)是我名为 Deskbots 的更大项目的一部分。住在您办公桌上的小机器人。到目前为止,我已经制作了它们的 3 种不同的变体。

这是一个具有 freeRTOS 和面部表情的微型 ESP32 机器人。它通过 wifi 应用程序进行无线控制。它有一个 Oled 屏幕,根据机器人正在做什么显示不同的面部动画。

这是该项目的第一个版本。我只添加了基本表达式,将来会添加更多。将来我还会添加 SLAM 和 Swarming 功能。我已经在尝试 SLAM 并取得了非常好的结果。在一个项目中解释所有内容会太多,因此我决定制作它的多个部分。此项目包含有关如何组装和使用机器人的说明。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

材料与用品

ESP32 扫帚 32D
128X64 镀锌
TB6612FNG 电机驱动器
DC-DC 降压模块
7.4 伏 Lipo 电池
MPU6050 (可选)
2 X N20 电机
M3(15 毫米和 6 毫米)螺丝
2mm 螺丝

在这里插入图片描述

第 1 步:3D 打印部件

机身在 ender 3 Pro 上用 PLA 打印。它使用 M3 和 2mm 螺钉连接在一起。我没有把它设计得很坚固,我想尽可能地小。所以有些部分有点弱。但好消息是我还包含了原始设计文件,因此您可以轻松更改设计。

您可以从此处下载 STL 文件。Grabcad 具有爆炸模型功能,因此如果您不确定哪个部分放在哪里,请使用它。我在教程视频中详细展示了该组件。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第 2 步:电路和电路板连接

我为我是如何找到减少布线的方法感到非常自豪的。基本上,不同的组件安装在两个 PCB 上以连接 PCB,我使用非常大的母头作为板对板连接器。他们给了我足够的空间来放置带有引脚和接头的传感器。

我的目标是使电路尽可能小,因此我使用了双面穿孔板。电路分为两层,顶层包含 ESP32,底层有电机驱动器和降压模块。也有MPU6050的空间,但目前,我没有使用它,因为我把 MPU 和 OLED 放在同一行,所以当我尝试同时使用它们时,我在动画方面遇到了一些问题。所以现在我已经删除了 MPU。

我在视频的这一部分展示了如何组装电路板。

电池和电源

我正在使用 7 伏 Lipo 供电。降压模块用于将电压降低到 5V 为 ESP32 供电。Oled 由 ESP32 本身通过 3V 线路供电。

“不要向 ESP5 提供超过 32V 的电压”

为电机驱动器供电

电机驱动器有两个电源连接,一个用于电机驱动器芯片,一个用于电机。对于电机,我们为“VM”引脚提供 7V,对于驱动芯片,我们从 ESP32 板向“VCC”提供 3-5V 电压。请阅读本文以了解此电动机驱动器的工作原理。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第 3 步:上传程序

使用 Arduino ide 上传 sketch。您必须选择 ESP32 开发板才能执行此作。默认情况下,您不会在开发板的管理器中看到 ESP32。使用此链接将 ESP32 添加到 Arduino IDE

“请勿同时将电池和 USB 数据线连接到 ESP32”

您将需要一些额外的库来编译代码。它们都可以在 Arduino 库管理器中找到。如果您需要帮助,请阅读这篇文章 如何将库添加到 Arduino IDE。

Adafruit GFX
Adafruit SSD1306
异步 UDP
要编译代码,请选择您的 ESP32 开发板。此代码应该适用于大多数 ESP32 开发板。

Tools > Ports > Select your board(ESP32 Dev Kit 1)

如果一切设置正常,代码应该可以编译。然后,如果一切正常,只需上传代码,您将看到机器人第一次闪烁。代码在 GitHub 上可用,如果有任何问题请提出问题,我会尽力帮助你。

*请使用 IDE 1.8 新版,错误很多

*如果您遇到任何与 anlogwrite 函数相关的错误,请尝试在 MotorController.h 文件中注释或取消注释 #include < analogWrite.h>

在这里插入图片描述
在这里插入图片描述

第 4 步:开源与代码解释

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "WiFi.h"
#include "roboteyes.h"
#include "AsyncUDP.h"
#include "seperatestring.h"
#include "MotorController.h"
#define freertos/queue.h

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

//char Direction ='';
//left side motor
#define PWM1 12
#define AIN1 14
#define AIN2 27
//Right side motor
#define PWM2 32
#define BIN1 33
#define BIN2 25
//#define STBY 26
#define CommandArray_SIZE   20

char *CommandArray [CommandArray_SIZE]; // this is reused each call
char *strData = NULL; // this is allocated in separate and needs to be free( ) eventually

const char * ssid = "SINGTEL-42E6";//Change your wifi name
const char * password = "huiweimaev";//Change your wifi password
const char * BroadcastData = "";
AsyncUDP udp;
String PhrasedData = "";

int Speed=255;
int count = 1;
int aninum=0;
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

TaskHandle_t xHandle = NULL;
//Animation task
//There are better ways of doing this but this was the easiest.
//Let me know if you can make this part better.
void dispTask(void *pvParameters) {

     while (1) {   
          if(aninum ==1) //animation number 1 when robot goes left
          {
            //Serial.println("left");
             look_left(500);
          }
          if(aninum ==2)//animation number 2 when robot goes right
          {  //Serial.println("right");
             look_right(500);
          }
          if(aninum ==0)//animation number 0 for rest.
          {  //Serial.println("right");
             //blink_eyes(1000,1);
             sad_blink(400,1);
          }
         // vTaskDelete(xHandle);
           //vTaskSuspend( NULL );
    vTaskDelay(1);
    }
}

void setup() {
      //Motor drivers pins
  pinMode(PWM1,OUTPUT);
  pinMode(AIN1,OUTPUT);
  pinMode(AIN2,OUTPUT);
  pinMode(PWM2,OUTPUT);
  pinMode(BIN1,OUTPUT);
  pinMode(BIN2,OUTPUT);
  Serial.begin(115200);
  //-startwifi  
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    if (WiFi.waitForConnectResult() != WL_CONNECTED) {
        Serial.println("WiFi Failed");
        while(1) {
            vTaskDelay(500/ portTICK_RATE_MS);
        }
    }
      // Recieve data through wifi
  if(udp.listen(1234)) {//UDP port number if there is any problem change this.
        Serial.print("UDP Listening on IP: ");
        Serial.println(WiFi.localIP());
        udp.onPacket([] (AsyncUDPPacket packet) {      
            /*
            Serial.print(", From: ");
            Serial.print(packet.remoteIP());
            Serial.print(":");
            Serial.print(", Length: ");
            Serial.print(packet.length());
            Serial.print(", Data: ");
            Serial.write(packet.data(), packet.length());
            Serial.println();
            */
            //Data phrasing making recieved readable into string
            //packet.printf("Got %u bytes of data", packet.length());
            char* tmpStr = (char*) malloc(packet.length() + 1);
            memcpy(tmpStr, packet.data(), packet.length());
            tmpStr[packet.length()] = '\0'; // ensure null termination
            PhrasedData = String(tmpStr);
            free(tmpStr); // Strign(char*) creates a copy so we can delete our one
            Serial.println(PhrasedData);   
        });      
  }
 //display 
 if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
    Serial.println(F("SSD1306 allocation failed"));
    for(;;);
  }
  display.clearDisplay();
  //Create a task to diaply eyes animation
    xTaskCreatePinnedToCore(
    dispTask,   // Display task
    "dispTask", // Task name
    4096,       // Stack size
    NULL,       // No parameters
    0,          // Priority
    &xHandle,       // No handle returned
    0);

}
void loop() {
  
  Plzgetstring(PhrasedData);// Get the incoming data and seprerate the commands into array.
  Speed = (String(CommandArray [1])).toInt();//Speed of the robot
  if(String(CommandArray [0])=="f"|| String(CommandArray [0])=="F")
{
 forward(PWM1,AIN1,AIN2,PWM2,BIN1,BIN2,Speed); 
}
if(String(CommandArray [0])=="b"|| String(CommandArray [0])=="B")
{
 backward(PWM1,AIN1,AIN2,PWM2,BIN1,BIN2,Speed); 
}
if(String(CommandArray [0])=="L"|| String(CommandArray [0])=="l")
{
 left(PWM1,AIN1,AIN2,PWM2,BIN1,BIN2,Speed);
 aninum=1;
}
if(String(CommandArray [0])=="R"|| String(CommandArray [0])=="r")
{
 right(PWM1,AIN1,AIN2,PWM2,BIN1,BIN2,Speed);
 aninum=2;
}
if(String(CommandArray [0])=="S" || String(CommandArray [0])=="s")
{
 aninum=0;
 Stop(PWM1,AIN1,AIN2,PWM2,BIN1,BIN2,Speed);
} 
}

void Plzgetstring(String phraseData)
{
  String str;
  int N = 0;
  str = phraseData;
  //Serial.print(F("input str:")); Serial.println(str);
  N = separate (str, CommandArray, CommandArray_SIZE,  &strData);
 // for (int n = 0; n < N; n++) {
   // Serial.println (CommandArray [n]);
 // }
  freeData(&strData);
}

代码非常简单,基本上它通过 wifi 接收数据并分离字符串并根据查看的命令控制电机。 “roboteyes.h”、“seperatestring.h” 和 “MotorController.h” 是自定义库。这些包含在 sketch 文件夹中。我将来会更新更多的表情和面部动画。这是我第一次尝试制作一个库,所以任何反馈和建议都会有所帮助。

您需要的所有库。

   #include <Wire.h>
	#include <Adafruit_GFX.h>
	#include <Adafruit_SSD1306.h>
	#include "WiFi.h"
	#include "roboteyes.h"
	#include "AsyncUDP.h"
	#include "seperatestring.h"
	#include "MotorController.h"
	#define freertos/queue.h

将您的 SSID 和密码更改为您的 wifi 网络。

const char * ssid = "SINGTEL-42E6";//Change your wifi name
const char * password = "huiweimaev";//Change your wifi password

这是 UDP 端口。我把它设为 1234,所以很容易记住,但你可以把它改成你想要的任何四位数。

   if(udp.listen(1234)) {//UDP port number if there is any problem change this.

在这里,我们创建 “dispTask” 来处理面部动画。借助 FreeRTOS,我们可以同时播放动画和移动机器人。有更好的方法可以做到这一点,比如使用队列,但我无法让它工作。所以如果你能改进这部分,请告诉我。

xTaskCreatePinnedToCore(
    dispTask,   // Display task
    "dispTask", // Task name
    4096,       // Stack size
    NULL,       // No parameters
    0,          // Priority
    &xHandle,       // No handle returned
    0);

此函数通过 wifi 读取传入数据,并将每个命令分离到一个数组中

Plzgetstring(PhrasedData);// Get the incoming data and seprerate the commands into array.

在这里插入图片描述

第 5 步:应用程序

在您的手机上下载并安装该应用程序。Apk 文件包含在 Github 上的代码中。要使用该应用程序,您需要两样东西:IP 地址和 UDP 端口号。

默认 UDP 端口为 1234

要获取 IP 地址,请在 Deskbot 通过 USB 连接到 PC 时打开串行监视器。然后按 ESP32 上的 reset。如果 wifi 连接成功,您将看到 IP 地址。

Tools > Serial monitor > Change baud rate to 115200

打开应用程序并键入 IP 地址和 UDP 端口号。您的手机和 Deskbot 应该在同一个 wifi 网络上。如果一切正常,您应该能够驾驶钻头。

“请勿同时将电池和 USB 数据线连接到 EPS32”

现在我们已经结束了,享受与您的新可爱朋友一起玩耍的乐趣。如果您被困在任何地方或需要更详细的说明,请告诉我我会更新此页面。

在这里插入图片描述
在这里插入图片描述

第 6 步:您可以改进的地方

结构

我试图让这个东西尽可能小,所以有些部件非常薄,比如顶部机箱。用于固定板的顶部结构有很大的回旋余地。PCB 应该像刷信用卡一样滑入和滑出,但我搞砸了公差,所以现在 weels 阻止了滑动。发生这种情况是因为我使用了 M3 螺丝,它有一个非常大的螺丝头。我订购了一些较小的螺丝,但它们没有按时到达。你可以在视频中看到我努力把所有东西都放在一起。我建议你使用更小的螺丝并调整模型文件,但一切都会适合,因为它是用 PLA 打印的,所以它有点灵活。

自定义库

为了保持代码的简洁性,我为电机控制和动画制作了一个自定义库。这是我的第一次尝试,我想要一些快速的东西,但可以由更有经验的人来改进。此外,我使用 Tasks 来显示动画,这不是最有效的方法。任何有 FreeRTOS 经验的人都请帮助我改进这一点。

齿轮系

车轮系统是一个齿轮系。你应该用数学来正确地制作它们,但我软件中的齿轮功能无法正常工作,所以我只是手动绘制它们并调整公差,直到它们适合在一起。我将使用小轴承,使将来一切顺利进行。

电池放置

最初电池应该放在底部,但后来我决定选择更大的电池并将其放在后面。现在,如果您尝试攀爬大于 30 度的角度,机器人会翻转。您可以使用两个适合底部的小 3V lipo。

车轮

您会注意到轮子上有凹槽。它们帮助机器人攀爬大部分表面。由于齿轮机构,机器人具有很大的扭矩。凹槽实际上是用来添加橡皮筋的,就像坦克履带一样,但有时橡皮筋会打滑,所以如果你需要更多的抓地力,你可以剪断橡皮筋,然后用胶水粘在每个轮子的缝隙里。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第 7 步:未来更新

这是这个项目的第一部分,所以我只包含了基本的东西。在未来的更新中,我将添加更逼真的动画和更多传感器。

我这个项目的最终目标是制作一个小巧的桌面助手,它位于我的办公桌上,并且足够智能,可以在不碰撞或从桌子上掉下来的情况下导航。我不知道它在现实生活中的用途是什么。

您可能已经在代码中看到了一些额外的内容,例如 MPU6050 引用和应用程序中的一些冗余功能。那是因为我已经在试验 SLAM。您在应用程序中看到的黑框是一个画布,您将在其中看到机器人在桌子上移动时绘制的地图。此外,顶部的风扇未连接到任何东西。我打算添加 LED,但认为事情可能会变热,所以我添加了风扇。它看起来很酷,所以我保留了它。

附录
项目链接:https://www.instructables.com/Tiny-Desktop-Robot-With-ESP32-and-FreeRTOS/
项目作者:印度 shubham_bhatt
视频教程(6分钟):https://www.youtube.com/watch?v=DRXjLnhupkE
3D打印文件:https://grabcad.com/library/esp32-3d-printed-robot-1
项目开源代码:https://github.com/sastejugaad/LittleBoi

在这里插入图片描述

### Arduino ESP32-S3 上使用 FreeRTOS 的教程 #### 1. 安装必要的库支持包 为了在Arduino IDE中支持ESP32-S3开发板以及FreeRTOS功能,需安装对应的硬件支持包。通过Arduino IDE首选项页面添加URL指向Espressif Systems官方资源链接[^2]。 #### 2. 创建基于FreeRTOS的任务 创建新项目时可以定义多个独立执行路径即任务(Task),这些任务将在CPU时间片分配下轮流运行。下面是一个简单的例子展示如何建立两个并发工作的函数作为单独的任务来处理不同工作负载: ```cpp #include "freertos/FreeRTOS.h" #include "freertos/task.h" void TaskBlink(void *pvParameters){ const char* pcTaskName = (char*) pvParameters; int ledPin = *((int *)pcTaskName); while(true){ digitalWrite(ledPin, HIGH); vTaskDelay(pdMS_TO_TICKS(500)); // 延迟500ms digitalWrite(ledPin, LOW); vTaskDelay(pdMS_TO_TICKS(500)); } } void setup() { Serial.begin(115200); pinMode(LED_BUILTIN, OUTPUT); xTaskCreate( TaskBlink, "LED Blink", configMINIMAL_STACK_SIZE + 1024, &LED_BUILTIN, tskIDLE_PRIORITY + 1, NULL ); } ``` 此代码片段展示了如何启动一个名为`TaskBlink`的新线程去控制内置LED闪烁操作。 #### 3. 实现任务间的同步与通信 当应用程序中有多个任务需要共享数据或协调动作时,则需要用到队列(Queue)、信号量(Semaphore)等机制来进行安全有效的交互。例如,在生产者-消费者模式里可以通过消息队列传递物品给另一个进程消费;又或者是利用互斥锁(mutex)保护临界区内的变量访问防止竞争条件发生。 ```cpp SemaphoreHandle_t xBinarySemaphore; void setup(){ ... xBinarySemaphore = xSemaphoreCreateBinary(); if(xBinarySemaphore != NULL){ // 初始化二进制信号量状态... } } ``` 上述示例说明了怎样声明并初始化了一个二进制型别的信号量对象用于后续的任务间同步目的。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

驴友花雕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值