Pneumatic kiln door control system built on the BSI Machine Controller M4 (ATSAMD51J20A, 120 MHz Cortex-M4F).
| Component | Interface | Notes |
|---|---|---|
| ILI9341 320x240 TFT | SPI (SERCOM 5 ALT) | Landscape rotation 3 |
| FT6206 capacitive touch | I2C (SERCOM 3) | Shared reset with TFT |
| 6x WS2812B NeoPixel LEDs | GPIO (PIN_LED_DATA) | Status + per-function indicators |
| 14x digital inputs | MOSFET-isolated, 5 V pull-up | Buttons active HIGH, E-Stop NC (active LOW) |
| 8x digital outputs | MOSFET-driven 24 VDC | HIGH = ON at terminal |
| 6x analog inputs | Opamp-buffered /5, 0-10.24 V range | ADC ref: 2.048 V VREFA |
| 2x analog outputs (DAC) | Opamp-buffered x5 | Provisioned, not used |
| RS485 (SERCOM 2) | Half-duplex | Provisioned, not used |
Defined in src/hardware/wiring_def.h:
Buttons (momentary, active HIGH):
- DI1: Open, DI2: Close, DI3: Lock, DI4: Release, DI5: E-Stop Panel (NC, active LOW), DI12: E-Stop Door (NC, active LOW)
Sensors (active HIGH):
- DI6: Door Open, DI7: Door Closed, DI8: Locked, DI9: Lock Lowered, DI10: Lock Raised, DI11: Human Detected
Actuators (active HIGH = 24 VDC):
- DO1: Open (raise door) — dual solenoid, momentary
- DO2: Close (vent raise side) — dual solenoid, momentary
- DO3: Lock Top — single solenoid, spring-return; OFF = retracted/locked, ON = extended (wiring inverted)
- DO4: Lock Bottom — single solenoid, spring-return; ON = engaged
- DO5: Lock Raise — single solenoid, spring-return; ON = raised
Solenoid hold rule: The Open/Close ram uses two separate valves (energise to move, OFF when stationary). The lock-bottom and lock-raise solenoids each use a single spring-return valve and must remain energised to hold position. Lock Top has inverted wiring: spring-return goes to the retracted (locked) position, so it is safe on power loss — it only needs power to extend. In IDLE, FAULT, and PAUSED states the controller maintains the last commanded output state — sensors confirm operations but do not drive outputs.
Analog:
- AI1: Air pressure (FESTO SPTE-P10R-Q4-V-2.5K, 0-10 V = 0-10 bar)
Controller connections:
Valve box — solenoid valves and pressure sensor:
Position sensors:
src/
main.cpp Main loop: inputs -> FSM -> LEDs -> LVGL -> UI
config.h Compile-time constants (timeouts, gains, debounce)
hardware/
pin_def.h Arduino pin numbers (matches variant.cpp)
wiring_def.h Application signal aliases + operation description
led_def.h LED addresses and colour definitions
drivers/
display.h/.cpp LVGL 9 display driver (ILI9341 via SPI)
touch.h/.cpp LVGL 9 touch driver (FT6206 via I2C)
led_manager.h/.cpp NeoPixel manager with solid/pulse/breathe effects
control/
input_reader.h/.cpp Debounced digital inputs + analog readings
output_controller.h/.cpp Safe outputs with OPEN/CLOSE interlock
door_fsm.h/.cpp Goal-oriented door state machine
ui/
ui_common.h Shared colour palette
ui_manager.h/.cpp Screen navigation (fade transitions)
ui_home.h/.cpp Home screen: door state, sensors, analog
ui_io.h/.cpp I/O diagnostic page: all inputs/outputs/analog
ui_outputs.h/.cpp Manual output control page (compile-time optional)
| Step | Outputs | Hold outputs | Wait for |
|---|---|---|---|
| 1. Unlock | lock_top ON (extend), lock_bottom OFF | lock_raise holds current position | locked sensor LOW |
| 2. Raise locks | lock_raise ON, lock_top ON (extended) | lock_raised sensor HIGH | |
| 3. Retract top locks | lock_top OFF (retract), lock_raise ON | 2 s timeout (no sensor) | |
| 4. Open door | open ON, lock_top OFF (retracted), lock_raise ON | door_open sensor HIGH | |
| IDLE (open) | open OFF, close OFF | lock_top OFF (retracted), lock_raise ON | — |
| Step | Outputs | Hold outputs | Wait for |
|---|---|---|---|
| 1. Close door | close ON, lock_top OFF (retracted), lock_raise ON (hold) | door_closed sensor HIGH | |
| 2. Extend top locks | lock_top ON (extend), lock_raise ON | 2 s timeout (no sensor) | |
| 3. Lower locks | lock_raise OFF (spring return) | lock_top ON (extended) | lock_lowered sensor HIGH |
| 4. Lock | lock_top OFF (retract), lock_bottom ON | lock_raise OFF | locked sensor HIGH |
| IDLE (locked) | open OFF, close OFF | lock_top OFF (retracted), lock_bottom ON, lock_raise OFF | — |
Only available when the door is fully closed. Lock and Release buttons trigger just the locking/unlocking portion of the sequence. The Lock sequence includes the extend → lower → engage steps if locks are not already lowered.
After a power loss, all outputs default to OFF (spring-return). Lock Top has inverted wiring so it springs to the retracted (safe) position automatically. However, the lock-raise solenoid also springs OFF (lowered), so the lock carriage drops. If a Close or Open goal is started while the locks are not in the expected position, the FSM detects this and runs the necessary preparatory steps (raise, retract) before any ram movement.
If a different button is pressed mid-sequence, the FSM stops the current operation and starts the new one from the current physical state (determined by sensors). Steps already satisfied are skipped automatically.
- OPEN/CLOSE interlock: Outputs are mutually exclusive. Setting one forces the other off first.
- E-Stop: Two E-Stop inputs (Panel DI5, Door DI12) — either triggers the same response. Ram movement stopped immediately; solenoid hold outputs maintained. If door is NOT closed+locked, the open sequence runs automatically to reach a safe state (with appropriate holds). If closed+locked, locks remain engaged. No other buttons accepted while active. UI shows which E-Stop is active (Panel, Door, or Both).
- Human detection: Pauses the current operation — ram movement stopped, solenoid positions held via sensor feedback. Resumes automatically when sensor clears. No effect when disconnected (sensor reads LOW).
- Hold-to-operate (compile-time, disabled by default): When
HOLD_TO_OPERATEis set to 1 inconfig.h, the operator must hold the Open or Close button during door ram movement. Releasing the button pauses the ram and suspends the step timeout; pressing and holding again resumes. Lock manipulation steps run automatically and are not affected. - Step timeouts: Each FSM step has a configurable timeout (in
config.h). If exceeded, the system enters FAULT state — ram movement stopped, solenoid positions held. Operator must press RESET on the touch screen. - Startup: All outputs OFF. A 500 ms grace period (
STARTUP_GRACE_MS) lets inputs stabilise, then a startup-idle lock prevents the FSM from driving any outputs until the operator presses a physical button (Open, Close, Lock, or Release). This ensures transient E-Stop or sensor readings at power-on cannot unexpectedly activate solenoids. Entering the Manual Outputs page also clears the startup lock. - FAULT / PAUSED hold: Single-solenoid valves (locks, lock-raise) are maintained in their current position based on sensor state, preventing uncontrolled spring-return movement.
| State | Description |
|---|---|
| IDLE | No operation, position determined by sensors |
| UNLOCKING | Disengaging locks |
| RAISING_LOCKS | Raising upper locks to clear door path |
| RETRACTING_LOCKS | Retracting top locks while raised (timed, no sensor) |
| OPENING | Door opening |
| REOPENING | Auto re-raising door after droop detection (3 s timeout) |
| CLOSING | Door closing |
| EXTENDING_LOCKS | Extending top locks before lowering (timed, no sensor) |
| LOWERING_LOCKS | Lowering upper locks into position |
| LOCKING | Engaging locks |
| ESTOP | E-Stop active |
| FAULT | Timeout or error, requires manual reset |
| PAUSED | Human detected, auto-resumes when clear |
| LED | Address | Function |
|---|---|---|
| Status | 0 | Green breathe = normal, Amber breathe = operating, Red pulse = fault/E-Stop |
| E-Stop | 1 | Green solid = normal, Red pulse = panel E-Stop active (door E-Stop uses status LED only) |
| Open | 2 | Blue breathe = opening/re-raising, Green solid = open |
| Close | 3 | Amber breathe = closing, Green solid = closed |
| Lock | 4 | Magenta breathe = locking/lowering/extending, Green solid = locked |
| Release | 5 | Yellow breathe = unlocking/raising/retracting, Green solid = released |
When idle, the expected physical state is determined from commanded outputs. If any sensor contradicts the expected state, the relevant LED flashes red pulse to alert the operator. The Status LED switches to amber pulse when any warning is active.
| Expected State | Commanded Outputs | Open LED red if | Close LED red if | Lock LED red if |
|---|---|---|---|---|
| Open & Retracted | raise ON, lock_top OFF, lock_bot OFF | door not open | door closed | locked, not raised, or lowered |
| Closed & Locked | raise OFF, lock_top OFF, lock_bot ON | door open | door not closed | not locked, raised, or not lowered |
| Closed & Unlocked | raise OFF, lock_top ON, lock_bot OFF | door open | door not closed | locked, raised, or not lowered |
This covers failed operations, unexpected states at power-on, and uncommanded position changes (e.g. air leaks causing ram droop).
All tuning parameters are in src/config.h (compile-time only, not runtime configurable):
- Debounce: 30 ms buttons, 20 ms sensors
- Timeouts: 5-16 s per step (approx 1.5x expected mechanical duration)
- Analog scaling: ADC ref 2.048 V, input gain x5, PSU divider 14.333:1
- Pressure sensor: 0-10 V = 0-10 bar, displayed with 1 decimal place
- LED brightness: 0-255 (compile-time)
- Safety thresholds: PSU 20-26 V, air pressure 5-7 bar (operations refused if out of range)
- Manual output page:
MANUAL_OUTPUT_PAGE_ENABLED(1 = enabled, 0 = hidden)
Screens are navigated by swiping left/right (no on-screen buttons). Screen order: Home → I/O → Manual Outputs (if enabled).
Primary operator view:
- Door state (large, colour-coded: green=idle, amber=operating, red=fault/E-Stop)
- Current goal and step elapsed timer (seconds, only during active operations)
- All sensor flags (open, closed, locked, locks raised/lowered)
- PSU voltage and air pressure (fixed-position labels)
- E-Stop and human detection status
- FAULT reset button (visible only in fault state)
Commissioning and troubleshooting view:
- All 12 digital input states with colour indicators
- All 5 output states with colour indicators
- All 6 analog input voltages, PSU voltage, and air pressure in engineering units
Direct output control and safety check bypass for commissioning:
- Switch per output (amber track = ON, dark = OFF)
- Safety check bypass switches: Pressure, PSU Voltage, and Human Detection (green = check enabled, red = bypassed)
- Warning popup on entry: "Safety features will not work!" with Confirm / Go Back
- On Confirm: all outputs set OFF, FSM bypassed, all switches enabled
- On Go Back: outputs unchanged, returns to Home
- Swiping away returns output control to the FSM; outputs held until the next user-initiated operation (button press)
- Safety check bypass states persist across page navigations
Before accepting a new goal from physical buttons, the FSM checks:
- Air pressure: must be 5-7 bar (if pressure check enabled)
- PSU voltage: must be 20-26 V (if voltage check enabled)
If a reading is out of range and its check is enabled, the button press is ignored and a message is logged to serial. E-Stop emergency open always works regardless of these checks. All checks default to enabled on boot and can be bypassed from the Manual Outputs page.
If the door slowly drops under its own weight while in the open position (e.g. due to a pneumatic leak), the FSM detects the open sensor going inactive and automatically re-raises the door:
- Conditions: IDLE, no active goal, lock_top and lock_raise outputs ON (open position), open sensor inactive, not in manual mode.
- The open ram is activated for up to 3 s (
TIMEOUT_RERAISE_MS). - If the open sensor re-activates within the timeout, the FSM returns to IDLE.
- If the timeout expires without the sensor re-activating, the FSM enters FAULT.
This replaces the previous behaviour where a sensor mismatch warning was the only indication of droop.
The lock sensor is only considered active when the door closed sensor AND locks lowered sensor are also active. This prevents false lock readings caused by the door swinging into the lock sensor when in the open/raised position, which would otherwise cause the FSM to spuriously cycle lock outputs (particularly during E-Stop recovery).
# Build
pio run -e bsi_machine_ctrl_m4
# Upload via Atmel-ICE
pio run -e bsi_machine_ctrl_m4 --target upload- lvgl/lvgl ^9.1.0
- adafruit/Adafruit NeoPixel ^1.15.4
- adafruit/Adafruit ILI9341 ^1.6.1
- adafruit/Adafruit FT6206 Library ^1.1.0
- adafruit/Adafruit GFX Library ^1.11.11
scripts/lvgl_build.pyexcludes ARM Helium/NEON assembly files from LVGL (not supported on Cortex-M4)include/lv_conf.hconfigures LVGL: 16-bit colour, 48 KB memory pool, Montserrat 14/16/20/24 fonts- Custom board variant in
hardware/board-def/with DAC workaround for Adafruit core bug - Pin mapping: DI 0-13, Analog 14-23 (A0=14), DO 24-31, peripherals 32+



