Otto DIY 在一小时内打造您自己的机器人!
组件和用品
![]() |
| × | 1 | |||
| × | 1 | ||||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
![]() |
| × | 4 | |||
![]() |
| × | 1 | |||
![]() |
| × | 6 | |||
| × | 1 | ||||
![]() |
| × | 1 | |||
![]() |
| × | 1 |
必要的工具和机器
![]() |
| |||
|
应用和在线服务
![]() |
| |||
![]() |
| |||
![]() |
|
关于这个项目
奥托是谁?
任何人都可以制作的交互式机器人!

奥托能做什么?
奥托会走路、跳舞、发声并避开障碍物。

为什么奥托很特别?
Otto 是完全开源的、兼容 Arduino、可 3D 打印的,并肩负着为孩子们创造一个包容性环境的社会影响使命。

Otto 的灵感来自另一个机器人可指导的 BoB BiPed,并使用另一个名为 Zowi 的开源双足机器人的代码进行编程。

CC-BY-SA
奥托的不同之处在于组装尺寸(11 厘米 x 7 厘米 x 12 厘米)、组件和表情的更清晰的集成。使用现成的 3D 打印部件、简单的电子连接(几乎不需要焊接)和基本的编码技能,您将能够在短短一小时内建立自己的可爱奥托朋友!甚至更少
Otto 是使用 Autodesk 123D Design 进行设计的,现在使用 ThinkerCAD 软件,您可以对其进行修改以进行自定义或进一步改进!



这一步的重点是如何构建Otto DIY简单版; 是的,还有更先进的双向机器人,比如 Otto DIY+ 或 Tito
第一步:首先收集所有零件和工具

收集此组装所需的所有现成硬件部件。
迷你面包板是屏蔽的替代品,但需要更多的布线和时间
那么你总共只需要3D打印6个零件:
11. 3D打印头。
12. 3D打印车身。
13. 3D 打印腿 x2.
14. 3D 打印脚 x2
可选:用于后期清洁3D零件的切割器(如果3D打印质量足够好不需要)和烙铁(如果你想要电池电源,否则仍然可以通过USB连接它以通电)
就这么简单! 下载所有 .stl 文件 , 如果您没有 3D 打印机,您可以随时前往当地的创客空间。
如果您没有或难以获得零件,您可以购买我们的完整套件 访问 www.ottodiy.com
3D 打印设置

Otto 非常适合 3D 打印,您下载的文件是面向属性和居中的,所以如果您遵循以下通用参数,就不会给您带来麻烦:
- 建议使用带有 PLA 材料的 FDM 3D 打印机。
- 根本不需要支架或木筏。
- 分辨率:0.15mm
- 填充密度 20%
用于切片和生成机器免费切片器软件的g代码 像 Cura 或我们使用的 FlashForge Finder 3D 打印机附带的 FlashPrint(如果您将打印外包,则无需担心)
打印后,您需要稍微清洁一下固定电机的腿和脚区域。
检查零件
如第 2 步所述,微型伺服电机附带图片中的 3 个螺钉,现在包括并重新排列零件编号以方便阅读。
记得磁化你的迷你螺丝刀。
下载或打印说明手册或直接观看此视频:
脚踏舵机总成
把微型舵机放在脚里面然后推进去,如果太硬可能需要用刀具清洁更多的区域。
检查舵机是否能够向每侧至少旋转 90 度非常重要。
检查机芯后,只需使用小螺丝固定即可。
另一只脚同样的过程。
将舵机固定到车身
取另外两个微型舵机,将它们放置在 3D 打印体的指定位置,并仅用尖头螺钉固定。
将腿固定在身体上
将腿连接到微型舵机的轮毂,重要的是像脚舵机一样,您必须检查腿是否能够相对于身体的每一侧旋转 90 度。
确认对齐后,使用小螺钉将它们固定到支腿内的孔中。
将脚固定在腿上
如图所示处理好线缆,应将线缆放入机身的槽内,穿过腿部的孔。
一旦它们处于正确位置,使用尖头螺钉从后面固定它们。
机头总成
把眼睛拉到极限,从超声波传感器开始很重要。
将 Arduino nano 放入屏蔽层后,您可以选择将电池座正极电缆焊接到板上的 Vin 并将负极焊接到任何 GND。
将两块板子一起面向USB连接器斜插入3D打印头的孔中,然后用最后2颗尖头螺丝固定。
第九步:电气连接

