Skip to content
Closed
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
205 changes: 205 additions & 0 deletions rp2-pio/examples/hub40screen/hub40screen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
package main

import (
"machine"
"runtime"
"time"
"unsafe"

pio "github.com/tinygo-org/pio/rp2-pio"
"github.com/tinygo-org/pio/rp2-pio/piolib"
)

func main() {
time.Sleep(time.Second)
const usePIO = true
var p6 Parallel6Bus
if usePIO {
sm, _ := pio.PIO0.ClaimStateMachine()
p6concrete, err := piolib.NewParallel6(sm, 12_000, 0, 6)
if err != nil {
panic(err)
}
err = p6concrete.EnableDMA(true)
if err != nil {
panic(err)
}
p6 = p6concrete
} else {
p6 = &bitbangParallel6{
rd0: makepinout(0),
gd0: makepinout(1),
bd0: makepinout(2),
rd1: makepinout(3),
gd1: makepinout(4),
bd1: makepinout(5),
clk: makepinout(6),
}
}
var emptyScreen [4][4]uint32
s := NewHUB40Screen(p6, makepinout(7), makepinout(8))
s.Draw(&emptyScreen)
for {
// testScreen(s)
testScreenBus(s)
}
}

// SetScreen sets a 3-bit RGB value at (ix, iy).
// ix: 0..15, iy: 0..7
func SetScreen(screen *[4][4]uint32, ix, iy int, rgb3bits uint8) {
if ix < 0 || ix >= 16 || iy < 0 || iy >= 8 {
return // or panic
}
colBlock := ix / 4 // 0..3
rowIdx := iy % 4 // 0..3 within the half
up := 1 - (iy / 4) // 0 = lower half, 1 = upper half

groupBase := (ix % 4) * 6 // 6 bits per group
bitoff := groupBase + up*3

mask := uint32(0b111) << bitoff
v := &screen[colBlock][rowIdx]
*v &^= mask
*v |= uint32(rgb3bits&0x7) << bitoff
}

type Parallel6Bus interface {
Tx24(buf []uint32) error
}

func NewHUB40Screen(bus Parallel6Bus, latch, outputEnable pinout) *HUB40Screen {
return &HUB40Screen{
lat: latch,
oe: outputEnable,
p6: bus,
}
}

func testScreen(s *HUB40Screen) {
var emptyscreen [4][4]uint32
var screen [4][4]uint32
s.Draw(&emptyscreen)
for iy := 0; iy < 8; iy++ {
for ix := 0; ix < 16; ix++ {
SetScreen(&screen, ix, iy, 0b101)
s.Draw(&screen)
time.Sleep(30 * time.Millisecond)
}
}
for range 2 {
s.Draw(&screen)
time.Sleep(time.Second / 3)
s.Draw(&emptyscreen)
time.Sleep(time.Second / 3)
}
for i := range screen {
for j := range screen[i] {
screen[i][j] = 0xffff_ffff
}
}
s.Draw(&screen)
time.Sleep(2 * time.Second)
}

func testScreenBus(s *HUB40Screen) {
var screen [4][4]uint32
buf := unsafe.Slice(&screen[0][0], 4*4)
for j := range buf {
for i := 0; i < 24; i++ {
mask := uint32(1) << i
buf[j] = mask
if i >= 24 {
println("off screen")
}
buf[0] |= 1
buf[len(buf)-1] |= 1 << 23
s.Draw(&screen)
time.Sleep(50 * time.Millisecond)
}
}
}

// HUB40Screen is a RGB single-bit-per-pixel screen that works on the basis of
// several shift registers arranges in two rows. Each clock represents 6 bits
// over the parallel-6 bus, so two LEDs per clock. The device I am using has
// no markings other than "HUB40" and "OF-20R3A2-0816-V21JC" which is likely not a real model.
// It seems to be a one-off design for a media company. Data lines are:
// - RD0..BD1: Six Red/Green/Blue data lines for row 0 and row 1.
// - OE: Output enable. Turns entire display on or off. Is negated.
// - LAT: Latch. Latches shift registers values. Basically switches display buffer to the one written most recently.
type HUB40Screen struct {
// data pins.
lat pinout
oe pinout
p6 Parallel6Bus
}

