[23호]DIY 프로젝트 공모전 – 저가 개인용 CNC 만들기
심사평
NtrexLab DIY 공모전에 어울리는 도전으로 판단됩니다. 특히 간단하긴 하지만, CNC의 형태를 이루기 위한 자작의 내용이 좋고, MACH3의 출력값을 받아서 처리하도록 하여, 범용한 CNC의 내용을 이해하고 있는 것으로도 보입니다. 단, 엔드툴 등에 좀 더 신경을 쓰면 더 좋은 작품이 되었을 텐데하는 아쉬움이 남습니다.
JK전자 MACH를 사용하지 않고 GCODE를 분석했다면 기술성과 실용성에서 굉장히 점수를 많이 주고 싶었으나 상용 S/W 인 MACH를 이용해 신호만 받아서 좀 더 간단하게 처리함으로서 아쉬웠습니다. 하지만 전자적인 지식 뿐만이 아니라 기계적으로 부품들을 가공하면서 작품을 완성시키는 열정은 높게 평가합니다.
싱크웍스 실용적으로 좋은 작품인 것 같다. 다만 동작을 확인해 볼 수가 없어서 작품의 완성도는 높게 평가할 수 없었다. 기성 제품은 있지만 저렴하게 나에게 맞는 CNC를 만들어 사용한다는 것에 점수를 주고 싶다.
작품 개요
취미가 무엇인가를 만드는 건데 이번에 로봇 쪽에 관심을 두게 되었다. 로봇의 뼈대를 개인적으로 만들고 싶은데 일일이 주문 제작하기에는 가격적인 부담이 너무 커서 이참에 로봇 프레임 제작에 사용할 간단한 CNC 하나를 제작해 보자 해서 시작하게 되었습니다.
실제 재품 판매 가격은 100만원대가 훌쩍 넘어 버려서 도저히 학생이 감당할 금액이 안되다보니 최소한의 비용으로 최대의 효과를 낼 수 있도록 기본 재료들로 CNC를 DIY하게 되었습니다.
3.1 주요 동작 및 특징
주요 동작으로는 컴퓨터의 CAM/CAM을 통해 3D를 G-code로 변환한 뒤 G-code를 MACH3에 넣어주면 컴퓨터의 parallel port로 x,y,z에 대한 동작 신호를 출력해줍니다. 그 신호를 MCU에서 읽어들여서 x,y,z축에 해당하는 스테핑 모터를 구동시켜 줍니다. 스테핑 모터의 회전을 전산 볼트와 너트로 수직운동으로 바꾸어 원하는 좌표에 도달하게 해줍니다. 스태핑 모터 구동을 위해 MCU에서 나오는 3.3V의 신호를 FET구동을 위해 10V이상으로 증폭을 시키기 위해 OPAMP의 전압 비교기 회로를 사용했습니다. 모터 구동은 FET H-bridge 방식입니다.
3.2 전체 시스템 구성
각 축마다 전산볼트로 중심 축을 잡고 끝단에 베어링을 달아 중심을 맞추고 투명 튜브를 사용해서 커플링을 대체했습니다. 메인 모터는 일반 충전 드릴을 사용했습니다. 이동하는 틀을 잡기위한 이속 레일에는 서랍 레일을 사용했으며, 각 부분에 5t의 아크릴을 각각 사용했습니다. 개인용 조작 키패드를 추가로 장착했으며 수동으로 모터의 속도도 조절이 가능합니다. 모터에 인가되는 전압원을 1개로 특정지어 자유로운 전압으로 모터 구동이 가능합니다.
3.3 개발 환경
MCU : ARM Cortex-M3
Compiler : IAR Embedded Workbench
Language : C
Software : Mach3, mastercam(CAD/CAM), token2shell(Serial 신호 수신)
단계별 제작 과정
구매 물품 : 프로파일, 전산볼트 너트, 5t아크릴, 기타 볼트 너트, 레일, 스테핑모터 3개, 드릴, mdf합판, 베어링, 공압튜브 기타
제작하시고 싶은 크기에 따라서 각 부품의 치수에 자유롭게 변경이 가능하여서 따로 치수는 기입하지 않겠습니다.
아직 기계 쪽을 많이 접하지도 않고 설계도를 그려본 적이 없어서 그리기가 많이 어렵네요. 기본 개념만 사용해서 만든 것이기 때문에 쉽게 만드실 수 있습니다.
http://cafe.naver.com/allcnc/ (네이버 cnc카페입니다. 이곳에서 다양한 cnc 구조 특성 등을 간략하게 배우실 수 있습니다. 저도 주로 이곳에서 배웠습니다.)
축이 될 전산 볼트와 그에 맞는 너트를 아크릴로 고정하고 좌우 왕복할 때 드는 오차를 줄이기 위해 사이에 스프링을 넣어주었습니다.
탭을 내어서 브라켓과 프로파일 사이에 90도 브라켓으로 고정을 합니다. x축에 사용되는 것입니다. 이와 동일하게 y축용도 만들어 줍니다.
5t아크릴 판에 레일을 양쪽에 올리고 그 위에 다시 아크릴 판을 올렸습니다. 조립은 볼트 너트로만 조립할 수 있게 했습니다.
가운데 전산 볼트를 넣고 양쪽에 베어링을 넣고 얇은 쇠의 양쪽에 볼트로 해서 고정했습니다.
중심에 윗판을 이동하기 위해 전산 너트에 에폭시를 덧 붙여서 윗 판과 볼트로 고정했습니다. (에폭시가 쉽게 부서져서 나중에 더 크게 만들었습니다.)
mdf합판을 드릴모터가 고정될수 있게 만든 뒤 위쪽 아크릴 판에 고정했습니다.
합판을 볼트 너트로 조여서 드릴 모터를 잡는 강도를 조정할 수 있습니다.
Y축이 될 레일과 프로파일을 볼트 너트로 조립 후 z축판과도 서로 볼트 너트로 조합합니다.
y축 전산볼트를 통과시킬수 있는 것 하나와 끝부분이 될 것 하나를 베어링과 아크릴로 만듭니다.
한쪽은 통과하지 못하게 만들고 한쪽은 통과하여 y축 프레임에 결속합니다. 통과 못하는 부분과 한쪽은 통과하여 모터와 연결할수 있도록 합니다.
스테핑 모터와 x, y축입니다. 모터는 아크릴로 각 위치에 맞게 고정할 수 있도록 합니다.
프로파일을 관통해서 모터와 축이 만날 수 있도록 했습니다.
모터와 공압 튜브로 서로 연결합니다.
전산 볼트와 너트의 축이 되는 부분을 서로 볼트로 체결하고 조립합니다.
x축이 될 부분도 동일하게 베어링과 얇은 쇠판으로 프로파일에 고정시켜 줍니다.
z축 또한 모터와 축을 연결하여 줍니다. 연결시 공압용 튜브에 케이블 타이로 조였습니다.
원래는 커플링이라는 걸 사용해야 되지만 커플링 가격이 개당 만원대여서 싼 것으로 대체 했습니다.
왼쪽사진에서의 만든 z축이 부서져서 에폭시로 추가로 더 크게 만들었습니다. (아래쪽사진)
하드웨어 부분의 최종 완성입니다.
각 축마다 내용은 동일합니다. 레일 두 개를 설치하고 가운데 전산볼트로 축을 잡고 축에 맞춰 이동할수잇는 부분을 만든뒤 끝에 스테핑 모터를 달아서 컨트롤 할 수 있게 합니다.
컨트롤용 MCU와 신호 증폭 보드입니다.
각 축마다 N채널 FET 4개 P채널 FET 4개가 사용됩니다. (H-brid ge 회로입니다.)
모터와 연결 할 수 있도록 핀을 빼놓습니다.
opamp 칩과 그 옆쪽이 키패드와 병렬포트 연결하는 곳입니다.
디바이스마트에서 판매하는 cortex m3 보드입니다. 가격이 싸서 사용했습니다.
컴퓨터로 조작하기 위해 연결하는 병렬 포트입니다.
수동 조작을 위해 사용하는 키패드 부분입니다.
기타(회로도, 소스코드, 참고문헌 등)
소스코드 (step.c, step.h, main.c)
#include
#include
#include
#include
#include
int key[3][2] = { {KEY_FRONT, KEY_BEHIND},{KEY_RIGHT, KEY_LEFT}, {KEY_UP, KEY_DOWN}};
int rem[3] = {0,0,0};
int p_bit[3][3] = {{Y_STEP,Y_DIR ,Y_LIM},
{X_STEP,X_DIR ,X_LIM},
{Z_STEP,Z_DIR ,Z_LIM}};
int step_rem[3] = {0,0,0};
GPIO_TypeDef* pt[3] = {GPIOC, GPIOC, GPIOA};
int pass[3];
int step_round[3][4] = {{0, STEP_A2, STEP_A2|STEP_A1, STEP_A1},
{0, STEP_C2, STEP_C1|STEP_C2, STEP_C1},
{0, STEP_B1,STEP_B1|STEP_B2 , STEP_B2}};
void P_CON(int q)
{
if (GPIO_ReadInputDataBit(Parallel,p_bit[q][0]) == 1)
{
if (pass[q] == 0)
{
if (GPIO_ReadInputDataBit(Parallel, p_bit[q][1]) == 1)
{
pass[q] = 1;
STEP_UP(q);
}
else
{
pass[q] = 1;
STEP_DOWN(q);
}
}
else
{
pass[q] = 0;
}
}
}
void CONTROL(int w)
{
if (GPIO_ReadInputDataBit(LIM_PORT, p_bit[w][2]) == 0)
{
if (GPIO_ReadInputDataBit(pt[w], key[w][0]) == 0)
{
if (rem[w] == left)
{
STEP_UP(w);
}
}
else if (GPIO_ReadInputDataBit(pt[w], key[w][1]) == 0)
{
if (rem[w] == right)
{
STEP_DOWN(w);
}
}
}
else{
if (GPIO_ReadInputDataBit(pt[w], key[w][0]) == 0)
{
// UART1_putString(“STEP_UP\r\n”);
STEP_UP(w);
rem[w] = right;
}
else if (GPIO_ReadInputDataBit(pt[w], key[w][1]) == 0)
{
// UART1_putString(“STEP_DOWN\r\n”);
STEP_DOWN(w);
rem[w] = left;
}
}
}
void STEP_UP(int a)
{
if (step_rem[a] == 5)
{
step_rem[a] = 0;
}
switch(++(step_rem[a]))
{
case 1:
GPIO_STEP->BRR |= step_round[a][2];
GPIO_STEP->BSRR |= step_round[a][0];
break;
case 2:
GPIO_STEP->BRR |= step_round[a][3];
GPIO_STEP->BSRR |= step_round[a][1];
break;
case 3:
GPIO_STEP->BRR |= step_round[a][0];
GPIO_STEP->BSRR |= step_round[a][2];
break;
case 4:
GPIO_STEP->BRR |= step_round[a][1];
GPIO_STEP->BSRR |= step_round[a][3];
step_rem[a] = 0 ;
break;
}
}
void STEP_DOWN(int a)
{
if(step_rem[a] == 0)
{
step_rem[a] = 5;
}
switch(–(step_rem[a]))
{
case 1:
GPIO_STEP->BRR |= step_round[a][2];
GPIO_STEP->BSRR |= step_round[a][0];
step_rem[a] = 5;
break;
case 2:
GPIO_STEP->BRR |= step_round[a][3];
GPIO_STEP->BSRR |= step_round[a][1];
break;
case 3:
GPIO_STEP->BRR |= step_round[a][0];
GPIO_STEP->BSRR |= step_round[a][2];
break;
case 4:
GPIO_STEP->BRR |= step_round[a][1];
GPIO_STEP->BSRR |= step_round[a][3];
break;
}
}
void GPIO_INIT_FUNCTION(void)
{
RCC->APB2ENR |= RCC_APB2Periph_GPIOC;
RCC->APB2ENR |= RCC_APB2Periph_GPIOB;
RCC->APB2ENR |= RCC_APB2Periph_GPIOA;
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = X_STEP|X_DIR|Y_STEP|Y_DIR|Z_STEP|Z_DIR;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(Parallel, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = KEY_UP|KEY_DOWN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = KEY_RIGHT|KEY_LEFT|KEY_FRONT|KEY_BEHIND|X_LIM|Y_LIM|Z_LIM;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = STEP_A2|STEP_A1|STEP_B2|STEP_B1|STEP_C2|STEP_C1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
====================STEP.h=====================
#define KEY_PORT1 GPIOC
#define KEY_PORT2 GPIOA
#define KEY_RIGHT GPIO_Pin_8
#define KEY_LEFT GPIO_Pin_7
#define KEY_FRONT GPIO_Pin_6
#define KEY_BEHIND GPIO_Pin_5
#define KEY_UP GPIO_Pin_3
#define KEY_DOWN GPIO_Pin_1
#define Parallel GPIOB
#define X_STEP GPIO_Pin_9
#define Z_STEP GPIO_Pin_5
#define Y_STEP GPIO_Pin_8
#define X_DIR GPIO_Pin_7
#define Y_DIR GPIO_Pin_6
#define Z_DIR GPIO_Pin_2
#define LIM_PORT GPIOC
#define Y_LIM GPIO_Pin_11
#define Z_LIM GPIO_Pin_13
#define X_LIM GPIO_Pin_9
#define GPIO_STEP GPIOB
#define STEP_A1 GPIO_Pin_14
#define STEP_A2 GPIO_Pin_15
#define STEP_B1 GPIO_Pin_12
#define STEP_B2 GPIO_Pin_13
#define STEP_C1 GPIO_Pin_10
#define STEP_C2 GPIO_Pin_11
//위 6개가 오피엠프를 거쳐서 fet로 증폭되어 모터에 들어가는 부분입니다.
//각 모터당 A, B, C 로 나누어 놯습니다.
#define right 1
#define left 2
void GPIO_INIT_FUNCTION(void);
void CONTROL(int);
void P_CON(int);
void STEP_UP(int);
void STEP_DOWN(int);
====================main.c=====================
/****************************************************************
*
* Used with ICCARM and AARM.
*
* (c) Copyright IAR Systems 2007
*
* File name : main.c
* Description : Define main module
*
* History :
* 1. Date : 19, July 2006
* Author : Stanimir Bonev
* Description : Create
*
* This example project shows how to use the IAR Embedded Workbench
* for ARM to develop code for the IAR STM32-SK board.
* It implements USB CDC (Communication Device Class) device and install
* it like a Virtual COM port. UART3 is used for physical implementation
* of the RS232 port.
*
* Jumpers:
* PWR_SEL – depending of power source
*
* $Revision: 1.1.2.1 $
* www.elogics.co.kr
*****************************************************************/
#include “includes.h”
#define DATA_LOGGING
#include “step.h”
#include
#include
Int32U CriticalSecCntr;
void Clk_Init (void)
{
// 1. Clocking the controller from internal HSI RC (8 MHz)
RCC_HSICmd(ENABLE);
// wait until the HSI is ready
while(RCC_GetFlagStatus(RCC_FLAG_HSIRDY) == RESET);
RCC_SYSCLKConfig(RCC_SYSCLKSource_HSI);
// 2. Enable ext. high frequency OSC
RCC_HSEConfig(RCC_HSE_ON);
// wait until the HSE is ready
while(RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET);
// 3. Init PLL
RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9); // 8Mhz x 9 = 72MHz
RCC_PLLCmd(ENABLE);
// wait until the PLL is ready
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
// 4. Set system clock dividers
RCC_USBCLKConfig(RCC_USBCLKSource_PLLCLK_1Div5);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
RCC_PCLK2Config(RCC_HCLK_Div1);
RCC_PCLK1Config(RCC_HCLK_Div2);
RCC_HCLKConfig(RCC_SYSCLK_Div1);
#ifdef EMB_FLASH
// 5. Init Embedded Flash
// Zero wait state, if 0 < HCLK 24 MHz
// One wait state, if 24 MHz < HCLK 56 MHz
// Two wait states, if 56 MHz APB2ENR |= RCC_APB2Periph_USART1;
UART_Initialize(UART1,115200) ;
EXT_CRT_SECTION(); // Main IRQ Enable
UART1_putString(“complete setting..\r\n”);
uint32_t timeval = 10;
char cha[4];
char *str=cha;
GPIO_SetBits(GPIOB, GPIO_Pin_3);
GPIO_SetBits(GPIOB, GPIO_Pin_4);
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_13)==0)
{
GPIO_ResetBits(GPIOB, GPIO_Pin_4);
UART1_putString(“Select sw1, KEY Control..\r\n”);
for (int a =0;a<10000000;a++){}
while(1)
{
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_13)==0)
{
timeval += 1;
for (int a =0;a1)
{ timeval -= 1;}
for (int a =0;a<500000;a++){}
cha[0] = timeval/100+0×30;
cha[1] = (timeval%100)/10+0×30;
cha[2] = (timeval%10)+0×30;
cha[3] = ”;
UART1_putString(str);
UART1_putString(“000\r\n”);
}
else
{
CONTROL(0);
CONTROL(1);
CONTROL(2);
for(int hhh=0; hhh<timeval*1000;hhh++)
{}
}
}
}
else if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_15)==0)
{
GPIO_ResetBits(GPIOB, GPIO_Pin_3);
UART1_putString(“Select sw2, Parallel Control…\r\n”);
while(1)
{
P_CON(0);
P_CON(1);
P_CON(2);
}
}
else
{
UART1_putString(“sw1 : KEY control , sw2 : parallel port con….\r\n”);
while(1)
{
led_blink();
}
}
}
#ifdef DEBUG
/*******************************************************************************
* Function Name : assert_failed
* Description : Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* Input : – file: pointer to the source file name
* – line: assert_param error line source number
* Output : None
* Return : None
*******************************************************************************/
void assert_failed(u8* file, u32 line)
{
/* User can add his own implementation to report the file name and line number,
ex: printf(“Wrong parameters value: file %s on line %d\r\n”, file, line) */
/* Infinite loop */
while (1)
{
}
}
#endif
동영상
참고문헌
· arm cortex-m3 시스템 프로그래밍 완전정복 1,2
· MALVINO 전자회로 7판
· 열혈강의 c
회로도
우수상을 수상하신 광운대학교의 한재승님께 진심으로 축하의 말씀을 드립니다. 수상하신 팀에게는 적립금 25만원과 함께 JK전자에서 제공해드리는 소정의 경품이 제공되었습니다. 다음호에는 단국대학교 전기전자공학부 학술동아리 김재현 외 3명의 “돈돈”이 소개될 예정입니다.