驴友花雕 发表于 2025-5-27 15:08:09

【Arduino 动手做】BaBot:打造你自己的球平衡机器人

本帖最后由 驴友花雕 于 2025-6-7 06:31 编辑





驴友花雕 发表于 2025-5-27 15:25:29

【Arduino 动手做】BaBot:打造你自己的球平衡机器人

BaBot 是一款紧凑型开源机器人,它使用红外 (IR) 传感器和 ATmega32U4 微控制器实时平衡球体,将控制理论变为现实。无论您是学生、教育工作者还是业余爱好者,BaBot 都能为您提供一种探索 PID 控制、传感器集成和机器人技术的有趣方式。

BaBot 始于 2018 年的一个高中项目,最初使用电脑和高架摄像头来追踪球。经过多次迭代,包括使用树莓派在透明板下方安装摄像头的版本,我开发出了一种更高效的设计。当前版本采用红外传感器和 ATmega32U4 微控制器,打造出一款更紧凑、更经济、更易于使用的机器人。





驴友花雕 发表于 2025-5-27 15:40:44

【Arduino 动手做】BaBot:打造你自己的球平衡机器人

材料:
1x 亚克力板(用于平台)2mm PMMA
16个红外光电晶体管
16 个广角红外 LED
1x 定制 PCB(可通过 PCBWay 获取)
1个ATmega32U4微控制器
1x CD74HC4067 16通道模拟/数字多路复用器
1个5V 10A直流电源
3个MG90微型伺服器

工具:
激光切割机(用于亚克力部件)
3D打印机

软件:
Arduino IDE 2.0



驴友花雕 发表于 2025-5-27 15:45:13

【Arduino 动手做】BaBot:打造你自己的球平衡机器人

本帖最后由 驴友花雕 于 2025-5-27 15:46 编辑

步骤 1:订购 PCB


订购您的 PCB
为了使事情变得更容易和更可靠,我强烈建议订购预先组装的 PCB,特别是因为它们包含许多 SMD 组件。

您可以直接从PCBWay订购组装好的 PCB 。

或者,如果您想要最流畅的体验,您可以从ba-bot.com购买完整的 BaBot 套件(包含所有组件) 。这样,您就省去了采购单个零件的麻烦,可以全身心地投入到构建和享受机器人的乐趣中。

附件
下载 {{ file.name }}原理图_iNWgbALrz5.pdf下载










驴友花雕 发表于 2025-5-27 15:49:35

【Arduino 动手做】BaBot:打造你自己的球平衡机器人

步骤2:3D打印零件

BaBot 的所有结构部件均可 3D 打印。请务必使用精度高的打印机,尤其是与伺服器直接连接的部件。所有部件均可在Thingiverse 上找到。

附件
下载 {{ file.name }}所有.stl下载3D视图







驴友花雕 发表于 2025-5-27 15:52:04

【Arduino 动手做】BaBot:打造你自己的球平衡机器人

步骤 3:组装机械臂和伺服装置

组装机械臂和伺服器
3D 打印部件准备就绪后,首先组装三个臂并将它们连接到 MG90S 伺服器上。



驴友花雕 发表于 2025-5-27 15:56:40

【Arduino 动手做】BaBot:打造你自己的球平衡机器人

步骤4:组装底座


接下来,将伺服臂安装到底座上。该结构作为 BaBot 的基础,支撑电子设备并控制平衡机制。







驴友花雕 发表于 2025-5-27 15:58:28

【Arduino 动手做】BaBot:打造你自己的球平衡机器人

步骤5:安装顶板


现在将顶部丙烯酸板连接到第二个 PCB。





驴友花雕 发表于 2025-5-27 16:04:04

【Arduino 动手做】BaBot:打造你自己的球平衡机器人

步骤6:上传代码

上传代码
使用 Arduino IDE 将固件上传到 PCB 上的 ATmega32U4 微控制器。代码是开源的,可在GitHub页面上获取。代码已添加注释,以帮助您理解其工作原理。项目最终完成后,我们将在 ba-bot.com 上发布更详细的说明。

