Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hub75: begin porting logic from pico-examples #8

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions rp2-pio/pio.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}

Expand Down
3 changes: 2 additions & 1 deletion rp2-pio/piolib/all_generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
107 changes: 107 additions & 0 deletions rp2-pio/piolib/hub75.go
Original file line number Diff line number Diff line change
@@ -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})
}
}
113 changes: 113 additions & 0 deletions rp2-pio/piolib/hub75.pio
Original file line number Diff line number Diff line change
@@ -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;
}
%}
64 changes: 64 additions & 0 deletions rp2-pio/piolib/hub75_pio.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.