A bare-metal traffic-light controller for a two-approach intersection (North/South) with pedestrian service, FSR weight sensors, maintenance override, and dual 16×2 LCDs via I²C (MCP23008).
Written in C/C++ directly against AVR registers (GPIO/ADC/INT/Timer). Deterministic timing via a 1 kHz Timer1
tick and fast Serial logs.
- Deterministic timing: Timer1 CTC @ 1 kHz (1 ms scheduler tick).
- Global time scaling:
SPEED_PCT
scales all timings (100 = real time
,10 = 10× faster
, e.g.,25 ≈ 4× faster
). - I²C @ 400 kHz: Snappy dual-LCD updates (
A4 = SDA
,A5 = SCL
) via MCP23008. - Pedestrian priority: Pressing the ped button on the current side can cut GREEN immediately (after minimum green).
- Fairness & safety: Ped requests are served in the first ALL-RED after press with a “WALK NOW” window.
- Maintenance mode: Dominant override — blinking YELLOW on both approaches until released.
- Efficient LCD updates: Re-renders only on state changes (no countdown spam).
- Verbose logging: Human-readable events/phase changes over Serial @ 230400 baud.
Subsystem | Purpose | Key APIs/Regs |
---|---|---|
FSM | Phased intersection control | phase_t , fsm_step() |
GPIO | LEDs + buttons + maintenance | PORTx/DDRx/PINx |
ADC | FSR weight sensing (A1/A2) | ADMUX , ADCSRA , ADC |
INT | Pedestrian buttons (D2/D3) | INT0 , INT1 , EICRA , EIMSK |
Timer | 1 ms scheduler tick | Timer1 CTC (OCR1A=249 , prescale 64) |
I²C LCDs | Two 16×2 displays via MCP23008 | Wire , Adafruit_LiquidCrystal |
+-------------------+
| ALL_RED |
+---------+---------+
| (next: N)
v
+----------+-----------+
| N_GREEN |---> N_YELLOW ---> ALL_RED (+extra if ped)
+----------+-----------+
|
(next)
v
+----------+-----------+
| S_PRE_RED_AMBER |
+----------+-----------+
v
+----------+-----------+
| S_GREEN |---> S_YELLOW ---> ALL_RED (+extra if ped)
+----------------------+
- Ped on current side: Clamp GREEN to MIN and schedule extra ALL-RED; may cut immediately if current green > MIN.
- Ped on opposite side: Suppresses FSR-based green extension of current side.
- FSR extend: If current has weight and opposite has none, extend current green up to MAX.
- FSR cut: If no weight on current side, reduce to MIN.
All user-tunable constants live in config.h
. Times are human-readable seconds and scaled via SPEED_PCT
.
Symbol | Meaning | Default |
---|---|---|
SPEED_PCT |
Global time scale | 25 (≈4× faster sim) |
TICK_MS |
Scheduler tick | 1 ms |
GREEN_MIN_S |
Minimum green | 4 s |
GREEN_BASE_S |
Base green | 7 s |
GREEN_MAX_S |
Max green (with extend) | 10 s |
YELLOW_S |
Yellow duration | 1 s |
ALL_RED_S |
All-red safety | 2 s |
S_PRE_RED_AMBER_S |
South pre-red amber | 1 s |
GREEN_BLINK_LAST_S |
Green tail blink window | 3 s |
PED_RED_EXTEND_S |
Extra all-red for ped | 3 s |
FSR_ON_TH / OFF_TH |
Hysteresis thresholds | 300 / 250 |
Scaling macros: always use
SCALED_MS_FROM_S(sec)
/SCALED_MS(ms)
; avoid raw delays.
- Board: Arduino UNO R3 (ATmega328P @ 16 MHz)
- I²C: MCP23008-based 16×2 LCDs @
0x20
(North),0x21
(South) —A4=SDA
,A5=SCL
- LEDs:
- North (PORTD):
D5=GRN
,D6=YEL
,D7=RED
- South (PORTB):
D11=GRN
,D12=YEL
,D13=RED
- North (PORTD):
- Ped Buttons:
D2 (INT0)
North,D3 (INT1)
South (active-LOW with pull-ups) - Maintenance Switch:
A0
(active-LOW with pull-up) - FSR Sensors:
A1
(South),A2
(North)
Signal | Pin | Notes |
---|---|---|
North GREEN | D5 (PD5) | Output |
North YELLOW | D6 (PD6) | Output |
North RED | D7 (PD7) | Output |
South GREEN | D11 (PB3) | Output |
South YELLOW | D12 (PB4) | Output |
South RED | D13 (PB5) | Output |
Ped North Btn | D2 (INT0) | Input, pull-up |
Ped South Btn | D3 (INT1) | Input, pull-up |
Maintenance | A0 (PC0) | Input, pull-up, active LOW |
FSR South | A1 (ADC1) | Analog in |
FSR North | A2 (ADC2) | Analog in |
LCD North | I²C 0x20 |
MCP23008 |
LCD South | I²C 0x21 |
MCP23008 |
Two dev workflows are supported:
.
├─ arduino_ide/ # Flat layout for Arduino IDE
│ └─ STMS_Refactor/
│ ├─ stms.ino
│ ├─ config.h, pins.h, types.h
│ ├─ state.{h,cpp}, logging.{h,cpp}, gpio.{h,cpp}
│ ├─ adc.{h,cpp}, maint.{h,cpp}, timer.{h,cpp}, extint.{h,cpp}
│ ├─ phases.{h,cpp}, policy.{h,cpp}, blink.{h,cpp}, fsm.{h,cpp}
│ └─ lcd_ui.{h,cpp}, README.md
├─ platformio/ # PlatformIO project
│ ├─ platformio.ini
│ └─ src/
│ └─ (same module files as above)
└─ docs/
├─ overview.md # Module map & init order
└─ timing.md # Scaling details & notes
Why two layouts? Arduino IDE compiles files in the sketch root; PlatformIO prefers
src/
.
- Open
arduino_ide/STMS_Refactor/stms.ino
. - Select Board: Arduino Uno.
- Upload and open Serial Monitor @ 230400 baud.
cd platformio
pio run -t upload
pio device monitor -b 230400
Module | What it does |
---|---|
fsm.* |
One-step state machine; calls policy, blink, phases |
policy.* |
Ped/FSR rules: clamp/extend/ignore as needed |
phases.* |
enter_* helpers that set LEDs, timers, and logs |
blink.* |
Green tail blink + maintenance yellow blink |
extint.* |
INT0/INT1 ISRs for ped buttons |
timer.* |
Timer1 1 kHz tick + ISR (g_ms++ ) |
adc.* |
ADC init/read; FSR sampling with hysteresis |
gpio.* |
LED writes + GPIO init (buttons & pull-ups) |
lcd_ui.* |
Minimal LCD re-render on change |
logging.* |
Serial event helpers (timestamps in ms) |
state.* |
All extern globals (with volatile where needed) |
[0ms] [BOOT] Turbo mode SPEED_PCT=25%, starting ALL_RED then N_GREEN
[1234ms] [FSM] N_GREEN (7000 ms)
[2450ms] [N PED] Button pressed
[2450ms] [N PED] Immediate: Cut North green for North ped
[2451ms] [FSM] N_YELLOW (1000 ms)
[3452ms] [FSM] ALL_RED (2000 ms)
[3452ms] [N PED] Extra ALL_RED (WALK NOW)
SPEED_PCT
is your demo turbo: try25
for ≈4× speed.- Keep init order: Serial → Wire →
cli()
→ gpio/adc/timer/extint → FSM seed →sei()
→ LCD init → first render. - For reproducible tests, avoid changing scaling or thresholds between runs.
- Button bounce: ISRs set flags only; the logic is idempotent. If your hardware bounces excessively, add physical debouncing or extend the debounce at the interrupt source.
- Torn reads of
g_ms
: On AVR (8-bit), readg_ms
atomically if you access it outside the main loop tick. The default loop pattern already uses the ISR-driven scheduler correctly. - LCD artifacts: Strings are padded to clear leftovers. If you customize labels, keep line length ≤16 and pad with spaces.
- FSRs noisy? Tune
FSR_ON_TH
/FSR_OFF_TH
to your sensor range; hysteresis prevents chatter.
- Zero behavior change vs the single-file baseline: identical timings, ISR behavior, transitions, and LCD text.
volatile
globals preserved; ISRs remain minimal and deterministic.- LCD updates occur only on state changes.
PRs and issues welcome! Please:
- Keep any refactors behavior-identical unless explicitly flagged as optional.
- Add unit-style checks or log snippets demonstrating no timing/phase regressions.
- Match code style (C headers, small helpers, explicit side-effects in
enter_*
).
This project is released under the MIT License. You are free to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the software.
- LCD driver:
Adafruit_LiquidCrystal
- I²C: Arduino
Wire
Updated: 2025-09-21 12:22