Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 

Repository files navigation

Smart Color-Sorting Conveyor

A fully interrupt-driven embedded system that detects products on a moving conveyor, classifies them by color, and sorts them into the correct bin — built on an ARM Cortex-M33 microcontroller.

Platform: EFM32PG28 (ARM Cortex-M33) · BRD2506A dev board · Language: C (bare-metal) · SDK: Simplicity SDK / emlib

📺 Watch the demo video


Overview

A product travels along the conveyor belt. An IR sensor detects and counts it, a color sensor reads its color, and — based on that color — one of two servo arms pushes it into the matching bin. An on-board LCD cycles through live counters, two push-buttons control the belt, and the whole system can be monitored and controlled from a phone over Bluetooth.

The defining constraint of the project: every action is interrupt-driven. There are no busy-wait loops and no delay loops anywhere in the firmware. The CPU sleeps in low-power EM1 and wakes only when an event actually happens.

System block diagram


Key features

  • 100% event-driven architecture — GPIO, timer, and USART interrupts only. The main loop does nothing but dispatch flags and return to sleep (EMU_EnterEM1()).
  • Software PWM generated from timer interrupts — ~1 kHz for the DC motor and 50 Hz for two servos, implemented via overflow/compare ISRs after the hardware PWM path proved unusable on this board.
  • Frequency-based color sensing — the TCS3200's output frequency is counted inside a 30 ms gate, cycling through red/green/blue filters, then classified.
  • Hardware debounce — one-shot timers filter mechanical button bounce and electrical noise on the IR line, preventing double counts.
  • Interrupt-driven UART (both directions) — RX maps incoming characters to motor commands; TX feeds one byte per TXBL interrupt, so the CPU never spins waiting on the transmitter.
  • Modular firmware — one .c/.h module per subsystem, with the application logic in app_process_action() rather than main().

How it works

System flowchart

  1. Detection — the product breaks the IR beam, raising a GPIO edge interrupt. A 30 ms one-shot debounce timer starts.
  2. Confirmation — when the timer fires, the firmware samples the stable pin state. A new arrival triggers a color read; a departure increments the counters exactly once.
  3. Classification — the color sensor runs continuously in the background, sweeping R→G→B filters and updating the current color label.
  4. Sortingservo_sort() selects the arm and angle for that color, holds the arm out for ~4 seconds, then returns to rest once the product has passed.
  5. Reporting — every 2 seconds the LCD advances to the next counter screen and a status line is transmitted over Bluetooth.

Color classification logic

The dominant color is accepted only if it is both strong (above a minimum frequency threshold) and distinct (at least 1.2× the runner-up). Anything ambiguous is deliberately classified as INVALID and diverted to a separate bin, rather than guessed.


Hardware

Component Part Role
MCU EFM32PG28 (Cortex-M33), BRD2506A System controller
Motor driver L293D H-bridge Conveyor belt drive
DC motor Belt movement
IR sensor TCRT5000 Product detection & counting
Color sensor TCS3200 Red / green / blue classification
Servos 2 × MG90S Sorting arms
Display On-board segment LCD Live counters & speed
Bluetooth HC-06 Wireless monitoring & control
Buttons PB0 / PB1 Direction & speed control

Pin map

Signal Pin Notes
Motor PWM (EN) PD7 Software PWM, ~1 kHz
Motor IN1 / IN2 PD14 / PD15 Direction via H-bridge
Servo 1 (blue/red arm) PD6 TIMER3 CC0
Servo 2 (invalid arm) PD8 TIMER3 CC1
IR sensor PD12 Both-edge interrupt
Color OUT PD11 Rising-edge counting
Color S2 / S3 PD9 / PD10 Filter select
Buttons PB1 / PB6 Active-low, pull-up
UART TX / RX PA12 / PA3 9600 8-N-1

Peripheral allocation

