Oberon RTK

gen-pio-oberon

Generate Oberon modules from PIO assembly code for RP2040 and RP2350 microcontrollers

Name

gen-pio-oberon – generate an Oberon module from PIO assembly code

Synopsis

gen-pio-oberon <input_file> [-o <module_name>] [-v <version>] [--verbose]

Description

gen-pio-oberon converts PIO (Programmable I/O) assembly code into an Oberon source module that can be compiled and linked with an Astrobe program. The generated module provides a GetCode procedure that populates a PIO.Program record with the assembled instructions and configuration data, ready for loading into the PIO hardware on RP2040 and RP2350 microcontrollers.

How It Works

The tool performs three steps:

  1. extracts PIO assembly source from the input file
  2. runs the external pioasm assembler to produce JSON output containing the assembled instructions and metadata
  3. generates an Oberon module from the JSON data

The PIO assembly listing is included as a comment in the generated module for reference.

Input Formats

PIO assembly code can be provided in two ways:

Embedded in an Oberon module (.mod) – the PIO code is placed inside a comment block delimited by PIOBEGIN and PIOEND keywords:

(*
  PIOBEGIN
    .program square_wave
      set pindirs 1
    loop:
      set pins 1 [1]
      set pins 0
      jmp loop
  PIOEND
*)

This keeps the PIO source together with the Oberon program that uses it.

Separate PIO file (.pio) – a standalone file containing only PIO assembly code, without the delimiters.

Generated Module

The output module contains a single exported procedure:

PROCEDURE GetCode*(progName: ARRAY OF CHAR; VAR prog: PIO.Program);

The caller passes the PIO program name and receives the fully populated PIO.Program record containing:

  • assembled instructions as 16-bit hex values
  • instruction count
  • wrap target and wrap indices
  • origin address
  • sideset configuration (size, optional, pindirs)
  • public labels and symbols

If the input file contains multiple PIO programs, GetCode selects the matching program by name.

Using the Generated Module

In the Oberon program, import the generated module and call GetCode to retrieve the PIO program data:

PIOsquarePio.GetCode("square_wave", prog);
PIO.PutCode(pio, sm, prog);
PIO.ConfigWrap(pio, sm, prog);

PIO.PutCode loads the instructions into the PIO instruction memory, and PIO.ConfigWrap configures the wrap addresses for efficient looping.

Supported platforms: Windows and macOS.

Prerequisites

The pioasm assembler (version 2.x or later) must be on the system PATH. Earlier versions do not support JSON output. See External Tools for installation details.

Arguments

input_file
input file containing PIO assembly code. Can be an Oberon module (.mod) with embedded PIO code in a PIOBEGIN / PIOEND block, or a standalone PIO file (.pio).

Options

-o module_name
name of the generated Oberon module. Default: the input file stem with Pio appended (for example, PIOsquare.mod produces PIOsquarePio.mod).
-v version
PIO instruction set version passed to pioasm (0 or 1). Default: 1. Version 0 is the original RP2040 instruction set; version 1 adds RP2350 extensions.
--verbose
print details: file paths, processing steps, pioasm version.

Output

The generated module is written to the same directory as the input file. The default name is the input file stem with Pio appended and the .mod extension (for example, PIOsquare.mod produces PIOsquarePio.mod).

Two temporary files are created during processing:

  • _<stem>.pio.pio – extracted PIO assembly (input to pioasm)
  • _<stem>.pio.json – pioasm JSON output

These files are prefixed with _ and can be deleted after the module is generated.

Examples

Complete Example

The program module PIOsquare.mod contains two PIO programs embedded in a comment block:

(*
  PIOBEGIN
    .pio_version 0

    .program square_wave
      set pindirs 1
    loop:
      set pins 1 [1]
      set pins 0
      jmp loop

    .program square_wave_asym
      set pindirs 1
      .wrap_target
      set pins 1 [1]
      set pins 0
      .wrap
  PIOEND
*)

Run gen-pio-oberon on the module:

gen-pio-oberon PIOsquare.mod

Created Oberon module PIOsquarePio.mod in .

This generates PIOsquarePio.mod containing a GetCode procedure with the assembled instructions for both programs. The generated module selects the program by name:

PROCEDURE GetCode*(progName: ARRAY OF CHAR; VAR prog: PIO.Program);
  VAR i: INTEGER;
BEGIN
  IF progName = "square_wave" THEN
    prog.name := "square_wave";
    prog.wrapTarget := 0;
    prog.wrap := 3;
    prog.origin := -1;
    prog.sideset.size := 0;
    prog.sideset.optional := FALSE;
    prog.sideset.pindirs := FALSE;
    prog.instr[0] := 0E081H;
    prog.instr[1] := 0E101H;
    prog.instr[2] := 0E000H;
    prog.instr[3] := 00001H;
    prog.numInstr := 4;
    ...
  ELSIF progName = "square_wave_asym" THEN
    ...
  END
END GetCode;

The Oberon program imports the generated module and loads the PIO code into the hardware:

PIOsquarePio.GetCode("square_wave", prog);
PIO.PutCode(pioDev, prog.instr, offset, prog.numInstr);
PIO.ConfigWrap(pioDev, SM0, prog.wrapTarget, prog.wrap);
PIO.SetStartAddr(pioDev, SM0, offset);

Other Invocations

Generate from a standalone PIO file:

gen-pio-oberon square_wave.pio

Generate with a custom module name:

gen-pio-oberon PIOsquare.mod -o SquareWavePio

Generate for RP2040 instruction set (version 0):

gen-pio-oberon PIOsquare.mod -v 0

Diagnostics

  • "input file must be '.mod' or '.pio'" – the input file has an unsupported extension
  • "could not open and read input file" – the input file does not exist or cannot be read
  • "no PIOBEGIN … PIOEND block found" – a .mod input file does not contain a valid PIO code block
  • "'pioasm' found problems" – the pioasm assembler reported errors in the PIO assembly code

Notes

gen-pio-oberon can be added to Astrobe's Tools menu for convenient access during development. Configure the entry in Astrobe's Tools.ini file.

Each PIO state machine on the RP2040 and RP2350 has a maximum of 32 instructions. The .wrap and .wrap_target directives in PIO assembly create efficient loops without using a jump instruction, preserving instruction space.

See Also

make-uf2

Last updated: 7 March 2026