func (s *HUB40Screen) Latch() {
s.lat.High()
runtime.Gosched()
s.lat.Low()
}

func (s *HUB40Screen) Enable(b bool) {
println("enable", b)
s.oe(!b)
}

// Write writes the screen data without latching.
func (s *HUB40Screen) Write(screen *[4][4]uint32) (err error) {
buf := unsafe.Slice(&screen[0][0], 4*4)
return s.p6.Tx24(buf)
}

// Draw writes and then latches screen data.
func (s *HUB40Screen) Draw(screen *[4][4]uint32) (err error) {
err = s.Write(screen)
if err == nil {
s.Latch()
}
return err
}

type bitbangParallel6 struct {
rd0, gd0, bd0, rd1, gd1, bd1 pinout
clk pinout
}

func (s *bitbangParallel6) Tx24(buf []uint32) error {
for i := range buf {
s.clkout24(buf[i])
}
return nil
}

func (s *bitbangParallel6) clkout24(v uint32) {
const bits6 = 0b11_1111
s.rawclkout6(uint8(v & bits6))
s.rawclkout6(uint8((v >> 6) & bits6))
s.rawclkout6(uint8((v >> 12) & bits6))
s.rawclkout6(uint8((v >> 18) & bits6))
}

func (s *bitbangParallel6) rawclkout6(rgbBits uint8) {
s.clk.Low()
s.rd0(rgbBits&(1<<0) != 0)
s.gd0(rgbBits&(1<<1) != 0)
s.bd0(rgbBits&(1<<2) != 0)

s.rd1(rgbBits&(1<<3) != 0)
s.gd1(rgbBits&(1<<4) != 0)
s.bd1(rgbBits&(1<<5) != 0)
s.clk.High()
s.clk.Low()
}

type pinout func(level bool)

func (p pinout) High() { p(true) }
func (p pinout) Low() { p(false) }

func makepinout(pin machine.Pin) pinout {
pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
return pin.Set
}
1 change: 1 addition & 0 deletions rp2-pio/piolib/all_generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ var (
errDMAUnavail = errors.New("piolib:DMA channel unavailable")
)

//go:generate pioasm -o go parallel6.pio parallel6_pio.go
//go:generate pioasm -o go parallel8.pio parallel8_pio.go
//go:generate pioasm -o go pulsar.pio pulsar_pio.go
//go:generate pioasm -o go spi.pio spi_pio.go
Expand Down
115 changes: 115 additions & 0 deletions rp2-pio/piolib/parallel6.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package piolib

import (
"machine"

pio "github.com/tinygo-org/pio/rp2-pio"
)

// Parallel6 implements a 6-parallel bus.
type Parallel6 struct {
sm pio.StateMachine
dma dmaChannel
rgboffset uint8
}

// NewParallel6 instantiates a 6-parallel bus with pins dataBase..dataBase+5 and clock pin.
func NewParallel6(sm pio.StateMachine, baud uint32, dataBase machine.Pin, clock machine.Pin) (*Parallel6, error) {
sm.TryClaim()
Pio := sm.PIO()
whole, frac, err := pio.ClkDivFromFrequency(baud, machine.CPUFrequency())
if err != nil {
return nil, err
}
rgbOffset, err := Pio.AddProgram(parallel6Instructions, parallel6Origin)
if err != nil {
return nil, err
}
pinCfg := machine.PinConfig{Mode: Pio.PinMode()}
clock.Configure(pinCfg)
clkMask := uint32(1) << clock
var pinMask uint32 = clkMask
for i := 0; i < 6; i++ {
pin := dataBase + machine.Pin(i)
pinMask |= 1 << pin
pin.Configure(pinCfg)
}

// Configuration.
cfg := parallel6ProgramDefaultConfig(rgbOffset)
// Statemachine Pin configuration.
cfg.SetOutPins(dataBase, 6)
cfg.SetOutShift(true, true, 24)
cfg.SetSidesetPins(clock)

cfg.SetClkDivIntFrac(whole, frac)
cfg.SetFIFOJoin(pio.FifoJoinTx)

sm.SetPinsMasked(0, pinMask)
sm.SetPindirsMasked(pinMask, pinMask)
sm.Init(rgbOffset, cfg)
sm.SetEnabled(true)
p6 := Parallel6{
sm: sm,
rgboffset: rgbOffset,
}
return &p6, nil
}

