From 1354dec5f86cd19443224808123ad3f762a5a0db Mon Sep 17 00:00:00 2001 From: soypat Date: Thu, 11 Jan 2024 23:40:14 -0300 Subject: [PATCH] hub75: begin porting logic from pico-examples --- rp2-pio/pio.go | 8 +-- rp2-pio/piolib/all_generate.go | 3 +- rp2-pio/piolib/hub75.go | 107 +++++++++++++++++++++++++++++++ rp2-pio/piolib/hub75.pio | 113 +++++++++++++++++++++++++++++++++ rp2-pio/piolib/hub75_pio.go | 64 +++++++++++++++++++ 5 files changed, 290 insertions(+), 5 deletions(-) create mode 100644 rp2-pio/piolib/hub75.go create mode 100644 rp2-pio/piolib/hub75.pio create mode 100644 rp2-pio/piolib/hub75_pio.go diff --git a/rp2-pio/pio.go b/rp2-pio/pio.go index 14a1b98..8d75a53 100644 --- a/rp2-pio/pio.go +++ b/rp2-pio/pio.go @@ -86,7 +86,7 @@ func (pio *PIO) ClaimStateMachine() (sm StateMachine, err error) { // origin indicates where in the PIO execution memory the program must be loaded, // or -1 if the code is position independent. func (pio *PIO) AddProgram(instructions []uint16, origin int8) (offset uint8, _ error) { - maybeOffset := pio.findOffsetForProgram(instructions, origin) + maybeOffset := pio.FindOffsetForProgram(len(instructions), origin) if maybeOffset < 0 { return 0, ErrOutOfProgramSpace } @@ -141,13 +141,13 @@ func (pio *PIO) writeInstructionMemory(offset uint8, value uint16) { reg.Set(uint32(value)) } -func (pio *PIO) findOffsetForProgram(instructions []uint16, origin int8) int8 { - programLen := uint32(len(instructions)) +func (pio *PIO) FindOffsetForProgram(programLen int, origin int8) int8 { + programLen32 := uint32(programLen) programMask := uint32((1 << programLen) - 1) // Program has fixed offset (not relocatable) if origin >= 0 { - if uint32(origin) > 32-programLen { + if uint32(origin) > 32-programLen32 { return -1 } diff --git a/rp2-pio/piolib/all_generate.go b/rp2-pio/piolib/all_generate.go index 6d0aad5..1fd4493 100644 --- a/rp2-pio/piolib/all_generate.go +++ b/rp2-pio/piolib/all_generate.go @@ -22,7 +22,8 @@ var ( //go:generate pioasm -o go spi.pio spi_pio.go //go:generate pioasm -o go ws2812.pio ws2812_pio.go //go:generate pioasm -o go i2s.pio i2s_pio.go -//go:generate pioasm -o go spi3w.pio spi3w_pio.go +//go:generate pioasm -o go spi3w.pio spi3w_pio.go +//go:generate pioasm -o go hub75.pio hub75_pio.go func gosched() { runtime.Gosched() } diff --git a/rp2-pio/piolib/hub75.go b/rp2-pio/piolib/hub75.go new file mode 100644 index 0000000..a3bb1f7 --- /dev/null +++ b/rp2-pio/piolib/hub75.go @@ -0,0 +1,107 @@ +package piolib + +import ( + "device/rp" + "machine" + + pio "github.com/tinygo-org/pio/rp2-pio" +) + +// See https://github.com/raspberrypi/pico-examples/blob/master/pio/hub75/hub75.pio +type Hub75 struct { + sm pio.StateMachine + rgbOffset uint8 + rowOffset uint8 + nRowPins uint8 + rowBase machine.Pin + latchBase machine.Pin + clock machine.Pin + rgbBase machine.Pin +} + +func NewHub75(sm pio.StateMachine, clock, rgbBase, latchBase, rowBase machine.Pin, nRowPins uint8) (*Hub75, error) { + sm.TryClaim() + Pio := sm.PIO() + + rgbOffset, err := Pio.AddProgram(hub75_data_rgb888Instructions, hub75_data_rgb888Origin) + if err != nil { + return nil, err + } + rowOffset, err := Pio.AddProgram(hub75_rowInstructions, hub75_rowOrigin) + if err != nil { + Pio.ClearProgramSection(rgbOffset, uint8(len(hub75_data_rgb888Instructions))) + return nil, err + } + + hub := Hub75{ + sm: sm, + rgbOffset: rgbOffset, + rowOffset: rowOffset, + nRowPins: uint8(nRowPins), + rowBase: rowBase, + latchBase: latchBase, + } + return &hub, nil +} + +func (hub *Hub75) initRowProgram() { + cfgPindirsConsecutive(hub.sm, hub.rowBase, hub.nRowPins, true) + cfgPindirsConsecutive(hub.sm, hub.latchBase, 2, true) + + cfg := hub75_rowProgramDefaultConfig(hub.rowOffset) + cfg.SetOutPins(hub.rowBase, hub.nRowPins) + cfg.SetSidesetPins(hub.latchBase) + cfg.SetOutShift(true, true, 32) + hub.sm.Init(hub.rowOffset, cfg) + hub.sm.SetEnabled(true) +} + +func (hub *Hub75) waitTxStall() { + Pio := hub.sm.PIO() + txstallmask := 1 << (hub.sm.StateMachineIndex() + rp.PIO0_FDEBUG_TXSTALL_Pos) + fdebug := &Pio.HW().FDEBUG + fdebug.Set(uint32(txstallmask)) + for fdebug.Get()&uint32(txstallmask) == 0 { + gosched() + } +} + +func (hub *Hub75) initRGBProgram() { + cfgPindirsConsecutive(hub.sm, hub.rgbBase, 6, true) + cfgPindirsConsecutive(hub.sm, hub.clock, 1, true) + + cfg := hub75_data_rgb888ProgramDefaultConfig(hub.rgbOffset) + cfg.SetOutPins(hub.rgbBase, 6) + cfg.SetSidesetPins(hub.clock) + cfg.SetOutShift(true, true, 24) + // ISR shift to left. R0 ends up at bit 5. We push it up to MSB and then flip the register. + cfg.SetInShift(false, false, 32) + cfg.SetFIFOJoin(pio.FifoJoinTx) + + hub.sm.Init(hub.rgbOffset, cfg) + // What? This line does not make sense? Executing a position? Exec takes a instruction not an offset!?! + hub.sm.Exec(uint16(hub.rgbOffset) + hub75_data_rgb888offset_entry_point) + hub.sm.SetEnabled(true) +} + +// rgbSetShift patches a data program at `offset` to preshift pixels by `shamt` +func (hub *Hub75) rgbSetShift(offset, shamt uint8) { + var instr uint16 + if shamt == 0 { + instr = pio.EncodePull(false, true) // Blocking pull. + } else { + instr = pio.EncodeOut(pio.SrcDestNull, shamt) + } + mem := &(hub.sm.PIO().HW().INSTR_MEM) + mem[offset+hub75_data_rgb888offset_shift0].Set(uint32(instr)) + mem[offset+hub75_data_rgb888offset_shift1].Set(uint32(instr)) +} + +func cfgPindirsConsecutive(sm pio.StateMachine, base machine.Pin, nPins uint8, output bool) { + sm.SetPindirsConsecutive(base, nPins, output) + Pio := sm.PIO() + pinmode := Pio.PinMode() + for p := base; p < base+machine.Pin(nPins); p++ { + p.Configure(machine.PinConfig{Mode: pinmode}) + } +} diff --git a/rp2-pio/piolib/hub75.pio b/rp2-pio/piolib/hub75.pio new file mode 100644 index 0000000..65710c2 --- /dev/null +++ b/rp2-pio/piolib/hub75.pio @@ -0,0 +1,113 @@ +; +; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; + +.program hub75_row + +; side-set pin 0 is LATCH +; side-set pin 1 is OEn +; OUT pins are row select A-E +; +; Each FIFO record consists of: +; - 5-bit row select (LSBs) +; - Pulse width - 1 (27 MSBs) +; +; Repeatedly select a row, pulse LATCH, and generate a pulse of a certain +; width on OEn. + +.side_set 2 + +.wrap_target + out pins, 5 [7] side 0x2 ; Deassert OEn, output row select + out x, 27 [7] side 0x3 ; Pulse LATCH, get OEn pulse width +pulse_loop: + jmp x-- pulse_loop side 0x0 ; Assert OEn for x+1 cycles +.wrap + +% go { +//go:build rp2040 + +package piolib + +import ( + pio "github.com/tinygo-org/pio/rp2-pio" +) +%} + +.program hub75_data_rgb888 +.side_set 1 + +; Each FIFO record consists of a RGB888 pixel. (This is ok for e.g. an RGB565 +; source which has been gamma-corrected) +; +; Even pixels are sent on R0, G0, B0 and odd pixels on R1, G1, B1 (typically +; these are for different parts of the screen, NOT for adjacent pixels, so the +; frame buffer must be interleaved before passing to PIO.) +; +; Each pass through, we take bit n, n + 8 and n + 16 from each pixel, for n in +; {0...7}. Therefore the pixels need to be transmitted 8 times (ouch) to build +; up the full 8 bit value for each channel, and perform bit-planed PWM by +; varying pulse widths on the other state machine, in ascending powers of 2. +; This avoids a lot of bit shuffling on the processors, at the cost of DMA +; bandwidth (which we have loads of). + +; Might want to close your eyes before you read this +public entry_point: +.wrap_target +public shift0: + pull side 0 ; gets patched to `out null, n` if n nonzero (otherwise the PULL is required for fencing) + in osr, 1 side 0 ; shuffle shuffle shuffle + out null, 8 side 0 + in osr, 1 side 0 + out null, 8 side 0 + in osr, 1 side 0 + out null, 32 side 0 ; Discard remainder of OSR contents +public shift1: + pull side 0 ; gets patched to out null, n if n is nonzero (otherwise PULL required) + in osr, 1 side 1 ; Note this posedge clocks in the data from the previous iteration + out null, 8 side 1 + in osr, 1 side 1 + out null, 8 side 1 + in osr, 1 side 1 + out null, 32 side 1 + in null, 26 side 1 ; Note we are just doing this little manoeuvre here to get GPIOs in the order + mov pins, ::isr side 1 ; R0, G0, B0, R1, G1, B1. Can go 1 cycle faster if reversed +.wrap +; Note that because the clock edge for pixel n is in the middle of pixel n + +; 1, a dummy pixel at the end is required to clock the last piece of genuine +; data. (Also 1 pixel of garbage is clocked out at the start, but this is +; harmless) + +% c-sdk { +static inline void hub75_data_rgb888_program_init(PIO pio, uint sm, uint offset, uint rgb_base_pin, uint clock_pin) { + pio_sm_set_consecutive_pindirs(pio, sm, rgb_base_pin, 6, true); + pio_sm_set_consecutive_pindirs(pio, sm, clock_pin, 1, true); + for (uint i = rgb_base_pin; i < rgb_base_pin + 6; ++i) + pio_gpio_init(pio, i); + pio_gpio_init(pio, clock_pin); + + pio_sm_config c = hub75_data_rgb888_program_get_default_config(offset); + sm_config_set_out_pins(&c, rgb_base_pin, 6); + sm_config_set_sideset_pins(&c, clock_pin); + sm_config_set_out_shift(&c, true, true, 24); + // ISR shift to left. R0 ends up at bit 5. We push it up to MSB and then flip the register. + sm_config_set_in_shift(&c, false, false, 32); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); + pio_sm_init(pio, sm, offset, &c); + pio_sm_exec(pio, sm, offset + hub75_data_rgb888_offset_entry_point); + pio_sm_set_enabled(pio, sm, true); +} + +// Patch a data program at `offset` to preshift pixels by `shamt` +static inline void hub75_data_rgb888_set_shift(PIO pio, uint sm, uint offset, uint shamt) { + uint16_t instr; + if (shamt == 0) + instr = pio_encode_pull(false, true); // blocking PULL + else + instr = pio_encode_out(pio_null, shamt); + pio->instr_mem[offset + hub75_data_rgb888_offset_shift0] = instr; + pio->instr_mem[offset + hub75_data_rgb888_offset_shift1] = instr; +} +%} \ No newline at end of file diff --git a/rp2-pio/piolib/hub75_pio.go b/rp2-pio/piolib/hub75_pio.go new file mode 100644 index 0000000..f271e2c --- /dev/null +++ b/rp2-pio/piolib/hub75_pio.go @@ -0,0 +1,64 @@ +// Code generated by pioasm; DO NOT EDIT. + +//go:build rp2040 +package piolib +import ( + pio "github.com/tinygo-org/pio/rp2-pio" +) +// hub75_row + +const hub75_rowWrapTarget = 0 +const hub75_rowWrap = 2 + +var hub75_rowInstructions = []uint16{ + // .wrap_target + 0x7705, // 0: out pins, 5 side 2 [7] + 0x7f3b, // 1: out x, 27 side 3 [7] + 0x0042, // 2: jmp x--, 2 side 0 + // .wrap +} +const hub75_rowOrigin = -1 +func hub75_rowProgramDefaultConfig(offset uint8) pio.StateMachineConfig { + cfg := pio.DefaultStateMachineConfig() + cfg.SetWrap(offset+hub75_rowWrapTarget, offset+hub75_rowWrap) + cfg.SetSidesetParams(2, false, false) + return cfg; +} + +// hub75_data_rgb888 + +const hub75_data_rgb888WrapTarget = 0 +const hub75_data_rgb888Wrap = 15 + +const hub75_data_rgb888offset_entry_point = 0 +const hub75_data_rgb888offset_shift0 = 0 +const hub75_data_rgb888offset_shift1 = 7 + +var hub75_data_rgb888Instructions = []uint16{ + // .wrap_target + 0x80a0, // 0: pull block side 0 + 0x40e1, // 1: in osr, 1 side 0 + 0x6068, // 2: out null, 8 side 0 + 0x40e1, // 3: in osr, 1 side 0 + 0x6068, // 4: out null, 8 side 0 + 0x40e1, // 5: in osr, 1 side 0 + 0x6060, // 6: out null, 32 side 0 + 0x80a0, // 7: pull block side 0 + 0x50e1, // 8: in osr, 1 side 1 + 0x7068, // 9: out null, 8 side 1 + 0x50e1, // 10: in osr, 1 side 1 + 0x7068, // 11: out null, 8 side 1 + 0x50e1, // 12: in osr, 1 side 1 + 0x7060, // 13: out null, 32 side 1 + 0x507a, // 14: in null, 26 side 1 + 0xb016, // 15: mov pins, ::isr side 1 + // .wrap +} +const hub75_data_rgb888Origin = -1 +func hub75_data_rgb888ProgramDefaultConfig(offset uint8) pio.StateMachineConfig { + cfg := pio.DefaultStateMachineConfig() + cfg.SetWrap(offset+hub75_data_rgb888WrapTarget, offset+hub75_data_rgb888Wrap) + cfg.SetSidesetParams(1, false, false) + return cfg; +} +