#include <math.h>

// ---- PID Coefficients ----
const float P_GAIN = 2.0;
const float I_GAIN = 0.1;
const float D_GAIN = 30.0;

// ---- Smoothing Factors ----
const float EMA_ALPHA = 0.9;    // Exponential moving average
const float IR_ALPHA= 0.5;    // IR signal low-pass filter

// ---- Mechanical Constants ----
const float DEG2RAD= M_PI / 180.0;
const float RAD2DEG= 180.0 / M_PI;
const float R1       = 50.0;                           // Servo arm length
const float R2       = 39.2;                           // Passive link length
const float BASE_R   = 32.9 / sqrt(3.0);               // Base triangle radius
const float PLAT_R   = 107.9 / sqrt(3.0);            // Platform triangle radius

// ---- Pin Assignments ----
// Control
const int BUTTON_PIN   = A1;
const int LED_PIN      = 8;

// IR Sensor
const int IR_LED_PIN   = 7;
const int IR_RECEIVER_PIN= A0;

// Digital Potentiometer (MCP42xx)
const int DIGIPOT_CS   = 4;
const int DIGIPOT_DIN    = 1;
const int DIGIPOT_SCLK   = 0;

// Servo pins
const int SERVO_PIN_A    = 10;
const int SERVO_PIN_B    = 9;
const int SERVO_PIN_C    = 11;

// Button press timings
const unsigned long SHORT_PRESS_TIME= 50;
const unsigned long LONG_PRESS_TIME   = 1000;
const unsigned long DOUBLE_PRESS_TIME = 350;

// ---- Globals ----
// IR measurements and center tracking
int ambientLight = {0};
int irLight      = {0};
float irSignal   = {0.0};

float centerX      = 0.0;
float centerY      = 0.0;
float setpointX      = 0.0;
float setpointY      = 0.0;

// PID state
float lastErrorX   = 0.0;
float lastErrorY   = 0.0;
float integralX      = 0.0;
float integralY      = 0.0;

// Ball tracking
bool ballWasOnPlate= false;
unsigned long ballLostTime = 0;

// Button state
bool buttonPressed       = false;
unsigned long pressStart = 0;
unsigned long lastPress= 0;
bool singlePressFlag   = false;

// Trajectory
float trajectoryAngle = 0.0;

// ---- Objects ----
CD74HC4067 mux(5, 13, 6, 12);// S0,S1 -> 13, S2->6, S3->12
Servo servoA, servoB, servoC;

// ---- Kalman Filter Definition ----
struct KalmanFilter {
float x = 0, v = 0, p = 1;
const float q = 0.3, r = 1.0;
void update(float z, float dt) {
    // Prediction
    x += v * dt;
    p += q;
    // Correction
    float k = p / (p + r);
    v+= k * ((z - x) / dt);
    x+= k * (z - x);
    p *= (1 - k);
}
};
KalmanFilter kfX, kfY;

// ---- Function Prototypes ----
void   blinkLED(unsigned long interval);
void   measureIR();
void   setDigitalPot(byte value);
bool   ballOnPlate();
void   computeCenter(float rawX, float rawY);
void   pidControl(float input, float setpoint, float &lastError, float &integral, float &output);
void   movePlatform(float rollDeg, float pitchDeg, float height);
void   moveServos(float a, float b, float c);
void   checkButton();
void   calculateWeightedCenter(const float ir[], float &x, float &y);
void   sendSerialData();
void   setTrajectory(float radius, float speed);

// ---- Arduino Setup ----
void setup() {
pinMode(BUTTON_PIN, INPUT);
pinMode(LED_PIN, OUTPUT);
pinMode(IR_LED_PIN, OUTPUT);
pinMode(DIGIPOT_CS, OUTPUT);
pinMode(DIGIPOT_DIN, OUTPUT);
pinMode(DIGIPOT_SCLK, OUTPUT);
digitalWrite(DIGIPOT_CS, HIGH);

servoA.attach(SERVO_PIN_A);
servoB.attach(SERVO_PIN_B);
servoC.attach(SERVO_PIN_C);

// Initialize platform to neutral
movePlatform(0, -20, 60);
delay(1000);

Serial.begin(115200);
}

// ---- Main Loop ----
void loop() {
static unsigned long lastTime = 0;
unsigned long now = millis();
float dt = (now - lastTime) / 1000.0;

blinkLED(300);
setDigitalPot(255);
measureIR();
checkButton();
sendSerialData();

if (ballOnPlate()) {
    ballWasOnPlate = true;
    ballLostTime = now;

    // Raw center calculation
    float rawX, rawY;
    calculateWeightedCenter(irSignal, rawX, rawY);

    // Kalman & EMA filters
    kfX.update(rawX, dt);
    kfY.update(rawY, dt);
    centerX = EMA_ALPHA * rawX + (1 - EMA_ALPHA) * kfX.x;
    centerY = EMA_ALPHA * rawY + (1 - EMA_ALPHA) * kfY.x;

    // PID
    float outputX, outputY;
    pidControl(centerX, setpointX, lastErrorX, integralX, outputX);
    pidControl(centerY, setpointY, lastErrorY, integralY, outputY);

    movePlatform(outputX, outputY, 60);
}
else {
    if (ballWasOnPlate && now - ballLostTime < 1000) {
      // hold last
      movePlatform(0, 0, 60);
    } else {
      ballWasOnPlate = false;
      integralX = integralY = 0;
      lastErrorX = lastErrorY = 0;
      setpointX = setpointY = 0;
      movePlatform(0, -20, 60);
    }
}

lastTime = now;
}

// ---- Utility Functions ----

void blinkLED(unsigned long interval) {
static unsigned long lastToggle = 0;
if (millis() - lastToggle >= interval) {
    digitalWrite(LED_PIN, !digitalRead(LED_PIN));
    lastToggle = millis();
}
}

void setDigitalPot(byte val) {
digitalWrite(DIGIPOT_CS, LOW);
for (int i = 7; i >= 0; --i) {
    digitalWrite(DIGIPOT_DIN, (val & (1 << i)) ? HIGH : LOW);
    digitalWrite(DIGIPOT_SCLK, LOW);
    delayMicroseconds(10);
    digitalWrite(DIGIPOT_SCLK, HIGH);
    delayMicroseconds(10);
}
digitalWrite(DIGIPOT_CS, HIGH);
}

void measureIR() {
// Ambient
digitalWrite(IR_LED_PIN, LOW);
delay(1);
for (int i = 0; i < 16; i++) {
    mux.channel(i);
    delayMicroseconds(250);
    ambientLight = analogRead(IR_RECEIVER_PIN);
}
// IR On
digitalWrite(IR_LED_PIN, HIGH);
delay(5);
for (int i = 0; i < 16; i++) {
    mux.channel(i);
    delayMicroseconds(250);
    irLight = analogRead(IR_RECEIVER_PIN);
}
// Compute signal
for (int i = 0; i < 16; i++) {
    float delta = irLight - ambientLight;
    irSignal = IR_ALPHA * delta + (1 - IR_ALPHA) * irSignal;
}
}

bool ballOnPlate() {
long sum = 0;
int maxVal = irSignal;
for (int i = 0; i < 16; i++) {
    sum += irSignal;
    maxVal = max(maxVal, int(irSignal));
}
float avg = sum / 16.0;
return maxVal > 1.5 * avg;
}

void pidControl(float input, float target, float &lastErr, float &integ, float &out) {
float error = target - input;
integ += I_GAIN * error;
float deriv = D_GAIN * (error - lastErr);
out = P_GAIN * error + integ + deriv;
lastErr = error;
}