// IsEnabled returns true if the state machine on the Parallel6 is enabled and ready to transmit.
func (p6 *Parallel6) IsEnabled() bool {
return p6.sm.IsEnabled()
}

// SetEnabled enables or disables the state machine.
func (p6 *Parallel6) SetEnabled(b bool) {
p6.sm.SetEnabled(b)
}

// Tx24 transmits 6-parallel data over pins. Each 32 bit value contains 24 effective bits
// making a total of 4 clocks.
func (p6 *Parallel6) Tx24(data []uint32) (err error) {
p6.sm.ClearTxStalled()
if p6.IsDMAEnabled() {
err = p6.tx24DMA(data)
} else {
err = p6.tx24(data)
}
if err != nil {
return err
}
for !p6.sm.HasTxStalled() {
gosched() // Block until empty.
}
return nil
}

func (p6 *Parallel6) tx24(data []uint32) error {
i := 0
for i < len(data) {
if p6.sm.IsTxFIFOFull() {
gosched()
continue
}
p6.sm.TxPut(data[i])
i++
}
return nil
}

func (p6 *Parallel6) IsDMAEnabled() bool {
return p6.dma.helperIsEnabled()
}

func (p6 *Parallel6) EnableDMA(enabled bool) error {
return p6.dma.helperEnableDMA(enabled)
}

func (p6 *Parallel6) tx24DMA(data []uint32) error {
dreq := dmaPIO_TxDREQ(p6.sm)
err := p6.dma.Push32(&p6.sm.TxReg().Reg, data, dreq)
if err != nil {
return err
}
return nil
}
18 changes: 18 additions & 0 deletions rp2-pio/piolib/parallel6.pio
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.program parallel6
.side_set 1

.wrap_target
out pins, 6 side 0 ; write OSR databits into pin
nop side 1
nop side 0
.wrap

% go {
//go:build rp2040 || rp2350

package piolib

import (
pio "github.com/tinygo-org/pio/rp2-pio"
)
%}
27 changes: 27 additions & 0 deletions rp2-pio/piolib/parallel6_pio.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// This file is autogenerated by pioasm version 2.2.0; do not edit!

//go:build rp2040 || rp2350
package piolib
import (
pio "github.com/tinygo-org/pio/rp2-pio"
)
// parallel6

const parallel6WrapTarget = 0
const parallel6Wrap = 2

var parallel6Instructions = []uint16{
// .wrap_target
0x6006, // 0: out pins, 6 side 0
0xb042, // 1: nop side 1
0xa042, // 2: nop side 0
// .wrap
}
const parallel6Origin = -1
func parallel6ProgramDefaultConfig(offset uint8) pio.StateMachineConfig {
cfg := pio.DefaultStateMachineConfig()
cfg.SetWrap(offset+parallel6WrapTarget, offset+parallel6Wrap)
cfg.SetSidesetParams(1, false, false)
return cfg;
}

11 changes: 11 additions & 0 deletions rp2-pio/statemachine.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,17 @@ func (sm StateMachine) TxFIFOLevel() uint32 {
return (sm.pio.hw.FLEVEL.Get() >> uint32(bitoffs)) & mask
}

// IsTxStalled returns true if state machine has stalled.
// This value is sticky so it must be cleared with [StateMachine.ClearTxStalled] after reading true to be reset.
func (sm StateMachine) HasTxStalled() bool {
return sm.pio.hw.FDEBUG.HasBits(1 << (rp.PIO0_FDEBUG_TXSTALL_Pos + sm.index))
}

// ClearTxStalled clears the value of tx stall. See [StateMachine.HasTxStalled].
func (sm StateMachine) ClearTxStalled() {
sm.pio.hw.FDEBUG.Set(1 << (rp.PIO0_FDEBUG_TXSTALL_Pos + sm.index))
}

// IsTxFIFOEmpty returns true if state machine's TX FIFO is empty.
func (sm StateMachine) IsTxFIFOEmpty() bool {
return (sm.pio.hw.FSTAT.Get() & (1 << (rp.PIO0_FSTAT_TXEMPTY_Pos + sm.index))) != 0
Expand Down