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  }