Daniel Reynolds — REYN Consultancy / La Trobe University, 2024
A bare-metal embedded C project implementing a shaft encoder measurement system on the TI MSP430 LaunchPad. Built as part of ELE3DEC — Digital Electronics and Controllers at La Trobe University.
This project was deliberately implemented at register level with no Arduino, no HAL abstraction, and no external libraries. The goal was to understand the embedded hardware, not abstract it away.
An electronic shaft encoder system that measures rotational speed and position of a 3D-printed encoder wheel using a photo interrupter. Results are displayed on a 2-line LCD with three switchable display modes and a real-time spinning animation.
Most microcontroller tutorials use Arduino or vendor HAL layers that hide the hardware behind function calls. This project configures every peripheral directly:
- Timer registers set by hand for exact 500ms intervals
- GPIO interrupt flags, edge select, and pull-up resistors configured at bit level
- LCD HD44780 driven via 4-bit parallel interface — nibble writes, enable pulses, CGRAM character loading
- Custom CGRAM characters defined as 5×8 pixel bitmaps written directly to LCD controller memory
- ISR vectors defined explicitly with
#pragma vector
This approach builds the foundational understanding that HAL-based development skips.
┌──────────────────────────────────────────┐
│ MSP430G2553 LaunchPad │
│ │
│ PORT1 GPIO Interrupt │
│ ┌──────────────────┐ │
│ │ Photo Interrupter │──── slot detect │
│ │ (encoder wheel) │ rising edge │
│ └──────────────────┘ ISR: count++ │
│ │
│ TimerA0 — 500ms periodic interrupt │
│ ┌─────────────────────────────────────┐ │
│ │ RPM = (slots × 60000) │ │
│ │ ───────────────── │ │
│ │ (slots/rev × interval_ms) │ │
│ └─────────────────────────────────────┘ │
│ │
│ Push Buttons (active-low, pull-up) │
│ PB1 → reset count │
│ PB2 → toggle display mode │
│ │
│ Status LED — flashes on slot detection │
│ Speaker — GPIO frequency toggle │
└──────────────────┬───────────────────────┘
│ 4-bit parallel
▼
┌─────────────────┐
│ HD44780 LCD │
│ Line 1: data │
│ Line 2: anim │
└─────────────────┘
| Component | Details |
|---|---|
| Microcontroller | MSP430G2553 (TI LaunchPad) |
| Encoder | 3D-printed wheel, 10 slots, 40mm radius |
| Sensor | Photo interrupter (slot detection) |
| Display | 2×16 HD44780 LCD (4-bit interface) |
| Buttons | 2× push button (active-low, internal pull-up) |
| LED | Status indicator — flashes on each slot |
| Speaker | GPIO-toggled audio (bonus feature) |
Photo: docs/hardware_photo.jpg
| Component | Implementation |
|---|---|
| LCD driver | 4-bit parallel, nibble writes, HD44780 init sequence |
| Custom characters | CGRAM loaded at init — 4-frame spinning animation |
| Encoder sensing | GPIO interrupt, rising edge, internal pull-up |
| Button input | GPIO interrupt, falling edge, software debounce in main loop |
| RPM calculation | TimerA0 500ms periodic ISR counts slots per interval |
| Display modes | 3 modes cycled by PB2: Count → Distance → Speed |
| Bottom line | Animated custom character when turning, blank when stopped |
| Speaker | GPIO frequency toggling — brief chirp on each slot event |
| Sleep | LPM0 between events — woken by ISR |
RPM = (slots_in_500ms × 60000) / (slots_per_rev × 500)
= slots_in_500ms × 12
Arc per slot = 2π × 40mm / 10 = 25.1mm ≈ 25mm
Distance (mm) = total_slot_count × 25
Direct register access — no abstraction layer between code and hardware:
/* TimerA0: SMCLK source, /8 divider, Up mode, 500ms interval */
TA0CCR0 = 62500;
TA0CTL = TASSEL_2 | ID_3 | MC_1;
/* GPIO interrupt: rising edge, pull-up enabled */
P1REN |= ENCODER_PIN;
P1OUT |= ENCODER_PIN;
P1IES &= ~ENCODER_PIN;
P1IE |= ENCODER_PIN;Interrupt Service Routines with explicit vector declarations:
#pragma vector=PORT1_VECTOR
__interrupt void PORT1_ISR(void) { ... }
#pragma vector=TIMER0_A0_VECTOR
__interrupt void TIMER0_A0_ISR(void) { ... }Volatile variables for ISR-shared state — prevents compiler optimisation of variables modified in interrupt context:
volatile uint16_t slot_count = 0;
volatile uint16_t slots_in_interval = 0;
volatile uint8_t update_display = 0;CGRAM character loading — writing 5×8 pixel bitmaps directly to LCD controller character RAM:
lcd_cmd(0x40 | (char_index * 8)); /* CGRAM address */
for (j = 0; j < 8; j++) {
lcd_data(pixel_row[j]); /* 5 active bits per row */
}LPM0 sleep — processor sleeps between events, woken by interrupts:
__bis_SR_register(LPM0_bits); /* sleep */
__bic_SR_register_on_exit(LPM0_bits); /* wake from ISR */Software debounce — buttons disable their own interrupt in ISR, debounced in main loop, re-enabled after confirmation:
/* In ISR — disable and flag */
P1IE &= ~BTN_MODE;
btn_mode_pressed = 1;
/* In main loop — debounce then re-enable */
__delay_cycles(20000);
if (!(P1IN & BTN_MODE)) { /* confirm still pressed */
display_mode = (display_mode + 1) % NUM_MODES;
}
P1IE |= BTN_MODE;GPIO audio output — speaker driven by toggling a GPIO pin at audio frequency. No DAC, no PWM peripheral — just deterministic bit toggling:
for (i = 0; i < 50; i++) {
P1OUT ^= SPEAKER_PIN;
__delay_cycles(500); /* ~1kHz at 1MHz SMCLK */
}| Mode | Line 1 Content | Toggled By |
|---|---|---|
| 0 | Count: NNN |
PB2 |
| 1 | Dist: NNNN mm |
PB2 |
| 2 | Speed: NNNN RPM |
PB2 |
Line 2 shows a 4-frame spinning animation using custom CGRAM characters while the wheel is turning, and is blank when stopped.
PB1 resets count and RPM to zero at any time.
Requirements: Code Composer Studio (TI) or msp430-gcc
# With msp430-gcc
msp430-gcc -mmcu=msp430g2553 -O2 -o encoder.elf firmware/main.c
mspdebug rf2500 "prog encoder.elf"CCS: Create new CCS project, target MSP430G2553, add main.c.
Beyond the base assignment requirements:
- Speaker audio feedback — GPIO frequency toggling generates a chirp on each encoder slot detection
- LPM0 sleep mode — processor sleeps between events rather than busy-waiting, demonstrating power-aware embedded design
- Splash screen — startup display showing system identity before entering scan mode
This project complements the 3D Scanner repository:
- The 3D scanner demonstrates multidisciplinary systems integration — MicroPython, BLE, Python GUI, sensor fusion, mechatronic design
- The MSP430 encoder demonstrates low-level embedded foundations — register-level C, ISRs, timer peripherals, direct hardware interfacing
Together they show a range from bare-metal firmware through to full-stack mechatronic systems integration.
Developed as part of ELE3DEC — Digital Electronics and Controllers,
La Trobe University, Semester 1 2024.
Subject Coordinator: Associate Professor Robert Ross.