void movePlatform(float rollDeg, float pitchDeg, float height) {
float roll= -rollDeg * DEG2RAD;
float pitch = -pitchDeg * DEG2RAD;
float baseAngle    = {0, 120 * DEG2RAD, 240 * DEG2RAD};
float platX, platY, platZ, angles;

// Transform platform points
for (int i = 0; i < 3; i++) {
    float a = baseAngle;
    float px = PLAT_R * cos(a);
    float py = PLAT_R * sin(a);
    float pz = height;

    // Pitch
    float x1 = px * cos(pitch) + pz * sin(pitch);
    float z1 = -px * sin(pitch) + pz * cos(pitch);
    // Roll
    float y1 = py * cos(roll) - z1 * sin(roll);
    float z2 = py * sin(roll) + z1 * cos(roll);

    platX = x1; platY = y1; platZ = z2;
}
// Calculate servo angles
for (int i = 0; i < 3; i++) {
    float a = baseAngle;
    float bx = BASE_R * cos(a);
    float by = BASE_R * sin(a);
    float dx = platX - bx;
    float dy = platY - by;
    float dz = platZ;
    float dxl = dx * cos(a) + dy * sin(a);
    float dyl = dz;
    float d = sqrt(dxl*dxl + dyl*dyl);
    float theta = atan2(dyl, dxl) - acos(constrain((R1*R1 + d*d - R2*R2)/(2*R1*d), -1, 1));
    angles = theta * RAD2DEG;
}
moveServos(angles, angles, angles);
}

void moveServos(float a, float b, float c) {
a = constrain(a, -10, 65);
b = constrain(b, -10, 65);
c = constrain(c, -10, 65);
servoA.write(100 - a);
servoB.write(100 - b);
servoC.write(100 - c);
}

void calculateWeightedCenter(const float arr[], float &x, float &y) {
// If insufficient contrast, return (0,0)
float minV = arr, maxV = arr;
for (int i = 1; i < 16; i++) {
    minV = min(minV, arr);
    maxV = max(maxV, arr);
}
if (maxV - minV < 150) { x = y = 0; return; }

const float coordsX = {0,1,2,3, 0,1,2,3, 0,1,2,3, 0,1,2,3};
const float coordsY = {0,0,0,0, 1,1,1,1, 2,2,2,2, 3,3,3,3};
float sumW=0, wx=0, wy=0;
for (int i = 0; i < 16; i++) {
    float norm = pow((arr - minV)/(maxV - minV), 4);
    wx += coordsX * norm;
    wy += coordsY * norm;
    sumW += norm;
}
x = wx/sumW - 1.5;
y = wy/sumW - 1.5;
}

void checkButton() {
bool state = digitalRead(BUTTON_PIN);
unsigned long now = millis();
static bool lastState = LOW;

if (state && !lastState) {
    pressStart = now;
    buttonPressed = true;
}
if (buttonPressed && state && (now - pressStart > LONG_PRESS_TIME)) {
    Serial.println("Long Press Detected");
    buttonPressed = false;
}
if (buttonPressed && !state) {
    unsigned long dur = now - pressStart;
    if (dur >= SHORT_PRESS_TIME && dur < LONG_PRESS_TIME) {
      if (now - lastPress < DOUBLE_PRESS_TIME) {
      Serial.println("Double Press Detected");
      singlePressFlag = false;
      } else {
      singlePressFlag = true;
      }
      lastPress = now;
    }
    buttonPressed = false;
}
if (singlePressFlag && now - lastPress > DOUBLE_PRESS_TIME) {
    Serial.println("Single Press Detected");
    singlePressFlag = false;
}
lastState = state;
}

void sendSerialData() {
for (int i = 0; i < 16; i++) {
    Serial.print(irSignal);
    Serial.print(',');
}
Serial.print(centerX); Serial.print(',');
Serial.print(centerY); Serial.print(',');
Serial.print(setpointX); Serial.print(',');
Serial.println(setpointY);
}