Peripheral Purpose Configuration
LETIMER0 LCD refresh & status tick 2 s, LFXO 32768 Hz
TIMER0 Motor PWM ~1 kHz, prescale /1
TIMER1 Button debounce 40 ms one-shot
TIMER2 Color measurement gate 30 ms window
TIMER3 Servo PWM 50 Hz, CC0 + CC1
TIMER4 IR debounce 30 ms one-shot
USART0 Bluetooth link 9600 baud, RX + TXBL interrupts

Firmware structure

src/
├── app.c / app.h     Application layer: app_init() + app_process_action()
├── lcd.c             Segment LCD display & 2 s cyclic refresh
├── motor.c           DC motor: software PWM + direction control
├── buttons.c         Push-button handling, debounce, shared command API
├── ir.c              IR detection, debounce, product counting
├── color.c           TCS3200 frequency measurement & classification
├── servo.c           Dual-servo 50 Hz PWM & sorting logic
└── uart.c            Interrupt-driven Bluetooth UART (RX + TX)

main() only calls app_init() and then loops on app_process_action(). All timing-critical work happens in ISRs; the loop handles deferred rendering and transmission, then sleeps.


Technical highlights

Software PWM. The hardware compare/PWM output was dead on the original pin (verified flat on a scope). Rather than work around it in a fragile way, PWM was reimplemented in software: a timer overflow ISR toggles the pin and reloads the next TOP value, splitting each period into HIGH and LOW segments. The motor runs at ~1 kHz; the servos use a 50 Hz frame with 0.5–2.5 ms pulses mapped linearly to 0–180°.

Motor PWM on the scope

Timer values derived, not guessed. Every prescaler and TOP value was calculated from the clock tree and then verified against the actual registers in the debugger. For example, the servo frame: 39 MHz ÷ 16 = 2,437,500 Hz, and 2,437,500 ÷ 50 Hz = 48,750 ticks per 20 ms frame.

UART frame verified on the scope. Incoming Bluetooth commands were captured and decoded bit by bit to confirm correct framing and baud rate.

UART frame decode

Interrupt-driven transmit. uart_send_status() builds the status line into a buffer and enables the TX-buffer-empty (TXBL) interrupt. Each interrupt feeds exactly one byte, so the CPU is free (and can sleep) between characters — no blocking while loop on the transmitter.


Engineering challenges

Three problems shaped the final design, and each one taught more than the working parts did:

Dead PWM output. The initial hardware PWM produced a flat line instead of a square wave. Tracing it pin by pin on the oscilloscope — rather than re-reading the code — located the problem and led to the software PWM implementation. Lesson: measure the signal before trusting the abstraction.

Double product counts. Electrical noise, mostly from the servos, generated spurious edges on the IR line. Adding a hardware input filter plus a debounce window, and counting only on a confirmed present→absent transition, made the count reliable.

Bluetooth dropping out mid-sort. Every servo actuation caused a supply dip that browned out the HC-06. The fix was electrical rather than firmware: separating the motor/servo power rail from the logic rail in a star topology and adding bulk and decoupling capacitors.


Building and flashing

  1. Install Simplicity Studio 5 with the Simplicity SDK.
  2. Create an Empty C Project targeting the BRD2506A / EFM32PG28 board.
  3. Copy the contents of src/ into the project's source directory.
  4. Build and flash to the board via the on-board debugger.

Wire the peripherals according to the pin map above. Power the motor and servos from a separate supply rail from the logic and Bluetooth module, with a common ground — this is required for stable operation.


Bluetooth commands

Character Action
R Run belt right
L Run belt left
S Stop / resume

The board transmits a periodic status line containing all counters, current speed, and direction.


Author

Mofeed Hamdan — Electrical & Electronics Engineering student, Braude Academic College Built as the final project for the Microcontrollers course (31226).

About

"Interrupt-driven color-sorting conveyor on ARM Cortex-M33 (EFM32PG28)"

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages