Overview
This program demonstrates the use and integration of PIO assembly programs with an Oberon program. The focus is on the set-up and toolchain, not yet any real use of PIO. The PIO programs simply output a square-ish wave signal on two GPIO pins, without any interaction via the PIO FIFOs and all that good stuff.
Tool pio2o
The tool pio2o
runs the pioasm
assembler to create corresponding binary code, and then generates an Oberon module to access that code for loading it into the PIO instruction registers, including the .wrap
and .wrap_target
parameters.
See pio2o for more information.
Program Description
The PIO assembly source code – borrowed from the RP2040 datasheet – is contained directly in the program module PIOsquare.mod:
(*
PIOBEGIN
.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
*)
Keeping the PIO code in the Oberon module may or may not be a good idea, this requires more practical exploration. PIO programs usually are a few instructions (lines) only, and can encompass maximally 32 instructions.
Alternatively, PIO code can be put into a separate file with extension .pio
(without the PIOBEGIN
and PIOEND
markers, of course). There’s a corresponding file PIOsquare.pio
in the example program directory.
Running pio2o
on PIOsquare.mod
, either via command line, or the Astrobe tools menu, extracts the above PIO code, runs it through the pioasm
assembler, and creates an Oberon module named PIOsquarePio
, that is, by default Pio
gets added to the source module name.1 Normally, we wouldn’t keep generated files in the repo, I include it here for easy reference in this context.
Program module PIOsquare
IMPORTs PIOsquarePio
, and gets the binary code via PIOsquarePio.GetCode
as an array, which is then loaded into the PIO device’s instruction registers using PIO.PutCode
.
Since we have two PIO programs, they need to be loaded into the instruction registers at different locations. PIOsquarePio.GetCode
also provides the two address values for .wrap
and .wrap_target
, which need to be configured for the state machines using PIO.ConfigWrap
.
By default, all state machines start executing at address zero, hence to run the program loaded at the higher address, the starting address needs to be set with PIO.SetStartAddress
for one of the state machines.
Module PIO
The example program uses a rudimentary first version of device module PIO
, to access the registers of the two PIO peripherals and their state machines, plus a few required procedures to implement this program. his module is likely to change.
Signal Output
Traces:
- yellow: output from state machine 0
- blue: output from state machine 1
The square wave from state machine 0 (yellow) has a measured frequency of about 478 Hz. The clock divider for the state machine is set to 65,536, hence it runs at 125 MHz / 65,535 = 1.91 kHz
. The PIO program above sets the GPIO pin to 1, and delays for one clock cycle using [1]
. Then the pin is set to 0 in the next cycle, with the jmp
taking another clock cycle, hence the state machine clock rate is divided by 4, so 1.91 kHz / 4 = 477 Hz
. Close enough.
The output of state machine 1 (blue) shows the effect of using .wrap
and .wrap_target
. Since we can omit the jmp
instruction, the second half of the wave is only one state machine clock cycle long. Using the wrap mechanics saves us one instruction and thus one storage register of the precious 32 in total per PIO device.
Output Terminals
See Set-up, one-terminal set-up. No much text output, though. It’s the signal that matters in this case.
Build and Run
- Configure Astrobe
- Run
pio2o
on modulePIOsquare
to generatePIOsquarePio
. - Build and run
Repository
- libv2: for RP2040/Pico and RP2350/Pico2: PIOsquare
- lib (v1): this version is no longer being maintained: PIOsquare
-
You can optionally specify a different output module name using
-o module_name
. ↩︎