四福来轮全向底盘实现写字功能

news2025/1/17 0:58:12

1. 功能说明

     本文示例将实现R310b样机四福来轮全向底盘绘制“探索者”空心字的功能。

2. 电子硬件

     本实验中采用了以下硬件:

主控板

Basra主控板(兼容Arduino Uno)

扩展板

Bigfish2.1扩展板

SH-ST步进电机扩展板
电池11.1v动力电池

其它

步进电机、标准舵机、笔架

电路连接:

      舵机连接在Bigfish扩展板的D11针脚上;4个步进电机与SH-ST扩展板的连接位置见下图:

3. 功能实现

      在这里我们采用了一种算法,该算法的思路是:先建立一个平面坐标系,将我们所需要画的图形放置在该坐标系中,这样就可以确定该图形每个顶点的坐标,两个相邻的顶点之间确定一条直线,直线上各点坐标通过插补计算得到,然后画笔依次沿着这些坐标进行移动,完成绘制。所以在这个过程中,我们需要知道如何建立一个图形的坐标系,以及什么是插补计算。插补计算方法可参考 【R311】双轴XY平台-绘制斜向多边形 

     本实验将基于四福来轮全向底盘利用processing软件处理gcode文件后,进行绘制文字“探索者”。gcode文件的生成可参考【R312】三轴XYZ平台-生成gcode文件

3.1示例程序

编程环境:Arduino 1.8.19

下面给大家提供一个写字-探索者的参考例程(stepper_car_write.ino),将参考例程下载到主控板中:

/*------------------------------------------------------------------------------------

  版权说明: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);

}

3.2 图形绘制

      接下来我们将通过上位机的processing软件发送生成文字“探索者”的 gcode文件给四福来轮全向底盘进行图形绘制。具体操作步骤可参考【R312】三轴XYZ平台-绘制空心字

4. 资料内容

①写字-例程源代码

②软件资料包

资料内容详见:四福来轮全向底盘(十字)-写字

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/474119.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

干货分享:AI绘图学习心得-Midjourney绘画AI,让你的AI绘画之路少走弯路

干货分享&#xff1a;AI绘图学习心得-Midjourney绘画AI 最重要的Prompt和参数基本 Prompts高级Prompts 一、构图指令结构二、常用指令分享三、操作技巧总结四、常用风格词汇五、常用构图词汇六、高频实用词汇推荐&#xff1a;七、其他AI资料获取&#xff1a; 本篇没有什么长篇大…

01-Vue技术栈之基础篇(上)

目录 1、Vue简介1.1 Vue官网1.2 介绍与描述1.3 Vue 的特点1.4 与其它 JS 框架的关联1.5 Vue 周边库 2. 初识Vue2.1 Vue初体验2.2 注意事项2.3 js表达式和js代码&#xff08;语句&#xff09; 3、Vue模板语法3.1 语法分类3.2 插值语法3.3 指令语法 4、Vue模板语法4.1 数据绑定方…

Java中几种常量池面试总结

字符串常量池&#xff08;string pool&#xff09; 字符串常量池是JVM为了提升性能和减少内存消耗针对字符串&#xff08;String类&#xff09;专门开辟的一块区域&#xff0c;主要目的是为了避免字符串的重复创建。 当需要使用字符串时&#xff0c;先去字符串池中查看该字符…

使用vscode 创建vue3.0项目,应用element-plus框架

使用npm指令创建项目 npm init vite-app 项目名称 npm install npm run dev输入http://localhost:3000/ 查看 2、可自定义vue模板 输入vue.json 回车。复制下述代码&#xff0c;然后保存。 {"Print to console": {"prefix": "vue","b…

每天一道算法练习题--Day15 第一章 --算法专题 --- -----------二叉树的遍历

概述 二叉树作为一个基础的数据结构&#xff0c;遍历算法作为一个基础的算法&#xff0c;两者结合当然是经典的组合了。很多题目都会有 ta 的身影&#xff0c;有直接问二叉树的遍历的&#xff0c;有间接问的。比如要你找到树中满足条件的节点&#xff0c;就是间接考察树的遍历…

STM32物联网实战开发(3)——串口打印

串口打印 串口的使用在单片机开发过程中经常出现&#xff0c;因为他在显示数据和调试过程中特别的方便&#xff0c;使用起来也很简单。 1.用STM32CubeMx配置串口 串口1模式选择异步&#xff0c;不开启硬件控制流&#xff08;串口通信分为同步通信和异步通信&#xff0c;他们往…

云HIS : 电子病历模板制作过程技术经验分享

电子病历的制作就是按照医院机构的特色&#xff0c;根据不同业务需求&#xff0c;使用模板编辑与预览工具&#xff0c; 综合运用工具模块制作个性化、实用化、特色化电子病历模板的过程。 按照制作流程分为以下几个步骤&#xff1a; 1.明确病历类型&#xff1a;根据业务方向…

掌握好这几款TikTok数据分析工具,让你轻松提高曝光率!

为什么别人在TikTok发的普普通通的视频却有那么高的流量、几天内疯狂涨粉&#xff0c;而自己想破脑袋装饰自己的视频&#xff0c;结果却不如人意。 其实原因很简单&#xff0c;TikTok不像国内的抖音只面向中华民族&#xff0c;而是覆盖了150多个国家和75种语言用户&#xff0c…

【五一创作】Scratch资料袋

Scratch软件是免费的、免费的、免费的。任何需要花钱才能下载Scratch软件的全是骗子。 1、什么是Scratch Scratch是麻省理工学院的“终身幼儿园团队”开发的一种图形化编程工具。是面向青少年的一款模块化&#xff0c;积木化、可视化的编程语言。 什么是模块化、积木化&…

【VM服务管家】VM4.x算子SDK开发_3.1 环境配置类

目录 3.1.1 环境配置&#xff1a;CSharp算子SDK开发环境配置方法3.1.2 算子封装&#xff1a;使用C封装算子SDK的方法3.1.3 异常中断&#xff1a;算子SDK软件运行报错“托管调试助手”中断的解决方法3.1.4 深度学习&#xff1a;GPU运行深度学习算子引发StackOverFlow异常的方法 …

FP独立站推广成本太高?那是因为你没看到这篇!

近年来&#xff0c;越来越多的商家开始搭建自己的跨境电商独立站&#xff0c;做起了FP独立站。那么用独立站做FP到底有什么优势&#xff1f;还有&#xff0c;推广成本真的很高吗&#xff1f;今天这期就给大家扒一扒。 用独立站做FP的优势 1、塑造品牌&#xff0c;扩大经营触及…

【HarmonyOS】元服务WebView组件 H5使用localstorage

在日常开发中我们会在应用种接入H5网页&#xff0c;localStorage作为H5本地存储web storage特性的API之一&#xff0c;主要作用是将数据保存在客户端中。对于快速开发元服务&#xff0c;通过WebView组件运行H5如何使用localstorage呢&#xff1f;下文以API7 JavaUI为例为大家做…

k8s 集群搭建详细教程

参考&#xff1a; Kubernetes 文档 / 入门 / 生产环境 / 使用部署工具安装 Kubernetes / 使用 kubeadm 引导集群 / 安装 kubeadm B. 准备开始 一台兼容的 Linux 主机。Kubernetes 项目为基于 Debian 和 Red Hat 的 Linux 发行版以及一些不提供包管理器的发行版提供通用的指令每…

3.3 Linux shell命令(权限、输入输出)

目录 shell shell概述 shell分类 查看当前系统的shell 权限相关命令&#xff08;也是shell命令&#xff09; 基本命令 输入输出相关操作 输出命令 输入输出重定向 通配符 管道 历史查询、补齐功能 历史查询 自动补齐 命令置换 shell 什么是shell shell是一种负…

【VM服务管家】VM4.0软件使用_1.2 工具类

目录 1.2.1 文本保存&#xff1a;逐行保存格式化模块输出的方法1.2.2 脚本模块&#xff1a;循环模块搭配脚本使用的方法1.2.3 几何查找&#xff1a;彩色图像的几何查找方法1.2.4 深度学习&#xff1a;图像分割的面积的获取方法1.2.5 颜色识别&#xff1a;使用颜色识别工具做分类…

【Leetcode -86.分隔链表 -92.反转链表Ⅱ】

Leetcode Leetcode -86.分隔链表Leetcode -92.反转链表Ⅱ Leetcode -86.分隔链表 题目&#xff1a;给你一个链表的头节点 head 和一个特定值 x &#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 你应当 保留 两个分区中每…

java数据结构之HashMap

目录 前言 1、初始化 1.1、初始化 1.2、插入第一条数据 2、数组 链表 2.1、插入数据&#xff1a;没有hash冲突 2.2、插入数据&#xff1a;Key不同&#xff0c;但产生hash冲突 2.3、插入数据&#xff1a;Key相同 3、数组 红黑树 3.1、链表如何转化为红黑树&#xff1f; 3.…

Postman测试实践笔记

Postman测试实践 文章目录 Postman测试实践一、Postman安装与使用1.1 Postman下载及安装1.1.2 Postman Mac版 1.2 Postman 更新1.2.1 mac 版更新 1.3 Postman 其他问题 二、网络相关知识2.1 接口2.1.1 软件为什么需要接口 2.2 接口测试2.2.1 什么是接口测试&#xff1a;2.2.2 为…

VTK下载并安装

去官网下载https://vtk.org/download/ 选择最新稳定版本 然后点击source后边的压缩包进行下载。 下载完成后将其解压到特定的文件夹下&#xff0c;然后打开cmake-gui.exe&#xff0c;第一行选择刚刚解压的文件夹&#xff0c;这个文件夹下有一个CMakeLists.txt文件&#xff0c…

【6. 激光雷达接入ROS】

欢迎大家阅读2345VOR的博客【6. 激光雷达接入ROS】&#x1f973;&#x1f973;&#x1f973; 2345VOR鹏鹏主页&#xff1a; 已获得CSDN《嵌入式领域优质创作者》称号&#x1f47b;&#x1f47b;&#x1f47b;&#xff0c;座右铭&#xff1a;脚踏实地&#xff0c;仰望星空&#…