PIO::Program is a class that represents a PIO program. You can create an instance of PIO::Program and add instructions to it using instance functions that have similar names to pioasm's instructions and directives, such as .mov() and .wrap_target(). Now, let's call this feature Method-Chain Assembler.
Below is a skeleton code of a PIO program coded with the Method-Chain Assembler:
The method chain starts with the .program() function, which specifies the name of the program. You can add instructions and directives in the middle of the chain, and end it with the .end() function.
The PIO program coded with the Method-Chain Assembler has the similar appearance with the one coded in pioasm. See the differences between them in the following codes:
.program("blink").pull().out("y",32).wrap_target().mov("x","y").set("pins",1)// Turn LED on.L("lp1").jmp("x--","lp1")// Delay for (x + 1) cycles, x is a 32 bit number.mov("x","y").set("pins",0)// Turn LED off.L("lp2").jmp("x--","lp2")// Delay for the same number of cycles again.wrap()// Blink forever!.end();
.programblinkpullouty,32.wrap_targetmovx,ysetpins,1; Turn LED onlp1:jmpx--lp1; Delay for (x + 1) cycles, x is a 32 bit numbermovx,ysetpins,0; Turn LED offlp2:jmpx--lp2; Delay for the same number of cycles again.wrap; Blink forever!;
Example Programs
Pico SDK provides a collection of PIO examples in its official repository. You can find them at the following link:
This section shows the same PIO programs as those examples, but coded in C++ using pico-jxglib's Method-Chain Assembler. You can compare the C++ code with the original pioasm code to see how they correspond to each other.
.program("addition").pull()// Pull first operand.mov("x","~osr")// Store complement of first operand in x.pull()// Pull second operand.mov("y","osr")// Store second operand in y.jmp("test")// this loop is equivalent to the following C code:.L("incr")// while (y--).jmp("x--","test")// x--;.L("test")// This has the effect of subtracting y from x, eventually..jmp("y--","incr").mov("isr","~x")// Store result (complement of x) in ISR.push()// Push result to FIFO.end();
.programadditionpull; Pull first operandmovx,~osr; Store complement of first operand in xpull; Pull second operandmovy,osr; Store second operand in yjmptest; this loop is equivalent to the following C code:incr:; while (y--)jmpx--test; x--;test:; This has the effect of subtracting y from x, eventually.jmpy--incrmovisr,~x; Store result (complement of x) in ISRpush; Push result to FIFO;
.program("blink").pull().out("y",32).wrap_target().mov("x","y").set("pins",1)// Turn LED on.L("lp1").jmp("x--","lp1")// Delay for (x + 1) cycles, x is a 32 bit number.mov("x","y").set("pins",0)// Turn LED off.L("lp2").jmp("x--","lp2")// Delay for the same number of cycles again.wrap()// Blink forever!.end();
.programblinkpullouty,32.wrap_targetmovx,ysetpins,1; Turn LED onlp1:jmpx--lp1; Delay for (x + 1) cycles, x is a 32 bit numbermovx,ysetpins,0; Turn LED offlp2:jmpx--lp2; Delay for the same number of cycles again.wrap; Blink forever!;
.program("differential_manchester_tx").side_set(1).opt().L("start").L("initial_high").out("x",1)// Start of bit period: always assert transition.jmp("!x","high_0").side(1)[6]// Test the data bit we just shifted out of OSR.L("high_1").nop().jmp("initial_high").side(0)[6]// For `1` bits, also transition in the middle.L("high_0").jmp("initial_low")[7]// Otherwise, the line is stable in the middle.L("initial_low").out("x",1)// Always shift 1 bit from OSR to X so we can.jmp("!x","low_0").side(0)[6]// branch on it. Autopull refills OSR for us..L("low_1").nop().jmp("initial_low").side(1)[6]// If there are two transitions, return to.L("low_0").jmp("initial_high")[7]// the initial line state is flipped!.end();
.programdifferential_manchester_tx.side_set1optpublicstart:initial_high:outx,1; Start of bit period: always assert transitionjmp!xhigh_0side1[6]; Test the data bit we just shifted out of OSRhigh_1:nopjmpinitial_highside0[6]; For `1` bits, also transition in the middlehigh_0:jmpinitial_low[7]; Otherwise, the line is stable in the middleinitial_low:outx,1; Always shift 1 bit from OSR to X so we canjmp!xlow_0side0[6]; branch on it. Autopull refills OSR for us.low_1:nopjmpinitial_lowside1[6]; If there are two transitions, return tolow_0:jmpinitial_high[7]; the initial line state is flipped!;
.program("i2c").side_set(1).opt().pindirs().L("do_nack").jmp("y--","entry_point")// Continue if NAK was expected.irq("wait",0).rel()// Otherwise stop, ask for help.L("do_byte").set("x",7)// Loop 8 times.L("bitloop").out("pindirs",1)[7]// Serialise write data (all-ones if reading).nop().side(1)[2]// SCL rising edge.wait(1,"pin",1)[4]// Allow clock to be stretched.in("pins",1)[7]// Sample read data in middle of SCL pulse.jmp("x--","bitloop").side(0)[7]// SCL falling edge// Handle ACK pulse.out("pindirs",1)[7]// On reads, we provide the ACK..nop().side(1)[7]// SCL rising edge.wait(1,"pin",1)[7]// Allow clock to be stretched.jmp("pin","do_nack").side(0)[2]// Test SDA for ACK/NAK, fall through if ACK.L("entry_point").pub(&entry_point)// public entry_point:.wrap_target().out("x",6)// Unpack Instr count.out("y",1)// Unpack the NAK ignore bit.jmp("!x","do_byte")// Instr == 0, this is a data record..out("null",32)// Instr > 0, remainder of this OSR is invalid.L("do_exec").out("exec",16)// Execute one instruction per FIFO word.jmp("x--","do_exec")// Repeat n + 1 times.wrap().end();
.programi2c.side_set1optpindirsdo_nack:jmpy--entry_point; Continue if NAK was expectedirqwait0rel; Otherwise stop, ask for helpdo_byte:setx,7; Loop 8 timesbitloop:outpindirs,1[7]; Serialise write data (all-ones if reading)nopside1[2]; SCL rising edgewait1pin,1[4]; Allow clock to be stretchedinpins,1[7]; Sample read data in middle of SCL pulsejmpx--bitloopside0[7]; SCL falling edge; Handle ACK pulseoutpindirs,1[7]; On reads, we provide the ACK.nopside1[7]; SCL rising edgewait1pin,1[7]; Allow clock to be stretchedjmppindo_nackside0[2]; Test SDA for ACK/NAK, fall through if ACKpublicentry_point:.wrap_targetoutx,6; Unpack Instr countouty,1; Unpack the NAK ignore bitjmp!xdo_byte; Instr == 0, this is a data record.outnull,32; Instr > 0, remainder of this OSR is invaliddo_exec:outexec,16; Execute one instruction per FIFO wordjmpx--do_exec; Repeat n + 1 times.wrap;
.program("hub75_data_rgb888").side_set(1).L("entry_point")// public entry_point:.wrap_target().L("shift0")// public shift0:.pull().side(0)// gets patched to `out null, n` if n nonzero (otherwise the PULL is required for fencing).in("osr",1).side(0)// shuffle shuffle shuffle.out("null",8).side(0).in("osr",1).side(0).out("null",8).side(0).in("osr",1).side(0).out("null",32).side(0)// Discard remainder of OSR contents.L("shift1")// public shift1:.pull().side(0)// gets patched to out null, n if n is nonzero (otherwise PULL required).in("osr",1).side(1)// Note this posedge clocks in the data from the previous iteration.out("null",8).side(1).in("osr",1).side(1).out("null",8).side(1).in("osr",1).side(1).out("null",32).side(1).in("null",26).side(1)// Note we are just doing this little manoeuvre here to get GPIOs in the order.mov("pins","::isr").side(1)// R0, G0, B0, R1, G1, B1. Can go 1 cycle faster if reversed.wrap().end();
.programhub75_data_rgb888.side_set1publicentry_point:.wrap_targetpublicshift0:pullside0; gets patched to `out null, n` if n nonzero (otherwise the PULL is required for fencing)inosr,1side0; shuffle shuffle shuffleoutnull,8side0inosr,1side0outnull,8side0inosr,1side0outnull,32side0; Discard remainder of OSR contentspublicshift1:pullside0; gets patched to out null, n if n is nonzero (otherwise PULL required)inosr,1side1; Note this posedge clocks in the data from the previous iterationoutnull,8side1inosr,1side1outnull,8side1inosr,1side1outnull,32side1innull,26side1; Note we are just doing this little manoeuvre here to get GPIOs in the ordermovpins,::isrside1; R0, G0, B0, R1, G1, B1. Can go 1 cycle faster if reversed.wrap;
.program("manchester_tx").side_set(1).opt().wrap_target().L("do_1").nop().side(0)[5]// Low for 6 cycles (5 delay, +1 for nop).jmp("get_bit").side(1)[3]// High for 4 cycles. 'get_bit' takes another 2 cycles.L("do_0").nop().side(1)[5]// Output high for 6 cycles.nop().side(0)[3]// Output low for 4 cycles.L("start").entry().L("get_bit").out("x",1)// Always shift out one bit from OSR to X, so we can.jmp("!x","do_0")// branch on it. Autopull refills the OSR when empty..wrap().end();
.programmanchester_tx.side_set1opt.wrap_targetdo_1:nopside0[5]; Low for 6 cycles (5 delay, +1 for nop)jmpget_bitside1[3]; High for 4 cycles. 'get_bit' takes another 2 cyclesdo_0:nopside1[5]; Output high for 6 cyclesnopside0[3]; Output low for 4 cyclespublicstart:get_bit:outx,1; Always shift out one bit from OSR to X, so we canjmp!xdo_0; branch on it. Autopull refills the OSR when empty..wrap;
.program("nec_carrier_burst").wrap_target().set("x",NUM_CYCLES-1)// initialise the loop counter.wait(1,"irq",BURST_IRQ)// wait for the IRQ then clear it.L("cycle_loop").set("pins",1)// set the pin high (1 cycle).set("pins",0)[1]// set the pin low (2 cycles).jmp("x--","cycle_loop")// (1 more cycle).wrap().end();
.programnec_carrier_burst.defineNUM_CYCLES21; how many carrier cycles to generate.defineBURST_IRQ7; which IRQ should trigger a carrier burst.definepublicTICKS_PER_LOOP4; the number of instructions in the loop (for timing).wrap_targetsetX,(NUM_CYCLES-1); initialise the loop counterwait1irqBURST_IRQ; wait for the IRQ then clear itcycle_loop:setpins,1; set the pin high (1 cycle)setpins,0[1]; set the pin low (2 cycles)jmpX--,cycle_loop; (1 more cycle).wrap;
.program("nec_carrier_control").wrap_target().pull()// fetch a data word from the transmit FIFO into the// output shift register, blocking if the FIFO is empty.set("x",NUM_INITIAL_BURSTS-1)// send a sync burst (9ms).L("long_burst").irq(BURST_IRQ).jmp("x--","long_burst").nop()[15]// send a 4.5ms space.irq(BURST_IRQ)[1]// send a 562.5us burst to begin the first data bit.L("data_bit").out("x",1)// shift the least-significant bit from the OSR.jmp("!x","burst")// send a short delay for a '0' bit.nop()[3]// send an additional delay for a '1' bit.L("burst").irq(BURST_IRQ)// send a 562.5us burst to end the data bit.jmp("!osre","data_bit")// continue sending bits until the OSR is empty.wrap()// fetch another data word from the FIFO.end();
.programnec_carrier_control.defineBURST_IRQ7; the IRQ used to trigger a carrier burst.defineNUM_INITIAL_BURSTS16; how many bursts to transmit for a 'sync burst'.wrap_targetpull; fetch a data word from the transmit FIFO into the; output shift register, blocking if the FIFO is emptysetX,(NUM_INITIAL_BURSTS-1); send a sync burst (9ms)long_burst:irqBURST_IRQjmpX--long_burstnop[15]; send a 4.5ms spaceirqBURST_IRQ[1]; send a 562.5us burst to begin the first data bitdata_bit:outX,1; shift the least-significant bit from the OSRjmp!Xburst; send a short delay for a '0' bitnop[3]; send an additional delay for a '1' bitburst:irqBURST_IRQ; send a 562.5us burst to end the data bitjmp!OSREdata_bit; continue sending bits until the OSR is empty.wrap; fetch another data word from the FIFO;
.program("nec_receive").wrap_target().L("next_burst").set("x",BURST_LOOP_COUNTER).wait(0,"pin",0)// wait for the next burst to start.L("burst_loop").jmp("pin","data_bit")// the burst ended before the counter expired.jmp("x--","burst_loop")// wait for the burst to end// the counter expired - this is a sync burst.mov("isr","null")// reset the Input Shift Register.wait(1,"pin",0)// wait for the sync burst to finish.jmp("next_burst")// wait for the first data bit.L("data_bit").nop()[BIT_SAMPLE_DELAY-1]// wait for 1.5 burst periods before sampling the bit value.in("pins",1)// if the next burst has started then detect a '0' (short gap)// otherwise detect a '1' (long gap)// after 32 bits the ISR will autopush to the receive FIFO.wrap().end();
.programnec_receive.defineBURST_LOOP_COUNTER30; the detection threshold for a 'frame sync' burst.defineBIT_SAMPLE_DELAY15; how long to wait after the end of the burst before sampling.wrap_targetnext_burst:setX,BURST_LOOP_COUNTERwait0pin0; wait for the next burst to startburst_loop:jmppindata_bit; the burst ended before the counter expiredjmpX--burst_loop; wait for the burst to end; the counter expired - this is a sync burstmovISR,NULL; reset the Input Shift Registerwait1pin0; wait for the sync burst to finishjmpnext_burst; wait for the first data bitdata_bit:nop[BIT_SAMPLE_DELAY-1]; wait for 1.5 burst periods before sampling the bit valueinPINS,1; if the next burst has started then detect a '0' (short gap); otherwise detect a '1' (long gap); after 32 bits the ISR will autopush to the receive FIFO.wrap;
.program("onewire").side_set(1).pindirs().L("reset_bus").pub(&reset_bus)// PUBLIC reset_bus:.set("x",28).side(1)[15]// pull bus low 16.L("loop_a").jmp("x--","loop_a").side(1)[15]// 29 x 16.set("x",8).side(0)[6]// release bus 7.L("loop_b").jmp("x--","loop_b").side(0)[6]// 9 x 7.mov("isr","pins").side(0)// read all pins to ISR (avoids autopush) 1.push().side(0)// push result manually 1.set("x",24).side(0)[7]// 8.L("loop_c").jmp("x--","loop_c").side(0)[15]// 25 x 16.wrap_target().L("fetch_bit").pub(&fetch_bit)// PUBLIC fetch_bit:.out("x",1).side(0)// shift next bit from OSR (autopull) 1.jmp("!x","send_0").side(1)[5]// pull bus low, branch if sending '0' 6.L("send_1")// send a '1' bit.set("x",2).side(0)[8]// release bus, wait for slave response 9.in("pins",1).side(0)[4]// read bus, shift bit to ISR (autopush) 5.L("loop_e").jmp("x--","loop_e").side(0)[15]// 3 x 16.jmp("fetch_bit").side(0)// 1.L("send_0")// send a '0' bit.set("x",2).side(1)[5]// continue pulling bus low 6.L("loop_d").jmp("x--","loop_d").side(1)[15]// 3 x 16.in("null",1).side(0)[8]// release bus, shift 0 to ISR (autopush) 9.wrap().end();
.programonewire.side_set1pindirsPUBLICreset_bus:setx,28side1[15]; pull bus low 16loop_a:jmpx--loop_aside1[15]; 29 x 16setx,8side0[6]; release bus 7loop_b:jmpx--loop_bside0[6]; 9 x 7movisr,pinsside0; read all pins to ISR (avoids autopush) 1pushside0; push result manually 1setx,24side0[7]; 8loop_c:jmpx--loop_cside0[15]; 25 x 16.wrap_targetPUBLICfetch_bit:outx,1side0; shift next bit from OSR (autopull) 1jmp!xsend_0side1[5]; pull bus low, branch if sending '0' 6send_1:; send a '1' bitsetx,2side0[8]; release bus, wait for slave response 9inpins,1side0[4]; read bus, shift bit to ISR (autopush) 5loop_e:jmpx--loop_eside0[15]; 3 x 16jmpfetch_bitside0; 1send_0:; send a '0' bitsetx,2side1[5]; continue pulling bus low 6loop_d:jmpx--loop_dside1[15]; 3 x 16innull,1side0[8]; release bus, shift 0 to ISR (autopush) 9.wrap;
.program("pwm").side_set(1).opt().pull().noblock().side(0)// Pull from FIFO to OSR if available, else copy X to OSR..mov("x","osr")// Copy most-recently-pulled value back to scratch X.mov("y","isr")// ISR contains PWM period. Y used as counter..L("countloop").jmp("x!=y","noset")// Set pin high if X == Y, keep the two paths length matched.jmp("skip").side(1).L("noset").nop()// Single dummy cycle to keep the two paths the same length.L("skip").jmp("y--","countloop")// Loop until Y hits 0, then pull a fresh PWM value from FIFO.end();
.programpwm.side_set1optpullnoblockside0; Pull from FIFO to OSR if available, else copy X to OSR.movx,osr; Copy most-recently-pulled value back to scratch Xmovy,isr; ISR contains PWM period. Y used as counter.countloop:jmpx!=ynoset; Set pin high if X == Y, keep the two paths length matchedjmpskipside1noset:nop; Single dummy cycle to keep the two paths the same lengthskip:jmpy--countloop; Loop until Y hits 0, then pull a fresh PWM value from FIFO;
.program("spi_cpha0").side_set(1).out("pins",1).side(0)[1]// Stall here on empty (sideset proceeds even if.in("pins",1).side(1)[1]// instruction stalls, so we stall with SCK low).end();
.programspi_cpha0.side_set1outpins,1side0[1]; Stall here on empty (sideset proceeds even ifinpins,1side1[1]; instruction stalls, so we stall with SCK low);
.program("squarewave").set("pindirs",1)// Set pin to output.L("again").set("pins",1)[1]// Drive pin high and then delay for one cycle.set("pins",0)// Drive pin low.jmp("again")// Set PC to label `again`.end();
.programsquarewavesetpindirs,1; Set pin to outputagain:setpins,1[1]; Drive pin high and then delay for one cyclesetpins,0; Drive pin lowjmpagain; Set PC to label `again`;
.program("squarewave_wrap").set("pindirs",1)// Set pin to output.wrap_target().set("pins",1)[1]// Drive pin high and then delay for one cycle.set("pins",0)[1]// Drive pin low and then delay for one cycle.wrap().end();
.programsquarewave_wrapsetpindirs,1; Set pin to output.wrap_targetsetpins,1[1]; Drive pin high and then delay for one cyclesetpins,0[1]; Drive pin low and then delay for one cycle.wrap;
.program("uart_rx").L("start").wait(0,"pin",0)// Stall until start bit is asserted.set("x",7)[10]// Preload bit counter, then delay until halfway through.L("bitloop").in("pins",1)// Shift data bit into ISR.jmp("x--","bitloop")[6]// Loop 8 times, each loop iteration is 8 cycles.jmp("pin","good_stop")// Check stop bit (should be high).irq(4).rel()// Either a framing error or a break. Set a sticky flag,.wait(1,"pin",0)// and wait for line to return to idle state..jmp("start")// Don't push data if we didn't see good framing..L("good_stop")// No delay before returning to start; a little slack is.push()// important in case the TX clock is slightly too fast.end();
.programuart_rxstart:wait0pin0; Stall until start bit is assertedsetx,7[10]; Preload bit counter, then delay until halfway throughbitloop:; the first data bit (12 cycles incl wait, set).inpins,1; Shift data bit into ISRjmpx--bitloop[6]; Loop 8 times, each loop iteration is 8 cyclesjmppingood_stop; Check stop bit (should be high)irq4rel; Either a framing error or a break. Set a sticky flag,wait1pin0; and wait for line to return to idle state.jmpstart; Don't push data if we didn't see good framing.good_stop:; No delay before returning to start; a little slack ispush; important in case the TX clock is slightly too fast.;
.program("uart_tx").side_set(1).opt().pull().side(1)[7]// Assert stop bit, or stall with line in idle state.set("x",7).side(0)[7]// Preload bit counter, assert start bit for 8 clocks.L("bitloop")// This loop will run 8 times (8n1 UART).out("pins",1)// Shift 1 bit from OSR to the first OUT pin.jmp("x--","bitloop")[6]// Each loop iteration is 8 cycles..end();
.programuart_tx.side_set1optpullside1[7]; Assert stop bit, or stall with line in idle statesetx,7side0[7]; Preload bit counter, assert start bit for 8 clocksbitloop:; This loop will run 8 times (8n1 UART)outpins,1; Shift 1 bit from OSR to the first OUT pinjmpx--bitloop[6]; Each loop iteration is 8 cycles.;
.program("ws2812").side_set(1).wrap_target().L("bitloop").out("x",1).side(0)[T3-1]// Side-set still takes place when instruction stalls.jmp("!x","do_zero").side(1)[T1-1]// Branch on the bit we shifted out. Positive pulse.L("do_one").jmp("bitloop").side(1)[T2-1]// Continue driving high, for a long pulse.L("do_zero").nop().side(0)[T2-1]// Or drive low, for a short pulse.wrap().end();
.programws2812.side_set1.definepublicT13.definepublicT23.definepublicT34.wrap_targetbitloop:outx,1side0[T3-1]; Side-set still takes place when instruction stallsjmp!xdo_zeroside1[T1-1]; Branch on the bit we shifted out. Positive pulsedo_one:jmpbitloopside1[T2-1]; Continue driving high, for a long pulsedo_zero:nopside0[T2-1]; Or drive low, for a short pulse.wrap;