제조공정
| Arduino Nano R3 | 1개 |
| TCS34725 RGB 센서 | 1개 |
| LM2596 벅 컨버터 | 1개 |
| MG-90S 서보 모터 | 2개 |
| MR105ZZ 볼 베어링 (5×10×4mm) | 3개 |
| 608RS 또는 608ZZ 볼 베어링 (8×22×7mm) | 1개 |
| 6003RS 볼 베어링 (17×35×10mm) | 1개 |
| M3×15mm 카운터싱크 나사 | 1개 |
| M2×10 셀프 태핑 나사 | 20개 |
| RGB LED (공통 애노드) | 1개 |
| 220Ω 저항 | 3개 |
| 전선 | 1세트 |
| 2.1×5.5mm 잭 플러그 | 1개 |
| 수축 튜브 | 1개 |
| 듀폰 커넥터 | 1세트 |
| 9V 또는 12V 파워 서플라이 (최소 1A) | 1개 |
| 5개 테스트 튜브 (22mm OD × 100mm, Danmu Art) | 1세트 |
| 스키틀즈 | 1봉 |

재미있는 사실은 이 프로젝트가 애초에 계획된 것이 아니었다는 점입니다. 약 3년 반 전, 더 화려한 색상 분류 머신을 설계하기 시작했지만, 제 많은 프로젝트처럼 창가에 먼지만 쌓인 채 반쯤 완성된 상태로 방치되었습니다.
몇 달 전, 이 오래된 프로젝트를 다시 꺼내들었습니다. 가장 먼저 해결해야 할 문제는 저장소에서 스키틀즈를 수집하고, 색상을 신뢰성 있게 확인하면서 시스템을 최대한 컴팩트하게 유지하는 것이었습니다(첫 시도 때는 실패했습니다). 단순히 수집 시스템 설계를 검증하기 위해 더 단순한 색상 분류 머신을 만들기로 했습니다. 주말 이틀이면 끝날 줄 알았는데... 완전히 틀렸습니다.
완전히 다른 설계 2번과 수많은 프로토타입 수정을 거쳐 겨우 만족스러운 결과를 얻었습니다(여전히 가끔 픽업 문제가 발생합니다).



완벽하지 않지만 결과에 꽤 만족합니다.
머신은 크게 2단계로 구성됩니다.
1단계: 공급 메커니즘 (상단)
상단에는 미분류 스키틀즈 저장소가 있고, 바닥에는 공급 메커니즘이 있어 스키틀즈를 수집해 컬러 센서 앞에 위치시킵니다. 공급 메커니즘에는 저장소 내용을 흔들어 스키틀즈가 공급구로 원활히 흐르도록 하는 암(arm)이 포함되어 있습니다(아직 가끔 걸리기도 합니다).

2단계: 테스트 튜브 홀더 (하단)
스키틀즈 색상이 확인되면, 테스트 튜브 홀더가 올바른 튜브를 공급 메커니즘 앞에 정렬해 스키틀즈가 떨어지도록 합니다. 이 사이클은 저장소가 빌 때까지 반복됩니다.

상세 제작 가이드: littlefrenchkev.com/colour-sorting-machine

파일을 다운로드해 출력하면 다음과 같은 부품들을 얻습니다.

프론트 쉘은 2가지 버전이 있으며, 기능상 차이는 없고 외관만 다릅니다.



조립 과정 영상도 준비되어 있습니다(슬로모션 3D 프린터 샷으로 인트로 제작 시도!).
전원을 켜면 가장 먼저 두 서보가 초기 위치로 이동합니다. 최초 실행 시에는 기어/샤프트(스키틀즈 수집부)가 최상단 위치에 있고, 튜브 홀더가 공급 메커니즘과 정렬되어 있는지 확인해야 합니다.

