Thumbnail 3D printed Coin sorter

3D Printed Coin Sorting Machine with Arduino – up to 280 coins per minute!

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.

3D printing of the coin sorter 1
3D printing of the coin sorter
3D printing of the coin sorter 2
3D printed coin ejection channel

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.




Acrylic glass base plate for the coin sorter
Acrylic glass base plat

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.

Coin ejection channels
Coin ejection channels
Turntable for transporting the coins
Turntable for transporting the coins

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.




Angle of the rotating disk
Angle of the rotating disk
Height of the first ejection channel
Height of the first ejection channel

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.





Incorrect size of the recesses in the rotation disk
Incorrect size of the recesses in the rotation disk

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.




Angle of the recess in the rotation disk
Angle of the recess in the rotation disk
Movement of coins to the center of the disc
Movement of coins to the center of the disc

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.


Minimum working distance of the sensors at about 40mm
Minimum working distance of the sensors at about 40mm

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.




Detect the coins with infrared reflection sensors
Detect the coins with infrared reflection sensors
View from below into the ejection channel
View from below into the ejection channel

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

#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;

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() {

  Sensor1_Start = analogRead(Sensor1);
  Sensor2_Start = analogRead(Sensor2);
  Sensor3_Start = analogRead(Sensor3);
  Sensor4_Start = analogRead(Sensor4);
  Sensor5_Start = analogRead(Sensor5);

  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);
    else if(buttonState == 1){//Evaluate
      buttonState = 2;
      analogWrite(GSM1, 0);//Motor stops
      //digitalWrite(in1, LOW); 
      //digitalWrite(in2, LOW);
    else if(buttonState == 2){//Reset
      buttonState = 0;
      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: ");

      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: ");

      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: ");

      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: ");

      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: ");

//TFT Start
void TFT_start(){

  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.setCursor(15, 10);
  tft.println("Coin Sorter");
  tft.fillRect(0, 56, 200, 15, QDTech_BLACK);
  tft.setCursor(30, 60);
  tft.println("Push the button");
  tft.fillRect(0, 113, 200, 12, QDTech_BLACK);
  tft.setCursor(5, 115);
  tft.println("made by FraensEngineering");
//TFT Running
void TFT_running(){
  tft.fillRect(0, 50, 200, 30, QDTech_WHITE);
  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

  // 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.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.setCursor(COL_START + CELL_WIDTH + 8, ROW_START);
  tft.setCursor(COL_START + (CELL_WIDTH * 2)+ 8, ROW_START);
  tft.drawFastHLine(0, ROW_START + 13, 160, QDTech_YELLOW);

  // Beispiel-Daten schreiben
  tft.print("2 Euro");    // geändert auf 2 Euro

  tft.setCursor(COL_START, ROW_START + (2 * CELL_HEIGHT));
  tft.print("1 Euro");
  tft.drawFastHLine(0, 2 * CELL_HEIGHT + 13, 160, QDTech_YELLOW);
  tft.setCursor(COL_START, ROW_START + (3 * CELL_HEIGHT));
  tft.print("50 Cent");
  tft.drawFastHLine(0, 3 * CELL_HEIGHT + 13, 160, QDTech_YELLOW);
  tft.setCursor(COL_START, ROW_START + (4 * CELL_HEIGHT));
  tft.print("20 Cent");
  tft.drawFastHLine(0, 4 * CELL_HEIGHT + 13, 160, QDTech_YELLOW);
  tft.setCursor(COL_START, ROW_START + (5 * CELL_HEIGHT));
  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.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.setCursor(xx, 34);

  tft.setCursor(x, 50);
  tft.setCursor(xx, 50);

  tft.setCursor(x, 65);
  tft.setCursor(xx, 65);

  tft.setCursor(x, 81);
  tft.setCursor(xx, 81);

  tft.setCursor(x, 97);
  tft.setCursor(xx, 97);

  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.setCursor(xx, 116);

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.




Circuit diagram of the 3D printed coin sorter 1
Circuit diagram Arduino
Circuit diagram of the 3D printed coin sorter 2

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.

2 Kommentare zu „3D Printed Coin Sorting Machine with Arduino – up to 280 coins per minute!“

    1. 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.

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Nach oben scrollen