Skip to content

Commit

Permalink
Driver for SSD1289 LCD
Browse files Browse the repository at this point in the history
  • Loading branch information
Steven Pearson authored and deadprogram committed Feb 18, 2022
1 parent 0ced126 commit b3c0315
Show file tree
Hide file tree
Showing 7 changed files with 364 additions and 1 deletion.
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -225,13 +225,15 @@ endif
@md5sum ./build/test.elf
tinygo build -size short -o ./build/test.hex -target=nucleo-wl55jc ./examples/sx126x/lora_rxtx/
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/ssd1289/main.go
@md5sum ./build/test.uf2

DRIVERS = $(wildcard */)
NOTESTS = build examples flash semihosting pcd8544 shiftregister st7789 microphone mcp3008 gps microbitmatrix \
hcsr04 ssd1331 ws2812 thermistor apa102 easystepper ssd1351 ili9341 wifinina shifter hub75 \
hd44780 buzzer ssd1306 espat l9110x st7735 bmi160 l293x dht keypad4x4 max72xx p1am tone tm1637 \
pcf8563 mcp2515 servo sdcard rtl8720dn image cmd i2csoft hts221 lps22hb apds9960 axp192 xpt2046 \
ft6336 sx126x
ft6336 sx126x ssd1289
TESTS = $(filter-out $(addsuffix /%,$(NOTESTS)),$(DRIVERS))

