A deep dive into the system architecture, from LISP source to hardware execution.
HIL is a Hardware-in-the-Loop language with three layers:
The host LISP interpreter runs your code directly on your computer. No Arduino, no simulator, no hardware needed. This is the fastest way to test logic, algorithms, and control flow.
The host monitor interprets every LISP expression in real-time. Serial commands like WRITE and TONE are logged instead of sent to hardware.
Your LISP code is transpiled to Arduino C, compiled to a .hex firmware, and flashed to a real ATmega328P board.
The transpiler converts each LISP S-expression into equivalent C code, including function wrappers for hardware calls.
Same as Arduino hardware, but the .hex is loaded into the CRUMB circuit simulator instead of real hardware.
The command-line interface. Orchestrates all workflows, simulation, compilation, CRUMB generation, firmware flashing. Built in Go for single-binary distribution.
Parses S-expressions into an AST. Handles hex literals, float literals, string concatenation, Arduino pin notation, and nested expressions. Unicode-safe with BOM stripping.
Converts LISP AST to Arduino C source code. Generates complete .ino files with setup/loop, helper functions (servo, LCD, DHT, PID, NeoPixel), and proper Arduino API calls.
Real-time LISP evaluator for headless simulation. Maintains environment with variables, functions, and sensor state. Includes all 15+ operators, PID control, and event system.
Creates .cru XML files compatible with the CRUMB Circuit Simulator. Injects transpiled Arduino C into the CRUMB template with proper component wiring.
TextMate grammar for syntax highlighting, 20+ code snippets, language configuration for bracket matching, and build tasks for simulation and CRUMB.
Each LISP expression maps to specific Arduino C code:
| LISP | Arduino C |
|---|---|
(WRITE D 13 1) | digitalWrite(13, HIGH); |
(READ A 0) | analogRead(A0); |
(WRITE P 9 128) | analogWrite(9, 128); |
(DELAY 500) | delay(500); |
(TONE 11 440 200) | tone(11, 440, 200); |
(SERVO 9 90) | myServo.write(90); |
(SET x 10) | int x = 10; |
(IF (> x 5) ...) | if (x > 5) { ... } |
(WHILE 1 ...) | while (true) { ... } |
(FOR i 0 10 ...) | for (int i = 0; i < 10; i++) { ... } |
(DEFUN FOO (a b) ...) | int FOO(int a, int b) { ... } |
Every LISP expression returns a numeric value. Strings are stored as variables and passed through LOG. The environment is a flat key-value store — no scoping, no types.
HIL is designed to be fault-tolerant:
This makes it safe for embedded use where crashing is not an option.