制作基于Arduino的迷你CNC绘图机

ChangeCode
转载
发布时间: 2025-06-14 15:13:25 | 阅读数 0收藏数 0评论数 0
封面
在这篇文章中,我们将看到如何使用旧的废弃DVD驱动器、Arduino和L293D电机驱动板制作迷你CNC绘图机。我会涵盖所有要点,包括硬件组装、Arduino代码、Processing GUI界面、G代码生成等。接下来,我们将深入探讨每个步骤的具体细节,从准备所需材料开始,直到完成整个项目的软件编程

准备工作:

材料:

概述

CNC绘图机基本上是一个2.5轴的CNC机器,它在X轴和Y轴上各有一个步进电机,在Z轴上有一个伺服电机。一支笔连接在Y轴上,而Z轴用于控制笔的上下移动。

正如名称所示,绘图机根据给定的指令绘制或描绘图形。为了给机器下达绘制什么内容的指令,需要一种特殊的代码——G代码。图像将通过特殊软件转换为G代码,然后此G代码被发送到控制器,控制器指挥电机如何移动。结果是,机器将在纸上绘制出图像。

现在,让我们看看如何构建这样的机器,首先从材料清单开始。

材料清单


序号

项目

数量

1

报废DVD驱动器

2

2

Arduino UNO

1

3

L293D电机驱动板

1

4

迷你伺服电机

1

5

5V 1A电源适配器

1

6

若干电机连接线

——

7

所需的螺母和螺栓

——

软件清单

  1. Arduino IDE:用于编写和上传代码到Arduino板。
  2. Processing IDE:可用于进一步处理绘图数据,或作为替代方案生成G代码。
  3. Inkscape版本0.48.5:一个开源的矢量图形编辑软件,可以用来创建或编辑要由CNC绘图机制作的设计,并帮助将图像转换为G代码。
1

DVD光驱

为了制作基于Arduino的迷你CNC绘图机,我们显然需要两个废弃的DVD光驱。

我在当地的电脑维修店以不到1美元的价格购买了这些光驱。我们将使用其中的步进电机及滑动机构。需要注意的是,并非所有DVD光驱都内置步进电机;如果电机接了4根电线,则表明它是一个步进电机。

2

光驱拆解

我用螺丝刀迅速拧开DVD光驱的外壳,并通过施加一些力从中取出步进电机机构。这样,我就得到了两个步进电机机构和两个空的DVD光驱外壳。

3

电机接线

取出步进电机机构后,我用剪刀剪断了默认的电机连接线。然后,我拿来大约40厘米长的杜邦4芯线,将其切成两段,每段用于一个步进电机的连接。接着,我小心地剥去杜邦线的外皮,避免损坏铜丝,然后将它们焊接到步进电机裸露的引脚上。

4

外壳喷色

为了成品的好看,我给他喷涂了一些灰色颜料。

5

角铁固定

用一块20 x 20毫米的角铁制作X轴和Y轴的支架。我在角铁上钻了5毫米的孔,并将其切成两段夹具,进一步使用这些夹具和M5X10螺母及螺栓来固定两个轴。

6

钻出电机所需螺孔

现在,我在DVD光驱外壳上标记孔的位置,以便为两个步进电机机构的安装做准备。我使用钻孔机小心地钻出5毫米的孔。

7

电机固定

在DVD光驱外壳上钻孔后,我在步进电机机构的四个角上固定了四颗M4 X 60的螺母和螺栓。现在,我把步进电机机构放到相应的位置,并用M4螺母固定住所有四个螺栓。

8

制作笔的上下移动机制

这是制作迷你Arduino CNC绘图机中最重要的一步,我们将在此步骤中制作笔的上下移动机制。首先,我取了一个圆规,并小心地移除了它的笔夹部分。然后,我使用了一支顶部和底部可以打开的普通笔。首先取出笔芯,从顶端切下大约2厘米的部分。现在,我在笔芯的顶部放置了一个从其他按压式笔中 salvaged(回收)的弹簧。接着,我用一根结实的线,在笔芯的中部绑紧,并用超级胶水固定在其位置上。现在,在笔身中部正上方的位置,我钻了一个小孔。

接下来,我小心地将笔芯放入笔中,并把线从孔中拉出。这样我就完成了笔的上下移动机制:当我拉动线时,笔芯会向上推;当我释放线时,笔芯会落下。由于在笔芯顶部装有弹簧,笔尖能与纸张保持良好的摩擦力。最后,我把笔放在笔夹中,并用超级胶水将其固定在X轴上。

9

伺服电机控制

我在X轴上安装了一个小型伺服电机,并将线绑在小型伺服电机的旋钮上。

10

安装Arduino控制器及电机驱动板

我在机器背面钻了四个孔,并拧入四个15毫米的间隔柱,以便将Arduino UNO安装上去。然后,将L293D电机驱动板安装在Arduino UNO上。通过这些步骤,Arduino CNC绘图机的组装就完成了,接下来我们将进行接线。

11

电路图


12

步进电机接线前的准备工作

我们使用Arduino UNO作为CNC机器的核心,正如我们知道的,CNC机器中使用了步进电机。步进电机不容易控制,因此我们这里使用L293D电机驱动板来控制步进电机,同时使用一个伺服电机来实现笔的上下移动。

在开始接线之前,首先我们需要确认步进电机的正确接线。我们的步进电机有4根线,这意味着它有两个线圈,即两组线各自形成一个线圈。因此,我们需要找出哪两根线属于同一个线圈。这里我使用万用表进行连续性测试来确定这一点。

我把万用表设置为连续性测试模式,并将测试笔逐一连接到电机的线上。如果在任意两根线之间测得连续性(几欧姆的电阻),则说明这两根线属于同一个线圈,剩下的两根线则属于另一个线圈。

13

完成接线

别忘了移除黄色跳线,并按照上述图示连接步进电机的电线。同时,将伺服电机连接到L293D电机驱动板上的伺服1端子。

你需要一个电源适配器为机器供电,可以使用5V直流1安的电源适配器。

通过这些步骤,接线工作就完成了。接下来,我们可以进入Arduino代码上传的过程。

14

ARDUINO 代码讲解

你可以从下面的链接下载。

  1. Arduino代码
  2. AFMotor库

首先,我们需要在Arduino IDE中安装AFMotor库。如果你不知道如何添加库,只需谷歌搜索即可。

现在,无需做任何修改,直接上传代码。

这里,我将解释代码中一些可能对你有用的重要的部分。

以下是伺服电机上下移动的值,如果需要可以增加或减少。如果伺服电机的工作方向相反,请交换penZUppenZDown的值。

// Servo position for Up and Down
const int penZUp = 120;
const int penZDown = 50;

以下是用于更改CNC绘图机速度的值,你可以将StepDelay的值从0调整到2:

  1. 0代表最高速度,
  2. 2代表最低速度,

理想情况下,将其保持在1。

float StepInc = 1;
int StepDelay = 1;
int LineDelay =0;
int penDelay = 50;

如果你的绘图区域较大,你可以通过修改这里的XmaxYmax的值来调整最大绘图范围。这样可以根据实际需要扩展CNC绘图机的工作区域。

float Xmin = 0;
float Xmax = 40;
float Ymin = 0;
float Ymax = 40;
float Zmin = 0;
float Zmax = 1;
15

完整代码

#include <Servo.h>
#include <AFMotor.h>

#define LINE_BUFFER_LENGTH 512

char STEP = MICROSTEP ;

const int penZUp = 120;
const int penZDown = 50;

const int penServoPin =10 ;

const int stepsPerRevolution = 48;

Servo penServo;

AF_Stepper myStepperY(stepsPerRevolution,1);
AF_Stepper myStepperX(stepsPerRevolution,2);

struct point {
float x;
float y;
float z;
};

struct point actuatorPos;

float StepInc = 1;
int StepDelay = 1;
int LineDelay =0;
int penDelay = 50;

float StepsPerMillimeterX = 100.0;
float StepsPerMillimeterY = 100.0;

float Xmin = 0;
float Xmax = 40;
float Ymin = 0;
float Ymax = 40;
float Zmin = 0;
float Zmax = 1;

float Xpos = Xmin;
float Ypos = Ymin;
float Zpos = Zmax;

boolean verbose = false;

void setup() {
Serial.begin( 9600 );
penServo.attach(penServoPin);
penServo.write(penZUp);
delay(100);

myStepperX.setSpeed(600);

myStepperY.setSpeed(600);
Serial.println("Mini CNC Plotter alive and kicking!");
Serial.print("X range is from ");
Serial.print(Xmin);
Serial.print(" to ");
Serial.print(Xmax);
Serial.println(" mm.");
Serial.print("Y range is from ");
Serial.print(Ymin);
Serial.print(" to ");
Serial.print(Ymax);
Serial.println(" mm.");
}

