Programmable IO on the RP2040 (and RP2350)

Why?

IO is hard.

IO is hard

  • Timing is important
  • We have to simple work with regular intervals
  • Transmitting a lot of data requires a lot of CPU

Existing solutions

Busywaiting


gpio_put(PIN_SCL, true);
delay(100);
gpio_put(PIN_SDA, true);
delay(100);
gpio_put(PIN_SCL, false);
delay(100);
gpio_put(PIN_SDA, false);
delay(100);
                        
  • can't do anything else
  • pipeline ⇒ 1 clock ≠ 1 instruction

Interrupts


bool state = ON;
int handler() {
    gpio_put(PIN_SCL, state);
    state = !state;
}
int main() {
    struct repeating_timer timer;
    add_repeating_timer_ms(100, handler, NULL, &timer);
}
                        
  • interrupt handling has overhead
  • pipeline ⇒ 1 clock ≠ 1 instruction

Hardware support

  • dedicated parts of the chip
  • separate clock
  • direct memory access


  • can clock faster
  • cpu is free
  • limited amount of buses
  • limited amount of protocols
  • increases complexity

FPGA

  • Field Programmable Gate Array
  • programmable array of logic gates


  • can clock faster
  • incredibly fast
  • hard to program
  • expensive

Programmable IO!

  • 9 instructions
  • 1 clock = 1 instruction
  • clever interface to CPU
  • SIMD (kinda)
  • IO mapping

RP2040 × 2, RP2350 × 3

.program squarewave
    set pindirs, 1
again:
    set pins, 1 [1]
    set pins, 0
    jmp again
                        

Clock Divider

+ autopush & autopull

Pin Mapping

  • up to 32 pins
  • start + count
  • count at assembly, start at init
.program auto_push_pull

.wrap_target
    out x, 32
    in x, 32
.wrap
                        
  • 000: (no condition): Always
  • 001: !X: scratch X zero
  • 010: X--: scratch X non-zero, prior to decrement
  • 011: !Y: scratch Y zero
  • 100: Y--: scratch Y non-zero, prior to decrement
  • 101: X!=Y: scratch X not equal scratch Y
  • 110: PIN: branch on input pin
  • 111: !OSRE: output shift register not empty

Side-set

.program spi_tx_fast
.side_set 1

loop:
    out pins, 1     side 0
    jmp loop        side 1
                        

More!

  • MOV EXEC & OUT EXEC
  • pin mapping is separate for OUT, SET, IN and for side-setting
  • ...
.program addition

; Pop two 32 bit integers from the TX FIFO, add them together, and push the
; result to the TX FIFO. Autopush/pull should be disabled as we're using
; explicit push and pull instructions.
;
; This program uses the two's complement identity x + y == ~(~x - y)

	pull
	mov x, ~osr
	pull
	mov y, osr
	jmp test        ; this loop is equivalent to the following C code:
incr:               ; while (y--)
	jmp x-- test    ;     x--;
test:               ; This has the effect of subtracting y from x, eventually.
	jmp y-- incr
	mov isr, ~x
	push
                    

Further reading