mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-21 02:02:23 +00:00
227 lines
6.5 KiB
C++
227 lines
6.5 KiB
C++
#include "./SSD16XX.h"
|
|
|
|
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
|
using namespace NicheGraphics::Drivers;
|
|
|
|
SSD16XX::SSD16XX(uint16_t width, uint16_t height, UpdateTypes supported, uint8_t bufferOffsetX)
|
|
: EInk(width, height, supported), bufferOffsetX(bufferOffsetX)
|
|
{
|
|
// Pre-calculate size of the image buffer, for convenience
|
|
|
|
// Determine the X dimension of the image buffer, in bytes.
|
|
// Along rows, pixels are stored 8 per byte.
|
|
// Not all display widths are divisible by 8. Need to make sure bytecount accommodates padding for these.
|
|
bufferRowSize = ((width - 1) / 8) + 1;
|
|
|
|
// Total size of image buffer, in bytes.
|
|
bufferSize = bufferRowSize * height;
|
|
}
|
|
|
|
void SSD16XX::begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t pin_busy, uint8_t pin_rst)
|
|
{
|
|
this->spi = spi;
|
|
this->pin_dc = pin_dc;
|
|
this->pin_cs = pin_cs;
|
|
this->pin_busy = pin_busy;
|
|
this->pin_rst = pin_rst;
|
|
|
|
pinMode(pin_dc, OUTPUT);
|
|
pinMode(pin_cs, OUTPUT);
|
|
pinMode(pin_busy, INPUT);
|
|
|
|
// If using a reset pin, hold high
|
|
// Reset is active low for solmon systech ICs
|
|
if (pin_rst != 0xFF)
|
|
pinMode(pin_rst, INPUT_PULLUP);
|
|
|
|
reset();
|
|
}
|
|
|
|
void SSD16XX::wait()
|
|
{
|
|
// Busy when HIGH
|
|
while (digitalRead(pin_busy) == HIGH)
|
|
yield();
|
|
}
|
|
|
|
void SSD16XX::reset()
|
|
{
|
|
// Check if reset pin is defined
|
|
if (pin_rst != 0xFF) {
|
|
pinMode(pin_rst, OUTPUT);
|
|
digitalWrite(pin_rst, LOW);
|
|
delay(50);
|
|
pinMode(pin_rst, INPUT_PULLUP);
|
|
wait();
|
|
}
|
|
|
|
sendCommand(0x12);
|
|
wait();
|
|
}
|
|
|
|
void SSD16XX::sendCommand(const uint8_t command)
|
|
{
|
|
spi->beginTransaction(spiSettings);
|
|
digitalWrite(pin_dc, LOW); // DC pin low indicates command
|
|
digitalWrite(pin_cs, LOW);
|
|
spi->transfer(command);
|
|
digitalWrite(pin_cs, HIGH);
|
|
digitalWrite(pin_dc, HIGH);
|
|
spi->endTransaction();
|
|
}
|
|
|
|
void SSD16XX::sendData(uint8_t data)
|
|
{
|
|
// spi->beginTransaction(spiSettings);
|
|
// digitalWrite(pin_dc, HIGH); // DC pin HIGH indicates data, instead of command
|
|
// digitalWrite(pin_cs, LOW);
|
|
// spi->transfer(data);
|
|
// digitalWrite(pin_cs, HIGH);
|
|
// digitalWrite(pin_dc, HIGH);
|
|
// spi->endTransaction();
|
|
sendData(&data, 1);
|
|
}
|
|
|
|
void SSD16XX::sendData(const uint8_t *data, uint32_t size)
|
|
{
|
|
spi->beginTransaction(spiSettings);
|
|
digitalWrite(pin_dc, HIGH); // DC pin HIGH indicates data, instead of command
|
|
digitalWrite(pin_cs, LOW);
|
|
|
|
// Platform-specific SPI command
|
|
#if defined(ARCH_ESP32)
|
|
spi->transferBytes(data, NULL, size); // NULL for a "write only" transfer
|
|
#elif defined(ARCH_NRF52)
|
|
spi->transfer(data, NULL, size); // NULL for a "write only" transfer
|
|
#else
|
|
#error Not implemented yet? Feel free to add other platforms here.
|
|
#endif
|
|
|
|
digitalWrite(pin_cs, HIGH);
|
|
digitalWrite(pin_dc, HIGH);
|
|
spi->endTransaction();
|
|
}
|
|
|
|
void SSD16XX::configFullscreen()
|
|
{
|
|
// Placing this code in a separate method because it's probably pretty consistent between displays
|
|
// Should make it tidier to override SSD16XX::configure
|
|
|
|
// Define the boundaries of the "fullscreen" region, for the controller IC
|
|
static const uint16_t sx = bufferOffsetX; // Notice the offset
|
|
static const uint16_t sy = 0;
|
|
static const uint16_t ex = bufferRowSize + bufferOffsetX - 1; // End is "max index", not "count". Minus 1 handles this
|
|
static const uint16_t ey = height;
|
|
|
|
// Split into bytes
|
|
static const uint8_t sy1 = sy & 0xFF;
|
|
static const uint8_t sy2 = (sy >> 8) & 0xFF;
|
|
static const uint8_t ey1 = ey & 0xFF;
|
|
static const uint8_t ey2 = (ey >> 8) & 0xFF;
|
|
|
|
// Data entry mode - Left to Right, Top to Bottom
|
|
sendCommand(0x11);
|
|
sendData(0x03);
|
|
|
|
// Select controller IC memory region to display a fullscreen image
|
|
sendCommand(0x44); // Memory X start - end
|
|
sendData(sx);
|
|
sendData(ex);
|
|
sendCommand(0x45); // Memory Y start - end
|
|
sendData(sy1);
|
|
sendData(sy2);
|
|
sendData(ey1);
|
|
sendData(ey2);
|
|
|
|
// Place the cursor at the start of this memory region, ready to send image data x=0 y=0
|
|
sendCommand(0x4E); // Memory cursor X
|
|
sendData(sx);
|
|
sendCommand(0x4F); // Memory cursor y
|
|
sendData(sy1);
|
|
sendData(sy2);
|
|
}
|
|
|
|
void SSD16XX::update(uint8_t *imageData, UpdateTypes type)
|
|
{
|
|
this->updateType = type;
|
|
this->buffer = imageData;
|
|
|
|
reset();
|
|
|
|
configFullscreen();
|
|
configScanning(); // Virtual, unused by base class
|
|
configVoltages(); // Virtual, unused by base class
|
|
configWaveform(); // Virtual, unused by base class
|
|
wait();
|
|
|
|
if (updateType == FULL) {
|
|
writeNewImage();
|
|
writeOldImage();
|
|
} else {
|
|
writeNewImage();
|
|
}
|
|
|
|
configUpdateSequence();
|
|
sendCommand(0x20); // Begin executing the update
|
|
|
|
// Let the update run async, on display hardware. Base class will poll completion, then finalize.
|
|
// For a blocking update, call await after update
|
|
detachFromUpdate();
|
|
}
|
|
|
|
// Send SPI commands for controller IC to begin executing the refresh operation
|
|
void SSD16XX::configUpdateSequence()
|
|
{
|
|
switch (updateType) {
|
|
default:
|
|
sendCommand(0x22); // Set "update sequence"
|
|
sendData(0xF7); // Non-differential, load waveform from OTP
|
|
break;
|
|
}
|
|
}
|
|
|
|
void SSD16XX::writeNewImage()
|
|
{
|
|
sendCommand(0x24);
|
|
sendData(buffer, bufferSize);
|
|
}
|
|
|
|
void SSD16XX::writeOldImage()
|
|
{
|
|
sendCommand(0x26);
|
|
sendData(buffer, bufferSize);
|
|
}
|
|
|
|
void SSD16XX::detachFromUpdate()
|
|
{
|
|
// To save power / cycles, displays can choose to specify an "expected duration" for various refresh types
|
|
// If we know a full-refresh takes at least 4 seconds, we can delay polling until 3 seconds have passed
|
|
// If not implemented, we'll just poll right from the get-go
|
|
switch (updateType) {
|
|
default:
|
|
EInk::beginPolling(100, 0);
|
|
}
|
|
}
|
|
|
|
bool SSD16XX::isUpdateDone()
|
|
{
|
|
// Busy when HIGH
|
|
if (digitalRead(pin_busy) == HIGH)
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
void SSD16XX::finalizeUpdate()
|
|
{
|
|
// Put a copy of the image into the "old memory".
|
|
// Used with differential refreshes (e.g. FAST update), to determine which px need to move, and which can remain in place
|
|
// We need to keep the "old memory" up to date, because don't know whether next refresh will be FULL or FAST etc.
|
|
if (updateType != FULL) {
|
|
writeNewImage(); // Only required by some controller variants. Todo: Override just for GDEY0154D678?
|
|
writeOldImage();
|
|
sendCommand(0x7F); // Terminate image write without update
|
|
wait();
|
|
}
|
|
}
|
|
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|