四轮全向底盘工训纪实
1 工程训练纪实前言
这是南京理工大学暑期工程训练(金工实习)机器人项目第二天的记录博客,记录了四轮全向底盘的全过程细节,在此先向这一个月中的各位老师表达感谢,尤其感谢张小兵教授的教导。先叠个甲,以后的学弟学妹请勿完全照抄本文本人方案,仅为我个人记录,吾技术浅薄,仅供参考。
2 组装过程
2.1 结构说明
四福来轮全向移动底盘采用四个全向福来轮作为执行轮,这四个轮成正方形分布,且每个轮在斜45°方向安装。全向福来轮由主轮和副轮组成,主轮和副轮成垂直分布。驱动系统采用精度较高的42步进电机,执行末端为伺服电机。通过四个步进电机运动的相互配合,四个步进电机驱动将圆周转动转化为直线运动,从而实现全向移动。
四福来轮全向移动底盘具有结构简单、运动灵活等特点,可以用于实现各种复杂的移动和定位任务。例如,它可以用于机器人、自动化设备、无人驾驶车辆等领域,实现精确的定位、移动和操控。
2.2 具体步骤
总体上难度不大,拼起来很快,直接上PPT。注意要先调试电机再安装。
安装电机时,接线头应朝向侧面,不得朝向下面。接线的时候要注意线材颜色,否则电机易烧毁,可以给助教看看。
其中笔架需要自己设计完善,可以通过螺丝去固定,也可以用透明胶带进行捆绑,无论用什么方法,笔要稳,不能抖。
3 调整电控
3.1 伺服电机初始化
在上课之前,老师会提前发一个压缩包,里面有各种各样的工具软件还有半成品代码,这里就要用到arduino和controller。
先将以下命名为servo_bigfish.ino的代码通过arduino烧录进开发板中。
/*
* Bigfish扩展板舵机口; 4, 7, 11, 3, 8, 12, 14, 15, 16, 17, 18, 19
* 使用软件调节舵机时请拖拽对应序号的控制块
*/
#include <Servo.h>
#define ANGLE_VALUE_MIN 0
#define ANGLE_VALUE_MAX 180
#define PWM_VALUE_MIN 500
#define PWM_VALUE_MAX 2500
#define SERVO_NUM 12
Servo myServo[SERVO_NUM];
int data_array[2] = {0,0}; //servo_pin: data_array[0], servo_value: data_array[1];
int servo_port[SERVO_NUM] = {4, 7, 11, 3, 8, 12, 14, 15, 16, 17, 18, 19};
int servo_value[SERVO_NUM] = {};
String data = "";
boolean dataComplete = false;
void setup() {
Serial.begin(9600);
}
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] = angle.toInt();
servo_value[pin2index(data_array[0])] = data_array[1];
}
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)
{
for(int i=0;i<SERVO_NUM;i++)
{
ServoGo(i, servo_value[i]);
/*********************************串口查看输出***********************************/
// Serial.print(servo_value[i]);
// Serial.print(" ");
}
// Serial.println();
/*********************************++++++++++++***********************************/
dataComplete = false;
}
}
void ServoStart(int which){
if(!myServo[which].attached() && (servo_value[which] != 0))myServo[which].attach(servo_port[which]);
else return;
pinMode(servo_port[which], OUTPUT);
}
void ServoStop(int which){
myServo[which].detach();
digitalWrite(servo_port[which],LOW);
}
void ServoGo(int which , int where){
ServoStart(which);
if(where >= ANGLE_VALUE_MIN && where <= ANGLE_VALUE_MAX)
{
myServo[which].write( where);
}
else if(where >= PWM_VALUE_MIN && where <= PWM_VALUE_MAX)
{
myServo[which].writeMicroseconds(where);
}
}
int pin2index(int _pin){
int index;
switch(_pin)
{
case 4: index = 0; break;
case 7: index = 1; break;
case 11: index = 2; break;
case 3: index = 3; break;
case 8: index = 4; break;
case 12: index = 5; break;
case 14: index = 6; break;
case 15: index = 7; break;
case 16: index = 8; break;
case 17: index = 9; break;
case 18: index = 10; break;
case 19: index = 11; break;
}
return index;
}
代码烧录后,进入controller选择端口号及波特率进行连接,在对应模块中拖动控制条,使电机转动,最终置于90°的位置,伺服电机初始化调整成功。
3.2 步进电机控制
步进电机控制这一段是整体最有趣的地方,看起来有些难,但仅需有一些C语言程序设计基础即可完成,无需理解全部的代码,只需要看懂位移如何控制的就行。
下面是一个现成的画六边形的代码,仅供参考。
#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 = 180; //定义舵机放下角度
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);
}
3.2.1 抬笔与落笔
角度需要自己用controller一次一次去尝试,而且由于安装不同,penZup可能不一定是抬笔,penZdown也可能不一定是落笔。
int penZup = 145; //定义舵机抬起角度
int penZdown = 180; //定义舵机放下角度
3.2.2 修改绘图范围
建议增大绘图范围,超范围就会在那里描边。
float Xmin = -60; //定义绘图范围
float Xmax = 60;
float Ymin = -60;
float Ymax = 60;
3.2.3 控制全向底盘移动
下面截取了画六边形的移动控制方法,以此为例。
float LEAD = sqrt(2) * 58 * PI; //定义步进电机转动 1 圈,小车前进的距离,单位 mm
float len = 60; //定义六边形边长
//定义六边形顶点坐标数组
float coords[7][2] = {
-len*1/2, -sqrt(3)/2*len,
len*1/2, -sqrt(3)/2*len,
len, 0,
len*1/2, sqrt(3)/2*len,
-len*1/2, sqrt(3)/2*len,
-len, 0 ,
-l*1/2, -sqrt(3)/2*len
};
在float coords二维数组中存储了每次移动的目标点位的坐标,在修改时可以将len作为一单位长度,这样方便修改。如果要控制全向底盘写字的话,不一定要按照正常的笔画进行运动。