使用三轴XYZ平台绘制空心字

news2025/1/16 8:19:32

1. 功能说明 

     本文示例将实现R312三轴XYZ平台绘制“机器时代”空心字的功能。

2. 电子硬件

      在这个示例中,采用了以下硬件,请大家参考:

主控板

Basra主控板(兼容Arduino Uno)

扩展板

Bigfish2.1扩展板

SH-ST步进电机扩展板
电池11.1V动力电池
传感器触碰传感器

其它

笔架×1(自制,可根据文末资料提供的3D文件打印)

3. 功能实现

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

建立坐标系:

     三轴XYZ平台绘图仪,即通过X, Y, Z三轴的步进电机协调控制绘图笔来进行图形的绘制。通过上位机(PC)来发送gcode代码;下位机(三轴XYZ平台绘图仪)通过对接收到的gcode坐标代码进行解析,并通过插补算法来控制各个轴的步进电机进行图形绘制。

本实验将基于三轴XYZ平台利用processing软件处理gcode文件后,进行绘制文字“机器时代”。

3.1硬件连接

    ① 各轴步进电机与SH-ST步进电机扩展板的接线顺序如下(从上至下):

         X:红蓝黑绿

         Y:红蓝黑绿

         Z:黑绿红蓝

    ② 各个轴的限位传感器(触碰)与Bigfish扩展板的接线如下:

        X:A0

        Y:A4

        Z:A2

3.2 示例程序

编程环境:Arduino 1.8.19

      将参考例程代码(_4_smile.ino)下载到主控板中,烧录完成后打开电源,三轴XYZ平台绘图仪各轴步进电机将进行复位,复位完成后,绘图笔将到达绘图区域中心,本实验中三轴XYZ平台绘图仪绘图面积为80*80mm。

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

  版权说明: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-30 https://www.robotway.com/

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

#define EN        8       //步进电机使能端,低电平有效

#define X_DIR     5       //X轴 步进电机方向控制

#define Y_DIR     6       //y轴 步进电机方向控制

#define Z_DIR     7       //z轴 步进电机方向控制

#define X_STP     2       //x轴 步进控制

#define Y_STP     3       //y轴 步进控制

#define Z_STP     4       //z轴 步进控制

boolean DIR;              //boolean类型变量 DIR,控制步进电机方向,true为正向,false为反向,根据接线做调整

int stepper_pulse = 40;   //定义步进电机脉冲发送的时间间隔


#define LINE_BUFFER_LENGTH 512  


const int SENSOR_X = 14;   //定义X,Y,Z轴复位传感器引脚

const int SENSOR_Y = 18;

const int SENSOR_Z = 16;


const int stepsPerRevolution = 3200;   //定义步进电机每圈转动的步数,细分为16


float LEAD = 8;   //定义丝杠导程,即步进电机转动一圈,丝杠前进8mm


struct point {   

  float x;

  float y;

  float z;

};


// Current position of plothead

struct point actuatorPos;


float Xmin = -40;   //定义绘图区域范围

float Xmax = 40;

float Ymin = -40;

float Ymax = 40;


float Xpos = 0;

float Ypos = 0;


boolean verbose = false;


void setup()

