산업 제조
산업용 사물 인터넷 | 산업자재 | 장비 유지 보수 및 수리 | 산업 프로그래밍 |
home  MfgRobots >> 산업 제조 >  >> Manufacturing Technology >> 제조공정

아두이노와 RGB 센서를 활용한 고급 색상 분류 머신

부품 및 소모품

Arduino Nano R31개
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봉

필수 도구 및 장비

프로젝트 소개

아두이노와 RGB 센서를 활용한 고급 색상 분류 머신

시작하며

재미있는 사실은 이 프로젝트가 애초에 계획된 것이 아니었다는 점입니다. 약 3년 반 전, 더 화려한 색상 분류 머신을 설계하기 시작했지만, 제 많은 프로젝트처럼 창가에 먼지만 쌓인 채 반쯤 완성된 상태로 방치되었습니다.

몇 달 전, 이 오래된 프로젝트를 다시 꺼내들었습니다. 가장 먼저 해결해야 할 문제는 저장소에서 스키틀즈를 수집하고, 색상을 신뢰성 있게 확인하면서 시스템을 최대한 컴팩트하게 유지하는 것이었습니다(첫 시도 때는 실패했습니다). 단순히 수집 시스템 설계를 검증하기 위해 더 단순한 색상 분류 머신을 만들기로 했습니다. 주말 이틀이면 끝날 줄 알았는데... 완전히 틀렸습니다.

완전히 다른 설계 2번과 수많은 프로토타입 수정을 거쳐 겨우 만족스러운 결과를 얻었습니다(여전히 가끔 픽업 문제가 발생합니다).

아두이노와 RGB 센서를 활용한 고급 색상 분류 머신
아두이노와 RGB 센서를 활용한 고급 색상 분류 머신
아두이노와 RGB 센서를 활용한 고급 색상 분류 머신

완벽하지 않지만 결과에 꽤 만족합니다.

작동 원리

머신은 크게 2단계로 구성됩니다.

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

아두이노와 RGB 센서를 활용한 고급 색상 분류 머신

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

직접 만들고 싶으신가요?

아두이노와 RGB 센서를 활용한 고급 색상 분류 머신

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

아두이노와 RGB 센서를 활용한 고급 색상 분류 머신

조립 과정

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

아두이노와 RGB 센서를 활용한 고급 색상 분류 머신

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

아두이노와 RGB 센서를 활용한 고급 색상 분류 머신
아두이노와 RGB 센서를 활용한 고급 색상 분류 머신
아두이노와 RGB 센서를 활용한 고급 색상 분류 머신

조립 과정 영상도 준비되어 있습니다(슬로모션 3D 프린터 샷으로 인트로 제작 시도!).

초기 설정

전원을 켜면 가장 먼저 두 서보가 초기 위치로 이동합니다. 최초 실행 시에는 기어/샤프트(스키틀즈 수집부)가 최상단 위치에 있고, 튜브 홀더가 공급 메커니즘과 정렬되어 있는지 확인해야 합니다.

아두이노와 RGB 센서를 활용한 고급 색상 분류 머신

문제 해결

직접 제작 시 자주 발생하는 문제들과 해결 방법입니다.

LED가 빨간색으로 깜빡이며 시작 시퀀스 미진행

초기 위치 이동 후 시작 시퀀스가 진행되지 않고 LED가 빨간색으로 깜빡이면 RGB 센서를 찾지 못한 것입니다. 배선 문제이거나 센서 불량입니다.

튜브가 제대로 정렬되지 않음

원인 1: 시작 시퀀스 중 튜브 홀더가 색상별로 5회 이동합니다. 서보 정밀도 부족이나 출력물 치수 차이로 정렬이 안 맞을 수 있습니다. 소프트웨어에서 colour_angle 배열 값을 수정해 각 색상별 정지 위치를 미세 조정하세요.

원인 2: 출력 공차로 인해 튜브 홀더 기어에 백래시(backlash)가 생길 수 있습니다. 회전 방향에 따라 관성으로 목표 위치를 넘어설 수 있는데, forward/backward_anti_backlash 변수로 보정 가능합니다.

색상 혼동

아두이노와 RGB 센서를 활용한 고급 색상 분류 머신

색상 혼동 시 colour_limit_values 배열을 수정하세요. 각 배열은 R, G, B의 최소/최댓값을 나타냅니다. 한 색상만 넣고 시리얼 모니터를 열면 실측 RGB 값과 프로그램 판단 색상을 확인할 수 있어 캘리브레이션에 도움이 됩니다.

주요 특징

RGB LED: 머신 바닥에 장착된 RGB LED는 분류 중인 스키틀즈 색상에 맞춰 색이 변하며, 저장소가 비면 모든 색상을 순환 표시합니다. 센서 이상 시 시작할 때 빨간색으로 점멸합니다.

백래시 보상: 스퍼 기어로 튜브 홀더를 회전시키는데, 출력 공차로 인한 유격을 소프트웨어적으로 보상합니다. 회전 방향에 따라 목표 위치보다 약간 일찍/늦게 정지하도록 합니다.

딜레이 중 LED 전환: set_led(ms, color) 함수는 delay(ms)처럼 작동하면서 LED 색상을 부드럽게 전환합니다. 튜브 홀더가 회전하는 동안 LED 색상을 바꾸는 데 유용합니다.

추가 참고사항

아두이노와 RGB 센서를 활용한 고급 색상 분류 머신

코드

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인지 반드시 확인한 후 연결하세요!

아두이노와 RGB 센서를 활용한 고급 색상 분류 머신

제조공정

  1. 뇌파계(EEG): 설계, 제조, 새로운 응용 분야
  2. 자판기: 366억 달러 규모의 미국 거대 산업
  3. 재봉틀의 진화와 제조: 19세기 혁신에서 현대 자동화까지
  4. 텔레비전: 기계식 기원에서 스마트 미래까지
  5. 스핀들 베어링: 정밀도와 품질 보증
  6. 선반 머신 필수 부품: 기능 및 개요
  7. 선반 머신 이해: 역사, 종류 및 용도
  8. 밀링 머신 핵심 구성 요소 상세 해설
  9. 기계란 무엇인가? 정의, 작동 원리, 메커니즘 총정리
  10. JW Machine: 정밀 CNC 계약 제조 서비스