diff --git a/packages/drivers/src/ili9341.ts b/packages/drivers/src/ili9341.ts new file mode 100644 index 0000000000..a824d739ce --- /dev/null +++ b/packages/drivers/src/ili9341.ts @@ -0,0 +1,22 @@ +import { Image } from "@devicescript/graphics" +import { STLikeDisplayOptions, STLikeDisplayDriver } from "./stlikedisplay" + +export interface ILI9341Options extends STLikeDisplayOptions {} + +export class ILI9341Driver extends STLikeDisplayDriver { + constructor(image: Image, options: ILI9341Options) { + super( + image, + options, + hex` +01 80 80 // software reset +ED 04 64 03 12 81 //power on sequence control +3A 01 55 // pixel format +B1 02 00 18 // FRMCTR1 +B6 03 08 A2 27 // display function control +11 80 78 +29 80 78 +` + ) + } +} diff --git a/packages/drivers/src/index.ts b/packages/drivers/src/index.ts index 891c5b2dfc..9f37917915 100644 --- a/packages/drivers/src/index.ts +++ b/packages/drivers/src/index.ts @@ -11,7 +11,10 @@ export * from "./ssd1306" export * from "./characterscreendisplay" export * from "./indexedscreen" export * from "./dotmatrix" +export * from "./stlikedisplay" export * from "./st7735" +export * from "./st7789" +export * from "./ili9341" export * from "./uc8151" export * from "./trafficlight" export * from "./ledserver" diff --git a/packages/drivers/src/st7735.ts b/packages/drivers/src/st7735.ts index b9a6bec379..96b348da26 100644 --- a/packages/drivers/src/st7735.ts +++ b/packages/drivers/src/st7735.ts @@ -1,248 +1,5 @@ -import { - GPIOMode, - OutputPin, - assert, - delay, - isSimulator, -} from "@devicescript/core" -import { SPI, spi } from "@devicescript/spi" -import { - Display, - Image, - SpiImageFlags, - spiSendImage, - Palette, -} from "@devicescript/graphics" -import "@devicescript/gpio" - -const ST7735_NOP = 0x00 -const ST7735_SWRESET = 0x01 -const ST7735_RDDID = 0x04 -const ST7735_RDDST = 0x09 -const ST7735_SLPIN = 0x10 -const ST7735_SLPOUT = 0x11 -const ST7735_PTLON = 0x12 -const ST7735_NORON = 0x13 -const ST7735_INVOFF = 0x20 -const ST7735_INVON = 0x21 -const ST7735_DISPOFF = 0x28 -const ST7735_DISPON = 0x29 -const ST7735_CASET = 0x2a -const ST7735_RASET = 0x2b -const ST7735_RAMWR = 0x2c -const ST7735_RAMRD = 0x2e -const ST7735_PTLAR = 0x30 -const ST7735_COLMOD = 0x3a -const ST7735_MADCTL = 0x36 -const ST7735_FRMCTR1 = 0xb1 -const ST7735_FRMCTR2 = 0xb2 -const ST7735_FRMCTR3 = 0xb3 -const ST7735_INVCTR = 0xb4 -const ST7735_GMCTRP1 = 0xe0 -const ST7735_GMCTRN1 = 0xe1 -const MADCTL_MY = 0x80 -const MADCTL_MX = 0x40 -const MADCTL_MV = 0x20 -const MADCTL_ML = 0x10 -const MADCTL_RGB = 0x00 -const MADCTL_BGR = 0x08 -const MADCTL_MH = 0x04 - -export interface FourWireOptions { - /** - * SPI CS pin - */ - cs: OutputPin - - /** - * Pin for switching between command and data (D/C or RS). - */ - dc: OutputPin - - /** - * SPI bus instance to use - */ - spi?: SPI - - /** - * Pin for resetting the display. - */ - reset?: OutputPin -} - -export interface STLikeDisplayOptions extends FourWireOptions { - /** - * Flip the display 180 deg. - * Without flipping, the connector ribbon of the screen should be on the top or left. - */ - flip?: boolean - - /** - * FRMCTR1 display register setting. Three bytes encoded as big endian number. - * - * @default 0x00_06_03 - */ - frmctr1?: number - - /** - * X-offset of the matrix. - * - * @default 0 - */ - offX?: number - - /** - * Y-offset of the matrix. - * - * @default 0 - */ - offY?: number -} - -export class FourWireDriver { - constructor(public options: OPT) {} - - protected async sendSeq(seq: Buffer) { - let i = 0 - while (i < seq.length) { - const cmd = seq[i++] - const len = seq[i++] - const args: number[] = [] - for (let j = 0; j < (len & 0x7f); ++j) args.push(seq[i++]) - let delayMS = 0 - if (len & 0x80) { - delayMS = seq[i++] - if (delayMS === 0xff) delayMS = 500 - } - await this.sendCmd(cmd, ...args) - if (delayMS) await delay(delayMS) - } - } - - protected async cmdPrep(cmd: number) { - const { spi, cs, dc } = this.options - dc.write(0) - cs.write(0) - await spi.write(Buffer.from([cmd])) - dc.write(1) - } - - protected async cmdFinish() { - const { cs } = this.options - cs.write(1) - } - - protected async sendCmd(cmd: number, ...args: number[]) { - if (isSimulator()) return - const { spi, cs } = this.options - await this.cmdPrep(cmd) - if (args.length) await spi.write(Buffer.from(args)) - await this.cmdFinish() - } - - protected async initPins() { - if (isSimulator()) return - const { cs, dc, reset } = this.options - - if (reset) { - reset.setMode(GPIOMode.OutputLow) - await delay(20) - reset.setMode(GPIOMode.OutputHigh) - await delay(20) - } - - dc.setMode(GPIOMode.OutputHigh) - cs.setMode(GPIOMode.OutputHigh) - } -} - -export class STLikeDisplayDriver - extends FourWireDriver - implements Display -{ - public readonly palette: Palette - private rot = 0 - - constructor( - public image: Image, - options: STLikeDisplayOptions, - protected initSeq: Buffer - ) { - super(options) - assert(image.bpp === 4) - this.options = Object.assign({}, this.options) - if (this.options.frmctr1 == undefined) this.options.frmctr1 = 0x000603 - if (this.options.spi == undefined) this.options.spi = spi - - this.palette = Palette.arcade() - if (image.width > image.height) this.rot = 1 - if (this.options.flip) this.rot |= 2 - } - - private async setAddrWindow(x: number, y: number, w: number, h: number) { - w += x - 1 - h += y - 1 - await this.sendCmd(ST7735_RASET, 0, x, w >> 8, w & 0xff) - await this.sendCmd(ST7735_CASET, 0, y, h >> 8, h & 0xff) - } - - private async doInit() { - await this.initPins() - await this.sendSeq(this.initSeq) - - await this.sendCmd(ST7735_MADCTL, hex`00 40 C0 80`[this.rot]) - const frmctr1 = [ - this.options.frmctr1 >> 16, - (this.options.frmctr1 >> 8) & 0xff, - this.options.frmctr1 & 0xff, - ] - if (frmctr1[2] === 0xff) frmctr1.pop() - await this.sendCmd(ST7735_FRMCTR1, ...frmctr1) - - const offX = this.options.offX ?? 0 - const offY = this.options.offY ?? 0 - - if (this.rot & 1) - await this.setAddrWindow( - offX, - offY, - this.image.width, - this.image.height - ) - else - await this.setAddrWindow( - offY, - offX, - this.image.height, - this.image.width - ) - } - - async init() { - await this.doInit() - } - - async show() { - if (isSimulator()) return - - const { spi, cs } = this.options - await this.cmdPrep(ST7735_RAMWR) - - let flags = SpiImageFlags.MODE_565 - - if (this.rot & 1) flags |= SpiImageFlags.BY_COL - else flags |= SpiImageFlags.BY_ROW - - await spiSendImage({ - spi, - image: this.image, - palette: this.palette, - flags, - }) - - await this.cmdFinish() - } -} +import { Image } from "@devicescript/graphics" +import { STLikeDisplayDriver, STLikeDisplayOptions } from "./stlikedisplay" export interface ST7735Options extends STLikeDisplayOptions {} @@ -262,22 +19,3 @@ export class ST7735Driver extends STLikeDisplayDriver { ) } } - -export interface ST7789Options extends STLikeDisplayOptions {} - -export class ST7789Driver extends STLikeDisplayDriver { - constructor(image: Image, options: ST7789Options) { - super( - image, - options, - hex` -01 80 78 // SWRESET + 120ms -11 80 78 // SLPOUT + 120ms -21 00 // INVON -3A 01 55 // COLMOD 16bit 16bit -13 80 0A // NORON + 10ms -29 80 0A // DISPON + 10ms -` - ) - } -} diff --git a/packages/drivers/src/st7789.ts b/packages/drivers/src/st7789.ts new file mode 100644 index 0000000000..c5a2838e69 --- /dev/null +++ b/packages/drivers/src/st7789.ts @@ -0,0 +1,21 @@ +import { Image } from "@devicescript/graphics" +import { STLikeDisplayOptions, STLikeDisplayDriver } from "./stlikedisplay" + +export interface ST7789Options extends STLikeDisplayOptions {} + +export class ST7789Driver extends STLikeDisplayDriver { + constructor(image: Image, options: ST7789Options) { + super( + image, + options, + hex` +01 80 78 // SWRESET + 120ms +11 80 78 // SLPOUT + 120ms +21 00 // INVON +3A 01 55 // COLMOD 16bit 16bit +13 80 0A // NORON + 10ms +29 80 0A // DISPON + 10ms +` + ) + } +} diff --git a/packages/drivers/src/stlikedisplay.ts b/packages/drivers/src/stlikedisplay.ts new file mode 100644 index 0000000000..7ff1b51582 --- /dev/null +++ b/packages/drivers/src/stlikedisplay.ts @@ -0,0 +1,218 @@ +import { + GPIOMode, + OutputPin, + assert, + delay, + isSimulator, +} from "@devicescript/core" +import { SPI, spi } from "@devicescript/spi" +import { + Display, + Image, + SpiImageFlags, + spiSendImage, + Palette, +} from "@devicescript/graphics" +import "@devicescript/gpio" + +const ST7735_CASET = 0x2a +const ST7735_RASET = 0x2b +const ST7735_RAMWR = 0x2c +const ST7735_MADCTL = 0x36 +const ST7735_FRMCTR1 = 0xb1 + +export interface FourWireOptions { + /** + * SPI CS pin + */ + cs: OutputPin + + /** + * Pin for switching between command and data (D/C or RS). + */ + dc: OutputPin + + /** + * SPI bus instance to use + */ + spi?: SPI + + /** + * Pin for resetting the display. + */ + reset?: OutputPin +} + +export interface STLikeDisplayOptions extends FourWireOptions { + /** + * Flip the display 180 deg. + * Without flipping, the connector ribbon of the screen should be on the top or left. + */ + flip?: boolean + + /** + * FRMCTR1 display register setting. Three bytes encoded as big endian number. + * + * @default 0x00_06_03 + */ + frmctr1?: number + + /** + * X-offset of the matrix. + * + * @default 0 + */ + offX?: number + + /** + * Y-offset of the matrix. + * + * @default 0 + */ + offY?: number +} + +export class FourWireDriver { + constructor(public options: OPT) {} + + protected async sendSeq(seq: Buffer) { + let i = 0 + while (i < seq.length) { + const cmd = seq[i++] + const len = seq[i++] + const args: number[] = [] + for (let j = 0; j < (len & 0x7f); ++j) args.push(seq[i++]) + let delayMS = 0 + if (len & 0x80) { + delayMS = seq[i++] + if (delayMS === 0xff) delayMS = 500 + } + await this.sendCmd(cmd, ...args) + if (delayMS) await delay(delayMS) + } + } + + protected async cmdPrep(cmd: number) { + const { spi, cs, dc } = this.options + dc.write(0) + cs.write(0) + await spi.write(Buffer.from([cmd])) + dc.write(1) + } + + protected async cmdFinish() { + const { cs } = this.options + cs.write(1) + } + + protected async sendCmd(cmd: number, ...args: number[]) { + if (isSimulator()) return + const { spi } = this.options + await this.cmdPrep(cmd) + if (args.length) await spi.write(Buffer.from(args)) + await this.cmdFinish() + } + + protected async initPins() { + if (isSimulator()) return + const { cs, dc, reset } = this.options + + if (reset) { + reset.setMode(GPIOMode.OutputLow) + await delay(20) + reset.setMode(GPIOMode.OutputHigh) + await delay(20) + } + + dc.setMode(GPIOMode.OutputHigh) + cs.setMode(GPIOMode.OutputHigh) + } +} + +export class STLikeDisplayDriver + extends FourWireDriver + implements Display +{ + public readonly palette: Palette + private rot = 0 + + constructor( + public image: Image, + options: STLikeDisplayOptions, + protected initSeq: Buffer + ) { + super(options) + assert(image.bpp === 4) + this.options = Object.assign({}, this.options) + if (this.options.frmctr1 == undefined) this.options.frmctr1 = 0x000603 + if (this.options.spi == undefined) this.options.spi = spi + + this.palette = Palette.arcade() + if (image.width > image.height) this.rot = 1 + if (this.options.flip) this.rot |= 2 + } + + private async setAddrWindow(x: number, y: number, w: number, h: number) { + w += x - 1 + h += y - 1 + await this.sendCmd(ST7735_RASET, 0, x, w >> 8, w & 0xff) + await this.sendCmd(ST7735_CASET, 0, y, h >> 8, h & 0xff) + } + + private async doInit() { + await this.initPins() + await this.sendSeq(this.initSeq) + + await this.sendCmd(ST7735_MADCTL, hex`00 40 C0 80`[this.rot]) + const frmctr1 = [ + this.options.frmctr1 >> 16, + (this.options.frmctr1 >> 8) & 0xff, + this.options.frmctr1 & 0xff, + ] + if (frmctr1[2] === 0xff) frmctr1.pop() + await this.sendCmd(ST7735_FRMCTR1, ...frmctr1) + + const offX = this.options.offX ?? 0 + const offY = this.options.offY ?? 0 + + if (this.rot & 1) + await this.setAddrWindow( + offX, + offY, + this.image.width, + this.image.height + ) + else + await this.setAddrWindow( + offY, + offX, + this.image.height, + this.image.width + ) + } + + async init() { + await this.doInit() + } + + async show() { + if (isSimulator()) return + + const { spi } = this.options + await this.cmdPrep(ST7735_RAMWR) + + let flags = SpiImageFlags.MODE_565 + + if (this.rot & 1) flags |= SpiImageFlags.BY_COL + else flags |= SpiImageFlags.BY_ROW + + await spiSendImage({ + spi, + image: this.image, + palette: this.palette, + flags, + }) + + await this.cmdFinish() + } +} diff --git a/packages/drivers/src/uc8151.ts b/packages/drivers/src/uc8151.ts index 51c2541d49..8ce11a1511 100644 --- a/packages/drivers/src/uc8151.ts +++ b/packages/drivers/src/uc8151.ts @@ -2,7 +2,7 @@ // by Pimoroni Ltd, MIT license import { GPIOMode, InputPin, LOW, delay, isSimulator } from "@devicescript/core" -import { FourWireDriver, FourWireOptions } from "./st7735" +import { FourWireDriver, FourWireOptions } from "./stlikedisplay" import { Display, Image, diff --git a/website/docs/api/drivers/st7789.mdx b/website/docs/api/drivers/st7789.mdx index ad0d5eb80d..e91894813d 100644 --- a/website/docs/api/drivers/st7789.mdx +++ b/website/docs/api/drivers/st7789.mdx @@ -2,19 +2,21 @@ title: ST7735, ILI9163, ST7789 --- -# ST7735, ILI9163, ST7789 +# ST7735, ILI9163, ST7789, ILI9341 Driver for ST7735, ST7789 and similar LCD screens using SPI. The ST7735 driver also works for ILI9163C. -ILI9341 should be [easy to add](https://github.com/microsoft/devicescript/issues/568). ```ts import { ST7789Driver } from "@devicescript/drivers" import { ST7735Driver } from "@devicescript/drivers" +import { ILI9341Driver } from "@devicescript/drivers" ``` - [Datasheet for ST7789](https://www.waveshare.com/w/upload/a/ae/ST7789_Datasheet.pdf) - [Datasheet for ST7735](https://www.displayfuture.com/Display/datasheet/controller/ST7735.pdf) +- [Datasheet for ILI9163](https://www.newhavendisplay.com/app_notes/ILI9163.pdf) +- [Datasheet for ILI9341](https://cdn-shop.adafruit.com/datasheets/ILI9341.pdf) ![WaveShare Pico-LCD shield](https://github.com/mmoskal/devicescript-waveshare-pico-lcd/blob/main/assets/pico-lcd-114.png?raw=true) diff --git a/website/docs/developer/graphics/display.mdx b/website/docs/developer/graphics/display.mdx index 2238368922..0b6d47bdda 100644 --- a/website/docs/developer/graphics/display.mdx +++ b/website/docs/developer/graphics/display.mdx @@ -10,7 +10,7 @@ interface provides an abstraction over a small screen. The `Display` interface i for various hardware peripherical and can be used with various services. - [SSD1306](/api/drivers/ssd1306) or [SH110X](/api/drivers/sh110x), OLED monochrome, I2C -- [ST7789, ST7735, ILI9163](/api/drivers/st7789), LCD color, SPI +- [ST7789, ST7735, ILI9163, ILI9341](/api/drivers/st7789), LCD color, SPI - [UC8151](/api/drivers/uc8151), eInk monochrome, SPI :::caution I8080 not supported