【Arduino 动手做】认识 TN24,您迷人且可定制的桌面伴侣机器人

在这里插入图片描述
《Arduino 手册(思路与案例)》栏目介绍:
在电子制作与智能控制的应用领域,本栏目涵盖了丰富的内容,包括但不限于以下主题:Arduino BLDC、Arduino CNC、Arduino E-Ink、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,以及Arduino智能家居、智慧交通、月球基地、智慧校园和智慧农业等多个方面与领域。不仅探讨了这些技术的基础知识和应用领域,还提供了众多具体的参考案例,帮助读者更好地理解和运用Arduino平台进行创新项目。目前,本栏目已有近4000篇相关博客,旨在为广大电子爱好者和开发者提供全面的学习资源与实践指导。通过这些丰富的案例和思路,读者可以获取灵感,推动自己的创作与开发进程。
https://blog.youkuaiyun.com/weixin_41659040/category_12422453.html

在这里插入图片描述
认识 TN24,您迷人且可定制的桌面伴侣机器人。它结合了运动、表情和方向感应,为任何工作空间带来欢乐和活力。我在设计时考虑到了简单性,TN24 易于构建,非常适合初学者和经验丰富的制造商。它的机身由回收纸板制成,不仅重量轻而且环保。无论您是想调整其动作、表情还是设计,TN24 都为个性化提供了无限的可能性。TN24 不仅仅是一个小工具,它还是一个回应和娱乐的小朋友。

用品
以下是构建 TN24 所需的内容:

电子学
Xiao ESP32S3 — 主微控制器。
4 个伺服电机 — 用于腿部运动(例如 SG90 或 MG90S)。
OLED 显示屏 — 用于眼睛动画(例如,0.96 英寸SSD1306)。
加速度计 — 用于方向检测(例如,MPU6050 或 ADXL345)。
锂离子电池 — 为机器人供电(例如,3.7V 1200mAh)。
接头引脚 - 连接伺服器

结构组件
用于身体和腿部部件的回收纸板。

工具
烙铁和焊料。
热胶枪
剪线钳和剥线钳。

软件
Arduino IDE (用于编写 Xiao ESP32S3)。
必需库:Servo、Adafruit_GFX Adafruit_SSD1306。

第 1 步:设计和组装
车身设计
我设计了一个紧凑的机器人身体,为微控制器、电池、伺服系统和 OLED 显示屏提供了空间。随意尝试设计,但请确保它是矩形的。
使用回收的纸板制作身体和腿部。

腿机构
每条腿都由伺服电机提供动力,以实现行走运动。
使用螺钉或胶水将伺服喇叭连接到纸板腿上。
确保腿部对称并对齐以实现平衡运动。

眼睛显示
将 OLED 显示屏牢固地安装在机器人的正面。
显示屏将显示动画眼睛,这些眼睛会根据机器人的方向或输入改变表情。

方向传感器
将加速度计安装在机器人的中心,以实现准确的方向检测。
用双面胶带或螺丝固定。

第 2 步:电子元件和布线
我们将使用 tinkercad 来测试和体验伺服系统,以确保它们运行良好。

小ESP32S3:连接舵机、OLED、加速度计和电池。
舵机:将信号引脚连接到 GPIO 引脚(例如,D0 - 左前舵机,D1 - 右前舵机,D2 - 后舵机,D3 - 右后舵机)。
OLED:将 SDA 和 SCL 连接到 I2C 引脚。D4(SDA) 和 D5(SCl)
加速度计:将 SDA 和 SCL 连接到 I2C 引脚。D4(SDA) 和 D5(SCl)
电池:连接小ESP32S3的电源输入(确保极性正确)。

我使用了穿孔板来实现干净的连接并减少电线缠结。
焊接每个伺服器、IMU 和电池的接头引脚,使其易于接近。
捆绑并固定电线以防止缠结。

第 3 步:编程
将 Xiao ESP32S3 编程为:
协调行走的伺服运动。
在 OLED 上显示动画眼睛。
使用加速度计检测方向并触发反应。
Arduino 代码
从我的 GitHub 存储库下载代码
确保 expressions.h 与 TN24.ino 位于同一文件夹中,以便 OLED 动画正常工作。
您可以根据需要随意自定义代码,以实现与机器人;)一起玩乐所需的乐趣
此处提供了完整的代码。
解压缩文件夹后。在 arduino IDE 中打开代码,确保您选择了正确的板子并上传代码

第 4 步:电源和电池管理
使用锂离子电池为机器人供电。
如果您希望 TN24 可充电,请集成充电模块。
为方便起见,请添加电源开关。

第 5 步:测试和校准
单独测试每个伺服以确保平稳运动。
校准步行模式以实现平衡运动。
调整眼睛动画以获得所需的表情。
测试加速度计以实现准确的方向检测。

