|
【R310】四福来轮全向底盘(十字)
作者:机器谱
概述 画图 写字 |
概述
1. 运动功能说明
本文示例采用R310a样机,是一款十字分布型四福来轮全向底盘。它可以实现各个方向的平移运动以及转向运动。
全向福来轮底盘的一个特点是可以灵活的全向移动,四轮全向轮的全向移动需要四个轮的相互配合,运动方向和各个轮的转向关系如图所示(箭头方向表示轮或车的运动方向):
2. 电子硬件
本实验中采用了以下硬件:
主控板 | STM32主控板 |
扩展板 | STM32扩展板 |
电池 | 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-03-03 https://www.robotway.com/ ------------------------------*/ #include "sys.h" #include "led.h" #include "a4988.h" #include "pwm.h" #include "math.h" #define PI 3.14 void Put_Up(void);//落笔函数 void Put_Down(void);//抬笔函数 void draw_sexangle(void);//画正六边形函数 void drawLine(float x1, float y1);//直线插补函数 float Xmin = -60; //定义绘图范围 float Xmax = 60; float Ymin = -60; float Ymax = 60; float Xpos = 0; float Ypos = 0; float l = 60;//边长 const int stepsPerRevolution = 3200;//步进电机步数 int step_test = 8000; int step_test1 = 6000; int main(void) { delay_init(168);//初始化延时,168为CPU运行频率 TIM4_PWM_Init(20000-1,84-1);//舵机初始化 Stepper_Motor_Init(0);//步进电机初始化 while(1) { All_stepper_move( step_test, -step_test, step_test, -step_test);delay_ms(500);//前进 All_stepper_move(-step_test, step_test, -step_test, step_test);delay_ms(500);//后退 All_stepper_move(-step_test, -step_test, -step_test, -step_test);delay_ms(500);//左转 All_stepper_move( step_test, step_test, step_test, step_test);delay_ms(500);//右转 All_stepper_move(-step_test, -step_test, step_test, step_test);delay_ms(500);//左平移 All_stepper_move( step_test, step_test, -step_test, -step_test);delay_ms(500);//右平移 //move_test(); while(1){ } } } |
电路连接:
我们先对机构的关键部件进行编号(见下图)。
接下来把步进电机线与扩展板进行连接(见下图)。
3. 运动功能实现
编程环境:keil5
功能:实现四轮全向底盘的前进、后退、转向、平移。
请编写程序并实现功能,下面给大家提供一个参考例程(USER\test.uvprojx),下面是main.c的代码:
4. 资料清单
序号 | 内容 |
1 | R310-例程源代码 |
2 | R310-样机3D文件 |
【整体打包】-【R310】四轮全向底盘-概述-资料附件.zip | 14.04MB | 下载43次 | 下载 |
画图
1. 功能说明
本文示例将实现R310b样机四轮全向底盘绘制正六边形的功能。
2. 结构说明
全向底盘具备结构简单、运动灵活等特点。四轮全向底盘采用全向福来轮作为执行轮,四个轮成正方形分布,且每个轮在斜45°方向安装。全向福来轮由主轮和副轮组成,主轮和副轮成垂直分布。
驱动系统采用精度较高的42步进电机;执行末端为伺服电机。通过四个步进电机运动的相互配合,四个步进电机驱动将圆周转动转化为直线运动。
3. 电子硬件
本实验中采用了以下硬件:
主控板 | |
扩展板 | |
SH-ST步进电机扩展板 | |
电池 | 11.1v动力电池 |
其它 | 步进电机、标准舵机、笔架 |
/*------------------------------------------------------------------------------------ 版权说明: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-03-09 https://www.robotway.com/ ------------------------------*/ #include <Servo.h> /* * en:定义步进电机使能引脚 * servo_pin:定义舵机引脚 * stepper_count:定义步进电机数量 * stepperPulse_delay:定义步进电机脉冲生成间隔 * LINE_BUFFER_LENGTH:定义串口接收数据长度 */ #define en 8 #define servo_pin 11 #define stepper_count 4 #define stepperPulse_delay 850 #define LINE_BUFFER_LENGTH 512 /* * positive_x:定义正向 X 轴 * positive_y:定义正向 Y 轴 * negative_x:定义负向 X 轴 * negative_y:定义负向 Y 轴 */ #define positive_x 0 #define positive_y 1 #define negative_x 2 #define negative_y 3 /* * stepperDir_pin:定义步进电机方向引脚数组 * stepperStp_pin:定义步进电机步进引脚数组 * dir: x: 5, y: 6, z: 7, a: 13 * stp: x: 2, y: 3, z: 4, a: 12 */ int stepperDir_pin[4] = {5, 6, 7, 13}; int stepperStp_pin[4] = {2, 3, 4, 12}; Servo myServo; const int stepsPerRevolution = 3200; //定义步进电机每圈转动的步数,此处为16细分,每圈 3200 步 int penZup = 145; //定义舵机抬起角度 int penZdown = 150; //定义舵机放下角度 float LEAD = sqrt(2) * 58 * PI; //定义步进电机转动 1 圈,小车前进的距离,单位 mm float l = 60; //定义六边形边长 //定义六边形顶点坐标数组 float coords[7][2] = { -l*1/2, -sqrt(3)/2*l, l*1/2, -sqrt(3)/2*l, l, 0, l*1/2, sqrt(3)/2*l, -l*1/2, sqrt(3)/2*l, -l, 0 , -l*1/2, -sqrt(3)/2*l }; float Xmin = -60; //定义绘图范围 float Xmax = 60; float Ymin = -60; float Ymax = 60; float Xpos = 0; float Ypos = 0; void setup() { Serial.begin(9600); //开启串口通信,波特率为 9600 myServo.attach(servo_pin); myServo.write(penZup); for(int i=0;i<stepper_count;i++) { pinMode(stepperDir_pin[i], OUTPUT); pinMode(stepperStp_pin[i], OUTPUT); } pinMode(en, OUTPUT); digitalWrite(en, LOW); delay(1000); } void loop() { draw_sexangle(); while(1){}; } //六边形绘制函数 void draw_sexangle() { drawLine(coords[0][0], coords[0][1]); delay(200);
for(int i=0;i<sizeof(coords) / sizeof(coords[0]);i++) { penDown(); delay(200); drawLine(coords[i][0], coords[i][1]); penUp(); } } //直线插补函数,参数为点坐标值 void drawLine(float x1, float y1) { int dx, dy, n, k, i, f, stepInc;
if (x1 >= Xmax) { x1 = Xmax; } if (x1 <= Xmin) { x1 = Xmin; } if (y1 >= Ymax) { y1 = Ymax; } if (y1 <= Ymin) { y1 = Ymin; }
x1 = (int)(x1/LEAD*stepsPerRevolution); y1 = (int)(y1/LEAD*stepsPerRevolution); float x0 = Xpos; float y0 = Ypos;
dx = abs(x1-x0); dy = abs(y1-y0); n = abs(dx+dy); if(x1 >= x0) { k = y1 >= y0 ? 1:4; } else { k = y1 >= y0 ? 2:3; }
for(i=0,f=0;i<n;i+=1) { if(f>=0) { switch(k) { case 1: stepper_move(positive_x, 1); f = f - dy; //Serial.println("+x"); break; case 2: stepper_move(negative_x, 1); f = f - dy; //Serial.println("-x"); break; case 3: stepper_move(negative_x, 1); f = f - dy; //Serial.println("-x"); break; case 4: stepper_move(positive_x, 1); f = f - dy; //Serial.println("+x"); break; default:break; } } else { switch(k) { case 1: stepper_move(positive_y, 1); f = f + dx; //Serial.println("+y"); break; case 2: stepper_move(positive_y, 1); f = f + dx; //Serial.println("+y"); break; case 3: stepper_move(negative_y, 1); f = f + dx; //Serial.println("-y"); break; case 4: stepper_move(negative_y, 1); f = f +dx; //Serial.println("-y"); break; default:break; } } }
Xpos = x1; Ypos = y1; } //小车行进方向控制函数 void stepper_dir(int positiveDir_x, int positiveDir_y, int negativeDir_x, int negativeDir_y) { int dir_value[] = {positiveDir_x, positiveDir_y, negativeDir_x, negativeDir_y};
for(int i=0;i<stepper_count;i++) { //Serial.print(dir_value[i]); //Serial.print(","); digitalWrite(stepperDir_pin[i], dir_value[i]); } //Serial.println();
for(int j=0;j<stepper_count;j++) { digitalWrite(stepperStp_pin[j], HIGH); } delayMicroseconds(stepperPulse_delay); for(int j=0;j<stepper_count;j++) { digitalWrite(stepperStp_pin[j], LOW); } delayMicroseconds(stepperPulse_delay); } //步进电机转动函数,参数 dir_xy:步进电机转动方向,steps:步进电机转动步数 void stepper_move(int dir_xy, int steps) { for(int i=0;i<abs(steps);i++) { switch(dir_xy) { case 0: stepper_dir(1, 1, 0, 0); break; case 1: stepper_dir(1, 0, 1, 0); break; case 2: stepper_dir(0, 0, 1, 1); break; case 3: stepper_dir(0, 1, 0, 1); break; default:break; } } } //舵机抬笔函数 void penUp() { myServo.write(penZup); } //舵机落笔函数 void penDown() { myServo.write(penZdown); } |
电路连接:
舵机连接在Bigfish扩展板的D11针脚上;4个步进电机与SH-ST扩展板的连接位置见下图:
4. 功能实现
编程环境:Arduino 1.8.19
① 四轮全向底盘运动算法
全向福来轮底盘的一个特点是可以灵活的全向移动,四轮全向轮的全向移动需要四个轮的相互配合,运动方向和各个轮的转向关系如图所示(箭头方向表示轮或车的运动方向):
多边形绘制算法:
正六边形和其它多边形有一个相同的条件-每个顶角角度一致 ,并且所有的多边形外角=360/n(n为边数),这样的话两条相邻边的角度是一致的,所以在这里我们采用了一种算法,该算法的思路是:
先以多边形的一个顶点A创建直角坐标系,然后确定相邻一条边上另一个顶点B的坐标,利用插补法完成一条边的绘制,然后再以顶点B为原点创建一个直角坐标系,继续绘制下一条边,重复上面的流程,完成多边形绘制,通过这种方法,我们只需要知道多边形的边长和边数就可以完成任意正多边形的绘制。
多边形计算公式:
n——为循环中绘制的第几条边为0~(m-1)变量值
m——总边数
L——边长
备注:坐标系原点为上一笔最终点,坐标系方向不变。
② 示例程序
下面给大家提供一个绘制边长为10cm的正六边形参考例程(stepper_car_write_sexangle.ino),大家看懂之后可尝试修改参数,完成其它正多边形的绘制:
5. 资料清单
序号 | 内容 |
1 | 【R310】-画图-例程源代码 |
2 | 【R310】-画图-样机3D文件 |
【整体打包】-【R310】四轮全向底盘-画图-资料附件.zip | 499.91KB | 下载9次 | 下载 |
写字
1. 功能说明
本文示例将实现R310b样机四福来轮全向底盘绘制“探索者”空心字的功能。
2. 电子硬件
本实验中采用了以下硬件:
主控板 | |
扩展板 | |
SH-ST步进电机扩展板 | |
电池 | 11.1v动力电池 |
其它 | 步进电机、标准舵机、笔架 |
/*------------------------------------------------------------------------------------ 版权说明: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-03-09 https://www.robotway.com/ ------------------------------*/ #include <Servo.h> /* * en:定义步进电机使能引脚 * servo_pin:定义舵机引脚 * stepper_count:定义步进电机数量 * stepperPulse_delay:定义步进电机脉冲生成间隔 * LINE_BUFFER_LENGTH:定义串口接收数据长度 */ #define en 8 #define servo_pin 11 #define stepper_count 4 #define stepperPulse_delay 850 #define LINE_BUFFER_LENGTH 512 /* * positive_x:定义正向 X 轴 * positive_y:定义正向 Y 轴 * negative_x:定义负向 X 轴 * negative_y:定义负向 Y 轴 */ #define positive_x 0 #define positive_y 1 #define negative_x 2 #define negative_y 3 /* * stepperDir_pin:定义步进电机方向引脚数组 * stepperStp_pin:定义步进电机步进引脚数组 * dir: x: 5, y: 6, z: 7, a: 13 * stp: x: 2, y: 3, z: 4, a: 12 */ int stepperDir_pin[4] = {5, 6, 7, 13}; int stepperStp_pin[4] = {2, 3, 4, 12}; Servo myServo; const int stepsPerRevolution = 3200; //定义步进电机每圈转动的步数,此处为16细分,每圈 3200 步 int penZup = 145; //定义舵机抬起角度 int penZdown = 150; //定义舵机放下角度 float LEAD = sqrt(2) * 58 * PI; //定义步进电机转动 1 圈,小车前进的距离,单位 mm struct point { float x; float y; }; struct point actuatorPos; // Current position of plothead float Xmin = -60; //定义绘图范围,长120mm , 宽120mm float Xmax = 60; float Ymin = -60; float Ymax = 60; float Xpos = 0; float Ypos = 0; boolean verbose = true; void setup() { Serial.begin(9600); //开启串口通信,波特率为 9600 myServo.attach(servo_pin); myServo.write(penZup); for(int i=0;i<stepper_count;i++) { pinMode(stepperDir_pin[i], OUTPUT); pinMode(stepperStp_pin[i], OUTPUT); } pinMode(en, OUTPUT); digitalWrite(en, LOW); delay(1000); } void loop() { delay(200); char line[ LINE_BUFFER_LENGTH ]; char c; int lineIndex; bool lineIsComment, lineSemiColon; lineIndex = 0; lineSemiColon = false; lineIsComment = false; while (1) { // 接受来自Grbl的串口数据 while ( Serial.available()>0 ) { c = Serial.read(); if (( c == '\n') || (c == '\r') ) { // End of line reached if ( lineIndex > 0 ) { // Line is complete. Then execute! line[ lineIndex ] = '\0'; // Terminate string if (verbose) { //Serial.print( "Received : "); Serial.println( line ); } processIncomingLine( line, lineIndex ); lineIndex = 0; } else { // Empty or comment line. Skip block. } lineIsComment = false; lineSemiColon = false; Serial.println("ok"); } else { if ( (lineIsComment) || (lineSemiColon) ) { // Throw away all comment characters if ( c == ')' ) lineIsComment = false; // End of comment. Resume line. } else { if ( c <= ' ' ) { // Throw away whitepace and control characters } else if ( c == '/' ) { // Block delete not supported. Ignore character. } else if ( c == '(' ) { // Enable comments flag and ignore all characters until ')' or EOL. lineIsComment = true; } else if ( c == ';' ) { lineSemiColon = true; } else if ( lineIndex >= LINE_BUFFER_LENGTH-1 ) { Serial.println( "ERROR - lineBuffer overflow" ); lineIsComment = false; lineSemiColon = false; } else if ( c >= 'a' && c <= 'z' ) { // Upcase lowercase line[ lineIndex++ ] = c-'a'+'A'; } else { line[ lineIndex++ ] = c; } } } } }
} //串口数据处理函数 void processIncomingLine( char* line, int charNB ) { int currentIndex = 0; char buffer[ 64 ]; // Hope that 64 is enough for 1 parameter struct point newPos; newPos.x = 0.0; newPos.y = 0.0; // Needs to interpret // G1 for moving // G4 P300 (wait 150ms) // G1 X60 Y30 // G1 X30 Y50 // M300 S30 (pen down) // M300 S50 (pen up) // Discard anything with a ( // Discard any other command! while( currentIndex < charNB ) { switch ( line[ currentIndex++ ] ) { // Select command, if any case 'U': penUp(); break; case 'D': penDown(); break; case 'G': buffer[0] = line[ currentIndex++ ]; // /!\ Dirty - Only works with 2 digit commands // buffer[1] = line[ currentIndex++ ]; // buffer[2] = '\0'; buffer[1] = '\0'; switch ( atoi( buffer ) ){ // Select G command case 0: // G00 & G01 - Movement or fast movement. Same here case 1: // /!\ Dirty - Suppose that X is before Y char* indexX = strchr( line+currentIndex, 'X' ); // Get X/Y position in the string (if any) char* indexY = strchr( line+currentIndex, 'Y' ); if ( indexY <= 0 ) { newPos.x = atof( indexX + 1); newPos.y = actuatorPos.y; } else if ( indexX <= 0 ) { newPos.y = atof( indexY + 1); newPos.x = actuatorPos.x; } else { newPos.y = atof( indexY + 1); indexY = '\0'; newPos.x = atof( indexX + 1); } drawLine(newPos.x, newPos.y ); // Serial.println("ok"); actuatorPos.x = newPos.x; actuatorPos.y = newPos.y; break; } break; case 'M': buffer[0] = line[ currentIndex++ ]; // /!\ Dirty - Only works with 3 digit commands buffer[1] = line[ currentIndex++ ]; buffer[2] = line[ currentIndex++ ]; buffer[3] = '\0'; switch ( atoi( buffer ) ){ case 300: { char* indexS = strchr( line+currentIndex, 'S' ); float Spos = atof( indexS + 1); // Serial.println("ok"); if (Spos == 30) { penDown(); } if (Spos == 50) { penUp(); } break; } case 114: // M114 - Repport position Serial.print( "Absolute position : X = " ); Serial.print( actuatorPos.x ); Serial.print( " - Y = " ); Serial.println( actuatorPos.y ); break; default: Serial.print( "Command not recognized : M"); Serial.println( buffer ); } } }
} //直线插补函数,参数为点坐标值 void drawLine(float x1, float y1) { int dx, dy, n, k, i, f, stepInc;
if (x1 >= Xmax) { x1 = Xmax; } if (x1 <= Xmin) { x1 = Xmin; } if (y1 >= Ymax) { y1 = Ymax; } if (y1 <= Ymin) { y1 = Ymin; }
x1 = (int)(x1/LEAD*stepsPerRevolution); y1 = (int)(y1/LEAD*stepsPerRevolution); float x0 = Xpos; float y0 = Ypos; Serial.print("X = "); Serial.println(Xpos); Serial.print("Y = "); Serial.println(Ypos);
dx = abs(x1-x0); dy = abs(y1-y0); n = abs(dx+dy); if(x1 >= x0) { k = y1 >= y0 ? 1:4; } else { k = y1 >= y0 ? 2:3; }
for(i=0,f=0;i<n;i+=1) { if(f>=0) { switch(k) { case 1: stepper_move(positive_x, 1); f = f - dy; //Serial.println("+x"); break; case 2: stepper_move(negative_x, 1); f = f - dy; //Serial.println("-x"); break; case 3: stepper_move(negative_x, 1); f = f - dy; //Serial.println("-x"); break; case 4: stepper_move(positive_x, 1); f = f - dy; //Serial.println("+x"); break; default:break; } } else { switch(k) { case 1: stepper_move(positive_y, 1); f = f + dx; //Serial.println("+y"); break; case 2: stepper_move(positive_y, 1); f = f + dx; //Serial.println("+y"); break; case 3: stepper_move(negative_y, 1); f = f + dx; //Serial.println("-y"); break; case 4: stepper_move(negative_y, 1); f = f +dx; //Serial.println("-y"); break; default:break; } } } Xpos = x1; Ypos = y1; } //小车行进方向控制函数 void stepper_dir(int positiveDir_x, int positiveDir_y, int negativeDir_x, int negativeDir_y) { int dir_value[] = {positiveDir_x, positiveDir_y, negativeDir_x, negativeDir_y};
for(int i=0;i<stepper_count;i++) { //Serial.print(dir_value[i]); //Serial.print(","); digitalWrite(stepperDir_pin[i], dir_value[i]); } //Serial.println();
for(int j=0;j<stepper_count;j++) { digitalWrite(stepperStp_pin[j], HIGH); } delayMicroseconds(stepperPulse_delay); for(int j=0;j<stepper_count;j++) { digitalWrite(stepperStp_pin[j], LOW); } delayMicroseconds(stepperPulse_delay); } //步进电机转动函数,参数 dir_xy:步进电机转动方向,steps:步进电机转动步数 void stepper_move(int dir_xy, int steps) { for(int i=0;i<abs(steps);i++) { switch(dir_xy) { case 0: stepper_dir(1, 1, 0, 0); // X 正方向 break; case 1: stepper_dir(1, 0, 1, 0); // Y 正方向 break; case 2: stepper_dir(0, 0, 1, 1); // X 负方向 break; case 3: stepper_dir(0, 1, 0, 1); // Y 负方向 break; default:break; } } } //舵机抬笔函数 void penUp() { myServo.write(penZup); } //舵机落笔函数 void penDown() { myServo.write(penZdown); } |
电路连接:
舵机连接在Bigfish扩展板的D11针脚上;4个步进电机与SH-ST扩展板的连接位置见下图:
3. 功能实现
在这里我们采用了一种算法,该算法的思路是:先建立一个平面坐标系,将我们所需要画的图形放置在该坐标系中,这样就可以确定该图形每个顶点的坐标,两个相邻的顶点之间确定一条直线,直线上各点坐标通过插补计算得到,然后画笔依次沿着这些坐标进行移动,完成绘制。所以在这个过程中,我们需要知道如何建立一个图形的坐标系,以及什么是插补计算。插补计算方法可参考 【R311】双轴XY平台-绘制斜向多边形 。
本实验将基于四福来轮全向底盘利用processing软件处理gcode文件后,进行绘制文字“探索者”。gcode文件的生成可参考【R312】三轴XYZ平台-生成gcode文件 。
3.1示例程序
编程环境:Arduino 1.8.19
下面给大家提供一个写字-探索者的参考例程(stepper_car_write.ino),将参考例程下载到主控板中:
3.2 图形绘制
接下来我们将通过上位机的processing软件发送生成文字“探索者”的 gcode文件给四福来轮全向底盘进行图形绘制。 具体操作步骤可参考【R312】三轴XYZ平台-绘制空心字 。
4. 资料清单
序号 | 内容 |
1 | 【R310】-写字-例程源代码 |
2 | 软件资料包 |
【整体打包】-【R310】四福来轮全向底盘(十字)-写字-资料附件.zip | 206.63MB | 下载8次 | 下载 |