From b3c0315a0910db0316f30e736998462a5c8229d4 Mon Sep 17 00:00:00 2001 From: Steven Pearson Date: Thu, 9 Dec 2021 21:04:50 +0100 Subject: [PATCH] Driver for SSD1289 LCD --- Makefile | 4 +- README.md | 1 + examples/ssd1289/main.go | 68 +++++++++++++ ssd1289/pinbus.go | 37 ++++++++ ssd1289/registers.go | 23 +++++ ssd1289/rp2040bus.go | 32 +++++++ ssd1289/ssd1289.go | 200 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 examples/ssd1289/main.go create mode 100644 ssd1289/pinbus.go create mode 100644 ssd1289/registers.go create mode 100644 ssd1289/rp2040bus.go create mode 100644 ssd1289/ssd1289.go diff --git a/Makefile b/Makefile index e5bf87c71..6f14e8f2b 100644 --- a/Makefile +++ b/Makefile @@ -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: diff --git a/README.md b/README.md index e66a9de30..48b5390dd 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/examples/ssd1289/main.go b/examples/ssd1289/main.go new file mode 100644 index 000000000..c68f328df --- /dev/null +++ b/examples/ssd1289/main.go @@ -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. + } +} diff --git a/ssd1289/pinbus.go b/ssd1289/pinbus.go new file mode 100644 index 000000000..aacdf79aa --- /dev/null +++ b/ssd1289/pinbus.go @@ -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) +} diff --git a/ssd1289/registers.go b/ssd1289/registers.go new file mode 100644 index 000000000..9cf5e7dfb --- /dev/null +++ b/ssd1289/registers.go @@ -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 +) diff --git a/ssd1289/rp2040bus.go b/ssd1289/rp2040bus.go new file mode 100644 index 000000000..6202d7413 --- /dev/null +++ b/ssd1289/rp2040bus.go @@ -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) +} diff --git a/ssd1289/ssd1289.go b/ssd1289/ssd1289.go new file mode 100644 index 000000000..a00ed1834 --- /dev/null +++ b/ssd1289/ssd1289.go @@ -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 +}