직접 제작 시 자주 발생하는 문제들과 해결 방법입니다.
초기 위치 이동 후 시작 시퀀스가 진행되지 않고 LED가 빨간색으로 깜빡이면 RGB 센서를 찾지 못한 것입니다. 배선 문제이거나 센서 불량입니다.
원인 1: 시작 시퀀스 중 튜브 홀더가 색상별로 5회 이동합니다. 서보 정밀도 부족이나 출력물 치수 차이로 정렬이 안 맞을 수 있습니다. 소프트웨어에서 colour_angle 배열 값을 수정해 각 색상별 정지 위치를 미세 조정하세요.
원인 2: 출력 공차로 인해 튜브 홀더 기어에 백래시(backlash)가 생길 수 있습니다. 회전 방향에 따라 관성으로 목표 위치를 넘어설 수 있는데, forward/backward_anti_backlash 변수로 보정 가능합니다.

색상 혼동 시 colour_limit_values 배열을 수정하세요. 각 배열은 R, G, B의 최소/최댓값을 나타냅니다. 한 색상만 넣고 시리얼 모니터를 열면 실측 RGB 값과 프로그램 판단 색상을 확인할 수 있어 캘리브레이션에 도움이 됩니다.
RGB LED: 머신 바닥에 장착된 RGB LED는 분류 중인 스키틀즈 색상에 맞춰 색이 변하며, 저장소가 비면 모든 색상을 순환 표시합니다. 센서 이상 시 시작할 때 빨간색으로 점멸합니다.
백래시 보상: 스퍼 기어로 튜브 홀더를 회전시키는데, 출력 공차로 인한 유격을 소프트웨어적으로 보상합니다. 회전 방향에 따라 목표 위치보다 약간 일찍/늦게 정지하도록 합니다.
딜레이 중 LED 전환: set_led(ms, color) 함수는 delay(ms)처럼 작동하면서 LED 색상을 부드럽게 전환합니다. 튜브 홀더가 회전하는 동안 LED 색상을 바꾸는 데 유용합니다.

