|
【R111】12自由度六足
作者:机器谱
原地舞蹈 步态规划 蓝牙遥控 |
原地舞蹈
1. 功能描述
R111样机是一款拥有12个自由度的串联仿生六足机器人。本文示例所实现的功能为:R111样机12自由度六足机器人原地舞蹈。
2. 结构说明
R111样机(R111b)由6个2自由度连杆仿生腿结构组成,2自由度连杆仿生腿两两对称组合,分别构成机器昆虫的三对足。三对足之间由零件结合固定,从而构成机器昆虫的本体,呈现出非常规整的模块化结构。
该2自由度串联结构由舵机1和舵机2驱动,其中舵机1实现腿部前后摆动,舵机2实现腿部的上下抬伸。其中抬伸通过一个平行四连杆ABCD作为传动结构以增加腿部的行程和增强腿部运动的稳定性。
3. 电子硬件
在这个示例中,我们采用了以下硬件,请大家参考:
主控板 | |
扩展板 | SH-SR舵机扩展板 |
舵机 | 标准舵机 |
电池 | 7.4V锂电池 |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-02-16 https://www.robotway.com/ ------------------------------*/ #include <Tlc5940.h> #include <tlc_servos.h> int data_array[2] = {0,0}; //servo_pin: data_array[0], servo_value: data_array[1]; String data = ""; boolean dataComplete = false; void setup() { Serial.begin(9600); Tlc.init(0); tlc_initServos(); delay(1000); } void loop() {
while(Serial.available()) { int B_flag, P_flag, T_flag; data = Serial.readStringUntil('\n'); data.trim(); for(int i=0;i<data.length();i++) { //Serial.println(data[i]); switch(data[i]) { case '#': B_flag = i; break; case 'P': { String pin = ""; P_flag = i; for(int i=B_flag+1;i<P_flag;i++) { pin+=data[i]; } data_array[0] = pin.toInt(); } break; case 'T': { String angle = ""; T_flag = i; for(int i=P_flag+1;i<T_flag;i++) { angle += data[i]; } data_array[1] = map(angle.toInt(), 500, 2500, 0, 180); } break; default: break; } }
/* Serial.println(B_flag); Serial.println(P_flag); Serial.println(T_flag);
for(int i=0;i<2;i++) { Serial.println(data_array[i]); } */
dataComplete = true; }
if(dataComplete) { tlc_setServo(data_array[0], data_array[1]); Tlc.update(); dataComplete = false; }
} |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-02-16 https://www.robotway.com/ ------------------------------*/ #include <Arduino.h> #include <avr/pgmspace.h> #include "Config.h" #include <Tlc5940.h> #include <tlc_servos.h> int count_input = 0; boolean _b = false; /**************+++++++++++动作组数组,请将转换后的动作组数据粘贴到相应的动作组中+++++++***************************/ const PROGMEM int actionInit[] = { 1090, 1790, 1220, 980, 650, 1800, 1020, 1330, 520, 1155, 1260, 2020, 1090, 1790, 1220, 980, 650, 1800, 1020, 1330, 520, 1155, 1260, 2020, }; const PROGMEM int actionLeft[] = { }; const PROGMEM int actionRight[] = { }; const PROGMEM int actionBack[] = { }; const PROGMEM int actionDance[] = { }; const PROGMEM int actionDance[] = { 1660, 1230, 1220, 980, 1280, 1190, 1020, 1330, 1090, 1155, 1260, 1540, 1090,1790,1220,980,650,1800,1020,1330,520,1155,1260,2020, 1490,1590,1220,980,850,1400,1020,1330,1320,1155,1260,2020, 1490,1590,1220,980,850,1400,1020,1330,1320,1155,1700,1700, 1490,1590,1220,980,850,1400,1020,1330,1320,1155,1700,2020, 1090,1790,1220,980,650,1800,1020,1330,520,1155,1260,2020, 1490,1590,1220,980,850,1400,1020,1330,520,1155,1260,1220, 1490,1590,1220,980,850,1400,1020,1330,800,700,1260,1220, 1490,1590,1220,980,850,1400,1020,1330,520,700,1260,1220, 1090,1790,1220,980,650,1800,1020,1330,520,1155,1260,2020, 1670,1190,1220,980,1370,1100,1020,1330,1100,1155,1260,1430, 1090,1190,1220,980,1370,1800,620,1730,1100,1155,1260,1430, 1090,1190,1220,980,1370,1100,620,1730,1100,1155,1260,1430, 1670,1190,1220,980,1370,1800,620,1730,1100,1155,1260,1430, 1090,1190,1220,980,1370,1100,620,1730,1100,1155,1260,1430, 1090,1190,1220,980,1370,1800,700,1570,1100,1155,1260,1430, 1670,1190,1220,980,1370,1100,700,1570,1100,1155,1260,1430, 1670,1190,1220,980,1370,1100,700,1570,520,520,1930,2020, 1670,1190,1220,980,1370,1100,700,1570,800,520,1930,1700, 1670,1190,1220,980,1370,1100,700,1570,520,520,1930,2020, 1670,1190,1220,980,1370,1100,700,1570,800,520,1930,1700, 1670,1190,1220,980,1370,1100,700,1570,1100,1155,1260,1430, 1670,1190,1220,980,1370,1100,1020,1330,1100,1155,1260,1430, 1670,1190,1220,980,1370,1100,1020,1330,1100,1155,1260,1430, 1670,1190,1220,980,900,1590,1020,1330,720,1155,1260,1430, 1320,1540,1220,980,1370,1100,1020,1330,1100,1155,1260,1830, 1670,1190,1220,980,1370,1100,1020,1330,1100,1155,1260,1430, 1320,1410,1220,980,1370,1100,1020,1330,1100,1155,1260,1950, 1300,1670,1220,980,1370,1100,1020,1330,1100,1155,1260,1600, 1670,1190,1220,980,940,1540,1020,1330,920,1155,1260,1430, 1670,1190,1220,980,1235,1480,1020,1330,580,1155,1260,1430, 1670,1190,1220,980,940,1540,1020,1330,920,1155,1260,1430, 1670,1190,990,1210,940,1540,1140,1100,920,1400,1060,1430, 1300,1670,990,1210,1370,1100,1140,1100,1100,1400,1060,1600, 1320,1540,990,1210,1370,1100,1140,1100,1100,1400,1060,1830, 1670,1190,990,1210,1370,1100,1140,1100,1100,1400,1060,1430, 1670,1190,990,1210,1370,1100,1140,1100,1100,1400,1060,1430, 1670,1190,1280,920,1370,1100,920,1410,1100,1030,1410,1430, 1670,1190,990,1210,1370,1100,1140,1100,1100,1400,1060,1430, 1670,1190,1280,920,1370,1100,920,1410,1100,1030,1410,1430, 1670,1190,1220,980,1370,1100,1020,1330,1100,1155,1260,1430, }; /**************************+++++---------分割线--------++++++*******************************************************/ //动作组数组长度获取函数,增加动作组时需要按如下格式添加:actPwmNum[增加的动作组序号] = sizeof(增加的动作组名称) / sizeof(增加的动作组名称[0]); void act_length() { actPwmNum[0] = (sizeof(actionMove) / sizeof(actionMove[0]))/servo_num; actPwmNum[1] = (sizeof(actionLeft) / sizeof(actionLeft[0]))/servo_num; actPwmNum[2] = (sizeof(actionRight) / sizeof(actionRight[0]))/servo_num; actPwmNum[3] = (sizeof(actionBack) / sizeof(actionBack[0]))/servo_num; actPwmNum[4] = (sizeof(actionDance) / sizeof(actionDance[0]))/servo_num; actPwmNum[5] = (sizeof(actionInit) / sizeof(actionInit[0]))/servo_num; } //map映射函数 long map_servo(long x, long in_min, long in_max, long out_min, long out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } //PWM 转换为舵机角度的函数,增加动作组时需要修改 void vlaue2angle(int p, int act) { switch(act) { case 0: value_cur[p] = map_servo(pgm_read_word_near(actionMove + p + servo_num * count_input), 500, 2500, 0, 180); break; case 1: value_cur[p] = map_servo(pgm_read_word_near(actionLeft + p + servo_num * count_input), 500, 2500, 0, 180); break; case 2: value_cur[p] = map_servo(pgm_read_word_near(actionRight + p + servo_num * count_input), 500, 2500, 0, 180); break; case 3: value_cur[p] = map_servo(pgm_read_word_near(actionBack + p + servo_num * count_input), 500, 2500, 0, 180); break; case 4: value_cur[p] = map_servo(pgm_read_word_near(actionDance + p + servo_num * count_input), 500, 2500, 0, 180); break; case 5: value_cur[p] = map_servo(pgm_read_word_near(actionInit + p + servo_num * count_input), 500, 2500, 0, 180); break; default: break; } } //舵机初始化函数,动作组第一行为舵机初始化值 void servo_init(int act, int num) { if(!_b) { for(int i=0;i<servo_num;i++) { vlaue2angle(i, act); tlc_setServo(servo_pin[i], value_cur[i]); value_pre[i] = value_cur[i]; } Tlc.update(); //delay(1000); }
num == 1 ? _b = true : _b = false; } //舵机移动函数,参数: act:动作组宏定义名称 ;num:动作组执行的次数,num > 0 ; void servo_move(int act, int num) { float value_delta[servo_num] = {}; float in_value[servo_num] = {}; servo_init(act, num); for(int i=0;i< num * actPwmNum[act];i++) { count_input++;
if(count_input == actPwmNum[act]) { count_input = 0; continue; } for(int i=0;i<servo_num;i++) { vlaue2angle(i, act); in_value[i] = value_pre[i]; value_delta[i] = (value_cur[i] - value_pre[i]) / frequency; } for(int i=0;i<frequency;i++) { for(int k=0;k<servo_num;k++) { in_value[k] += value_delta[k]; value_pre[k] = in_value[k]; } for(int j=0;j<servo_num;j++) { tlc_setServo(servo_pin[j], in_value[j]); delayMicroseconds(servo_speed); } Tlc.update(); } delayMicroseconds(action_delay); } } /********************************************************-------分割线--------****************************************************/ //初始化函数 void setup() { Serial.begin(9600); //开启串口通信,波特率9600 Tlc.init(0); tlc_initServos(); act_length(); delay(action_delay); } //主函数,跳舞 void loop() { servo_move(action_dance, 2); delay(100); } |
电路连接说明:
因为12自由度六足机器人结构上装有12个舵机,所以将采用 SH-SR舵机扩展板 。
舵机接线顺序:1、3; 4、 5; 6、 8;11、13;15、 21;24、26。
4. 功能实现
上位机:Controller 1.0
下位机编程环境:Arduino 1.8.19
4.1初始位置的设定
① 将Controller下位机程序servo.ino直接下载到主控板。这段代码供Controller上位机与主控板通信,并允许调试舵机。代码如下:
下载完成后,保持主控板和电脑的USB连接,并打开主控板电源,以便利用上位机进行调试。
② 双击打开Controller 1.0b.exe:
③ 点击联机,选择对应的端口号,波特率设置为9600,如下图所示:
控制块内容说明如下:
④ 此时上位机软件已经与下位机建立通信连接,我们拖动舵机引脚号对应的滑块即可对舵机进行角度调节。下图为软件中各个按钮说明:
软件各按钮说明
转换后的数据
⑤ 设置面板说明:点击左上角设置-设置面板,即可打开(如下图)。点击对应序号的复选框即可控制软件主界面中相应控制块的显示和隐藏,以及全部显示和全部隐藏等;点击重置按钮,可将各个控制块恢复初始位置;点击确定可关闭设置界面。
⑥ 控制块任意位置拖动:如下图,可拖动至需要的形状。
变化后的形状 注:每次重启会恢复初始位置。
3.2 示例程序
调试好角度后,将角度参数复制到下面的12自由度六足机器人原地舞蹈的参考例程(robot_dance.ino)之中,并下载到主控板:
对于机器人来说,实现某一个自身结构的极限动作并不难,难度在于如何让动作变得有节奏-舞蹈。因为机器人无法达到人体的柔和度,所以为了让机器人跳舞更具有观赏性,建议大家选择节奏点清晰的音乐,尝试根据音乐节奏来设计舞蹈动作。
4. 扩展样机
改变足对之间的连接零件,就可以扩展出不同的样机,甚至还可以考虑在足对之间加入关节,使其具备更丰富的自由度。
5. 资料清单
序号 | 内容 |
1 | R111-样机3D文件 |
2 | R111-原地舞蹈-例程源代码 |
3 | Controller1.0b资料包 |
4 | 音乐素材.mp3 |
【整体打包】-【R111】12自由度六足-原地舞蹈-资料附件.zip | 69.92MB | 下载34次 | 下载 |
步态规划
1. 运动功能描述
R111样机是一款拥有12个自由度的串联仿生六足机器人。本文示例实现12自由度六足机器人的行走功能,包括前进、后退、左转、右转。
2. 结构说明
R111样机由六个2自由度串联结构组成,中间由舵机双折弯和螺柱结合固定,从而达到一个仿生机器人的结构。
该2自由度串联结构由舵机1和舵机2驱动,其中舵机1实现腿部前后摆动,舵机2实现腿部的上下抬伸。其中抬伸通过一个平行四连杆ABCD作为传动结构以增加腿部的行程和增强腿部运动的稳定性。
3. 步态原理
12自由度六足机器人的运动步态有两种:一种是采用三角步态进行运动,一种是采用波动步态运动。
① 三角步态
12自由度六足仿生机器人的三角步态是将机器人六足分成两组腿(身体一侧的前足、后足与另一侧的中足)分别进行摆动和支撑,即处于三角形上的三条腿的动作一样,均处于摆动相或均处于支撑相。如图:
② 波动步态
12自由度六足仿生机器人的波动步态是机器人每条腿两侧依次运动,即左(右)侧一条腿先迈步,再右(左)侧腿迈步,再左(右)侧下一条腿运动,如此循环完成波动步态的运动。如图:
提示:可以参考蜈蚣的步态。
本文讲一下三角步态的实现案例,波动步态大家可以自己研究。
4. 基于STM32主控板实现
4.1电子硬件
在这个示例中,我们采用了以下硬件,请大家参考:
主控板 | STM32主控板 |
扩展板 | STM32扩展板 |
舵机 | 标准舵机 |
电池 | 7.4V锂电池 |
主控板 | |
扩展板 | |
舵机 | 标准舵机 |
电池 | 7.4V锂电池 |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-02-16 https://www.robotway.com/ ------------------------------*/ #include <Arduino.h> #include <avr/pgmspace.h> #include "Config.h" #include <Tlc5940.h> #include <tlc_servos.h> int count_input = 0; boolean _b = false; /**************+++++++++++动作组数组,请将转换后的动作组数据粘贴到相应的动作组中+++++++***************************/ const PROGMEM int actionInit[] = { 1090, 1790, 1220, 980, 650, 1800, 1020, 1330, 520, 1155, 1260, 2020, 1090, 1790, 1220, 980, 650, 1800, 1020, 1330, 520, 1155, 1260, 2020, }; const PROGMEM int actionMove[] = { 1660, 1230, 1220, 980, 1280, 1190, 1020, 1330, 1090, 1155, 1260, 1540, 1660, 1790, 1320, 1080, 1280, 1800, 920, 1230, 1090, 1255, 1360, 2020, 1660, 1230, 1320, 1080, 1280, 1190, 920, 1230, 1090, 1255, 1360, 1540, 1090, 1300, 1120, 880, 650, 900, 1120, 1430, 520, 1055, 1160, 1300, 1660, 1300, 1120, 880, 1280, 900, 1120, 1430, 1090, 1055, 1160, 1300, }; const PROGMEM int actionLeft[] = { }; const PROGMEM int actionRight[] = { }; const PROGMEM int actionBack[] = { }; const PROGMEM int actionDance[] = { }; /**************************+++++---------分割线--------++++++*******************************************************/ //动作组数组长度获取函数,增加动作组时需要按如下格式添加:actPwmNum[增加的动作组序号] = sizeof(增加的动作组名称) / sizeof(增加的动作组名称[0]); void act_length() { actPwmNum[0] = (sizeof(actionMove) / sizeof(actionMove[0]))/servo_num; actPwmNum[1] = (sizeof(actionLeft) / sizeof(actionLeft[0]))/servo_num; actPwmNum[2] = (sizeof(actionRight) / sizeof(actionRight[0]))/servo_num; actPwmNum[3] = (sizeof(actionBack) / sizeof(actionBack[0]))/servo_num; actPwmNum[4] = (sizeof(actionDance) / sizeof(actionDance[0]))/servo_num; actPwmNum[5] = (sizeof(actionInit) / sizeof(actionInit[0]))/servo_num; } //map映射函数 long map_servo(long x, long in_min, long in_max, long out_min, long out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } //PWM 转换为舵机角度的函数,增加动作组时需要修改 void vlaue2angle(int p, int act) { switch(act) { case 0: value_cur[p] = map_servo(pgm_read_word_near(actionMove + p + servo_num * count_input), 500, 2500, 0, 180); break; case 1: value_cur[p] = map_servo(pgm_read_word_near(actionLeft + p + servo_num * count_input), 500, 2500, 0, 180); break; case 2: value_cur[p] = map_servo(pgm_read_word_near(actionRight + p + servo_num * count_input), 500, 2500, 0, 180); break; case 3: value_cur[p] = map_servo(pgm_read_word_near(actionBack + p + servo_num * count_input), 500, 2500, 0, 180); break; case 4: value_cur[p] = map_servo(pgm_read_word_near(actionDance + p + servo_num * count_input), 500, 2500, 0, 180); break; case 5: value_cur[p] = map_servo(pgm_read_word_near(actionInit + p + servo_num * count_input), 500, 2500, 0, 180); break; default: break; } } //舵机初始化函数,动作组第一行为舵机初始化值 void servo_init(int act, int num) { if(!_b) { for(int i=0;i<servo_num;i++) { vlaue2angle(i, act); tlc_setServo(servo_pin[i], value_cur[i]); value_pre[i] = value_cur[i]; } Tlc.update(); //delay(1000); } num == 1 ? _b = true : _b = false; } //舵机移动函数,参数: act:动作组宏定义名称 ;num:动作组执行的次数,num > 0 ; void servo_move(int act, int num) { float value_delta[servo_num] = {}; float in_value[servo_num] = {}; servo_init(act, num);
for(int i=0;i< num * actPwmNum[act];i++) { count_input++; if(count_input == actPwmNum[act]) { count_input = 0; continue; } for(int i=0;i<servo_num;i++) { vlaue2angle(i, act); in_value[i] = value_pre[i]; value_delta[i] = (value_cur[i] - value_pre[i]) / frequency; } for(int i=0;i<frequency;i++) { for(int k=0;k<servo_num;k++) { in_value[k] += value_delta[k]; value_pre[k] = in_value[k]; } for(int j=0;j<servo_num;j++) { tlc_setServo(servo_pin[j], in_value[j]); delayMicroseconds(servo_speed); } Tlc.update(); } delayMicroseconds(action_delay); } } /********************************************************-------分割线--------****************************************************/ //初始化函数 void setup() { Serial.begin(9600); //开启串口通信,波特率9600 Tlc.init(0); tlc_initServos(); act_length(); delay(action_delay); } //主函数,walk void loop() { servo_move(action_move, 6); delay(100); } |
物料 | 引脚号 | 物料 | 引脚号 |
舵机0 | PE9 | 舵机6 | PD15 |
舵机1 | PE11 | 舵机7 | PD14 |
舵机2 | PE13 | 舵机8 | PD13 |
舵机3 | PE14 | 舵机9 | PB15 |
舵机4 | PC6 | 舵机10 | PB14 |
舵机5 | PC7 | 舵机11 | PB9 |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-02-16 https://www.robotway.com/ ------------------------------*/ #include "sys.h" #include "led.h" #include "stdio.h" #include "delay.h" #include "usart.h" #include "stdio.h" #include "core_cm4.h" #include "string.h" #include "stdlib.h" #include "pwm.h" #include "timer.h" #include "math.h" /* 7 1 | | 6----| |----0 | | 9 3 | | 8----| |----2 | | 11 5 | | 10----| |----4 | | */ float Ang[12] = {100, 85, 90, 90, 90, 80, 100, 90, 95, 90, 90, 95}; int Ang_Init[12] = {100, 85, 90, 90, 90, 80, 100, 90, 95, 90, 90, 95}; float data_f[12] = {0}; void split(char *src,const char *separator,char **dest,int *num);//字符串拆分函数 float sort(float data[12]);//排序函数(小->大) float find_min(float *de);//寻找最小值函数 void Set_Ang(float, float, float, float, float, float, float, float, float, float, float, float);//设置舵机旋转角度函数 void Steering_Gear_Init(void);//机械臂初始化位置函数 void Steering_Gear_Angle(u8 num, float ang);//单个舵机控制函数 void Steering_Gear_Ang_Pwm1(float data[12]);//机械臂移动函数 int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2 delay_init(168);//初始化延时,168为CPU运行频率 usart_init(115200); //串口初始化 LED_Init();//初始化LED灯 TIM1_PWM_Init(20000-1, 168-1); TIM4_PWM_Init(20000-1,84-1); TIM3_PWM_Init(20000-1,84-1); TIM11_PWM_Init(20000-1,168-1); TIM12_PWM_Init(20000-1,84-1); Steering_Gear_Init();//六足位置初始化 while(1) { Set_Ang(80,85,110,90,110,80,80,90,115,90,70,95); Steering_Gear_Ang_Pwm1(data_f); delay_ms(1000); while(1){ Set_Ang(90,100,105,75,110,95,90,105,105,75,80,110); Steering_Gear_Ang_Pwm1(data_f); Set_Ang(70,100,70,75,130,95,120,105,120,75,120,110); Steering_Gear_Ang_Pwm1(data_f); Set_Ang(70,70,70,105,130,65,120,75,120,105,120,80); Steering_Gear_Ang_Pwm1(data_f); Set_Ang(90,70,105,105,110,65,90,75,105,105,80,80); Steering_Gear_Ang_Pwm1(data_f); Set_Ang(120,70,120,105,60,65,80,75,70,105,70,80); Steering_Gear_Ang_Pwm1(data_f); Set_Ang(120,100,120,75,60,95,80,105,70,75,70,110); Steering_Gear_Ang_Pwm1(data_f); } } } void split(char *src,const char *separator,char **dest,int *num) { char *pNext; int count = 0; if (src == NULL || strlen(src) == 0)//字符串为空或遇到结束标志 return; if (separator == NULL || strlen(separator) == 0)//分隔符为空 return; pNext = strtok(src,separator);//字符串分割 while(pNext != NULL) { *dest++ = pNext; ++count; pNext = strtok(NULL,separator); } *num = count; } //对数组内素有成员按从小到大的顺序从新排列,并返回最小值 float sort(float data1[12]) { float array[12] = {0}; int i = 0; int j = 0; int temp; for(int i=0; i < 12; i++){ array[i] = data1[i]; } for ( i = 0; i < 12; i++){ for ( j = 0; j < 11-i; j++){ if(array[j]>array[j+1]){ temp=array[j]; array[j]=array[j+1]; array[j+1]=temp; } } } return find_min(array); } //寻找数组中的最小值 float find_min(float *de) { if((int)*de != 0) return *de; find_min(++de); } //给数组赋值 void Set_Ang(float ang1, float ang2, float ang3, float ang4, float ang5, float ang6, float ang7, float ang8, float ang9, float ang10, float ang11, float ang12) { data_f[0] = ang1; data_f[1] = ang2; data_f[2] = ang3; data_f[3] = ang4; data_f[4] = ang5; data_f[5] = ang6; data_f[6] = ang7; data_f[7] = ang8; data_f[8] = ang9; data_f[9] = ang10; data_f[10] = ang11; data_f[11] = ang12; } //将两个位置之间需要舵机移动的度数转换为多步,然后分步完成 void Steering_Gear_Ang_Pwm1(float data[12]) { float den = 0; float dif[12] = {0}; floatang[12] = {0}; //计算两个位置之间的差值 for(int i=0; i < 12; i++){ dif[i] = fabs(data[i] - Ang[i]); } //得到最小差值 den = sort(dif); //计算12个舵机每次移动的最小距离 if((int)den == 0){ for(int i=0; i < 12; i++) ang[i] = 0; }else{ for(int i=0; i < 12; i++){ ang[i] = (data[i] - Ang[i])/den; } for(int i=0; i<den; i++){ for(int j=0; j < 12; j++) Ang[j] += ang[j]; //对多个舵机进行位置限制,防止舵机堵转 for(int g=0; g < 12; g++){ if(Ang[g] < 0) Ang[g] = 0; else if(Ang[g] > 180) Ang[g] = 180; } //舵机控制 for(int k=0; k<12; k++){ Steering_Gear_Angle(k, Ang[k]); } } } } void Steering_Gear_Angle(u8 num, float ang) { u32 Ang = 0; Ang = 500 + ang*2000/180; switch(num){ case 0 : TIM_SetCompare1(TIM1, Ang);//修改比较值,修改占空比 break; case 1 : TIM_SetCompare2(TIM1, Ang);//修改比较值,修改占空比 break; case 2 : TIM_SetCompare3(TIM1, Ang);//修改比较值,修改占空比 break; case 3 : TIM_SetCompare4(TIM1, Ang);//修改比较值,修改占空比 break; case 4 : TIM_SetCompare1(TIM3, Ang);//修改比较值,修改占空比 break; case 5 : TIM_SetCompare2(TIM3, Ang);//修改比较值,修改占空比 break; case 6 : TIM_SetCompare4(TIM4, Ang);//修改比较值,修改占空比 break; case 7 : TIM_SetCompare3(TIM4, Ang);//修改比较值,修改占空比 break; case 8 : TIM_SetCompare2(TIM4, Ang);//修改比较值,修改占空比 break; case 9 : TIM_SetCompare2(TIM12, Ang);//修改比较值,修改占空比 break; case 10 : TIM_SetCompare1(TIM12, Ang);//修改比较值,修改占空比 break; case 11 : TIM_SetCompare1(TIM11, Ang);//修改比较值,修改占空比 break; default: break; } delay_ms(1); } //六足起始位置 void Steering_Gear_Init(void) { for(int i=0; i < 12; i++){ Steering_Gear_Angle(i,Ang_Init[i]); } delay_ms(1000); } |
按下图进行电路连接: 我们先对机构的舵机进行编号(见下图)
接下来与扩展板进行连接(见下图)
相应的引脚编号见下表,这些引脚号在编程中将会用到。
4.2 示例程序
编程环境:keil5
使用Controller获取姿态参数,并拷贝到下面的程序中。
三角步态前进的参考例程(USER\test.uvprojx),main.c的代码:
5. 基于Arduino主控板实现
5.1 电子硬件
在这个示例中,我们采用了以下硬件,请大家参考:
电路连接说明:
因为12自由度六足机器人结构上装有12个舵机,所以将采用 SH-SR舵机扩展板 。
舵机接线顺序:1、3; 4、 5; 6、 8;11、13;15、 21;24、26。
5.2 示例程序
使用Controller获取姿态参数,并拷贝到下面的12自由度六足机器人三角步态的参考例程(robot_walk.ino)之中,并下载到主控板:
注意:
上面虽然给大家提供了一个参考例程,但并不意味着直接将程序烧录成功后机器人即可正常运行,还需要大家对一些参数或者结构进行调整。调试提示:
(1)安装时将每个关节的舵机角度调成与程序一致;
(2)程序里面初始角度调整为安装时的角度,然后根据之前运动的角度差调整其它动作的角度;
(3)注意每条腿上舵机对应的端口号,运动需要与三角步态对应;
(4)可以通过调整延迟参数来控制机器人的运动快慢。
6. 资料清单
序号 | 内容 |
1 | R111-步态规划-STM32例程源代码 |
2 | R111-步态规划-Arduino例程源代码 |
【整体打包】-【R111】12自由度六足-步态规划-资料附件.zip | 15.35MB | 下载11次 | 下载 |
//主函数,三种执行动作的方式 void loop() { /*********************************************+++++++++++++ 1、串口控制+++++++++**************************************************/ // while(Serial.available()) // { // int data; // data = Serial.parseInt(); // // switch(data) // { // case 1: servo_move(action_move, 2); break; // case 2: servo_move(action_back, 2); break; // case 3: servo_move(action_left, 1); break; // case 4: servo_move(action_right, 1); break; // case 5: servo_move(action_dance, 1); break; // case 6: servo_move(action_init, 1); break; // case 7: // { // servo_move(action_move, 4); // delay(actChangeDelay); // servo_move(action_back, 4); // delay(actChangeDelay); // servo_move(action_left, 4); // delay(actChangeDelay); // servo_move(action_right, 4); // delay(actChangeDelay); // servo_move(action_dance, 2); // } // break; // default: break; // } // } /*****************************+++++++++++++ 2、指定每个动作执行的次数,以 while 循环结束+++++++++********************************/ servo_move(action_move, 6); delay(actChangeDelay); servo_move(action_back, 6); delay(actChangeDelay); servo_move(action_left, 4); delay(actChangeDelay); servo_move(action_right, 4); delay(actChangeDelay); servo_move(action_dance, 2); delay(1000); // while(1){ }; /****************************+++++++++++ 3、重复执行一个动作,次数设置为 1 ,取消 while 循环++++++++******************************/ // servo_move(action_dance, 1); // while(1){}; } |
蓝牙遥控
1. 功能描述
R111样机是一款拥有12个自由度的串联仿生六足机器人。本文为这个六足机器人增加蓝牙遥控功能,可以通过安卓手机APP对机器人的动作实现遥控。
2. 电子硬件
在这个示例中,我们采用了以下硬件,请大家参考:
主控板 | Basra主控板(兼容Arduino Uno) |
扩展板 | SH-SR舵机扩展板 |
舵机 | 标准舵机 |
电池 | 7.4V锂电池 |
通信 | 蓝牙串口模块 |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-02-16 https://www.robotway.com/ ------------------------------*/ #include <Arduino.h> #include <avr/pgmspace.h> #include "Config.h" #include <Tlc5940.h> #include <tlc_servos.h> int count_input = 0; boolean _b = false; /**************+++++++++++动作组数组,请将转换后的动作组数据粘贴到相应的动作组中+++++++***************************/ const PROGMEM int actionInit[] = { 1090, 1790, 1220, 980, 650, 1800, 1020, 1330, 520, 1155, 1260, 2020, 1090, 1790, 1220, 980, 650, 1800, 1020, 1330, 520, 1155, 1260, 2020, // 1660, 1230, 1220, 980, 1280, 1190, 1020, 1330, 1090, 1155, 1260, 1540, // 1090, 1790, 1220, 980, 650, 1800, 1020, 1330, 520, 1155, 1260, 2020 }; const PROGMEM int actionMove[] = { 1660, 1230, 1220, 980, 1280, 1190, 1020, 1330, 1090, 1155, 1260, 1540, 1660, 1790, 1320, 1080, 1280, 1800, 920, 1230, 1090, 1255, 1360, 2020, 1660, 1230, 1320, 1080, 1280, 1190, 920, 1230, 1090, 1255, 1360, 1540, 1090, 1300, 1120, 880, 650, 900, 1120, 1430, 520, 1055, 1160, 1300, 1660, 1300, 1120, 880, 1280, 900, 1120, 1430, 1090, 1055, 1160, 1300, }; const PROGMEM int actionLeft[] = { 1660, 1230, 1220, 980, 1280, 1190, 1020, 1330, 1090, 1155, 1260, 1540, 1080,1160,1420,780,650,1100,1220,1130,520,955,1460,1430, 1700,1160,1420,780,1440,1100,1220,1130,1100,955,1460,1430, 1700,1700,1020,1180,1440,1800,820,1530,1100,1355,1060,2020, 1700,1160,1020,1180,1440,1100,820,1530,1100,1355,1060,1430, }; const PROGMEM int actionRight[] = { 1660, 1230, 1220, 980, 1280, 1190, 1020, 1330, 1090, 1155, 1260, 1540, 1700,1700,1420,780,1440,1800,1220,1130,1100,955,1460,2020, 1700,1160,1420,780,1440,1100,1220,1130,1100,955,1460,1430, 1090,1160,1020,1180,650,1100,820,1530,520,1355,1060,1430, 1700,1160,1020,1180,1440,1100,820,1530,1100,1355,1060,1430, }; const PROGMEM int actionBack[] = { 1660, 1230, 1220, 980, 1280, 1190, 1020, 1330, 1090, 1155, 1260, 1540, 1700,1680,1020,780,1440,1800,1220,1530,1100,955,1060,2020, 1700,1060,1020,780,1440,1100,1220,1530,1100,955,1060,1430, 1100,1060,1220,1180,650,1100,1020,1130,520,1355,1260,1430, 1700,1060,1220,1180,1440,1100,1020,1130,1110,1355,1260,1430, }; const PROGMEM int actionDance[] = { 1660, 1230, 1220, 980, 1280, 1190, 1020, 1330, 1090, 1155, 1260, 1540, 1090,1790,1220,980,650,1800,1020,1330,520,1155,1260,2020, 1490,1590,1220,980,850,1400,1020,1330,1320,1155,1260,2020, 1490,1590,1220,980,850,1400,1020,1330,1320,1155,1700,1700, 1490,1590,1220,980,850,1400,1020,1330,1320,1155,1700,2020, 1090,1790,1220,980,650,1800,1020,1330,520,1155,1260,2020, 1490,1590,1220,980,850,1400,1020,1330,520,1155,1260,1220, 1490,1590,1220,980,850,1400,1020,1330,800,700,1260,1220, 1490,1590,1220,980,850,1400,1020,1330,520,700,1260,1220, 1090,1790,1220,980,650,1800,1020,1330,520,1155,1260,2020, 1670,1190,1220,980,1370,1100,1020,1330,1100,1155,1260,1430, 1090,1190,1220,980,1370,1800,620,1730,1100,1155,1260,1430, 1090,1190,1220,980,1370,1100,620,1730,1100,1155,1260,1430, 1670,1190,1220,980,1370,1800,620,1730,1100,1155,1260,1430, 1090,1190,1220,980,1370,1100,620,1730,1100,1155,1260,1430, 1090,1190,1220,980,1370,1800,700,1570,1100,1155,1260,1430, 1670,1190,1220,980,1370,1100,700,1570,1100,1155,1260,1430, 1670,1190,1220,980,1370,1100,700,1570,520,520,1930,2020, 1670,1190,1220,980,1370,1100,700,1570,800,520,1930,1700, 1670,1190,1220,980,1370,1100,700,1570,520,520,1930,2020, 1670,1190,1220,980,1370,1100,700,1570,800,520,1930,1700, 1670,1190,1220,980,1370,1100,700,1570,1100,1155,1260,1430, 1670,1190,1220,980,1370,1100,1020,1330,1100,1155,1260,1430, 1670,1190,1220,980,1370,1100,1020,1330,1100,1155,1260,1430, 1670,1190,1220,980,900,1590,1020,1330,720,1155,1260,1430, 1320,1540,1220,980,1370,1100,1020,1330,1100,1155,1260,1830, 1670,1190,1220,980,1370,1100,1020,1330,1100,1155,1260,1430, 1320,1410,1220,980,1370,1100,1020,1330,1100,1155,1260,1950, 1300,1670,1220,980,1370,1100,1020,1330,1100,1155,1260,1600, 1670,1190,1220,980,940,1540,1020,1330,920,1155,1260,1430, 1670,1190,1220,980,1235,1480,1020,1330,580,1155,1260,1430, 1670,1190,1220,980,940,1540,1020,1330,920,1155,1260,1430, 1670,1190,990,1210,940,1540,1140,1100,920,1400,1060,1430, 1300,1670,990,1210,1370,1100,1140,1100,1100,1400,1060,1600, 1320,1540,990,1210,1370,1100,1140,1100,1100,1400,1060,1830, 1670,1190,990,1210,1370,1100,1140,1100,1100,1400,1060,1430, 1670,1190,990,1210,1370,1100,1140,1100,1100,1400,1060,1430, 1670,1190,1280,920,1370,1100,920,1410,1100,1030,1410,1430, 1670,1190,990,1210,1370,1100,1140,1100,1100,1400,1060,1430, 1670,1190,1280,920,1370,1100,920,1410,1100,1030,1410,1430, 1670,1190,1220,980,1370,1100,1020,1330,1100,1155,1260,1430, }; /**************************+++++---------分割线--------++++++*******************************************************/ //动作组数组长度获取函数,增加动作组时需要按如下格式添加:actPwmNum[增加的动作组序号] = sizeof(增加的动作组名称) / sizeof(增加的动作组名称[0]); void act_length() { actPwmNum[0] = (sizeof(actionMove) / sizeof(actionMove[0]))/servo_num; actPwmNum[1] = (sizeof(actionLeft) / sizeof(actionLeft[0]))/servo_num; actPwmNum[2] = (sizeof(actionRight) / sizeof(actionRight[0]))/servo_num; actPwmNum[3] = (sizeof(actionBack) / sizeof(actionBack[0]))/servo_num; actPwmNum[4] = (sizeof(actionDance) / sizeof(actionDance[0]))/servo_num; actPwmNum[5] = (sizeof(actionInit) / sizeof(actionInit[0]))/servo_num; // for(int i=0;i<act_num;i++) // { // Serial.println(actPwmNum[i]); // } //---+++---在此处添加------+++------- } //map映射函数 long map_servo(long x, long in_min, long in_max, long out_min, long out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } //PWM 转换为舵机角度的函数,增加动作组时需要修改 void vlaue2angle(int p, int act) { switch(act) { case 0: value_cur[p] = map_servo(pgm_read_word_near(actionMove + p + servo_num * count_input), 500, 2500, 0, 180); break; case 1: value_cur[p] = map_servo(pgm_read_word_near(actionLeft + p + servo_num * count_input), 500, 2500, 0, 180); break; case 2: value_cur[p] = map_servo(pgm_read_word_near(actionRight + p + servo_num * count_input), 500, 2500, 0, 180); break; case 3: value_cur[p] = map_servo(pgm_read_word_near(actionBack + p + servo_num * count_input), 500, 2500, 0, 180); break; case 4: value_cur[p] = map_servo(pgm_read_word_near(actionDance + p + servo_num * count_input), 500, 2500, 0, 180); break; case 5: value_cur[p] = map_servo(pgm_read_word_near(actionInit + p + servo_num * count_input), 500, 2500, 0, 180); break; default: break; } } //舵机初始化函数,动作组第一行为舵机初始化值 void servo_init(int act, int num) { if(!_b) { for(int i=0;i<servo_num;i++) { vlaue2angle(i, act); tlc_setServo(servo_pin[i], value_cur[i]); value_pre[i] = value_cur[i]; } Tlc.update(); //delay(1000); }
num == 1 ? _b = true : _b = false; } //舵机移动函数,参数: act:动作组宏定义名称 ;num:动作组执行的次数,num > 0 ; void servo_move(int act, int num) { float value_delta[servo_num] = {}; float in_value[servo_num] = {};
servo_init(act, num);
for(int i=0;i< num * actPwmNum[act];i++) { //Serial.println(i); //Serial.println(count_input); count_input++;
if(count_input == actPwmNum[act]) { count_input = 0; continue; }
for(int i=0;i<servo_num;i++) { vlaue2angle(i, act); in_value[i] = value_pre[i]; value_delta[i] = (value_cur[i] - value_pre[i]) / frequency; /**************************************************串口查看输出**************************************************/ // Serial.print(value_pre[i]); // Serial.print(" "); // Serial.print(value_cur[i]); // Serial.print(" "); // Serial.print(value_delta[i]); // Serial.println(); /**************************************************串口查看输出**************************************************/ } // Serial.println();
for(int i=0;i<frequency;i++) { for(int k=0;k<servo_num;k++) { in_value[k] += value_delta[k]; value_pre[k] = in_value[k]; /**************************************************串口查看输出**************************************************/ // Serial.print(in_value[k]); // Serial.print(" "); /**************************************************串口查看输出**************************************************/ } // Serial.println();
for(int j=0;j<servo_num;j++) { tlc_setServo(servo_pin[j], in_value[j]); delayMicroseconds(servo_speed); } Tlc.update(); } delayMicroseconds(action_delay); /**************************************************串口查看输出**************************************************/ // for(int i=0;i<servo_num;i++) // { // Serial.println(value_pre[i]); // } /**************************************************串口查看输出**************************************************/ } } /********************************************************-------分割线--------****************************************************/ //初始化函数 void setup() { Serial.begin(9600); //开启串口通信,波特率9600 Tlc.init(0); tlc_initServos(); act_length();
delay(action_delay); } |
电路连接说明:
12自由度六足机器人结构上装有12个舵机,所以将采用 SH-SR舵机扩展板 。
舵机接线顺序:1、3; 4、 5; 6、 8;11、13;15、 21;24、26。
蓝牙串口模块可以通过杜邦线与扩展板进行连接,具体可以参考 如何使用探索者通信模块 一文中蓝牙串口模块部分。
3. 功能实现
下面的例程可以实现通过安卓手机APP按钮实现机器人前进、后退、左转、右转、跳舞等动作。安卓APP的设置也可以参考 如何使用探索者通信模块一文中蓝牙串口模块部分。
你也可以设计自己想要的动作,利用Controller 1.0b软件获取想要的动作参数,拷贝到下面的例程里。
将参考例程(R111_bluetooth.ino)下载到主控板:
4. 资料清单
序号 | 内容 |
1 | R111-蓝牙遥控-例程源代码 |
2 | Controller1.0b资料包 |
【整体打包】-【R111】12自由度六足-蓝牙遥控-资料附件.zip | 67.32MB | 下载5次 | 下载 |