Skip to content

examples/parallel/tufty: fix ST7789 parallel display init - #55

Merged
soypat merged 4 commits into
tinygo-org:mainfrom
davecheney:davecheney-dfc/tufty-pio-display
Sep 9, 2026
Merged

soypat merged 4 commits into
tinygo-org:mainfrom
davecheney:davecheney-dfc/tufty-pio-display

Conversation

@davecheney

@davecheney davecheney commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

The Tufty parallel display example did not drive the panel. Several independent problems, any one of which was enough to leave the screen blank:

  • rdPin was never configured as an output, so rdPin.High() was a no-op on a floating input. RD must be held high for the ST7789 to accept writes.
  • configureDisplayRotation sent CASET twice, so the row address window was never set. It also byte swapped the CASET/RASET values and then emitted them big endian, swapping them twice.
  • machine.GP10 and friends do not exist, so the example did not compile. Use the GPIOnn constants for the same pins.

While here, correct some other problems:

  • FillRectangle allocated a 150KB framebuffer regardless of the size of the rectangle requested, and issued RAMWR twice because setWindow had already sent it. It now streams a small pre-filled chunk with CS held low for the duration.
  • Deassert CS before the first command, and let the bus settle before raising CS so the final WR edge lands while the panel is still selected.
  • Drop a stray Tx8("Hello World") issued before CS and DC were configured.
  • Fill with blue rather than white, as the surrounding code claims.

The init sequence values are updated to match Pimoroni's 320x240 Tufty profile, and each command is now commented with what it does in place of the println tracing used to debug this.

Testing

Built with TinyGo 0.42.0 for -target=tufty2040 and flashed to a Tufty 2040. The display shows a full screen blue fill.

DAD30273-2B0F-4C16-A58D-39DA99A49F97_4_5005_c

The Tufty parallel display example did not drive the panel. Several
independent problems, any one of which was enough to leave the screen
blank:

- rdPin was never configured as an output, so rdPin.High() was a no-op on
  a floating input. RD must be held high for the ST7789 to accept writes.
- configureDisplayRotation sent CASET twice, so the row address window was
  never set. It also byte swapped the CASET/RASET values and then emitted
  them big endian, swapping them twice.
- machine.GP10 and friends do not exist, so the example did not compile.
  Use the GPIOnn constants for the same pins.

While here, correct some other problems:

- FillRectangle allocated a 150KB framebuffer regardless of the size of
  the rectangle requested, and issued RAMWR twice because setWindow had
  already sent it. It now streams a small pre-filled chunk with CS held
  low for the duration.
- Deassert CS before the first command, and let the bus settle before
  raising CS so the final WR edge lands while the panel is still selected.
- Drop a stray Tx8("Hello World") issued before CS and DC were configured.
- Fill with blue rather than white, as the surrounding code claims.

The init sequence values are updated to match Pimoroni's 320x240 Tufty
profile, and each command is now commented with what it does in place of
the println tracing used to debug this.

Verified on hardware: displays a full screen blue fill.
CS and DC were configured as outputs by CommonInit(), but that ran
AFTER piolib.NewParallel had started the PIO state machine. During PIO
SM startup the state machine drives its initial (zeroed) OSR contents
onto the data lines with WR toggling; with CS floating those stray
bytes were being latched by the panel non-deterministically depending
on line state, leaving the ST7789 in an unknown mode. The user
reported this as "sometimes the display does not come up after reset,
but resetting again clears it".

Configure csPin, dcPin and rdPin as outputs at safe idle levels (all
high) in main() before calling piolib.NewParallel, and drop the now-
redundant pin setup from the top of CommonInit. Add a small settle
delay in CommonInit so warm resets have time for VDDI/VCI to
stabilise before the first SWRESET.
@soypat

soypat commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Hey Dave! It seems you are set to be the proud owner of a bounty I had setup a while back. The code looks A-OK. Will leave this open in case anyone wants to test it out

@conejoninja

Copy link
Copy Markdown
Member

I can confirm this PR makes the situation much better, it display a blue screen on tinygo 0.41.1 (while before it displays nothing) 👍 , but the FillRectangle seems off ,

The following code

        blue := color.RGBA{0, 0, 255, 255}
	if err := display.FillRectangle(0, 0, 320, 240, blue); err != nil {
		panic(err.Error())
	}
	white := color.RGBA{255, 255, 255, 255}
	if err := display.FillRectangle(0, 0, 160, 120, white); err != nil {
		panic(err.Error())
	}

produces this image, it should be a 1/4 of the screen (from the middle point to a corner):

image
  • rotation too seems to not be working properly either

@davecheney

Copy link
Copy Markdown
Contributor Author

Let me have a look at rotation today 👍

command() and FillRectangle's inlined RAMWR write both flipped DC to
high immediately after Tx8 returned for the command byte. Tx8 can
return a couple of PIO cycles before the command's final WR edge has
actually landed, so DC could change mid-pulse. This intermittently
corrupted the command byte and/or its latching, causing CASET/RASET to
not reliably take effect (FillRectangle then painted vertical bands
whose width depended on byte count rather than the requested window)
and produced visible pixel corruption at colour-band boundaries.

Add the same 10us settle already used before raising CS, but before
flipping DC high, in both command() and FillRectangle's RAMWR path.

Also fix configureDisplayRotation: with SWAP_XY (MV) set, ROW_ORDER and
COL_ORDER's effect on the physical axes is swapped, so the two branches
had the wrong MADCTL bit selected, producing a top/bottom mirrored
image. Swap them to match.

Verified on hardware with quadrant and full-width band fills: correct
geometry, correct top-to-bottom order, no corruption at boundaries.
configureDisplayRotation previously only handled the landscape
rotations (0/180) correctly; portrait (90/270) inherited landscape's
MADCTL bits and hardcoded 320x239 CASET/RASET regardless of the
requested orientation. Rework it to compute width/height, MADCTL, and
the addressing window from the rotation directly, so all four
rotations produce a correctly oriented, correctly sized window.

Replace the single static blue fill in main() with a continuously
looping demo: for each of the four rotations, run through four
stages (2s pause between each):
  1. full screen blue fill
  2. quadrant fill (white/red/green/yellow, TL/TR/BL/BR)
  3. four colored corner boxes on a black background
  4. a fill "stress test" that shrinks the fill window by 2px per
     iteration, alternating a palette color and black, producing
     concentric 1px rings -- a visual check that FillRectangle's
     addressing is pixel-accurate at small sizes and screen edges.

Verified on hardware: all four rotations render each stage correctly
oriented and sized, with no corruption.
@davecheney

Copy link
Copy Markdown
Contributor Author

@soypat @conejoninja PTAL, fill and rotation now work better

IMG_2491.MOV

@conejoninja

Copy link
Copy Markdown
Member

Yeah! Thanks for your work 😄 we can finally have this display on the list.

image

Only thing missing is this, so it implements the Displayer interface.

	// SetPizel modifies the internal buffer.
	SetPixel(x, y int16, c color.RGBA)

	// Display sends the buffer (if any) to the screen.
	Display() error

I use this for the demo, and it's also similar to the st7789 driver already in the repo

func (st *ST7789) SetPixel(x, y int16, c color.RGBA) {
	st.FillRectangle(x, y, 1, 1, c)
}

func (st *ST7789) Display() error {
	return nil
}

With that, we can approve this PR ✔️

@davecheney

davecheney commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor Author

@conejoninja I have a branch which is stacked on this one that implements the displayer interface. I haven't pushed a PR yet, but check it out https://github.com/davecheney/pio/tree/davecheney-tufty-display-improvements

it needs to be squashed and rebased when this PR lands.

@davecheney

Copy link
Copy Markdown
Contributor Author

@conejoninja i tried, and failed to push a stacked PR on top of #55, you'll have to make do with the branch for now.

@davecheney

Copy link
Copy Markdown
Contributor Author

Or, #58, which is dirty because it contains this PR as well

@conejoninja

Copy link
Copy Markdown
Member

no rush, I'm happy this is getting done and being taken care of

@davecheney

Copy link
Copy Markdown
Contributor Author

no rush, I'm happy this is getting done and being taken care of

I can port the Displayer changes from #58 into this PR if you like, then you don't have to take the silly demo examples along with it. let me know what works for you

@conejoninja

Copy link
Copy Markdown
Member

don't worry, I don't need to use right now, and prefer to wait for 55+58 to be merged.

Will leave this open in case anyone wants to test it out

tested! @soypat can be merge this?

@soypat

soypat commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

tested! @soypat can be merge this?

With great pleasure @conejoninja

@soypat
soypat merged commit e04cd34 into tinygo-org:main Sep 9, 2026
1 check passed
@davecheney
davecheney deleted the davecheney-dfc/tufty-pio-display branch September 9, 2026 11:59
davecheney added a commit to davecheney/pio that referenced this pull request Sep 10, 2026
Add the trivial SetPixel and Display stubs requested in the PR tinygo-org#55 review, plus a compile-time interface assertion. Fix the SetPixel interface comment typo.
soypat pushed a commit that referenced this pull request Sep 10, 2026
Add the trivial SetPixel and Display stubs requested in the PR #55 review, plus a compile-time interface assertion. Fix the SetPixel interface comment typo.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants