tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/sh1106/sh1106.go (about) 1 // Package sh1106 implements a driver for the SH1106 display controller 2 // 3 // Copied from https://github.com/toyo/tinygo-sh1106 (under BSD 3-clause license) 4 package sh1106 // import "tinygo.org/x/drivers/sh1106" 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 ) 15 16 // Device wraps an SPI connection. 17 type Device struct { 18 bus Buser 19 buffer []byte 20 cmdbuf [1]byte 21 width int16 22 height int16 23 bufferSize int16 24 vccState VccMode 25 } 26 27 // Config is the configuration for the display 28 type Config struct { 29 Width int16 30 Height int16 31 VccState VccMode 32 Address uint16 33 } 34 35 type I2CBus struct { 36 wire drivers.I2C 37 Address uint16 38 } 39 40 type SPIBus struct { 41 wire drivers.SPI 42 dcPin machine.Pin 43 resetPin machine.Pin 44 csPin machine.Pin 45 } 46 47 type Buser interface { 48 configure() 49 tx(data []byte, isCommand bool) 50 setAddress(address uint16) 51 } 52 53 type VccMode uint8 54 55 // NewI2C creates a new SH1106 connection. The I2C wire must already be configured. 56 func NewI2C(bus drivers.I2C) Device { 57 return Device{ 58 bus: &I2CBus{ 59 wire: bus, 60 Address: Address, 61 }, 62 } 63 } 64 65 // NewSPI creates a new SH1106 connection. The SPI wire must already be configured. 66 func NewSPI(bus drivers.SPI, dcPin, resetPin, csPin machine.Pin) Device { 67 dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) 68 resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) 69 csPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) 70 return Device{ 71 bus: &SPIBus{ 72 wire: bus, 73 dcPin: dcPin, 74 resetPin: resetPin, 75 csPin: csPin, 76 }, 77 } 78 } 79 80 // Configure initializes the display with default configuration 81 func (d *Device) Configure(cfg Config) { 82 if cfg.Width != 0 { 83 d.width = cfg.Width 84 } else { 85 d.width = 128 86 } 87 if cfg.Height != 0 { 88 d.height = cfg.Height 89 } else { 90 d.height = 64 91 } 92 if cfg.Address != 0 { 93 d.bus.setAddress(cfg.Address) 94 } 95 if cfg.VccState != 0 { 96 d.vccState = cfg.VccState 97 } else { 98 d.vccState = SWITCHCAPVCC 99 } 100 d.bufferSize = d.width * d.height / 8 101 d.buffer = make([]byte, d.bufferSize) 102 103 d.bus.configure() 104 105 // busyWaitDelay(100 * time.Nanosecond) 106 time.Sleep(100 * time.Nanosecond) 107 d.Command(DISPLAYOFF) 108 d.Command(SETDISPLAYCLOCKDIV) 109 d.Command(0x80) 110 d.Command(SETMULTIPLEX) 111 d.Command(uint8(d.height - 1)) 112 d.Command(SETDISPLAYOFFSET) 113 d.Command(0x0) 114 d.Command(SETSTARTLINE | 0x0) 115 d.Command(CHARGEPUMP) 116 if d.vccState == EXTERNALVCC { 117 d.Command(0x10) 118 } else { 119 d.Command(0x14) 120 } 121 d.Command(MEMORYMODE) 122 d.Command(0x00) 123 d.Command(SEGREMAP | 0x1) 124 d.Command(COMSCANDEC) 125 126 if (d.width == 128 && d.height == 64) || (d.width == 64 && d.height == 48) { // 128x64 or 64x48 127 d.Command(SETCOMPINS) 128 d.Command(0x12) 129 d.Command(SETCONTRAST) 130 if d.vccState == EXTERNALVCC { 131 d.Command(0x9F) 132 } else { 133 d.Command(0xCF) 134 } 135 } else if d.width == 128 && d.height == 32 { // 128x32 136 d.Command(SETCOMPINS) 137 d.Command(0x02) 138 d.Command(SETCONTRAST) 139 d.Command(0x8F) 140 } else if d.width == 96 && d.height == 16 { // 96x16 141 d.Command(SETCOMPINS) 142 d.Command(0x2) 143 d.Command(SETCONTRAST) 144 if d.vccState == EXTERNALVCC { 145 d.Command(0x10) 146 } else { 147 d.Command(0xAF) 148 } 149 } else { 150 // fail silently, it might work 151 println("there's no configuration for this display's size") 152 } 153 154 d.Command(SETPRECHARGE) 155 if d.vccState == EXTERNALVCC { 156 d.Command(0x22) 157 } else { 158 d.Command(0xF1) 159 } 160 d.Command(SETVCOMDETECT) 161 d.Command(0x40) 162 d.Command(DISPLAYALLON_RESUME) 163 d.Command(NORMALDISPLAY) 164 d.Command(DEACTIVATE_SCROLL) 165 d.Command(DISPLAYON) 166 } 167 168 // ClearBuffer clears the image buffer 169 func (d *Device) ClearBuffer() { 170 for i := int16(0); i < d.bufferSize; i++ { 171 d.buffer[i] = 0 172 } 173 } 174 175 // ClearDisplay clears the image buffer and clear the display 176 func (d *Device) ClearDisplay() { 177 d.ClearBuffer() 178 d.Display() 179 } 180 181 // Display sends the whole buffer to the screen 182 func (d *Device) Display() error { 183 // In the 128x64 (SPI) screen resetting to 0x0 after 128 times corrupt the buffer 184 // Since we're printing the whole buffer, avoid resetting it 185 if d.width != 128 || d.height != 64 { 186 d.Command(COLUMNADDR) 187 d.Command(0) 188 d.Command(uint8(d.width - 1)) 189 d.Command(PAGEADDR) 190 d.Command(0) 191 d.Command(uint8(d.height/8) - 1) 192 } 193 194 for pg := uint8(0); pg < uint8(d.height/8); pg++ { 195 d.Command(0xB0 | (pg & 0x07)) // SET_PAGE_ADDR 196 d.Command(SETLOWCOLUMN | 2) 197 d.Command(SETHIGHCOLUMN | 0) 198 d.Tx(d.buffer[uint16(pg)*0x80:uint16(pg+1)*0x80], false) 199 } 200 201 return nil 202 } 203 204 // SetPixel enables or disables a pixel in the buffer 205 // color.RGBA{0, 0, 0, 255} is consider transparent, anything else 206 // with enable a pixel on the screen 207 func (d *Device) SetPixel(x int16, y int16, c color.RGBA) { 208 if x < 0 || x >= d.width || y < 0 || y >= d.height { 209 return 210 } 211 byteIndex := x + (y/8)*d.width 212 if c.R != 0 || c.G != 0 || c.B != 0 { 213 d.buffer[byteIndex] |= 1 << uint8(y%8) 214 } else { 215 d.buffer[byteIndex] &^= 1 << uint8(y%8) 216 } 217 } 218 219 // GetPixel returns if the specified pixel is on (true) or off (false) 220 func (d *Device) GetPixel(x int16, y int16) bool { 221 if x < 0 || x >= d.width || y < 0 || y >= d.height { 222 return false 223 } 224 byteIndex := x + (y/8)*d.width 225 return (d.buffer[byteIndex] >> uint8(y%8) & 0x1) == 1 226 } 227 228 // SetBuffer changes the whole buffer at once 229 func (d *Device) SetBuffer(buffer []byte) error { 230 if int16(len(buffer)) != d.bufferSize { 231 //return ErrBuffer 232 return errors.New("wrong size buffer") 233 } 234 for i := int16(0); i < d.bufferSize; i++ { 235 d.buffer[i] = buffer[i] 236 } 237 return nil 238 } 239 240 func (d *Device) SetScroll(line int16) { 241 d.Command(SETSTARTLINE + uint8(line&0b111111)) 242 } 243 244 // Command sends a command to the display 245 func (d *Device) Command(command uint8) { 246 d.cmdbuf[0] = command 247 d.bus.tx(d.cmdbuf[:], true) 248 } 249 250 // setAddress sets the address to the I2C bus 251 func (b *I2CBus) setAddress(address uint16) { 252 b.Address = address 253 } 254 255 // setAddress does nothing, but it's required to avoid reflection 256 func (b *SPIBus) setAddress(address uint16) { 257 // do nothing 258 println("trying to Configure an address on a SPI device") 259 } 260 261 // configure does nothing, but it's required to avoid reflection 262 func (b *I2CBus) configure() {} 263 264 // configure configures some pins with the SPI bus 265 func (b *SPIBus) configure() { 266 b.csPin.Low() 267 b.dcPin.Low() 268 b.resetPin.Low() 269 270 b.resetPin.High() 271 // busyWaitDelay(time.Millisecond) 272 time.Sleep(1 * time.Millisecond) 273 b.resetPin.Low() 274 // busyWaitDelay(10 * time.Millisecond) 275 time.Sleep(10 * time.Millisecond) 276 b.resetPin.High() 277 } 278 279 // Tx sends data to the display 280 func (d *Device) Tx(data []byte, isCommand bool) { 281 d.bus.tx(data, isCommand) 282 } 283 284 // tx sends data to the display (I2CBus implementation) 285 func (b *I2CBus) tx(data []byte, isCommand bool) { 286 if isCommand { 287 legacy.WriteRegister(b.wire, uint8(b.Address), 0x00, data) 288 } else { 289 legacy.WriteRegister(b.wire, uint8(b.Address), 0x40, data) 290 } 291 } 292 293 // tx sends data to the display (SPIBus implementation) 294 func (b *SPIBus) tx(data []byte, isCommand bool) { 295 if isCommand { 296 b.csPin.High() 297 riseTimeDelay() 298 b.dcPin.Low() 299 b.csPin.Low() 300 301 b.wire.Tx(data, nil) 302 b.csPin.High() 303 } else { 304 b.csPin.High() 305 riseTimeDelay() 306 b.dcPin.High() 307 b.csPin.Low() 308 309 b.wire.Tx(data, nil) 310 b.csPin.High() 311 } 312 } 313 314 // Size returns the current size of the display. 315 func (d *Device) Size() (w, h int16) { 316 return d.width, d.height 317 } 318 319 // TODO: is this really necessary? seems to work fine without this on macropad-rp2040 at least 320 func riseTimeDelay() { 321 busyWaitDelay(1 * time.Microsecond) 322 } 323 324 func busyWaitDelay(duration time.Duration) { 325 for start := time.Now(); time.Since(start) < duration; { 326 } 327 }