《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