In this article I describe my 3D printed coin sorting machine. With this it is possible via Arduino not only to sort the coins but also to count them. A TFT display shows the type of coins and the amount of coins. To get a good idea about my coin sorter I recommend to watch my video.
For a long time I cherished the thought of building a coin sorting machine. There are numerous videos of self-built coin sorters on YouTube. However, none of these machines met my specific requirements. All coin sorters presented on YouTube work on the same principle. Therefore, I took it upon myself to develop my own machine. In countless hours of work, I designed my own construction. The main goal of this project was to sort and count a considerable amount of coins as quickly and error-free as possible. Looking back on this project, I am extremely satisfied with the result achieved.
The 3D printed coin sorting machine sorts the following coins: 10 cent, 20 cent, 50 cent, 1 euro, 2 euro. I have designed a rotation disk and ejection channels for dollar coins. However, I could not test these for lack of dollar coins.
3D printing of the coin sorter
The 3D printed coin sorting machine was designed with a focus on 3D-printed components, which make up the majority of the design. The design of the parts was done in such a way that they do not require any additional support structures. I mainly used PETG material to make these components, although using ABS or PLA is also possible. The prerequisite for printing is a 3D printer with a build space of at least 250x250mm.
The coin ejection channel undoubtedly represented one of the most challenging parts I have ever designed. And that’s saying something, since I’ve been involved in 3D part design for more than a decade. It’s hard for me to pinpoint the exact number of days I spent trying different approaches until I finally managed to come up with a design solution that was suitable for 3D printing.
Aesthetics were not the main focus of my printing. Rather, my goal was to keep the print time as short as possible to minimize build time loss. For this reason, I used my Voron 2.4 printer, which is considered top-of-the-line in 3D printing, for the 3D printed parts. Such impressive print speeds are not achievable with inexpensive Chinese printers.
I like to use acrylic glass for large-area components in my constructions. This can be processed excellently with a scroll saw. In addition, it can be very well chip-removing processing such as filing or drilling. It can also be cut threads that have a high strength. However, acrylic glass must not be confused with polystyrene glass. Polystyrene is very brittle and can hardly be cut or drilled. Polystyrene is more suitable for rectangular straight components.
How the Coin sorting machine works
The coins enter the rotation disc through a rotating hopper. This disc is provided with specially shaped recesses that serve to convey the coins upward to the ejection channels. Once they reach the ejection channels, sorting occurs based on their size, or diameter. The coins fall into the ejection channels in ascending order by size. As they do so, they are detected by infrared reflective sensors mounted inside the ejection channels. Since only coins of the correct size enter each of the five ejection channels, the Arduino can easily count the number of coins in each channel and thus calculate the total sum of all coins.
The rotating disc is driven by means of a geared motor. The speed can be controlled via the Arduino and an H-bridge.
A central factor that has a significant influence on the function is the angle of inclination of the rotating disc. If this angle is too shallow, this causes the coins to slide back down late. This delay results in coins lying on top of each other moving very far upwards with them. One consequence of this is that coins can get into unwanted ejection channels.
On the other hand, if the angle is too steep, it becomes difficult for the coins to fit into the intended recesses of the rotating disc. This dynamic can be clearly seen in the video. Finding an optimal middle ground between these two extremes proved to be a challenging task, requiring a large number of test runs.
To ensure that the coins enter the ejection channels without any errors, precise alignment in advance is essential. This is achieved by the special recesses in the rotation disk.
In practice, it works as follows: The coin is placed in one of these recesses and transported upwards by the rotation disk. During this process, the coin rolls over the flank of the recess, which moves towards the center of the rotating disk, and finally rests against the inner edge.
The angle of this flank in the disc plays an extremely important role. A steeper angle causes the coins to align more quickly in the recess. A shallower angle, on the other hand, causes alignment to occur later. The main objective is to ensure that the coins take up the correct position in the recess as quickly as possible in order to be accurately placed in the first ejection channel.
It should be noted, however, that too steep an angle, as illustrated in the attached figure, can result in excessively large recesses in which multiple coins could accumulate – a scenario that must obviously be avoided.
On the other hand, if the angle is too shallow, the coins will align too late to be captured by the first ejection channel. Therefore, the precise adjustment of this angle is crucial to guarantee smooth operation.
Detection of the coins
In an optimal scenario, the correct coins fall into the corresponding ejection channels. There they are detected by infrared reflection sensors. These sensors are connected to an Arduino Uno, which reads out the data. In the attached picture it is clearly visible on the serial monitor of the Arduino how the coins are detected. The detection of a coin is shown by a prominent change in the analog sensor signal.
A significant aspect concerns the working distance of the sensors. From a distance of about 40 mm to the object, the analog value of the sensor shows a sharp increase. It is therefore important to ensure that the back wall of the ejection channels is at a distance of more than 40 mm from the sensor. With less proximity to the back wall, the analog signal is very small, making the deflections in the signal triggered by a coin barely perceptible. This in turn makes it more difficult for the software to detect such signals.
The logic of the 3D printed coin sorter
Here you can see the Arduino code, that is the brain of the 3D printed coin sorting machine. I try to go into some important points here to understand it better.
//Parameter settings int Sensor_puffer = 40; //Analog value deviation when coin is detected int Sensor_delay_time = 100;//How long is sensor deactivated after detecting a coin
These are one of the most important settings to make. „Sensor_puffer“ is the required analog change to detect a coin. You have to build in some hysteresis here because the sensors vary a bit in their output analog value from the ground up. In addition, the sensors also react to the ambient light.
The variable „Sensor_delay_time“ is used to deactivate the sensor for a short time after detecting a coin. You can imagine it like this: A coin falls into the ejection channel – the sensor changes its analog value – as soon as the change is greater than „Sensor_puffer“ a coin is detected – the coin now needs a certain time to fall through the ejection channel – during this time the sensor must be deactivated otherwise it would detect a lot of coins.
if(analogRead(Sensor1) >= Sensor1_Start + Sensor_puffer && millis() >= Sensor1_time + Sensor_delay_time){
Here you can see the logic of coin counting. I will try to describe this in a few words: When a coin flies into the ejection channel the variable „Sensor_time“ is set to the current internal system time.
If now the measured analog value is greater than the one set before, and the system time is greater than the time of the previous coin plus the set value, the coin count variable is increased by one. This happens for each of the 5 ejection channels.
#include -Adafruit_QDTech.h #include SPI.h //Display #define CS_PIN 10 #define DC_PIN 9 #define RST_PIN 8 Adafruit_QDTech tft = Adafruit_QDTech(CS_PIN, DC_PIN, RST_PIN); //Counting variables int two_Euro_count = 0; int one_Euro_count = 0; int fifty_cents_count = 0; int twenty_cents_count = 0; int ten_cents_count = 0; int Sensor1 = A4; //10 Cent int Sensor2 = A3; //20 Cent int Sensor3 = A2; //1 Euro int Sensor4 = A1; //50 Cent int Sensor5 = A0; //2 Euro //Start time on which the coin is recognized unsigned long Sensor1_time = 0; unsigned long Sensor2_time = 0; unsigned long Sensor3_time = 0; unsigned long Sensor4_time = 0; unsigned long Sensor5_time = 0; //Analog values at the start int Sensor1_Start; int Sensor2_Start; int Sensor3_Start; int Sensor4_Start; int Sensor5_Start; //Motor H-bridge int GSM1 = 3; int in1 = 4; int in2 = 2; //Button int buttonPin = 12; int buttonState = 0;//Button Status unsigned long buttonTime = 0; //Parameter settings int Sensor_puffer = 40; //Analog value deviation when coin is detected int Sensor_delay_time = 100;//How long is sensor deactivated after detecting a coin void setup() { Serial.begin(9600); tft.init(); TFT_start(); Sensor1_Start = analogRead(Sensor1); Sensor2_Start = analogRead(Sensor2); Sensor3_Start = analogRead(Sensor3); Sensor4_Start = analogRead(Sensor4); Sensor5_Start = analogRead(Sensor5); Serial.println(Sensor1_Start); Serial.println(Sensor2_Start); Serial.println(Sensor3_Start); Serial.println(Sensor4_Start); Serial.println(Sensor5_Start); pinMode(buttonPin, INPUT_PULLUP); } void loop() { //Set operating mode / debounce button if(digitalRead(buttonPin) == LOW && millis() >= buttonTime + 500){ if(buttonState == 0){ buttonState = 1; analogWrite(GSM1, 180);//Start the engine 0-255 digitalWrite(in1, LOW); digitalWrite(in2, HIGH); TFT_running(); } else if(buttonState == 1){//Evaluate buttonState = 2; analogWrite(GSM1, 0);//Motor stops //digitalWrite(in1, LOW); //digitalWrite(in2, LOW); TFT_evaluation(); } else if(buttonState == 2){//Reset buttonState = 0; TFT_start(); two_Euro_count = 0; one_Euro_count = 0; fifty_cents_count = 0; twenty_cents_count = 0; ten_cents_count = 0; } buttonTime = millis(); } if(buttonState == 1){ if(analogRead(Sensor1) >= Sensor1_Start + Sensor_puffer && millis() >= Sensor1_time + Sensor_delay_time){ Sensor1_time = millis(); ten_cents_count = ten_cents_count + 1; //Serial.print("10 Cent: "); //Serial.println(ten_cents_count); //Serial.println(millis()); //Serial.println(analogRead(Sensor1)); } if(analogRead(Sensor2) >= Sensor2_Start + Sensor_puffer && millis() >= Sensor2_time + Sensor_delay_time){ Sensor2_time = millis(); twenty_cents_count = twenty_cents_count + 1; //Serial.print("20 Cent: "); //Serial.println(twenty_cents_count); //Serial.println(millis()); //Serial.println(analogRead(Sensor1)); } if(analogRead(Sensor3) >= Sensor3_Start + Sensor_puffer && millis() >= Sensor3_time + Sensor_delay_time){ Sensor3_time = millis(); one_Euro_count = one_Euro_count + 1; //Serial.print("1 Euro: "); //Serial.println(one_Euro_count); //Serial.println(millis()); //Serial.println(analogRead(Sensor3)); } if(analogRead(Sensor4) >= Sensor4_Start + Sensor_puffer && millis() >= Sensor4_time + Sensor_delay_time){ Sensor4_time = millis(); fifty_cents_count = fifty_cents_count + 1; //Serial.print("50 Cent: "); //Serial.println(fifty_cents_count); //Serial.println(millis()); //Serial.println(analogRead(Sensor4)); } if(analogRead(Sensor5) >= Sensor5_Start + Sensor_puffer && millis() >= Sensor5_time + Sensor_delay_time){ Sensor5_time = millis(); two_Euro_count = two_Euro_count + 1; //Serial.print("2 Euro: "); //Serial.println(two_Euro_count); //Serial.println(millis()); //Serial.println(analogRead(Sensor5)); } } } //--------------------------------------- //TFT Start void TFT_start(){ tft.setRotation(1); tft.fillScreen(QDTech_BLACK); for (int radius = 0; radius <= max(tft.width()/2, tft.height()/2); radius += 5) { tft.drawCircle(tft.width()/2, tft.height()/2, radius, QDTech_WHITE); //Draw circle delay(100); //Short delay so that the effect is visible } tft.fillRect(0, 0, 200, 30, QDTech_BLACK); tft.setTextSize(2); tft.setTextColor(QDTech_GREEN); tft.setCursor(15, 10); tft.println("Coin Sorter"); tft.setTextSize(1); tft.fillRect(0, 56, 200, 15, QDTech_BLACK); tft.setCursor(30, 60); tft.println("Push the button"); tft.setTextSize(1); tft.fillRect(0, 113, 200, 12, QDTech_BLACK); tft.setCursor(5, 115); tft.println("made by FraensEngineering"); } //--------------------------------------- //TFT Running void TFT_running(){ tft.setRotation(1); tft.fillScreen(QDTech_BLACK); tft.fillRect(0, 50, 200, 30, QDTech_WHITE); tft.setTextSize(1); tft.setTextColor(QDTech_BLACK); tft.setCursor(25, 60); tft.println("Machine is sorting"); } //--------------------------------------- //Tft evaluation (evaluation at the end of the count) void TFT_evaluation(){ const int NUM_ROWS = 6; const int NUM_COLS = 3; const int CELL_WIDTH = 50; const int CELL_HEIGHT = 16; const int ROW_START = 18; const int COL_START = 10; const int TEXT_SIZE = 1; // change font size to 1 tft.setRotation(1); tft.fillScreen(QDTech_BLACK); // create header with name "Coinsorter" int headerX = 0; // x position of header int headerY = 0; // y position of header int headerWidth = 160; // width of header int headerHeight = 15; // height of header tft.fillRect(headerX, headerY, headerWidth, headerHeight, tft.Color565 (127, 255, 0)); // draw red rectangle tft.setCursor(headerX + 18, headerY + 4); // set cursor position tft.setTextColor(QDTech_BLACK); tft.setTextSize(1); // set font size tft.print("C o i n s o r t e r"); // print header text // draw filled rectangle for column header int colHeaderX = 0; int colHeaderY = ROW_START - 3; int colHeaderWidth = CELL_WIDTH * 4; int colHeaderHeight = CELL_HEIGHT; tft.fillRect(colHeaderX, colHeaderY, colHeaderWidth, colHeaderHeight, tft.Color565(255, 0, 0)); // draw filled rectangle for Summe tft.fillRect(0, 109, 160, 128, tft.Color565(255, 0, 0)); // draw vertical lines for (int i = 0; i < NUM_COLS - 1; i++) { int x = COL_START + ((i + 1) * CELL_WIDTH); tft.drawFastVLine(x, ROW_START, (NUM_ROWS+1) * CELL_HEIGHT, QDTech_YELLOW); } // write column headers tft.setCursor(COL_START + 3, ROW_START); tft.setTextColor(QDTech_YELLOW); tft.setTextSize(TEXT_SIZE); tft.print("Coin"); tft.setCursor(COL_START + CELL_WIDTH + 8, ROW_START); tft.setTextColor(QDTech_YELLOW); tft.setTextSize(TEXT_SIZE); tft.print("Number"); tft.setCursor(COL_START + (CELL_WIDTH * 2)+ 8, ROW_START); tft.setTextColor(QDTech_YELLOW); tft.setTextSize(TEXT_SIZE); tft.print("Total"); tft.drawFastHLine(0, ROW_START + 13, 160, QDTech_YELLOW); // Beispiel-Daten schreiben tft.setCursor(COL_START, ROW_START + CELL_HEIGHT); tft.setTextColor(QDTech_WHITE); tft.setTextSize(TEXT_SIZE); tft.print("2 Euro"); // geändert auf 2 Euro tft.setCursor(COL_START, ROW_START + (2 * CELL_HEIGHT)); tft.setTextColor(QDTech_WHITE); tft.setTextSize(TEXT_SIZE); tft.print("1 Euro"); tft.drawFastHLine(0, 2 * CELL_HEIGHT + 13, 160, QDTech_YELLOW); tft.setCursor(COL_START, ROW_START + (3 * CELL_HEIGHT)); tft.setTextColor(QDTech_WHITE); tft.setTextSize(TEXT_SIZE); tft.print("50 Cent"); tft.drawFastHLine(0, 3 * CELL_HEIGHT + 13, 160, QDTech_YELLOW); tft.setCursor(COL_START, ROW_START + (4 * CELL_HEIGHT)); tft.setTextColor(QDTech_WHITE); tft.setTextSize(TEXT_SIZE); tft.print("20 Cent"); tft.drawFastHLine(0, 4 * CELL_HEIGHT + 13, 160, QDTech_YELLOW); tft.setCursor(COL_START, ROW_START + (5 * CELL_HEIGHT)); tft.setTextColor(QDTech_WHITE); tft.setTextSize(TEXT_SIZE); tft.print("10 Cent"); tft.drawFastHLine(0, 5 * CELL_HEIGHT + 13, 160, QDTech_YELLOW); tft.setCursor(COL_START, ROW_START + (6 * CELL_HEIGHT)+2); tft.setTextColor(QDTech_GREEN); tft.setTextSize(TEXT_SIZE); tft.print("Sum tot.:"); tft.drawFastHLine(0, 6 * CELL_HEIGHT + 13, 160, QDTech_YELLOW); const int x = 82; const int xx = 120; //TFT mit Zahlen befüllen tft.setCursor(x, 34); tft.setTextColor(QDTech_WHITE); tft.setTextSize(1); tft.print(two_Euro_count); tft.setCursor(xx, 34); tft.print(two_Euro_count*2); tft.setCursor(x, 50); tft.setTextColor(QDTech_WHITE); tft.setTextSize(1); tft.print(one_Euro_count); tft.setCursor(xx, 50); tft.print(one_Euro_count*1); tft.setCursor(x, 65); tft.setTextColor(QDTech_WHITE); tft.setTextSize(1); tft.print(fifty_cents_count); tft.setCursor(xx, 65); tft.print(fifty_cents_count*0.5); tft.setCursor(x, 81); tft.setTextColor(QDTech_WHITE); tft.setTextSize(1); tft.print(twenty_cents_count); tft.setCursor(xx, 81); tft.print(twenty_cents_count*0.2); tft.setCursor(x, 97); tft.setTextColor(QDTech_WHITE); tft.setTextSize(1); tft.print(ten_cents_count); tft.setCursor(xx, 97); tft.print(ten_cents_count*0.1); int sum_count = two_Euro_count + one_Euro_count + fifty_cents_count + twenty_cents_count + ten_cents_count; float sum_total = two_Euro_count*2 + one_Euro_count*1 + fifty_cents_count*0.5 + twenty_cents_count*0.2 + ten_cents_count*0.1; tft.setCursor(x, 116); tft.setTextColor(QDTech_GREEN); tft.setTextSize(1); tft.print(sum_count); tft.setCursor(xx, 116); tft.print(sum_total); }
The circuit diagram
The circuit diagrams and electronic information presented on this website are for illustrative purposes only and are based on my own experience and procedures. I assume no responsibility for the accuracy, completeness or timeliness of the content provided. Any use or implementation of the information presented here is at the user’s own risk. I expressly exclude any liability for damages that may arise from the use of this information. It is recommended that you consult a specialist before implementing any electronic projects and take appropriate safety precautions.
The wiring looks a bit complicated at first. But if you connect everything one after the other, there should be no problems.
You can download the building plan here:
You will get the following digital files:
- All .stl files you need to print the machine.
- Assembly drawing in .pdf -format.
- The complete 3D model of the machine in .step – format.
- .svg file for laser engraving of the signs.
- The cut-out drawing of the base plate .dxf, .pdf format.
- A detailed parts list of all installed parts including internet links to the supplier.
- An instruction video with all building steps.
- I will do my best to help you with any problems or questions.
Wow, superb blog layout! How long have you been blogging for? you make blogging look easy. The overall look of your site is magnificent, as well as the content!
Hello, thank you very much for your nice comment. I write a post for each of my projects. My website is the ideal complement to my videos. Here I can describe my projects in detail.