驴友花雕 发表于 5 天前

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

认识 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 并探索机器人世界。













驴友花雕 发表于 5 天前

【Arduino 动手做】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);

}


驴友花雕 发表于 5 天前

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

【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





页: [1]
查看完整版本: 【Arduino 动手做】TN24,您迷人且可定制的桌面伴侣机器人