Final Project¶
πΉ My purpose¶
My original idea, since the start of FabAcademy journey, was to do a color sorting machine for bottle caps. I had this desire because I work in a studio-fablab and since March 2020 we have collected 3d printed PLA scraps from other labs and universities in Milan to recycle them. This initiative, which is very cool, is also time consuming in one specific moment: the division and sorting of the scraps by colour, which you can see in the following video.
We also collect plastic bottles caps that we then shred and we divide these by colour, too. My purpose is to change this process, designing an Automatic Cap Color Sorter Machine, or for short, ACCSM! The idea is to start with the caps and not with the scraps because the caps have a similar size and shape, while the scraps can be big, small, squared, triangular, etc: the shape cannot be controlled and therefore the useful space cannot be designed.
πΆβπ«οΈ Research¶
On the market, there are a lot of industrial colour sorters and their functioning is quite sofisticated. They are able to sort even super small particles of plastic, in a huge amount of other colors. However, the maker world has found solutions for the color sorting problem, that may not concern only the bottle caps. I guess that people like Skittle to be divided by colour (KIDDING) since there are a lot of Arduino projects that devide the skitles by colour.
What I find interesting in these projects are the structure solutions, for example the fact that the Skittles are collected in a circular frame. I see the perks of the design behind the circular structure:
-
you need two motors
-
everything is “condensed” in less space
Even if I saw the potentials in these decisions, I decided to go on with another design that could be more similar to a game like a flipper, e.i.
π₯Έ Planning vs reality¶
Week | Plan | Reality |
---|---|---|
Principles and Practices | Sketch | Sketch |
Computer Aided Design | 3d models | 3d models done, then changed |
Computer Controlled Cutting | Laser the structure | I decided to change the model, I did not laser the structure |
3D Scanning and Printing | print part of the structure | I printed a vase |
Electronics Design | Design a board with a button to learn how it works | Board designed, then changed and designed with servo motors |
Embedded Programming | Control the servo | Controlled the servo |
Output Devices | Control the servo | Controlled the servo |
Input Devices | Test color sensors | Potentiomenter and servo |
Networking & Communications | Make communicate a board with servos with one with color sensor | Which boards? |
Project Development | Put everything together | Somehow managed to |
πͺ± Functions¶
ποΈ Steps¶
π Principles and Practices¶
I started off by this sketch I did by hand:
The ACCSM should have a funnel to insert the caps in. Right after the funnel there could be a sensor capable of reading the colour of the caps, and based on the response of the sensor, the caps will go in a a direction or in another. Here there is a rough sketch of the functioning of the machine, altouhg the shape is not defined yet. Going on I did a more accurate, yet still vague, sketch of the machine: there will be 8 colours identified, which are the most present in the caps we collect. The boxed colours will be the containers of the sorted caps.
π½ Computer Aided Design¶
The previous week I sketched on the machine I want to create. So I started the design from this 2D sketch. I designed this sketch using Figma, a collaborative web application for interface design.
I then proceeded to use Fusion360 to model the strucure of my machine. I started off with the tree, since I wanted to 3d print it. I the modeled accordingly the back of the structure and made holes for the servomotors.
Done this, I had the majority of the structure ready to be printed or lasered.
π½οΈ Computer controlled cutting¶
Even if I did not do it during the computer controlled cutting week, for this part I did the structure in wood of my machine. I had already designed the structure in Fusion360 and I then exported the sketch as a dxf.
With that dxf I could open Rhinoceros and laser cut it a piece of MDF. The laser cutting was easy since I only had to cut the borders. I did some tests before laser cutting everything because I had to be sure that the kerf was nicely calculated.
Luckily, I managed to laser it perfectly and I had the prove of that because the pieces I 3dprinted fitted perfectly.
π¦ 3d printing¶
During this week, I could focus on the project but I decided to 3dprint a vase for my bonsai tree. It was not a problem because I could print the pieces whenever I wanted, since I have the machines in my studio.
I decided to 3d model a tree structure for the sorting part of the machine and to 3d print it in order to have an assemblable and jointable structure. Indeed the sorting walls are a few tenth of a millimiter smaller than the holes in the MDF.
After modeling the pieces, I exported them in stl and opened them in Simplify3D. I printed them with a 0.4mm nozzle and with a layer height of 0.2mm.
I printed the pieces with a Delta Wasp 2040 in transparent PETG because I wanted the shiniest effect.
𦧠Electronics design¶
For this part, I designed two boards: one for the servo motors and one for the color sensor. Let’s start off with the servo board.
π ESP32¶
In this board I needed enogh pins to have:
- 7 servo motors;
- one IR sensors;
- one switch;
- a voltage regulator;
- diode;
- led;
- photoresistor;
Therefore, my FABolous instructor suggested me to use an ESP32. I started off by searching the ESP32 datasheet and pinout.
I created a new project in EAGLE, and I started to add the components I needed as it follows:
- For each servo: CONN_031X03_NO_SILK
- For the IR sensors: CONN_021X02_NO_SILK
- 2 switches: SW_SWITCH_TACTILE_6MM
- An FTDI connector: CONN_06_FTDI-SMD-HEADER
- Voltage regulator: REGULATOR_SOT223
- One slide switch
- Diode
- Led: LEDFAB1206
- Photoresistor
Since the ESP32 has 3.3V power and the servos need 5V I needed also two different VCC signals in my schematic. For the servos I also decided to connect them in a line of GND and VCC so that I could have a better organisation of space.
Once the schematic was ready, I went onto the board and with a lot of patience managed to route everything.
π¦ Electronic production¶
I exported the board from EAGLE and proceeded in doing as I explained in Electronic Production. Here some passages:
- I went on FabModules and separated the process in two because the esp32 traces needed the traces to be thinner.
- I milled the board:
This is the board milled and cleaned up.
I started off collecting all the components I needed for my board.
I then started to solder the ESP32. ESP32 is the most fun and easy to solder microcontroller. KIDDING.
It is pure hell. But eventually I managed to solder everything. After three tries.
πΏοΈ Embedded programming¶
π¬ TinkerCAD¶
For embedded programming section, I started off on TinkerCAD. I started to do a simulation with Arduino UNO, a servo and a photoresistor as you can see in the following image.
I then wrote a code that made the servo move accordingly to the value that the photoresistor senses.
Here is the code:
#include <Servo.h>
int lightval;
int lightpin = A0;
int tm = 100;
int servopin = 9;
Servo myservo;
int angle;
void setup() {
Serial.begin(9600);
pinMode(lightpin, INPUT);
myservo.attach(servopin);
pinMode(servopin, OUTPUT);
}
void loop() {
lightval = analogRead(lightpin);
Serial.println(lightval);
delay(tm);
angle = lightval/5;
myservo.write(angle);
Serial.println("angle is");
Serial.prinln(angle);
}
I leveled the game up by adding two photoresistors to simulate the R, G and B values.
π¦ Input devices¶
For the input devices part, I focused of course on the color sensor. To start off, I used Adafruit Adafruit_TCS34725 color sensor.
π¦ Color sensor¶
π‘ Adafruit_TCS34725¶
Adafruit_TCS34725 is a board with the TCS34725, the actual color sensor, which has RGB and Clear light sensing elements. An IR blocking filter, integrated on-chip and localized to the color sensing photodiodes, minimizes the IR spectral component of the incoming light and allows color measurements to be made accurately. The filter means you’ll get much truer color than most sensors, since humans don’t see IR. The sensor also has an incredible 3,800,000:1 dynamic range with adjustable integration time and gain so it is suited for use behind darkened glass.
πΌ Testing the sensor¶
Research and experimentation was necessary to understand the correct functioning of the colour sensor. First of all, a treasure hunt with my beloved little friend William began in the laboratory in search of the most diverse objects that met certain characteristics. Same colour but different shapes and transparencies, but also different colours and similar shapes.
And it was here that things started to get serious. By scanning and analysing each object at different distances, it was possible to compile a table of values that described as accurately as possible what the sensor was seeing. At this point I was in possession of a fairly accurate classification of the colours sensed by the sensor.
These tests led to the realisation that the RGB colour method was not the most correct for describing my objects and that I therefore had to find another way for this.
π¦ Color theory¶
HSV is a cylindrical color model that remaps the RGB primary colors into dimensions that are easier for humans to understand. Like the Munsell Color System, these dimensions are hue, saturation, and value. Hue specifies the angle of the color on the RGB color circle.
The HSV model defines color perception in a way that is close to how the human eye usually does. In terms of a mixture of primary colors, RGB describes color. The HSV color model is frequently favored over the RGB model in circumstances where color description is crucial.
The terms “Hue,” “Saturation,” and “Value” denote the color, the proportion of that color to white in each case, and the proportion of that color to black in each case (Gray level).
We are unable to distinguish brightness from color information in RGB. To separate the color and brightness information in an image, utilize HSV, or hue saturation value.
We choose HSV over RGB or BGR for color recognition and thresholding because HSV is more resistant to changes in ambient lighting. This indicates that hue values shift significantly less than RGB values when there are slight variations in the ambient lighting (such as pale shadows, etc.).
Two tints of red, for instance, could have comparable Hue values but vastly differing RGB values. We must do everything possible to ensure that our application functions properly regardless of environmental changes in real-world circumstances, such as object tracking based on color. Therefore, HSV colour thresholding is preferred over RGB.
As the project involves the use of caps of different transparencies and colours, the need to be able to identify colours as consequential ranges based on an initial pilot value (hue) was decisive in the decision to use this reference system as opposed to RGB.
π The code¶
Given all the things I said above, for the final I realised I had to convert the RGB values to HSV values. To do this, I created a function called RGBtoHSV as follows:
void RGBtoHSV(int RGBColor[], int HSVColor[]) {
double color[3];
double Cmax, Cmin, delta;
double hue, sat, val;
double hueAngle, satPerc, valPerc;
color[0] = RGBColor[0];
color[1] = RGBColor[1];
color[2] = RGBColor[2];
color[0] /= 255;
color[1] /= 255;
color[2] /= 255;
Cmax = arrayMax(color, 3);
Cmin = arrayMin(color, 3);
delta = Cmax - Cmin;
if (delta == 0) {
hue = 0;
}
if (Cmax == color[0]) {
hue = fmod(60 * (((color[1] - color[2]) / delta) + 360), 360);
if (color[1] - color[2] < 0) {
hue += 255;
}
}
if (Cmax == color[1]) {
hue = fmod(60 * (((color[2] - color[0]) / delta) + 120), 360);
}
if (Cmax == color[2]) {
hue = fmod(60 * (((color[0] - color[1]) / delta) + 240), 360);
}
if (Cmax == 0) {
sat = 0;
} else {
sat = (delta / Cmax) * 100;
}
val = Cmax * 100;
HSVColor[0] = (int)hue;
HSVColor[1] = (int)sat;
HSVColor[2] = (int)val;
}
Moreover, I created a tridimensional array to put my calibrated values for the color I had. The array is composed by 8 colors, in which I have a maximum and a minimum value and three values for each max and min.
const double calibratedvalues[8][2][3] = {
{
{
//RED
{ 0.000, 81.23, 73.87 },
{ 117.7, 105.9, 108.4 },
},
{
//ORANGE
{ 12.00, 60.00, 86.69 },
{ 35.67, 120.5, 120.5 },
},
{
//YELLOW
{ 38.01, 87.88, 70.59 },
{ 62.54, 148.5, 149.4 },
},
{
//GREEN
{ 97.11, 108.6, 57.48 },
{ 108.1, 130.0, 62.52 },
},
{
//BLUE
{ 126.2, 110.0, 36.63 },
{ 142.1, 200.0, 58.03 },
},
{
//WHITE TO DO
{ 0.000, 81.23, 73.87 },
{ 117.7, 105.9, 108.4 },
},
{
//BLACK. TO DO
{ 0.000, 81.23, 73.87 },
{ 117.7, 105.9, 108.4 },
},
{
//PINK. TO DO
{ 0.000, 81.23, 73.87 },
{ 117.7, 105.9, 108.4 },
},
};
}
For the sensing section, I wrote this code for my sensor that collects data in rgb format and then there is the convertion I talked about before.
void measureColor(int measuredColor[]) {
float r, g, b;
uint16_t red, green, blue, clear;
uint32_t sum;
// Get raw data and store it in variables
tcs.getRawData(&red, &green, &blue, &clear);
// Do calculations to convert the measured values to adjusted 0-255 RGB values
sum = clear;
r = red;
g = green;
b = blue;
r /= sum;
g /= sum;
b /= sum;
r *= 256;
g *= 256;
b *= 256;
measuredColor[0] = r;
measuredColor[1] = g;
measuredColor[2] = b;
}
Color sensor pcb¶
I also produced a color sensor pcb for my project very similar to the Adafruit TCTCS34725. Here there are the traces, output and holes files:
π Output devices¶
πββ¬ Servos¶
To understand the whole logic of the code, I did a step back and used an ArduinoUno and a breadboard power supply to power the servos.
To connect the servos, I crimped seven wires (actually 42 crimpes) and numbered them in groups of 3 to have clear which servo is which.
I wrote a code that could help me in debugging and understanding the right angles for each color. Here it is:
#include <Servo.h>
#define SERVO_AMOUNT 7
Servo servo[SERVO_AMOUNT];
int servo_pin[SERVO_AMOUNT] = {9, 10, 6, 11, 12, 3, 5};
int angle_init[SERVO_AMOUNT] = {70, 70, 70, 70, 70, 70, 70};
int pos;
const int d = 50;
const int s = 150;
enum Colors { RED, ORANGE, YELLOW, GREEN, BLUE, WHITE, BLACK, PINK, END }; //i percorsi x colore
int rotations[Colors::END][SERVO_AMOUNT] = { //dico ai servo che rotazione fare per ogni Colors
{s, s, d, s, d, d, d},
{s, s, d, d, d, d, d},
{s, d, d, d, s, d, d},
{s, d, d, d, d, d, d},
{d, s, s, d, d, s, d},
{d, s, s, d, d, d, d},
{d, d, d, s, d, d, s},
{d, d, d, s, d, d, d},
};
struct Pattern { //associa la rotazione al colore
int* rotation;
Colors color;
};
Pattern p[Colors::END];
void setup() {
Serial.begin(9600);
Serial.println("Daje");
for (short i = 0; i < Colors::END; i++) { //associo pattern alle rotazioni
p[i].rotation = &rotations[i][0];
}
for (int i = 0; i < SERVO_AMOUNT; i++) {
pinMode(servo_pin[i], OUTPUT);
servo[i].attach(servo_pin[i]);
servo[i].write(angle_init[i]); //posizione iniziale
}
}
void loop() {
if (Serial.available()) {
String state = Serial.readString();
int i = int(state[0]) - 48;
if (i < Colors::END) {
for (short k = 0; k < SERVO_AMOUNT; k++) {
servo[k].write(p[i].rotation[k]);
}
}
else {
Serial.println("Ma che stai a fa'?");
}
}
}
Once the breadboard supply was connected with jumpers to the Arduino (5V to the 5V and GND with GND) and all the servos were connected (the VCC to the + of the breadboard supply and the GND to the - of the breadboard) I used a power supply with 9V and 2000mA to power the power supply.
There were some problems and then saw that the ams1117 of the power supply was FUSING. We decided to do some math:
- Arduino needs 250mA.
- Each servo needs ~170mA (170*7 = 1190mA).
This means that the overall consuption is of 1440mA. We saw on the datasheet of the ams1117 that it has an output of 800mA. So of course it was overloaded.
I opted on using a DIN power supply then and it actually worked. So I got back to Embedded programming to close the code.
π The whole code¶
#include <Servo.h>
#include "Adafruit_TCS34725.h"
#include "functions.h"
#define SERVO_AMOUNT 7
Servo servo[SERVO_AMOUNT];
int servo_pin[SERVO_AMOUNT] = { 9, 10, 6, 11, 12, 3, 5 };
int angle_init[SERVO_AMOUNT] = { 70, 70, 70, 70, 70, 70, 70 };
int pos;
const int d = 50;
const int s = 150;
enum Colors { RED,
ORANGE,
YELLOW,
GREEN,
BLUE,
WHITE,
BLACK,
PINK,
END }; //i percorsi x colore
int rotations[Colors::END][SERVO_AMOUNT] = {
//dico ai servo che rotazione fare per ogni Colors
{ s, s, d, s, d, d, d },
{ s, s, d, d, d, d, d },
{ s, d, d, d, s, d, d },
{ s, d, d, d, d, d, d },
{ d, s, s, d, d, s, d },
{ d, s, s, d, d, d, d },
{ d, d, d, s, d, d, s },
{ d, d, d, s, d, d, d },
};
struct Pattern { //associa la rotazione al colore
int* rotation;
Colors color;
};
Pattern p[Colors::END];
Adafruit_TCS34725 tcs = Adafruit_TCS34725(); //inizializza il sensore colore
void setup() {
Serial.begin(9600);
Serial.println("Daje");
for (short i = 0; i < Colors::END; i++) { //associo pattern alle rotazioni
p[i].rotation = &rotations[i][0];
}
for (int i = 0; i < SERVO_AMOUNT; i++) {
pinMode(servo_pin[i], OUTPUT);
servo[i].attach(servo_pin[i]);
servo[i].write(angle_init[i]); //posizione iniziale
}
/************* Color Sensor ************************/
if (tcs.begin()) { //vedere se il sensore Γ¨ connesso
Serial.println("Found sensor");
} else {
Serial.println("No TCS34725 found ... check your connections");
while (1)
;
}
int rgb[3] = { 255, 0, 0 };
int hsv[3];
RGBtoHSV(rgb, hsv);
}
void loop() {
if (Serial.available()) {
String state = Serial.readString();
int i = int(state[0]) - 48;
if (i < Colors::END) {
for (short k = 0; k < SERVO_AMOUNT; k++) {
servo[k].write(p[i].rotation[k]);
}
} else {
Serial.println("Ma che stai a fa'?");
}
}
void measureColor(int measuredColor[]);
void RGBtoHSV(int RGBColor[], int HSVColor[]);
Colors determineColor(int measuredColor[]);
}
void measureColor(int measuredColor[]) {
float r, g, b;
uint16_t red, green, blue, clear;
uint32_t sum;
// Get raw data and store it in variables
tcs.getRawData(&red, &green, &blue, &clear);
// Do calculations to convert the measured values to adjusted 0-255 RGB values
sum = clear;
r = red;
g = green;
b = blue;
r /= sum;
g /= sum;
b /= sum;
r *= 256;
g *= 256;
b *= 256;
measuredColor[0] = r;
measuredColor[1] = g;
measuredColor[2] = b;
}
void RGBtoHSV(int RGBColor[], int HSVColor[]) {
double color[3];
double Cmax, Cmin, delta;
double hue, sat, val;
double hueAngle, satPerc, valPerc;
color[0] = RGBColor[0];
color[1] = RGBColor[1];
color[2] = RGBColor[2];
color[0] /= 255;
color[1] /= 255;
color[2] /= 255;
Cmax = arrayMax(color, 3);
Cmin = arrayMin(color, 3);
delta = Cmax - Cmin;
if (delta == 0) {
hue = 0;
}
if (Cmax == color[0]) {
hue = fmod(60 * (((color[1] - color[2]) / delta) + 360), 360);
if (color[1] - color[2] < 0) {
hue += 255;
}
}
if (Cmax == color[1]) {
hue = fmod(60 * (((color[2] - color[0]) / delta) + 120), 360);
}
if (Cmax == color[2]) {
hue = fmod(60 * (((color[0] - color[1]) / delta) + 240), 360);
}
if (Cmax == 0) {
sat = 0;
} else {
sat = (delta / Cmax) * 100;
}
val = Cmax * 100;
HSVColor[0] = (int)hue;
HSVColor[1] = (int)sat;
HSVColor[2] = (int)val;
}
Colors determineColor(int measuredColor[]) {
int i;
Colors color = unknown;
for (i = 0; i < 8; i++) {
if ((measuredColor[0] >= calibratedvalues[i][0][0][0]) && // H lower bound check
(measuredColor[0] <= calibratedvalues[i][0][1][0]) && // H upper bound check
(measuredColor[1] >= calibratedvalues[i][0][0][1]) && // S lower bound check
(measuredColor[1] <= calibratedvalues[i][0][1][1])) { // S upper bound check
// Set the item color when a check is succesful
color = (Colors)i;
i = 8;
}
return color;
}
const double calibratedvalues[8][2][3] = {
{
{
//RED
{ 0.000, 81.23, 73.87 },
{ 117.7, 105.9, 108.4 },
},
{
//ORANGE
{ 12.00, 60.00, 86.69 },
{ 35.67, 120.5, 120.5 },
},
{
//YELLOW
{ 38.01, 87.88, 70.59 },
{ 62.54, 148.5, 149.4 },
},
{
//GREEN
{ 97.11, 108.6, 57.48 },
{ 108.1, 130.0, 62.52 },
},
{
//BLUE
{ 126.2, 110.0, 36.63 },
{ 142.1, 200.0, 58.03 },
},
{
//WHITE
{ 0.000, 81.23, 73.87 },
{ 117.7, 105.9, 108.4 },
},
{
//BLACK.
{ 0.000, 20.23, 73.87 },
{ 117.7, 35.9, 108.4 },
},
{
//PINK.
{ 0.000, 81.23, 73.87 },
{ 117.7, 105.9, 108.4 },
},
};
}
π¦ Bill of Materials¶
Material | Specs | Quantity | Cost β¬ |
---|---|---|---|
STRUCTURE | |||
MDF | 400x400x5 mm | 1 | 0,50β¬ |
Transparent PETG | 100g | 0,30β¬ | |
Plexyglass | 400x400x4 mm | 1 | 0,50β¬ |
ELECTRONICS | |||
Servo motors | micro | 9 | 20,50β¬ |
Color sensor | 1 | 0,40β¬ | |
ESP32 | 1 | 3,92β¬ | |
Phenolic paper | 200x100 mm | 1 | 10,00β¬ |
Resistors | 6 | 0,60β¬ | |
Capacitors | 6 | 1,20β¬ | |
Switch buttons | 3 | 1,00β¬ | |
LED | 5 | 1,25β¬ | |
Voltage regulator | 2 | 0,5β¬ | |
Diode | 5 | 0,03β¬ | |
Pin Connectors | 60 | 15,00β¬ | |
FTDI | 5 | 0,20β¬ | |
IR sensor | |||
Photoresistor | |||
TOTAL COST | 55,90β¬ |