// Package uc8151 implements a driver for e-ink displays controlled by UC8151 // // Inspired by https://github.com/pimoroni/pimoroni-pico/blob/main/drivers/uc8151/uc8151.cpp // Datasheet: https://www.buydisplay.com/download/ic/UC8151C.pdf package uc8151 // import "tinygo.org/x/drivers/uc8151" import ( "errors" "image/color" "machine" "time" "tinygo.org/x/drivers" ) type Config struct { Width int16 Height int16 Rotation Rotation // Rotation is clock-wise Speed Speed // Value from DEFAULT, MEDIUM, FAST, TURBO Blocking bool } type Device struct { bus drivers.SPI cs machine.Pin dc machine.Pin rst machine.Pin busy machine.Pin width int16 height int16 buffer []uint8 bufferLength uint32 rotation Rotation speed Speed blocking bool } type Rotation uint8 type Speed uint8 // New returns a new epd2in13x driver. Pass in a fully configured SPI bus. func New(bus drivers.SPI, csPin, dcPin, rstPin, busyPin machine.Pin) Device { csPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) rstPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) busyPin.Configure(machine.PinConfig{Mode: machine.PinInput}) return Device{ bus: bus, cs: csPin, dc: dcPin, rst: rstPin, busy: busyPin, } } // Configure sets up the device. func (d *Device) Configure(cfg Config) { if cfg.Width != 0 { d.width = cfg.Width } else { d.width = EPD_WIDTH } if cfg.Height != 0 { d.height = cfg.Height } else { d.height = EPD_HEIGHT } d.rotation = cfg.Rotation d.speed = cfg.Speed d.blocking = cfg.Blocking d.bufferLength = (uint32(d.width) * uint32(d.height)) / 8 d.buffer = make([]uint8, d.bufferLength) for i := uint32(0); i < d.bufferLength; i++ { d.buffer[i] = 0xFF } d.Reset() d.SendCommand(PSR) if d.speed == 0 { d.SendData(RES_128x296 | LUT_OTP | FORMAT_BW | SHIFT_RIGHT | BOOSTER_ON | RESET_NONE | SCAN_UP) } else { d.SendData(RES_128x296 | LUT_REG | FORMAT_BW | SHIFT_RIGHT | BOOSTER_ON | RESET_NONE | SCAN_UP) } d.SetLUT(d.speed) d.SendCommand(PWR) d.SendData(VDS_INTERNAL | VDG_INTERNAL) d.SendData(VCOM_VG | VGHL_16V) d.SendData(0x2B) d.SendData(0x2B) d.SendData(0x2B) d.SendCommand(PON) d.WaitUntilIdle() d.SendCommand(BTST) d.SendData(START_10MS | STRENGTH_3 | OFF_6_58US) d.SendData(START_10MS | STRENGTH_3 | OFF_6_58US) d.SendData(START_10MS | STRENGTH_3 | OFF_6_58US) d.SendCommand(PFS) d.SendData(FRAMES_1) d.SendCommand(TSE) d.SendData(TEMP_INTERNAL | OFFSET_0) d.SendCommand(TCON) d.SendData(0x22) d.SendCommand(CDI) d.SendData(0x4C) // 4C //5C d.SendCommand(PLL) d.SendData(HZ_100) d.SendCommand(POF) d.WaitUntilIdle() } // Reset resets the device func (d *Device) Reset() { d.rst.Low() time.Sleep(10 * time.Millisecond) d.rst.High() time.Sleep(10 * time.Millisecond) d.WaitUntilIdle() } // PowerOff power off the device func (d *Device) PowerOff() { d.SendCommand(POF) } // SendCommand sends a command to the display func (d *Device) SendCommand(command uint8) { d.sendDataCommand(true, command) } // SendData sends a data byte to the display func (d *Device) SendData(data uint8) { d.sendDataCommand(false, data) } // sendDataCommand sends image data or a command to the screen func (d *Device) sendDataCommand(isCommand bool, data uint8) { if isCommand { d.dc.Low() } else { d.dc.High() } d.cs.Low() d.bus.Transfer(data) d.cs.High() } // SetPixel modifies the internal buffer in a single pixel. // The display have 2 colors: black and white // We use RGBA(0,0,0, 255) as white (transparent) // Anything else as black func (d *Device) SetPixel(x int16, y int16, c color.RGBA) { x, y = d.xy(x, y) if x < 0 || x >= d.width || y < 0 || y >= d.height { return } byteIndex := x/8 + y*(d.width/8) if c.R == 0 && c.G == 0 && c.B == 0 { // TRANSPARENT / WHITE d.buffer[byteIndex] &^= 0x80 >> uint8(x%8) } else { // WHITE / EMPTY d.buffer[byteIndex] |= 0x80 >> uint8(x%8) } } // Display sends the buffer to the screen. func (d *Device) Display() error { if d.blocking { d.WaitUntilIdle() } d.SendCommand(PON) d.SendCommand(PTOU) d.SendCommand(DTM2) for i := uint32(0); i < d.bufferLength; i++ { d.SendData(d.buffer[i]) } d.SendCommand(DSP) d.SendCommand(DRF) if d.blocking { d.WaitUntilIdle() d.PowerOff() } return nil } // DisplayRect sends only an area of the buffer to the screen. // The rectangle points need to be a multiple of 8 in the screen. // They might not work as expected if the screen is rotated. func (d *Device) DisplayRect(x int16, y int16, width int16, height int16) error { if d.blocking { d.WaitUntilIdle() } x, y = d.xy(x, y) if x < 0 || y < 0 || x >= d.width || y >= d.height || width < 0 || height < 0 { return errors.New("wrong rectangle") } if d.rotation == ROTATION_90 { width, height = height, width x -= width } else if d.rotation == ROTATION_180 { x -= width - 1 y -= height - 1 } else if d.rotation == ROTATION_270 { width, height = height, width y -= height } x &= 0xF8 width &= 0xF8 width = x + width // reuse variables if width >= d.width { width = d.width } height = y + height if height > d.height { height = d.height } d.SendCommand(PON) d.SendCommand(PTIN) d.SendCommand(PTL) d.SendData(uint8(x)) d.SendData(uint8(x+width-1) | 0x07) d.SendData(uint8(y >> 8)) d.SendData(uint8(y)) d.SendData(uint8((y + height - 1) >> 8)) d.SendData(uint8(y + height - 1)) d.SendData(0x01) d.SendCommand(DTM2) x = x / 8 width = width / 8 for ; y < height; y++ { for i := x; i < width; i++ { d.SendData(d.buffer[i+y*(d.width/8)]) } } d.SendCommand(DSP) d.SendCommand(DRF) if d.blocking { d.WaitUntilIdle() d.PowerOff() } return nil } // ClearDisplay erases the device SRAM func (d *Device) ClearDisplay() { d.ClearBuffer() d.Display() } // WaitUntilIdle waits until the display is ready func (d *Device) WaitUntilIdle() { for !d.busy.Get() { time.Sleep(100 * time.Millisecond) } } // IsBusy returns the busy status of the display func (d *Device) IsBusy() bool { return d.busy.Get() } // ClearBuffer sets the buffer to 0xFF (white) func (d *Device) ClearBuffer() { for i := uint32(0); i < d.bufferLength; i++ { d.buffer[i] = 0x00 } } // Size returns the current size of the display. func (d *Device) Size() (w, h int16) { if d.rotation == ROTATION_90 || d.rotation == ROTATION_270 { return d.height, d.width } return d.width, d.height } // SetRotation changes the rotation (clock-wise) of the device func (d *Device) SetRotation(rotation Rotation) { d.rotation = rotation } // SetBlocking changes the blocking flag of the device func (d *Device) SetBlocking(blocking bool) { d.blocking = blocking } // xy chages the coordinates according to the rotation func (d *Device) xy(x, y int16) (int16, int16) { switch d.rotation { case NO_ROTATION: return x, y case ROTATION_90: return d.width - y - 1, x case ROTATION_180: return d.width - x - 1, d.height - y - 1 case ROTATION_270: return y, d.height - x - 1 } return x, y } // SetSpeed changes the refresh speed of the device (the display needs to re-configure) func (d *Device) SetSpeed(speed Speed) { d.Configure(Config{ Width: d.width, Height: d.height, Rotation: d.rotation, Speed: speed, Blocking: d.blocking, }) } // Invert sets the display' invert mode func (d *Device) Invert(invert bool) { if invert { d.SendData(0x5C) } else { d.SendData(0x4C) } } // SetLUT sets the look up tables for full or partial updates func (d *Device) SetLUT(speed Speed) { switch speed { case MEDIUM: var lut = [44]uint8{ 0x00, 0x16, 0x16, 0x0d, 0x00, 0x01, 0x00, 0x23, 0x23, 0x00, 0x00, 0x02, 0x00, 0x16, 0x16, 0x0d, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, } d.SendCommand(LUT_VCOM) for i := 0; i < 44; i++ { d.SendData(lut[i]) } lut = [44]uint8{ 0x54, 0x16, 0x16, 0x0d, 0x00, 0x01, 0x60, 0x23, 0x23, 0x00, 0x00, 0x02, 0xa8, 0x16, 0x16, 0x0d, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} d.SendCommand(LUT_WW) // do not send last two bytes for i := 0; i < 42; i++ { d.SendData(lut[i]) } lut = [44]uint8{ 0x54, 0x16, 0x16, 0x0d, 0x00, 0x01, 0x60, 0x23, 0x23, 0x00, 0x00, 0x02, 0xa8, 0x16, 0x16, 0x0d, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} d.SendCommand(LUT_BW) // do not send last two bytes for i := 0; i < 42; i++ { d.SendData(lut[i]) } lut = [44]uint8{ 0xa8, 0x16, 0x16, 0x0d, 0x00, 0x01, 0x60, 0x23, 0x23, 0x00, 0x00, 0x02, 0x54, 0x16, 0x16, 0x0d, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} d.SendCommand(LUT_WB) // do not send last two bytes for i := 0; i < 42; i++ { d.SendData(lut[i]) } lut = [44]uint8{ 0xa8, 0x16, 0x16, 0x0d, 0x00, 0x01, 0x60, 0x23, 0x23, 0x00, 0x00, 0x02, 0x54, 0x16, 0x16, 0x0d, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} d.SendCommand(LUT_BB) // do not send last two bytes for i := 0; i < 42; i++ { d.SendData(lut[i]) } break case FAST: var lut = [44]uint8{ 0x00, 0x04, 0x04, 0x07, 0x00, 0x01, 0x00, 0x0c, 0x0c, 0x00, 0x00, 0x02, 0x00, 0x04, 0x04, 0x07, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, } d.SendCommand(LUT_VCOM) for i := 0; i < 44; i++ { d.SendData(lut[i]) } lut = [44]uint8{ 0x54, 0x04, 0x04, 0x07, 0x00, 0x01, 0x60, 0x0c, 0x0c, 0x00, 0x00, 0x02, 0xa8, 0x04, 0x04, 0x07, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} d.SendCommand(LUT_WW) // do not send last two bytes for i := 0; i < 42; i++ { d.SendData(lut[i]) } lut = [44]uint8{ 0x54, 0x04, 0x04, 0x07, 0x00, 0x01, 0x60, 0x0c, 0x0c, 0x00, 0x00, 0x02, 0xa8, 0x04, 0x04, 0x07, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} d.SendCommand(LUT_BW) // do not send last two bytes for i := 0; i < 42; i++ { d.SendData(lut[i]) } lut = [44]uint8{ 0xa8, 0x04, 0x04, 0x07, 0x00, 0x01, 0x60, 0x0c, 0x0c, 0x00, 0x00, 0x02, 0x54, 0x04, 0x04, 0x07, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} d.SendCommand(LUT_WB) // do not send last two bytes for i := 0; i < 42; i++ { d.SendData(lut[i]) } lut = [44]uint8{ 0xa8, 0x04, 0x04, 0x07, 0x00, 0x01, 0x60, 0x0c, 0x0c, 0x00, 0x00, 0x02, 0x54, 0x04, 0x04, 0x07, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} d.SendCommand(LUT_BB) // do not send last two bytes for i := 0; i < 42; i++ { d.SendData(lut[i]) } break case TURBO: var lut = [44]uint8{ 0x00, 0x01, 0x01, 0x02, 0x00, 0x01, 0x00, 0x02, 0x02, 0x00, 0x00, 0x02, 0x00, 0x02, 0x02, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, } d.SendCommand(LUT_VCOM) for i := 0; i < 44; i++ { d.SendData(lut[i]) } lut = [44]uint8{ 0x54, 0x01, 0x01, 0x02, 0x00, 0x01, 0x60, 0x02, 0x02, 0x00, 0x00, 0x02, 0xa8, 0x02, 0x02, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} d.SendCommand(LUT_WW) // do not send last two bytes for i := 0; i < 42; i++ { d.SendData(lut[i]) } lut = [44]uint8{ 0x54, 0x01, 0x01, 0x02, 0x00, 0x01, 0x60, 0x02, 0x02, 0x00, 0x00, 0x02, 0xa8, 0x02, 0x02, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} d.SendCommand(LUT_BW) // do not send last two bytes for i := 0; i < 42; i++ { d.SendData(lut[i]) } lut = [44]uint8{ 0xa8, 0x01, 0x01, 0x02, 0x00, 0x01, 0x60, 0x02, 0x02, 0x00, 0x00, 0x02, 0x54, 0x02, 0x02, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} d.SendCommand(LUT_WB) // do not send last two bytes for i := 0; i < 42; i++ { d.SendData(lut[i]) } lut = [44]uint8{ 0xa8, 0x01, 0x01, 0x02, 0x00, 0x01, 0x60, 0x02, 0x02, 0x00, 0x00, 0x02, 0x54, 0x02, 0x02, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} d.SendCommand(LUT_BB) // do not send last two bytes for i := 0; i < 42; i++ { d.SendData(lut[i]) } break default: var lut = [44]uint8{ 0x00, 0x64, 0x64, 0x37, 0x00, 0x01, 0x00, 0x8c, 0x8c, 0x00, 0x00, 0x04, 0x00, 0x64, 0x64, 0x37, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, } d.SendCommand(LUT_VCOM) for i := 0; i < 44; i++ { d.SendData(lut[i]) } lut = [44]uint8{ 0x54, 0x64, 0x64, 0x37, 0x00, 0x01, 0x60, 0x8c, 0x8c, 0x00, 0x00, 0x04, 0xa8, 0x64, 0x64, 0x37, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} d.SendCommand(LUT_WW) // do not send last two bytes for i := 0; i < 42; i++ { d.SendData(lut[i]) } lut = [44]uint8{ 0x54, 0x64, 0x64, 0x37, 0x00, 0x01, 0x60, 0x8c, 0x8c, 0x00, 0x00, 0x04, 0xa8, 0x64, 0x64, 0x37, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} d.SendCommand(LUT_BW) // do not send last two bytes for i := 0; i < 42; i++ { d.SendData(lut[i]) } lut = [44]uint8{ 0xa8, 0x64, 0x64, 0x37, 0x00, 0x01, 0x60, 0x8c, 0x8c, 0x00, 0x00, 0x04, 0x54, 0x64, 0x64, 0x37, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} d.SendCommand(LUT_WB) // do not send last two bytes for i := 0; i < 42; i++ { d.SendData(lut[i]) } lut = [44]uint8{ 0xa8, 0x64, 0x64, 0x37, 0x00, 0x01, 0x60, 0x8c, 0x8c, 0x00, 0x00, 0x04, 0x54, 0x64, 0x64, 0x37, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} d.SendCommand(LUT_BB) // do not send last two bytes for i := 0; i < 42; i++ { d.SendData(lut[i]) } break } }