PIO square-wave test on RP2040 / RP2350 always produces a static high level (Pico and Pico 2W)

Dear all,

I am still struggling with PIO on both the Raspberry Pi Pico (RP2040) and Pico 2W (RP2350) while preparing Chapter 6 of my Ada on the Raspberry Pi Pico tutorial. I would be very grateful for any insight from those who have successfully driven PIO state machines from Ada.

The goal of the minimal test is the simplest possible square wave (no DMA, no side-set, no structured data, just two set pins instructions). The expectation is an 800 kHz 50 % duty-cycle signal on GP2 (physical pin 4).

with RP.Device;
with RP.GPIO;
with RP.PIO;
with RP.Clock;
with Pico.UART_IO;

--  Generate a clean 800 kHz square wave on GP2 using PIO Tested on both Pico and Pico 2W (RP2040 / RP2350) This scetch
--  is meand to be used with an oscilloscope to verify the timing of the PIO program. You should see a perfect 1.25 µs
--  period, 50% duty cycle square wave on GP2 (physical pin 4). Optionally use CoolTerm or similar to monitor the debug
--  output.
procedure Square_Wave is
   --  The squarewave program above (you can also load it from .pio file via pioasm)
   --!pp off
   Squarewave_Program : constant RP.PIO.Program := [
                        --  .program squarewave
                        --  .wrap_target
       0 => 16#6001#,   --  set pins, 1 [0]     ; high for 1 cycle + delay
       1 => 16#6000#    --  set pins, 0 [0]     ; low  for 1 cycle + delay
                        --  .wrap
   ];
   --!pp on

   --  Choose any free GPIO. GP2 is convenient (pin 4).
   Pin          : RP.GPIO.GPIO_Point renames Pico.GP2;
   PIO          : RP.PIO.PIO_Device renames RP.Device.PIO_0;
   ASM_Offset   : constant RP.PIO.PIO_Address := 0;
   Freq         : constant                    := 800_000;
   SM           : constant RP.PIO.PIO_SM      := 0;
   Config       : RP.PIO.PIO_SM_Config        := RP.PIO.Default_SM_Config;
   System_Clock : constant RP.Hertz           := RP.Clock.Frequency (RP.Clock.SYS);
begin
   Pico.UART_IO.Initialise;
   Pico.UART_IO.Put_Line ("+ Square_Wave");
   Pico.UART_IO.Put_Line ("=== 800 kHz PIO Square Wave Test ===");
   Pico.UART_IO.Put_Line ("Scope on GP2 (physical pin 4)");

   --  Make sure system clock is 125 MHz (default on both Pico and Pico 2W)
   if System_Clock in 125_000_000 | 150_000_000 then
      Pico.UART_IO.Put_Line
         ("Warning: SYS clock is " & System_Clock'Image &
          " not 125 MHz (Pico) / 150 MHz (Pico 2W). Timing will be off!");
   end if;

   --  Configure the GPIO for PIO control
   Pin.Configure (RP.GPIO.Output, RP.GPIO.Pull_Up, PIO.GPIO_Function);

   PIO.Load (Prog => Squarewave_Program, Offset => ASM_Offset);
   PIO.Set_Pin_Direction (SM, Pin.Pin, RP.PIO.Output);

   Config.Set_Out_Pins (Pin.Pin, 1);
   Config.Set_Wrap (ASM_Offset + Squarewave_Program'First, ASM_Offset + Squarewave_Program'Last);
   Config.Set_Clock_Frequency (Freq);

   PIO.SM_Initialize (SM, ASM_Offset, Config);
   PIO.Set_Enabled (SM, True);

   --  Start the state machine
   PIO.Enable;

   Pico.UART_IO.Put_Line ("Square wave running on GP2 at 800 kHz.");
   Pico.UART_IO.Put_Line ("You should see a perfect 1.25 µs period, 50% duty cycle.");

   --  Keep the main task alive
   loop
      Pico.UART_IO.Put_Line ("Still running... Check your scope! ... Stop with debug console.");
      delay 1.0;
   end loop;
end Square_Wave;

Observed behaviour (identical on both Pico and Pico 2W, multiple GPIO pins, single-stepped with debugger):

  • The pin sits at 0 V after reset.
  • During initialisation it jumps to ~3.27 V and stays there – a perfect flat line on the oscilloscope.
  • No toggling occurs, even though the state machine reports as enabled and the PIO block is enabled.
  • The same code path was used with the original ws2812_demo from Jeremy Grosser (with and without DMA) – same result: static high after one transition.

I have verified:

  • The program loads correctly (offset 0, two instructions).
  • Wrap is set to the two-instruction block.
  • Set_Pin_Direction and Set_Out_Pins are called.
  • The GPIO function is correctly set to PIO.
  • System clock is the expected 125 MHz / 150 MHz.

What I am unsure about is whether the square-wave program itself is correct for the Ada binding. In C SDK examples the equivalent often includes an explicit set pindirs, 1 inside the PIO program or uses sm_config_set_set_pins + pio_sm_set_consecutive_pindirs. I am not certain whether PIO.Set_Pin_Direction + Config.Set_Out_Pins is sufficient, or if something is missing in the side-set / out mapping.

Has anyone managed to get a minimal PIO square wave (or any continuous toggling PIO program) running reliably with the current rp2040_hal / RP.PIO on either RP2040 or RP2350? Any obvious mistake in the configuration sequence, or a known gotcha with PIO initialisation on the Pico 2W?

I will happily update the tutorial chapter and push a corrected example to the repository once we find the solution.

Many thanks in advance,
Martin

why 0x6000/1? My pioasm gives me 0xe000/1 and this matches the manual, because 0x6000 if out instruction.

Thank you for the quick and helpful replies!

I finally got the square wave working cleanly on both the Pico and Pico 2W. The oscilloscope now shows a perfect 800 kHz signal with a 1.25 µs period and 50 % duty
.

The three main issues that were tripping me up were:

  1. Hand-written PIO instructions — I have now switched to generating the Ada code automatically with the official pioasm tool. Much safer!

  2. Missing Config.Set_Set_Pins — I had configured the Out pins but completely forgot the Set pins. Without this the PIO program runs but the GPIO pin never toggles. A very easy mistake that had me staring at the scope for far too long.

  3. Wrong order of PIO.Enable — I was enabling the state machine after initializing the PIO block and loading the program. The instructions were therefore never written or deleted again into the instruction memory as PIO.Enable is actually a “Reset and Enable” and first command you need to call.

I have added detailed comments to the source code explaining all three gotchas so that others can avoid the same traps. The working version (including the automatically generated PIO program) is now in the repository:

The full write up will be in the Chapter 6 of my tutorial.

Once again, a big thank you to Max and to the whole forum for the fast and precise help. It made all the difference!

— Martin (krischik)