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  }