void setTrajectory(float radius, float speed) {
unsigned long now = millis();
static unsigned long lastT = 0;
float dt = (now - lastT)/2000.0;
trajectoryAngle += speed * dt;
setpointX = radius * cos(trajectoryAngle);
setpointY = radius * sin(trajectoryAngle);
lastT = now;
}

驴友花雕 发表于 2025-5-27 16:05:14

【Arduino 动手做】BaBot:打造你自己的球平衡机器人


驴友花雕 发表于 2025-5-27 16:08:14

【Arduino 动手做】BaBot:打造你自己的球平衡机器人

步骤7:工作原理




当你将球放在 BaBot 的透明板上时,会发生一系列事件:​

红外探测:板下方是一块二级印刷电路板,内嵌多个红外 (IR) LED 和红外光电晶体管。这些红外 LED 向上发射光线,光线经球体底部反射后被光电晶体管捕获。这种设置使 BaBot 能够实时准确地确定球体在板上的位置。


数据处理:辅助 PCB 收集的位置数据传输到主 PCB,主 PCB 内置一个 ATmega32U4 微控制器,类似于 Arduino Leonardo 中的微控制器。该微控制器使用 PID(比例-积分-微分)算法处理数据。PID 算法计算球的当前位置与目标位置(通常是板的中心)之间的差值。


计算板倾斜度:为了校正球的位置,BaBot 必须适当倾斜板。这需要求解逆运动学方程,以确定三个伺服电机达到所需倾斜度所需的精确角度。


驱动运动:计算出的角度被发送到伺服电机,每个电机连接到铰接臂,铰接臂末端是金属球。这些金属球充当关节,并通过磁力吸附在板的底面上,从而实现平稳灵敏的运动。


持续反馈回路:调整板面方向后,系统会重新测量球的位置,并重复此过程。该反馈回路每秒运行约30次,确保球在板上保持平衡。


传感器、控制算法和机械部件的无缝集成使BaBot能够实时保持球平衡,从而提供机器人和控制系统的引人入胜的演示。


驴友花雕 发表于 2025-5-27 16:43:31

【Arduino 动手做】BaBot:打造你自己的球平衡机器人

本帖最后由 驴友花雕 于 2025-6-7 06:32 编辑

步骤8:在哪里获取你的BaBot?

哪里可以买到你的 BaBot?
BaBot提供构建和理解实时控制系统的实践经验。其开源设计鼓励实验和学习,使其成为初学者和经验丰富的创客的理想项目。

对于那些有兴趣进一步探索 BaBot 或获取套件的人,请访问ba-bot.com了解更多信息。

无论您是寻求引人入胜的课堂项目的老师,还是渴望深入研究机器人技术的学生,或者仅仅是欣赏创新小工具的人,BaBot 都旨在吸引和教育您。​

无需任何经验,只需有好奇心和创造非凡事物的热情。​


项目链接:https://www.ba-bot.com/
https://www.instructables.com/Ba ... ll-Balancing-Robot/
项目作者:瑞士 约翰·林克
项目视频:https://www.bilibili.com/video/B ... a9be994cbb4a86cc987
原理图:https://content.instructables.co ... FZKJW2BMAL3FTM5.pdf
3D文件:https://www.thingiverse.com/thing:7021268
项目代码:https://github.com/JohanLink/BABOT
项目动图:
https://cdn.thingiverse.com/asse ... .com-optimize_2.gif
https://cdn.thingiverse.com/asse ... if.com-optimize.gif



https://www.bilibili.com/video/BV1V3j9zYEcj/?share_source=copy_web&vd_source=371a292a55e5ca9be994cbb4a86cc987

驴友花雕 发表于 2025-5-27 16:54:08

【Arduino 动手做】BaBot:打造你自己的球平衡机器人




驴友花雕 发表于 7 天前

【Arduino 动手做】BaBot:打造你自己的球平衡机器人






页: [1]
查看完整版本: 【Arduino 动手做】BaBot:打造你自己的球平衡机器人