四轮全向底盘工训纪实

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作为一单位长度,这样方便修改。如果要控制全向底盘写字的话,不一定要按照正常的笔画进行运动。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值