{

  Serial.begin(9600);      //开启串口通信,波特率为9600

  pinMode(X_DIR, OUTPUT); pinMode(X_STP, OUTPUT);

  pinMode(Y_DIR, OUTPUT); pinMode(Y_STP, OUTPUT);

  pinMode(Z_DIR, OUTPUT); pinMode(Z_STP, OUTPUT);

  pinMode(EN, OUTPUT);

  digitalWrite(EN, LOW);  

  resetStepper();

  digitalWrite(EN, HIGH);

  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

  stepper_pulse = 40;         //设置Z轴抬笔落笔时步进电机脉冲间隔

  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':

      step(Z_DIR, Z_STP, 2000);

      break;

    case 'D':

      step(Z_DIR, Z_STP, -2000);

      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) {

            step(Z_DIR, Z_STP, -2000);

          }

          if (Spos == 50) {

            step(Z_DIR, Z_STP, 2000);

          }

          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;

  stepper_pulse = 150;              //设置步进电机写字时脉冲间隔

 

  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.println(Xpos);

  //Serial.println(Ypos);

 

  dx = abs(x1-x0);

  dy = abs(y1-y0);

  n = abs(dx+dy);

 

  if(dx==0||dy==0)

  {

    stepInc = 1;

  }

  else

  {

    stepInc = 10;

  }


  if(x1 >= x0)

  {

    k = y1 >= y0 ? 1:4;

  }

  else

  {

    k = y1 >= y0 ? 2:3;

  }

 

  for(i=0,f=0;i<n;i+=stepInc)

  {

    if(f>=0)

    {

      switch(k)

      {

         case 1:

         step(X_DIR, X_STP, stepInc);

         f = f - dy;

         //Serial.println("+x");

         break;

         case 2:

         step(X_DIR, X_STP, -stepInc);

         f = f - dy;

         //Serial.println("-x");

         break;

         case 3:

         step(X_DIR, X_STP, -stepInc);

         f = f - dy;

         //Serial.println("-x");

         break;

         case 4:

         step(X_DIR, X_STP, stepInc);

         f = f - dy;

         //Serial.println("+x");

         break;

         default:break;

      }

    }

    else

    {

      switch(k)

      {

        case 1:

        step(Y_DIR, Y_STP, stepInc);

        f = f + dx;

        //Serial.println("+y");

        break;

        case 2:

        step(Y_DIR, Y_STP, stepInc);

        f = f + dx;

        //Serial.println("+y");

        break;

        case 3:

        step(Y_DIR, Y_STP, -stepInc);

        f = f + dx;

        //Serial.println("-y");

        break;

        case 4:

        step(Y_DIR, Y_STP, -stepInc);

        f = f +dx;

        //Serial.println("-y");

        break;

        default:break;

      }

    }

  }

  Xpos = x1;

  Ypos = y1;

}


/*

//函数:step    功能:控制步进电机方向,步数。

//参数:dirPin对应步进电机的DIR引脚,stepperPin 对应步进电机的step引脚, steps 步进的步数

//无返回值

*/

void step(byte dirPin, byte stepperPin, int steps)

{

  digitalWrite(EN, LOW);

  boolean DIR = steps>0 ? true : false;  

  digitalWrite(dirPin,DIR);

  for(int i=0;i<abs(steps); i++)

  {

    digitalWrite(stepperPin, HIGH);

    delayMicroseconds(stepper_pulse);

    digitalWrite(stepperPin, LOW);

    delayMicroseconds(stepper_pulse);

  }

  digitalWrite(EN, HIGH);

}


//步进电机复位函数

void resetStepper()

{

    stepper_pulse = 40; //设置步进电机复位脉冲间隔

   

    while(digitalRead(SENSOR_Z))

        step(Z_DIR,Z_STP,10);

    step(Z_DIR,Z_STP,-15);

while(digitalRead(SENSOR_X))

        step(X_DIR,X_STP,-10);

    step(X_DIR,X_STP,15);

while(digitalRead(SENSOR_Y))

    step(Y_DIR,Y_STP,10);

    step(Y_DIR,Y_STP,-15);


    //复位笔至平台中间位置,步数根据中间位置距离复位传感器的距离计算

    step(X_DIR, X_STP, 28000);

    step(Y_DIR, Y_STP, -16000);

    step(Z_DIR, Z_STP, -30000);

}

3.3 图形绘制

      接下来我们将通过上位机的processing软件发送生成文字“机器时代”的 gcode文件给三轴XYZ平台绘图仪进行图形绘制。

      首先将 软件资料包\processing-2.0b8.zip 文件解压到电脑上任意磁盘,然后打开processing.exe来启动 Processing 软件,之后按下图所示步骤进行操作:

      此时打开绘图仪电源开关,在英文输入法状态下按键盘P键,选择端口号,等待三轴XYZ平台绘图仪复位完毕,进入接收上位机指令状态;然后英文输入法状态下按键盘G键,选择之前生成的 gcode文件,点击确定,开始发送gcode文件代码,三轴XYZ平台绘图仪开始绘图;三轴XYZ平台绘图仪在绘图过程中,可以按X键来停止发送gcode文件代码。

注意事项:

① 关于绘图笔的安装,可以让绘图仪进入工作状态后关闭电源,此时安装绘图笔使其与纸面相接即可。

② 程序中步进电机使用的细分数为16细分,无细分时200步/圈,16细分即 3200步/圈。

③ 生成gcode坐标文件后,使用windows的笔记本或者Notepad++软件打开gcode文件,然后删除第一行和第二行,如下图所示:

4. 资料内容

①绘制空心字-例程源代码

②绘制空心字-样机3D文件

③软件资料包

资料内容详见:三轴XYZ平台-绘制空心字

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

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

相关文章

2023年最系统的自动化测试,测试开发面试题,10k以下不建议看

鉴于现在严峻的就业形势&#xff0c;千万大学生即将出新手村&#xff0c;今天给大家打包好了2023最能避免薪资倒挂的《面试圣经》。不经一番寒彻骨,怎得梅花扑鼻香。这份面试题&#xff0c;与君共勉&#xff01; 一、开场白 Q&#xff1a;简单自我介绍一下吧 Q&#xff1a;项…

Bots攻击威胁石油石化企业 瑞数动态安全实现从“人防”到“技防”

近日&#xff0c;中国石油石化企业信息技术交流大会暨油气产业数字化转型高峰论坛在京召开。本届大会由中国石油学会、中国石油、中国石化、中国海油、国家管网、国家能源、中国中化、中国航油、延长石油、中国地质调查局等单位共同主办。 作为我国石油石化行业的盛会&#xf…

论坛现场回顾:维视教育的新工科人才培养 「最佳实践 」

全国高校电子信息类专业教学论坛隆重召开 由教育部高等学校电子信息类专业教学指导委员会主办&#xff0c;苏州大学、清华大学出版社承办的“全国高校电子信息类专业教学论坛”于2023年4月14日-16日在江苏省苏州市隆重开幕&#xff0c;维视教育作为电子信息类教学指导委员会战略…

最优化方法Python计算:连续函数的单峰区间计算

我们知道&#xff0c;闭区间上的一元连续函数必在区间上取得最大值和最小值。实践中我们需要能数值地确定含有 f ( x ) f(x) f(x)的唯一最优解 x 0 x_0 x0​的区间 [ a , b ] [a,b] [a,b]。这里介绍寻求连续函数 f ( x ) f(x) f(x)在一点 x ∗ x^* x∗附近单峰区间的包围算法及…

飞桨paddlespeech语音唤醒推理C实现

上篇&#xff08;飞桨paddlespeech 语音唤醒初探&#xff09;初探了paddlespeech下的语音唤醒方案&#xff0c;通过调试也搞清楚了里面的细节。因为是python 下的&#xff0c;不能直接部署&#xff0c;要想在嵌入式上部署需要有C下的推理实现&#xff0c;于是我就在C下把这个方…

Android Stuido中修改项目SDK版本号

通过Android Studio创建项目时&#xff0c;只能选择项目支持的最低SDK版本号&#xff0c;而无法选择当前编译使用的版本号&#xff0c;如图1所示。 图1 选择项目支持的最小版本号 而编译项目的SDK版本号默认是Android Studio安装的最新SDK。如果不想使用最新SDK来编译项目&…

C++入门(3)

C入门 1.auto关键字&#xff08;C11&#xff09;1.1. 类型别名的思考1.2. auto简介1.3. auto使用情景1.4. auto的使用细则1.5. auto不能推导的场景 2.函数内联2.1. 问题提出2.2. 概念&#xff08;关键字——inline&#xff09;2.3. 特性2.4. 面试题 3. 基于范围的for循环(C11)3…

Python 基础(八):流程控制语句

❤️ 博客主页&#xff1a;水滴技术 &#x1f338; 订阅专栏&#xff1a;Python 入门核心技术 &#x1f680; 支持水滴&#xff1a;点赞&#x1f44d; 收藏⭐ 留言&#x1f4ac; 文章目录 一、条件语句1.1、if1.2、if...else...1.3、if...elif...else... 二、匹配语句2.1、ma…

亚马逊、ebay、temu如何提升产品点击率?测评自养号解析

产品点击率对于店铺销售额的影响至关重要&#xff0c;尤其是在竞争越来越激烈的市场环境中&#xff0c;想要有销量和转化&#xff0c;提高产品listing点击率成为了非常关键的一环。 1. 产品主图 顾客浏览产品时&#xff0c;第一眼看到的就是主图&#xff0c;一张优质的主图更容…

Scala之面向对象

目录 Scala包&#xff1a; 基础语法&#xff1a; Scala包的三大作用&#xff1a; 包名的命名规范&#xff1a; 写包的好处&#xff1a; 包对象&#xff1a; 导包说明&#xff1a; 类和对象&#xff1a; 定义类&#xff1a; 封装&#xff1a; 构造器&#xff1a; 主从…