准备好杜邦线、微动开关和蜂鸣器。
然后按照图中的引脚编号,确保将它们放在正确的位置。
如果您没有或找不到 Arduino Nano 扩展板,请使用面包板并按照此电路图 TinkerCAD 电路
代码块
您可以随时尝试来自 Otto Wikifactory 的 Arduino 源代码。
在这里,您可以找到一些免费的教程,供初学者快速开始使用 Arduino 的每个组件:
- 伺服电机教程:https://arduinogetstarted.com/tutorials/arduino-servo-motor
- 超声波传感器教程:https://arduinogetstarted.com/tutorials/arduino-ultrasonic-sensor
- 触摸传感器教程:https://arduinogetstarted.com/tutorials/arduino-touch-sensor
-蜂鸣器教程:https://arduinogetstarted.com/tutorials/arduino-piezo-buzzer
访问 www.ottodiy.com
Otto DIY+版本有更多的可能性,使用蓝牙,更多的传感器和交互。
在 Wikifactory 中发布您的混音和修改
代码
- 避开障碍
避开障碍物Arduino
奥托会不停地走,直到看到范围内的障碍物,会感到惊讶,回来转身然后继续走//-------------------------------- ------------------------------------------//--- Zowi 基础固件 v2适应奥托//-- (c) BQ。在 GPL 许可下发布//-- 2015 年 12 月 4 日//-- 作者:Anita de Prado:[email protected]//-- Jose Alberca:[email protected]//-- Javier Isabel: [email protected]//-- Juan Gonzalez (obijuan):[email protected]//-- Irene Sanz :[email protected]//---------- -------------------------------------------------- -----//--- 试用 Otto 的所有功能,感谢 Zowi!//--------------------------- --------------------------------------#include#include <振荡器。 h>#include #include #include #include #include #include OttoSerialCommand SCmd; //SerialCommand对象//-- Otto Library#include Otto Otto; //这是奥托!//------------------------------------------- ---------------//-- 第一步:配置舵机所在的引脚/* --------------- |哦哦| |---------------|YR 3==> | | <==YL 2 --------------- || || || ||RR 5==> ----- ------ <==RL 4 |----- ------|*/ #define PIN_YL 2 //servo[0] #define PIN_YR 3 //servo[1] #define PIN_RL 4 //servo[2] #define PIN_RR 5 //servo[3]//---奥托按钮#define PIN_SecondButton 6#define PIN_ThirdButton 7////// ////////////////////////////////////////////////// ////////////-- 全局变量 ---------------------------------- ---------///////////////////////////////////////// //////////////////////////const char programID[]="Otto_todo"; //每个程序都会有一个IDconst char name_fac='$'; //工厂名称const char name_fir='#'; //名字//---运动参数int T=1000; //运动初始时长int moveId=0; //移动次数int moveSize=15; //与一些动作的高度相关//-------------------------------------- ------------------//-- Otto 有 5 种模式://-- * MODE =0:Otto 正在等待 //-- * MODE =1:跳舞模式! //-- * MODE =2:障碍物检测器模式 //-- * MODE =3:噪声检测器模式 //-- * MODE =4:OttoPAD 或任何遥操作模式(侦听 SerialPort)。 //------------------------------------------------ ---------易失性 int MODE=0; // Otto 在主要状态机中的状态。 volatile bool buttonPushed=false; //当按钮被按下时要记住的变量volatile bool buttonAPushed=false; //当A按钮被按下时要记住的变量volatile bool buttonBPushed=false; //当按下 B 按钮时要记住的变量 unsigned long previousMillis=0;int randomDance=0;int randomSteps=0;bool barrierDetected =false;////////////////// ////////////////////////////////////////////////// - 设置 - - - - - - - - - - - - - - - - - - - - - - - - -------/////////////////////////////////////////// ///////////////////////void setup(){ //串口通信初始化Serial.begin(115200); pinMode(PIN_SecondButton,INPUT); pinMode(PIN_ThirdButton,INPUT); //设置舵机引脚 Otto.init(PIN_YL,PIN_YR,PIN_RL,PIN_RR,true); //取消注释以手动设置伺服微调并保存在 EEPROM //Otto.setTrims(TRIM_YL, TRIM_YR, TRIM_RL, TRIM_RR); //Otto.saveTrimsOnEEPROM(); //当您最终设置修剪时,仅针对一次上传取消注释。 //设置随机种子randomSeed(analogRead(A6)); //中断 enableInterrupt(PIN_SecondButton, secondButtonPushed, RISING);启用中断(PIN_ThirdButton,thirdButtonPushed,RISING); //为 SerialCommand 命令设置回调 SCmd.addCommand("S", receiveStop); // sendAck &sendFinalAck SCmd.addCommand("L", receiveLED); // sendAck &sendFinalAck SCmd.addCommand("T", recieveBuzzer); // sendAck &sendFinalAck SCmd.addCommand("M", receiveMovement); // sendAck &sendFinalAck SCmd.addCommand("H", receiveGesture); // sendAck &sendFinalAck SCmd.addCommand("K", receiveSing); // sendAck &sendFinalAck SCmd.addCommand("C", receiveTrims); // sendAck &sendFinalAck SCmd.addCommand("G", receiveServo); // sendAck &sendFinalAck SCmd.addCommand("D", requestDistance); SCmd.addCommand("N", requestNoise); SCmd.addCommand("B", requestBattery); SCmd.addCommand("I", requestProgramId); SCmd.addDefaultHandler(receiveStop); //奥托醒来! Otto.sing(S_connection);奥托.home();延迟(50); // 动画 Uuuuuh - 最初的小惊喜 //----- for(int i=0; i<2; i++){ for (int i=0;i<8;i++){ if(buttonPushed) {break;} Otto.putAnimationMouth(littleUuh,i);延迟(150); } } //----- //为快乐的奥托微笑 :) if(!buttonPushed){ Otto.putMouth(smile);奥托.sing(S_happy);延迟(200); } //如果Otto的名字是'#'表示Otto还没有受洗 //在这种情况下,Otto做了一个更长的问候 //5 =包含名字字符的EEPROM地址 if (EEPROM.read(5)==name_fir){ if(!buttonPushed){ Otto.jump(1,700);延迟(200); } if(!buttonPushed){ Otto.shakeLeg(1,T,1); } if(!buttonPushed){ Otto.putMouth(smallSurprise);奥托.swing(2,800,20);奥托.home(); } } if(!buttonPushed){ Otto.putMouth(happyOpen); } previousMillis =millis();}//////////////////////////////////////// ///////////////////////////-- 主循环------------------- --------------------------//////////////////////// /////////////////////////////////////////////void loop() { //-- 模式 2 - 障碍物检测器模式 if(obstacleDetected){ Otto.sing(S_surprise); Otto.playGesture(OttoFretful);奥托.sing(S_fart3);奥托.walk(2,1300,-1);奥托.turn(2,1000,-1);延迟(50);障碍物检测器(); } else{ 奥托.walk(1,1000,1);障碍物检测器(); } } //////////////////////////////////////////////// ///////////////////// - 职能 - - - - - - - - - - - - - ------------------------////////////////////////// /////////////////////////////////////////////-- 函数执行时第二个按钮被pushvoid secondButtonPushed(){ buttonAPushed=true; if(!buttonPushed){ buttonPushed=true; Otto.putMouth(smallSurprise); } }//-- 按下第三个按钮时执行的函数void thirdButtonPushed(){ buttonBPushed=true; if(!buttonPushed){ buttonPushed=true; Otto.putMouth(smallSurprise); }}//-- 读取距离传感器和实现障碍物检测的函数,变量空障碍物检测器(){ int distance =Otto.getDistance();如果(距离<15){障碍检测=真; }else{ 障碍检测 =假; }}//-- 接收停止命令的函数。void receiveStop(){ sendAck();奥托.home(); sendFinalAck();}//-- 接收 LED 命令的函数void receiveLED(){ //sendAck 并在必要时停止 sendAck();奥托.home(); //接收LED蓝牙命令示例 //L 000000001000010100100011000000000 //L 00111111111111111111111111111111 (todos los LED encendidos; unsigned long intendidos)字符 *arg;字符 *endstr; arg=SCmd.next(); //Serial.println(arg); if (arg !=NULL) { 矩阵=strtoul(arg,&endstr,2); // 将字符字符串转换为无符号长整数 Otto.putMouth(matrix,false); }else{ Otto.putMouth(xMouth);延迟(2000);奥托.clearMouth(); } sendFinalAck();}//-- 接收蜂鸣器命令的函数void recieveBuzzer(){ //sendAck 并在必要时停止 sendAck();奥托.home();布尔错误=假;内部频率; int 持续时间;字符 *arg; arg =SCmd.next(); if (arg !=NULL) { frec=atoi(arg); } // 将字符字符串转换为整数 else {error=true;} arg =SCmd.next(); if (arg !=NULL) { 持续时间=atoi(arg); } // 将字符字符串转换为整数 else {error=true;} if(error==true){ Otto.putMouth(xMouth);延迟(2000);奥托.clearMouth(); }else{ Otto._tone(frec, duration, 1); } sendFinalAck();}//-- 接收 TRims 命令的函数void receiveTrims(){ //sendAck 并在必要时停止 sendAck();奥托.home(); int trim_YL,trim_YR,trim_RL,trim_RR; //伺服蓝牙命令定义 //C trim_YL trim_YR trim_RL trim_RR //receiveTrims蓝牙命令示例 //C 20 0 -8 3 bool error =false;字符 *arg; arg=SCmd.next(); if (arg !=NULL) { trim_YL=atoi(arg); } // 将字符字符串转换为整数 else {error=true;} arg =SCmd.next(); if (arg !=NULL) { trim_YR=atoi(arg); } // 将字符字符串转换为整数 else {error=true;} arg =SCmd.next(); if (arg !=NULL) { trim_RL=atoi(arg); } // 将字符字符串转换为整数 else {error=true;} arg =SCmd.next(); if (arg !=NULL) { trim_RR=atoi(arg); } // 将字符字符串转换为整数 else {error=true;} if(error==true){ Otto.putMouth(xMouth);延迟(2000);奥托.clearMouth(); }else{ //保存在EEPROM中 Otto.setTrims(trim_YL, trim_YR, trim_RL, trim_RR); Otto.saveTrimsOnEEPROM(); //当您最终设置修剪时,仅针对一次上传取消注释。 } sendFinalAck();}//-- 接收伺服命令的函数void receiveServo(){ sendAck();移动 ID =30; //Servo蓝牙指令定义 //Gservo_YLservo_YRservo_RLservo_RR //接收Servo蓝牙指令示例 //G 90 85 96 78 bool error =false;字符 *arg; int 伺服_YL,servo_YR,servo_RL,servo_RR; arg=SCmd.next(); if (arg !=NULL) { 伺服_YL=atoi(arg); } // 将字符字符串转换为整数 else {error=true;} arg =SCmd.next(); if (arg !=NULL) { 伺服_YR=atoi(arg); } // 将字符字符串转换为整数 else {error=true;} arg =SCmd.next(); if (arg !=NULL) { 伺服 RL=atoi(arg); } // 将字符字符串转换为整数 else {error=true;} arg =SCmd.next(); if (arg !=NULL) { 伺服 RR=atoi(arg); } // 将字符字符串转换为整数 else {error=true;} if(error==true){ Otto.putMouth(xMouth);延迟(2000);奥托.clearMouth(); }else{ //更新伺服:intservoPos[4]={servo_YL,servo_YR,servo_RL,servo_RR}; Otto._moveServos(200,servoPos); //移动200ms } sendFinalAck();}//-- 接收移动命令的函数void receiveMovement(){ sendAck(); if (Otto.getRestState()==true){ Otto.setRestState(false); } //运动蓝牙命令定义 //M MoveID T MoveSize char *arg; arg =SCmd.next(); if (arg !=NULL) {moveId=atoi(arg);} else{ Otto.putMouth(xMouth);延迟(2000);奥托.clearMouth();移动 ID=0; //停止 } arg =SCmd.next(); if (arg !=NULL) {T=atoi(arg);} else{ T=1000; } arg =SCmd.next(); if (arg !=NULL) {moveSize=atoi(arg);} else{ moveSize =15; }}//-- 根据收到的移动命令执行正确移动的函数。 void move(int moveId){ bool manualMode =false; switch (moveId) { case 0:Otto.home();休息; case 1://M 1 1000 Otto.walk(1,T,1);休息; case 2://M 2 1000 Otto.walk(1,T,-1);休息; case 3://M 3 1000 Otto.turn(1,T,1);休息; case 4://M 4 1000 Otto.turn(1,T,-1);休息; case 5://M 5 1000 30 Otto.updown(1,T,moveSize);休息; case 6://M 6 1000 30 Otto.moonwalker(1,T,moveSize,1);休息; case 7://M 7 1000 30 Otto.moonwalker(1,T,moveSize,-1);休息; case 8://M 8 1000 30 Otto.swing(1,T,moveSize);休息; case 9://M 9 1000 30 Otto.crusaito(1,T,moveSize,1);休息; case 10://M 10 1000 30 Otto.crusaito(1,T,moveSize,-1);休息; case 11://M 11 1000 Otto.jump(1,T);休息; case 12://M 12 1000 30 Otto.flapping(1,T,moveSize,1);休息; case 13://M 13 1000 30 Otto.flapping(1,T,moveSize,-1);休息; case 14://M 14 1000 20 Otto.tiptoeSwing(1,T,moveSize);休息; case 15://M 15 500 Otto.bend(1,T,1);休息; case 16://M 16 500 Otto.bend(1,T,-1);休息; case 17://M 17 500 Otto.shakeLeg(1,T,1);休息; case 18://M 18 500 Otto.shakeLeg(1,T,-1);休息; case 19://M 19 500 20 Otto.jitter(1,T,moveSize);休息; case 20://M 20 500 15 Otto.ascendingTurn(1,T,moveSize);休息;默认值:manualMode =true;休息; } if(!manualMode){ sendFinalAck(); } }//-- 接收手势命令的函数void receiveGesture(){ //sendAck &stop if必要 sendAck();奥托.home(); //Gesture蓝牙命令定义 //H GestureID intgesture =0;字符 *arg; arg =SCmd.next(); if (arg !=NULL) {gesture=atoi(arg);} else { Otto.putMouth(xMouth);延迟(2000);奥托.clearMouth(); } switch (gesture) { case 1://H 1 Otto.playGesture(OttoHappy);休息; case 2://H 2 Otto.playGesture(OttoSuperHappy);休息; case 3://H 3 Otto.playGesture(OttoSad);休息; case 4://H 4 Otto.playGesture(OttoSleeping);休息; case 5://H 5 Otto.playGesture(OttoFart);休息; case 6://H 6 Otto.playGesture(OttoConfused);休息; case 7://H 7 Otto.playGesture(OttoLove);休息; case 8://H 8 Otto.playGesture(OttoAngry);休息; case 9://H 9 Otto.playGesture(OttoFretful);休息; case 10://H 10 Otto.playGesture(OttoMagic);休息; case 11://H 11 Otto.playGesture(OttoWave);休息; case 12://H 12 Otto.playGesture(OttoVictory);休息; case 13://H 13 Otto.playGesture(OttoFail);休息;默认值:中断; } sendFinalAck();}//-- 接收sing命令的函数void receiveSing(){ //sendAck &stop if必要 sendAck();奥托.home(); //Sing蓝牙命令定义 //K SingID int sing =0;字符 *arg; arg =SCmd.next(); if (arg !=NULL) {sing=atoi(arg);} else { Otto.putMouth(xMouth);延迟(2000);奥托.clearMouth(); } switch (sing) { case 1://K 1 Otto.sing(S_connection);休息; case 2://K 2 Otto.sing(S_disconnection);休息; case 3://K 3 Otto.sing(S_surprise);休息; case 4://K 4 Otto.sing(S_OhOoh);休息; case 5://K 5 Otto.sing(S_OhOoh2);休息; case 6://K 6 Otto.sing(S_cuddly);休息; case 7://K 7 Otto.sing(S_sleeping);休息; case 8://K 8 Otto.sing(S_happy);休息; case 9://K 9 Otto.sing(S_superHappy);休息; case 10://K 10 Otto.sing(S_happy_short);休息; case 11://K 11 Otto.sing(S_sad);休息; case 12://K 12 Otto.sing(S_confused);休息; case 13://K 13 Otto.sing(S_fart1);休息; case 14://K 14 Otto.sing(S_fart2);休息; case 15://K 15 Otto.sing(S_fart3);休息; case 16://K 16 Otto.sing(S_mode1);休息; case 17://K 17 Otto.sing(S_mode2);休息; case 18://K 18 Otto.sing(S_mode3);休息; case 19://K 19 Otto.sing(S_buttonPushed);休息;默认值:中断; } sendFinalAck();}//-- 发送超声波传感器测量值的函数(以“cm”为单位的距离)void requestDistance(){ Otto.home(); // 必要时停止 int distance =Otto.getDistance(); Serial.print(F("&&")); Serial.print(F("D"));串行打印(距离); Serial.println(F("%%")); Serial.flush();}//-- 发送噪声传感器measurevoid requestNoise(){ Otto.home(); //必要时停止 int麦克风=Otto.getNoise(); //analogRead(PIN_NoiseSensor); Serial.print(F("&&")); Serial.print(F("N")); Serial.print(麦克风); Serial.println(F("%%")); Serial.flush();}//-- 发送电池电压百分比的函数 requestBattery(){ Otto.home(); //如果需要就停止 //第一次读取电池往往是错误的读取,所以我们会丢弃这个值。 double batteryLevel =Otto.getBatteryLevel(); Serial.print(F("&&")); Serial.print(F("B")); Serial.print(batteryLevel); Serial.println(F("%%")); Serial.flush();}//-- 发送程序ID的函数void requestProgramId(){ Otto.home(); // 必要时停止 Serial.print(F("&&")); Serial.print(F("I")); Serial.print(programID); Serial.println(F("%%")); Serial.flush();}//-- 发送Ack命令的函数(A)void sendAck(){ delay(30); Serial.print(F("&&")); Serial.print(F("A")); Serial.println(F("%%")); Serial.flush();}//-- 发送最终确认命令的函数 (F)void sendFinalAck(){ delay(30); Serial.print(F("&&")); Serial.print(F("F")); Serial.println(F("%%")); Serial.flush();}//-- 带动画的函数//--------------------------------- ----------------------void OttoLowBatteryAlarm(){ double batteryLevel =Otto.getBatteryLevel(); if(batteryLevel<45){ while(!buttonPushed){ Otto.putMouth(thunder); Otto.bendTones (880, 2000, 1.04, 8, 3); //A5 =880 延迟(30); Otto.bendTones (2000, 880, 1.02, 8, 3); //A5 =880 Otto.clearMouth();延迟(500); } }}void OttoSleeping_withInterrupts(){ int bedPos_0[4]={100, 80, 60, 120}; if(!buttonPushed){ Otto._moveServos(700, bedPos_0); } for(int i=0; i<4;i++){ if(buttonPushed){break;} Otto.putAnimationMouth(dreamMouth,0); Otto.bendTones (100, 200, 1.04, 10, 10); if(buttonPushed){break;} Otto.putAnimationMouth(dreamMouth,1); Otto.bendTones (200, 300, 1.04, 10, 10); if(buttonPushed){break;} Otto.putAnimationMouth(dreamMouth,2); Otto.bendTones (300, 500, 1.04, 10, 10);延迟(500); if(buttonPushed){break;} Otto.putAnimationMouth(dreamMouth,1); Otto.bendTones (400, 250, 1.04, 10, 1); if(buttonPushed){break;} Otto.putAnimationMouth(dreamMouth,0); Otto.bendTones (250, 100, 1.04, 10, 1);延迟(500); } if(!buttonPushed){ Otto.putMouth(lineMouth); Otto.sing(S_cuddly);奥托.home(); if(!buttonPushed){Otto.putMouth(happyOpen);} }
Arduino 源码
先安装这些库https://github.com/OttoDIY/DIY定制零件和外壳
Thingiverse .STL 3D 可打印文件
在thingiverse.com 上构建Otto.CAD 文件时使用的3D 打印部件示意图
伺服、超声波传感器、蜂鸣器和电池连接,按照电缆进行。使用杜邦电缆。
制造工艺