← Projects

Pill sorting machine

May – July 2025 · University group project · Embedded Systems and Robotics · C, MSP430, I2C, Embedded

Code

Put a pill on the platform, and the machine works out its colour, turns towards the matching bin and tips the pill in. Red, green and blue pills each end up in their own compartment, and the display keeps count. We built it as a team of three in the Embedded Systems and Robotics course at Reutlingen University. The firmware is plain C on a 16-bit MSP430 microcontroller, written without an operating system or a hardware abstraction library, register by register.

The platform sorting a red pill Fig. 1: A pill is detected, classified as red and tipped into the red bin

How a pill gets sorted

  1. Detect. The colour sensor looks at the empty platform and remembers how bright it is. A pill casts a shadow on the sensor, and the measured brightness drops.
  2. Measure. The sensor switches on its white LED and measures the red, green and blue light reflected by the pill.
  3. Classify. If one colour clearly dominates the other two, the pill gets that colour. Otherwise it is left where it is.
  4. Sort. The direction servo turns the platform towards the bin for that colour. The tilt servo then tips the platform and the pill slides off.
  5. Report. The LCD shows the detected colour and the updated counters, and two LEDs show whether the machine is ready or busy.

The complete machine on the desk Fig. 2: The complete setup. The platform sits on a servo tower above a ring with three bins. The MSP430 board is on the right, the LCD in front.

Hardware

PartRoleConnection
MSP430FR2355 LaunchPadmicrocontroller, 16-bit, 1 MHz
TCS34725RGB colour sensor with white LEDI2C, address 0x29
PCA968516-channel PWM driver for the two servosI2C, address 0x40
LCD16022 × 16 character display with I2C adapterI2C, address 0x3F
2 servosturn the platform and tilt itPWM from the PCA9685
2 buttons, 2 LEDsuser input and statusGPIO

All three peripherals share one I2C bus, so the microcontroller needs only two pins for everything except buttons and LEDs. The logic runs at 3.3 V. The servos get a separate 5 V supply, so that the current they draw when they start moving does not disturb the microcontroller.

Wiring diagram Fig. 3: Wiring diagram

Firmware architecture: sleep until something happens

The machine spends almost all of its time waiting: for a button, for a pill, for a timer. The firmware is therefore built around events and low power. Interrupt service routines (ISRs) do almost nothing themselves. They set a bit in a global eventBits variable and wake the CPU. The main loop takes the pending events one at a time, hands each to the state machine, and puts the CPU back into low-power mode 3 (LPM3) once nothing is left. In LPM3 the CPU is stopped and draws almost no current, while the timers keep running on a 32.768 kHz clock.

while (true)
{
    event = get_event(&eventBits);      // highest pending bit, cleared on read
    while (event > EVT_NO_EVENT)
    {
        handle_event_FSM(&currentState, event);
        event = get_event(&eventBits);
    }
    LPM3;                               // sleep until the next interrupt
}

Each event is one bit, and get_event scans from the highest bit down. The bit position is therefore also the priority. If a timeout and a button press arrive together, the timeout is handled first.

BitEventSet by
4DEACTIVATION_PER_TIMERTimer B3, after 12 s without activity
3OBJECT_DETECTEDthe object check, when the brightness drops
2S2button S2 (port interrupt)
1S1button S1 (port interrupt)
0SYSTEM_TICKTimer B0, once per second in auto mode

Four hardware timers, all clocked from the 32.768 kHz clock, cover the time-based parts: the 1 s system tick (B0), blocking delays during which the CPU still sleeps (B1), a 500 ms debounce window for the buttons (B2), and the inactivity timeout (B3). For debouncing, the button ISR disables both button interrupts and starts Timer B2. Its ISR enables them again 500 ms later, so the bouncing contacts cannot trigger a second press.

The state machine

All behaviour lives in one state machine with five states. Every state reacts to every event, often by ignoring it. Writing it out as a nested switch over state and event makes it easy to check that no combination is forgotten.

The demo runs the same logic as the firmware: the same events and priorities, the same transitions, the original display texts, colour thresholds and servo angles. Only the sensor readings are example values.

LCD showing the mode selection Fig. 4: The mode selection on the real display, “S1: auto, S2: manual”

Seeing colour

The TCS34725 measures four channels: red, green, blue and an unfiltered clear channel for overall brightness. Each measurement integrates light for about 100 ms, set as 42 integration cycles of 2.4 ms. To save power, the firmware switches the sensor on for each measurement and off again afterwards.

Is there a pill? When a sorting mode starts, the firmware reads the clear channel of the empty platform as a reference. A pill shades the sensor. If the clear value falls more than 40 % below the reference, a pill is present. No extra light barrier is needed.

Which colour is it? With the LED on, the firmware reads the three colour channels and scales them down to 8 bits. It halves all three together until the largest fits into 0 to 255, so the ratios between them stay the same. Then a colour counts as detected if it is at least 1.4 times as strong as both others, above 100 itself, and both others are below 100. For red:

10 r>14 g  ∧  10 r>14 b  ∧  r>100  ∧  g<100  ∧  b<10010\,r > 14\,g \;\land\; 10\,r > 14\,b \;\land\; r > 100 \;\land\; g < 100 \;\land\; b < 100

The factor 1.4 appears as 10r>14g10 r > 14 g, because the MSP430 has no floating-point unit. Integer comparisons cost a few cycles, while a software floating-point multiplication would cost hundreds.

if (red * 10 > green * 14 && red * 10 > blue * 14 &&
    green < 100 && blue < 100 && red > 100)
{
    plattform_empty_r();               // turn to the red bin and tip
    display_detected_color(RED);
    redSorted++;
}

Moving the pill

Hobby servos expect a pulse every 20 ms, i.e. at 50 Hz, and the width of the pulse sets the angle. The PCA9685 generates these pulses for us. It divides its 25 MHz clock into 4096 steps per period:

prescale=round ⁣(25 000 0004096⋅50)−1=121\text{prescale} = \text{round}\!\left(\frac{25\,000\,000}{4096 \cdot 50}\right) - 1 = 121

One step is then 20 ms/4096≈4.9 μs20\text{ ms} / 4096 \approx 4.9\,\mu\text{s}, and each angle becomes a number of steps:

AngleStepsPulseUsed for
40°2401.17 mstilt: tip the pill off
50°2651.29 msdirection: red bin
90°3651.78 msdirection: green bin, tilt: level
120°4402.15 msdirection: blue bin
135°4772.33 mstilt: folded away while switched off

A sort turns the platform to the bin, waits 0.7 s, tilts to 40°, waits 1.5 s for the pill to slide off, and returns to the level position.

Platform turned towards a bin with a red pill Fig. 5: The platform over the ring of bins, with a red pill on it

I2C, interrupt-driven

All three devices are driven by the eUSCI_B0 module as I2C master at 50 kHz (the 1 MHz system clock divided by 20). Transfers are interrupt-driven: the code loads the number of bytes, sends a START and goes to sleep in LPM3. The ISR feeds the transmit buffer byte by byte, the hardware sends the STOP automatically when the byte counter reaches zero, and the STOP interrupt wakes the CPU again. A NACK, i.e. a device that does not answer, wakes it as well instead of hanging forever.