ArduinoとRGBセンサーで作る高度な色選別マシン
必要なパーツと用品
| パーツ | 数量 |
|---|---|
| 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 DCジャック | 1 |
| 熱収縮チューブ | 1式 |
| デュポンコネクター | 1式 |
| 9Vまたは12V電源 (1A以上) | 1 |
| 試験管 22mm外径×100mm (Danmu Art製) | 5本 |
| スキットルズ (お菓子) | 1袋 |
必要な工具と機械
- 3Dプリンター
- ドライバー
- ワイヤーストリッパー
- ペンチ
- はんだごて
- マルチメーター
- 圧着工具
プロジェクト概要
このプロジェクトが生まれたきっかけは、実は偶然でした。約3年半前、もっと複雑な色選別マシンの設計を始めたものの、多くのプロジェクト同様、未完成のまま窓辺に放置されていました(誰も共感できないですよね、笑)。
数ヶ月前、この古いプロジェクトを再開することにしました。最初に解決すべきだったのは、貯蔵タンクからスキットルズを取り出し、確実に色を判別しながら、システムをできるだけコンパクトにする方法です(前回はこれに失敗しました)。「シンプルな色選別マシンを作って、供給システムの設計を正しくしよう」と思い立ったのが運の尽き。週末2回で終わると甘く考えていましたが、大間違いでした。
2つの全く異なる設計と多数のプロトタイプを経て、ようやく許容できるレベルで動作するものができました(それでも稀にピックアップの問題が発生します)。完璧ではありませんが、結果には満足しています。
動作原理
マシンは大きく2つのステージで構成されています。
第1ステージ:供給と色判別
上部には未選別のスキットルズを入れる貯蔵タンクがあります。その底部にある供給機構がスキットルズを1粒ずつ取り出し、カラーセンサーの前で位置決めして色を判別します。貯蔵タンク内の内容物を振動させるアームにより、スキットルズが詰まることなくスムーズに供給口へ流れるよう設計されています(それでも稀に詰まることはあります)。
第2ステージ:試験管への振り分け
色が判別されると、第2ステージが作動します。試験管ホルダーが正しい試験管を供給機構の前に移動させ、スキットルズを落とします。このサイクルを貯蔵タンクが空になるまで繰り返します。
自作するには?
詳細な製作ガイドと3Dプリント用ファイルは以下のサイトで公開しています:
https://www.littlefrenchkev.com/colour-sorting-machine
組み立て手順
3Dプリントしたパーツをすべて出力すると、以下のような部品が揃います。
組み立て動画も用意しています(スローモーションの3Dプリンター映像でイントロを作ってみました)。
初期設定
電源を入れると、まず両サーボが初期位置に移動します。初回起動時は、スキットルズを回収するギア/シャフトが最上部にあり、試験管ホルダーが供給機構と一直線になるよう調整してください。
トラブルシューティング
LEDが赤点滅して起動シーケンスが始まらない
初期位置移動後、起動シーケンスに入らずLEDが赤点滅する場合、RGBセンサーが検出されていません。配線の問題か、センサー自体の故障です。
試験管が正しく位置合わせされない
原因1:サーボの精度不足またはプリント寸法のばらつき
起動シーケンスで試験管ホルダーが5色分移動しますが、位置がズレることがあります。これはソフトウェア側で colour_angle 変数配列を調整することで補正可能です。各値が各色のサーボ位置に対応しています。
原因2:ギアのバックラッシュ(遊び)
プリント公差によりギアに遊びが生じ、慣性でオーバーシュートすることがあります。これも forward_anti_backlash と backward_anti_backlash 変数で補正できます。回転方向に応じて角度を加減算します。
色が混ざって判別される
colour_limit_values 配列を調整してください。各配列は赤・緑・青の最小・最大値を表します。調整方法:1色だけを機械に入れ、PCに接続してシリアルモニターを開きます。スキットルズが判別されるたびにRGB値と判定色が表示されるので、それを参考に閾値を設定します。
こだわりの機能たち
RGB LEDによる視覚フィードバック
ベース部分にRGB LEDを搭載。選別中は判別されたスキットルズの色に合わせて光り、貯蔵タンクが空になると全色を順繰りに表示します。起動時にセンサー異常があれば赤点滅で知らせます。
バックラッシュ補正機能
試験管ホルダーはスパーギアで駆動しますが、プリント公差で完全に遊びをなくすのは困難です。コード側で回転方向に応じて早めに停止することで、オーバーシュートを補正しています。
LEDスムーズ遷移付き遅延関数
set_led(ms, color) 関数は delay(ms) と同様に待機しますが、その間にLEDを滑らかに次の色へ変化させます。試験管ホルダーの回転中に色を変えるのに最適です。
補足ノート
- 黒色プリントの影響: 黒フィラメントでプリントすると、センサーが読み取る色値に影響します(特にスキットルズがない時)。
- スキットルズの劣化: 何度も選別を繰り返すと、表面の光沢が失われたり手の油でベタついたりして、色判別が不安定になります。校正には「新品」のスキットルズを使ってください。
- 食品安全: 食品を扱うため、食品安全性のあるフィラメントを使用し、衛生面に十分配慮してください。
- SNS: 制作過程や次回作の予告はInstagram(@little_french_kev)で公開しています。
ソースコード
LFK_sorting_machine (C/C++)
Arduino IDE用の完全なスケッチです。TCS34725ライブラリとServoライブラリが必要です。
#include <Wire.h>
#include "Adafruit_TCS34725.h"
#include <Servo.h>
// 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;
// 色判定閾値 [Rmin, Rmax, Gmin, Gmax, Bmin, Bmax]
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ピン (共通アノードなのでLOWで点灯)
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]);
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);
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.println(total_b);
// 色判定ロジック(閾値との比較)
if (total_r < black_limit_values[1] && total_g < black_limit_values[3] && total_b < black_limit_values[5]) { colour = 0; Serial.println("black"); }
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]) { colour = 1; Serial.println("yellow"); }
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]) { colour = 2; Serial.println("green"); }
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]) { colour = 3; Serial.println("orange"); }
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]) { colour = 4; Serial.println("red"); }
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]) { colour = 5; Serial.println("purple"); }
else { colour = 6; Serial.println("unknown"); }
}
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) {
holder_servo.write(new_holder_position - backward_anti_backlash);
} else {
holder_servo.write(new_holder_position + forward_anti_backlash);
holder_servo.write(new_holder_position);
}
// LED色変更と同期
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() {
feeder_servo.write(180); delay(release_delay);
feeder_servo.write(120); delay(80);
for (int i = 0; i <= 5; i++) {
feeder_servo.write(90); delay(80);
feeder_servo.write(180); delay(80);
}
}
void starting_sequence() {
for (int c = 1; c <= 5; c++) { colour = c; move_holder(); set_led(300, (c==1?yellow:c==2?green:c==3?orange:c==4?red:purple)); delay(400); }
previous_colour = 5; colour = 3; move_holder(); end_loop(200);
}
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);
}
// delay()の代わりにLEDを滑らかに変化させながら待機
void set_led(int duration, int color[3]) {
int start = millis();
while (millis() - start < duration) {
int elapsed = millis() - start;
analogWrite(R_pin, map(elapsed, 0, duration, current_red_value, color[0]));
analogWrite(G_pin, map(elapsed, 0, duration, current_green_value, color[1]));
analogWrite(B_pin, map(elapsed, 0, duration, current_blue_value, color[2]));
}
current_red_value = color[0]; current_green_value = color[1]; current_blue_value = color[2];
}
回路図
配線はシンプルです。バックコンバーターの出力が5Vになっていることを確認してから接続してください!
製造プロセス