void loop()
{
delay(100);
char line[ LINE_BUFFER_LENGTH ];
char c;
int lineIndex;
bool lineIsComment, lineSemiColon;

lineIndex = 0;
lineSemiColon = false;
lineIsComment = false;

while (1) {

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;

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

if (verbose)
{
Serial.print("fx1, fy1: ");
Serial.print(x1);
Serial.print(",");
Serial.print(y1);
Serial.println("");
}

if (x1 >= Xmax) {
x1 = Xmax;
}
if (x1 <= Xmin) {
x1 = Xmin;
}
if (y1 >= Ymax) {
y1 = Ymax;
}
if (y1 <= Ymin) {
y1 = Ymin;
}

if (verbose)
{
Serial.print("Xpos, Ypos: ");
Serial.print(Xpos);
Serial.print(",");
Serial.print(Ypos);
Serial.println("");
}

if (verbose)
{
Serial.print("x1, y1: ");
Serial.print(x1);
Serial.print(",");
Serial.print(y1);
Serial.println("");
}

x1 = (int)(x1*StepsPerMillimeterX);
y1 = (int)(y1*StepsPerMillimeterY);
float x0 = Xpos;
float y0 = Ypos;

long dx = abs(x1-x0);
long dy = abs(y1-y0);
int sx = x0<x1 ? StepInc : -StepInc;
int sy = y0<y1 ? StepInc : -StepInc;

long i;
long over = 0;

if (dx > dy) {
for (i=0; i<dx; ++i) {
myStepperX.onestep(sx,STEP);
over+=dy;
if (over>=dx) {
over-=dx;
myStepperY.onestep(sy,STEP);
}
delay(StepDelay);
}
}
else {
for (i=0; i<dy; ++i) {
myStepperY.onestep(sy,STEP);
over+=dx;
if (over>=dy) {
over-=dy;
myStepperX.onestep(sx,STEP);
}
delay(StepDelay);
}
}

if (verbose)
{
Serial.print("dx, dy:");
Serial.print(dx);
Serial.print(",");
Serial.print(dy);
Serial.println("");
}

if (verbose)
{
Serial.print("Going to (");
Serial.print(x0);
Serial.print(",");
Serial.print(y0);
Serial.println(")");
}

delay(LineDelay);
// Update the positions
Xpos = x1;
Ypos = y1;
}

void penUp() {
penServo.write(penZUp);
delay(penDelay);
Zpos=Zmax;
digitalWrite(15, LOW);
digitalWrite(16, HIGH);
if (verbose) {
Serial.println("Pen up!");
}
}

void penDown() {
penServo.write(penZDown);
delay(penDelay);
Zpos=Zmin;
digitalWrite(15, HIGH);
digitalWrite(16, LOW);
if (verbose) {
Serial.println("Pen down.");
}
}

目前代码已经上传,现在是时候生成G代码了。

INO
CNCDraw.ino
9.48KB
16

G-code生成

要使用CNC绘图机进行绘制,我们显然需要G代码。G代码是CNC机器的语言。

在这个项目中,我们将使用Inkscape软件和Makerboat G代码库来生成图像的G代码。

首先,下载Inkscape 0.48.5版本,并下载Makerboat G代码扩展。

安装Inkscape软件,并按照以下步骤添加扩展。


请注意,您需要下载Inkscape 0.48.5版本。
17

导出G-code注意事项

注意:如果在机器即将开始时Arduino断开连接或重置,请在导出G代码时取消选中以下选项。

18

Inkscape模板

注意:如果你的机器只绘制了图案的一部分,你可以使用这个Inkscape模板。下载此模板并在Inkscape中打开它,然后将你的设计放置在虚线框内。

19

GCTRL

此时,我们的机器已经准备好绘制任何东西,只需等待命令。我们还生成了G代码,但是如何将这个G代码发送给机器呢?

为此,我们将使用GCTRL,这是Processing中的一个G代码发送GUI。我们通过Processing和GCTRL的帮助向机器流式传输G代码。

希望你已经从上面下载了GCTRL,如果没有,请从这里下载

只需双击打开GCTRL.pde文件。现在点击Processing窗口右上角可见的播放按钮,将会出现这样的窗口:

使用GCTRL的步骤

所有关于如何使用此图形用户界面(GUI)的详细信息都清楚地写在那里。

- 通过按“p”键,你可以从下拉列表中选择COM端口。

- 选择端口后,可以使用箭头键来手动移动X轴和Y轴。

- 使用数字键“5”和“2”来控制笔的上下移动。

- 要发送G代码到Arduino,请按“g”键并从浏览窗口中选择G代码文件。一旦选择文件并按下回车键,机器就开始绘制图案。

通过这种方式,基于Arduino的迷你CNC绘图机的制作过程就完成了。希望我已经涵盖了主要的要点。

阅读记录0
点赞0
收藏0
禁止 本文未经作者允许授权,禁止转载
猜你喜欢
评论/提问(已发布 0 条)
评论 评论
收藏 收藏
分享 分享
pdf下载 下载