LFK_sorting_machine (C/C++)
#include <Wire.h>
#include "Adafruit_TCS34725.h"
#include <Servo.h>
/* Adafruit TCS34725 브레이크아웃 라이브러리 예제 코드 기반 */
// RGB 센서 관련 변수
Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_24MS, TCS34725_GAIN_1X);
int colour = 0; // 0-검정, 1-노랑, 2-초록, 3-주황, 4-빨강, 5-보라
int previous_colour = colour;
int black_count = 0;
int start_measurment_angle = 88;
int measurment_sweep = 2;
int measurment_angle_increment = 4;
// 색상별 임계값 {min_R, max_R, min_G, max_G, min_B, max_B}
uint16_t black_limit_values[] = {0, 200, 0, 200, 0, 200};
uint16_t yellow_limit_values[] = {850, 2200, 800, 2200, 400, 1100};
uint16_t green_limit_values[] = {400, 800, 700, 1400, 300, 700};
uint16_t orange_limit_values[] = {650, 1800, 300, 850, 210, 600};
uint16_t red_limit_values[] = {400, 950, 150, 450, 150, 400};
uint16_t purple_limit_values[] = {150, 400, 150, 450, 150, 500};
// RGB LED 관련 변수
byte R_pin = 5, G_pin = 6, B_pin = 11;
int current_red_value = 255, current_green_value = 255, current_blue_value = 255;
const int orange[] = {0, 230, 255};
const int red[] = {0, 255, 255};
const int green[] = {255, 0, 255};
const int yellow[] = {0, 170, 255};
const int purple[] = {0, 255, 40};
// 피더 서보 관련 변수
int release_delay = 220;
const int colour_angles[] = {10, 46, 82, 118, 154};
const int backward_anti_backlash = 2;
const int forward_anti_backlash = 2;
Servo feeder_servo;
Servo holder_servo;
void setup() {
Serial.begin(9600);
pinMode(R_pin, OUTPUT);
pinMode(G_pin, OUTPUT);
pinMode(B_pin, OUTPUT);
analogWrite(R_pin, 255);
analogWrite(G_pin, 255);
analogWrite(B_pin, 255);
feeder_servo.attach(3);
holder_servo.attach(2);
feeder_servo.write(0);
holder_servo.write(colour_angles[0]);
int white[] = {0, 0, 0};
int black[] = {255, 255, 255};
set_led(1000, white);
if (tcs.begin()) {
Serial.println("Found sensor");
starting_sequence();
} else {
Serial.println("No TCS34725 found ... check your connections");
while (1) {
set_led(300, red);
set_led(300, black);
}
}
}
void loop() {
if (black_count < 10) {
feeder_servo.write(0);
delay(450);
previous_colour = colour;
get_colour();
if (colour == 0) {
black_count++;
shake();
} else if (colour == 6) {
black_count++;
} else {
move_holder();
release_skittle();
black_count = 0;
}
} else {
end_loop(1000);
}
}
// 색상 감지 함수
void get_colour() {
uint16_t r, g, b, c;
uint16_t total_r = 0, total_g = 0, total_b = 0;
feeder_servo.write(start_measurment_angle);
delay(200);
Serial.println("----------------");
for (int i = 0; i <= measurment_sweep; i++) {
tcs.getRawData(&r, &g, &b, &c);
feeder_servo.write(start_measurment_angle + i * measurment_angle_increment);
total_r += r;
total_g += g;
total_b += b;
delay(15);
}
total_r /= measurment_sweep;
total_g /= measurment_sweep;
total_b /= measurment_sweep;
Serial.print(total_r); Serial.print(" ");
Serial.print(total_g); Serial.print(" ");
Serial.print(total_b); Serial.println(" ");
// 색상 판별 로직
if ((total_r < black_limit_values[1]) && (total_g < black_limit_values[3]) && (total_b < black_limit_values[5])) {
Serial.println("black"); colour = 0;
} else if ((total_r >= yellow_limit_values[0]) && (total_r < yellow_limit_values[1]) &&
(total_g >= yellow_limit_values[2]) && (total_g < yellow_limit_values[3]) &&
(total_b >= yellow_limit_values[4]) && (total_b < yellow_limit_values[5])) {
Serial.println("yellow"); colour = 1;
} else if ((total_r >= green_limit_values[0]) && (total_r < green_limit_values[1]) &&
(total_g >= green_limit_values[2]) && (total_g < green_limit_values[3]) &&
(total_b >= green_limit_values[4]) && (total_b < green_limit_values[5])) {
Serial.println("green"); colour = 2;
} else if ((total_r >= orange_limit_values[0]) && (total_r < orange_limit_values[1]) &&
(total_g >= orange_limit_values[2]) && (total_g < orange_limit_values[3]) &&
(total_b >= orange_limit_values[4]) && (total_b < orange_limit_values[5])) {
Serial.println("orange"); colour = 3;
} else if ((total_r >= red_limit_values[0]) && (total_r < red_limit_values[1]) &&
(total_g >= red_limit_values[2]) && (total_g < red_limit_values[3]) &&
(total_b >= red_limit_values[4]) && (total_b < red_limit_values[5])) {
Serial.println("red"); colour = 4;
} else if ((total_r >= purple_limit_values[0]) && (total_r < purple_limit_values[1]) &&
(total_g >= purple_limit_values[2]) && (total_g < purple_limit_values[3]) &&
(total_b >= purple_limit_values[4]) && (total_b < purple_limit_values[5])) {
Serial.println("purple"); colour = 5;
} else {
Serial.println("unknown"); colour = 6;
}
}
// 튜브 홀더 이동 (백래시 보상 포함)
void move_holder() {
int new_holder_position = colour_angles[colour - 1];
int holder_delay = (abs(previous_colour - colour) * 70);
if (holder_delay < 0) holder_delay = 0;
if (previous_colour > colour) {
int anti_backlash_angle = new_holder_position - backward_anti_backlash;
holder_servo.write(anti_backlash_angle);
} else {
int anti_backlash_angle = new_holder_position + forward_anti_backlash;
holder_servo.write(new_holder_position);
}
if (colour == 1) set_led(holder_delay, yellow);
else if (colour == 2) set_led(holder_delay, green);
else if (colour == 3) set_led(holder_delay, orange);
else if (colour == 4) set_led(holder_delay, red);
else if (colour == 5) set_led(holder_delay, purple);
}
// 스키틀즈 배출
void release_skittle() {
feeder_servo.write(180);
delay(release_delay);
}
// 저장소 흔들기
void shake() {
int shake_delay = 80;
int shake_amount = 5;
int shake_min_value = 90;
int shake_max_value = 180;
feeder_servo.write(180);
delay(release_delay);
feeder_servo.write(120);
delay(80);
for (int i = 0; i <= shake_amount; i++) {
feeder_servo.write(shake_min_value);
delay(shake_delay);
feeder_servo.write(shake_max_value);
delay(shake_delay);
}
}
// 시작 시퀀스
void starting_sequence() {
colour = 1; move_holder(); set_led(300, yellow); delay(400);
colour = 2; move_holder(); set_led(300, green); delay(400);
colour = 3; move_holder(); set_led(300, orange); delay(400);
colour = 4; move_holder(); set_led(300, red); delay(400);
colour = 5; move_holder(); set_led(300, purple); delay(400);
previous_colour = colour;
colour = 3; move_holder(); end_loop(200);
}
// 종료 루프 (LED 색상 순환)
void end_loop(int duration) {
set_led(duration, orange);
set_led(duration, red);
set_led(duration, green);
set_led(duration, yellow);
set_led(duration, purple);
}
// LED 색상 전환 (딜레이 기능 포함)
void set_led(int duration, int color[3]) {
int start_time = millis();
int current_time = start_time;
int current_duration = 0;
while (current_duration < duration) {
analogWrite(R_pin, map(current_duration, 0, duration, current_red_value, color[0]));
analogWrite(G_pin, map(current_duration, 0, duration, current_green_value, color[1]));
analogWrite(B_pin, map(current_duration, 0, duration, current_blue_value, color[2]));
current_time = millis();
current_duration = current_time - start_time;
}
current_red_value = color[0];
current_green_value = color[1];
current_blue_value = color[2];
}
배선은 매우 직관적입니다. 벅 컨버터 출력이 5V인지 반드시 확인한 후 연결하세요!

