├── go.mod ├── rp2-pio ├── pio_rp2040.go ├── examples │ ├── rxfifoputget │ │ ├── rxfifoputget.pio │ │ ├── rxfifoputget_pio.go │ │ └── rxfifoputget.go │ ├── pulsar │ │ └── pulsar.go │ ├── rxfifoput │ │ ├── rxfifoput.pio │ │ ├── rxfifoput_pio.go │ │ └── rxfifoput.go │ ├── blinky │ │ ├── main.go │ │ ├── blink.pio │ │ └── blink_pio.go │ ├── parallel │ │ ├── tufty │ │ │ ├── const.go │ │ │ ├── tufty.go │ │ │ ├── st7789.go │ │ │ └── dma.go │ │ └── hub40screen │ │ │ └── hub40.go │ ├── i2s │ │ └── main.go │ ├── ws2812b │ │ └── main.go │ └── ws2812bfourpixels │ │ └── ws2812bfourpixels.go ├── piolib │ ├── pulsar.pio │ ├── pulsar_pio.go │ ├── i2s_pio.go │ ├── ws2812b_pio.go │ ├── ws2812bfourpixels_pio.go │ ├── spi_pio.go │ ├── ws2812b.pio │ ├── i2s.pio │ ├── spi.pio │ ├── ws2812bfourpixels.pio │ ├── all_generate.go │ ├── pulsar.go │ ├── i2s.go │ ├── ws2812b.go │ ├── ws2812bfourpixels.go │ ├── parallel.go │ ├── spi.go │ ├── spi3w.go │ └── dma.go ├── statemachine_rp2040.go ├── statemachine_rp2350.go ├── pio_rp2350.go ├── pio_test.go ├── instrv1.go ├── pio.go ├── config.go ├── statemachine.go └── instr.go ├── .gitignore ├── .github └── workflows │ └── build.yml ├── LICENSE ├── Makefile └── README.md /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tinygo-org/pio 2 | 3 | go 1.22 4 | -------------------------------------------------------------------------------- /rp2-pio/pio_rp2040.go: -------------------------------------------------------------------------------- 1 | //go:build rp2040 2 | 3 | package pio 4 | 5 | import ( 6 | "device/rp" 7 | ) 8 | 9 | const ( 10 | rp2350ExtraReg = 0 11 | ) 12 | 13 | func (pio *PIO) blockIndex() uint8 { 14 | switch pio.hw { 15 | case rp.PIO0: 16 | return 0 17 | case rp.PIO1: 18 | return 1 19 | } 20 | panic(badPIO) 21 | } 22 | -------------------------------------------------------------------------------- /rp2-pio/examples/rxfifoputget/rxfifoputget.pio: -------------------------------------------------------------------------------- 1 | ; This PIO program is not compatible with RP2040 2 | .pio_version 1 3 | 4 | .program rxfifoputget 5 | 6 | ; Set RX FIFO to PUTGET mode 7 | .fifo putget 8 | 9 | .wrap_target 10 | 11 | ; Do something to fill the ISR here 12 | 13 | mov rxfifo[0], isr ; Save ISR to the RX FIFO 14 | 15 | mov osr, rxfifo[0] ; Copy the saved ISR to the OSR 16 | 17 | ; Do something with the OSR here 18 | 19 | .wrap 20 | 21 | % go { 22 | //go:build rp2350 23 | package main 24 | 25 | import ( 26 | pio "github.com/tinygo-org/pio/rp2-pio" 27 | ) 28 | %} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Binary objects 12 | *.uf2 13 | *.bin 14 | *.hex 15 | 16 | # Test binary, built with `go test -c` 17 | *.test 18 | 19 | # Output of the go coverage tool, specifically when used with LiteIDE 20 | *.out 21 | 22 | # Dependency directories (remove the comment below to include it) 23 | # vendor/ 24 | 25 | # Go workspace file 26 | go.work 27 | go.sum 28 | .vscode 29 | local -------------------------------------------------------------------------------- /rp2-pio/piolib/pulsar.pio: -------------------------------------------------------------------------------- 1 | ; Author: Patricio Whittingslow 2 | 3 | 4 | .program pulsar 5 | .wrap_target 6 | set pindirs, 1 ; Set pin to output. 7 | pull block ; Block until 32bit word loaded from Tx FIFO to OSR (output shift register). 8 | mov x, osr ; Copy the word from OSR into scratch register x. 9 | 10 | decloop: 11 | set pins, 1 [1] ; Square wave high and delay 1 cycle. 12 | set pins, 0 ; Square wave low. 13 | jmp x-- decloop ; Decrement X, our counter. 14 | .wrap 15 | 16 | 17 | 18 | % go { 19 | //go:build rp2040 || rp2350 20 | package piolib 21 | 22 | import ( 23 | pio "github.com/tinygo-org/pio/rp2-pio" 24 | ) 25 | %} -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | container: ghcr.io/tinygo-org/tinygo-dev:latest 14 | steps: 15 | - name: Work around CVE-2022-24765 16 | # We're not on a multi-user machine, so this is safe. 17 | run: git config --global --add safe.directory "$GITHUB_WORKSPACE" 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | - name: TinyGo version check 21 | run: tinygo version 22 | - name: Run build and smoke tests 23 | run: make smoke-test 24 | -------------------------------------------------------------------------------- /rp2-pio/examples/rxfifoputget/rxfifoputget_pio.go: -------------------------------------------------------------------------------- 1 | // Code generated by pioasm; DO NOT EDIT. 2 | 3 | //go:build rp2350 4 | package main 5 | import ( 6 | pio "github.com/tinygo-org/pio/rp2-pio" 7 | ) 8 | // rxfifoputget 9 | 10 | const rxfifoputgetWrapTarget = 0 11 | const rxfifoputgetWrap = 1 12 | 13 | var rxfifoputgetInstructions = []uint16{ 14 | // .wrap_target 15 | 0x8018, // 0: mov rxfifo[0], isr 16 | 0x8098, // 1: mov osr, rxfifo[0] 17 | // .wrap 18 | } 19 | const rxfifoputgetOrigin = -1 20 | func rxfifoputgetProgramDefaultConfig(offset uint8) pio.StateMachineConfig { 21 | cfg := pio.DefaultStateMachineConfig() 22 | cfg.SetWrap(offset+rxfifoputgetWrapTarget, offset+rxfifoputgetWrap) 23 | return cfg; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /rp2-pio/examples/pulsar/pulsar.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "machine" 5 | "time" 6 | 7 | pio "github.com/tinygo-org/pio/rp2-pio" 8 | "github.com/tinygo-org/pio/rp2-pio/piolib" 9 | ) 10 | 11 | func main() { 12 | time.Sleep(time.Second) 13 | const pin = machine.LED 14 | sm, _ := pio.PIO0.ClaimStateMachine() 15 | pulsar, err := piolib.NewPulsar(sm, pin) 16 | if err != nil { 17 | panic(err.Error()) 18 | } 19 | println("start pulsing") 20 | 21 | for { 22 | // Max period is 0.5ms. PIO state machines can run at minimum of 2kHz. 23 | for period := time.Microsecond; period < time.Millisecond/3; period *= 2 { 24 | err = pulsar.SetPeriod(period) 25 | if err != nil { 26 | panic(err.Error()) 27 | } 28 | for i := uint32(10); i < 100; i *= 2 { 29 | pulsar.TryQueue(i) 30 | time.Sleep(time.Second / 2) 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /rp2-pio/examples/rxfifoput/rxfifoput.pio: -------------------------------------------------------------------------------- 1 | ; This PIO program is not compatible with RP2040 2 | .pio_version 1 3 | 4 | .program rxfifoput 5 | 6 | ; Set TX FIFO to normal mode and set RX FIFO to PUT mode 7 | .fifo txput 8 | 9 | ; Set all of the RX FIFO elements to zero 10 | ; ISR is the only source that can write to the RX FIFO 11 | mov isr, NULL 12 | set y, 3 13 | 14 | zero_loop: 15 | mov rxfifo[y], isr 16 | jmp y--, zero_loop 17 | 18 | .wrap_target 19 | 20 | ; Set x to uint32 max so we can use it as an inverted-bit counter 21 | mov x, ~NULL 22 | 23 | count: 24 | ; Write the inverted value of x to the RX FIFO 25 | mov isr, ~x 26 | mov rxfifo[0], isr 27 | ; Decrement x (incrementing the inverted value) 28 | jmp x--, count 29 | 30 | .wrap 31 | 32 | % go { 33 | //go:build rp2350 34 | package main 35 | 36 | import ( 37 | pio "github.com/tinygo-org/pio/rp2-pio" 38 | ) 39 | %} -------------------------------------------------------------------------------- /rp2-pio/statemachine_rp2040.go: -------------------------------------------------------------------------------- 1 | //go:build rp2040 2 | 3 | package pio 4 | 5 | import ( 6 | "device/rp" 7 | ) 8 | 9 | func (sm StateMachine) setConfig(cfg StateMachineConfig) { 10 | sm.PIO().BlockIndex() // Panic if PIO or state machine not at valid offset. 11 | if sm.index > 3 { 12 | panic(badStateMachineIndex) 13 | } 14 | hw := sm.HW() 15 | hw.CLKDIV.Set(cfg.ClkDiv) 16 | hw.EXECCTRL.Set(cfg.ExecCtrl) 17 | hw.SHIFTCTRL.Set(cfg.ShiftCtrl) 18 | hw.PINCTRL.Set(cfg.PinCtrl) 19 | } 20 | 21 | func (sm StateMachine) isValid() bool { 22 | return sm.pio != nil && sm.index <= 3 && 23 | (sm.pio.hw == rp.PIO0 || sm.pio.hw == rp.PIO1) 24 | } 25 | 26 | func (sm StateMachine) getRxFIFOAt(fifoIndex int) uint32 { 27 | panic("GetRxFIFOAt not supported on rp2040") 28 | } 29 | 30 | func (sm StateMachine) setRxFIFOAt(data uint32, fifoIndex int) { 31 | panic("SetRxFIFOAt not supported on rp2040") 32 | } 33 | -------------------------------------------------------------------------------- /rp2-pio/piolib/pulsar_pio.go: -------------------------------------------------------------------------------- 1 | // Code generated by pioasm; DO NOT EDIT. 2 | 3 | //go:build rp2040 || rp2350 4 | package piolib 5 | import ( 6 | pio "github.com/tinygo-org/pio/rp2-pio" 7 | ) 8 | // pulsar 9 | 10 | const pulsarWrapTarget = 0 11 | const pulsarWrap = 5 12 | 13 | var pulsarInstructions = []uint16{ 14 | // .wrap_target 15 | 0xe081, // 0: set pindirs, 1 16 | 0x80a0, // 1: pull block 17 | 0xa027, // 2: mov x, osr 18 | 0xe101, // 3: set pins, 1 [1] 19 | 0xe000, // 4: set pins, 0 20 | 0x0043, // 5: jmp x--, 3 21 | // .wrap 22 | } 23 | const pulsarOrigin = -1 24 | func pulsarProgramDefaultConfig(offset uint8) pio.StateMachineConfig { 25 | cfg := pio.DefaultStateMachineConfig() 26 | cfg.SetWrap(offset+pulsarWrapTarget, offset+pulsarWrap) 27 | return cfg; 28 | } 29 | 30 | -------------------------------------------------------------------------------- /rp2-pio/statemachine_rp2350.go: -------------------------------------------------------------------------------- 1 | //go:build rp2350 2 | 3 | package pio 4 | 5 | import ( 6 | "device/rp" 7 | ) 8 | 9 | func (sm StateMachine) setConfig(cfg StateMachineConfig) { 10 | sm.PIO().BlockIndex() // Panic if PIO or state machine not at valid offset. 11 | if sm.index > 3 { 12 | panic(badStateMachineIndex) 13 | } 14 | hw := sm.HW() 15 | hw.CLKDIV.Set(cfg.ClkDiv) 16 | hw.EXECCTRL.Set(cfg.ExecCtrl) 17 | hw.SHIFTCTRL.Set(cfg.ShiftCtrl) 18 | hw.PINCTRL.Set(cfg.PinCtrl) 19 | } 20 | 21 | func (sm StateMachine) isValid() bool { 22 | return sm.pio != nil && sm.index <= 3 && 23 | (sm.pio.hw == rp.PIO0 || sm.pio.hw == rp.PIO1 || sm.pio.hw == rp.PIO2) 24 | } 25 | 26 | func (sm StateMachine) getRxFIFOAt(fifoIndex int) uint32 { 27 | pioHW := sm.pio.HW() 28 | return pioHW.RXF_PUTGET[0][sm.index][fifoIndex].Get() 29 | } 30 | 31 | func (sm StateMachine) setRxFIFOAt(data uint32, fifoIndex int) { 32 | pioHW := sm.pio.HW() 33 | pioHW.RXF_PUTGET[0][sm.index][fifoIndex].Set(data) 34 | } 35 | -------------------------------------------------------------------------------- /rp2-pio/examples/blinky/main.go: -------------------------------------------------------------------------------- 1 | //go:generate pioasm -o go blink.pio blink_pio.go 2 | 3 | package main 4 | 5 | import ( 6 | "machine" 7 | "time" 8 | 9 | pio "github.com/tinygo-org/pio/rp2-pio" 10 | ) 11 | 12 | func main() { 13 | // Sleep to catch prints. 14 | time.Sleep(2 * time.Second) 15 | Pio := pio.PIO0 16 | 17 | offset, err := Pio.AddProgram(blinkInstructions, blinkOrigin) 18 | if err != nil { 19 | panic(err.Error()) 20 | } 21 | println("Loaded program at", offset) 22 | 23 | blinkPinForever(Pio.StateMachine(0), offset, machine.LED, 3) 24 | blinkPinForever(Pio.StateMachine(1), offset, machine.GPIO6, 4) 25 | blinkPinForever(Pio.StateMachine(2), offset, machine.GPIO11, 1) 26 | } 27 | 28 | func blinkPinForever(sm pio.StateMachine, offset uint8, pin machine.Pin, freq uint32) { 29 | blinkProgramInit(sm, offset, pin) 30 | clockFreq := machine.CPUFrequency() 31 | sm.SetEnabled(true) 32 | println("Blinking", int(pin), "at", freq, "Hz") 33 | sm.TxPut(uint32(clockFreq/(2*freq)) - 3) 34 | } 35 | -------------------------------------------------------------------------------- /rp2-pio/examples/parallel/tufty/const.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const ( 4 | SWRESET byte = 0x01 5 | TEOFF byte = 0x34 6 | TEON byte = 0x35 7 | MADCTL byte = 0x36 8 | COLMOD byte = 0x3A 9 | GCTRL byte = 0xB7 10 | VCOMS byte = 0xBB 11 | LCMCTRL byte = 0xC0 12 | VDVVRHEN byte = 0xC2 13 | VRHS byte = 0xC3 14 | VDVS byte = 0xC4 15 | FRCTRL2 byte = 0xC6 16 | PWCTRL1 byte = 0xD0 17 | PORCTRL byte = 0xB2 18 | GMCTRP1 byte = 0xE0 19 | GMCTRN1 byte = 0xE1 20 | INVOFF byte = 0x20 21 | SLPOUT byte = 0x11 22 | DISPON byte = 0x29 23 | GAMSET byte = 0x26 24 | DISPOFF byte = 0x28 25 | RAMWR byte = 0x2C 26 | INVON byte = 0x21 27 | CASET byte = 0x2A 28 | RASET byte = 0x2B 29 | PWMFRSEL byte = 0xCC 30 | ) 31 | 32 | const ( 33 | ROW_ORDER uint8 = 0b10000000 34 | COL_ORDER uint8 = 0b01000000 35 | SWAP_XY uint8 = 0b00100000 // AKA "MV" 36 | SCAN_ORDER uint8 = 0b00010000 37 | RGB_BGR uint8 = 0b00001000 38 | HORIZ_ORDER uint8 = 0b00000100 39 | ) 40 | -------------------------------------------------------------------------------- /rp2-pio/examples/rxfifoput/rxfifoput_pio.go: -------------------------------------------------------------------------------- 1 | // Code generated by pioasm; DO NOT EDIT. 2 | 3 | //go:build rp2350 4 | package main 5 | import ( 6 | pio "github.com/tinygo-org/pio/rp2-pio" 7 | ) 8 | // rxfifoput 9 | 10 | const rxfifoputWrapTarget = 4 11 | const rxfifoputWrap = 7 12 | 13 | var rxfifoputInstructions = []uint16{ 14 | 0xa0c3, // 0: mov isr, null 15 | 0xe043, // 1: set y, 3 16 | 0x8010, // 2: mov rxfifo[y], isr 17 | 0x0082, // 3: jmp y--, 2 18 | // .wrap_target 19 | 0xa02b, // 4: mov x, !null 20 | 0xa0c9, // 5: mov isr, !x 21 | 0x8018, // 6: mov rxfifo[0], isr 22 | 0x0045, // 7: jmp x--, 5 23 | // .wrap 24 | } 25 | const rxfifoputOrigin = -1 26 | func rxfifoputProgramDefaultConfig(offset uint8) pio.StateMachineConfig { 27 | cfg := pio.DefaultStateMachineConfig() 28 | cfg.SetWrap(offset+rxfifoputWrapTarget, offset+rxfifoputWrap) 29 | return cfg; 30 | } 31 | 32 | -------------------------------------------------------------------------------- /rp2-pio/piolib/i2s_pio.go: -------------------------------------------------------------------------------- 1 | // Code generated by pioasm; DO NOT EDIT. 2 | 3 | //go:build rp2040 || rp2350 4 | package piolib 5 | import ( 6 | pio "github.com/tinygo-org/pio/rp2-pio" 7 | ) 8 | // i2s 9 | 10 | const i2sWrapTarget = 0 11 | const i2sWrap = 7 12 | 13 | const i2soffset_entry_point = 7 14 | 15 | var i2sInstructions = []uint16{ 16 | // .wrap_target 17 | 0x7001, // 0: out pins, 1 side 2 18 | 0x1840, // 1: jmp x--, 0 side 3 19 | 0x6001, // 2: out pins, 1 side 0 20 | 0xe82e, // 3: set x, 14 side 1 21 | 0x6001, // 4: out pins, 1 side 0 22 | 0x0844, // 5: jmp x--, 4 side 1 23 | 0x7001, // 6: out pins, 1 side 2 24 | 0xf82e, // 7: set x, 14 side 3 25 | // .wrap 26 | } 27 | const i2sOrigin = -1 28 | func i2sProgramDefaultConfig(offset uint8) pio.StateMachineConfig { 29 | cfg := pio.DefaultStateMachineConfig() 30 | cfg.SetWrap(offset+i2sWrapTarget, offset+i2sWrap) 31 | cfg.SetSidesetParams(2, false, false) 32 | return cfg; 33 | } 34 | 35 | -------------------------------------------------------------------------------- /rp2-pio/piolib/ws2812b_pio.go: -------------------------------------------------------------------------------- 1 | // Code generated by pioasm; DO NOT EDIT. 2 | 3 | //go:build rp2040 || rp2350 4 | package piolib 5 | import ( 6 | pio "github.com/tinygo-org/pio/rp2-pio" 7 | ) 8 | // ws2812b_led 9 | 10 | const ws2812b_ledWrapTarget = 0 11 | const ws2812b_ledWrap = 7 12 | 13 | const ws2812b_ledoffset_entry_point = 0 14 | 15 | var ws2812b_ledInstructions = []uint16{ 16 | // .wrap_target 17 | 0x80e0, // 0: pull ifempty block 18 | 0xe001, // 1: set pins, 1 19 | 0x6041, // 2: out y, 1 20 | 0x0065, // 3: jmp !y, 5 21 | 0x0206, // 4: jmp 6 [2] 22 | 0xe200, // 5: set pins, 0 [2] 23 | 0xe000, // 6: set pins, 0 24 | 0x01e1, // 7: jmp !osre, 1 [1] 25 | // .wrap 26 | } 27 | const ws2812b_ledOrigin = -1 28 | func ws2812b_ledProgramDefaultConfig(offset uint8) pio.StateMachineConfig { 29 | cfg := pio.DefaultStateMachineConfig() 30 | cfg.SetWrap(offset+ws2812b_ledWrapTarget, offset+ws2812b_ledWrap) 31 | return cfg; 32 | } 33 | 34 | -------------------------------------------------------------------------------- /rp2-pio/examples/blinky/blink.pio: -------------------------------------------------------------------------------- 1 | 2 | ; 3 | ; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 4 | ; 5 | ; SPDX-License-Identifier: BSD-3-Clause 6 | ; 7 | 8 | ; SET pin 0 should be mapped to your LED GPIO 9 | 10 | .program blink 11 | pull block 12 | out y, 32 13 | .wrap_target 14 | mov x, y 15 | set pins, 1 ; Turn LED on 16 | lp1: 17 | jmp x-- lp1 ; Delay for (x + 1) cycles, x is a 32 bit number 18 | mov x, y 19 | set pins, 0 ; Turn LED off 20 | lp2: 21 | jmp x-- lp2 ; Delay for the same number of cycles again 22 | .wrap ; Blink forever! 23 | 24 | 25 | % go { 26 | package main 27 | 28 | import ( 29 | "machine" 30 | pio "github.com/tinygo-org/pio/rp2-pio" 31 | ) 32 | 33 | // this is a raw helper function for use by the user which sets up the GPIO output, and configures the SM to output on a particular pin 34 | func blinkProgramInit(sm pio.StateMachine, offset uint8, pin machine.Pin) { 35 | pin.Configure(machine.PinConfig{Mode: sm.PIO().PinMode()}) 36 | sm.SetPindirsConsecutive(pin, 1, true) 37 | cfg := blinkProgramDefaultConfig(offset) 38 | cfg.SetSetPins(pin, 1) 39 | sm.Init(offset, cfg) 40 | } 41 | %} -------------------------------------------------------------------------------- /rp2-pio/piolib/ws2812bfourpixels_pio.go: -------------------------------------------------------------------------------- 1 | // Code generated by pioasm; DO NOT EDIT. 2 | 3 | //go:build rp2350 4 | package piolib 5 | import ( 6 | pio "github.com/tinygo-org/pio/rp2-pio" 7 | ) 8 | // ws2812bfourpixels 9 | 10 | const ws2812bfourpixelsWrapTarget = 0 11 | const ws2812bfourpixelsWrap = 8 12 | 13 | const ws2812bfourpixelsCyclesPerBit = 10 14 | const ws2812bfourpixelsT1 = 2 15 | const ws2812bfourpixelsT2 = 5 16 | const ws2812bfourpixelsT3 = 3 17 | 18 | var ws2812bfourpixelsInstructions = []uint16{ 19 | // .wrap_target 20 | 0x8090, // 0: mov osr, rxfifo[y] side 0 21 | 0x6221, // 1: out x, 1 side 0 [2] 22 | 0x1124, // 2: jmp !x, 4 side 1 [1] 23 | 0x14e1, // 3: jmp !osre, 1 side 1 [4] 24 | 0x04e1, // 4: jmp !osre, 1 side 0 [4] 25 | 0x0080, // 5: jmp y--, 0 side 0 26 | 0xef3f, // 6: set x, 31 side 0 [15] 27 | 0xef43, // 7: set y, 3 side 0 [15] 28 | 0x0f47, // 8: jmp x--, 7 side 0 [15] 29 | // .wrap 30 | } 31 | const ws2812bfourpixelsOrigin = -1 32 | func ws2812bfourpixelsProgramDefaultConfig(offset uint8) pio.StateMachineConfig { 33 | cfg := pio.DefaultStateMachineConfig() 34 | cfg.SetWrap(offset+ws2812bfourpixelsWrapTarget, offset+ws2812bfourpixelsWrap) 35 | cfg.SetSidesetParams(1, false, false) 36 | return cfg; 37 | } 38 | 39 | -------------------------------------------------------------------------------- /rp2-pio/piolib/spi_pio.go: -------------------------------------------------------------------------------- 1 | // Code generated by pioasm; DO NOT EDIT. 2 | 3 | //go:build rp2040 || rp2350 4 | package piolib 5 | import ( 6 | pio "github.com/tinygo-org/pio/rp2-pio" 7 | ) 8 | // spi_cpha0 9 | 10 | const spi_cpha0WrapTarget = 0 11 | const spi_cpha0Wrap = 1 12 | 13 | var spi_cpha0Instructions = []uint16{ 14 | // .wrap_target 15 | 0x6101, // 0: out pins, 1 side 0 [1] 16 | 0x5101, // 1: in pins, 1 side 1 [1] 17 | // .wrap 18 | } 19 | const spi_cpha0Origin = -1 20 | func spi_cpha0ProgramDefaultConfig(offset uint8) pio.StateMachineConfig { 21 | cfg := pio.DefaultStateMachineConfig() 22 | cfg.SetWrap(offset+spi_cpha0WrapTarget, offset+spi_cpha0Wrap) 23 | cfg.SetSidesetParams(1, false, false) 24 | return cfg; 25 | } 26 | 27 | // spi_cpha1 28 | 29 | const spi_cpha1WrapTarget = 0 30 | const spi_cpha1Wrap = 2 31 | 32 | var spi_cpha1Instructions = []uint16{ 33 | // .wrap_target 34 | 0x6021, // 0: out x, 1 side 0 35 | 0xb101, // 1: mov pins, x side 1 [1] 36 | 0x4001, // 2: in pins, 1 side 0 37 | // .wrap 38 | } 39 | const spi_cpha1Origin = -1 40 | func spi_cpha1ProgramDefaultConfig(offset uint8) pio.StateMachineConfig { 41 | cfg := pio.DefaultStateMachineConfig() 42 | cfg.SetWrap(offset+spi_cpha1WrapTarget, offset+spi_cpha1Wrap) 43 | cfg.SetSidesetParams(1, false, false) 44 | return cfg; 45 | } 46 | 47 | -------------------------------------------------------------------------------- /rp2-pio/examples/blinky/blink_pio.go: -------------------------------------------------------------------------------- 1 | // Code generated by pioasm; DO NOT EDIT. 2 | 3 | package main 4 | import ( 5 | "machine" 6 | pio "github.com/tinygo-org/pio/rp2-pio" 7 | ) 8 | // this is a raw helper function for use by the user which sets up the GPIO output, and configures the SM to output on a particular pin 9 | func blinkProgramInit(sm pio.StateMachine, offset uint8, pin machine.Pin) { 10 | pin.Configure(machine.PinConfig{Mode: sm.PIO().PinMode()}) 11 | sm.SetPindirsConsecutive(pin, 1, true) 12 | cfg := blinkProgramDefaultConfig(offset) 13 | cfg.SetSetPins(pin, 1) 14 | sm.Init(offset, cfg) 15 | } 16 | // blink 17 | 18 | const blinkWrapTarget = 2 19 | const blinkWrap = 7 20 | 21 | var blinkInstructions = []uint16{ 22 | 0x80a0, // 0: pull block 23 | 0x6040, // 1: out y, 32 24 | // .wrap_target 25 | 0xa022, // 2: mov x, y 26 | 0xe001, // 3: set pins, 1 27 | 0x0044, // 4: jmp x--, 4 28 | 0xa022, // 5: mov x, y 29 | 0xe000, // 6: set pins, 0 30 | 0x0047, // 7: jmp x--, 7 31 | // .wrap 32 | } 33 | const blinkOrigin = -1 34 | func blinkProgramDefaultConfig(offset uint8) pio.StateMachineConfig { 35 | cfg := pio.DefaultStateMachineConfig() 36 | cfg.SetWrap(offset+blinkWrapTarget, offset+blinkWrap) 37 | return cfg; 38 | } 39 | 40 | -------------------------------------------------------------------------------- /rp2-pio/piolib/ws2812b.pio: -------------------------------------------------------------------------------- 1 | ; Timings according to Datasheet 2 | ; T0H = 400ns 3 | ; T0L = 850ns 4 | ; T1H = 800ns 5 | ; T1L = 450ns 6 | ; Thus, a whole cycle is 1250ns. This will be our base unit of transmission. 7 | ; Next unit of time is one that is closest to T0H and T1L. 8 | ; If we divide baseline by 3 we get 1250/3 = 416.67, falling between 400 and 450. 9 | ; Since we need more than 1 instruction per high/low time, we examine cases where 10 | ; we split the split baseline in 3 and then calculate result PIO clockdiv since 3 is minimum 11 | ; number of instructions to do first part of high time. 12 | ; 13 | ; From the equations: 14 | ; piofreq = 256*clockfreq / (256*whole + frac) 15 | ; where period = 1e9/freq => freq = 1e9/period, so: 16 | ; 1e9/period = 256*clockfreq / (256*whole + frac) => 17 | ; 256*whole + frac = 256*clockfreq*period/1e9 18 | ; So, the previous equations yield: 19 | ; 416.67/3=138.89 -> ??? 20 | 21 | .program ws2812b_led 22 | public entry_point: 23 | pull ifempty block ; Do autopull, blocks if Tx empty. 24 | bitloop: 25 | ; 3 instructions high logic level 26 | set pins, 1 ; Drive pin high at start of pulse. 27 | out y, 1 ; Shift 1 bit out, and write it to y. 28 | jmp !y lolo ; Jump to LOW bit part. 29 | jmp hilo [2] ; Jump to 30 | 31 | lolo: 32 | set pins, 0 [2]; To create T0L we need 6 cycles, 3 of them are part of hi branch. 33 | hilo: 34 | set pins, 0 35 | jmp !osre bitloop [1] 36 | 37 | % go { 38 | //go:build rp2040 || rp2350 39 | package piolib 40 | 41 | import ( 42 | pio "github.com/tinygo-org/pio/rp2-pio" 43 | ) 44 | %} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023-2025 The TinyGo Authors. All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /rp2-pio/pio_rp2350.go: -------------------------------------------------------------------------------- 1 | //go:build rp2350 2 | 3 | package pio 4 | 5 | import ( 6 | "device/rp" 7 | ) 8 | 9 | const ( 10 | rp2350ExtraReg = 1 11 | ) 12 | 13 | // RP2350 PIO peripheral handles. 14 | var ( 15 | PIO2 = &PIO{ 16 | hw: rp.PIO2, 17 | } 18 | ) 19 | 20 | func (pio *PIO) blockIndex() uint8 { 21 | switch pio.hw { 22 | case rp.PIO0: 23 | return 0 24 | case rp.PIO1: 25 | return 1 26 | case rp.PIO2: 27 | return 2 28 | } 29 | panic(badPIO) 30 | } 31 | 32 | // SetGPIOBase configures the GPIO base for the PIO block, or which GPIO pin is 33 | // seen as pin 0 inside the PIO. Can only be set to values of 0 or 16 and only 34 | // sensible for use on RP2350B. 35 | func (pio *PIO) SetGPIOBase(base uint32) { 36 | switch base { 37 | case 0, 16: 38 | pio.hw.GPIOBASE.Set(base) 39 | default: 40 | panic("pio:invalid gpiobase") 41 | } 42 | } 43 | 44 | // SetNextPIOMask configures the 4-bit mask for state machines in the next PIO block 45 | // that should be affected by ClkDivRestart() and SetEnabled() functions on this PIO 46 | // block's state machines, allowing for cycle-perfect synchronization. RP2350-only. 47 | func (pio *PIO) SetNextPIOMask(mask uint32) { 48 | pio.hw.CTRL.ReplaceBits(mask, rp.PIO0_CTRL_NEXT_PIO_MASK_Msk, rp.PIO0_CTRL_NEXT_PIO_MASK_Pos) 49 | } 50 | 51 | // SetPrevPIOMask configures the 4-bit mask for state machines in the previous PIO 52 | // block that should be affected by ClkDivRestart() and SetEnabled() functions on this 53 | // PIO block's state machines, allowing for cycle-perfect synchronization. RP2350-only. 54 | func (pio *PIO) SetPrevPIOMask(mask uint32) { 55 | pio.hw.CTRL.ReplaceBits(mask, rp.PIO0_CTRL_PREV_PIO_MASK_Msk, rp.PIO0_CTRL_PREV_PIO_MASK_Pos) 56 | } 57 | -------------------------------------------------------------------------------- /rp2-pio/piolib/i2s.pio: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 3 | ; 4 | ; SPDX-License-Identifier: BSD-3-Clause 5 | ; 6 | 7 | ; Transmit a mono or stereo I2S audio stream as stereo 8 | ; This is 16 bits per sample; can be altered by modifying the "set" params, 9 | ; or made programmable by replacing "set x" with "mov x, y" and using Y as a config register. 10 | ; 11 | ; Autopull must be enabled, with threshold set to 32. 12 | ; Since I2S is MSB-first, shift direction should be to left. 13 | ; Hence the format of the FIFO word is: 14 | ; 15 | ; | 31 : 16 | 15 : 0 | 16 | ; | sample ws=0 | sample ws=1 | 17 | ; 18 | ; Data is output at 1 bit per clock. Use clock divider to adjust frequency. 19 | ; Fractional divider will probably be needed to get correct bit clock period, 20 | ; but for common syslck freqs this should still give a constant word select period. 21 | ; 22 | ; One output pin is used for the data output. 23 | ; Two side-set pins are used. Bit 0 is clock, bit 1 is word select. 24 | 25 | ; Send 16 bit words to the PIO for mono, 32 bit words for stereo 26 | 27 | .program i2s 28 | .side_set 2 29 | 30 | ; /--- LRCLK 31 | ; |/-- BCLK 32 | bitloop1: ; || 33 | out pins, 1 side 0b10 34 | jmp x-- bitloop1 side 0b11 35 | out pins, 1 side 0b00 36 | set x, 14 side 0b01 37 | 38 | bitloop0: 39 | out pins, 1 side 0b00 40 | jmp x-- bitloop0 side 0b01 41 | out pins, 1 side 0b10 42 | public entry_point: 43 | set x, 14 side 0b11 44 | 45 | % go { 46 | //go:build rp2040 || rp2350 47 | 48 | package piolib 49 | 50 | import ( 51 | pio "github.com/tinygo-org/pio/rp2-pio" 52 | ) 53 | %} -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | clean: 3 | @rm -rf build 4 | 5 | FMT_PATHS = ./ 6 | 7 | fmt-check: 8 | @unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1 9 | 10 | pio-test: 11 | go test ./rp2-pio 12 | 13 | smoke-test: 14 | @mkdir -p build 15 | tinygo build -target pico-w -size short -o build/test.uf2 ./rp2-pio/examples/blinky 16 | tinygo build -target pico-w -size short -o build/test.uf2 ./rp2-pio/examples/i2s 17 | tinygo build -target pico-w -size short -o build/test.uf2 ./rp2-pio/examples/pulsar 18 | tinygo build -target pico-w -size short -o build/test.uf2 ./rp2-pio/examples/parallel/hub40screen 19 | tinygo build -target pico-w -size short -o build/test.uf2 ./rp2-pio/examples/parallel/tufty 20 | tinygo build -target pico-w -size short -o build/test.uf2 ./rp2-pio/examples/ws2812b 21 | tinygo build -target pico2 -size short -o build/test.uf2 ./rp2-pio/examples/blinky 22 | tinygo build -target pico2 -size short -o build/test.uf2 ./rp2-pio/examples/i2s 23 | tinygo build -target pico2 -size short -o build/test.uf2 ./rp2-pio/examples/pulsar 24 | tinygo build -target pico2 -size short -o build/test.uf2 ./rp2-pio/examples/rxfifoput 25 | tinygo build -target pico2 -size short -o build/test.uf2 ./rp2-pio/examples/rxfifoputget 26 | tinygo build -target pico-w -size short -o build/test.uf2 ./rp2-pio/examples/parallel/hub40screen 27 | tinygo build -target pico2 -size short -o build/test.uf2 ./rp2-pio/examples/parallel/tufty 28 | tinygo build -target pico2 -size short -o build/test.uf2 ./rp2-pio/examples/ws2812b 29 | tinygo build -target pico2 -size short -o build/test.uf2 ./rp2-pio/examples/ws2812bfourpixels 30 | 31 | test: clean fmt-check pio-test smoke-test 32 | -------------------------------------------------------------------------------- /rp2-pio/examples/i2s/main.go: -------------------------------------------------------------------------------- 1 | // this example uses the rp2040 I2S peripheral to play a sine wave. 2 | // It uses a PCM5102A DAC to convert the digital signal to analog. 3 | // The sine wave is played at 44.1 kHz. 4 | // The sine wave is played in blocks of 32 samples. 5 | // The sine wave is played for 500 ms, then paused for 500 ms. 6 | // The sine wave is played indefinitely. 7 | // The sine wave is played on both the left and right channels. 8 | // connect PCM5102A DIN to GPIO2 9 | // connect PCM5102A BCK to GPIO3 10 | // connect PCM5102A LCK to GPIO4 11 | package main 12 | 13 | import ( 14 | "machine" 15 | "time" 16 | 17 | pio "github.com/tinygo-org/pio/rp2-pio" 18 | "github.com/tinygo-org/pio/rp2-pio/piolib" 19 | ) 20 | 21 | const ( 22 | i2sDataPin = machine.GPIO2 23 | i2sClockPin = machine.GPIO3 24 | 25 | NUM_SAMPLES = 32 26 | NUM_BLOCKS = 5 27 | ) 28 | 29 | // sine wave data 30 | var sine []int16 = []int16{ 31 | 6392, 12539, 18204, 23169, 27244, 30272, 32137, 32767, 32137, 32 | 30272, 27244, 23169, 18204, 12539, 6392, 0, -6393, -12540, 33 | -18205, -23170, -27245, -30273, -32138, -32767, -32138, -30273, -27245, 34 | -23170, -18205, -12540, -6393, -1, 35 | } 36 | 37 | func main() { 38 | time.Sleep(time.Millisecond * 500) 39 | 40 | sm, _ := pio.PIO0.ClaimStateMachine() 41 | i2s, err := piolib.NewI2S(sm, i2sDataPin, i2sClockPin) 42 | if err != nil { 43 | panic(err.Error()) 44 | } 45 | 46 | i2s.SetSampleFrequency(44100) 47 | 48 | data := make([]uint32, NUM_SAMPLES*NUM_BLOCKS) 49 | for i := 0; i < NUM_SAMPLES*NUM_BLOCKS; i++ { 50 | data[i] = uint32(sine[i%NUM_SAMPLES]) | uint32(sine[i%NUM_SAMPLES])<<16 51 | } 52 | 53 | // Play the sine wave 54 | for { 55 | for i := 0; i < 50; i++ { 56 | i2s.WriteStereo(data) 57 | } 58 | 59 | time.Sleep(time.Millisecond * 500) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /rp2-pio/piolib/spi.pio: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 3 | ; 4 | ; SPDX-License-Identifier: BSD-3-Clause 5 | ; 6 | 7 | ; These programs implement full-duplex SPI, with a SCK period of 4 clock 8 | ; cycles. A different program is provided for each value of CPHA, and CPOL is 9 | ; achieved using the hardware GPIO inversion available in the IO controls. 10 | ; 11 | ; Transmit-only SPI can go twice as fast -- see the ST7789 example! 12 | 13 | 14 | 15 | .program spi_cpha0 16 | .side_set 1 17 | 18 | ; Pin assignments: 19 | ; - SCK is side-set pin 0 20 | ; - MOSI is OUT pin 0 21 | ; - MISO is IN pin 0 22 | ; 23 | ; Autopush and autopull must be enabled, and the serial frame size is set by 24 | ; configuring the push/pull threshold. Shift left/right is fine, but you must 25 | ; justify the data yourself. This is done most conveniently for frame sizes of 26 | ; 8 or 16 bits by using the narrow store replication and narrow load byte 27 | ; picking behaviour of RP2040's IO fabric. 28 | 29 | ; Clock phase = 0: data is captured on the leading edge of each SCK pulse, and 30 | ; transitions on the trailing edge, or some time before the first leading edge. 31 | 32 | out pins, 1 side 0 [1] ; Stall here on empty (sideset proceeds even if 33 | in pins, 1 side 1 [1] ; instruction stalls, so we stall with SCK low) 34 | 35 | % go { 36 | //go:build rp2040 || rp2350 37 | package piolib 38 | 39 | import ( 40 | pio "github.com/tinygo-org/pio/rp2-pio" 41 | ) 42 | %} 43 | 44 | .program spi_cpha1 45 | .side_set 1 46 | 47 | ; Clock phase = 1: data transitions on the leading edge of each SCK pulse, and 48 | ; is captured on the trailing edge. 49 | 50 | out x, 1 side 0 ; Stall here on empty (keep SCK deasserted) 51 | mov pins, x side 1 [1] ; Output data, assert SCK (mov pins uses OUT mapping) 52 | in pins, 1 side 0 ; Input data, deassert SCK 53 | 54 | -------------------------------------------------------------------------------- /rp2-pio/examples/rxfifoputget/rxfifoputget.go: -------------------------------------------------------------------------------- 1 | //go:build rp2350 2 | 3 | package main 4 | 5 | import ( 6 | "time" 7 | 8 | pio "github.com/tinygo-org/pio/rp2-pio" 9 | ) 10 | 11 | //go:generate pioasm -o go rxfifoputget.pio rxfifoputget_pio.go 12 | 13 | /* 14 | RxFIFOPutGet is a non-functional example purely for documentation purposes. 15 | 16 | It demonstrates how to enable the new pio.FifoJoinRxPutGet mode, enabling 17 | both of the new FJOIN_RX_GET and FJOIN_RX_PUT modes. This allows the PIO 18 | to use the RX FIFO for both reading and writing data at any desired index, 19 | at the cost of denying all access to the RX FIFO from the system. 20 | */ 21 | func main() { 22 | sm, _ := pio.PIO0.ClaimStateMachine() 23 | putget, err := NewRxFIFOPutGet(sm) 24 | if err != nil { 25 | panic(err.Error()) 26 | } 27 | 28 | putget.Enable(true) 29 | 30 | for { 31 | time.Sleep(5 * time.Second) 32 | } 33 | } 34 | 35 | type RxFIFOPutGet struct { 36 | sm pio.StateMachine 37 | offsetPlusOne uint8 38 | } 39 | 40 | func NewRxFIFOPutGet(sm pio.StateMachine) (*RxFIFOPutGet, error) { 41 | sm.TryClaim() // SM should be claimed beforehand, we just guarantee it's claimed. 42 | Pio := sm.PIO() 43 | 44 | // rxfifoputgetInstructions and rxfifoputgetOrigin are auto-generated in rxfifoputget_pio.go 45 | // by the pioasm tool based on the .program field in the PIO assembly code 46 | offset, err := Pio.AddProgram(rxfifoputgetInstructions, rxfifoputgetOrigin) 47 | if err != nil { 48 | return nil, err 49 | } 50 | cfg := rxfifoputgetProgramDefaultConfig(offset) 51 | // Enable FJOIN_RX_PUT and FJOIN_RX_GET mode in the state machine. This mode prevents access 52 | // to the RX FIFO from the system, but gives the PIO four extra registers. 53 | cfg.SetFIFOJoin(pio.FifoJoinRxPutGet) 54 | sm.Init(offset, cfg) 55 | sm.SetEnabled(true) 56 | 57 | return &RxFIFOPutGet{sm: sm, offsetPlusOne: offset + 1}, nil 58 | } 59 | 60 | func (r *RxFIFOPutGet) Enable(enabled bool) { 61 | r.sm.SetEnabled(enabled) 62 | } 63 | -------------------------------------------------------------------------------- /rp2-pio/examples/ws2812b/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "machine" 5 | "strconv" 6 | "time" 7 | 8 | pio "github.com/tinygo-org/pio/rp2-pio" 9 | "github.com/tinygo-org/pio/rp2-pio/piolib" 10 | ) 11 | 12 | var ws2812Pin string 13 | 14 | /* 15 | This example package can be flashed, specifying the GPIO number via the -ldflags 16 | flag like so: 17 | tinygo flash -target=$TARGET_NAME -ldflags "-X main.ws2812Pin=$GPIO_NUMBER" ./examples/ws2812b/ 18 | */ 19 | func main() { 20 | pinNum, err := strconv.Atoi(ws2812Pin) 21 | if err != nil { 22 | println("Invalid pin number: " + ws2812Pin) 23 | pinNum = 16 24 | } 25 | sm, _ := pio.PIO0.ClaimStateMachine() 26 | ws, err := piolib.NewWS2812B(sm, machine.Pin(pinNum)) 27 | if err != nil { 28 | panic(err.Error()) 29 | } 30 | const lightIntensity = 64 31 | rawred := rawcolor(lightIntensity, 0, 0) 32 | rawgreen := rawcolor(0, lightIntensity, 0) 33 | // Make Christmas lights of first part of strip. 34 | ws.EnableDMA(true) 35 | ws.WriteRaw([]uint32{ 36 | 0, 37 | rawred, 38 | rawgreen, 39 | rawred, 40 | rawgreen, 41 | rawred, 42 | rawgreen, 43 | rawred, 44 | rawgreen, 45 | rawred, 46 | rawgreen, 47 | rawred, 48 | rawgreen, 49 | rawred, 50 | rawgreen, 51 | }) 52 | 53 | // And sweep first LED. 54 | const sweepIncrement = 1 55 | const sweepPeriod = time.Second / 4 56 | for { 57 | println("red sweep") 58 | for r := uint8(0); r < 255; r += sweepIncrement { 59 | ws.PutRGB(r, 0, 0) 60 | time.Sleep(sweepPeriod) 61 | } 62 | time.Sleep(time.Second) 63 | println("green sweep") 64 | for g := uint8(0); g < 255; g += sweepIncrement { 65 | ws.PutRGB(0, g, 0) 66 | time.Sleep(sweepPeriod) 67 | } 68 | time.Sleep(time.Second) 69 | println("blue sweep") 70 | for b := uint8(0); b < 255; b += sweepIncrement { 71 | ws.PutRGB(0, 0, b) 72 | time.Sleep(sweepPeriod) 73 | } 74 | time.Sleep(time.Second) 75 | } 76 | } 77 | 78 | func rawcolor(r, g, b uint8) uint32 { 79 | return uint32(g)<<24 | uint32(r)<<16 | uint32(b)<<8 80 | } 81 | -------------------------------------------------------------------------------- /rp2-pio/examples/rxfifoput/rxfifoput.go: -------------------------------------------------------------------------------- 1 | //go:build rp2350 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "time" 8 | 9 | pio "github.com/tinygo-org/pio/rp2-pio" 10 | ) 11 | 12 | //go:generate pioasm -o go rxfifoput.pio rxfifoput_pio.go 13 | 14 | /* 15 | RxFIFOPut is a simple example of a PIO counter demonstrating how to use the new 16 | PIO FJOIN_RX_PUT mode introduced with the RP2350. 17 | 18 | RxFIFOPut increments a counter in three PIO cycles, or every 20 nanoseconds. It 19 | is not possible for the system to read from the RX FIFO fast enough to keep up 20 | with the PIO at this speed, so the RX FIFO is used as a status buffer to store 21 | the current counter value. 22 | 23 | In order to read from the RX FIFO at a specific index, the system uses the new 24 | StateMachine.GetRxFIFOAt() method. 25 | */ 26 | func main() { 27 | sm, _ := pio.PIO0.ClaimStateMachine() 28 | put, err := NewRxFIFOPut(sm) 29 | if err != nil { 30 | panic(err.Error()) 31 | } 32 | 33 | for { 34 | fmt.Printf("FIFO: %08x %08x %08x %08x\r\n", put.Get(0), put.Get(1), put.Get(2), put.Get(3)) 35 | time.Sleep(5 * time.Second) 36 | } 37 | } 38 | 39 | type RxFIFOPut struct { 40 | sm pio.StateMachine 41 | offsetPlusOne uint8 42 | } 43 | 44 | func NewRxFIFOPut(sm pio.StateMachine) (*RxFIFOPut, error) { 45 | sm.TryClaim() // SM should be claimed beforehand, we just guarantee it's claimed. 46 | Pio := sm.PIO() 47 | 48 | // rxfifoputInstructions and rxfifoputOrigin are auto-generated in rxfifoput_pio.go 49 | // by the pioasm tool based on the .program field in the PIO assembly code 50 | offset, err := Pio.AddProgram(rxfifoputInstructions, rxfifoputOrigin) 51 | if err != nil { 52 | return nil, err 53 | } 54 | cfg := rxfifoputProgramDefaultConfig(offset) 55 | // Enable FJOIN_RX_PUT mode 56 | cfg.SetFIFOJoin(pio.FifoJoinRxPut) 57 | sm.Init(offset, cfg) 58 | sm.SetEnabled(true) 59 | 60 | return &RxFIFOPut{sm: sm, offsetPlusOne: offset + 1}, nil 61 | } 62 | 63 | func (r *RxFIFOPut) Enable(enabled bool) { 64 | r.sm.SetEnabled(enabled) 65 | } 66 | 67 | func (r *RxFIFOPut) Get(index int) uint32 { 68 | return r.sm.GetRxFIFOAt(index) 69 | } 70 | -------------------------------------------------------------------------------- /rp2-pio/pio_test.go: -------------------------------------------------------------------------------- 1 | package pio 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestAssemblerV0_spi3w(t *testing.T) { 8 | assm := AssemblerV0{ 9 | SidesetBits: 1, 10 | } 11 | const ( 12 | wloopOff = 0 13 | rloopOff = 5 14 | endOff = 7 15 | ) 16 | 17 | var program = []uint16{ 18 | // .wrap_target 19 | // write out x-1 bits. 20 | wloopOff:// Write/Output loop. 21 | assm.Out(OutDestPins, 1).Side(0).Encode(), // 0: out pins, 1 side 0 22 | assm.Jmp(wloopOff, JmpXNZeroDec).Side(1).Encode(), // 1: jmp x--, 0 side 1 23 | assm.Jmp(endOff, JmpYZero).Side(0).Encode(), // 2: jmp !y, 7 side 0 24 | assm.Set(SetDestPindirs, 0).Side(0).Encode(), // 3: set pindirs, 0 side 0 25 | assm.Nop().Side(0).Encode(), // 4: nop side 0 26 | // read in y-1 bits. 27 | rloopOff:// Read/input loop 28 | assm.In(InSrcPins, 1).Side(1).Encode(), // 5: in pins, 1 side 1 29 | assm.Jmp(rloopOff, JmpYNZeroDec).Side(0).Encode(), // 6: jmp y--, 5 side 0 30 | // Wait for SPI packet on IRQ. 31 | endOff:// Wait on input pin. 32 | assm.WaitPin(true, 0).Side(0).Encode(), // 7: wait 1 pin, 0 side 0 33 | assm.IRQSet(false, 0).Side(0).Encode(), // 8: irq nowait 0 side 0 34 | } 35 | var expectedProgram = []uint16{ 36 | // .wrap_target 37 | 0x6001, // 0: out pins, 1 side 0 38 | 0x1040, // 1: jmp x--, 0 side 1 39 | 0x0067, // 2: jmp !y, 7 side 0 40 | 0xe080, // 3: set pindirs, 0 side 0 41 | 0xa042, // 4: nop side 0 42 | 0x5001, // 5: in pins, 1 side 1 43 | 0x0085, // 6: jmp y--, 5 side 0 44 | 0x20a0, // 7: wait 1 pin, 0 side 0 45 | 0xc000, // 8: irq nowait 0 side 0 46 | // .wrap 47 | } 48 | if len(program) != len(expectedProgram) { 49 | t.Fatal("program length mismatch, this should not be tested") 50 | } 51 | for i := range program { 52 | if program[i] != expectedProgram[i] { 53 | t.Errorf("instr %d mismatch got!=expected: %#x != %#x", i, program[i], expectedProgram[i]) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /rp2-pio/examples/parallel/tufty/tufty.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image/color" 5 | "machine" 6 | "time" 7 | 8 | pio "github.com/tinygo-org/pio/rp2-pio" 9 | "github.com/tinygo-org/pio/rp2-pio/piolib" 10 | ) 11 | 12 | const clockHz = 133000000 13 | 14 | // Pimoroni Tufty definitions https://tinygo.org/docs/reference/microcontrollers/tufty2040/ 15 | const ( 16 | csPin = machine.GP10 17 | dcPin = machine.GP11 18 | wrPin = machine.GP12 19 | db0Pin = machine.GP14 20 | rdPin = machine.GP13 21 | blPin = machine.GP2 22 | ) 23 | 24 | func main() { 25 | time.Sleep(5 * time.Second) 26 | println("Initializing Display") 27 | const MHz = 1_000_000 28 | sm, _ := pio.PIO0.ClaimStateMachine() 29 | p8tx, err := piolib.NewParallel(sm, piolib.ParallelConfig{ 30 | Baud: 1 * MHz, 31 | Clock: wrPin, 32 | DataBase: db0Pin, 33 | BusWidth: 8, 34 | BitsPerPull: 8, 35 | }) 36 | if err != nil { 37 | panic(err.Error()) 38 | } 39 | display := ST7789{ 40 | pl: p8tx, 41 | cs: csPin, 42 | dc: dcPin, 43 | rd: rdPin, 44 | bl: blPin, 45 | width: 320, 46 | height: 240, 47 | rotation: Rotation0, 48 | } 49 | 50 | if err != nil { 51 | panic(err.Error()) 52 | } 53 | display.pl.Tx8([]byte("Hello World")) 54 | // Setup DMA 55 | println("Setting Up DMA") 56 | // display.pl.EnableDMA(2) 57 | rdPin.High() 58 | 59 | println("Display Common Init") 60 | display.CommonInit() 61 | 62 | println("Making Screen Blue") 63 | blue := color.RGBA{255, 255, 255, 255} 64 | display.FillRectangle(0, 0, 320, 240, blue) 65 | } 66 | 67 | type Displayer interface { 68 | // Size returns the current size of the display. 69 | Size() (x, y int16) 70 | 71 | // SetPizel modifies the internal buffer. 72 | SetPixel(x, y int16, c color.RGBA) 73 | 74 | // Display sends the buffer (if any) to the screen. 75 | Display() error 76 | } 77 | 78 | // Rotation is how much a display has been rotated. Displays can be rotated, and 79 | // sometimes also mirrored. 80 | type Rotation uint8 81 | 82 | // Clockwise rotation of the screen. 83 | const ( 84 | Rotation0 = iota 85 | Rotation90 86 | Rotation180 87 | Rotation270 88 | Rotation0Mirror 89 | Rotation90Mirror 90 | Rotation180Mirror 91 | Rotation270Mirror 92 | ) 93 | -------------------------------------------------------------------------------- /rp2-pio/piolib/ws2812bfourpixels.pio: -------------------------------------------------------------------------------- 1 | ; This PIO program takes pixel data for up to 4 NeoPixels (ws2812b LEDs) and sends 2 | ; it out on repeat with no intervention needed except to change the pixel colors. 3 | 4 | ; RP2350+ 5 | .pio_version 1 6 | 7 | .program ws2812bfourpixels 8 | 9 | ; Set TX FIFO to normal mode and RX FIFO to GET mode for RX FIFO random reads. 10 | ; The RX FIFO is where pixel data is stored for repeat use. 11 | .fifo txget 12 | 13 | ; Using side-set for the signal pin 14 | .side_set 1 15 | 16 | .define public CyclesPerBit T1+T2+T3 17 | .define public T1 2 ; beginning cycles 18 | .define public T2 5 ; middle cycles, high for 1, low for 0 19 | .define public T3 3 ; end cycles 20 | ; Using the Adafruit library's timing here. It's further 21 | ; from the spec than the raspi/pico-examples timing (3, 3, 4), 22 | ; but should have wider hardware compatibility. 23 | ; Works out to 7H/3L cycles for 1 and 2H/8L cycles for 0 24 | ; 800kHz bit period * 10 cycles per bit = 8 MHz PIO clock 25 | ; 26 | ; Ref: 27 | ; https://github.com/adafruit/Adafruit_NeoPixel/blob/1.15.1/rp2040_pio.h#L19-L21 28 | ; https://github.com/raspberrypi/pico-examples/blob/sdk-2.2.0/pio/ws2812/ws2812.pio#L15-L17 29 | 30 | .wrap_target 31 | 32 | pixelloop: 33 | mov osr, rxfifo[y] side 0 ; Load the pixel data from RX FIFO into OSR 34 | 35 | bitloop: 36 | out x, 1 side 0 [T3 - 1] ; End of bit/pre-first bit (pull the next) 37 | jmp !x do_zero side 1 [T1 - 1] ; Beginning 38 | do_one: 39 | jmp !osre bitloop side 1 [T2 - 1] ; Middle of 1 bit 40 | do_zero: 41 | jmp !osre bitloop side 0 [T2 - 1] ; Middle of 0 bit 42 | jmp y-- pixelloop side 0 ; Decrement our FIFO index to get the next pixel 43 | 44 | finalize: 45 | ; Before the color data is applied there needs to be a 50us+ pause 46 | ; for the messages to "latch." 47 | set x, 31 side 0 [15] 48 | 49 | delayloop: 50 | ; If not for the delay loop required, resetting the y value would be unnecessary. 51 | ; Only the last two y bits are referenced when used as the rxfifo index. 52 | set y, 3 side 0 [15] 53 | jmp x--, delayloop side 0 [15] 54 | 55 | .wrap 56 | 57 | % go { 58 | //go:build rp2350 59 | package piolib 60 | 61 | import ( 62 | pio "github.com/tinygo-org/pio/rp2-pio" 63 | ) 64 | %} -------------------------------------------------------------------------------- /rp2-pio/piolib/all_generate.go: -------------------------------------------------------------------------------- 1 | package piolib 2 | 3 | import ( 4 | "errors" 5 | "math" 6 | "runtime" 7 | "time" 8 | "unsafe" 9 | 10 | pio "github.com/tinygo-org/pio/rp2-pio" 11 | ) 12 | 13 | const timeoutRetries = math.MaxUint16 * 8 14 | 15 | var ( 16 | errTimeout = errors.New("piolib:timeout") 17 | errContentionTimeout = errors.New("piolib:contention timeout") 18 | errBusy = errors.New("piolib:busy") 19 | 20 | errDMAUnavail = errors.New("piolib:DMA channel unavailable") 21 | ) 22 | 23 | //go:generate pioasm -o go parallel8.pio parallel8_pio.go 24 | //go:generate pioasm -o go pulsar.pio pulsar_pio.go 25 | //go:generate pioasm -o go spi.pio spi_pio.go 26 | //go:generate pioasm -o go ws2812b.pio ws2812b_pio.go 27 | //go:generate pioasm -o go i2s.pio i2s_pio.go 28 | //go:generate pioasm -o go spi3w.pio spi3w_pio.go 29 | //go:generate pioasm -o go ws2812bfourpixels.pio ws2812bfourpixels_pio.go 30 | 31 | func gosched() { 32 | runtime.Gosched() 33 | } 34 | 35 | type deadline struct { 36 | t time.Time 37 | } 38 | 39 | func (dl deadline) expired() bool { 40 | if dl.t.IsZero() { 41 | return false 42 | } 43 | return time.Since(dl.t) > 0 44 | } 45 | 46 | type deadliner struct { 47 | // timeout is a bitshift value for the timeout. 48 | timeout uint8 49 | } 50 | 51 | func (ch deadliner) newDeadline() deadline { 52 | var t time.Time 53 | if ch.timeout != 0 { 54 | calc := time.Duration(1 << ch.timeout) 55 | t = time.Now().Add(calc) 56 | } 57 | return deadline{t: t} 58 | } 59 | 60 | func (ch *deadliner) setTimeout(timeout time.Duration) { 61 | if timeout <= 0 { 62 | ch.timeout = 0 63 | return // No timeout. 64 | } 65 | for i := uint8(0); i < 64; i++ { 66 | calc := time.Duration(1 << i) 67 | if calc > timeout { 68 | ch.timeout = i 69 | return 70 | } 71 | } 72 | } 73 | 74 | // helperPushUntilStall pushes buf data elements into TxReg through DMA if enabled or via [pio.StateMachine.TxPut] if dma disabled. 75 | // It blocks until TxStall flag is set in state machine FDEBUG register. TxStall flag cleared immediately on this function call. 76 | func helperPushUntilStall[T uint8 | uint16 | uint32](sm pio.StateMachine, dma dmaChannel, buf []T) (err error) { 77 | sm.ClearTxStalled() 78 | if dma.helperIsEnabled() { 79 | dreq := dmaPIO_TxDREQ(sm) 80 | err = dmaPush(dma, (*T)(unsafe.Pointer(sm.TxReg())), buf, dreq) 81 | } else { 82 | i := 0 83 | for i < len(buf) { 84 | if sm.IsTxFIFOFull() { 85 | gosched() 86 | continue 87 | } 88 | sm.TxPut(uint32(buf[i])) 89 | i++ 90 | } 91 | } 92 | if err != nil { 93 | return err 94 | } 95 | for !sm.HasTxStalled() { 96 | gosched() // Block until empty. 97 | } 98 | return nil 99 | } 100 | -------------------------------------------------------------------------------- /rp2-pio/examples/ws2812bfourpixels/ws2812bfourpixels.go: -------------------------------------------------------------------------------- 1 | //go:build rp2350 2 | 3 | package main 4 | 5 | import ( 6 | "machine" 7 | "math" 8 | "strconv" 9 | "time" 10 | 11 | pio "github.com/tinygo-org/pio/rp2-pio" 12 | "github.com/tinygo-org/pio/rp2-pio/piolib" 13 | ) 14 | 15 | var ws2812Pin string 16 | 17 | /* 18 | WS2812bFourPixels is a toy implementation of a NeoPixel library using the 19 | new PIO FJOIN_RX_GET mode introduced with the RP2350. 20 | 21 | It supports a string of up to 4 NeoPixels, using the RX FIFO to persistently 22 | store the NeoPixel color data instead of system memory via DMA. The NeoPixels 23 | will be continuously updated from the color data stored in the RX FIFO and can 24 | be updated at any time using the ns.SetRGB(), ns.SetRGBW(), ns.SetRaw(), and 25 | ns.SetColor() methods. These methods use the StateMachine.SetRxFIFOAt() 26 | method to update the color data in the RX FIFO. 27 | 28 | WS2812bFourPixels supports both RGB and RGBW modes, RGB using the NewWS2812bFourPixelsRGB() 29 | function as shown here and RGBW using the NewWS2812bFourPixelsRGBW() function. 30 | 31 | This example package can be flashed, specifying the GPIO number via the -ldflags 32 | flag like so: 33 | tinygo flash -target=$TARGET_NAME -ldflags "-X main.ws2812Pin=$GPIO_NUMBER" ./examples/ws2812bfourpixels/ 34 | */ 35 | func main() { 36 | pinNum, err := strconv.Atoi(ws2812Pin) 37 | if err != nil { 38 | println("Invalid pin number: " + ws2812Pin) 39 | pinNum = 25 40 | } 41 | Pio := pio.PIO0 42 | sm, _ := Pio.ClaimStateMachine() 43 | ns, err := piolib.NewWS2812bFourPixelsRGB(sm, machine.Pin(pinNum)) 44 | if err != nil { 45 | panic(err.Error()) 46 | } 47 | 48 | i := 0 49 | stepMax := 64 50 | for { 51 | ns.SetRaw(0, getRainbowGRB(i+6%stepMax, 0.3, 64)) 52 | ns.SetRaw(1, getRainbowGRB(i+4%stepMax, 0.3, 64)) 53 | ns.SetRaw(2, getRainbowGRB(i+2%stepMax, 0.3, 64)) 54 | ns.SetRaw(3, getRainbowGRB(i+0%stepMax, 0.3, 64)) 55 | i++ 56 | time.Sleep(100 * time.Millisecond) 57 | } 58 | } 59 | 60 | func getRainbowGRB(step int, frequency, maxValue float64) uint32 { 61 | // Frequency controls how fast the colors change. 62 | // A smaller value creates a longer, slower cycle. 63 | 64 | baseStep := float64(step) * frequency 65 | stepTwo := baseStep + math.Pi*2/3 66 | stepThree := baseStep + math.Pi*4/3 67 | 68 | // We use sine waves offset by 120 degrees (2*Pi/3 radians) for each color channel. 69 | // This creates a smooth transition through the color spectrum. 70 | red := math.Sin(baseStep)*0.5 + 0.5 71 | green := math.Sin(stepTwo)*0.5 + 0.5 72 | blue := math.Sin(stepThree)*0.5 + 0.5 73 | 74 | // The sine waves produce values between 0.0 and 1.0. 75 | // We scale these values by the desired maxValue. 76 | r := uint32(red * maxValue) 77 | g := uint32(green * maxValue) 78 | b := uint32(blue * maxValue) 79 | 80 | return g<<24 | r<<16 | b<<8 81 | } 82 | -------------------------------------------------------------------------------- /rp2-pio/piolib/pulsar.go: -------------------------------------------------------------------------------- 1 | //go:build rp2040 || rp2350 2 | 3 | package piolib 4 | 5 | import ( 6 | "errors" 7 | "machine" 8 | "time" 9 | 10 | pio "github.com/tinygo-org/pio/rp2-pio" 11 | ) 12 | 13 | var errQueueFull = errors.New("Pulsar:queue full") 14 | 15 | // Pulsar implements a square-wave generator that pulses a determined amount of pulses. 16 | type Pulsar struct { 17 | sm pio.StateMachine 18 | offsetPlusOne uint8 19 | } 20 | 21 | // NewPulsar returns a new Pulsar ready for use. 22 | func NewPulsar(sm pio.StateMachine, pin machine.Pin) (*Pulsar, error) { 23 | sm.TryClaim() // SM should be claimed beforehand, we just guarantee it's claimed. 24 | Pio := sm.PIO() 25 | 26 | offset, err := Pio.AddProgram(pulsarInstructions, pulsarOrigin) 27 | if err != nil { 28 | return nil, err 29 | } 30 | pin.Configure(machine.PinConfig{Mode: sm.PIO().PinMode()}) 31 | sm.SetPindirsConsecutive(pin, 1, true) 32 | cfg := pulsarProgramDefaultConfig(offset) 33 | cfg.SetSetPins(pin, 1) 34 | sm.Init(offset, cfg) 35 | sm.SetEnabled(true) 36 | return &Pulsar{sm: sm, offsetPlusOne: offset + 1}, nil 37 | } 38 | 39 | // IsQueueFull checks if the pulsar's queue is full. 40 | func (p *Pulsar) IsQueueFull() bool { 41 | p.mustValid() 42 | return p.sm.IsTxFIFOFull() 43 | } 44 | 45 | // Queued returns amount of actions in the pulsar's queue. 46 | func (p *Pulsar) Queued() uint8 { 47 | return uint8(p.sm.TxFIFOLevel()) 48 | } 49 | 50 | // TryQueue adds an action to the pulsar's queue. If the queue is full it returns an error. 51 | func (p *Pulsar) TryQueue(count uint32) error { 52 | if count == 0 { 53 | return nil 54 | } else if p.IsQueueFull() { 55 | return errQueueFull 56 | } 57 | p.sm.TxPut(count - 1) 58 | return nil 59 | } 60 | 61 | // SetPeriod sets the pulsar's square-wave period. Is safe to call while pulsar is running. 62 | func (p *Pulsar) SetPeriod(period time.Duration) error { 63 | p.mustValid() 64 | period /= 4 // Full pulse cycle is 4 instructions. 65 | whole, frac, err := pio.ClkDivFromPeriod(uint32(period), uint32(machine.CPUFrequency())) 66 | if err != nil { 67 | return err 68 | } 69 | p.sm.SetClkDiv(whole, frac) 70 | return nil 71 | } 72 | 73 | // Pause pauses the pulsar if enabled is true. If false unpauses the pulsar. 74 | func (p *Pulsar) Pause(disabled bool) { 75 | p.mustValid() 76 | p.sm.SetEnabled(!disabled) 77 | } 78 | 79 | // Stop stops and resets the pulsar to initial state. 80 | // Will unpause pulsar as well if paused and clear it's queue. 81 | func (p *Pulsar) Stop() { 82 | p.mustValid() 83 | // See StateMachine.Init for reference on this sequence of operations. 84 | p.sm.SetEnabled(false) 85 | p.sm.ClearFIFOs() 86 | p.sm.Restart() 87 | p.sm.ClkDivRestart() 88 | p.sm.Jmp(p.offsetPlusOne-1, pio.JmpAlways) 89 | p.sm.SetEnabled(true) 90 | } 91 | 92 | func (p *Pulsar) mustValid() { 93 | if p.offsetPlusOne == 0 { 94 | panic("piolib: Pulsar not initialized") 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pio 2 | 3 | [![Build](https://github.com/tinygo-org/pio/actions/workflows/build.yml/badge.svg)](https://github.com/tinygo-org/pio/actions/workflows/build.yml) 4 | 5 | Provides clean API to interact with RP2040/RP2350 on-board Programable Input/Output (PIO) block. 6 | See chapter 3 of the [RP2040 datasheet](https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#page=310) for more information. 7 | 8 | 9 | ### piolib 10 | This module contains the [piolib](./rp2-pio/piolib) package which contains importable drivers for use with a PIO such as: 11 | 12 | - SPI driver 13 | - 8-pin send-only parallel bus 14 | - WS2812 (Neopixel) driver 15 | - A pulse-constrained square wave generator (Pulsar) 16 | 17 | 18 | ## Introduction to PIO 19 | The PIO is a versatile hardware interface. It can support a variety of IO standards, 20 | including: 21 | - 8080 and 6800 parallel bus 22 | - I2C 23 | - 3-pin I2S 24 | - SDIO 25 | - SPI, DSPI, QSPI 26 | - UART 27 | - DPI or VGA (via resistor DAC) 28 | 29 | PIO is programmable in the same sense as a processor and has a total of nine instructions: JMP, WAIT, IN, OUT, PUSH, PULL, MOV, IRQ, and SET. These are programmed in PIO assembly format describing a PIO program, where each command corresponds to one instruction in the output binary. Below is an example program in PIO assembly: 30 | 31 | ```pio 32 | .program squarewave 33 | again: 34 | set pins, 1 [1] ; Drive pin high and then delay for one cycle 35 | set pins, 0 ; Drive pin low 36 | jmp again ; Set PC to label `again` 37 | ``` 38 | 39 | ### How to install pioasm 40 | 41 | To develop new code using PIO with this package, you must build and install the `pioasm` tool. It can be be built from the pico-sdk: 42 | 43 | ```shell 44 | git clone git@github.com:raspberrypi/pico-sdk.git 45 | cd pico-sdk/tools/pioasm 46 | cmake . 47 | make 48 | sudo make install 49 | ``` 50 | 51 | ### How to develop a PIO program 52 | 53 | To develop a PIO program you first start out with the .pio file. Let's look at the Pulsar example first. 54 | 55 | 1. [`pulsar.pio`](./rp2-pio/piolib/pulsar.pio): specifies a binary PIO program that can be loaded to the PIO program memory. 56 | 2. [`all_generate.go`](./rp2-pio/piolib/all_generate.go): holds the code generation command on the line with `//go:generate pioasm -o go pulsar.pio pulsar_pio.go` which by itself generates the raw binary code that can be loaded onto the PIO along with helper code to load it correctly inside `pulsar_pio.go`. 57 | 3. [`pulsar_pio.go`](./rp2-pio/piolib/pulsar_pio.go): contains the generated code by the `pioasm` tool. 58 | 4. [`pulsar.go`](./rp2-pio/piolib/pulsar.go): contains the User facing code that allows using the PIO as intended by the author. 59 | 60 | ### Regenerating piolib 61 | 62 | ```shell 63 | go generate ./... 64 | ``` 65 | 66 | ### Other notes 67 | 68 | Keep in mind PIO programs are very finnicky, especially differentiating between SetOutPins and SetSetPins. The difference is subtle but it can be the difference between spending days debugging a silly conceptual mistake. Have fun! 69 | -------------------------------------------------------------------------------- /rp2-pio/piolib/i2s.go: -------------------------------------------------------------------------------- 1 | //go:build rp2040 || rp2350 2 | 3 | package piolib 4 | 5 | import ( 6 | "errors" 7 | "machine" 8 | 9 | pio "github.com/tinygo-org/pio/rp2-pio" 10 | ) 11 | 12 | // I2S is a wrapper around a PIO state machine that implements I2S. 13 | // Currently only supports writing to the I2S peripheral. 14 | type I2S struct { 15 | sm pio.StateMachine 16 | offset uint8 17 | writing bool 18 | } 19 | 20 | // NewI2S creates a new I2S peripheral using the given PIO state machine. 21 | func NewI2S(sm pio.StateMachine, data, clockAndNext machine.Pin) (*I2S, error) { 22 | sm.TryClaim() // SM should be claimed beforehand, we just guarantee it's claimed. 23 | Pio := sm.PIO() 24 | 25 | offset, err := Pio.AddProgram(i2sInstructions, i2sOrigin) 26 | if err != nil { 27 | return nil, err 28 | } 29 | cfg := i2sProgramDefaultConfig(offset) 30 | 31 | // Configure pins 32 | pinCfg := machine.PinConfig{Mode: Pio.PinMode()} 33 | data.Configure(pinCfg) 34 | clockAndNext.Configure(pinCfg) 35 | (clockAndNext + 1).Configure(pinCfg) 36 | 37 | // https://github.com/raspberrypi/pico-extras/blob/09c64d509f1d7a49ceabde699ed6c74c77e195a1/src/rp2_common/pico_audio_i2s/audio_i2s.pio#L48C4-L60C81 38 | cfg.SetOutPins(data, 1) 39 | cfg.SetSidesetPins(clockAndNext) 40 | cfg.SetOutShift(false, true, 32) 41 | 42 | sm.Init(offset, cfg) 43 | 44 | pinMask := uint32(1<>8), uint8(g16>>8), uint8(b16>>8)) 80 | } 81 | 82 | // WriteRaw writes raw GRB values to a strip of WS2812B LEDs. Each uint32 is a WS2812B color 83 | // which can be created with 3 uint8 color values: 84 | // 85 | // color := uint32(g)<<24 | uint32(r)<<16 | uint32(b)<<8 86 | func (ws *WS2812B) WriteRaw(rawGRB []uint32) error { 87 | if ws.IsDMAEnabled() { 88 | return ws.writeDMA(rawGRB) 89 | } 90 | dl := ws.dma.dl.newDeadline() 91 | i := 0 92 | for i < len(rawGRB) { 93 | if ws.IsQueueFull() { 94 | if dl.expired() { 95 | return errTimeout 96 | } 97 | gosched() 98 | continue 99 | } 100 | ws.sm.TxPut(rawGRB[i]) 101 | i++ 102 | } 103 | return nil 104 | } 105 | 106 | func (ws *WS2812B) writeDMA(w []uint32) error { 107 | dreq := dmaPIO_TxDREQ(ws.sm) 108 | err := ws.dma.Push32(&ws.sm.TxReg().Reg, w, dreq) 109 | if err != nil { 110 | return err 111 | } 112 | return nil 113 | } 114 | 115 | // EnableDMA enables DMA for vectorized writes. 116 | func (ws *WS2812B) EnableDMA(enabled bool) error { 117 | return ws.dma.helperEnableDMA(enabled) 118 | } 119 | 120 | // IsDMAEnabled returns true if DMA is enabled. 121 | func (ws *WS2812B) IsDMAEnabled() bool { 122 | return ws.dma.helperIsEnabled() 123 | } 124 | -------------------------------------------------------------------------------- /rp2-pio/piolib/ws2812bfourpixels.go: -------------------------------------------------------------------------------- 1 | //go:build rp2350 2 | 3 | package piolib 4 | 5 | import ( 6 | "image/color" 7 | "machine" 8 | 9 | pio "github.com/tinygo-org/pio/rp2-pio" 10 | ) 11 | 12 | type WS2812bFourPixelsMode uint8 13 | 14 | const ( 15 | WS2812bFourPixelsModeRGB WS2812bFourPixelsMode = iota 16 | WS2812bFourPixelsModeRGBW 17 | ) 18 | 19 | // WS2812bFourPixels is an RGB LED strip controller implementation, also known as NeoPixel. 20 | type WS2812bFourPixels struct { 21 | sm pio.StateMachine 22 | cfg pio.StateMachineConfig 23 | offset uint8 24 | mode WS2812bFourPixelsMode 25 | } 26 | 27 | // WS2812bFourPixels uses the RP2350 PIO's FJOIN_RX_GET mode to control 4 NeoPixels. The 28 | // PIO sends color data out on repeat with no loops/buffer-filling/intervention 29 | // of any kind needed except to change the pixel colors or brightness. 30 | // RP2350-only due to RX FIFO register usage. 31 | func NewWS2812bFourPixelsRGB(sm pio.StateMachine, pin machine.Pin) (*WS2812bFourPixels, error) { 32 | return newWS2812bFourPixels(sm, pin, WS2812bFourPixelsModeRGB) 33 | } 34 | 35 | func NewWS2812bFourPixelsRGBW(sm pio.StateMachine, pin machine.Pin) (*WS2812bFourPixels, error) { 36 | return newWS2812bFourPixels(sm, pin, WS2812bFourPixelsModeRGBW) 37 | } 38 | 39 | func newWS2812bFourPixels(sm pio.StateMachine, pin machine.Pin, mode WS2812bFourPixelsMode) (*WS2812bFourPixels, error) { 40 | sm.TryClaim() // SM should be claimed beforehand, we just guarantee it's claimed. 41 | 42 | const pixelFreq = 800 * machine.KHz 43 | whole, frac, err := pio.ClkDivFromFrequency(pixelFreq*ws2812bfourpixelsCyclesPerBit, machine.CPUFrequency()) 44 | if err != nil { 45 | return nil, err 46 | } 47 | Pio := sm.PIO() 48 | offset, err := Pio.AddProgram(ws2812bfourpixelsInstructions, ws2812bfourpixelsOrigin) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | pin.Configure(machine.PinConfig{Mode: Pio.PinMode()}) 54 | sm.SetPindirsConsecutive(pin, 1, true) 55 | 56 | cfg := ws2812bfourpixelsProgramDefaultConfig(offset) 57 | cfg.SetSidesetPins(pin) 58 | cfg.SetClkDivIntFrac(whole, frac) 59 | cfg.SetFIFOJoin(pio.FifoJoinRxGet) 60 | 61 | switch mode { 62 | case WS2812bFourPixelsModeRGBW: 63 | cfg.SetOutShift(false, true, 32) 64 | default: 65 | cfg.SetOutShift(false, true, 24) 66 | } 67 | 68 | ns := &WS2812bFourPixels{ 69 | sm: sm, 70 | cfg: cfg, 71 | offset: offset, 72 | mode: mode, 73 | } 74 | ns.sm.Init(ns.offset, ns.cfg) 75 | ns.sm.SetEnabled(true) 76 | 77 | // Zero out the FIFO values to avoid sending residual junk data 78 | for i := range 4 { 79 | ns.SetRaw(i, 0) 80 | } 81 | 82 | return ns, nil 83 | } 84 | 85 | // SetEnabled starts or stops the state machine. 86 | func (ns *WS2812bFourPixels) SetEnabled(enable bool) { 87 | ns.sm.SetEnabled(enable) 88 | } 89 | 90 | // GetMode returns the mode of the WS2812bFourPixels instance. 91 | func (ns *WS2812bFourPixels) GetMode() WS2812bFourPixelsMode { 92 | return ns.mode 93 | } 94 | 95 | // SetRGB sets a given pixel to an RGB color value. 96 | func (ns *WS2812bFourPixels) SetRGB(index int, r, g, b uint8) { 97 | ns.SetRGBW(index, r, g, b, 0) 98 | } 99 | 100 | // SetRGBW sets a given pixel to an RGBW color value. 101 | func (ns *WS2812bFourPixels) SetRGBW(index int, r, g, b, w uint8) { 102 | // Shift occurs to left for WS2812B to interpret correctly. 103 | color := uint32(g)<<24 | uint32(r)<<16 | uint32(b)<<8 | uint32(w) 104 | ns.SetRaw(index, color) 105 | } 106 | 107 | // SetRaw sets a given pixel to a raw color value. The grb uint32 is a WS2812B color 108 | // which can be created with 3 uint8 color values: 109 | // 110 | // color := uint32(green)<<24 | uint32(red)<<16 | uint32(blue)<<8 111 | func (ns *WS2812bFourPixels) SetRaw(index int, grbw uint32) { 112 | // Reverse the index, the PIO prints in reverse order 113 | fifoIndex := 3 - index 114 | ns.sm.SetRxFIFOAt(grbw, fifoIndex) 115 | } 116 | 117 | // SetColor wraps SetRGB for a [color.Color] type. 118 | func (ns *WS2812bFourPixels) SetColor(index int, c color.Color) { 119 | r16, g16, b16, _ := c.RGBA() 120 | ns.SetRGB(index, uint8(r16>>8), uint8(g16>>8), uint8(b16>>8)) 121 | } 122 | -------------------------------------------------------------------------------- /rp2-pio/piolib/parallel.go: -------------------------------------------------------------------------------- 1 | package piolib 2 | 3 | import ( 4 | "errors" 5 | "machine" 6 | "math" 7 | 8 | pio "github.com/tinygo-org/pio/rp2-pio" 9 | ) 10 | 11 | // Parallel implements a parallel bus of arbitrary number of data lines (up to 32). 12 | type Parallel struct { 13 | sm pio.StateMachine 14 | progOff uint8 15 | dma dmaChannel 16 | } 17 | 18 | type ParallelConfig struct { 19 | // Baud determines the clock speed of the parallel bus. 20 | Baud uint32 21 | // Clock is the single clock pin for the parallel bus. 22 | Clock machine.Pin 23 | // DataBase is the first of BusWidth consecutive pins defining the data lines of the parallel bus. 24 | DataBase machine.Pin 25 | // BusWidth is the amount of output pins of the parallel bus. 26 | BusWidth uint8 27 | // BitsPerPull sets the output shift register (OSR) pull threshold. 28 | // It determines how many bits to send over bus per value pulled before discarding current OSR value 29 | // and pulling a new value from TxFIFO. 30 | // Must be a multiple of BusWidth. 31 | BitsPerPull uint8 32 | } 33 | 34 | func NewParallel(sm pio.StateMachine, cfg ParallelConfig) (*Parallel, error) { 35 | const sideSetBitCount = 1 36 | const programOrigin = -1 37 | asm := pio.AssemblerV0{ 38 | SidesetBits: sideSetBitCount, 39 | } 40 | var program = [3]uint16{ 41 | asm.Out(pio.OutDestPins, cfg.BusWidth).Side(0).Encode(), // 0: out pins, side 0 42 | asm.Nop().Side(1).Encode(), // 1: nop side 1 43 | asm.Nop().Side(0).Encode(), // 2: nop side 0 44 | } 45 | maxBaud := math.MaxUint32 / uint32(len(program)) 46 | if cfg.Baud > maxBaud { 47 | return nil, errors.New("max baud for parallel exceeded") 48 | } else if cfg.BitsPerPull%cfg.BusWidth != 0 { 49 | return nil, errors.New("bits per pull must be multiple of bus width") 50 | } else if cfg.BitsPerPull < cfg.BusWidth { 51 | return nil, errors.New("bits per pull must be greater or equal to bus width") 52 | } else if cfg.BusWidth == 0 { 53 | return nil, errors.New("zero bus width") 54 | } 55 | piofreq := cfg.Baud * uint32(len(program)) 56 | whole, frac, err := pio.ClkDivFromFrequency(piofreq, machine.CPUFrequency()) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | sm.TryClaim() 62 | Pio := sm.PIO() 63 | progOffset, err := Pio.AddProgram(program[:], programOrigin) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | clkMask := uint32(1) << cfg.Clock 69 | pinMask := clkMask 70 | pinCfg := machine.PinConfig{Mode: Pio.PinMode()} 71 | for pinoff := 0; pinoff < int(cfg.BusWidth); pinoff++ { 72 | pin := cfg.DataBase + machine.Pin(pinoff) 73 | pinMask |= 1 << pin 74 | pin.Configure(pinCfg) 75 | } 76 | cfg.Clock.Configure(pinCfg) 77 | 78 | scfg := asm.DefaultStateMachineConfig(progOffset, program[:]) 79 | 80 | scfg.SetOutPins(cfg.DataBase, cfg.BusWidth) 81 | scfg.SetOutShift(true, true, uint16(cfg.BitsPerPull)) 82 | scfg.SetSidesetPins(cfg.Clock) 83 | 84 | scfg.SetClkDivIntFrac(whole, frac) 85 | scfg.SetFIFOJoin(pio.FifoJoinTx) 86 | 87 | sm.SetPinsMasked(0, pinMask) 88 | sm.SetPindirsMasked(pinMask, pinMask) 89 | sm.Init(progOffset, scfg) 90 | sm.SetEnabled(true) 91 | return &Parallel{ 92 | sm: sm, 93 | progOff: progOffset, 94 | }, nil 95 | } 96 | 97 | // IsEnabled returns true if the state machine on the Parallel6 is enabled and ready to transmit. 98 | func (p6 *Parallel) IsEnabled() bool { 99 | return p6.sm.IsEnabled() 100 | } 101 | 102 | // SetEnabled enables or disables the state machine. 103 | func (p6 *Parallel) SetEnabled(b bool) { 104 | p6.sm.SetEnabled(b) 105 | } 106 | 107 | // Tx32 pushes the uint32 buffer to the PIO Tx register. 108 | func (p6 *Parallel) Tx32(data []uint32) (err error) { 109 | return helperPushUntilStall(p6.sm, p6.dma, data) 110 | } 111 | 112 | // Tx16 pushes the uint16 buffer to the PIO Tx register. 113 | func (p6 *Parallel) Tx16(data []uint16) (err error) { 114 | return helperPushUntilStall(p6.sm, p6.dma, data) 115 | } 116 | 117 | // Tx16 pushes the uint8 buffer to the PIO Tx register. 118 | func (p6 *Parallel) Tx8(data []uint8) (err error) { 119 | return helperPushUntilStall(p6.sm, p6.dma, data) 120 | } 121 | 122 | func (p6 *Parallel) IsDMAEnabled() bool { 123 | return p6.dma.helperIsEnabled() 124 | } 125 | 126 | func (p6 *Parallel) EnableDMA(enabled bool) error { 127 | return p6.dma.helperEnableDMA(enabled) 128 | } 129 | -------------------------------------------------------------------------------- /rp2-pio/piolib/spi.go: -------------------------------------------------------------------------------- 1 | //go:build rp2040 || rp2350 2 | 3 | package piolib 4 | 5 | import ( 6 | "errors" 7 | "machine" 8 | 9 | pio "github.com/tinygo-org/pio/rp2-pio" 10 | ) 11 | 12 | type SPI struct { 13 | sm pio.StateMachine 14 | progOffset uint8 15 | mode uint8 16 | } 17 | 18 | func NewSPI(sm pio.StateMachine, spicfg machine.SPIConfig) (*SPI, error) { 19 | sm.TryClaim() // SM should be claimed beforehand, we just guarantee it's claimed. 20 | const nbits = 8 21 | // https://github.com/raspberrypi/pico-examples/blob/eca13acf57916a0bd5961028314006983894fc84/pio/spi/spi.pio#L46 22 | if !sm.IsValid() { 23 | return nil, errors.New("invalid state machine") 24 | } 25 | 26 | whole, frac, err := pio.ClkDivFromFrequency(spicfg.Frequency, machine.CPUFrequency()) 27 | if err != nil { 28 | return nil, err 29 | } 30 | Pio := sm.PIO() 31 | var instructions []uint16 32 | var origin int8 33 | var cfger func(uint8) pio.StateMachineConfig 34 | switch spicfg.Mode { 35 | case 0b00: 36 | instructions = spi_cpha0Instructions 37 | origin = spi_cpha0Origin 38 | cfger = spi_cpha0ProgramDefaultConfig 39 | case 0b01: 40 | // The pin muxes can be configured to invert the output (among other things 41 | // and this is a cheesy way to get CPOL=1 42 | // rp.IO_BANK0.GPIO0_CTRL.ReplaceBits(value, ) TODO: https://github.com/raspberrypi/pico-sdk/blob/6a7db34ff63345a7badec79ebea3aaef1712f374/src/rp2_common/hardware_gpio/gpio.c#L80 43 | // SPI is synchronous, so bypass input synchroniser to reduce input delay. 44 | 45 | instructions = spi_cpha1Instructions 46 | origin = spi_cpha1Origin 47 | cfger = spi_cpha1ProgramDefaultConfig 48 | case 0b10, 0b11: 49 | return nil, errors.New("unsupported mode") 50 | default: 51 | panic("invalid mode") 52 | } 53 | 54 | offset, err := Pio.AddProgram(instructions, origin) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | cfg := cfger(offset) 60 | 61 | cfg.SetOutPins(spicfg.SDO, 1) 62 | cfg.SetInPins(spicfg.SDI, 1) 63 | cfg.SetSidesetPins(spicfg.SCK) 64 | 65 | cfg.SetOutShift(false, true, uint16(nbits)) 66 | cfg.SetInShift(false, true, uint16(nbits)) 67 | 68 | cfg.SetClkDivIntFrac(whole, frac) 69 | 70 | // MOSI, SCK output are low, MISO is input. 71 | outMask := uint32((1 << spicfg.SCK) | (1 << spicfg.SDO)) 72 | inMask := uint32(1 << spicfg.SDI) 73 | sm.SetPinsMasked(0, outMask) 74 | sm.SetPindirsMasked(outMask, outMask|inMask) 75 | 76 | pincfg := machine.PinConfig{Mode: Pio.PinMode()} 77 | spicfg.SCK.Configure(pincfg) 78 | spicfg.SDO.Configure(pincfg) 79 | spicfg.SDI.Configure(pincfg) 80 | Pio.SetInputSyncBypassMasked(inMask, inMask) 81 | 82 | sm.Init(offset, cfg) 83 | sm.SetEnabled(true) 84 | 85 | spi := &SPI{sm: sm, progOffset: offset, mode: spicfg.Mode} 86 | return spi, nil 87 | } 88 | 89 | func (spi *SPI) Tx(w, r []byte) error { 90 | rxRemain, txRemain := len(r), len(w) 91 | if rxRemain != txRemain { 92 | return errors.New("expect lengths to be equal") 93 | } 94 | retries := int8(32) 95 | for rxRemain != 0 || txRemain != 0 { 96 | stall := true 97 | if txRemain != 0 && !spi.sm.IsTxFIFOFull() { 98 | spi.sm.TxPut(uint32(w[len(w)-txRemain])) 99 | txRemain-- 100 | stall = false 101 | } 102 | if txRemain != 0 && !spi.sm.IsRxFIFOEmpty() { 103 | r[len(r)-rxRemain] = uint8(spi.sm.RxGet()) 104 | rxRemain-- 105 | stall = false 106 | } 107 | retries-- 108 | if retries <= 0 { 109 | return errors.New("pioSPI timeout") 110 | } else if stall { 111 | // We stalled on this iteration, yield process. 112 | gosched() 113 | } 114 | } 115 | return nil 116 | } 117 | 118 | func (spi *SPI) Transfer(c byte) (rx byte, _ error) { 119 | waitTx := true 120 | waitRx := true 121 | retries := int8(16) 122 | for waitTx || waitRx { 123 | if waitTx && !spi.sm.IsTxFIFOFull() { 124 | spi.sm.TxPut(uint32(c)) 125 | waitTx = false 126 | } 127 | if waitRx && !spi.sm.IsRxFIFOEmpty() { 128 | rx = byte(spi.sm.RxGet()) 129 | waitRx = false 130 | } 131 | retries-- 132 | if retries <= 0 { 133 | return 0, errors.New("pioSPI timeout") 134 | } 135 | } 136 | return rx, nil 137 | } 138 | 139 | // SPI represents a SPI bus. It is implemented by the machine.SPI type. 140 | type _SPI interface { 141 | // Tx transmits the given buffer w and receives at the same time the buffer r. 142 | // The two buffers must be the same length. The only exception is when w or r are nil, 143 | // in which case Tx only transmits (without receiving) or only receives (while sending 0 bytes). 144 | Tx(w, r []byte) error 145 | 146 | // Transfer writes a single byte out on the SPI bus and receives a byte at the same time. 147 | // If you want to transfer multiple bytes, it is more efficient to use Tx instead. 148 | Transfer(b byte) (byte, error) 149 | } 150 | -------------------------------------------------------------------------------- /rp2-pio/examples/parallel/tufty/st7789.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "image/color" 6 | "machine" 7 | "time" 8 | 9 | "github.com/tinygo-org/pio/rp2-pio/piolib" 10 | ) 11 | 12 | // ST7789 wraps a Parallel ST7789 Display 13 | type ST7789 struct { 14 | // Pins 15 | cs machine.Pin 16 | dc machine.Pin 17 | rd machine.Pin 18 | bl machine.Pin 19 | 20 | pl *piolib.Parallel 21 | 22 | // General Display Stuff 23 | width uint16 24 | height uint16 25 | rotation Rotation 26 | 27 | //Copied stuff from the TinyGo Drivers implementation 28 | buf [6]byte 29 | } 30 | 31 | func (st *ST7789) SetBacklight(on bool) { 32 | if st.bl != machine.NoPin { 33 | pwm := machine.PWM1 // LCD LED on Tufty2040 corresponds to PWM1. 34 | // Configure the PWM 35 | pwm.Configure(machine.PWMConfig{}) 36 | ch, err := pwm.Channel(st.bl) 37 | if err != nil { 38 | println(err.Error()) 39 | return 40 | } 41 | if on { 42 | pwm.Set(ch, pwm.Top()) 43 | return 44 | } 45 | pwm.Set(ch, 0) 46 | return 47 | } 48 | println("no backlight pin defined") 49 | } 50 | 51 | func (st *ST7789) CommonInit() { 52 | st.dc.Configure(machine.PinConfig{Mode: machine.PinOutput}) 53 | st.cs.Configure(machine.PinConfig{Mode: machine.PinOutput}) 54 | 55 | // Configure Backlight Pin 56 | st.SetBacklight(false) 57 | 58 | println("SWRESET") 59 | st.command(SWRESET, []byte{}) 60 | 61 | time.Sleep(150 * time.Millisecond) 62 | 63 | //Common Init 64 | println("TEON") 65 | st.command(TEON, []byte{}) 66 | println("COLMOD") 67 | st.command(COLMOD, []byte{0x05}) // 16 bits per pixel 68 | println("PORCTRL") 69 | st.command(PORCTRL, []byte{0x0c, 0x0c, 0x00, 0x33, 0x33}) 70 | println("LCMCTRL") 71 | st.command(LCMCTRL, []byte{0x2c}) 72 | println("VDVVHREN") 73 | st.command(VDVVRHEN, []byte{0x01}) 74 | println("VRHS") 75 | st.command(VRHS, []byte{0x12}) 76 | println("VDVS") 77 | st.command(VDVS, []byte{0x20}) 78 | println("PWCTRL1") 79 | st.command(PWCTRL1, []byte{0xa4, 0xa1}) 80 | println("FRCTRL2") 81 | st.command(FRCTRL2, []byte{0x0f}) 82 | 83 | // Tufty is 320x240 84 | println("GCTRL") 85 | st.command(GCTRL, []byte{0x35}) 86 | println("VCOMS") 87 | st.command(VCOMS, []byte{0x1f}) 88 | println("0xD6") 89 | st.command(0xD6, []byte{0xa1}) // ??? 90 | println("GMCTRP1") 91 | st.command(GMCTRP1, []byte{0xD0, 0x08, 0x11, 0x08, 0x0C, 0x15, 0x39, 0x33, 0x50, 0x36, 0x13, 0x14, 0x29, 0x2D}) 92 | println("GMCTRN1") 93 | st.command(GMCTRN1, []byte{0xD0, 0x08, 0x10, 0x08, 0x06, 0x06, 0x39, 0x44, 0x51, 0x0B, 0x16, 0x14, 0x2F, 0x31}) 94 | 95 | println("INVON") 96 | st.command(INVON, []byte{}) 97 | println("SLPOUT") 98 | st.command(SLPOUT, []byte{}) 99 | println("DISPON") 100 | st.command(DISPON, []byte{}) 101 | 102 | time.Sleep(100 * time.Millisecond) 103 | 104 | // Configure Display Rotation 105 | st.configureDisplayRotation(st.rotation) 106 | 107 | println("Turning on backlight") 108 | if st.bl != machine.NoPin { 109 | time.Sleep(50 * time.Millisecond) 110 | st.SetBacklight(true) 111 | } 112 | } 113 | 114 | func (st *ST7789) configureDisplayRotation(rotation Rotation) { 115 | var madctl uint8 116 | var rotate180 bool 117 | caset := []uint16{0, 0} 118 | raset := []uint16{0, 0} 119 | 120 | if rotation == Rotation180 || rotation == Rotation90 { 121 | rotate180 = true 122 | } 123 | if rotation == Rotation90 || rotation == Rotation270 { 124 | st.width, st.height = st.height, st.width 125 | } 126 | 127 | caset[0] = 0 128 | caset[1] = 319 129 | raset[0] = 0 130 | raset[1] = 239 131 | if rotate180 { 132 | madctl = ROW_ORDER 133 | } else { 134 | madctl = COL_ORDER 135 | } 136 | madctl |= SWAP_XY | SCAN_ORDER 137 | 138 | caset[0] = (caset[0] << 8) | ((caset[0] >> 8) & 0xFF) 139 | caset[1] = (caset[1] << 8) | ((caset[1] >> 8) & 0xFF) 140 | raset[0] = (raset[0] << 8) | ((raset[0] >> 8) & 0xFF) 141 | raset[1] = (raset[1] << 8) | ((raset[1] >> 8) & 0xFF) 142 | 143 | st.command(CASET, []byte{byte(caset[0] >> 8), byte(caset[0] & 0xff), byte(caset[1] >> 8), byte(caset[1] & 0xff)}) 144 | st.command(CASET, []byte{byte(raset[0] >> 8), byte(raset[0] & 0xff), byte(raset[1] >> 8), byte(raset[1] & 0xff)}) 145 | st.command(MADCTL, []byte{madctl}) 146 | } 147 | 148 | func (st *ST7789) command(command byte, data []byte) { 149 | st.dc.Low() 150 | st.cs.Low() 151 | st.pl.Tx8([]byte{command}) 152 | // st.writeBlockingParallel([]byte{command}, 1) 153 | 154 | if len(data) > 0 { 155 | st.dc.High() 156 | st.pl.Tx8(data) 157 | // st.writeBlockingParallel(data, len(data)) 158 | } 159 | st.cs.High() 160 | } 161 | 162 | func RGBATo565(c color.RGBA) uint16 { 163 | r, g, b, _ := c.RGBA() 164 | return uint16((r & 0xF800) + 165 | ((g & 0xFC00) >> 5) + 166 | ((b & 0xF800) >> 11)) 167 | } 168 | 169 | func (st *ST7789) Size() (w, h int16) { 170 | return int16(st.width), int16(st.height) 171 | } 172 | 173 | func (st *ST7789) setWindow(x, y, w, h int16) { 174 | x += 0 175 | y += 0 176 | copy(st.buf[:4], []uint8{uint8(x >> 8), uint8(x), uint8((x + w - 1) >> 8), uint8(x + w - 1)}) 177 | st.command(CASET, st.buf[:4]) 178 | copy(st.buf[:4], []uint8{uint8(y >> 8), uint8(y), uint8((y + h - 1) >> 8), uint8(y + h - 1)}) 179 | st.command(RASET, st.buf[:4]) 180 | st.command(RAMWR, []byte{}) 181 | } 182 | 183 | func (st *ST7789) FillRectangle(x, y, width, height int16, c color.RGBA) error { 184 | k, i := st.Size() 185 | if x < 0 || y < 0 || width <= 0 || height <= 0 || 186 | x >= k || (x+width) > k || y >= i || (y+height) > i { 187 | return errors.New("rectangle coordinates outside display area") 188 | } 189 | st.setWindow(x, y, width, height) 190 | c565 := RGBATo565(c) 191 | c1 := uint8(c565 >> 8) 192 | c2 := uint8(c565) 193 | 194 | fb := make([]uint8, st.width*st.height*2) 195 | for i := 0; i < len(fb)/2; i++ { 196 | fb[i*2] = c1 197 | fb[i*2+1] = c2 198 | } 199 | st.command(RAMWR, fb) 200 | return nil 201 | } 202 | -------------------------------------------------------------------------------- /rp2-pio/examples/parallel/hub40screen/hub40.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "machine" 5 | "runtime" 6 | "time" 7 | "unsafe" 8 | 9 | pio "github.com/tinygo-org/pio/rp2-pio" 10 | "github.com/tinygo-org/pio/rp2-pio/piolib" 11 | ) 12 | 13 | func main() { 14 | // sleep to manage to visualize output. 15 | time.Sleep(2 * time.Second) 16 | const usePIO = true 17 | var p6 Parallel6Bus 18 | if usePIO { 19 | println("use pio") 20 | sm, _ := pio.PIO0.ClaimStateMachine() 21 | p6concrete, err := piolib.NewParallel(sm, piolib.ParallelConfig{ 22 | Baud: 12_000, 23 | DataBase: machine.GPIO0, 24 | BusWidth: 6, 25 | Clock: machine.GPIO6, 26 | BitsPerPull: 24, 27 | }) 28 | if err != nil { 29 | panic(err) 30 | } 31 | err = p6concrete.EnableDMA(true) 32 | if err != nil { 33 | panic(err) 34 | } 35 | p6 = ¶llel6{Parallel: *p6concrete} 36 | } else { 37 | p6 = &bitbangParallel6{ 38 | rd0: makepinout(0), 39 | gd0: makepinout(1), 40 | bd0: makepinout(2), 41 | rd1: makepinout(3), 42 | gd1: makepinout(4), 43 | bd1: makepinout(5), 44 | clk: makepinout(6), 45 | } 46 | } 47 | var emptyScreen [4][4]uint32 48 | s := NewHUB40Screen(p6, makepinout(7), makepinout(8)) 49 | s.Draw(&emptyScreen) 50 | for { 51 | // testScreen(s) 52 | testScreenBus(s) 53 | } 54 | } 55 | 56 | type parallel6 struct { 57 | piolib.Parallel 58 | } 59 | 60 | func (p6 *parallel6) Tx24(b []uint32) error { 61 | return p6.Parallel.Tx32(b) // parallel bus already configured as 24 shift bits. 62 | } 63 | 64 | // SetScreen sets a 3-bit RGB value at (ix, iy). 65 | // ix: 0..15, iy: 0..7 66 | func SetScreen(screen *[4][4]uint32, ix, iy int, rgb3bits uint8) { 67 | if ix < 0 || ix >= 16 || iy < 0 || iy >= 8 { 68 | return // or panic 69 | } 70 | colBlock := ix / 4 // 0..3 71 | rowIdx := iy % 4 // 0..3 within the half 72 | up := 1 - (iy / 4) // 0 = lower half, 1 = upper half 73 | 74 | groupBase := (ix % 4) * 6 // 6 bits per group 75 | bitoff := groupBase + up*3 76 | 77 | mask := uint32(0b111) << bitoff 78 | v := &screen[colBlock][rowIdx] 79 | *v &^= mask 80 | *v |= uint32(rgb3bits&0x7) << bitoff 81 | } 82 | 83 | type Parallel6Bus interface { 84 | Tx24(buf []uint32) error 85 | } 86 | 87 | func NewHUB40Screen(bus Parallel6Bus, latch, outputEnable pinout) *HUB40Screen { 88 | return &HUB40Screen{ 89 | lat: latch, 90 | oe: outputEnable, 91 | p6: bus, 92 | } 93 | } 94 | 95 | func testScreen(s *HUB40Screen) { 96 | var emptyscreen [4][4]uint32 97 | var screen [4][4]uint32 98 | s.Draw(&emptyscreen) 99 | for iy := 0; iy < 8; iy++ { 100 | for ix := 0; ix < 16; ix++ { 101 | SetScreen(&screen, ix, iy, 0b101) 102 | s.Draw(&screen) 103 | time.Sleep(30 * time.Millisecond) 104 | } 105 | } 106 | for range 2 { 107 | s.Draw(&screen) 108 | time.Sleep(time.Second / 3) 109 | s.Draw(&emptyscreen) 110 | time.Sleep(time.Second / 3) 111 | } 112 | for i := range screen { 113 | for j := range screen[i] { 114 | screen[i][j] = 0xffff_ffff 115 | } 116 | } 117 | s.Draw(&screen) 118 | time.Sleep(2 * time.Second) 119 | } 120 | 121 | func testScreenBus(s *HUB40Screen) { 122 | var screen [4][4]uint32 123 | buf := unsafe.Slice(&screen[0][0], 4*4) 124 | for j := range buf { 125 | for i := 0; i < 24; i++ { 126 | mask := uint32(1) << i 127 | buf[j] = mask 128 | buf[0] |= 1 129 | buf[len(buf)-1] |= 1 << 23 130 | s.Draw(&screen) 131 | time.Sleep(50 * time.Millisecond) 132 | } 133 | } 134 | } 135 | 136 | // HUB40Screen is a RGB single-bit-per-pixel screen that works on the basis of 137 | // several shift registers arranges in two rows. Each clock represents 6 bits 138 | // over the parallel-6 bus, so two LEDs per clock. The device I am using has 139 | // no markings other than "HUB40" and "OF-20R3A2-0816-V21JC" which is likely not a real model. 140 | // It seems to be a one-off design for a media company. Data lines are: 141 | // - RD0..BD1: Six Red/Green/Blue data lines for row 0 and row 1. 142 | // - OE: Output enable. Turns entire display on or off. Is negated. 143 | // - LAT: Latch. Latches shift registers values. Basically switches display buffer to the one written most recently. 144 | type HUB40Screen struct { 145 | // data pins. 146 | lat pinout 147 | oe pinout 148 | p6 Parallel6Bus 149 | } 150 | 151 | func (s *HUB40Screen) Latch() { 152 | s.lat.High() 153 | runtime.Gosched() 154 | s.lat.Low() 155 | } 156 | 157 | func (s *HUB40Screen) Enable(b bool) { 158 | s.oe(!b) 159 | } 160 | 161 | // Write writes the screen data without latching. 162 | func (s *HUB40Screen) Write(screen *[4][4]uint32) (err error) { 163 | buf := unsafe.Slice(&screen[0][0], 4*4) 164 | return s.p6.Tx24(buf) 165 | } 166 | 167 | // Draw writes and then latches screen data. 168 | func (s *HUB40Screen) Draw(screen *[4][4]uint32) (err error) { 169 | err = s.Write(screen) 170 | if err == nil { 171 | s.Latch() 172 | } 173 | return err 174 | } 175 | 176 | type bitbangParallel6 struct { 177 | rd0, gd0, bd0, rd1, gd1, bd1 pinout 178 | clk pinout 179 | } 180 | 181 | func (s *bitbangParallel6) Tx24(buf []uint32) error { 182 | for i := range buf { 183 | s.clkout24(buf[i]) 184 | } 185 | return nil 186 | } 187 | 188 | func (s *bitbangParallel6) clkout24(v uint32) { 189 | const bits6 = 0b11_1111 190 | s.rawclkout6(uint8(v & bits6)) 191 | s.rawclkout6(uint8((v >> 6) & bits6)) 192 | s.rawclkout6(uint8((v >> 12) & bits6)) 193 | s.rawclkout6(uint8((v >> 18) & bits6)) 194 | } 195 | 196 | func (s *bitbangParallel6) rawclkout6(rgbBits uint8) { 197 | s.clk.Low() 198 | s.rd0(rgbBits&(1<<0) != 0) 199 | s.gd0(rgbBits&(1<<1) != 0) 200 | s.bd0(rgbBits&(1<<2) != 0) 201 | 202 | s.rd1(rgbBits&(1<<3) != 0) 203 | s.gd1(rgbBits&(1<<4) != 0) 204 | s.bd1(rgbBits&(1<<5) != 0) 205 | s.clk.High() 206 | s.clk.Low() 207 | } 208 | 209 | type pinout func(level bool) 210 | 211 | func (p pinout) High() { p(true) } 212 | func (p pinout) Low() { p(false) } 213 | 214 | func makepinout(pin machine.Pin) pinout { 215 | pin.Configure(machine.PinConfig{Mode: machine.PinOutput}) 216 | return pin.Set 217 | } 218 | -------------------------------------------------------------------------------- /rp2-pio/instrv1.go: -------------------------------------------------------------------------------- 1 | package pio 2 | 3 | // AssemblerV1 provides a fluent API for programming PIO 4 | // within the Go language for PIO version 1 (RP2350). 5 | // Most logic is shared with [AssemblerV0]. 6 | type AssemblerV1 struct { 7 | SidesetBits uint8 8 | } 9 | 10 | func (asm AssemblerV1) v0() AssemblerV0 { 11 | return AssemblerV0{ 12 | SidesetBits: asm.SidesetBits, 13 | } 14 | } 15 | 16 | // Jmp instruction unchanged from [AssemblerV0.Jmp]. 17 | func (asm AssemblerV1) Jmp(addr uint8, cond JmpCond) instructionV0 { return asm.v0().Jmp(addr, cond) } 18 | 19 | // WaitGPIO instruction unchanged from [AssemblerV0.WaitGPIO]. 20 | func (asm AssemblerV1) WaitGPIO(polarity bool, pin uint8) instructionV0 { 21 | return asm.v0().WaitGPIO(polarity, pin) 22 | } 23 | 24 | // WaitIRQ instruction unchanged from [AssemblerV0.WaitIRQ]. 25 | func (asm AssemblerV1) WaitIRQ(polarity bool, relative bool, irqindex uint8) instructionV0 { 26 | return asm.v0().WaitIRQ(polarity, relative, irqindex) 27 | } 28 | 29 | // WaitPin instruction unchanged from [AssemblerV0.WaitPin]. 30 | func (asm AssemblerV1) WaitPin(polarity bool, pin uint8) instructionV0 { 31 | return asm.v0().WaitPin(polarity, pin) 32 | } 33 | 34 | // WaitJmpPin waits on the pin indexed by the PINCTRL_JMP_PIN configuration, plus an Index in the range 0-3, all 35 | // modulo 32. Other values of Index are reserved. 36 | func (asm AssemblerV1) WaitJmpPin(polarity bool, pin uint8) instructionV0 { 37 | flag := boolAsU8(polarity) << 2 38 | return asm.v0().instrArgs(_INSTR_BITS_WAIT, 0b11|flag, pin) 39 | } 40 | 41 | // In instruction unchanged from [AssemblerV0.In]. 42 | func (asm AssemblerV1) In(src InSrc, value uint8) instructionV0 { 43 | return asm.v0().In(src, value) 44 | } 45 | 46 | // Out instruction unchanged from [AssemblerV0.Out]. 47 | func (asm AssemblerV1) Out(dest OutDest, value uint8) instructionV0 { 48 | return asm.v0().Out(dest, value) 49 | } 50 | 51 | // Push instruction unchanged from [AssemblerV0.Push]. 52 | func (asm AssemblerV1) Push(ifFull bool, block bool) instructionV0 { 53 | return asm.v0().Push(ifFull, block) 54 | } 55 | 56 | // Pull instruction unchanged from [AssemblerV0.Pull]. 57 | func (asm AssemblerV1) Pull(ifEmpty bool, block bool) instructionV0 { 58 | return asm.v0().Pull(ifEmpty, block) 59 | } 60 | 61 | // Mov in version 1 of PIO works identically to version 0 but adding following new functionality. 62 | // - Added Pindirs as destination for MOV: This allows changing the direction of all OUT-mapped pins with a single instruction: MOV PINDIRS, NULL or MOV 63 | // PINDIRS, ~NULL 64 | // - Adds SM IRQ flags as a source for MOV x, STATUS. This allows branching (as well as blocking) on the assertion of SM IRQ flags. 65 | // - Adds the FJOIN_RX_GET FIFO mode. A new MOV encoding reads any of the four RX FIFO storage registers into OSR. 66 | // - New FJOIN_RX_PUT FIFO mode. A new MOV encoding writes the ISR into any of the four RX FIFO storage registers. 67 | func (asm AssemblerV1) Mov(dest MovDest, src MovSrc) instructionV0 { 68 | return asm.v0().Mov(dest, src) 69 | } 70 | 71 | // MovInvert is [AssemblerV0.MovInvert] unchanged but with available [AssemblerV1.Mov] functionality. 72 | func (asm AssemblerV1) MovInvert(dest MovDest, src MovSrc) instructionV0 { 73 | return asm.v0().MovInvert(dest, src) 74 | } 75 | 76 | // MovReverse is [AssemblerV0.MovReverse] unchanged but with available [AssemblerV1.Mov] functionality. 77 | func (asm AssemblerV1) MovReverse(dest MovDest, src MovSrc) instructionV0 { 78 | return asm.v0().MovReverse(dest, src) 79 | } 80 | 81 | // MovOSRFromRx reads the selected RX FIFO entry into the OSR. The PIO state machine can read the FIFO entries in any order, indexed 82 | // either by the Y register, or an immediate Index in the instruction. Requires the SHIFTCTRL_FJOIN_RX_GET configuration field 83 | // to be set, otherwise its operation is undefined. 84 | // - If idxByImmediate (index by immediate) is set, the RX FIFO’s registers are indexed by the two least-significant bits of the Index 85 | // operand. Otherwise, they are indexed by the two least-significant bits of the Y register. When IdxI is clear, all non-zero 86 | // values of Index are reserved encodings, and their operation is undefined. 87 | func (asm AssemblerV1) MovOSRFromRx(idxByImmediate bool, RxFifoIndex uint8) instructionV0 { 88 | instr := _INSTR_BITS_MOV | (0b1001 << 4) | (uint16(boolAsU8(idxByImmediate) << 3)) | uint16(RxFifoIndex)&0b111 89 | return asm.v0().instr(instr) 90 | } 91 | 92 | // MovISRToRx writes the ISR to a selected RX FIFO entry. The state machine can write the RX FIFO entries in any order, indexed either 93 | // by the Y register, or an immediate Index in the instruction. Requires the SHIFTCTRL_FJOIN_RX_PUT configuration field to be 94 | // set, otherwise its operation is undefined. The FIFO configuration can be specified for the program via the .fifo directive 95 | // (see pioasm_fifo). 96 | // - If idxByImmediate (index by immediate) is set, the RX FIFO’s registers are indexed by the two least-significant bits of the Index 97 | // operand. Otherwise, they are indexed by the two least-significant bits of the Y register. When IdxI is clear, all non-zero 98 | // values of Index are reserved encodings, and their operation is undefined. 99 | func (asm AssemblerV1) MovISRToRx(idxByImmediate bool, RxFifoIndex uint8) instructionV0 { 100 | instr := _INSTR_BITS_MOV | (0b1000 << 4) | (uint16(boolAsU8(idxByImmediate) << 3)) | uint16(RxFifoIndex)&0b111 101 | return asm.v0().instr(instr) 102 | } 103 | 104 | // Set instruction unchanged from [AssemblerV0.Set]. 105 | func (asm AssemblerV1) Set(dest SetDest, value uint8) instructionV0 { 106 | return asm.v0().Set(dest, value) 107 | } 108 | 109 | // IRQSet sets the IRQ flag selected by irqIndex. 110 | func (asm AssemblerV1) IRQSet(irqIndex uint8, idxMode IRQIndexMode) instructionV0 { 111 | return asm.irq(false, false, irqIndex, idxMode) 112 | } 113 | 114 | // IRQClear clears the IRQ flag selected by irqIndex argument. See [AssemblerV1.IRQSet]. 115 | func (asm AssemblerV1) IRQClear(irqIndex uint8, idxMode IRQIndexMode) instructionV0 { 116 | return asm.irq(true, false, irqIndex, idxMode) 117 | } 118 | 119 | // IRQWait sets the IRQ flag selected by irqIndex and waits for it to be cleared before proceeding. 120 | // If Wait is set, Delay cycles do not begin until after the wait period elapses. 121 | func (asm AssemblerV1) IRQWait(irqIndex uint8, idxMode IRQIndexMode) instructionV0 { 122 | return asm.irq(false, true, irqIndex, idxMode) 123 | } 124 | 125 | func (asm AssemblerV1) irq(clear, wait bool, irqIndex uint8, idxMode IRQIndexMode) instructionV0 { 126 | instr := _INSTR_BITS_IRQ | uint16(boolAsU8(clear))<<6 | uint16(boolAsU8(wait))<<5 | uint16(idxMode&0b11)<<3 | uint16(irqIndex&0b111) 127 | return asm.v0().instr(instr) 128 | } 129 | 130 | // Nop instruction unchanged from [AssemblerV0.Nop]. 131 | func (asm AssemblerV1) Nop() instructionV0 { return asm.v0().Nop() } 132 | -------------------------------------------------------------------------------- /rp2-pio/examples/parallel/tufty/dma.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "device/rp" 5 | "runtime/volatile" 6 | "unsafe" 7 | ) 8 | 9 | // Single DMA channel. See rp.DMA_Type. 10 | type dmaChannel struct { 11 | READ_ADDR volatile.Register32 12 | WRITE_ADDR volatile.Register32 13 | TRANS_COUNT volatile.Register32 14 | CTRL_TRIG volatile.Register32 15 | _ [12]volatile.Register32 // aliases 16 | } 17 | 18 | // Static assignment of DMA channels to peripherals. 19 | // Allocating them statically is good enough for now. If lots of peripherals use 20 | // DMA, these might need to be assigned at runtime. 21 | const ( 22 | spi0DMAChannel = iota 23 | spi1DMAChannel 24 | ) 25 | 26 | // DMA channels usable on the RP2040. 27 | var dmaChannels = (*[12]dmaChannel)(unsafe.Pointer(rp.DMA)) 28 | 29 | const ( 30 | DREQ_PIO0_TX0 = 0x0 31 | DREQ_PIO0_TX1 = 0x1 32 | DREQ_PIO0_TX2 = 0x2 33 | DREQ_PIO0_TX3 = 0x3 34 | DREQ_PIO0_RX0 = 0x4 35 | DREQ_PIO0_RX1 = 0x5 36 | DREQ_PIO0_RX2 = 0x6 37 | DREQ_PIO0_RX3 = 0x7 38 | DREQ_PIO1_TX0 = 0x8 39 | DREQ_PIO1_TX1 = 0x9 40 | DREQ_PIO1_TX2 = 0xa 41 | DREQ_PIO1_TX3 = 0xb 42 | DREQ_PIO1_RX0 = 0xc 43 | DREQ_PIO1_RX1 = 0xd 44 | DREQ_PIO1_RX2 = 0xe 45 | DREQ_PIO1_RX3 = 0xf 46 | DREQ_SPI0_TX = 0x10 47 | DREQ_SPI0_RX = 0x11 48 | DREQ_SPI1_TX = 0x12 49 | DREQ_SPI1_RX = 0x13 50 | DREQ_UART0_TX = 0x14 51 | DREQ_UART0_RX = 0x15 52 | DREQ_UART1_TX = 0x16 53 | DREQ_UART1_RX = 0x17 54 | DREQ_PWM_WRAP0 = 0x18 55 | DREQ_PWM_WRAP1 = 0x19 56 | DREQ_PWM_WRAP2 = 0x1a 57 | DREQ_PWM_WRAP3 = 0x1b 58 | DREQ_PWM_WRAP4 = 0x1c 59 | DREQ_PWM_WRAP5 = 0x1d 60 | DREQ_PWM_WRAP6 = 0x1e 61 | DREQ_PWM_WRAP7 = 0x1f 62 | DREQ_I2C0_TX = 0x20 63 | DREQ_I2C0_RX = 0x21 64 | DREQ_I2C1_TX = 0x22 65 | DREQ_I2C1_RX = 0x23 66 | DREQ_ADC = 0x24 67 | DREQ_XIP_STREAM = 0x25 68 | DREQ_XIP_SSITX = 0x26 69 | DREQ_XIP_SSIRX = 0x27 70 | ) 71 | 72 | type DMATransferSize uint32 73 | 74 | const ( 75 | DMA_SIZE_8 DMATransferSize = iota 76 | DMA_SIZE_16 77 | DMA_SIZE_32 78 | ) 79 | 80 | /* 81 | static inline dma_channel_config dma_channel_get_default_config(uint channel) { 82 | dma_channel_config c = {0}; 83 | channel_config_set_read_increment(&c, true); 84 | channel_config_set_write_increment(&c, false); 85 | channel_config_set_dreq(&c, DREQ_FORCE); 86 | channel_config_set_chain_to(&c, channel); 87 | channel_config_set_transfer_data_size(&c, DMA_SIZE_32); 88 | channel_config_set_ring(&c, false, 0); 89 | channel_config_set_bswap(&c, false); 90 | channel_config_set_irq_quiet(&c, false); 91 | channel_config_set_enable(&c, true); 92 | channel_config_set_sniff_enable(&c, false); 93 | channel_config_set_high_priority( &c, false); 94 | return c; 95 | } 96 | */ 97 | func getDefaultDMAConfig(channel uint32) uint32 { 98 | var cc uint32 99 | setReadIncrement(cc, true) 100 | setWriteIncrement(cc, false) 101 | setDREQ(cc, rp.DMA_CH0_CTRL_TRIG_TREQ_SEL_PERMANENT) 102 | setChainTo(cc, channel) 103 | setTransferDataSize(cc, DMA_SIZE_32) 104 | setRing(cc, false, 0) 105 | setBSwap(cc, false) 106 | setIRQQuiet(cc, false) 107 | setEnable(cc, true) 108 | setSniffEnable(cc, false) 109 | setHighPriority(cc, false) 110 | return cc 111 | } 112 | 113 | /* 114 | static inline void channel_config_set_read_increment(dma_channel_config *c, bool incr) { 115 | c->ctrl = incr ? (c->ctrl | DMA_CH0_CTRL_TRIG_INCR_READ_BITS) : (c->ctrl & ~DMA_CH0_CTRL_TRIG_INCR_READ_BITS); 116 | } 117 | */ 118 | func setReadIncrement(cc uint32, incr bool) { 119 | if incr { 120 | cc = cc | rp.DMA_CH0_CTRL_TRIG_INCR_READ 121 | return 122 | } 123 | cc = cc & ^uint32(rp.DMA_CH0_CTRL_TRIG_INCR_READ) 124 | } 125 | 126 | /* 127 | static inline void channel_config_set_write_increment(dma_channel_config *c, bool incr) { 128 | c->ctrl = incr ? (c->ctrl | DMA_CH0_CTRL_TRIG_INCR_WRITE_BITS) : (c->ctrl & ~DMA_CH0_CTRL_TRIG_INCR_WRITE_BITS); 129 | } 130 | */ 131 | func setWriteIncrement(cc uint32, incr bool) { 132 | if incr { 133 | cc = cc | rp.DMA_CH0_CTRL_TRIG_INCR_WRITE 134 | return 135 | } 136 | cc = cc & ^uint32(rp.DMA_CH0_CTRL_TRIG_INCR_WRITE) 137 | } 138 | 139 | /* 140 | static inline void channel_config_set_dreq(dma_channel_config *c, uint dreq) { 141 | assert(dreq <= DREQ_FORCE); 142 | c->ctrl = (c->ctrl & ~DMA_CH0_CTRL_TRIG_TREQ_SEL_BITS) | (dreq << DMA_CH0_CTRL_TRIG_TREQ_SEL_LSB); 143 | } 144 | */ 145 | func setDREQ(cc uint32, dreq uint32) { 146 | cc = (cc & ^uint32(rp.DMA_CH0_CTRL_TRIG_TREQ_SEL_Msk)) | (uint32(dreq) << rp.DMA_CH0_CTRL_TRIG_TREQ_SEL_Pos) 147 | } 148 | 149 | /* 150 | static inline void channel_config_set_chain_to(dma_channel_config *c, uint chain_to) { 151 | assert(chain_to <= NUM_DMA_CHANNELS); 152 | c->ctrl = (c->ctrl & ~DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS) | (chain_to << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB); 153 | } 154 | */ 155 | func setChainTo(cc uint32, chainTo uint32) { 156 | cc = (cc & ^uint32(rp.DMA_CH0_CTRL_TRIG_CHAIN_TO_Msk)) | (chainTo << rp.DMA_CH0_CTRL_TRIG_CHAIN_TO_Pos) 157 | } 158 | 159 | /* 160 | static inline void channel_config_set_transfer_data_size(dma_channel_config *c, enum dma_channel_transfer_size size) { 161 | assert(size == DMA_SIZE_8 || size == DMA_SIZE_16 || size == DMA_SIZE_32); 162 | c->ctrl = (c->ctrl & ~DMA_CH0_CTRL_TRIG_DATA_SIZE_BITS) | (((uint)size) << DMA_CH0_CTRL_TRIG_DATA_SIZE_LSB); 163 | } 164 | */ 165 | func setTransferDataSize(cc uint32, size DMATransferSize) { 166 | cc = (cc & ^uint32(rp.DMA_CH0_CTRL_TRIG_DATA_SIZE_Msk)) | (uint32(size) << rp.DMA_CH0_CTRL_TRIG_DATA_SIZE_Pos) 167 | } 168 | 169 | /* 170 | static inline void channel_config_set_ring(dma_channel_config *c, bool write, uint size_bits) { 171 | assert(size_bits < 32); 172 | c->ctrl = (c->ctrl & ~(DMA_CH0_CTRL_TRIG_RING_SIZE_BITS | DMA_CH0_CTRL_TRIG_RING_SEL_BITS)) | 173 | (size_bits << DMA_CH0_CTRL_TRIG_RING_SIZE_LSB) | 174 | (write ? DMA_CH0_CTRL_TRIG_RING_SEL_BITS : 0); 175 | } 176 | */ 177 | func setRing(cc uint32, write bool, sizeBits uint32) { 178 | if write { 179 | cc = (cc & ^uint32(rp.DMA_CH0_CTRL_TRIG_RING_SIZE_Msk|rp.DMA_CH0_CTRL_TRIG_RING_SEL_Pos)) | 180 | (sizeBits << rp.DMA_CH0_CTRL_TRIG_RING_SIZE_Pos) | rp.DMA_CH0_CTRL_TRIG_RING_SEL_Pos 181 | return 182 | } 183 | cc = (cc & ^uint32(rp.DMA_CH0_CTRL_TRIG_RING_SIZE_Msk|rp.DMA_CH0_CTRL_TRIG_RING_SEL_Pos)) | 184 | (sizeBits << rp.DMA_CH0_CTRL_TRIG_RING_SIZE_Pos) | 0 185 | } 186 | 187 | /* 188 | static inline void channel_config_set_bswap(dma_channel_config *c, bool bswap) { 189 | c->ctrl = bswap ? (c->ctrl | DMA_CH0_CTRL_TRIG_BSWAP_BITS) : (c->ctrl & ~DMA_CH0_CTRL_TRIG_BSWAP_BITS); 190 | } 191 | */ 192 | func setBSwap(cc uint32, bswap bool) { 193 | if bswap { 194 | cc = cc | rp.DMA_CH0_CTRL_TRIG_BSWAP_Pos 195 | return 196 | } 197 | cc = cc & ^uint32(rp.DMA_CH0_CTRL_TRIG_BSWAP_Pos) 198 | } 199 | 200 | /* 201 | static inline void channel_config_set_irq_quiet(dma_channel_config *c, bool irq_quiet) { 202 | c->ctrl = irq_quiet ? (c->ctrl | DMA_CH0_CTRL_TRIG_IRQ_QUIET_BITS) : (c->ctrl & ~DMA_CH0_CTRL_TRIG_IRQ_QUIET_BITS); 203 | } 204 | */ 205 | func setIRQQuiet(cc uint32, irqQuiet bool) { 206 | if irqQuiet { 207 | cc = cc | rp.DMA_CH0_CTRL_TRIG_IRQ_QUIET_Pos 208 | return 209 | } 210 | cc = cc & ^uint32(rp.DMA_CH0_CTRL_TRIG_IRQ_QUIET_Pos) 211 | } 212 | 213 | /* 214 | static inline void channel_config_set_high_priority(dma_channel_config *c, bool high_priority) { 215 | c->ctrl = high_priority ? (c->ctrl | DMA_CH0_CTRL_TRIG_HIGH_PRIORITY_BITS) : (c->ctrl & ~DMA_CH0_CTRL_TRIG_HIGH_PRIORITY_BITS); 216 | } 217 | */ 218 | func setHighPriority(cc uint32, highPriority bool) { 219 | if highPriority { 220 | cc = cc | rp.DMA_CH0_CTRL_TRIG_HIGH_PRIORITY_Pos 221 | return 222 | } 223 | cc = cc & ^uint32(rp.DMA_CH0_CTRL_TRIG_HIGH_PRIORITY_Pos) 224 | } 225 | 226 | /* 227 | static inline void channel_config_set_enable(dma_channel_config *c, bool enable) { 228 | c->ctrl = enable ? (c->ctrl | DMA_CH0_CTRL_TRIG_EN_BITS) : (c->ctrl & ~DMA_CH0_CTRL_TRIG_EN_BITS); 229 | } 230 | */ 231 | func setEnable(cc uint32, enable bool) { 232 | if enable { 233 | cc = cc | rp.DMA_CH0_CTRL_TRIG_EN_Pos 234 | return 235 | } 236 | cc = cc & ^uint32(rp.DMA_CH0_CTRL_TRIG_EN_Pos) 237 | } 238 | 239 | /* 240 | static inline void channel_config_set_sniff_enable(dma_channel_config *c, bool sniff_enable) { 241 | c->ctrl = sniff_enable ? (c->ctrl | DMA_CH0_CTRL_TRIG_SNIFF_EN_BITS) : (c->ctrl & 242 | ~DMA_CH0_CTRL_TRIG_SNIFF_EN_BITS); 243 | } 244 | */ 245 | func setSniffEnable(cc uint32, sniffEnable bool) { 246 | if sniffEnable { 247 | cc = cc | rp.DMA_CH0_CTRL_TRIG_SNIFF_EN_Pos 248 | return 249 | } 250 | cc = cc & ^uint32(rp.DMA_CH0_CTRL_TRIG_SNIFF_EN_Pos) 251 | } 252 | -------------------------------------------------------------------------------- /rp2-pio/piolib/spi3w.go: -------------------------------------------------------------------------------- 1 | //go:build rp2040 || rp2350 2 | 3 | package piolib 4 | 5 | import ( 6 | "device/rp" 7 | "machine" 8 | "runtime/volatile" 9 | "time" 10 | "unsafe" 11 | 12 | pio "github.com/tinygo-org/pio/rp2-pio" 13 | ) 14 | 15 | // SPI3 is a 3-wire SPI implementation for specialized use cases, such as 16 | // the Pico W's on-board CYW43439 WiFi module. It uses a shared data input/output pin. 17 | type SPI3w struct { 18 | sm pio.StateMachine 19 | dma dmaChannel 20 | offset uint8 21 | 22 | statusEn bool 23 | programWrapTarget uint8 24 | lastStatus uint32 25 | pinMask uint32 26 | } 27 | 28 | func NewSPI3w(sm pio.StateMachine, dio, clk machine.Pin, baud uint32) (*SPI3w, error) { 29 | baud *= 2 // We have 2 instructions per bit in the hot loop. 30 | whole, frac, err := pio.ClkDivFromFrequency(baud, machine.CPUFrequency()) 31 | if err != nil { 32 | return nil, err // Early return on bad clock. 33 | } 34 | 35 | // https://github.com/embassy-rs/embassy/blob/c4a8b79dbc927e46fcc71879673ad3410aa3174b/cyw43-pio/src/lib.rs#L90 36 | sm.TryClaim() // SM should be claimed beforehand, we just guarantee it's claimed. 37 | Pio := sm.PIO() 38 | assm := pio.AssemblerV0{ 39 | SidesetBits: 1, 40 | } 41 | const ( 42 | origin = -1 43 | wloopOff = 0 44 | rloopOff = 5 45 | endOff = 7 46 | ) 47 | var program = [...]uint16{ 48 | // .wrap_target 49 | // write out x-1 bits. 50 | wloopOff:// Write/Output loop. 51 | assm.Out(pio.OutDestPins, 1).Side(0).Encode(), // 0: out pins, 1 side 0 52 | assm.Jmp(wloopOff, pio.JmpXNZeroDec).Side(1).Encode(), // 1: jmp x--, 0 side 1 53 | assm.Jmp(endOff, pio.JmpYZero).Side(0).Encode(), // 2: jmp !y, 7 side 0 54 | assm.Set(pio.SetDestPindirs, 0).Side(0).Encode(), // 3: set pindirs, 0 side 0 55 | assm.Nop().Side(0).Encode(), // 4: nop side 0 56 | // read in y-1 bits. 57 | rloopOff:// Read/input loop 58 | assm.In(pio.InSrcPins, 1).Side(1).Encode(), // 5: in pins, 1 side 1 59 | assm.Jmp(rloopOff, pio.JmpYNZeroDec).Side(0).Encode(), // 6: jmp y--, 5 side 0 60 | // Wait for SPI packet on IRQ. 61 | endOff:// Wait on input pin. 62 | assm.WaitPin(true, 0).Side(0).Encode(), // 7: wait 1 pin, 0 side 0 63 | assm.IRQSet(false, 0).Side(0).Encode(), // 8: irq nowait 0 side 0 64 | } 65 | const a = len(program) - 1 66 | 67 | offset, err := Pio.AddProgram(program[:], origin) 68 | if err != nil { 69 | return nil, err 70 | } 71 | cfg := assm.DefaultStateMachineConfig(offset, program[:]) 72 | // Configure state machine. 73 | cfg.SetOutPins(dio, 1) 74 | cfg.SetSetPins(dio, 1) 75 | cfg.SetInPins(dio, 1) 76 | cfg.SetSidesetPins(clk) 77 | cfg.SetOutShift(false, true, 32) 78 | cfg.SetInShift(false, true, 32) 79 | cfg.SetClkDivIntFrac(whole, frac) 80 | 81 | // Configure pins 82 | pinCfg := machine.PinConfig{Mode: Pio.PinMode()} 83 | dio.Configure(pinCfg) 84 | clk.Configure(pinCfg) 85 | Pio.SetInputSyncBypassMasked(1<> rp.PADS_BANK0_GPIO0_DRIVE_Pos 97 | dioPad.ReplaceBits(drive, driveMsk, rp.PADS_BANK0_GPIO0_DRIVE_Pos) 98 | 99 | dioPad.ReplaceBits(1, 1, rp.PADS_BANK0_GPIO0_SLEWFAST_Pos) // Enable fast slewrate. 100 | 101 | clkPad := pinPadCtrl(clk) 102 | clkPad.ReplaceBits(drive, driveMsk, rp.PADS_BANK0_GPIO0_DRIVE_Pos) 103 | clkPad.ReplaceBits(1, 1, rp.PADS_BANK0_GPIO0_SLEWFAST_Pos) // Enable fast slewrate. 104 | 105 | // Initialize state machine. 106 | sm.Init(offset, cfg) 107 | pinMask := uint32(1< 0 { 126 | writeBits = uint32(len(w)*32 - 1) 127 | } 128 | if len(r) > 0 { 129 | readBits = uint32(len(r)*32 - 1) 130 | } 131 | spi.prepTx(readBits, writeBits) 132 | deadline := spi.newDeadline() 133 | if len(w) > 0 { 134 | err = spi.write(w, deadline) 135 | if err != nil { 136 | return err 137 | } 138 | err = spi.waitWrite(deadline) 139 | if err != nil { 140 | return err 141 | } 142 | } 143 | if len(r) == 0 { 144 | return nil 145 | } 146 | return spi.read(r, deadline) 147 | } 148 | 149 | func (spi *SPI3w) CmdWrite(cmd uint32, w []uint32) (err error) { 150 | writeBits := (1+len(w))*32 - 1 151 | var readBits uint32 152 | if spi.statusEn { 153 | readBits = 31 154 | } 155 | 156 | spi.prepTx(readBits, uint32(writeBits)) 157 | deadline := spi.newDeadline() 158 | spi.sm.TxPut(cmd) 159 | err = spi.write(w, deadline) 160 | if err != nil { 161 | return err 162 | } 163 | err = spi.waitWrite(deadline) 164 | if err != nil { 165 | return err 166 | } 167 | if spi.statusEn { 168 | err = spi.getStatus(deadline) 169 | } 170 | return err 171 | } 172 | 173 | func (spi *SPI3w) CmdRead(cmd uint32, r []uint32) (err error) { 174 | const writeBits = 31 175 | readBits := len(r)*32 - 1 176 | if spi.statusEn { 177 | readBits += 32 178 | } 179 | 180 | spi.prepTx(uint32(readBits), writeBits) 181 | deadline := spi.newDeadline() 182 | spi.sm.TxPut(cmd) 183 | err = spi.read(r, deadline) 184 | if err != nil { 185 | return err 186 | } 187 | if spi.statusEn { 188 | err = spi.getStatus(deadline) 189 | } 190 | return err 191 | } 192 | 193 | func (spi *SPI3w) read(r []uint32, dl deadline) error { 194 | if spi.IsDMAEnabled() { 195 | return spi.readDMA(r) 196 | } 197 | i := 0 198 | for i < len(r) { 199 | if spi.sm.IsRxFIFOEmpty() { 200 | if dl.expired() { 201 | return errTimeout 202 | } 203 | gosched() 204 | continue 205 | } 206 | r[i] = spi.sm.RxGet() 207 | spi.sm.TxPut(r[i]) 208 | i++ 209 | } 210 | 211 | return nil 212 | } 213 | 214 | func (spi *SPI3w) write(w []uint32, dl deadline) error { 215 | if spi.IsDMAEnabled() { 216 | return spi.writeDMA(w) 217 | } 218 | 219 | i := 0 220 | for i < len(w) { 221 | if spi.sm.IsTxFIFOFull() { 222 | if dl.expired() { 223 | return errTimeout 224 | } 225 | gosched() 226 | continue 227 | } 228 | spi.sm.TxPut(w[i]) 229 | i++ 230 | } 231 | return nil 232 | } 233 | 234 | func (spi *SPI3w) waitWrite(deadline deadline) error { 235 | // DMA/TxPush is done after this point but we still have to wait for 236 | // the FIFO to be empty. 237 | for !spi.sm.IsTxFIFOEmpty() { 238 | if deadline.expired() { 239 | return errTimeout 240 | } 241 | gosched() 242 | } 243 | return nil 244 | } 245 | 246 | // LastStatus returns the latest status. This is only valid if EnableStatus(true) was called. 247 | func (spi *SPI3w) LastStatus() uint32 { 248 | return spi.lastStatus 249 | } 250 | 251 | // EnableStatus enables the reading of the last status word after a CmdRead/CmdWrite. 252 | func (spi *SPI3w) EnableStatus(enabled bool) { 253 | spi.statusEn = enabled 254 | } 255 | 256 | // SetTimeout sets the read/write timeout. Use 0 as argument to disable timeouts. 257 | func (spi *SPI3w) SetTimeout(timeout time.Duration) { 258 | spi.dma.dl.setTimeout(timeout) 259 | } 260 | 261 | func (spi *SPI3w) newDeadline() deadline { 262 | return spi.dma.dl.newDeadline() 263 | } 264 | 265 | func (spi *SPI3w) getStatus(dl deadline) error { 266 | for spi.sm.IsRxFIFOEmpty() { 267 | if dl.expired() { 268 | return errTimeout 269 | } 270 | gosched() 271 | } 272 | 273 | err := spi.read(unsafe.Slice(&spi.lastStatus, 1), dl) 274 | if err != nil { 275 | return err 276 | } 277 | return nil 278 | } 279 | 280 | func (spi *SPI3w) prepTx(readbits, writebits uint32) { 281 | spi.sm.SetEnabled(false) 282 | // Clearing the FIFO will prevent remaining data from leaving 283 | // a HIGH on the data pin apparently. 284 | spi.sm.ClearFIFOs() 285 | // The state machine must be restarted to prevent glitchiness. 286 | spi.sm.Restart() 287 | 288 | spi.sm.SetX(writebits) 289 | spi.sm.SetY(readbits) 290 | var asm pio.AssemblerV0 291 | spi.sm.Exec(asm.Set(pio.SetDestPindirs, 1).Encode()) // Set Pindir out. 292 | spi.sm.Jmp(spi.offset+spi.programWrapTarget, pio.JmpAlways) 293 | 294 | spi.sm.SetEnabled(true) 295 | } 296 | 297 | // DMA code below. 298 | 299 | func (spi *SPI3w) EnableDMA(enabled bool) error { 300 | return spi.dma.helperEnableDMA(enabled) 301 | } 302 | 303 | func (spi *SPI3w) readDMA(r []uint32) error { 304 | dreq := dmaPIO_RxDREQ(spi.sm) 305 | err := spi.dma.Pull32(r, &spi.sm.RxReg().Reg, dreq) 306 | if err != nil { 307 | return err 308 | } 309 | return nil 310 | } 311 | 312 | func (spi *SPI3w) writeDMA(w []uint32) error { 313 | dreq := dmaPIO_TxDREQ(spi.sm) 314 | err := spi.dma.Push32(&spi.sm.TxReg().Reg, w, dreq) 315 | if err != nil { 316 | return err 317 | } 318 | return nil 319 | } 320 | 321 | func (spi *SPI3w) IsDMAEnabled() bool { 322 | return spi.dma.helperIsEnabled() 323 | } 324 | 325 | func pinPadCtrl(pin machine.Pin) *volatile.Register32 { 326 | return (*volatile.Register32)(unsafe.Pointer(uintptr(unsafe.Pointer(&rp.PADS_BANK0.GPIO0)) + uintptr(4*pin))) 327 | } 328 | -------------------------------------------------------------------------------- /rp2-pio/pio.go: -------------------------------------------------------------------------------- 1 | //go:build rp2040 || rp2350 2 | 3 | package pio 4 | 5 | import ( 6 | "device/rp" 7 | "errors" 8 | "machine" 9 | "runtime/volatile" 10 | "unsafe" 11 | ) 12 | 13 | // RP2040 PIO peripheral handles. 14 | var ( 15 | PIO0 = &PIO{ 16 | hw: rp.PIO0, 17 | } 18 | PIO1 = &PIO{ 19 | hw: rp.PIO1, 20 | } 21 | ) 22 | 23 | // PIO errors. 24 | var ( 25 | ErrOutOfProgramSpace = errors.New("pio: out of program space") 26 | ErrNoSpaceAtOffset = errors.New("pio: program space unavailable at offset") 27 | errStateMachineClaimed = errors.New("pio: state machine already claimed") 28 | ) 29 | 30 | const ( 31 | badStateMachineIndex = "invalid state machine index" 32 | badPIO = "invalid PIO" 33 | badProgramBounds = "invalid program bounds" 34 | ) 35 | 36 | // PIO represents one of the two PIO peripherals in the RP2040 37 | type PIO struct { 38 | // hw points to the PIO hardware registers. 39 | hw *rp.PIO0_Type 40 | // Bitmask of used instruction space. Each PIO has 32 slots for instructions. 41 | usedSpaceMask uint32 42 | // Bitmask of used state machines. Each PIO has 4 state machines. 43 | claimedSMMask uint8 44 | nc noCopy 45 | } 46 | 47 | // BlockIndex returns 0, 1, or 2 depending on whether the underlying device is PIO0, PIO1, or PIO2. 48 | func (pio *PIO) BlockIndex() uint8 { 49 | return pio.blockIndex() 50 | } 51 | 52 | // StateMachine returns a state machine by index. 53 | func (pio *PIO) StateMachine(index uint8) StateMachine { 54 | if index > 3 { 55 | panic(badStateMachineIndex) 56 | } 57 | return StateMachine{ 58 | pio: pio, 59 | index: index, 60 | } 61 | } 62 | 63 | // ClaimtateMachine returns an unused state machine 64 | // or an error if all state machines on this PIO are claimed. 65 | func (pio *PIO) ClaimStateMachine() (sm StateMachine, err error) { 66 | for i := uint8(0); i < 4; i++ { 67 | sm = pio.StateMachine(i) 68 | if sm.TryClaim() { 69 | return sm, nil 70 | } 71 | } 72 | return StateMachine{}, errStateMachineClaimed 73 | } 74 | 75 | // AddProgram loads a PIO program into PIO memory and returns the offset where it was loaded. 76 | // This function will try to find the next available slot of memory for the program 77 | // and will return an error if there is not enough memory to add the program. 78 | // 79 | // The instructions argument holds program binary code in 16-bit words. 80 | // origin indicates where in the PIO execution memory the program must be loaded, 81 | // or -1 if the code is position independent. 82 | func (pio *PIO) AddProgram(instructions []uint16, origin int8) (offset uint8, _ error) { 83 | maybeOffset := pio.findOffsetForProgram(instructions, origin) 84 | if maybeOffset < 0 { 85 | return 0, ErrOutOfProgramSpace 86 | } 87 | offset = uint8(maybeOffset) 88 | pio.AddProgramAtOffset(instructions, origin, offset) 89 | return offset, nil 90 | } 91 | 92 | // AddProgramAtOffset loads a PIO program into PIO memory at a specific offset 93 | // and returns a non-nil error if there is not enough space. 94 | func (pio *PIO) AddProgramAtOffset(instructions []uint16, origin int8, offset uint8) error { 95 | if !pio.CanAddProgramAtOffset(instructions, origin, offset) { 96 | return ErrNoSpaceAtOffset 97 | } 98 | 99 | programLen := uint8(len(instructions)) 100 | for i := uint8(0); i < programLen; i++ { 101 | instr := instructions[i] 102 | 103 | // Patch jump instructions with relative offset 104 | if _INSTR_BITS_JMP == instr&_INSTR_BITS_Msk { 105 | pio.writeInstructionMemory(offset+i, instr+uint16(offset)) 106 | } else { 107 | pio.writeInstructionMemory(offset+i, instr) 108 | } 109 | } 110 | 111 | // Mark the instruction space as in-use 112 | programMask := uint32((1 << programLen) - 1) 113 | pio.usedSpaceMask |= programMask << uint32(offset) 114 | return nil 115 | } 116 | 117 | // CanAddProgramAtOffset returns true if there is enough space for program at given offset. 118 | func (pio *PIO) CanAddProgramAtOffset(instructions []uint16, origin int8, offset uint8) bool { 119 | // Non-relocatable programs must be added at offset 120 | if origin >= 0 && origin != int8(offset) { 121 | return false 122 | } 123 | 124 | programMask := uint32((1 << len(instructions)) - 1) 125 | return pio.usedSpaceMask&(programMask<= 0 { 144 | if uint32(origin) > 32-programLen { 145 | return -1 146 | } 147 | 148 | if (pio.usedSpaceMask & (programMask << origin)) != 0 { 149 | return -1 150 | } 151 | 152 | return origin 153 | } 154 | 155 | // work down from the top always 156 | for i := int8(32 - programLen); i >= 0; i-- { 157 | if pio.usedSpaceMask&(programMask< 32 { // 32 instructions max 169 | panic(badProgramBounds) 170 | } 171 | hw := pio.HW() 172 | for i := offset; i < offset+len; i++ { 173 | // We encode trap instructions to prevent undefined behaviour if 174 | // a state machine is currently using the program memory. 175 | hw.INSTR_MEM[i].Set(uint32(AssemblerV0{}.Jmp(offset, JmpAlways).Encode())) 176 | } 177 | pio.usedSpaceMask &^= uint32((1< 3 { 191 | panic(badStateMachineIndex) 192 | } 193 | // 24 bytes (6 registers) per state machine 194 | const size = unsafe.Sizeof(statemachineHW{}) 195 | 196 | ptrBase := unsafe.Pointer(&pio.hw.SM0_CLKDIV) // 0xC8 197 | ptr := uintptr(ptrBase) + uintptr(index)*size 198 | 199 | return (*statemachineHW)(unsafe.Pointer(uintptr(ptr))) 200 | } 201 | 202 | // PinMode returns the PinMode for a PIO state machine, one of 203 | // PIO0, PIO1, or PIO2. 204 | func (pio *PIO) PinMode() machine.PinMode { 205 | return machine.PinPIO0 + machine.PinMode(pio.BlockIndex()) 206 | } 207 | 208 | // GetIRQ gets lowest octet of PIO IRQ register. 209 | // State machine IRQ flags register. There are 8 210 | // state machine IRQ flags, which can be set, cleared, and waited on 211 | // by the state machines. There’s no fixed association between 212 | // flags and state machines — any state machine can use any flag. 213 | // Any of the 8 flags can be used for timing synchronisation 214 | // between state machines, using IRQ and WAIT instructions. The 215 | // lower four of these flags are also routed out to system-level 216 | // interrupt requests, alongside FIFO status interrupts — see e.g. 217 | // IRQ0_INTE. 218 | func (pio *PIO) GetIRQ() uint8 { 219 | return uint8(pio.hw.GetIRQ()) 220 | } 221 | 222 | // ClearIRQ clears IRQ flags when 1 is written to bit flag. 223 | func (pio *PIO) ClearIRQ(irqMask uint8) { 224 | pio.hw.SetIRQ(uint32(irqMask)) 225 | } 226 | 227 | // SetInputSyncBypassMasked sets the pinMask bits of the INPUT_SYNC_BYPASS register 228 | // with the values in the corresponding bypassMask bits. 229 | // 230 | // There is a 2-flipflop synchronizer on each GPIO input, which protects 231 | // PIO logic from metastabilities. This increases input delay, and for 232 | // fast synchronous IO (e.g. SPI) these synchronizers may need to be bypassed. 233 | // If bit set the corresponding synchronizer is bypassed. If in doubt leave as zeros. 234 | func (pio *PIO) SetInputSyncBypassMasked(bypassMask, pinMask uint32) { 235 | pio.hw.INPUT_SYNC_BYPASS.ReplaceBits(bypassMask, pinMask, 0) 236 | } 237 | 238 | // GPIOStates returns the current PIO-commanded state for output GPIOs. 239 | // This allows for debugging of a PIO program using setting or side-setting 240 | // GPIO pins as signals. 241 | func (pio *PIO) GPIOStates() uint32 { 242 | return pio.hw.DBG_PADOUT.Get() 243 | } 244 | 245 | // GPIODirections returns the current PIO-commanded pin directions (Output Enable). 246 | // Useful for debugging the state of a PIO program that swaps pin directions. 247 | func (pio *PIO) GPIODirections() uint32 { 248 | return pio.hw.DBG_PADOE.Get() 249 | } 250 | 251 | const ( 252 | // This bit address is retroactively declared valid as the PIO hardware version 253 | // for RP2040 (which is 0) according to the RP2350 datasheet, but undefined in 254 | // its datasheet or SVD so we define it here for compatibility. 255 | pio0_SM0_DBG_CFGINFO_VERSION_Pos = 0x1C 256 | ) 257 | 258 | // Version returns the version of the PIO hardware. 259 | // 0 for RP2040, 1 for RP2350. 260 | func (pio *PIO) Version() uint8 { 261 | return uint8(pio.hw.DBG_CFGINFO.Get() >> pio0_SM0_DBG_CFGINFO_VERSION_Pos) 262 | } 263 | 264 | // HW returns a pointer to the PIO's hardware registers. 265 | func (pio *PIO) HW() *pioHW { return (*pioHW)(unsafe.Pointer(pio.hw)) } 266 | 267 | // Programmable IO block 268 | type pioHW struct { 269 | CTRL volatile.Register32 // 0x0 270 | FSTAT volatile.Register32 // 0x4 271 | FDEBUG volatile.Register32 // 0x8 272 | FLEVEL volatile.Register32 // 0xC 273 | TXF [4]volatile.Register32 274 | RXF [4]volatile.Register32 275 | IRQ volatile.Register32 // 0x30 276 | IRQ_FORCE volatile.Register32 // 0x34 277 | INPUT_SYNC_BYPASS volatile.Register32 // 0x38 278 | DBG_PADOUT volatile.Register32 // 0x3C 279 | DBG_PADOE volatile.Register32 // 0x40 280 | DBG_CFGINFO volatile.Register32 // 0x44 281 | INSTR_MEM [32]volatile.Register32 // 0x48..0xC4 282 | SM [4]statemachineHW // SM0=[0xC8..0xDC], .. 0x124 283 | RXF_PUTGET [rp2350ExtraReg][4][4]volatile.Register32 // ----- | 0x128 284 | GPIOBASE [rp2350ExtraReg]volatile.Register32 // ----- | 0x168 285 | INTR volatile.Register32 // 0x128 | 0x16C 286 | IRQ_INT [2]irqINTHW // 0x12C..0x140 | 0x170..0x184 287 | } 288 | 289 | type irqINTHW struct { 290 | E volatile.Register32 291 | F volatile.Register32 292 | S volatile.Register32 293 | } 294 | 295 | const ( 296 | sizeOK = unsafe.Sizeof(rp.PIO0_Type{}) == unsafe.Sizeof(pioHW{}) 297 | ) 298 | 299 | // noCopy may be embedded into structs which must not be copied 300 | // after the first use. 301 | // 302 | // See https://golang.org/issues/8005#issuecomment-190753527 303 | // for details. 304 | type noCopy struct{} 305 | 306 | // Lock is a no-op used by -copylocks checker from `go vet`. 307 | func (*noCopy) Lock() {} 308 | func (*noCopy) UnLock() {} 309 | -------------------------------------------------------------------------------- /rp2-pio/config.go: -------------------------------------------------------------------------------- 1 | //go:build rp2040 || rp2350 2 | 3 | package pio 4 | 5 | import ( 6 | "device/rp" 7 | "machine" 8 | ) 9 | 10 | // DefaultStateMachineConfig returns the default configuration 11 | // for a PIO state machine. 12 | // 13 | // The default configuration here, mirrors the state from 14 | // pio_get_default_sm_config in the c-sdk. 15 | // 16 | // Note: Function used by pico-sdk's pioasm tool so signature MUST remain the same. 17 | func DefaultStateMachineConfig() StateMachineConfig { 18 | cfg := StateMachineConfig{} 19 | cfg.SetClkDivIntFrac(1, 0) 20 | cfg.SetWrap(0, 31) 21 | cfg.SetInShift(true, false, 32) 22 | cfg.SetOutShift(true, false, 32) 23 | return cfg 24 | } 25 | 26 | // DefaultStateMachineConfig is a facilitator method to produce a config for a PIO state machine given the program produced by the same assembler. 27 | // It is equivalent to the code produced by pioasm in _pio.go files. 28 | func (asm AssemblerV0) DefaultStateMachineConfig(progOffset uint8, program []uint16) StateMachineConfig { 29 | cfg := DefaultStateMachineConfig() 30 | cfg.SetWrap(progOffset, progOffset+uint8(len(program))-1) 31 | if asm.SidesetBits > 0 { 32 | cfg.SetSidesetParams(asm.SidesetBits, false, false) 33 | } 34 | return cfg 35 | } 36 | 37 | // StateMachineConfig holds the configuration for a PIO state 38 | // machine. 39 | // 40 | // Note: Type used by pico-sdk's pioasm tool so signature MUST remain the same. 41 | type StateMachineConfig struct { 42 | // Clock divisor register for state machine N 43 | // Frequency = clock freq / (CLKDIV_INT + CLKDIV_FRAC / 256) 44 | ClkDiv uint32 45 | // Execution/behavioural settings for state machine N 46 | ExecCtrl uint32 47 | // Control behaviour of the input/output shift registers for state machine N. 48 | ShiftCtrl uint32 49 | // State machine pin control. 50 | PinCtrl uint32 51 | // GPIO pin index, for accessing GPIOs 32 and above 52 | GPIOBase uint32 53 | } 54 | 55 | // SetClkDivIntFrac sets the clock divider for the state 56 | // machine from a whole and fractional part. 57 | // 58 | // Frequency = clock freq / (CLKDIV_INT + CLKDIV_FRAC / 256) 59 | func (cfg *StateMachineConfig) SetClkDivIntFrac(whole uint16, frac uint8) { 60 | cfg.ClkDiv = clkDiv(whole, frac) 61 | } 62 | 63 | func clkDiv(whole uint16, frac uint8) uint32 { 64 | return (uint32(frac) << rp.PIO0_SM0_CLKDIV_FRAC_Pos) | 65 | (uint32(whole) << rp.PIO0_SM0_CLKDIV_INT_Pos) 66 | } 67 | 68 | // SetWrap sets the wrapping configuration for the state machine 69 | // 70 | // Note: Function used by pico-sdk's pioasm tool so signature MUST remain the same. 71 | func (cfg *StateMachineConfig) SetWrap(wrapTarget uint8, wrap uint8) { 72 | cfg.ExecCtrl = 73 | (cfg.ExecCtrl & ^uint32(rp.PIO0_SM0_EXECCTRL_WRAP_TOP_Msk|rp.PIO0_SM0_EXECCTRL_WRAP_BOTTOM_Msk)) | 74 | (uint32(wrapTarget) << rp.PIO0_SM0_EXECCTRL_WRAP_BOTTOM_Pos) | 75 | (uint32(wrap) << rp.PIO0_SM0_EXECCTRL_WRAP_TOP_Pos) 76 | } 77 | 78 | // SetInShift sets the 'in' shifting parameters in a state machine configuration 79 | // - shiftRight is true if ISR shift direction is right, false if left. 80 | // - autoPush enables automatic ISR refilling after all of the ISR bits have been consumed. 81 | // - pushThreshold is threshold in bits to shift in before auto/conditional re-pushing of the ISR. 82 | func (cfg *StateMachineConfig) SetInShift(shiftRight bool, autoPush bool, pushThreshold uint16) { 83 | cfg.ShiftCtrl = cfg.ShiftCtrl & 84 | ^uint32(rp.PIO0_SM0_SHIFTCTRL_IN_SHIFTDIR_Msk| 85 | rp.PIO0_SM0_SHIFTCTRL_AUTOPUSH_Msk| 86 | rp.PIO0_SM0_SHIFTCTRL_PUSH_THRESH_Msk) | 87 | (boolToBit(shiftRight) << rp.PIO0_SM0_SHIFTCTRL_IN_SHIFTDIR_Pos) | 88 | (boolToBit(autoPush) << rp.PIO0_SM0_SHIFTCTRL_AUTOPUSH_Pos) | 89 | (uint32(pushThreshold&0x1f) << rp.PIO0_SM0_SHIFTCTRL_PUSH_THRESH_Pos) 90 | } 91 | 92 | // SetOutShift sets the 'out' shifting parameters in a state machine configuration 93 | // - shiftRight is true if OSR shift direction is right, false if left. 94 | // - autoPull enables automatic OSR refilling after all of the OSR bits have been consumed. 95 | // - pushThreshold is threshold in bits to shift out before auto/conditional re-pulling of the OSR. 96 | func (cfg *StateMachineConfig) SetOutShift(shiftRight bool, autoPull bool, pushThreshold uint16) { 97 | cfg.ShiftCtrl = cfg.ShiftCtrl & 98 | ^uint32(rp.PIO0_SM0_SHIFTCTRL_OUT_SHIFTDIR_Msk| 99 | rp.PIO0_SM0_SHIFTCTRL_AUTOPULL_Msk| 100 | rp.PIO0_SM0_SHIFTCTRL_PULL_THRESH_Msk) | 101 | (boolToBit(shiftRight) << rp.PIO0_SM0_SHIFTCTRL_OUT_SHIFTDIR_Pos) | 102 | (boolToBit(autoPull) << rp.PIO0_SM0_SHIFTCTRL_AUTOPULL_Pos) | 103 | (uint32(pushThreshold&0x1f) << rp.PIO0_SM0_SHIFTCTRL_PULL_THRESH_Pos) 104 | } 105 | 106 | // SetSidesetParams sets the side-set parameters in a state machine configuration. 107 | // - bitcount is number of bits to steal from delay field in the instruction for use of side set (max 5). 108 | // - optional is true if the topmost side set bit is used as a flag for whether to apply side set on that instruction. 109 | // - pindirs is true if the side-set affects pin directions rather than values. 110 | // 111 | // Note: Function used by pico-sdk's pioasm tool so signature MUST remain the same. 112 | func (cfg *StateMachineConfig) SetSidesetParams(bitCount uint8, optional bool, pindirs bool) { 113 | if bitCount > 5 { 114 | panic("SetSideSet: bitCount") 115 | } 116 | cfg.PinCtrl = (cfg.PinCtrl & ^uint32(rp.PIO0_SM0_PINCTRL_SIDESET_COUNT_Msk)) | 117 | (uint32(bitCount) << uint32(rp.PIO0_SM0_PINCTRL_SIDESET_COUNT_Pos)) 118 | 119 | cfg.ExecCtrl = (cfg.ExecCtrl & ^uint32(rp.PIO0_SM0_EXECCTRL_SIDE_EN_Msk|rp.PIO0_SM0_EXECCTRL_SIDE_PINDIR_Msk)) | 120 | (boolToBit(optional) << rp.PIO0_SM0_EXECCTRL_SIDE_EN_Pos) | 121 | (boolToBit(pindirs) << rp.PIO0_SM0_EXECCTRL_SIDE_PINDIR_Pos) 122 | } 123 | 124 | // SetSidesetPins sets the lowest-numbered pin that will be affected by a side-set 125 | // operation. 126 | // 127 | // Remember to also set the pindir of the pin(s). 128 | func (cfg *StateMachineConfig) SetSidesetPins(firstPin machine.Pin) { 129 | checkPinBaseAndCount(firstPin, 1) 130 | cfg.PinCtrl = (cfg.PinCtrl & ^uint32(rp.PIO0_SM0_PINCTRL_SIDESET_BASE_Msk)) | 131 | (uint32(firstPin) << rp.PIO0_SM0_PINCTRL_SIDESET_BASE_Pos) 132 | } 133 | 134 | // SetOutPins sets the pins a PIO 'out' instruction modifies. Can overlap with pins in IN, SET and SIDESET. 135 | // `out` instructions receive data from the OSR (output shift register) and write it to the GPIO pins in bitwise format, 136 | // thus OUT pins are best suited for driving data protocols with multiple data wires. 137 | // - Base defines the lowest-numbered pin that will be affected by an OUT PINS, 138 | // OUT PINDIRS or MOV PINS instruction. The data written to this pin will always be 139 | // the least-significant bit of the OUT or MOV data. 140 | // - Count defines the number of pins that will be affected by an OUT PINS, 0..32 inclusive. 141 | // 142 | // Remember to also set the pindir of the pin(s). 143 | func (cfg *StateMachineConfig) SetOutPins(base machine.Pin, count uint8) { 144 | checkPinBaseAndCount(base, count) 145 | cfg.PinCtrl = (cfg.PinCtrl & ^uint32(rp.PIO0_SM0_PINCTRL_OUT_BASE_Msk|rp.PIO0_SM0_PINCTRL_OUT_COUNT_Msk)) | 146 | (uint32(base) << rp.PIO0_SM0_PINCTRL_OUT_BASE_Pos) | 147 | (uint32(count) << rp.PIO0_SM0_PINCTRL_OUT_COUNT_Pos) 148 | } 149 | 150 | // SetSetPins sets the pins a PIO 'set' instruction modifies. 151 | // Can overlap with pins in IN, OUT and SIDESET. 152 | // Set pins are best suited to assert control signals such as clock/chip-selects. 153 | // 154 | // The mapping of SET and OUT onto pins is configured independently. They may be mapped to distinct locations, for example 155 | // if one pin is to be used as a clock signal, and another for data. They may also be overlapping ranges of pins: a UART 156 | // transmitter might use SET to assert start and stop bits, and OUT instructions to shift out FIFO data to the same pins. 157 | // 158 | // Remember to also set the pindir of the pin(s). 159 | func (cfg *StateMachineConfig) SetSetPins(base machine.Pin, count uint8) { 160 | checkPinBaseAndCount(base, count) 161 | cfg.PinCtrl = (cfg.PinCtrl & ^uint32(rp.PIO0_SM0_PINCTRL_SET_BASE_Msk|rp.PIO0_SM0_PINCTRL_SET_COUNT_Msk)) | 162 | (uint32(base) << rp.PIO0_SM0_PINCTRL_SET_BASE_Pos) | 163 | (uint32(count) << rp.PIO0_SM0_PINCTRL_SET_COUNT_Pos) 164 | } 165 | 166 | const ( 167 | // RP2350-only, redefined here for RP2040 compatibility. 168 | pio0_SM0_SHIFTCTRL_IN_COUNT_Msk uint32 = 0x1f 169 | ) 170 | 171 | // SetInPins in a state machine configuration. Can overlap with OUT, SET and SIDESET pins. 172 | // 173 | // On RP2350, pin count sets remaining bits to 0 in instructions such as `MOV x, PINS` that 174 | // would otherwise return a full 32-bit value of pin states. On RP2040 this has no effect. 175 | // Remember to also set the pindir of the pin(s). 176 | func (cfg *StateMachineConfig) SetInPins(base machine.Pin, count uint8) { 177 | checkPinBaseAndCount(base, count) 178 | cfg.PinCtrl = (cfg.PinCtrl & ^uint32(rp.PIO0_SM0_PINCTRL_IN_BASE_Msk)) | (uint32(base) << rp.PIO0_SM0_PINCTRL_IN_BASE_Pos) 179 | // Set pin count. These bits are unused on RP2040 180 | cfg.ShiftCtrl = (cfg.ShiftCtrl & ^pio0_SM0_SHIFTCTRL_IN_COUNT_Msk) | uint32(count) 181 | } 182 | 183 | // SetJmpPin sets the gpio pin to use as the source for a `jmp pin` instruction. 184 | func (cfg *StateMachineConfig) SetJmpPin(pin machine.Pin) { 185 | checkPinBaseAndCount(pin, 1) 186 | cfg.ExecCtrl = (cfg.ExecCtrl & ^uint32(rp.PIO0_SM0_EXECCTRL_JMP_PIN_Msk)) | (uint32(pin) << rp.PIO0_SM0_EXECCTRL_JMP_PIN_Pos) 187 | } 188 | 189 | // SetOutSpecial set special 'out' operations in a state machine configuration. 190 | // - sticky to enable 'sticky' output (i.e. re-asserting most recent OUT/SET pin values on subsequent cycles). 191 | // - hasEnablePin true to enable auxiliary OUT enable pin. 192 | // - enable pin for auxiliary OUT enable. 193 | func (cfg *StateMachineConfig) SetOutSpecial(sticky, hasEnablePin bool, enable machine.Pin) { 194 | if hasEnablePin { 195 | checkPinBaseAndCount(enable, 1) 196 | } 197 | cfg.ExecCtrl = (cfg.ExecCtrl & 198 | ^uint32(rp.PIO0_SM0_EXECCTRL_OUT_STICKY_Msk|rp.PIO0_SM0_EXECCTRL_INLINE_OUT_EN_Msk| 199 | rp.PIO0_SM0_EXECCTRL_OUT_EN_SEL_Msk)) | 200 | (boolToBit(sticky) << rp.PIO0_SM0_EXECCTRL_OUT_STICKY_Pos) | 201 | (boolToBit(hasEnablePin) << rp.PIO0_SM0_EXECCTRL_INLINE_OUT_EN_Pos) | 202 | ((uint32(enable) << rp.PIO0_SM0_EXECCTRL_OUT_EN_SEL_Pos) & rp.PIO0_SM0_EXECCTRL_OUT_EN_SEL_Msk) 203 | } 204 | 205 | // SetMovStatus sets source for 'mov status' in a state machine configuration. 206 | // - statusSel is the status operation selector. 207 | // - statusN parameter for the mov status operation (currently a bit count). 208 | func (cfg *StateMachineConfig) SetMovStatus(statusSel MovStatus, statusN uint32) { 209 | cfg.ExecCtrl = (cfg.ExecCtrl & 210 | ^uint32(rp.PIO0_SM0_EXECCTRL_STATUS_SEL_Msk|rp.PIO0_SM0_EXECCTRL_STATUS_N_Msk)) | 211 | ((uint32(statusSel) << rp.PIO0_SM0_EXECCTRL_STATUS_SEL_Pos) & rp.PIO0_SM0_EXECCTRL_STATUS_SEL_Msk) | 212 | ((statusN << rp.PIO0_SM0_EXECCTRL_STATUS_N_Pos) & rp.PIO0_SM0_EXECCTRL_STATUS_N_Msk) 213 | } 214 | 215 | func checkPinBaseAndCount(base machine.Pin, count uint8) { 216 | if base >= 32 { 217 | panic("pio:bad pin") 218 | } else if count > 32 { 219 | panic("pio:count too large") 220 | } 221 | } 222 | 223 | type FifoJoin uint8 224 | 225 | const ( 226 | // FifoJoinNone is the default FIFO joining configuration. The RX and TX FIFOs are separate and of length 4 each. 227 | FifoJoinNone FifoJoin = iota 228 | // FifoJoinTx joins the RX and TX FIFOs into a single TX FIFO of depth 8. 229 | FifoJoinTx 230 | // FifoJoinRx joins the RX and TX FIFOs into a single RX FIFO of depth 8. 231 | FifoJoinRx 232 | // FifoJoinRxGet disables the RX FIFO, but enables random reads from the state 233 | // machine and random writes from the system (for implementing control registers). 234 | FifoJoinRxGet 235 | // FifoJoinRxPut disables the RX FIFO, but enables random writes from the state 236 | // machine and random reads from the system (for implementing status registers). 237 | FifoJoinRxPut 238 | // FifoJoinRxPutGet disables and disables all system access to the RX FIFO, but 239 | // enables both random reads and random writes from the state machine, effectively 240 | // adding four additional scratch registers that have no side-effects. 241 | FifoJoinRxPutGet 242 | ) 243 | 244 | // MOV status types. 245 | type MovStatus uint8 246 | 247 | const ( 248 | MovStatusTxLessthan MovStatus = iota 249 | MovStatusRxLessthan 250 | ) 251 | 252 | const ( 253 | fifoJoinMask = rp.PIO0_SM0_SHIFTCTRL_FJOIN_TX_Msk | rp.PIO0_SM0_SHIFTCTRL_FJOIN_RX_Msk | 254 | pio0_SM0_SHIFTCTRL_FJOIN_RX_PUT_Msk | pio0_SM0_SHIFTCTRL_FJOIN_RX_GET_Msk 255 | 256 | // RP2350-only, redefined here for RP2040 compatibility 257 | pio0_SM0_SHIFTCTRL_FJOIN_RX_PUT_Msk uint32 = 0x8000 258 | pio0_SM0_SHIFTCTRL_FJOIN_RX_GET_Msk uint32 = 0x4000 259 | pio0_SM0_SHIFTCTRL_FJOIN_RX_GET_Pos uint32 = 14 // 0xe 260 | ) 261 | 262 | // SetFIFOJoin sets FIFO joining or RX FIFO random access on a state machine. 263 | func (cfg *StateMachineConfig) SetFIFOJoin(join FifoJoin) { 264 | if join > FifoJoinRxPutGet { 265 | panic("SetFIFOJoin: join") 266 | } 267 | 268 | var newBits uint32 269 | switch join { 270 | case FifoJoinRx, FifoJoinTx: 271 | newBits = uint32(join&0b11) << rp.PIO0_SM0_SHIFTCTRL_FJOIN_TX_Pos 272 | case FifoJoinRxGet, FifoJoinRxPut, FifoJoinRxPutGet: 273 | // These bits are unused on RP2040 and will have no effect. 274 | newBits = (uint32(join-FifoJoinRx) & 0b11) << pio0_SM0_SHIFTCTRL_FJOIN_RX_GET_Pos 275 | default: 276 | // No FIFO joining or random access, so we leave all the FJOIN bits cleared. 277 | } 278 | cfg.ShiftCtrl = (cfg.ShiftCtrl & ^fifoJoinMask) | newBits 279 | } 280 | 281 | func boolToBit(b bool) uint32 { 282 | if b { 283 | return 1 284 | } 285 | return 0 286 | } 287 | -------------------------------------------------------------------------------- /rp2-pio/piolib/dma.go: -------------------------------------------------------------------------------- 1 | //go:build rp2040 || rp2350 2 | 3 | package piolib 4 | 5 | import ( 6 | "device/rp" 7 | "runtime/volatile" 8 | "unsafe" 9 | 10 | pio "github.com/tinygo-org/pio/rp2-pio" 11 | ) 12 | 13 | var _DMA = &dmaArbiter{} 14 | 15 | type dmaArbiter struct { 16 | claimedChannels uint16 17 | } 18 | 19 | // ClaimChannel returns a DMA channel that can be used for DMA transfers. 20 | func (arb *dmaArbiter) ClaimChannel() (channel dmaChannel, ok bool) { 21 | for i := uint8(0); i < 12; i++ { 22 | ch := arb.Channel(i) 23 | if ch.TryClaim() { 24 | return ch, true 25 | } 26 | } 27 | return dmaChannel{}, false 28 | } 29 | 30 | func (arb *dmaArbiter) Channel(channel uint8) dmaChannel { 31 | if channel > 11 { 32 | panic("invalid DMA channel") 33 | } 34 | // DMA channels usable on the RP2040. 12 in total. 35 | var dmaChannels = (*[12]dmaChannelHW)(unsafe.Pointer(rp.DMA)) 36 | return dmaChannel{ 37 | hw: &dmaChannels[channel], 38 | arb: arb, 39 | idx: channel, 40 | } 41 | } 42 | 43 | func (dma *dmaChannel) helperIsEnabled() bool { 44 | return dma.IsValid() 45 | } 46 | 47 | func (dma *dmaChannel) helperEnableDMA(enabled bool) error { 48 | dmaAlreadyEnabled := dma.helperIsEnabled() 49 | if !enabled || dmaAlreadyEnabled { 50 | if !enabled && dmaAlreadyEnabled { 51 | dma.Unclaim() 52 | *dma = dmaChannel{} // Invalidate DMA channel. 53 | } 54 | return nil 55 | } 56 | channel, ok := _DMA.ClaimChannel() 57 | if !ok { 58 | return errDMAUnavail 59 | } 60 | channel.dl = dma.dl // save deadliner from existing DMA channel, maybe set by user in future. 61 | *dma = channel 62 | return nil 63 | } 64 | 65 | type dmaChannel struct { 66 | hw *dmaChannelHW 67 | arb *dmaArbiter 68 | dl deadliner 69 | idx uint8 70 | } 71 | 72 | // TryClaim claims the DMA channel for use by a peripheral and returns if it succeeded in claiming the channel. 73 | func (ch dmaChannel) TryClaim() bool { 74 | ch.mustValid() 75 | if ch.IsClaimed() { 76 | return false 77 | } 78 | ch.arb.claimedChannels |= 1 << ch.idx 79 | return true 80 | } 81 | 82 | // Unclaim releases the DMA channel so it can be used by other peripherals. 83 | // It does not check if the channel is currently claimed; it force-unclaims the channel. 84 | func (ch dmaChannel) Unclaim() { 85 | ch.mustValid() 86 | ch.arb.claimedChannels &^= 1 << ch.idx 87 | } 88 | 89 | // IsClaimed returns true if the DMA channel is currently claimed through software. 90 | func (ch dmaChannel) IsClaimed() bool { 91 | ch.mustValid() 92 | return ch.arb.claimedChannels&(1< select DREQ n as TREQ 383 | func (cc *dmaChannelConfig) setTREQ_SEL(dreq uint32) { 384 | cc.CTRL = (cc.CTRL & ^uint32(rp.DMA_CH0_CTRL_TRIG_TREQ_SEL_Msk)) | (uint32(dreq) << rp.DMA_CH0_CTRL_TRIG_TREQ_SEL_Pos) 385 | } 386 | 387 | func (cc *dmaChannelConfig) setChainTo(chainTo uint8) { 388 | cc.CTRL = (cc.CTRL & ^uint32(rp.DMA_CH0_CTRL_TRIG_CHAIN_TO_Msk)) | (uint32(chainTo) << rp.DMA_CH0_CTRL_TRIG_CHAIN_TO_Pos) 389 | } 390 | 391 | func (cc *dmaChannelConfig) setTransferDataSize(size dmaTxSize) { 392 | cc.CTRL = (cc.CTRL & ^uint32(rp.DMA_CH0_CTRL_TRIG_DATA_SIZE_Msk)) | (uint32(size) << rp.DMA_CH0_CTRL_TRIG_DATA_SIZE_Pos) 393 | } 394 | 395 | func (cc *dmaChannelConfig) setRing(write bool, sizeBits uint32) { 396 | /* 397 | static inline void channel_config_set_ring(dma_channel_config *c, bool write, uint size_bits) { 398 | assert(size_bits < 32); 399 | c->ctrl = (c->ctrl & ~(DMA_CH0_CTRL_TRIG_RING_SIZE_BITS | DMA_CH0_CTRL_TRIG_RING_SEL_BITS)) | 400 | (size_bits << DMA_CH0_CTRL_TRIG_RING_SIZE_LSB) | 401 | (write ? DMA_CH0_CTRL_TRIG_RING_SEL_BITS : 0); 402 | } 403 | */ 404 | cc.CTRL = (cc.CTRL & ^uint32(rp.DMA_CH0_CTRL_TRIG_RING_SIZE_Msk)) | 405 | (sizeBits << rp.DMA_CH0_CTRL_TRIG_RING_SIZE_Pos) 406 | setBitPos(&cc.CTRL, rp.DMA_CH0_CTRL_TRIG_RING_SEL_Pos, write) 407 | } 408 | 409 | func (cc *dmaChannelConfig) setReadIncrement(incr bool) { 410 | setBitPos(&cc.CTRL, rp.DMA_CH0_CTRL_TRIG_INCR_READ_Pos, incr) 411 | } 412 | 413 | func (cc *dmaChannelConfig) setWriteIncrement(incr bool) { 414 | setBitPos(&cc.CTRL, rp.DMA_CH0_CTRL_TRIG_INCR_WRITE_Pos, incr) 415 | } 416 | 417 | func (cc *dmaChannelConfig) setBSwap(bswap bool) { 418 | setBitPos(&cc.CTRL, rp.DMA_CH0_CTRL_TRIG_BSWAP_Pos, bswap) 419 | } 420 | 421 | func (cc *dmaChannelConfig) setIRQQuiet(irqQuiet bool) { 422 | setBitPos(&cc.CTRL, rp.DMA_CH0_CTRL_TRIG_IRQ_QUIET_Pos, irqQuiet) 423 | } 424 | 425 | func (cc *dmaChannelConfig) setHighPriority(highPriority bool) { 426 | setBitPos(&cc.CTRL, rp.DMA_CH0_CTRL_TRIG_HIGH_PRIORITY_Pos, highPriority) 427 | } 428 | 429 | func (cc *dmaChannelConfig) setEnable(enable bool) { 430 | setBitPos(&cc.CTRL, rp.DMA_CH0_CTRL_TRIG_EN_Pos, enable) 431 | } 432 | 433 | func (cc *dmaChannelConfig) setSniffEnable(sniffEnable bool) { 434 | setBitPos(&cc.CTRL, rp.DMA_CH0_CTRL_TRIG_SNIFF_EN_Pos, sniffEnable) 435 | } 436 | 437 | func setBitPos(cc *uint32, pos uint32, bit bool) { 438 | if bit { 439 | *cc = *cc | (1 << pos) 440 | } else { 441 | *cc = *cc & ^(1 << pos) // unset bit. 442 | } 443 | } 444 | 445 | func ptrAs[T ~uint32](ptr *T) uint32 { 446 | return uint32(uintptr(unsafe.Pointer(ptr))) 447 | } 448 | -------------------------------------------------------------------------------- /rp2-pio/statemachine.go: -------------------------------------------------------------------------------- 1 | //go:build rp2350 || rp2040 2 | 3 | package pio 4 | 5 | import ( 6 | "device/rp" 7 | "machine" 8 | "math/bits" 9 | "runtime/volatile" 10 | "unsafe" 11 | ) 12 | 13 | // assm is the default assembler used for state machine manipulation. No sidesetting nor delays. 14 | var assm AssemblerV0 15 | 16 | // StateMachine represents one of the four state machines in a PIO 17 | type StateMachine struct { 18 | // The pio containing this state machine 19 | pio *PIO 20 | 21 | // index of this state machine 22 | index uint8 23 | } 24 | 25 | // IsClaimed returns true if the state machine is claimed by other code and should not be used. 26 | func (sm StateMachine) IsClaimed() bool { return sm.pio.claimedSMMask&(1< 3 { 67 | panic(badStateMachineIndex) 68 | } 69 | 70 | // Halt the state machine to set sensible defaults 71 | sm.SetEnabled(false) 72 | 73 | if cfg == (StateMachineConfig{}) { 74 | cfg = DefaultStateMachineConfig() 75 | sm.SetConfig(cfg) 76 | } else { 77 | sm.SetConfig(cfg) 78 | } 79 | 80 | sm.ClearFIFOs() 81 | 82 | // Clear FIFO debug flags 83 | const fdebugMask = uint32((1 << rp.PIO0_FDEBUG_TXOVER_Pos) | 84 | (1 << rp.PIO0_FDEBUG_RXUNDER_Pos) | 85 | (1 << rp.PIO0_FDEBUG_TXSTALL_Pos) | 86 | (1 << rp.PIO0_FDEBUG_RXSTALL_Pos)) 87 | 88 | sm.pio.hw.FDEBUG.Set(fdebugMask << sm.index) 89 | 90 | sm.Restart() 91 | sm.ClkDivRestart() 92 | sm.Exec(assm.Jmp(initialPC, JmpAlways).Encode()) 93 | } 94 | 95 | // SetEnabled controls whether the state machine is running. 96 | func (sm StateMachine) SetEnabled(enabled bool) { 97 | sm.pio.hw.CTRL.ReplaceBits(boolToBit(enabled), 0x1, sm.index) 98 | } 99 | 100 | // IsEnabled returns true if the state machine is running. 101 | func (sm StateMachine) IsEnabled() bool { 102 | return sm.pio.hw.CTRL.HasBits(1 << (rp.PIO0_CTRL_SM_ENABLE_Pos + sm.index)) 103 | } 104 | 105 | // Restart clears internal StateMachine state which may otherwise be difficult to access, e.g. shift counters. 106 | func (sm StateMachine) Restart() { 107 | sm.pio.hw.CTRL.SetBits(1 << (rp.PIO0_CTRL_SM_RESTART_Pos + sm.index)) 108 | } 109 | 110 | // ClkDivRestart forces clock dividers to restart their count and clear fractional accumulators (phase is zeroed). 111 | func (sm StateMachine) ClkDivRestart() { 112 | sm.pio.hw.CTRL.SetBits(1 << (rp.PIO0_CTRL_CLKDIV_RESTART_Pos + sm.index)) 113 | } 114 | 115 | // SetConfig applies state machine configuration to a state machine 116 | func (sm StateMachine) SetConfig(cfg StateMachineConfig) { 117 | sm.setConfig(cfg) 118 | } 119 | 120 | // SetClkDiv sets the clock divider for the state machine from a whole and fractional part where: 121 | // 122 | // Frequency = clock freq / (CLKDIV_INT + CLKDIV_FRAC / 256) 123 | func (sm StateMachine) SetClkDiv(whole uint16, frac uint8) { 124 | sm.HW().CLKDIV.Set(clkDiv(whole, frac)) 125 | } 126 | 127 | // TxPut puts a value into the state machine's TX FIFO. 128 | // 129 | // This function does not check for fullness. If the FIFO is full the FIFO 130 | // contents are not affected and the sticky TXOVER flag is set for this FIFO in FDEBUG. 131 | func (sm StateMachine) TxPut(data uint32) { 132 | reg := sm.TxReg() 133 | reg.Set(data) 134 | } 135 | 136 | // RxGet reads a word of data from a state machine's RX FIFO. 137 | // 138 | // This function does not check for emptiness. If the FIFO is empty 139 | // the result is undefined and the sticky RXUNDER flag for this FIFO is set in FDEBUG. 140 | func (sm StateMachine) RxGet() uint32 { 141 | reg := sm.RxReg() 142 | return reg.Get() 143 | } 144 | 145 | // TxReg gets a pointer to the TX FIFO register for this state machine. 146 | func (sm StateMachine) TxReg() *volatile.Register32 { 147 | start := uintptr(unsafe.Pointer(&sm.pio.hw.TXF0)) // 0x10 148 | offset := uintptr(sm.index) * 4 149 | return (*volatile.Register32)(unsafe.Pointer(start + offset)) 150 | } 151 | 152 | // RxReg gets a pointer to the RX FIFO register for this state machine. 153 | func (sm StateMachine) RxReg() *volatile.Register32 { 154 | start := uintptr(unsafe.Pointer(&sm.pio.hw.RXF0)) // 0x20 155 | offset := uintptr(sm.index) * 4 156 | return (*volatile.Register32)(unsafe.Pointer(start + offset)) 157 | } 158 | 159 | // RxFIFOLevel returns the number of elements currently in a state machine's RX FIFO. 160 | // The number of elements returned is in the range 0..15. 161 | func (sm StateMachine) RxFIFOLevel() uint32 { 162 | const mask = rp.PIO0_FLEVEL_RX0_Msk >> rp.PIO0_FLEVEL_RX0_Pos 163 | bitoffs := rp.PIO0_FLEVEL_RX0_Pos + sm.index*(rp.PIO0_FLEVEL_RX1_Pos-rp.PIO0_FLEVEL_RX0_Pos) 164 | return (sm.pio.hw.FLEVEL.Get() >> uint32(bitoffs)) & mask 165 | } 166 | 167 | // TxFIFOLevel returns the number of elements currently in a state machine's TX FIFO. 168 | // The number of elements returned is in the range 0..15. 169 | func (sm StateMachine) TxFIFOLevel() uint32 { 170 | const mask = rp.PIO0_FLEVEL_TX0_Msk >> rp.PIO0_FLEVEL_TX0_Pos 171 | bitoffs := rp.PIO0_FLEVEL_TX0_Pos + sm.index*(rp.PIO0_FLEVEL_TX1_Pos-rp.PIO0_FLEVEL_TX0_Pos) 172 | return (sm.pio.hw.FLEVEL.Get() >> uint32(bitoffs)) & mask 173 | } 174 | 175 | // IsTxFIFOEmpty returns true if state machine's TX FIFO is empty. 176 | func (sm StateMachine) IsTxFIFOEmpty() bool { 177 | return (sm.pio.hw.FSTAT.Get() & (1 << (rp.PIO0_FSTAT_TXEMPTY_Pos + sm.index))) != 0 178 | } 179 | 180 | // IsTxFIFOFull returns true if state machine's TX FIFO is full. 181 | func (sm StateMachine) IsTxFIFOFull() bool { 182 | return (sm.pio.hw.FSTAT.Get() & (1 << (rp.PIO0_FSTAT_TXFULL_Pos + sm.index))) != 0 183 | } 184 | 185 | // IsRxFIFOEmpty returns true if state machine's RX FIFO is empty. 186 | func (sm StateMachine) IsRxFIFOEmpty() bool { 187 | return (sm.pio.hw.FSTAT.Get() & (1 << (rp.PIO0_FSTAT_RXEMPTY_Pos + sm.index))) != 0 188 | } 189 | 190 | // IsRxFIFOFull returns true if state machine's RX FIFO is full. 191 | func (sm StateMachine) IsRxFIFOFull() bool { 192 | return (sm.pio.hw.FSTAT.Get() & (1 << (rp.PIO0_FSTAT_RXFULL_Pos + sm.index))) != 0 193 | } 194 | 195 | // IsTxStalled returns true if state machine has stalled. 196 | // This value is sticky so it must be cleared with [StateMachine.ClearTxStalled] after reading true to be reset. 197 | func (sm StateMachine) HasTxStalled() bool { 198 | return sm.pio.hw.FDEBUG.HasBits(1 << (rp.PIO0_FDEBUG_TXSTALL_Pos + sm.index)) 199 | } 200 | 201 | // ClearTxStalled clears the value of tx stall. See [StateMachine.HasTxStalled]. 202 | func (sm StateMachine) ClearTxStalled() { 203 | sm.pio.hw.FDEBUG.Set(1 << (rp.PIO0_FDEBUG_TXSTALL_Pos + sm.index)) 204 | } 205 | 206 | // ClearFIFOs clears the TX and RX FIFOs of a state machine. 207 | func (sm StateMachine) ClearFIFOs() { 208 | hw := sm.HW() 209 | shiftctl := &hw.SHIFTCTRL 210 | // FIFOs are flushed when this bit is changed. Xoring twice returns bit to original state. 211 | xorBits(shiftctl, rp.PIO0_SM0_SHIFTCTRL_FJOIN_RX_Msk) 212 | xorBits(shiftctl, rp.PIO0_SM0_SHIFTCTRL_FJOIN_RX_Msk) 213 | } 214 | 215 | // GetRxFIFOAt reads data from the RX FIFO at a specific index. 216 | // Requires FifoJoinRxPut mode to be enabled, RP2350-only. 217 | func (sm StateMachine) GetRxFIFOAt(fifoIndex int) uint32 { 218 | return sm.getRxFIFOAt(fifoIndex) 219 | } 220 | 221 | // SetRxFIFOAt writes data to the RX FIFO at a specific index. 222 | // Requires FifoJoinRxGet mode to be enabled, RP2350-only. 223 | func (sm StateMachine) SetRxFIFOAt(data uint32, fifoIndex int) { 224 | sm.setRxFIFOAt(data, fifoIndex) 225 | } 226 | 227 | // Exec will immediately execute an instruction on the state machine 228 | func (sm StateMachine) Exec(instr uint16) { 229 | sm.HW().INSTR.Set(uint32(instr)) 230 | } 231 | 232 | // SetPindirsConsecutive sets a range of pins to either 'in' or 'out'. This must be done 233 | // for all used pins before the state machine is started, including SET, IN, OUT and SIDESET pins. 234 | func (sm StateMachine) SetPindirsConsecutive(pin machine.Pin, count uint8, isOut bool) { 235 | checkPinBaseAndCount(pin, count) 236 | sm.SetPindirsMasked(makePinmask(uint8(pin), count, uint8(boolToBit(isOut)))) 237 | } 238 | 239 | // SetPinsConsecutive sets a range of pins initial starting values. 240 | func (sm StateMachine) SetPinsConsecutive(pin machine.Pin, count uint8, level bool) { 241 | checkPinBaseAndCount(pin, count) 242 | sm.SetPinsMasked(makePinmask(uint8(pin), count, uint8(boolToBit(level)))) 243 | } 244 | 245 | func makePinmask(base, count, bit uint8) (valMask, pinMask uint32) { 246 | start := uint8(base) 247 | end := start + count 248 | for shift := start; shift < end; shift++ { 249 | valMask |= uint32(bit) << shift 250 | pinMask |= 1 << shift 251 | } 252 | return valMask, pinMask 253 | } 254 | 255 | // SetPinsMasked sets a value on multiple pins for the PIO instance. 256 | // This method repeatedly reconfigures the state machines pins. 257 | // Use this method as convenience to set initial pin states BEFORE running state machine. 258 | func (sm StateMachine) SetPinsMasked(valueMask, pinMask uint32) { 259 | sm.setPinExec(SetDestPins, valueMask, pinMask) 260 | } 261 | 262 | // SetPindirsMasked sets the pin directions (input/output) on multiple pins for 263 | // the PIO instance. This method repeatedly reconfigures the state machines pins. 264 | // Use this method as convenience to set initial pin states BEFORE running state machine. 265 | func (sm StateMachine) SetPindirsMasked(dirMask, pinMask uint32) { 266 | sm.setPinExec(SetDestPindirs, dirMask, pinMask) 267 | } 268 | 269 | func (sm StateMachine) setPinExec(dest SetDest, valueMask, pinMask uint32) { 270 | hw := sm.HW() 271 | pinctrlSaved := hw.PINCTRL.Get() 272 | execctrlSaved := hw.EXECCTRL.Get() 273 | hw.EXECCTRL.ClearBits(1 << rp.PIO0_SM0_EXECCTRL_OUT_STICKY_Pos) 274 | // select the algorithm to use. Naive or the pico-sdk way. 275 | const naive = true 276 | if naive { 277 | for i := uint8(0); i < 32; i++ { 278 | if pinMask&(1<>i) 286 | sm.Exec(assm.Set(dest, value).Encode()) 287 | } 288 | } else { 289 | for pinMask != 0 { 290 | // https://github.com/raspberrypi/pico-sdk/blob/6a7db34ff63345a7badec79ebea3aaef1712f374/src/rp2_common/hardware_pio/pio.c#L178 291 | base := uint32(bits.TrailingZeros32(pinMask)) 292 | 293 | hw.PINCTRL.Set( 294 | 1<>base) 299 | sm.Exec(assm.Set(dest, value).Encode()) 300 | pinMask &= pinMask - 1 301 | } 302 | } 303 | hw.PINCTRL.Set(pinctrlSaved) 304 | hw.EXECCTRL.Set(execctrlSaved) 305 | } 306 | 307 | // SetWrap sets the current wrap configuration for a state machine. 308 | func (sm StateMachine) SetWrap(target, wrap uint8) { 309 | if wrap >= 32 || target >= 32 { 310 | panic("pio:bad wrap") 311 | } 312 | hw := sm.HW() 313 | hw.EXECCTRL.ReplaceBits( 314 | (uint32(target)< side 0 19 | // // 1: nop side 1 20 | // // 2: nop side 0 21 | // var program = [3]uint16{ 22 | // asm.Out(pio.SrcDestPins, numberOfPins).Side(0).Encode(), 23 | // asm.Nop().Side(1).Encode(), 24 | // asm.Nop().Side(0).Encode(), 25 | // } 26 | type AssemblerV0 struct { 27 | SidesetBits uint8 28 | } 29 | 30 | type instructionV0 struct { 31 | instr uint16 32 | asm AssemblerV0 33 | } 34 | 35 | // EncodeInstr encodes an arbitrary PIO instruction with the given arguments. 36 | func (asm AssemblerV0) EncodeInstr(instr InstrKind, delaySideset, arg1_3b, arg2_5b uint8) uint16 { 37 | return uint16(instr&0b111)<<13 | uint16(delaySideset&0x1f)<<8 | uint16(arg1_3b&0b111)<<5 | uint16(arg2_5b&0x1f) 38 | } 39 | 40 | func (asm AssemblerV0) encodeIRQ(relative bool, irq uint8) uint8 { 41 | return boolAsU8(relative)<<4 | irq&0b111 42 | } 43 | 44 | func (instr instructionV0) majorbits() uint16 { 45 | return instr.instr & _INSTR_BITS_Msk 46 | } 47 | 48 | func (asm AssemblerV0) instrArgs(instr uint16, arg1_5b uint8, arg2 uint8) instructionV0 { 49 | return asm.instr(instr | (uint16(arg1_5b) << 5) | uint16(arg2&0x1f)) 50 | } 51 | 52 | func (asm AssemblerV0) instrSrcDest(instr uint16, srcDest uint8, value uint8) instructionV0 { 53 | return asm.instrArgs(instr, srcDest&7, value) 54 | } 55 | 56 | // Encode returns the finalized assembled instruction ready to be stored to the PIO program memory and used by a PIO state machine. 57 | func (instr instructionV0) Encode() uint16 { 58 | return instr.instr 59 | } 60 | 61 | // Side sets the sideset functionality of an instruction. 62 | // 63 | // value (see Section 3.3.2) is applied to the side_set pins at the start of the instruction. Note that 64 | // the rules for a side-set value via side are dependent on the .side_set (see 65 | // pioasm_side_set) directive for the program. If no .side_set is specified then the side 66 | // is invalid, if an optional number of sideset pins is specified then side may be 67 | // present, and if a non-optional number of sideset pins is specified, then side is 68 | // required. The must fit within the number of side-set bits specified in the .side_set 69 | // directive. 70 | func (instr instructionV0) Side(value uint8) instructionV0 { 71 | instr.instr &^= instr.asm.sidesetbits() 72 | instr.instr |= uint16(value) << (13 - instr.asm.SidesetBits) // TODO: panic on bit overflow. 73 | return instr 74 | } 75 | 76 | // Delay sets the delay functionality of an instruction. 77 | // 78 | // cycles specifies amount of cycles to delay after the instruction completes. The delay_value is 79 | // specified as a value (see Section 3.3.2), and in general is between 0 and 31 inclusive (a 5-bit 80 | // value), however the number of bits is reduced when sideset is enabled via the .side_set (see 81 | // pioasm_side_set) directive. If the is not present, then the instruction has no delay 82 | func (instr instructionV0) Delay(cycles uint8) instructionV0 { 83 | instr.instr &^= instr.asm.delaybits() 84 | instr.instr |= uint16(0b11111&cycles) << 8 // TODO: panic on bit overflow due to sideset bits excess. 85 | return instr 86 | } 87 | 88 | func (asm AssemblerV0) sidesetbits() uint16 { 89 | return delaySidesetbits & (uint16(0b111) << (13 - asm.SidesetBits)) 90 | } 91 | 92 | func (asm AssemblerV0) delaybits() uint16 { 93 | return delaySidesetbits & (0b11111 << (8 - asm.SidesetBits)) 94 | } 95 | 96 | func (asm AssemblerV0) instr(instr uint16) instructionV0 { 97 | return instructionV0{instr: instr, asm: asm} 98 | } 99 | 100 | // Set program counter to Address if Condition is true, otherwise no operation. 101 | // Delay cycles on a JMP always take effect, whether Condition is true or false, and they take place after Condition is 102 | // evaluated and the program counter is updated. 103 | func (asm AssemblerV0) Jmp(addr uint8, cond JmpCond) instructionV0 { 104 | return asm.instrArgs(_INSTR_BITS_JMP, uint8(cond&0b111), addr) 105 | } 106 | 107 | // WaitPin stalls until Input pin selected by Index. This state machine’s input IO mapping is applied first, and then Index 108 | // selects which of the mapped bits to wait on. In other words, the pin is selected by adding Index to the 109 | // PINCTRL_IN_BASE configuration, modulo 32. 110 | func (asm AssemblerV0) WaitPin(polarity bool, pin uint8) instructionV0 { 111 | flag := boolAsU8(polarity) << 2 112 | return asm.instrArgs(_INSTR_BITS_WAIT, 1|flag, pin) 113 | } 114 | 115 | // WaitIRQ stalls until PIO IRQ flag selected by irqindex. This IRQ behaves differently to other WAIT sources. 116 | // - If Polarity is 1, the selected IRQ flag is cleared by the state machine upon the wait condition being met. 117 | // - The flag index is decoded in the same way as the IRQ index field: if the MSB is set, the state machine ID (0…3) is 118 | // added to the IRQ index, by way of modulo-4 addition on the two LSBs. For example, state machine 2 with a flag 119 | // value of '0x11' will wait on flag 3, and a flag value of '0x13' will wait on flag 1. This allows multiple state machines 120 | // running the same program to synchronise with each other. 121 | func (asm AssemblerV0) WaitIRQ(polarity, relative bool, irqindex uint8) instructionV0 { 122 | flag := boolAsU8(polarity) << 2 123 | return asm.instrArgs(_INSTR_BITS_WAIT, 2|flag, asm.encodeIRQ(relative, irqindex)) 124 | } 125 | 126 | // WaitGPIO stalls until System GPIO input selected by Index. This is an absolute GPIO index, and is not affected by the state machine’s input IO mapping. 127 | func (asm AssemblerV0) WaitGPIO(polarity bool, pin uint8) instructionV0 { 128 | flag := boolAsU8(polarity) << 2 129 | return asm.instrArgs(_INSTR_BITS_WAIT, 0|flag, pin) 130 | } 131 | 132 | // Shift Bit count bits from Source into the Input Shift Register (ISR). Shift direction is configured for each state machine by 133 | // SHIFTCTRL_IN_SHIFTDIR. Additionally, increase the input shift count by Bit count, saturating at 32. 134 | func (asm AssemblerV0) In(src InSrc, value uint8) instructionV0 { 135 | return asm.instrSrcDest(_INSTR_BITS_IN, uint8(src), value) 136 | } 137 | 138 | // Shift Bit count bits out of the Output Shift Register (OSR), and write those bits to Destination. Additionally, increase the 139 | // output shift count by Bit count, saturating at 32. 140 | func (asm AssemblerV0) Out(dest OutDest, value uint8) instructionV0 { 141 | return asm.instrSrcDest(_INSTR_BITS_OUT, uint8(dest), value) 142 | } 143 | 144 | // Push the contents of the ISR into the RX FIFO, as a single 32-bit word. Clear ISR to all-zeroes. 145 | // - IfFull: If 1, do nothing unless the total input shift count has reached its threshold, SHIFTCTRL_PUSH_THRESH (the same 146 | // as for autopush; see Section 3.5.4). 147 | // - Block: If 1, stall execution if RX FIFO is full. 148 | func (asm AssemblerV0) Push(ifFull, block bool) instructionV0 { 149 | arg := boolAsU8(ifFull)<<1 | boolAsU8(block) 150 | return asm.instrArgs(_INSTR_BITS_PUSH, arg, 0) 151 | } 152 | 153 | // Load a 32-bit word from the TX FIFO into the OSR. 154 | // - ifEmpty: If 1, do nothing unless the total output shift count has reached its threshold, SHIFTCTRL_PULL_THRESH (the 155 | // same as for autopull; see Section 3.5.4). 156 | // - Block: If 1, stall if TX FIFO is empty. If 0, pulling from an empty FIFO copies scratch X to OSR. 157 | func (asm AssemblerV0) Pull(ifEmpty, block bool) instructionV0 { 158 | arg := boolAsU8(ifEmpty)<<1 | boolAsU8(block) 159 | return asm.instrArgs(_INSTR_BITS_PULL, arg, 0) 160 | } 161 | 162 | // Mov copies data from src to dest. 163 | func (asm AssemblerV0) Mov(dest MovDest, src MovSrc) instructionV0 { 164 | return asm.instrSrcDest(_INSTR_BITS_MOV, uint8(dest), uint8(src)&7) 165 | } 166 | 167 | // MovInvertBits does a Mov but inverting the resulting bits. 168 | func (asm AssemblerV0) MovInvert(dest MovDest, src MovSrc) instructionV0 { 169 | return asm.instrSrcDest(_INSTR_BITS_MOV, uint8(dest), (1<<3)|uint8(src&7)) 170 | } 171 | 172 | // MovReverse does a Mov but reversing the order of the resulting bits. 173 | func (asm AssemblerV0) MovReverse(dest MovDest, src MovSrc) instructionV0 { 174 | return asm.instrSrcDest(_INSTR_BITS_MOV, uint8(dest), (2<<3)|uint8(src&7)) 175 | } 176 | 177 | // IRQSet sets the IRQ flag selected by irqIndex argument. 178 | func (asm AssemblerV0) IRQSet(relative bool, irqIndex uint8) instructionV0 { 179 | return asm.instrArgs(_INSTR_BITS_IRQ, 0, asm.encodeIRQ(relative, irqIndex)) 180 | } 181 | 182 | // IRQClear clears the IRQ flag selected by irqIndex argument. See [AssemblerV0.IRQSet]. 183 | func (asm AssemblerV0) IRQClear(relative bool, irqIndex uint8) instructionV0 { 184 | return asm.instrArgs(_INSTR_BITS_IRQ, 2, asm.encodeIRQ(relative, irqIndex)) 185 | } 186 | 187 | // Set writes an immediate value Data in range 0..31 to Destination. 188 | func (asm AssemblerV0) Set(dest SetDest, value uint8) instructionV0 { 189 | return asm.instrSrcDest(_INSTR_BITS_SET, uint8(dest), value) 190 | } 191 | 192 | // Nop is pseudo instruction that lasts a single PIO cycle. Usually used for timings. 193 | func (asm AssemblerV0) Nop() instructionV0 { return asm.Mov(MovDestY, MovSrcY) } 194 | 195 | // InstrKind is a enum for the PIO instruction type. It only represents the kind of 196 | // instruction. It cannot store the arguments. 197 | type InstrKind uint8 198 | 199 | const ( 200 | InstrJMP InstrKind = iota // jmp 201 | InstrWAIT // wait 202 | InstrIN // in 203 | InstrOUT // out 204 | InstrPUSH // push 205 | InstrPULL // pull 206 | InstrMOV // mov 207 | InstrIRQ // irq 208 | InstrSET // set 209 | ) 210 | 211 | // This file contains the primitives for creating instructions dynamically 212 | const ( 213 | _INSTR_BITS_JMP = 0x0000 214 | _INSTR_BITS_WAIT = 0x2000 215 | _INSTR_BITS_IN = 0x4000 216 | _INSTR_BITS_OUT = 0x6000 217 | _INSTR_BITS_PUSH = 0x8000 218 | _INSTR_BITS_PULL = 0x8080 219 | _INSTR_BITS_MOV = 0xa000 220 | _INSTR_BITS_IRQ = 0xc000 221 | _INSTR_BITS_SET = 0xe000 222 | 223 | // Bit mask for instruction code 224 | _INSTR_BITS_Msk = 0xe000 225 | ) 226 | 227 | // OutDest encodes Out instruction data destination. 228 | type OutDest uint8 229 | 230 | const ( 231 | OutDestPins OutDest = 0b000 // pins 232 | OutDestX OutDest = 0b001 // x 233 | OutDestY OutDest = 0b010 // y 234 | OutDestNull OutDest = 0b011 // null 235 | OutDestPindirs OutDest = 0b100 // pindirs 236 | OutDestPC OutDest = 0b101 // pc 237 | OutDestISR OutDest = 0b110 // isr 238 | OutDestExec OutDest = 0b111 // exec 239 | ) 240 | 241 | // InSrc encodes In instruction data source. 242 | type InSrc uint8 243 | 244 | const ( 245 | InSrcPins InSrc = 0b000 // pins 246 | InSrcX InSrc = 0b001 // x 247 | InSrcY InSrc = 0b010 // y 248 | InSrcNull InSrc = 0b011 // null 249 | InSrcISR InSrc = 0b110 // isr 250 | InSrcOSR InSrc = 0b111 // osr 251 | ) 252 | 253 | // SetDest encodes Set instruction data destination. 254 | type SetDest uint8 255 | 256 | const ( 257 | SetDestPins SetDest = 0b000 // pins 258 | SetDestX SetDest = 0b001 // x 259 | SetDestY SetDest = 0b010 // y 260 | SetDestPindirs SetDest = 0b100 // pindirs 261 | ) 262 | 263 | // MovSrc encodes Mov instruction data source. 264 | type MovSrc uint8 265 | 266 | const ( 267 | MovSrcPins MovSrc = 0b000 // pins 268 | MovSrcX MovSrc = 0b001 // x 269 | MovSrcY MovSrc = 0b010 // y 270 | MovSrcNull MovSrc = 0b011 // null 271 | MovSrcStatus MovSrc = 0b101 // status 272 | MovSrcISR MovSrc = 0b110 // isr 273 | MovSrcOSR MovSrc = 0b111 // osr 274 | ) 275 | 276 | // MovDest encodes Mov instruction data destination. 277 | type MovDest uint8 278 | 279 | const ( 280 | MovDestPins MovDest = 0b000 // pins 281 | MovDestX MovDest = 0b001 // x 282 | MovDestY MovDest = 0b010 // y 283 | // MovDestPindirs was introduced in PIO version 1. Not available on RP2040 284 | MovDestPindirs MovDest = 0b011 // pindirs 285 | MovDestExec MovDest = 0b100 // exec 286 | MovDestPC MovDest = 0b101 // pc 287 | MovDestISR MovDest = 0b110 // isr 288 | MovDestOSR MovDest = 0b111 // osr 289 | ) 290 | 291 | type JmpCond uint8 292 | 293 | const ( 294 | // No condition, always jumps. 295 | JmpAlways JmpCond = iota 296 | // Jump if X is zero. 297 | JmpXZero 298 | // Jump if X is not zero, prior to decrement of X. 299 | JmpXNZeroDec 300 | // Jump if Y is zero. 301 | JmpYZero 302 | // Jump if Y is not zero, prior to decrement of Y. 303 | JmpYNZeroDec 304 | // Jump if X is not equal to Y. 305 | JmpXNotEqualY 306 | // Jump if EXECCTRL_JMP_PIN (state machine configured) is high. 307 | JmpPinInput 308 | // Compares the bits shifted out since last pull with the shift count theshold 309 | // (configured by SHIFTCTRL_PULL_THRESH) and jumps if there are remaining bits to shift. 310 | JmpOSRNotEmpty 311 | ) 312 | 313 | // IRQIndexMode modifies the behaviour if the Index field, either modifying the index, or indexing IRQ flags from a different PIO block. 314 | type IRQIndexMode uint8 315 | 316 | const ( 317 | // Direct: the three LSBs are used directly to index the IRQ flags in this PIO block. 318 | IRQDirect IRQIndexMode = 0b00 // direct 319 | // Prev: the instruction references an IRQ flag from the next-lower-numbered PIO in the system, wrapping to 320 | // the highest-numbered PIO if this is PIO0. Available on RP2350 only. 321 | IRQPrev IRQIndexMode = 0b01 // prev 322 | // Rel: the state machine ID (0…3) is added to the IRQ flag index, by way of modulo-4 addition on the two 323 | // LSBs. For example, state machine 2 with a flag value of '0x11' will wait on flag 3, and a flag value of '0x13' will 324 | // wait on flag 1. This allows multiple state machines running the same program to synchronise with each other. 325 | IRQRel IRQIndexMode = 0b10 // rel 326 | // Next: the instruction references an IRQ flag from the next-higher-numbered PIO in the system, wrapping to 327 | // PIO0 if this is the highest-numbered PIO. Available on RP2350 only. 328 | IRQNext IRQIndexMode = 0b11 // next 329 | ) 330 | 331 | // EncodeInstr encodes an arbitrary PIO instruction with the given arguments. 332 | func EncodeInstr(instr InstrKind, delaySideset, arg1_3b, arg2_5b uint8) uint16 { 333 | return uint16(instr&0b111)<<13 | uint16(delaySideset&0x1f)<<8 | uint16(arg1_3b&0b111)<<5 | uint16(arg2_5b&0x1f) 334 | } 335 | 336 | // ClkDivFromPeriod calculates the CLKDIV register values 337 | // to reach a given StateMachine cycle period given the RP2040 CPU frequency. 338 | // period is expected to be in nanoseconds. freq is expected to be in Hz. 339 | // 340 | // Prefer using ClkDivFromFrequency if possible for speed and accuracy. 341 | func ClkDivFromPeriod(period, cpuFreq uint32) (whole uint16, frac uint8, err error) { 342 | // freq = 256*clockfreq / (256*whole + frac) 343 | // where period = 1e9/freq => freq = 1e9/period, so: 344 | // 1e9/period = 256*clockfreq / (256*whole + frac) => 345 | // 256*whole + frac = 256*clockfreq*period/1e9 346 | return splitClkdiv(256 * uint64(period) * uint64(cpuFreq) / uint64(1e9)) 347 | } 348 | 349 | // ClkDivFromFrequency calculates the CLKDIV register values 350 | // to reach a given StateMachine cycle frequency. freq and cpuFreq are expected to be in Hz. 351 | // 352 | // Use powers of two for freq to avoid slow divisions and rounding errors. 353 | func ClkDivFromFrequency(freq, cpuFreq uint32) (whole uint16, frac uint8, err error) { 354 | // freq = 256*clockfreq / (256*whole + frac) 355 | // 256*whole + frac = 256*clockfreq / freq 356 | return splitClkdiv(256 * uint64(cpuFreq) / uint64(freq)) 357 | 358 | } 359 | 360 | func splitClkdiv(clkdiv uint64) (whole uint16, frac uint8, err error) { 361 | if clkdiv > 256*math.MaxUint16 { 362 | return 0, 0, errors.New("ClkDiv: too large period or CPU frequency") 363 | } else if clkdiv < 256 { 364 | return 0, 0, errors.New("ClkDiv: too small period or CPU frequency") 365 | } 366 | whole = uint16(clkdiv / 256) 367 | frac = uint8(clkdiv % 256) 368 | return whole, frac, nil 369 | } 370 | 371 | func boolAsU8(b bool) uint8 { 372 | if b { 373 | return 1 374 | } 375 | return 0 376 | } 377 | --------------------------------------------------------------------------------