tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/adafruit4650/device.go (about) 1 // Package adafruit4650 implements a driver for the Adafruit FeatherWing OLED - 128x64 OLED display. 2 // The display is backed itself by a SH1107 driver chip. 3 // 4 // Store: https://www.adafruit.com/product/4650 5 // 6 // Documentation: https://learn.adafruit.com/adafruit-128x64-oled-featherwing 7 package adafruit4650 8 9 import ( 10 "image/color" 11 "time" 12 13 "tinygo.org/x/drivers" 14 ) 15 16 const DefaultAddress = 0x3c 17 18 const ( 19 commandSetLowColumn = 0x00 20 commandSetHighColumn = 0x10 21 commandSetPage = 0xb0 22 ) 23 24 const ( 25 width = 128 26 height = 64 27 ) 28 29 // Device represents an Adafruit 4650 device 30 type Device struct { 31 bus drivers.I2C 32 Address uint8 33 buffer []byte 34 width int16 35 height int16 36 } 37 38 // New creates a new device, not configuring anything yet. 39 func New(bus drivers.I2C) Device { 40 return Device{ 41 bus: bus, 42 Address: DefaultAddress, 43 width: width, 44 height: height, 45 } 46 } 47 48 // Configure initializes the display with default configuration 49 func (d *Device) Configure() error { 50 51 bufferSize := d.width * d.height / 8 52 d.buffer = make([]byte, bufferSize) 53 54 // This sequence is an amalgamation of the datasheet, official Arduino driver, CircuitPython driver and other drivers 55 initSequence := []byte{ 56 0xae, // display off, sleep mode 57 //0xd5, 0x41, // set display clock divider (from original datasheet) 58 0xd5, 0x51, // set display clock divider (from Adafruit driver) 59 0xd9, 0x22, // pre-charge/dis-charge period mode: 2 DCLKs/2 DCLKs (POR) 60 0x20, // memory mode 61 0x81, 0x4f, // contrast setting = 0x4f 62 0xad, 0x8a, // set dc/dc pump 63 0xa0, // segment remap, flip-x 64 0xc0, // common output scan direction 65 0xdc, 0x00, // set display start line 0 (POR=0) 66 0xa8, 0x3f, // multiplex ratio, height - 1 = 0x3f 67 0xd3, 0x60, // set display offset mode = 0x60 68 0xdb, 0x35, // VCOM deselect level = 0.770 (POR) 69 0xa4, // entire display off, retain RAM, normal status (POR) 70 0xa6, // normal (not reversed) display 71 0xaf, // display on 72 } 73 74 err := d.writeCommands(initSequence) 75 if err != nil { 76 return err 77 } 78 79 // recommended in the datasheet, same in other drivers 80 time.Sleep(100 * time.Millisecond) 81 82 return nil 83 } 84 85 // ClearDisplay clears the image buffer as well as the actual display 86 func (d *Device) ClearDisplay() error { 87 d.ClearBuffer() 88 return d.Display() 89 } 90 91 // ClearBuffer clears the buffer 92 func (d *Device) ClearBuffer() { 93 bzero(d.buffer) 94 } 95 96 // SetPixel modifies the internal buffer. Since this display has a bit-depth of 1 bit any non-zero 97 // color component will be treated as 'on', otherwise 'off'. 98 func (d *Device) SetPixel(x int16, y int16, c color.RGBA) { 99 if x < 0 || x >= d.width || y < 0 || y >= d.height { 100 return 101 } 102 103 // RAM layout 104 // *-----> y 105 // | 106 // x| col0 col1 ... col63 107 // v p0 a0 b0 .. 108 // a1 b1 .. 109 // .. .. .. 110 // a7 b7 .. 111 // p1 a0 b0 112 // a1 b1 113 // 114 115 //flip y - so the display orientation matches the silk screen labeling etc. 116 y = d.height - y - 1 117 118 page := x / 8 119 bytesPerPage := d.height 120 byteIndex := y + bytesPerPage*page 121 bit := x % 8 122 if (c.R | c.G | c.B) != 0 { 123 d.buffer[byteIndex] |= 1 << uint8(bit) 124 } else { 125 d.buffer[byteIndex] &^= 1 << uint8(bit) 126 } 127 } 128 129 // Display sends the whole buffer to the screen 130 func (d *Device) Display() error { 131 132 bytesPerPage := d.height 133 134 pages := (d.width + 7) / 8 135 for page := int16(0); page < pages; page++ { 136 137 err := d.setRAMPosition(uint8(page), 0) 138 if err != nil { 139 return err 140 } 141 142 offset := page * bytesPerPage 143 err = d.writeRAM(d.buffer[offset : offset+bytesPerPage]) 144 if err != nil { 145 return err 146 } 147 } 148 149 return nil 150 } 151 152 // setRAMPosition updates the device's current page and column position 153 func (d *Device) setRAMPosition(page uint8, column uint8) error { 154 if page > 15 { 155 panic("page out of bounds") 156 } 157 if column > 127 { 158 panic("column out of bounds") 159 } 160 setPage := commandSetPage | (page & 0xF) 161 162 lo := column & 0xF 163 setLowColumn := commandSetLowColumn | lo 164 165 hi := (column >> 4) & 0x7 166 setHighColumn := commandSetHighColumn | hi 167 168 cmds := []byte{ 169 setPage, 170 setLowColumn, 171 setHighColumn, 172 } 173 174 return d.writeCommands(cmds) 175 } 176 177 // Size returns the current size of the display. 178 func (d *Device) Size() (w, h int16) { 179 return d.width, d.height 180 } 181 182 func (d *Device) writeCommands(commands []byte) error { 183 onlyCommandsFollowing := byte(0x00) 184 return d.bus.Tx(uint16(d.Address), append([]byte{onlyCommandsFollowing}, commands...), nil) 185 } 186 187 func (d *Device) writeRAM(data []byte) error { 188 onlyRAMFollowing := byte(0x40) 189 return d.bus.Tx(uint16(d.Address), append([]byte{onlyRAMFollowing}, data...), nil) 190 } 191 192 func bzero(buf []byte) { 193 for i := range buf { 194 buf[i] = 0 195 } 196 }