Skip to content

Commit dda29b1

Browse files
committed
ssd1306: extract i2c and spi bus implementations
1 parent ac87a39 commit dda29b1

File tree

3 files changed

+176
-179
lines changed

3 files changed

+176
-179
lines changed

ssd1306/ssd1306.go

Lines changed: 67 additions & 179 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package ssd1306 // import "tinygo.org/x/drivers/ssd1306"
66
import (
77
"errors"
88
"image/color"
9-
"machine"
109
"time"
1110

1211
"tinygo.org/x/drivers"
@@ -22,16 +21,15 @@ type ResetValue [2]byte
2221

2322
// Device wraps I2C or SPI connection.
2423
type Device struct {
25-
bus Buser
26-
buffer []byte
27-
width int16
28-
height int16
29-
bufferSize int16
30-
vccState VccMode
31-
canReset bool
32-
resetCol ResetValue
33-
resetPage ResetValue
34-
rotation drivers.Rotation
24+
bus Buser
25+
buffer []byte
26+
width int16
27+
height int16
28+
vccState VccMode
29+
canReset bool
30+
resetCol ResetValue
31+
resetPage ResetValue
32+
rotation drivers.Rotation
3533
}
3634

3735
// Config is the configuration for the display
@@ -50,51 +48,14 @@ type Config struct {
5048
Rotation drivers.Rotation
5149
}
5250

53-
type I2CBus struct {
54-
wire drivers.I2C
55-
Address uint16
56-
}
57-
58-
type SPIBus struct {
59-
wire drivers.SPI
60-
dcPin machine.Pin
61-
resetPin machine.Pin
62-
csPin machine.Pin
63-
}
64-
6551
type Buser interface {
66-
configure() error
67-
tx(data []byte, isCommand bool) error
68-
setAddress(address uint16) error
52+
configure(address uint16, size int16) []byte // configure the bus with the given configuration and return the buffer to use
53+
command(cmd uint8) error // send a command to the display
54+
flush() error // send the data in the buffer to the display
6955
}
7056

7157
type VccMode uint8
7258

73-
// NewI2C creates a new SSD1306 connection. The I2C wire must already be configured.
74-
func NewI2C(bus drivers.I2C) Device {
75-
return Device{
76-
bus: &I2CBus{
77-
wire: bus,
78-
Address: Address,
79-
},
80-
}
81-
}
82-
83-
// NewSPI creates a new SSD1306 connection. The SPI wire must already be configured.
84-
func NewSPI(bus drivers.SPI, dcPin, resetPin, csPin machine.Pin) Device {
85-
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
86-
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
87-
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
88-
return Device{
89-
bus: &SPIBus{
90-
wire: bus,
91-
dcPin: dcPin,
92-
resetPin: resetPin,
93-
csPin: csPin,
94-
},
95-
}
96-
}
97-
9859
// Configure initializes the display with default configuration
9960
func (d *Device) Configure(cfg Config) {
10061
var zeroReset ResetValue
@@ -108,9 +69,6 @@ func (d *Device) Configure(cfg Config) {
10869
} else {
10970
d.height = 64
11071
}
111-
if cfg.Address != 0 {
112-
d.bus.setAddress(cfg.Address)
113-
}
11472
if cfg.VccState != 0 {
11573
d.vccState = cfg.VccState
11674
} else {
@@ -126,77 +84,75 @@ func (d *Device) Configure(cfg Config) {
12684
} else {
12785
d.resetPage = ResetValue{0, uint8(d.height/8) - 1}
12886
}
129-
d.bufferSize = d.width * d.height / 8
130-
d.buffer = make([]byte, d.bufferSize+1) // +1 for the I2C command byte
13187
d.canReset = cfg.Address != 0 || d.width != 128 || d.height != 64 // I2C or not 128x64
13288

133-
d.bus.configure()
89+
d.buffer = d.bus.configure(cfg.Address, d.width*d.height/8)
13490

13591
time.Sleep(100 * time.Nanosecond)
136-
d.Command(DISPLAYOFF)
137-
d.Command(SETDISPLAYCLOCKDIV)
138-
d.Command(0x80)
139-
d.Command(SETMULTIPLEX)
140-
d.Command(uint8(d.height - 1))
141-
d.Command(SETDISPLAYOFFSET)
142-
d.Command(0x0)
143-
d.Command(SETSTARTLINE | 0x0)
144-
d.Command(CHARGEPUMP)
92+
d.bus.command(DISPLAYOFF)
93+
d.bus.command(SETDISPLAYCLOCKDIV)
94+
d.bus.command(0x80)
95+
d.bus.command(SETMULTIPLEX)
96+
d.bus.command(uint8(d.height - 1))
97+
d.bus.command(SETDISPLAYOFFSET)
98+
d.bus.command(0x0)
99+
d.bus.command(SETSTARTLINE | 0x0)
100+
d.bus.command(CHARGEPUMP)
145101
if d.vccState == EXTERNALVCC {
146-
d.Command(0x10)
102+
d.bus.command(0x10)
147103
} else {
148-
d.Command(0x14)
104+
d.bus.command(0x14)
149105
}
150-
d.Command(MEMORYMODE)
151-
d.Command(0x00)
106+
d.bus.command(MEMORYMODE)
107+
d.bus.command(0x00)
152108

153109
d.SetRotation(cfg.Rotation)
154110

155111
if (d.width == 128 && d.height == 64) || (d.width == 64 && d.height == 48) { // 128x64 or 64x48
156-
d.Command(SETCOMPINS)
157-
d.Command(0x12)
158-
d.Command(SETCONTRAST)
112+
d.bus.command(SETCOMPINS)
113+
d.bus.command(0x12)
114+
d.bus.command(SETCONTRAST)
159115
if d.vccState == EXTERNALVCC {
160-
d.Command(0x9F)
116+
d.bus.command(0x9F)
161117
} else {
162-
d.Command(0xCF)
118+
d.bus.command(0xCF)
163119
}
164120
} else if d.width == 128 && d.height == 32 { // 128x32
165-
d.Command(SETCOMPINS)
166-
d.Command(0x02)
167-
d.Command(SETCONTRAST)
168-
d.Command(0x8F)
121+
d.bus.command(SETCOMPINS)
122+
d.bus.command(0x02)
123+
d.bus.command(SETCONTRAST)
124+
d.bus.command(0x8F)
169125
} else if d.width == 96 && d.height == 16 { // 96x16
170-
d.Command(SETCOMPINS)
171-
d.Command(0x2)
172-
d.Command(SETCONTRAST)
126+
d.bus.command(SETCOMPINS)
127+
d.bus.command(0x2)
128+
d.bus.command(SETCONTRAST)
173129
if d.vccState == EXTERNALVCC {
174-
d.Command(0x10)
130+
d.bus.command(0x10)
175131
} else {
176-
d.Command(0xAF)
132+
d.bus.command(0xAF)
177133
}
178134
} else {
179135
// fail silently, it might work
180136
println("there's no configuration for this display's size")
181137
}
182138

183-
d.Command(SETPRECHARGE)
139+
d.bus.command(SETPRECHARGE)
184140
if d.vccState == EXTERNALVCC {
185-
d.Command(0x22)
141+
d.bus.command(0x22)
186142
} else {
187-
d.Command(0xF1)
143+
d.bus.command(0xF1)
188144
}
189-
d.Command(SETVCOMDETECT)
190-
d.Command(0x40)
191-
d.Command(DISPLAYALLON_RESUME)
192-
d.Command(NORMALDISPLAY)
193-
d.Command(DEACTIVATE_SCROLL)
194-
d.Command(DISPLAYON)
145+
d.bus.command(SETVCOMDETECT)
146+
d.bus.command(0x40)
147+
d.bus.command(DISPLAYALLON_RESUME)
148+
d.bus.command(NORMALDISPLAY)
149+
d.bus.command(DEACTIVATE_SCROLL)
150+
d.bus.command(DISPLAYON)
195151
}
196152

197153
// ClearBuffer clears the image buffer
198154
func (d *Device) ClearBuffer() {
199-
for i := int16(0); i < d.bufferSize; i++ {
155+
for i := 0; i < len(d.buffer); i++ {
200156
d.buffer[i] = 0
201157
}
202158
}
@@ -214,15 +170,15 @@ func (d *Device) Display() error {
214170
// In the 128x64 (SPI) screen resetting to 0x0 after 128 times corrupt the buffer
215171
// Since we're printing the whole buffer, avoid resetting it in this case
216172
if d.canReset {
217-
d.Command(COLUMNADDR)
218-
d.Command(d.resetCol[0])
219-
d.Command(d.resetCol[1])
220-
d.Command(PAGEADDR)
221-
d.Command(d.resetPage[0])
222-
d.Command(d.resetPage[1])
173+
d.bus.command(COLUMNADDR)
174+
d.bus.command(d.resetCol[0])
175+
d.bus.command(d.resetCol[1])
176+
d.bus.command(PAGEADDR)
177+
d.bus.command(d.resetPage[0])
178+
d.bus.command(d.resetPage[1])
223179
}
224180

225-
return d.Tx(d.buffer, false)
181+
return d.bus.flush()
226182
}
227183

228184
// SetPixel enables or disables a pixel in the buffer
@@ -251,12 +207,10 @@ func (d *Device) GetPixel(x int16, y int16) bool {
251207

252208
// SetBuffer changes the whole buffer at once
253209
func (d *Device) SetBuffer(buffer []byte) error {
254-
if int16(len(buffer)) != d.bufferSize {
210+
if len(buffer) != len(d.buffer) {
255211
return errBufferSize
256212
}
257-
for i := int16(0); i < d.bufferSize; i++ {
258-
d.buffer[i] = buffer[i]
259-
}
213+
copy(d.buffer, buffer)
260214
return nil
261215
}
262216

@@ -265,72 +219,6 @@ func (d *Device) GetBuffer() []byte {
265219
return d.buffer
266220
}
267221

268-
// Command sends a command to the display
269-
func (d *Device) Command(command uint8) {
270-
d.buffer[1] = command // The second byte is the actual command
271-
d.bus.tx(d.buffer[0:2], true)
272-
}
273-
274-
// setAddress sets the address to the I2C bus
275-
func (b *I2CBus) setAddress(address uint16) error {
276-
b.Address = address
277-
return nil
278-
}
279-
280-
// setAddress does nothing, but it's required to avoid reflection
281-
func (b *SPIBus) setAddress(address uint16) error {
282-
// do nothing
283-
println("trying to Configure an address on a SPI device")
284-
return nil
285-
}
286-
287-
// configure does nothing, but it's required to avoid reflection
288-
func (b *I2CBus) configure() error { return nil }
289-
290-
// configure configures some pins with the SPI bus
291-
func (b *SPIBus) configure() error {
292-
b.csPin.Low()
293-
b.dcPin.Low()
294-
b.resetPin.Low()
295-
296-
b.resetPin.High()
297-
time.Sleep(1 * time.Millisecond)
298-
b.resetPin.Low()
299-
time.Sleep(10 * time.Millisecond)
300-
b.resetPin.High()
301-
302-
return nil
303-
}
304-
305-
// Tx sends data to the display
306-
func (d *Device) Tx(data []byte, isCommand bool) error {
307-
return d.bus.tx(data, isCommand)
308-
}
309-
310-
// tx sends data to the display (I2CBus implementation)
311-
func (b *I2CBus) tx(data []byte, isCommand bool) error {
312-
if isCommand {
313-
data[0] = 0x00 // Command mode
314-
} else {
315-
data[0] = 0x40 // Data mode
316-
}
317-
return b.wire.Tx(uint16(b.Address), data, nil)
318-
}
319-
320-
// tx sends data to the display (SPIBus implementation)
321-
func (b *SPIBus) tx(data []byte, isCommand bool) error {
322-
b.csPin.High()
323-
if isCommand {
324-
b.dcPin.Low()
325-
} else {
326-
b.dcPin.High()
327-
}
328-
b.csPin.Low()
329-
err := b.wire.Tx(data[1:], nil) // The first byte is reserved for I2C communcation, strip it
330-
b.csPin.High()
331-
return err
332-
}
333-
334222
// Size returns the current size of the display.
335223
func (d *Device) Size() (w, h int16) {
336224
return d.width, d.height
@@ -362,15 +250,15 @@ func (d *Device) SetRotation(rotation drivers.Rotation) error {
362250
d.rotation = rotation
363251
switch d.rotation {
364252
case drivers.Rotation0:
365-
d.Command(SEGREMAP | 0x1) // Reverse horizontal mapping
366-
d.Command(COMSCANDEC) // Reverse vertical mapping
253+
d.bus.command(SEGREMAP | 0x1) // Reverse horizontal mapping
254+
d.bus.command(COMSCANDEC) // Reverse vertical mapping
367255
case drivers.Rotation180:
368-
d.Command(SEGREMAP) // Normal horizontal mapping
369-
d.Command(COMSCANINC) // Normal vertical mapping
256+
d.bus.command(SEGREMAP) // Normal horizontal mapping
257+
d.bus.command(COMSCANINC) // Normal vertical mapping
370258
// nothing to do
371259
default:
372-
d.Command(SEGREMAP | 0x1) // Reverse horizontal mapping
373-
d.Command(COMSCANDEC) // Reverse vertical mapping
260+
d.bus.command(SEGREMAP | 0x1) // Reverse horizontal mapping
261+
d.bus.command(COMSCANDEC) // Reverse vertical mapping
374262
}
375263
return nil
376264
}
@@ -380,9 +268,9 @@ func (d *Device) SetRotation(rotation drivers.Rotation) error {
380268
// should be kept.
381269
func (d *Device) Sleep(sleepEnabled bool) error {
382270
if sleepEnabled {
383-
d.Command(DISPLAYOFF)
271+
d.bus.command(DISPLAYOFF)
384272
} else {
385-
d.Command(DISPLAYON)
273+
d.bus.command(DISPLAYON)
386274
}
387275
return nil
388276
}

0 commit comments

Comments
 (0)