제조공정
밀링 머신은 비대칭 공작물 가공에 최적화된 만능 공작기계로, 금형 제조 핵심 장비입니다. 용도별로 다양하게 진화해왔으며, 주요 분류 체계를 정리합니다. 1. 컬럼-니 타입 (Column & Knee Type) - 가장 보편적 베이스에 수직 컬럼, 기어 드라이브 내장. 니(Knee)가 상하 이동, 새들(Saddle)이 횡이송, 테이블이 종이송·클램핑. 수직 밀링 머신 (Vertical Milling Machine) 스핀들 수직 배치. 아보어 불필요. 원통형 커터(페이스밀, 엔드밀) 사용. 금형, 금형 베이스, 평면/슬롯/포켓 가공에
연삭(Grinding)은 연마입자(Abrasive grains)가 결합된 연삭휠(Grinding wheel)로 공작물 표면을 미세 절삭하여 고정밀 치수, 미세 면조도, 고경도 소재 가공을 실현하는 정밀 마무리 공정입니다. 선반, 밀링 등 1차 가공 후 최종 치수/조도 확보용 2차 가공으로 필수적입니다. 연삭기 4대 기본 분류 (Conventional Grinding Machines) 1. 평면 연삭기 (Surface Grinding Machine) 평평한 면 가공 주목적. 성형 연삭휠 사용 시 곡면/단면도 가공 가능. 주축