232浏览
查看: 232|回复: 13

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

[复制链接]
本帖最后由 驴友花雕 于 2025-6-7 06:31 编辑



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

驴友花雕  中级技神
 楼主|

发表于 2025-5-27 15:25:29

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

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

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

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

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

回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 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

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

回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 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下载

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

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

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

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

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

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 2025-5-27 15:49:35

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

步骤2:3D打印零件

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

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

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

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

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

回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 2025-5-27 15:52:04

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

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

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

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

回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 2025-5-27 15:56:40

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

步骤4:组装底座


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

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

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

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

回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 2025-5-27 15:58:28

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

步骤5:安装顶板


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

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

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

回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 2025-5-27 16:04:04

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

步骤6:上传代码

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

  1. #include <math.h>
  2. // ---- PID Coefficients ----
  3. const float P_GAIN = 2.0;
  4. const float I_GAIN = 0.1;
  5. const float D_GAIN = 30.0;
  6. // ---- Smoothing Factors ----
  7. const float EMA_ALPHA = 0.9;    // Exponential moving average
  8. const float IR_ALPHA  = 0.5;    // IR signal low-pass filter
  9. // ---- Mechanical Constants ----
  10. const float DEG2RAD  = M_PI / 180.0;
  11. const float RAD2DEG  = 180.0 / M_PI;
  12. const float R1       = 50.0;                           // Servo arm length [mm]
  13. const float R2       = 39.2;                           // Passive link length [mm]
  14. const float BASE_R   = 32.9 / sqrt(3.0);               // Base triangle radius [mm]
  15. const float PLAT_R   = 107.9 / sqrt(3.0);              // Platform triangle radius [mm]
  16. // ---- Pin Assignments ----
  17. // Control
  18. const int BUTTON_PIN     = A1;
  19. const int LED_PIN        = 8;
  20. // IR Sensor
  21. const int IR_LED_PIN     = 7;
  22. const int IR_RECEIVER_PIN= A0;
  23. // Digital Potentiometer (MCP42xx)
  24. const int DIGIPOT_CS     = 4;
  25. const int DIGIPOT_DIN    = 1;
  26. const int DIGIPOT_SCLK   = 0;
  27. // Servo pins
  28. const int SERVO_PIN_A    = 10;
  29. const int SERVO_PIN_B    = 9;
  30. const int SERVO_PIN_C    = 11;
  31. // Button press timings [ms]
  32. const unsigned long SHORT_PRESS_TIME  = 50;
  33. const unsigned long LONG_PRESS_TIME   = 1000;
  34. const unsigned long DOUBLE_PRESS_TIME = 350;
  35. // ---- Globals ----
  36. // IR measurements and center tracking
  37. int ambientLight[16] = {0};
  38. int irLight[16]      = {0};
  39. float irSignal[16]   = {0.0};
  40. float centerX        = 0.0;
  41. float centerY        = 0.0;
  42. float setpointX      = 0.0;
  43. float setpointY      = 0.0;
  44. // PID state
  45. float lastErrorX     = 0.0;
  46. float lastErrorY     = 0.0;
  47. float integralX      = 0.0;
  48. float integralY      = 0.0;
  49. // Ball tracking
  50. bool ballWasOnPlate  = false;
  51. unsigned long ballLostTime = 0;
  52. // Button state
  53. bool buttonPressed       = false;
  54. unsigned long pressStart = 0;
  55. unsigned long lastPress  = 0;
  56. bool singlePressFlag     = false;
  57. // Trajectory
  58. float trajectoryAngle = 0.0;
  59. // ---- Objects ----
  60. CD74HC4067 mux(5, 13, 6, 12);  // S0,S1 -> 13, S2->6, S3->12
  61. Servo servoA, servoB, servoC;
  62. // ---- Kalman Filter Definition ----
  63. struct KalmanFilter {
  64.   float x = 0, v = 0, p = 1;
  65.   const float q = 0.3, r = 1.0;
  66.   void update(float z, float dt) {
  67.     // Prediction
  68.     x += v * dt;
  69.     p += q;
  70.     // Correction
  71.     float k = p / (p + r);
  72.     v  += k * ((z - x) / dt);
  73.     x  += k * (z - x);
  74.     p *= (1 - k);
  75.   }
  76. };
  77. KalmanFilter kfX, kfY;
  78. // ---- Function Prototypes ----
  79. void     blinkLED(unsigned long interval);
  80. void     measureIR();
  81. void     setDigitalPot(byte value);
  82. bool     ballOnPlate();
  83. void     computeCenter(float rawX, float rawY);
  84. void     pidControl(float input, float setpoint, float &lastError, float &integral, float &output);
  85. void     movePlatform(float rollDeg, float pitchDeg, float height);
  86. void     moveServos(float a, float b, float c);
  87. void     checkButton();
  88. void     calculateWeightedCenter(const float ir[], float &x, float &y);
  89. void     sendSerialData();
  90. void     setTrajectory(float radius, float speed);
  91. // ---- Arduino Setup ----
  92. void setup() {
  93.   pinMode(BUTTON_PIN, INPUT);
  94.   pinMode(LED_PIN, OUTPUT);
  95.   pinMode(IR_LED_PIN, OUTPUT);
  96.   pinMode(DIGIPOT_CS, OUTPUT);
  97.   pinMode(DIGIPOT_DIN, OUTPUT);
  98.   pinMode(DIGIPOT_SCLK, OUTPUT);
  99.   digitalWrite(DIGIPOT_CS, HIGH);
  100.   servoA.attach(SERVO_PIN_A);
  101.   servoB.attach(SERVO_PIN_B);
  102.   servoC.attach(SERVO_PIN_C);
  103.   // Initialize platform to neutral
  104.   movePlatform(0, -20, 60);
  105.   delay(1000);
  106.   Serial.begin(115200);
  107. }
  108. // ---- Main Loop ----
  109. void loop() {
  110.   static unsigned long lastTime = 0;
  111.   unsigned long now = millis();
  112.   float dt = (now - lastTime) / 1000.0;
  113.   blinkLED(300);
  114.   setDigitalPot(255);
  115.   measureIR();
  116.   checkButton();
  117.   sendSerialData();
  118.   if (ballOnPlate()) {
  119.     ballWasOnPlate = true;
  120.     ballLostTime = now;
  121.     // Raw center calculation
  122.     float rawX, rawY;
  123.     calculateWeightedCenter(irSignal, rawX, rawY);
  124.     // Kalman & EMA filters
  125.     kfX.update(rawX, dt);
  126.     kfY.update(rawY, dt);
  127.     centerX = EMA_ALPHA * rawX + (1 - EMA_ALPHA) * kfX.x;
  128.     centerY = EMA_ALPHA * rawY + (1 - EMA_ALPHA) * kfY.x;
  129.     // PID
  130.     float outputX, outputY;
  131.     pidControl(centerX, setpointX, lastErrorX, integralX, outputX);
  132.     pidControl(centerY, setpointY, lastErrorY, integralY, outputY);
  133.     movePlatform(outputX, outputY, 60);
  134.   }
  135.   else {
  136.     if (ballWasOnPlate && now - ballLostTime < 1000) {
  137.       // hold last
  138.       movePlatform(0, 0, 60);
  139.     } else {
  140.       ballWasOnPlate = false;
  141.       integralX = integralY = 0;
  142.       lastErrorX = lastErrorY = 0;
  143.       setpointX = setpointY = 0;
  144.       movePlatform(0, -20, 60);
  145.     }
  146.   }
  147.   lastTime = now;
  148. }
  149. // ---- Utility Functions ----
  150. void blinkLED(unsigned long interval) {
  151.   static unsigned long lastToggle = 0;
  152.   if (millis() - lastToggle >= interval) {
  153.     digitalWrite(LED_PIN, !digitalRead(LED_PIN));
  154.     lastToggle = millis();
  155.   }
  156. }
  157. void setDigitalPot(byte val) {
  158.   digitalWrite(DIGIPOT_CS, LOW);
  159.   for (int i = 7; i >= 0; --i) {
  160.     digitalWrite(DIGIPOT_DIN, (val & (1 << i)) ? HIGH : LOW);
  161.     digitalWrite(DIGIPOT_SCLK, LOW);
  162.     delayMicroseconds(10);
  163.     digitalWrite(DIGIPOT_SCLK, HIGH);
  164.     delayMicroseconds(10);
  165.   }
  166.   digitalWrite(DIGIPOT_CS, HIGH);
  167. }
  168. void measureIR() {
  169.   // Ambient
  170.   digitalWrite(IR_LED_PIN, LOW);
  171.   delay(1);
  172.   for (int i = 0; i < 16; i++) {
  173.     mux.channel(i);
  174.     delayMicroseconds(250);
  175.     ambientLight[i] = analogRead(IR_RECEIVER_PIN);
  176.   }
  177.   // IR On
  178.   digitalWrite(IR_LED_PIN, HIGH);
  179.   delay(5);
  180.   for (int i = 0; i < 16; i++) {
  181.     mux.channel(i);
  182.     delayMicroseconds(250);
  183.     irLight[i] = analogRead(IR_RECEIVER_PIN);
  184.   }
  185.   // Compute signal
  186.   for (int i = 0; i < 16; i++) {
  187.     float delta = irLight[i] - ambientLight[i];
  188.     irSignal[i] = IR_ALPHA * delta + (1 - IR_ALPHA) * irSignal[i];
  189.   }
  190. }
  191. bool ballOnPlate() {
  192.   long sum = 0;
  193.   int maxVal = irSignal[0];
  194.   for (int i = 0; i < 16; i++) {
  195.     sum += irSignal[i];
  196.     maxVal = max(maxVal, int(irSignal[i]));
  197.   }
  198.   float avg = sum / 16.0;
  199.   return maxVal > 1.5 * avg;
  200. }
  201. void pidControl(float input, float target, float &lastErr, float &integ, float &out) {
  202.   float error = target - input;
  203.   integ += I_GAIN * error;
  204.   float deriv = D_GAIN * (error - lastErr);
  205.   out = P_GAIN * error + integ + deriv;
  206.   lastErr = error;
  207. }
  208. void movePlatform(float rollDeg, float pitchDeg, float height) {
  209.   float roll  = -rollDeg * DEG2RAD;
  210.   float pitch = -pitchDeg * DEG2RAD;
  211.   float baseAngle[3]    = {0, 120 * DEG2RAD, 240 * DEG2RAD};
  212.   float platX[3], platY[3], platZ[3], angles[3];
  213.   // Transform platform points
  214.   for (int i = 0; i < 3; i++) {
  215.     float a = baseAngle[i];
  216.     float px = PLAT_R * cos(a);
  217.     float py = PLAT_R * sin(a);
  218.     float pz = height;
  219.     // Pitch
  220.     float x1 = px * cos(pitch) + pz * sin(pitch);
  221.     float z1 = -px * sin(pitch) + pz * cos(pitch);
  222.     // Roll
  223.     float y1 = py * cos(roll) - z1 * sin(roll);
  224.     float z2 = py * sin(roll) + z1 * cos(roll);
  225.     platX[i] = x1; platY[i] = y1; platZ[i] = z2;
  226.   }
  227.   // Calculate servo angles
  228.   for (int i = 0; i < 3; i++) {
  229.     float a = baseAngle[i];
  230.     float bx = BASE_R * cos(a);
  231.     float by = BASE_R * sin(a);
  232.     float dx = platX[i] - bx;
  233.     float dy = platY[i] - by;
  234.     float dz = platZ[i];
  235.     float dxl = dx * cos(a) + dy * sin(a);
  236.     float dyl = dz;
  237.     float d = sqrt(dxl*dxl + dyl*dyl);
  238.     float theta = atan2(dyl, dxl) - acos(constrain((R1*R1 + d*d - R2*R2)/(2*R1*d), -1, 1));
  239.     angles[i] = theta * RAD2DEG;
  240.   }
  241.   moveServos(angles[0], angles[1], angles[2]);
  242. }
  243. void moveServos(float a, float b, float c) {
  244.   a = constrain(a, -10, 65);
  245.   b = constrain(b, -10, 65);
  246.   c = constrain(c, -10, 65);
  247.   servoA.write(100 - a);
  248.   servoB.write(100 - b);
  249.   servoC.write(100 - c);
  250. }
  251. void calculateWeightedCenter(const float arr[], float &x, float &y) {
  252.   // If insufficient contrast, return (0,0)
  253.   float minV = arr[0], maxV = arr[0];
  254.   for (int i = 1; i < 16; i++) {
  255.     minV = min(minV, arr[i]);
  256.     maxV = max(maxV, arr[i]);
  257.   }
  258.   if (maxV - minV < 150) { x = y = 0; return; }
  259.   const float coordsX[16] = {0,1,2,3, 0,1,2,3, 0,1,2,3, 0,1,2,3};
  260.   const float coordsY[16] = {0,0,0,0, 1,1,1,1, 2,2,2,2, 3,3,3,3};
  261.   float sumW=0, wx=0, wy=0;
  262.   for (int i = 0; i < 16; i++) {
  263.     float norm = pow((arr[i] - minV)/(maxV - minV), 4);
  264.     wx += coordsX[i] * norm;
  265.     wy += coordsY[i] * norm;
  266.     sumW += norm;
  267.   }
  268.   x = wx/sumW - 1.5;
  269.   y = wy/sumW - 1.5;
  270. }
  271. void checkButton() {
  272.   bool state = digitalRead(BUTTON_PIN);
  273.   unsigned long now = millis();
  274.   static bool lastState = LOW;
  275.   if (state && !lastState) {
  276.     pressStart = now;
  277.     buttonPressed = true;
  278.   }
  279.   if (buttonPressed && state && (now - pressStart > LONG_PRESS_TIME)) {
  280.     Serial.println("Long Press Detected");
  281.     buttonPressed = false;
  282.   }
  283.   if (buttonPressed && !state) {
  284.     unsigned long dur = now - pressStart;
  285.     if (dur >= SHORT_PRESS_TIME && dur < LONG_PRESS_TIME) {
  286.       if (now - lastPress < DOUBLE_PRESS_TIME) {
  287.         Serial.println("Double Press Detected");
  288.         singlePressFlag = false;
  289.       } else {
  290.         singlePressFlag = true;
  291.       }
  292.       lastPress = now;
  293.     }
  294.     buttonPressed = false;
  295.   }
  296.   if (singlePressFlag && now - lastPress > DOUBLE_PRESS_TIME) {
  297.     Serial.println("Single Press Detected");
  298.     singlePressFlag = false;
  299.   }
  300.   lastState = state;
  301. }
  302. void sendSerialData() {
  303.   for (int i = 0; i < 16; i++) {
  304.     Serial.print(irSignal[i]);
  305.     Serial.print(',');
  306.   }
  307.   Serial.print(centerX); Serial.print(',');
  308.   Serial.print(centerY); Serial.print(',');
  309.   Serial.print(setpointX); Serial.print(',');
  310.   Serial.println(setpointY);
  311. }
  312. void setTrajectory(float radius, float speed) {
  313.   unsigned long now = millis();
  314.   static unsigned long lastT = 0;
  315.   float dt = (now - lastT)/2000.0;
  316.   trajectoryAngle += speed * dt;
  317.   setpointX = radius * cos(trajectoryAngle);
  318.   setpointY = radius * sin(trajectoryAngle);
  319.   lastT = now;
  320. }
复制代码


回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 2025-5-27 16:05:14

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

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

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 2025-5-27 16:08:14

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

步骤7:工作原理

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


当你将球放在 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

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



回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 2025-5-27 16:54:08

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

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

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

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 2025-6-7 06:34:28

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

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

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

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

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2025 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail