Skip to content
This repository was archived by the owner on Dec 8, 2025. It is now read-only.

Commit c2673e4

Browse files
authored
support for grove RGB LCD 16x2 (#639)
1 parent 79536d6 commit c2673e4

File tree

20 files changed

+378
-273
lines changed

20 files changed

+378
-273
lines changed

devs/samples/testrig.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as ds from "@devicescript/core"
22
import {
33
SSD1306Driver,
44
startBME680,
5-
startCharacterScreen,
5+
startCharacterScreenDisplay,
66
} from "@devicescript/drivers"
77
import { fetch } from "@devicescript/net"
88
import {
@@ -27,7 +27,7 @@ const servo = startServo({ pin: pins.A2 })
2727
const potentiometer = startPotentiometer({ pin: pins.A0 })
2828
const buzzer = startBuzzer({ pin: pins.A1 })
2929
const { temperature, humidity, pressure } = await startBME680()
30-
const display = await startCharacterScreen(
30+
const display = await startCharacterScreenDisplay(
3131
new SSD1306Driver({ width: 64, height: 48 })
3232
)
3333
const btnA = new ds.Button()

packages/drivers/src/characterscreen.ts renamed to packages/drivers/src/characterscreendisplay.ts

Lines changed: 64 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -6,62 +6,30 @@ import {
66
CharacterScreenServerSpec,
77
assert,
88
} from "@devicescript/core"
9-
import { Image, fontForText, Font, Display } from "@devicescript/graphics"
9+
import { fontForText, Display } from "@devicescript/graphics"
1010

1111
class CharacterScreenServer
1212
extends Server
1313
implements CharacterScreenServerSpec
1414
{
1515
private _message: string
16-
17-
private readonly _image: Image
18-
private readonly _font: Font
19-
private readonly _render: () => AsyncVoid
16+
private readonly _render: (message: string) => AsyncVoid
2017
private readonly _columns: number
2118
private readonly _rows: number
22-
/**
23-
* Foreground color (palete index)
24-
*/
25-
color = 1
26-
// letter spacing
27-
letterSpacing = 1
28-
// line spacing
29-
lineSpacing = 1
30-
// outer margin
31-
margin = 0
3219

3320
constructor(
34-
options: {
35-
display: Display
36-
font: Font
37-
} & CharacterScreenOptions &
38-
ServerOptions
21+
render: (message: string) => AsyncVoid,
22+
options: CharacterScreenOptions & ServerOptions
3923
) {
4024
super(ds.CharacterScreen.spec, options)
41-
this._image = options.display.image
42-
this._font = options.font
43-
this._render = options.display.show
25+
this._render = render
4426
this._columns = options.columns
4527
this._rows = options.rows
4628

47-
if (this._columns === undefined)
48-
this._columns = Math.floor(
49-
(this._image.width - 2 * this.margin) /
50-
(this._font.charWidth + this.letterSpacing)
51-
)
52-
if (this._rows === undefined)
53-
this._rows = Math.floor(
54-
(this._image.height - 2 * this.margin) /
55-
(this._font.charHeight + this.lineSpacing)
56-
)
57-
5829
if (this._rows === undefined || this._columns === undefined)
5930
throw new Error("rows or columns is missing")
6031

6132
this._message = ""
62-
63-
// clear screen
64-
if (this._image) this._image.fill(0)
6533
}
6634

6735
message() {
@@ -88,24 +56,68 @@ class CharacterScreenServer
8856

8957
private async render() {
9058
assert(!!this._render)
59+
await this._render(this._message)
60+
}
61+
}
9162

63+
export interface CharacterScreenOptions {
64+
columns?: number
65+
rows?: number
66+
}
67+
68+
/**
69+
* Starts a character screen server.
70+
*/
71+
export async function startCharacterScreen(
72+
render: (message: string) => AsyncVoid,
73+
options: CharacterScreenOptions & ServerOptions = {}
74+
) {
75+
const server = new CharacterScreenServer(render, options)
76+
return new CharacterScreen(startServer(server, options))
77+
}
78+
79+
/**
80+
* Starts a character screen server on a display.
81+
*/
82+
export async function startCharacterScreenDisplay(
83+
display: Display,
84+
options: CharacterScreenOptions & ServerOptions = {}
85+
) {
86+
const color = 1
87+
// letter spacing
88+
const letterSpacing = 1
89+
// line spacing
90+
const lineSpacing = 1
91+
// outer margin
92+
const margin = 0
93+
94+
const font = fontForText("")
95+
await display.init()
96+
const image = display.image
97+
await display.image.fill(0)
98+
99+
let columns = options.columns
100+
if (columns === undefined)
101+
columns = Math.floor(
102+
(image.width - 2 * margin) / (font.charWidth + letterSpacing)
103+
)
104+
let rows = options.rows
105+
if (rows === undefined)
106+
rows = Math.floor(
107+
(image.height - 2 * margin) / (font.charHeight + lineSpacing)
108+
)
109+
110+
const render = async (message: string) => {
92111
// paint image
93-
const img = this._image
112+
const img = image
94113
const ctx = img.allocContext()
95114

96-
const columns = this._columns
97-
const rows = this._rows
98-
const message = this._message
99-
100-
const cw = this._font.charWidth
101-
const ch = this._font.charHeight
102-
const letterSpacing = this.letterSpacing
103-
const lineSpacing = this.lineSpacing
104-
const margin = this.margin
115+
const cw = font.charWidth
116+
const ch = font.charHeight
105117

106-
ctx.font = this._font
107-
ctx.fillColor = this.color
108-
ctx.strokeColor = this.color
118+
ctx.font = font
119+
ctx.fillColor = color
120+
ctx.strokeColor = color
109121
ctx.clear()
110122
ctx.translate(margin, margin)
111123

@@ -126,31 +138,8 @@ class CharacterScreenServer
126138
}
127139

128140
// flush buffer
129-
await this._render()
141+
await display.show()
130142
}
131-
}
132-
133-
export interface CharacterScreenOptions {
134-
columns?: number
135-
rows?: number
136-
}
137143

138-
/**
139-
* Starts a character screen server on a display.
140-
*/
141-
export async function startCharacterScreen(
142-
display: Display,
143-
options: CharacterScreenOptions & ServerOptions = {}
144-
) {
145-
const font = fontForText("")
146-
147-
await display.init()
148-
149-
const server = new CharacterScreenServer({
150-
display,
151-
font,
152-
...options,
153-
})
154-
155-
return new CharacterScreen(startServer(server, options))
144+
return await startCharacterScreen(render, options)
156145
}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import "@dsboard/seeed_xiao_esp32c3"
2+
import * as ds from "@devicescript/core"
3+
import { I2CDriver, startCharacterScreen } from "@devicescript/drivers"
4+
import { delay } from "@devicescript/core"
5+
6+
// Device I2C Arress
7+
const LCD_ADDRESS = 0x7c >> 1
8+
const RGB_ADDRESS = 0xc4 >> 1
9+
const RGB_ADDRESS_V5 = 0x30
10+
11+
// color define
12+
const WHITE = 0
13+
const RED = 1
14+
const GREEN = 2
15+
const BLUE = 3
16+
17+
const REG_MODE1 = 0x00
18+
const REG_MODE2 = 0x01
19+
const REG_OUTPUT = 0x08
20+
21+
// commands
22+
const LCD_CLEARDISPLAY = 0x01
23+
const LCD_RETURNHOME = 0x02
24+
const LCD_ENTRYMODESET = 0x04
25+
const LCD_DISPLAYCONTROL = 0x08
26+
const LCD_CURSORSHIFT = 0x10
27+
const LCD_FUNCTIONSET = 0x20
28+
const LCD_SETCGRAMADDR = 0x40
29+
const LCD_SETDDRAMADDR = 0x80
30+
31+
// flags for display entry mode
32+
const LCD_ENTRYRIGHT = 0x00
33+
const LCD_ENTRYLEFT = 0x02
34+
const LCD_ENTRYSHIFTINCREMENT = 0x01
35+
const LCD_ENTRYSHIFTDECREMENT = 0x00
36+
37+
// flags for display on/off control
38+
const LCD_DISPLAYON = 0x04
39+
const LCD_DISPLAYOFF = 0x00
40+
const LCD_CURSORON = 0x02
41+
const LCD_CURSOROFF = 0x00
42+
const LCD_BLINKON = 0x01
43+
const LCD_BLINKOFF = 0x00
44+
45+
// flags for display/cursor shift
46+
const LCD_DISPLAYMOVE = 0x08
47+
const LCD_CURSORMOVE = 0x00
48+
const LCD_MOVERIGHT = 0x04
49+
const LCD_MOVELEFT = 0x00
50+
51+
// flags for function set
52+
const LCD_8BITMODE = 0x10
53+
const LCD_4BITMODE = 0x00
54+
const LCD_2LINE = 0x08
55+
const LCD_1LINE = 0x00
56+
const LCD_5x10DOTS = 0x04
57+
const LCD_5x8DOTS = 0x00
58+
59+
// https://wiki.seeedstudio.com/Grove-16x2_LCD_Series/#specification
60+
// converted from https://github.com/Seeed-Studio/Grove_LCD_RGB_Backlight/
61+
export class GroveRGBLCD extends I2CDriver {
62+
readonly columns: number
63+
readonly lines: number
64+
readonly dotsize: number
65+
private _displayfunction: number = 0
66+
private _displaymode: number = 0
67+
private _displaycontrol: number = 0
68+
private _currline: number = 0
69+
70+
constructor(
71+
readonly options: {
72+
columns: number
73+
rows: number
74+
dotsize: number
75+
devAddr?: number
76+
}
77+
) {
78+
super(options.devAddr || LCD_ADDRESS)
79+
this.columns = options.columns
80+
this.lines = options.rows
81+
this.dotsize = options.dotsize
82+
}
83+
84+
async initDriver(): Promise<void> {
85+
if (this.lines > 1) {
86+
this._displayfunction |= LCD_2LINE
87+
}
88+
this._currline = 0
89+
90+
// for some 1 line displays you can select a 10 pixel high font
91+
if (this.dotsize !== 0 && this.lines === 1) {
92+
this._displayfunction |= LCD_5x10DOTS
93+
}
94+
95+
// SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
96+
// according to datasheet, we need at least 40ms after power rises above 2.7V
97+
// before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50
98+
// delayMicroseconds(50000);
99+
await delay(5)
100+
101+
// this is according to the hitachi HD44780 datasheet
102+
// page 45 figure 23
103+
104+
// Send function set command sequence
105+
await this.command(LCD_FUNCTIONSET | this._displayfunction)
106+
await delay(5) // wait more than 4.1ms
107+
108+
// second try
109+
await this.command(LCD_FUNCTIONSET | this._displayfunction)
110+
await delay(1)
111+
112+
// third go
113+
await this.command(LCD_FUNCTIONSET | this._displayfunction)
114+
115+
// finally, set # lines, font size, etc.
116+
await this.command(LCD_FUNCTIONSET | this._displayfunction)
117+
118+
// turn the display on with no cursor or blinking default
119+
this._displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF
120+
await this.display()
121+
122+
// clear it off
123+
await this.clear()
124+
125+
// Initialize to default text direction (for romance languages)
126+
this._displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT
127+
// set the entry mode
128+
await this.command(LCD_ENTRYMODESET | this._displaymode)
129+
}
130+
131+
private async command(value: number): Promise<void> {
132+
await this.writeBuf(Buffer.from([0x80, value]))
133+
}
134+
135+
private async display() {
136+
this._displaycontrol |= LCD_DISPLAYON
137+
await this.command(LCD_DISPLAYCONTROL | this._displaycontrol)
138+
}
139+
140+
async clear() {
141+
await this.command(LCD_CLEARDISPLAY) // clear display, set cursor position to zero
142+
await delay(2) // this command takes a long time!
143+
}
144+
145+
private async write(value: number) {
146+
await this.writeBuf(Buffer.from([0x40, value]))
147+
}
148+
149+
async render(message: string) {
150+
await this.clear()
151+
if (message?.length > 0)
152+
for (let i = 0; i < message.length; ++i) {
153+
await this.write(message.charCodeAt(i))
154+
}
155+
}
156+
}
157+
158+
/**
159+
* Driver for the Grove RGB LCD 16x2 display.
160+
* @devsParts Grove RGB LCD 16x2
161+
* @devsWhenUsed
162+
*/
163+
export async function startGroveRGBLCD16x2() {
164+
const columns = 16
165+
const rows = 2
166+
167+
let render: (message: string) => ds.AsyncVoid = undefined
168+
if (!ds.isSimulator()) {
169+
const lcd = new GroveRGBLCD({
170+
columns,
171+
rows,
172+
dotsize: 0,
173+
devAddr: LCD_ADDRESS,
174+
})
175+
await lcd.init()
176+
render = async (message: string) => await lcd.render(message)
177+
}
178+
return await startCharacterScreen(render, {
179+
columns,
180+
rows,
181+
})
182+
}

0 commit comments

Comments
 (0)