Building a Digital Sequencer DSL for Embedded Systems
Digital Sequencer Inputs#
While thinking about the digital sequencer, I’ve also been considering how inputs should work.
For outputs, it is straightforward to shift pin states out using three microcontroller lines. On something like an ESP32-C3 or ESP32-C6, a 74HC595 provides a straightforward way to achieve this.
For inputs, I would also like a way to read multiple input states while using as few GPIO pins as possible. A 74HC165 seems like a sensible option here, as it provides parallel-in, serial-out operation — effectively the inverse of the 74HC595.
That said, there is little value in focusing on the surrounding circuit until the control model and instruction system are properly defined.
Digital Sequencer DSL#
At the moment, I’m thinking in terms of a small domain-specific language (DSL) for defining sequencer behaviour. It is not yet fully defined and there are still gaps to be addressed before implementation begins.
A package represents a complete sequencer configuration and contains one or more programs that execute independently.
Each program consists of two sections:
- a declaration section
- a processing section
A program is defined using the following structure:
PROGRAM name_of_program
DECLARE:
<DECLARATIONS>
PROCESS:
BEGIN
<PROGRAM INSTRUCTIONS>
END
All executable instructions must appear within the BEGIN and END block of the PROCESS section.
The PROCESS section defines the execution logic of the program.
Declarations#
Within the declaration section, logical names are mapped to physical inputs or outputs using the following syntax:
MAP <Name> AS <INPUT|OUTPUT> USING <PHYSICAL_PIN_IDENTIFIER>
A physical pin identifier is an implementation-defined value representing a hardware input or output channel.
Input and output pin identifiers refer to distinct hardware resources and cannot overlap at the hardware level.
Inputs may be evaluated based on level (HIGH/LOW) or edge transitions (RISING/FALLING). The exact semantics will be defined as part of the instruction set.
Example#
MAP DOOROPEN AS OUTPUT USING 0
MAP DOORSWITCH AS INPUT USING 0
In this case:
DOOROPENis declared as an output mapped to physical output pin0DOORSWITCHis declared as an input mapped to physical input pin0
Naming Rules#
Each symbolic input or output name:
- may be up to 255 characters long
- must begin with a letter or underscore
- may contain only:
A-Za-z0-9_
Valid Examples
- DOOROPEN
- DOOR_SWITCH
- _INTERNAL_FLAG
Invalid Examples
- 1STOUTPUT
- DOOR-OPEN
- DOOR OPEN
Symbolic names are case-sensitive.
Symbolic names must not match reserved keywords (case-insensitive).
Each symbolic name must be unique within a program and may not be redefined.
Pin Allocation Rules#
- Physical input identifiers and physical output identifiers exist in separate namespaces
INPUT 0andOUTPUT 0are not considered the same resource- Each physical pin may only be mapped once within a package
- Any duplicate mapping must result in a validation error
Language Rules#
To keep execution predictable, the following rules apply:
- A package must contain one or more
PROGRAMdefinitions - Each
PROGRAMmust include:- a
DECLAREsection - a
PROCESSsection
- a
- The
DECLAREsection defines all inputs and outputs used by the program - The
PROCESSsection contains executable instructions - A program may only read or write pins that it has declared
Reserved Keywords#
PROGRAM
DECLARE
PROCESS
BEGIN
END
MAP
AS
USING
INPUT
OUTPUT
HALT
Keywords are case-insensitive.
Program Execution Model#
Each program executes independently.
The PROCESS block is implicitly executed in a continuous loop. Execution restarts from the beginning of the block after reaching END, unless a HALT instruction has been encountered.
The HALT instruction terminates the execution of the current program. Once halted, the program will not restart unless explicitly reloaded or reset.
Programs may execute concurrently, however they must not share physical resources due to the pin allocation rules defined above.
The execution model is deterministic, meaning that instructions are processed in the order they are defined within the PROCESS block.
Validation occurs before execution begins. Programs containing validation errors must not be executed.
Compilation Model#
The intention is for the DSL to be compiled into a compact bytecode representation rather than interpreted directly from source text at runtime.
This would allow symbolic names, declarations, and validation rules to be resolved ahead of execution, reducing runtime overhead and making the sequencer logic easier to execute on constrained hardware.
In practice, this means the source DSL would act as a human-readable authoring format, while the target system would execute a validated and precompiled instruction stream.
Example (Conceptual)#
While the instruction set is not yet fully defined, a simple program might look like the following:
PROGRAM door_control
DECLARE:
MAP DOOROPEN AS OUTPUT USING 0
MAP DOORSWITCH AS INPUT USING 0
PROCESS:
BEGIN
IF DOORSWITCH IS HIGH THEN
SET DOOROPEN HIGH
WAIT 2000ms
SET DOOROPEN LOW
ENDIF
END
Notes#
At this stage, the focus is on defining a clear and predictable control model rather than the underlying hardware implementation.
The next step will be to define the instruction set used within the PROCESS block, including:
- output control (
SET,PULSE) - timing (
WAIT) - conditional logic (
IF,THEN,ENDIF) - input-triggered execution
These will form the core of how sequences are expressed and executed.
If you need custom control hardware, programmable timing systems, or embedded firmware designed for your automation needs, discover how we build solutions at Protoworks (NI).
- 2026-10-17 Refining the Protoworks service offerings Lab
- 2026-09-27 Belfast Tech Week Lab
- 2026-09-19 Refining the UCMOD Architecture R&D
- 2026-09-06 Giving AI Agents a Way to Talk to Protoworks Software
- 2026-09-03 A Morning at a Manufacturing Trade Show Lab
- 2026-09-01 Debugging 921600 Baud on the ATtiny1614 R&D