将项目部署到服务器上,并且使用JMeter测试项目性能

1、前提条件 首先呢&#xff0c;你要有一个有一个项目&#xff0c;并且打包成jar包。然后你要有一台服务器&#xff0c;你可以登录阿里云&#xff08;国内社区&#xff09;然后买按量付费的服务用一下&#xff0c;其实服务器就像虚拟机一样&#xff0c;linux命令执行。但是Cen…

操作系统的结构与功能流程

一、用户态和内核态 用户态可以理解为用户模式&#xff0c;内核态理解为内核模式 二、功能流程举例 假设你是一名顾客在一家餐厅用餐&#xff0c;餐厅有一个厨师负责烹饪食物&#xff0c;而你作为顾客只能在餐厅的就餐区域内进行点餐、享用餐点。这里就可以将就餐区域看作用户…

【基于视觉的分割】语义分割初探索:一些经典和先进的算法

写在前面&#xff1a; 有很长时间没有更新学习了&#xff0c;因为在忙着做试验写毕业论文。但是&#xff0c;学习不能停止&#xff0c;从今天开始&#xff0c;换成语义分割方向进行深入学习&#xff0c;有兴趣的小伙伴可以和我一起讨论&#xff0c;也欢迎才进入这个方向学习的…

【Grafana】連接mssql並圖表顯示

【Grafana】連接mssql並圖表顯示 1. 函數2. Demo2.1 Query2.2 Query 3. Awakening1.1 Big Data -- Postgres 1. 函數 Macro exampleReplaced by$__time(dateColumn)An expression to rename the column to time. For example, dateColumn as time$__timeEpoch(dateColumn)An e…

改变思想,拥抱毒瘤,让公司走的更远

牛B的人物&#xff0c;早已经厌倦了中英文混杂&#xff0c;他们更进一步&#xff0c;使用中英文缩写&#xff0c;对普通人进行降维打击。更厉害的&#xff0c;造就新的名词&#xff0c;并科普出去。 有几项技术&#xff0c;我从心底里鄙视和厌恶&#xff0c;但每次在技术方案中…

Cursor IDE一个GPT4人工智能自动程序编辑器

让我们来了解一下Cursor IDE是什么。Cursor IDE是一个新型的编程工具&#xff0c;可以通过它生成、编辑以及与人工智能进行交互分析代码。官方网站上的三个单词“Build Software. Fast.”&#xff08;快速构建软件&#xff09;以及“Write, edit, and chat about your code wit…

Spark 实现重新分区 partitionBy、coalesce、repartition(附代码演示)

文章目录 1、partitionBy 源码中的定义&#xff08;部分&#xff09; 调用方式 2、coalesce 源码中的定义 调用方式 3、repartition 源码中的定义 调用方式 repartition和coalesce的区别 代码演示 &#xff08;跳转代码&#xff09; 实现重新分区&#xff0c;本质上…

如何轻松进行接口测试?试试这款神器Apifox,亲测好用!

Apifox学习教程地址&#xff1a;https://www.bilibili.com/video/BV1mb411o7Go/? 目录&#xff1a;导读 ​引言 一、接口调试 二、Mock功能 三、自动化测试 引言 如果你曾经为手工编写和维护测试用例而感到疲惫&#xff0c;那么你需要试试这款神器——Apifox&#xff01; …

【云原生】Kubernetes(k8s)之Pod概念和使用

k8s之Pod概念和使用 一、Pod简介1.1、Pod的阶段&#xff08;状态&#xff09;1.2、容器状态 二、Pod的定义2.1、restartPolicy2.2、imagePullPolicy2.3、command2.4、args2.5、resources 三、Pod的使用3.1、创建并访问Pod3.2、多个应用容器3.3、Init容器3.3.1、Init容器与普通容…

劝人写码,千刀万剐——“前端已死”难道要成真了?

移动互联网的兴起&#xff0c;传统行业的数字化转型&#xff0c;大前端技术的普及&#xff0c;随之而来的就是Vue为代表的前端框架和工具的兴起&#xff0c;前端开发的门槛降低。但发展&#xff0c;稳定&#xff0c;衰落是亘古不变的事物发展规律。 一些有趣的迹象 最近逛社区…