unit-test:
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ The following 78 devices are supported.
| [WS2812 RGB LED](https://cdn-shop.adafruit.com/datasheets/WS2812.pdf) | GPIO |
| [XPT2046 touch controller](http://grobotronics.com/images/datasheets/xpt2046-datasheet.pdf) | GPIO |
| [Semtech SX126x Lora](https://www.semtech.com/products/wireless-rf/lora-transceiv-ers/sx1261) | SPI |
| [SSD1289 TFT color display](http://aitendo3.sakura.ne.jp/aitendo_data/product_img/lcd/tft2/M032C1289TP/3.2-SSD1289.pdf) | GPIO |

## Contributing

Expand Down
68 changes: 68 additions & 0 deletions examples/ssd1289/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package main

import (
"image/color"
"machine"
"math/rand"

"tinygo.org/x/drivers/ssd1289"
)

func main() {

//The SSD1289 is configured in 16 bit parallel mode and requires 16 GPIOs
//The Pin bus is the most flexible but ineffecient method it switches
//individual pins on and off. If you are able to use consecutive pins
//consider creating a more efficient bus implementation that uses
//your microcontrollers built in "ports"
//see rp2040bus.go for an example for the rapsberry pi pico
bus := ssd1289.NewPinBus([16]machine.Pin{
machine.GP4, //DB0
machine.GP5, //DB1
machine.GP6, //DB2
machine.GP7, //DB3
machine.GP8, //DB4
machine.GP9, //DB5
machine.GP10, //DB6
machine.GP11, //DB7
machine.GP12, //DB8
machine.GP13, //DB9
machine.GP14, //DB10
machine.GP15, //DB11
machine.GP16, //DB12
machine.GP17, //DB13
machine.GP18, //DB14
machine.GP19, //DB15
})

//Control pins for the SSD1289
rs := machine.GP0
wr := machine.GP1
cs := machine.GP2
rst := machine.GP3

display := ssd1289.New(rs, wr, cs, rst, bus)

display.Configure() //Sends intialization sequence to SSD1289.
//!! After configure the display will contain random data and needs to be cleared

background := color.RGBA{0, 0, 0, 255} //Black
display.FillDisplay(background) //Clears the display to the given color

for {
//Draw random filled coloured rectangles
x := int16(rand.Intn(120))
w := int16(rand.Intn(120))

y := int16(rand.Intn(160))
h := int16(rand.Intn(160))

r := uint8(rand.Intn(255))
g := uint8(rand.Intn(255))
b := uint8(rand.Intn(255))

c := color.RGBA{r, g, b, 255}

display.FillRect(x, y, w, h, c) //Fills the given rectangle the rest of the display is unaffected.
}
}
37 changes: 37 additions & 0 deletions ssd1289/pinbus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package ssd1289

import "machine"

type pinBus struct {
pins [16]machine.Pin
}

func NewPinBus(pins [16]machine.Pin) pinBus {

for i := 0; i < 16; i++ {
pins[i].Configure(machine.PinConfig{Mode: machine.PinOutput})
}

return pinBus{
pins: pins,
}
}

func (b pinBus) Set(data uint16) {
b.pins[15].Set((data & (1 << 15)) != 0)
b.pins[14].Set((data & (1 << 14)) != 0)
b.pins[13].Set((data & (1 << 13)) != 0)
b.pins[12].Set((data & (1 << 12)) != 0)
b.pins[11].Set((data & (1 << 11)) != 0)
b.pins[10].Set((data & (1 << 10)) != 0)
b.pins[9].Set((data & (1 << 9)) != 0)
b.pins[8].Set((data & (1 << 8)) != 0)
b.pins[7].Set((data & (1 << 7)) != 0)
b.pins[6].Set((data & (1 << 6)) != 0)
b.pins[5].Set((data & (1 << 5)) != 0)
b.pins[4].Set((data & (1 << 4)) != 0)
b.pins[3].Set((data & (1 << 3)) != 0)
b.pins[2].Set((data & (1 << 2)) != 0)
b.pins[1].Set((data & (1 << 1)) != 0)
b.pins[0].Set((data & (1 << 0)) != 0)
}
23 changes: 23 additions & 0 deletions ssd1289/registers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ssd1289

type Command byte

const (
OSCILLATIONSTART Command = 0x00
DRIVEROUTPUTCONTROL = 0x01
POWERCONTROL1 = 0x03
POWERCONTROL2 = 0x0C
POWERCONTROL3 = 0x0D
POWERCONTROL4 = 0x0E
POWERCONTROL5 = 0x1E
DISPLAYCONTROL = 0x07
SLEEPMODE = 0x10
ENTRYMODE = 0x11
LCDDRIVEACCONTROL = 0x02
HORIZONTALRAMADDRESSPOSITION = 0x44
VERTICALRAMADDRESSSTARTPOSITION = 0x45
VERTICALRAMADDRESSENDPOSITION = 0x46
SETGDDRAMYADDRESSCOUNTER = 0x4F
SETGDDRAMXADDRESSCOUNTER = 0x4E
RAMDATAREADWRITE = 0x22
)
32 changes: 32 additions & 0 deletions ssd1289/rp2040bus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//go:build rp2040
// +build rp2040

package ssd1289

import (
"device/rp"
"machine"
)

type rp2040Bus struct {
firstPin machine.Pin
}

func NewRP2040Bus(firstPin machine.Pin) rp2040Bus {

for i := uint8(0); i < 16; i++ {
pin := machine.Pin(i + uint8(firstPin))
pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
}

return rp2040Bus{
firstPin: firstPin,
}

}

func (b rp2040Bus) Set(data uint16) {
data32 := uint32(data)
rp.SIO.GPIO_OUT_CLR.Set(0xFFFF << b.firstPin)
rp.SIO.GPIO_OUT_SET.Set(data32 << b.firstPin)
}
200 changes: 200 additions & 0 deletions ssd1289/ssd1289.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// Package ssd1289 implements a driver for the SSD1289 led matrix controller as packaged on the TFT_320QVT board
//
// Datasheet: http://aitendo3.sakura.ne.jp/aitendo_data/product_img/lcd/tft2/M032C1289TP/3.2-SSD1289.pdf
//
package ssd1289

import (
"image/color"
"machine"
"time"
)

type Bus interface {
Set(data uint16)
}

type Device struct {
rs machine.Pin
wr machine.Pin
cs machine.Pin
rst machine.Pin
bus Bus
}

const width = int16(240)
const height = int16(320)

func New(rs machine.Pin, wr machine.Pin, cs machine.Pin, rst machine.Pin, bus Bus) Device {
d := Device{
rs: rs,
wr: wr,
cs: cs,
rst: rst,
bus: bus,
}

rs.Configure(machine.PinConfig{Mode: machine.PinOutput})
wr.Configure(machine.PinConfig{Mode: machine.PinOutput})
cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
rst.Configure(machine.PinConfig{Mode: machine.PinOutput})

cs.High()
rst.High()
wr.High()

return d
}

func (d *Device) lcdWriteCom(cmd Command) {
d.rs.Low()
d.lcdWriteBusInt(uint16(cmd))
}

func (d *Device) lcdWriteDataInt(data uint16) {
d.rs.High()
d.lcdWriteBusInt(data)
}

func (d *Device) lcdWriteComData(cmd Command, data uint16) {
d.lcdWriteCom(cmd)
d.lcdWriteDataInt(data)
}

func (d *Device) tx() {
d.wr.Low()
d.wr.High()
}

func (d *Device) lcdWriteBusInt(data uint16) {
d.bus.Set(data)
d.tx()
}

func (d *Device) Configure() {
d.rst.High()
time.Sleep(time.Millisecond * 5)
d.rst.Low()
time.Sleep(time.Millisecond * 15)
d.rst.High()
time.Sleep(time.Millisecond * 15)
d.cs.Low()

//Power supply setting
d.lcdWriteComData(POWERCONTROL1, 0xA8A4)
d.lcdWriteComData(POWERCONTROL2, 0x0000)
d.lcdWriteComData(POWERCONTROL3, 0x080C)
d.lcdWriteComData(POWERCONTROL4, 0x2B00)
d.lcdWriteComData(POWERCONTROL5, 0x00B7)

//Set R07h at 0021h
d.lcdWriteComData(DISPLAYCONTROL, 0x021)

//Set R00h at 0001h
d.lcdWriteComData(OSCILLATIONSTART, 0x0001)

//Set R07h at 0021h
d.lcdWriteComData(DISPLAYCONTROL, 0x023)

//Set R10h at 0000h, Exit sleep mode
d.lcdWriteComData(SLEEPMODE, 0x0000)

//Wait 30ms
time.Sleep(time.Millisecond * 30)

//Set R07h at 0033h
d.lcdWriteComData(DISPLAYCONTROL, 0x033)

//Entry Mode setting (R11h)
//DFM 11 --> 65k
//TRANS 0
//OEDEF 0
//WMODE 0 --> Normal data bus
//DMODE 00 --> Ram
//TY 01 --> 262k Type A not used as we are in 65k mode.
//ID 11 --> Horizontal & Vertical increment
//AM 0 --> Horizontal
//LG 000 --> No compare register usage
d.lcdWriteComData(ENTRYMODE, 0x6030)

//LCD Driver AC Setting
//I couldn't make sense of the documentation fortunately 0 seems to
//FLD 0 --> Normal driving
//ENWS 0 --> POR mode
//BC 1 --> Less flicker
//EOR 1 --> Less stripey
//WSMD 0 --> not used in POR mode
//NW 0 --> Least flicker
d.lcdWriteComData(LCDDRIVEACCONTROL, 0x0600)

//End of documented init

//RL 0 --> Output shift direction
//REV 1 --> Reverse colors
//CAD 0 --> Cs on common
//BGR 0 --> use RGB color assignment
//SM 0 --> standard gate scan sequence
//TB 1 --> Display is mirrored with 0
//MUX 319 --> Number of lines in display
d.lcdWriteComData(DRIVEROUTPUTCONTROL, 0x233F)

d.cs.High()

}

func (d *Device) setXY(x1 uint16, y1 uint16, x2 uint16, y2 uint16) {
d.lcdWriteComData(HORIZONTALRAMADDRESSPOSITION, (x2<<8)+x1)
d.lcdWriteComData(VERTICALRAMADDRESSSTARTPOSITION, y1)
d.lcdWriteComData(VERTICALRAMADDRESSENDPOSITION, y2)
d.lcdWriteComData(SETGDDRAMXADDRESSCOUNTER, x1)
d.lcdWriteComData(SETGDDRAMYADDRESSCOUNTER, y1)
d.lcdWriteCom(RAMDATAREADWRITE)
}

func (d *Device) ClearDisplay() {
d.FillDisplay(color.RGBA{0, 0, 0, 255})
}

func (d *Device) FillDisplay(c color.RGBA) {
d.FillRect(0, 0, width, height, c)
}

func encodeColor(c color.RGBA) uint16 {
encoded := (uint16(c.B)&248)<<8 | (uint16(c.G)&252)<<3 | (uint16(c.R)&248)>>3
return encoded
}

func (d *Device) SetPixel(x, y int16, c color.RGBA) {

encoded := encodeColor(c)

d.cs.Low()
d.setXY(uint16(x), uint16(y), uint16(x), uint16(y))
d.rs.High()
d.lcdWriteBusInt(encoded)
d.cs.High()
}

func (d *Device) FillRect(x, y, w, h int16, c color.RGBA) {
encoded := encodeColor(c)

d.cs.Low()
d.setXY(uint16(x), uint16(y), uint16(x+(w-1)), uint16(y+(h-1)))
d.rs.High()
d.bus.Set(encoded)
for i := int64(0); i < int64(w)*int64(h); i++ {
d.tx()
}
d.cs.High()
d.rs.Low()

}

func (d *Device) Display() error {
//Not enough memory to store an entire screen on most microcontrollers
return nil
}

func (d *Device) Size() (x, y int16) {
return width, height
}

0 comments on commit b3c0315

Please sign in to comment.