第 6 步:玩得开心
打开机器人的电源并将其放在办公桌或地板上,您将看到奇迹发生。要使机器人移动或玩耍,请点击它或摇晃它两次。这些动作在代码上是完全可定制的。我经常更新存储库以添加新的动作、游戏和自定义。

第 7 步:结论
TN24 是一款令人愉悦的桌面伴侣,展示了创造力、工程和运动的乐趣。TN24 由回收纸板制成,不仅具有创新性,而且环保,激发了可持续机器人技术的灵感。我希望本指南能激发您构建自己的 TN24 并探索机器人世界。

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

项目代码

#include <ESP32Servo.h>
#include <Wire.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include "expressions.h"

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define TAP_THRESHOLD 1    // Adjust based on testing
#define TAP_WINDOW 500     // Time window for double tap (ms)

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Adafruit_MPU6050 mpu;

// Servo objects and pins
Servo frontLeftServo;
Servo frontRightServo;
Servo backLeftServo;
Servo backRightServo;

const int frontLeftPin = D0;
const int frontRightPin = D1;
const int backLeftPin = D2;
const int backRightPin = D3;

// State definitions
enum RobotState {
  SLEEPING,
  ACTIVE,
  UPSIDE_DOWN
};

// Servo angles
const int standAngle = 90;
const int sleepAngle = 0;
const int forwardStep = 60;
const int backwardStep = 150;
const int sitAngle = 20;
const int danceAngle1 = 60;
const int danceAngle2 = 120;

// Global variables
RobotState currentState = SLEEPING;
unsigned long lastTapTime = 0;
int tapCount = 0;
int delay_time = 200;
bool isUpright = true;
unsigned long lastActionTime = 0;
const unsigned long IDLE_TIMEOUT = 30000; // 30 seconds before sleeping

void setup() {
  Serial.begin(115200);
  
  // Initialize display
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;);
  }
  
  // Show boot animation
  showBootAnimation();
  
  // Initialize servos
  frontLeftServo.attach(frontLeftPin);
  frontRightServo.attach(frontRightPin);
  backLeftServo.attach(backLeftPin);
  backRightServo.attach(backRightPin);
  
  // Initialize MPU6050
  initializeMPU();

  mpu.setHighPassFilter(MPU6050_HIGHPASS_0_63_HZ);
  mpu.setMotionDetectionThreshold(9);
  mpu.setMotionDetectionDuration(20);
  mpu.setInterruptPinLatch(true);	// Keep it latched.  Will turn off when reinitialized.
  mpu.setInterruptPinPolarity(true);
  mpu.setMotionInterrupt(true);
  
  stand();
  delay(1000);
  // Start in sleeping position
  sleep();
  sleepy();
}

void showBootAnimation() {
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(WHITE);
  
  // Fade in effect
  for(int i = 0; i < 255; i += 51) {
    display.clearDisplay();
    display.setTextColor(WHITE);
    display.setCursor(35, 25);
    display.println("TN 24");
    display.display();
    delay(100);
  }
  delay(1000);
  display.clearDisplay();
  display.display();
}

void loop() {
  checkOrientation();
  checkTaps();
  
  switch(currentState) {
    case SLEEPING:
      handleSleepingState();
      break;
      
    case ACTIVE:
      handleActiveState();
      break;
      
    case UPSIDE_DOWN:
      handleUpsideDownState();
      break;
  }
}

void checkTaps() {
  sensors_event_t a, g, temp;
  mpu.getEvent(&a, &g, &temp);
  
  // float acceleration = sqrt(sq(a.acceleration.x) + sq(a.acceleration.y) + sq(a.acceleration.z));
  // Serial.println(acceleration);

  if(mpu.getMotionInterruptStatus()) {
    unsigned long currentTime = millis();
    Serial.println("Tap");
    

      tapCount++;

      if (tapCount == 2) { // Double tap detected
        Serial.println("Double Tap");
        if (currentState == SLEEPING) {
          wakeUp();
        }
        tapCount = 0;
      }

    lastTapTime = currentTime;
  }
}

void handleSleepingState() {
  sleepy();
  if (millis() - lastActionTime > 5000) {
    // Occasionally show sleeping animation
    if (random(100) < 10) {
      for (int i = 0; i < 3; i++) {
        dizzy();
        delay(500);
      }
    }
  }
}

void handleActiveState() {
  // Check if idle for too long
  if (millis() - lastActionTime > IDLE_TIMEOUT) {
    goToSleep();
    return;
  }



     mischievous();
     walkForward();
     delay(1000);

    cute();
    dance();
    delay(1000);

    wink();
    react();
    delay(1000);

    thinking();
    sit();
    delay(1000);

    sleep();
    mischievous();
    delay(1000);

    stand();
    love();
    delay(1000);


    play();
    mischievous();
    delay(1000);

  
  // Random playful behaviors
  // int action = random(100);
  // if (action < 20) {
  //    mischievous();
  //    walkForward();
  // } else if (action < 40) {
  //   cute();
  //   dance();
  // } else if (action < 60) {
  //   wink();
  //   react();
  // } else if (action < 80) {
  //   thinking();
  //   sit();
  //   delay(1000);
  //   stand();
  // } else {
  //   love();
  //   play();
  // }
  
  lastActionTime = millis();
}

void handleUpsideDownState() {
  static unsigned long lastUpsideDownAction = 0;
  const unsigned long ACTION_INTERVAL = 1000;
  
  if (millis() - lastUpsideDownAction > ACTION_INTERVAL) {
    crying();
    panicMovement();
    lastUpsideDownAction = millis();
  }
}

void panicMovement() {
  // Random panicked leg movements
  for (int i = 0; i < 4; i++) {
    frontLeftServo.write(random(0, 180));
    frontRightServo.write(random(0, 180));
    backLeftServo.write(random(0, 180));
    backRightServo.write(random(0, 180));
    delay(200);
  }
  stand(); // Try to return to standing position
}

void wakeUp() {
  currentState = ACTIVE;
  happy();
  for (int angle = sleepAngle; angle <= standAngle; angle += 5) {
    frontLeftServo.write(angle);
    frontRightServo.write(angle);
    backLeftServo.write(angle);
    backRightServo.write(angle);
    delay(20);
  }
  lastActionTime = millis();
}

void goToSleep() {
  currentState = SLEEPING;
  sleepy();
  for (int angle = standAngle; angle >= sleepAngle; angle -= 5) {
    frontLeftServo.write(angle);
    frontRightServo.write(180 - angle);
    backLeftServo.write(180);
    backRightServo.write(angle);
    delay(20);
  }
}

void checkOrientation() {
  sensors_event_t a, g, temp;
  mpu.getEvent(&a, &g, &temp);
  
  float zAcceleration = a.acceleration.z;
  bool wasUpright = isUpright;
  isUpright = zAcceleration > 1.0;
  
  if (!isUpright && wasUpright) {
    currentState = UPSIDE_DOWN;
    crying();
  } else if (isUpright && !wasUpright) {
    currentState = ACTIVE;
    happy();
  }
}

void initializeMPU() {
  if (!mpu.begin()) {
    Serial.println("Failed to find MPU6050 chip!");
    while (1) delay(10);
  }
  
  mpu.setAccelerometerRange(MPU6050_RANGE_8_G);
  mpu.setGyroRange(MPU6050_RANGE_500_DEG);
  mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);
  delay(100);
}

void selfRight() {
  Serial.println("Attempting to self-right...");
  // Add logic to try returning to upright position
  frontLeftServo.write(sleepAngle);
  frontRightServo.write(180 - sleepAngle); // Inverted for the right side
  backLeftServo.write(180);
  backRightServo.write(sleepAngle); // Inverted for the right side
  delay(delay_time);
  stand();
  delay(delay_time);
}

// Rest of the movement functions (walkForward, walkBackward, etc.) remain unchanged
void stand() {
  frontLeftServo.write(standAngle);
  frontRightServo.write(standAngle);
  backLeftServo.write(standAngle);
  backRightServo.write(standAngle);
}

void sleep() {
  frontLeftServo.write(sleepAngle);
  frontRightServo.write(180 - sleepAngle); // Inverted for the right side
  backLeftServo.write(180);
  backRightServo.write(sleepAngle); // Inverted for the right side
  delay(1000);
}

void walkForward() {
  // Lift and move front-left and back-right legs
  frontLeftServo.write(forwardStep);
  backRightServo.write(180 - forwardStep); // Inverted for the right side
  delay(delay_time);

  // Reset to standing position
  frontLeftServo.write(standAngle);
  backRightServo.write(standAngle);
  delay(delay_time);

  // Lift and move front-right and back-left legs
  frontRightServo.write(180 - forwardStep); // Inverted for the right side
  backLeftServo.write(forwardStep);
  delay(delay_time);

  // Reset to standing position
  frontRightServo.write(standAngle);
  backLeftServo.write(standAngle);
  delay(delay_time);
}


void walkBackward() {
  // Similar to moveForward but in reverse
  frontLeftServo.write(backwardStep);
  backRightServo.write(180 - backwardStep); // Inverted for the right side
  delay(500);

  frontLeftServo.write(standAngle);
  backRightServo.write(standAngle);
  delay(500);

  frontRightServo.write(180 - backwardStep); // Inverted for the right side
  backLeftServo.write(backwardStep);
  delay(500);

  frontRightServo.write(standAngle);
  backLeftServo.write(standAngle);
  delay(500);
}


void turnLeft() {
  // Move only one side of the legs forward
  frontLeftServo.write(forwardStep);
  backLeftServo.write(forwardStep);
  delay(500);

  frontLeftServo.write(standAngle);
  backLeftServo.write(standAngle);
  delay(500);
}

void turnRight() {
  // Move only the other side of the legs forward
  frontRightServo.write(180 - forwardStep); // Inverted for the right side
  backRightServo.write(180 - forwardStep); // Inverted for the right side
  delay(500);

  frontRightServo.write(standAngle);
  backRightServo.write(standAngle);
  delay(500);
}

void sit() {
  // Set all legs to a sitting angle
  frontLeftServo.write(90);
  frontRightServo.write(90); // Inverted for the right side
  backLeftServo.write(180 - sitAngle);
  backRightServo.write(sitAngle); // Inverted for the right side
  delay(1000);


}

void dance() {
  for (int i = 0; i < 3; i++) {
    frontLeftServo.write(danceAngle1);
    backRightServo.write(180 - danceAngle1); // Inverted for the right side
    delay(200);

    frontRightServo.write(180 - danceAngle2); // Inverted for the right side
    backLeftServo.write(danceAngle2);
    delay(200);

    frontLeftServo.write(danceAngle2);
    backRightServo.write(180 - danceAngle2); // Inverted for the right side
    delay(200);

    frontRightServo.write(180 - danceAngle1); // Inverted for the right side
    backLeftServo.write(danceAngle1);
    delay(200);
  }

  // Return to standing position
  frontLeftServo.write(standAngle);
  frontRightServo.write(standAngle);
  backLeftServo.write(standAngle);
  backRightServo.write(standAngle);
}

void react() {
  frontLeftServo.write(90);
  frontRightServo.write(90); // Inverted for the right side
  backLeftServo.write(180 - sitAngle);
  backRightServo.write(sitAngle); // Inverted for the right side
  delay(1000);

  // Return to standing position
  stand();
  delay(500);

  frontLeftServo.write(sitAngle);
  frontRightServo.write(180 - sitAngle); // Inverted for the right side
  backLeftServo.write(90);
  backRightServo.write(90); // Inverted for the right side
  delay(1000);

int del = 250;
  frontLeftServo.write(0);
  frontRightServo.write(160); // Inverted for the right side
  delay(del);

  frontLeftServo.write(20 );
  frontRightServo.write(180 ); // Inverted for the right side
  delay(del);


  frontLeftServo.write(0);
  frontRightServo.write(180 - sitAngle); // Inverted for the right side
  delay(del);


  frontLeftServo.write(sitAngle );
  frontRightServo.write(180); // Inverted for the right side
  delay(del);

  frontLeftServo.write(0);
  frontRightServo.write(160); // Inverted for the right side
  delay(del);

  frontLeftServo.write(20 );
  frontRightServo.write(180 ); // Inverted for the right side
  delay(del);


  frontLeftServo.write(0);
  frontRightServo.write(180 - sitAngle); // Inverted for the right side
  delay(del);


  frontLeftServo.write(sitAngle );
  frontRightServo.write(170); // Inverted for the right side
  delay(del);

  // Return to standing position
  stand();
  delay(500);
}


void play()
{
  for (int i = 0; i < 3; i++) {
    frontLeftServo.write(danceAngle1);
    backRightServo.write(180 - danceAngle1); // Inverted for the right side
    delay(200);

    frontRightServo.write(180 - danceAngle2); // Inverted for the right side
    backLeftServo.write(danceAngle2);
    delay(200);

    frontLeftServo.write(danceAngle2);
    backRightServo.write(180 - danceAngle2); // Inverted for the right side
    delay(200);

    frontRightServo.write(180 - danceAngle1); // Inverted for the right side
    backLeftServo.write(danceAngle1);
    delay(200);
  }

  // Return to standing position
  frontLeftServo.write(standAngle);
  frontRightServo.write(standAngle);
  backLeftServo.write(standAngle);
  backRightServo.write(standAngle);

}

【Arduino 动手做】认识 TN24,您迷人且可定制的桌面伴侣机器人
项目链接:https://www.instructables.com/Cute-Desktop-Companion-Robot-TN24/
项目作者: tech_nickk

项目视频 :https://www.youtube.com/watch?v=3hISVFGLEw0
项目代码:https://github.com/tech-nickk/TN24

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

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

驴友花雕

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

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

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

打赏作者

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

抵扣说明:

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

余额充值