tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/ssd1306/ssd1306.go (about) 1 // Package ssd1306 implements a driver for the SSD1306 led matrix controller, it comes in various colors and screen sizes. 2 // 3 // Datasheet: https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf 4 package ssd1306 // import "tinygo.org/x/drivers/ssd1306" 5 6 import ( 7 "errors" 8 "image/color" 9 "machine" 10 "time" 11 12 "tinygo.org/x/drivers" 13 "tinygo.org/x/drivers/internal/legacy" 14 "tinygo.org/x/drivers/pixel" 15 ) 16 17 var ( 18 errBufferSize = errors.New("invalid size buffer") 19 errOutOfRange = errors.New("out of screen range") 20 errNotImplemented = errors.New("not implemented") 21 ) 22 23 type ResetValue [2]byte 24 25 // Device wraps I2C or SPI connection. 26 type Device struct { 27 bus Buser 28 buffer []byte 29 width int16 30 height int16 31 bufferSize int16 32 vccState VccMode 33 canReset bool 34 resetCol ResetValue 35 resetPage ResetValue 36 } 37 38 // Config is the configuration for the display 39 type Config struct { 40 Width int16 41 Height int16 42 VccState VccMode 43 Address uint16 44 // ResetCol and ResetPage are used to reset the screen to 0x0 45 // This is useful for some screens that have a different size than 128x64 46 // For example, the Thumby's screen is 72x40 47 // The default values are normally set automatically based on the size. 48 // If you're using a different size, you might need to set these values manually. 49 ResetCol ResetValue 50 ResetPage ResetValue 51 } 52 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 65 type Buser interface { 66 configure() error 67 tx(data []byte, isCommand bool) error 68 setAddress(address uint16) error 69 } 70 71 type VccMode uint8 72 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 98 // Configure initializes the display with default configuration 99 func (d *Device) Configure(cfg Config) { 100 var zeroReset ResetValue 101 if cfg.Width != 0 { 102 d.width = cfg.Width 103 } else { 104 d.width = 128 105 } 106 if cfg.Height != 0 { 107 d.height = cfg.Height 108 } else { 109 d.height = 64 110 } 111 if cfg.Address != 0 { 112 d.bus.setAddress(cfg.Address) 113 } 114 if cfg.VccState != 0 { 115 d.vccState = cfg.VccState 116 } else { 117 d.vccState = SWITCHCAPVCC 118 } 119 if cfg.ResetCol != zeroReset { 120 d.resetCol = cfg.ResetCol 121 } else { 122 d.resetCol = ResetValue{0, uint8(d.width - 1)} 123 } 124 if cfg.ResetPage != zeroReset { 125 d.resetPage = cfg.ResetPage 126 } else { 127 d.resetPage = ResetValue{0, uint8(d.height/8) - 1} 128 } 129 d.bufferSize = d.width * d.height / 8 130 d.buffer = make([]byte, d.bufferSize) 131 d.canReset = cfg.Address != 0 || d.width != 128 || d.height != 64 // I2C or not 128x64 132 133 d.bus.configure() 134 135 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) 145 if d.vccState == EXTERNALVCC { 146 d.Command(0x10) 147 } else { 148 d.Command(0x14) 149 } 150 d.Command(MEMORYMODE) 151 d.Command(0x00) 152 d.Command(SEGREMAP | 0x1) 153 d.Command(COMSCANDEC) 154 155 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) 159 if d.vccState == EXTERNALVCC { 160 d.Command(0x9F) 161 } else { 162 d.Command(0xCF) 163 } 164 } 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) 169 } else if d.width == 96 && d.height == 16 { // 96x16 170 d.Command(SETCOMPINS) 171 d.Command(0x2) 172 d.Command(SETCONTRAST) 173 if d.vccState == EXTERNALVCC { 174 d.Command(0x10) 175 } else { 176 d.Command(0xAF) 177 } 178 } else { 179 // fail silently, it might work 180 println("there's no configuration for this display's size") 181 } 182 183 d.Command(SETPRECHARGE) 184 if d.vccState == EXTERNALVCC { 185 d.Command(0x22) 186 } else { 187 d.Command(0xF1) 188 } 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) 195 } 196 197 // ClearBuffer clears the image buffer 198 func (d *Device) ClearBuffer() { 199 for i := int16(0); i < d.bufferSize; i++ { 200 d.buffer[i] = 0 201 } 202 } 203 204 // ClearDisplay clears the image buffer and clear the display 205 func (d *Device) ClearDisplay() { 206 d.ClearBuffer() 207 d.Display() 208 } 209 210 // Display sends the whole buffer to the screen 211 func (d *Device) Display() error { 212 // Reset the screen to 0x0 213 // This works fine with I2C 214 // In the 128x64 (SPI) screen resetting to 0x0 after 128 times corrupt the buffer 215 // Since we're printing the whole buffer, avoid resetting it in this case 216 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]) 223 } 224 225 return d.Tx(d.buffer, false) 226 } 227 228 // SetPixel enables or disables a pixel in the buffer 229 // color.RGBA{0, 0, 0, 255} is consider transparent, anything else 230 // with enable a pixel on the screen 231 func (d *Device) SetPixel(x int16, y int16, c color.RGBA) { 232 if x < 0 || x >= d.width || y < 0 || y >= d.height { 233 return 234 } 235 byteIndex := x + (y/8)*d.width 236 if c.R != 0 || c.G != 0 || c.B != 0 { 237 d.buffer[byteIndex] |= 1 << uint8(y%8) 238 } else { 239 d.buffer[byteIndex] &^= 1 << uint8(y%8) 240 } 241 } 242 243 // GetPixel returns if the specified pixel is on (true) or off (false) 244 func (d *Device) GetPixel(x int16, y int16) bool { 245 if x < 0 || x >= d.width || y < 0 || y >= d.height { 246 return false 247 } 248 byteIndex := x + (y/8)*d.width 249 return (d.buffer[byteIndex] >> uint8(y%8) & 0x1) == 1 250 } 251 252 // SetBuffer changes the whole buffer at once 253 func (d *Device) SetBuffer(buffer []byte) error { 254 if int16(len(buffer)) != d.bufferSize { 255 return errBufferSize 256 } 257 for i := int16(0); i < d.bufferSize; i++ { 258 d.buffer[i] = buffer[i] 259 } 260 return nil 261 } 262 263 // GetBuffer returns the whole buffer 264 func (d *Device) GetBuffer() []byte { 265 return d.buffer 266 } 267 268 // Command sends a command to the display 269 func (d *Device) Command(command uint8) { 270 d.bus.tx([]byte{command}, true) 271 } 272 273 // setAddress sets the address to the I2C bus 274 func (b *I2CBus) setAddress(address uint16) error { 275 b.Address = address 276 return nil 277 } 278 279 // setAddress does nothing, but it's required to avoid reflection 280 func (b *SPIBus) setAddress(address uint16) error { 281 // do nothing 282 println("trying to Configure an address on a SPI device") 283 return nil 284 } 285 286 // configure does nothing, but it's required to avoid reflection 287 func (b *I2CBus) configure() error { return nil } 288 289 // configure configures some pins with the SPI bus 290 func (b *SPIBus) configure() error { 291 b.csPin.Low() 292 b.dcPin.Low() 293 b.resetPin.Low() 294 295 b.resetPin.High() 296 time.Sleep(1 * time.Millisecond) 297 b.resetPin.Low() 298 time.Sleep(10 * time.Millisecond) 299 b.resetPin.High() 300 301 return nil 302 } 303 304 // Tx sends data to the display 305 func (d *Device) Tx(data []byte, isCommand bool) error { 306 return d.bus.tx(data, isCommand) 307 } 308 309 // tx sends data to the display (I2CBus implementation) 310 func (b *I2CBus) tx(data []byte, isCommand bool) error { 311 if isCommand { 312 return legacy.WriteRegister(b.wire, uint8(b.Address), 0x00, data) 313 } else { 314 return legacy.WriteRegister(b.wire, uint8(b.Address), 0x40, data) 315 } 316 } 317 318 // tx sends data to the display (SPIBus implementation) 319 func (b *SPIBus) tx(data []byte, isCommand bool) error { 320 var err error 321 322 if isCommand { 323 b.csPin.High() 324 time.Sleep(1 * time.Millisecond) 325 b.dcPin.Low() 326 b.csPin.Low() 327 328 err = b.wire.Tx(data, nil) 329 b.csPin.High() 330 } else { 331 b.csPin.High() 332 time.Sleep(1 * time.Millisecond) 333 b.dcPin.High() 334 b.csPin.Low() 335 336 err = b.wire.Tx(data, nil) 337 b.csPin.High() 338 } 339 340 return err 341 } 342 343 // Size returns the current size of the display. 344 func (d *Device) Size() (w, h int16) { 345 return d.width, d.height 346 } 347 348 // DrawBitmap copies the bitmap to the screen at the given coordinates. 349 func (d *Device) DrawBitmap(x, y int16, bitmap pixel.Image[pixel.Monochrome]) error { 350 width, height := bitmap.Size() 351 if x < 0 || x+int16(width) > d.width || y < 0 || y+int16(height) > d.height { 352 return errOutOfRange 353 } 354 355 for i := 0; i < width; i++ { 356 for j := 0; j < height; j++ { 357 d.SetPixel(x+int16(i), y+int16(j), bitmap.Get(i, j).RGBA()) 358 } 359 } 360 361 return nil 362 } 363 364 // Rotation returns the currently configured rotation. 365 func (d *Device) Rotation() drivers.Rotation { 366 return drivers.Rotation0 367 } 368 369 // SetRotation changes the rotation of the device (clock-wise). 370 // Would have to be implemented in software for this device. 371 func (d *Device) SetRotation(rotation drivers.Rotation) error { 372 return errNotImplemented 373 } 374 375 // Set the sleep mode for this display. When sleeping, the panel uses a lot 376 // less power. The display won't show an image anymore, but the memory contents 377 // should be kept. 378 func (d *Device) Sleep(sleepEnabled bool) error { 379 if sleepEnabled { 380 d.Command(DISPLAYOFF) 381 } else { 382 d.Command(DISPLAYON) 383 } 384 return nil 385 }