2019-07-23 10:09:07 +01:00
|
|
|
/*!
|
|
|
|
* @file Adafruit_SSD1306.cpp
|
|
|
|
*
|
|
|
|
* @mainpage Arduino library for monochrome OLEDs based on SSD1306 drivers.
|
|
|
|
*
|
|
|
|
* @section intro_sec Introduction
|
|
|
|
*
|
|
|
|
* This is documentation for Adafruit's SSD1306 library for monochrome
|
|
|
|
* OLED displays: http://www.adafruit.com/category/63_98
|
|
|
|
*
|
|
|
|
* These displays use I2C or SPI to communicate. I2C requires 2 pins
|
|
|
|
* (SCL+SDA) and optionally a RESET pin. SPI requires 4 pins (MOSI, SCK,
|
|
|
|
* select, data/command) and optionally a reset pin. Hardware SPI or
|
|
|
|
* 'bitbang' software SPI are both supported.
|
|
|
|
*
|
|
|
|
* Adafruit invests time and resources providing this open source code,
|
|
|
|
* please support Adafruit and open-source hardware by purchasing
|
|
|
|
* products from Adafruit!
|
|
|
|
*
|
|
|
|
* @section dependencies Dependencies
|
|
|
|
*
|
|
|
|
* This library depends on <a href="https://github.com/adafruit/Adafruit-GFX-Library">
|
|
|
|
* Adafruit_GFX</a> being present on your system. Please make sure you have
|
|
|
|
* installed the latest version before using this library.
|
|
|
|
*
|
|
|
|
* @section author Author
|
|
|
|
*
|
|
|
|
* Written by Limor Fried/Ladyada for Adafruit Industries, with
|
|
|
|
* contributions from the open source community.
|
|
|
|
*
|
|
|
|
* @section license License
|
|
|
|
*
|
|
|
|
* BSD license, all text above, and the splash screen included below,
|
|
|
|
* must be included in any redistribution.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef __AVR__
|
|
|
|
#include <avr/pgmspace.h>
|
|
|
|
#elif defined(ESP8266) || defined(ESP32)
|
|
|
|
#include <pgmspace.h>
|
|
|
|
#else
|
|
|
|
#define pgm_read_byte(addr) \
|
|
|
|
(*(const unsigned char *)(addr)) ///< PROGMEM workaround for non-AVR
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if !defined(__ARM_ARCH) && !defined(ENERGIA) && !defined(ESP8266) && !defined(ESP32) && !defined(__arc__)
|
|
|
|
#include <util/delay.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <Adafruit_GFX.h>
|
|
|
|
#include "Adafruit_SSD1306.h"
|
|
|
|
#include "splash.h"
|
|
|
|
|
|
|
|
// SOME DEFINES AND STATIC VARIABLES USED INTERNALLY -----------------------
|
|
|
|
|
|
|
|
#if defined(BUFFER_LENGTH)
|
|
|
|
#define WIRE_MAX BUFFER_LENGTH ///< AVR or similar Wire lib
|
|
|
|
#elif defined(SERIAL_BUFFER_SIZE)
|
|
|
|
#define WIRE_MAX (SERIAL_BUFFER_SIZE-1) ///< Newer Wire uses RingBuffer
|
|
|
|
#else
|
|
|
|
#define WIRE_MAX 32 ///< Use common Arduino core default
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define ssd1306_swap(a, b) \
|
|
|
|
(((a) ^= (b)), ((b) ^= (a)), ((a) ^= (b))) ///< No-temp-var swap operation
|
|
|
|
|
|
|
|
#if ARDUINO >= 100
|
|
|
|
#define WIRE_WRITE wire->write ///< Wire write function in recent Arduino lib
|
|
|
|
#else
|
|
|
|
#define WIRE_WRITE wire->send ///< Wire write function in older Arduino lib
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef HAVE_PORTREG
|
|
|
|
#define SSD1306_SELECT *csPort &= ~csPinMask; ///< Device select
|
|
|
|
#define SSD1306_DESELECT *csPort |= csPinMask; ///< Device deselect
|
|
|
|
#define SSD1306_MODE_COMMAND *dcPort &= ~dcPinMask; ///< Command mode
|
|
|
|
#define SSD1306_MODE_DATA *dcPort |= dcPinMask; ///< Data mode
|
|
|
|
#else
|
|
|
|
#define SSD1306_SELECT digitalWrite(csPin, LOW); ///< Device select
|
|
|
|
#define SSD1306_DESELECT digitalWrite(csPin, HIGH); ///< Device deselect
|
|
|
|
#define SSD1306_MODE_COMMAND digitalWrite(dcPin, LOW); ///< Command mode
|
|
|
|
#define SSD1306_MODE_DATA digitalWrite(dcPin, HIGH); ///< Data mode
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if (ARDUINO >= 157) && !defined(ARDUINO_STM32_FEATHER)
|
|
|
|
#define SETWIRECLOCK wire->setClock(wireClk) ///< Set before I2C transfer
|
|
|
|
#define RESWIRECLOCK wire->setClock(restoreClk) ///< Restore after I2C xfer
|
|
|
|
#else // setClock() is not present in older Arduino Wire lib (or WICED)
|
|
|
|
#define SETWIRECLOCK ///< Dummy stand-in define
|
|
|
|
#define RESWIRECLOCK ///< keeps compiler happy
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if defined(SPI_HAS_TRANSACTION)
|
|
|
|
#define SPI_TRANSACTION_START spi->beginTransaction(spiSettings) ///< Pre-SPI
|
|
|
|
#define SPI_TRANSACTION_END spi->endTransaction() ///< Post-SPI
|
|
|
|
#else // SPI transactions likewise not present in older Arduino SPI lib
|
|
|
|
#define SPI_TRANSACTION_START ///< Dummy stand-in define
|
|
|
|
#define SPI_TRANSACTION_END ///< keeps compiler happy
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// The definition of 'transaction' is broadened a bit in the context of
|
|
|
|
// this library -- referring not just to SPI transactions (if supported
|
|
|
|
// in the version of the SPI library being used), but also chip select
|
|
|
|
// (if SPI is being used, whether hardware or soft), and also to the
|
|
|
|
// beginning and end of I2C transfers (the Wire clock may be sped up before
|
|
|
|
// issuing data to the display, then restored to the default rate afterward
|
|
|
|
// so other I2C device types still work). All of these are encapsulated
|
|
|
|
// in the TRANSACTION_* macros.
|
|
|
|
|
|
|
|
// Check first if Wire, then hardware SPI, then soft SPI:
|
|
|
|
#define TRANSACTION_START \
|
|
|
|
if(wire) { \
|
|
|
|
SETWIRECLOCK; \
|
|
|
|
} else { \
|
|
|
|
if(spi) { \
|
|
|
|
SPI_TRANSACTION_START; \
|
|
|
|
} \
|
|
|
|
SSD1306_SELECT; \
|
|
|
|
} ///< Wire, SPI or bitbang transfer setup
|
|
|
|
#define TRANSACTION_END \
|
|
|
|
if(wire) { \
|
|
|
|
RESWIRECLOCK; \
|
|
|
|
} else { \
|
|
|
|
SSD1306_DESELECT; \
|
|
|
|
if(spi) { \
|
|
|
|
SPI_TRANSACTION_END; \
|
|
|
|
} \
|
|
|
|
} ///< Wire, SPI or bitbang transfer end
|
|
|
|
|
|
|
|
// CONSTRUCTORS, DESTRUCTOR ------------------------------------------------
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@brief Constructor for I2C-interfaced SSD1306 displays.
|
|
|
|
@param w
|
|
|
|
Display width in pixels
|
|
|
|
@param h
|
|
|
|
Display height in pixels
|
|
|
|
@param twi
|
|
|
|
Pointer to an existing TwoWire instance (e.g. &Wire, the
|
|
|
|
microcontroller's primary I2C bus).
|
|
|
|
@param rst_pin
|
|
|
|
Reset pin (using Arduino pin numbering), or -1 if not used
|
|
|
|
(some displays might be wired to share the microcontroller's
|
|
|
|
reset pin).
|
|
|
|
@param clkDuring
|
|
|
|
Speed (in Hz) for Wire transmissions in SSD1306 library calls.
|
|
|
|
Defaults to 400000 (400 KHz), a known 'safe' value for most
|
|
|
|
microcontrollers, and meets the SSD1306 datasheet spec.
|
|
|
|
Some systems can operate I2C faster (800 KHz for ESP32, 1 MHz
|
|
|
|
for many other 32-bit MCUs), and some (perhaps not all)
|
|
|
|
SSD1306's can work with this -- so it's optionally be specified
|
|
|
|
here and is not a default behavior. (Ignored if using pre-1.5.7
|
|
|
|
Arduino software, which operates I2C at a fixed 100 KHz.)
|
|
|
|
@param clkAfter
|
|
|
|
Speed (in Hz) for Wire transmissions following SSD1306 library
|
|
|
|
calls. Defaults to 100000 (100 KHz), the default Arduino Wire
|
|
|
|
speed. This is done rather than leaving it at the 'during' speed
|
|
|
|
because other devices on the I2C bus might not be compatible
|
|
|
|
with the faster rate. (Ignored if using pre-1.5.7 Arduino
|
|
|
|
software, which operates I2C at a fixed 100 KHz.)
|
|
|
|
@return Adafruit_SSD1306 object.
|
|
|
|
@note Call the object's begin() function before use -- buffer
|
|
|
|
allocation is performed there!
|
|
|
|
*/
|
|
|
|
Adafruit_SSD1306::Adafruit_SSD1306(uint8_t w, uint8_t h, TwoWire *twi,
|
|
|
|
int8_t rst_pin, uint32_t clkDuring, uint32_t clkAfter) :
|
2019-08-19 12:21:54 +01:00
|
|
|
Renderer(w, h), spi(NULL), wire(twi ? twi : &Wire), xbuffer(NULL),
|
2019-07-23 10:09:07 +01:00
|
|
|
mosiPin(-1), clkPin(-1), dcPin(-1), csPin(-1), rstPin(rst_pin),
|
|
|
|
wireClk(clkDuring), restoreClk(clkAfter) {
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@brief Constructor for SPI SSD1306 displays, using software (bitbang)
|
|
|
|
SPI.
|
|
|
|
@param w
|
|
|
|
Display width in pixels
|
|
|
|
@param h
|
|
|
|
Display height in pixels
|
|
|
|
@param mosi_pin
|
|
|
|
MOSI (master out, slave in) pin (using Arduino pin numbering).
|
|
|
|
This transfers serial data from microcontroller to display.
|
|
|
|
@param sclk_pin
|
|
|
|
SCLK (serial clock) pin (using Arduino pin numbering).
|
|
|
|
This clocks each bit from MOSI.
|
|
|
|
@param dc_pin
|
|
|
|
Data/command pin (using Arduino pin numbering), selects whether
|
|
|
|
display is receiving commands (low) or data (high).
|
|
|
|
@param rst_pin
|
|
|
|
Reset pin (using Arduino pin numbering), or -1 if not used
|
|
|
|
(some displays might be wired to share the microcontroller's
|
|
|
|
reset pin).
|
|
|
|
@param cs_pin
|
|
|
|
Chip-select pin (using Arduino pin numbering) for sharing the
|
|
|
|
bus with other devices. Active low.
|
|
|
|
@return Adafruit_SSD1306 object.
|
|
|
|
@note Call the object's begin() function before use -- buffer
|
|
|
|
allocation is performed there!
|
|
|
|
*/
|
|
|
|
Adafruit_SSD1306::Adafruit_SSD1306(uint8_t w, uint8_t h,
|
|
|
|
int8_t mosi_pin, int8_t sclk_pin, int8_t dc_pin, int8_t rst_pin,
|
2019-08-19 12:21:54 +01:00
|
|
|
int8_t cs_pin) : Renderer(w, h), spi(NULL), wire(NULL), xbuffer(NULL),
|
2019-07-23 10:09:07 +01:00
|
|
|
mosiPin(mosi_pin), clkPin(sclk_pin), dcPin(dc_pin), csPin(cs_pin),
|
|
|
|
rstPin(rst_pin) {
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@brief Constructor for SPI SSD1306 displays, using native hardware SPI.
|
|
|
|
@param w
|
|
|
|
Display width in pixels
|
|
|
|
@param h
|
|
|
|
Display height in pixels
|
|
|
|
@param spi
|
|
|
|
Pointer to an existing SPIClass instance (e.g. &SPI, the
|
|
|
|
microcontroller's primary SPI bus).
|
|
|
|
@param dc_pin
|
|
|
|
Data/command pin (using Arduino pin numbering), selects whether
|
|
|
|
display is receiving commands (low) or data (high).
|
|
|
|
@param rst_pin
|
|
|
|
Reset pin (using Arduino pin numbering), or -1 if not used
|
|
|
|
(some displays might be wired to share the microcontroller's
|
|
|
|
reset pin).
|
|
|
|
@param cs_pin
|
|
|
|
Chip-select pin (using Arduino pin numbering) for sharing the
|
|
|
|
bus with other devices. Active low.
|
|
|
|
@param bitrate
|
|
|
|
SPI clock rate for transfers to this display. Default if
|
|
|
|
unspecified is 8000000UL (8 MHz).
|
|
|
|
@return Adafruit_SSD1306 object.
|
|
|
|
@note Call the object's begin() function before use -- buffer
|
|
|
|
allocation is performed there!
|
|
|
|
*/
|
|
|
|
Adafruit_SSD1306::Adafruit_SSD1306(uint8_t w, uint8_t h, SPIClass *spi,
|
|
|
|
int8_t dc_pin, int8_t rst_pin, int8_t cs_pin, uint32_t bitrate) :
|
2019-08-19 12:21:54 +01:00
|
|
|
Renderer(w, h), spi(spi ? spi : &SPI), wire(NULL), xbuffer(NULL),
|
2019-07-23 10:09:07 +01:00
|
|
|
mosiPin(-1), clkPin(-1), dcPin(dc_pin), csPin(cs_pin), rstPin(rst_pin) {
|
|
|
|
#ifdef SPI_HAS_TRANSACTION
|
|
|
|
spiSettings = SPISettings(bitrate, MSBFIRST, SPI_MODE0);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@brief DEPRECATED constructor for SPI SSD1306 displays, using software
|
|
|
|
(bitbang) SPI. Provided for older code to maintain compatibility
|
|
|
|
with the current library. Screen size is determined by enabling
|
|
|
|
one of the SSD1306_* size defines in Adafruit_SSD1306.h. New
|
|
|
|
code should NOT use this.
|
|
|
|
@param mosi_pin
|
|
|
|
MOSI (master out, slave in) pin (using Arduino pin numbering).
|
|
|
|
This transfers serial data from microcontroller to display.
|
|
|
|
@param sclk_pin
|
|
|
|
SCLK (serial clock) pin (using Arduino pin numbering).
|
|
|
|
This clocks each bit from MOSI.
|
|
|
|
@param dc_pin
|
|
|
|
Data/command pin (using Arduino pin numbering), selects whether
|
|
|
|
display is receiving commands (low) or data (high).
|
|
|
|
@param rst_pin
|
|
|
|
Reset pin (using Arduino pin numbering), or -1 if not used
|
|
|
|
(some displays might be wired to share the microcontroller's
|
|
|
|
reset pin).
|
|
|
|
@param cs_pin
|
|
|
|
Chip-select pin (using Arduino pin numbering) for sharing the
|
|
|
|
bus with other devices. Active low.
|
|
|
|
@return Adafruit_SSD1306 object.
|
|
|
|
@note Call the object's begin() function before use -- buffer
|
|
|
|
allocation is performed there!
|
|
|
|
*/
|
|
|
|
Adafruit_SSD1306::Adafruit_SSD1306(int8_t mosi_pin, int8_t sclk_pin,
|
|
|
|
int8_t dc_pin, int8_t rst_pin, int8_t cs_pin) :
|
2019-08-19 12:21:54 +01:00
|
|
|
Renderer(SSD1306_LCDWIDTH, SSD1306_LCDHEIGHT), spi(NULL), wire(NULL),
|
|
|
|
xbuffer(NULL), mosiPin(mosi_pin), clkPin(sclk_pin), dcPin(dc_pin),
|
2019-07-23 10:09:07 +01:00
|
|
|
csPin(cs_pin), rstPin(rst_pin) {
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@brief DEPRECATED constructor for SPI SSD1306 displays, using native
|
|
|
|
hardware SPI. Provided for older code to maintain compatibility
|
|
|
|
with the current library. Screen size is determined by enabling
|
|
|
|
one of the SSD1306_* size defines in Adafruit_SSD1306.h. New
|
|
|
|
code should NOT use this. Only the primary SPI bus is supported,
|
|
|
|
and bitrate is fixed at 8 MHz.
|
|
|
|
@param dc_pin
|
|
|
|
Data/command pin (using Arduino pin numbering), selects whether
|
|
|
|
display is receiving commands (low) or data (high).
|
|
|
|
@param rst_pin
|
|
|
|
Reset pin (using Arduino pin numbering), or -1 if not used
|
|
|
|
(some displays might be wired to share the microcontroller's
|
|
|
|
reset pin).
|
|
|
|
@param cs_pin
|
|
|
|
Chip-select pin (using Arduino pin numbering) for sharing the
|
|
|
|
bus with other devices. Active low.
|
|
|
|
@return Adafruit_SSD1306 object.
|
|
|
|
@note Call the object's begin() function before use -- buffer
|
|
|
|
allocation is performed there!
|
|
|
|
*/
|
|
|
|
Adafruit_SSD1306::Adafruit_SSD1306(int8_t dc_pin, int8_t rst_pin,
|
2019-08-19 12:21:54 +01:00
|
|
|
int8_t cs_pin) : Renderer(SSD1306_LCDWIDTH, SSD1306_LCDHEIGHT),
|
|
|
|
spi(&SPI), wire(NULL), xbuffer(NULL), mosiPin(-1), clkPin(-1),
|
|
|
|
dcPin(dc_pin), csPin(cs_pin), rstPin(rst_pin) {
|
2019-07-23 10:09:07 +01:00
|
|
|
#ifdef SPI_HAS_TRANSACTION
|
|
|
|
spiSettings = SPISettings(8000000, MSBFIRST, SPI_MODE0);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@brief DEPRECATED constructor for I2C SSD1306 displays. Provided for
|
|
|
|
older code to maintain compatibility with the current library.
|
|
|
|
Screen size is determined by enabling one of the SSD1306_* size
|
|
|
|
defines in Adafruit_SSD1306.h. New code should NOT use this.
|
|
|
|
Only the primary I2C bus is supported.
|
|
|
|
@param rst_pin
|
|
|
|
Reset pin (using Arduino pin numbering), or -1 if not used
|
|
|
|
(some displays might be wired to share the microcontroller's
|
|
|
|
reset pin).
|
|
|
|
@return Adafruit_SSD1306 object.
|
|
|
|
@note Call the object's begin() function before use -- buffer
|
|
|
|
allocation is performed there!
|
|
|
|
*/
|
|
|
|
Adafruit_SSD1306::Adafruit_SSD1306(int8_t rst_pin) :
|
2019-08-19 12:21:54 +01:00
|
|
|
Renderer(SSD1306_LCDWIDTH, SSD1306_LCDHEIGHT), spi(NULL), wire(&Wire),
|
|
|
|
xbuffer(NULL), mosiPin(-1), clkPin(-1), dcPin(-1), csPin(-1),
|
2019-07-23 10:09:07 +01:00
|
|
|
rstPin(rst_pin) {
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@brief Destructor for Adafruit_SSD1306 object.
|
|
|
|
*/
|
|
|
|
Adafruit_SSD1306::~Adafruit_SSD1306(void) {
|
2021-04-21 10:01:40 +01:00
|
|
|
if (framebuffer) {
|
|
|
|
free (framebuffer);
|
|
|
|
framebuffer = NULL;
|
2019-07-23 10:09:07 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// LOW-LEVEL UTILS ---------------------------------------------------------
|
|
|
|
|
|
|
|
// Issue single byte out SPI, either soft or hardware as appropriate.
|
|
|
|
// SPI transaction/selection must be performed in calling function.
|
|
|
|
inline void Adafruit_SSD1306::SPIwrite(uint8_t d) {
|
|
|
|
if(spi) {
|
|
|
|
(void)spi->transfer(d);
|
|
|
|
} else {
|
|
|
|
for(uint8_t bit = 0x80; bit; bit >>= 1) {
|
|
|
|
#ifdef HAVE_PORTREG
|
|
|
|
if(d & bit) *mosiPort |= mosiPinMask;
|
|
|
|
else *mosiPort &= ~mosiPinMask;
|
|
|
|
*clkPort |= clkPinMask; // Clock high
|
|
|
|
*clkPort &= ~clkPinMask; // Clock low
|
|
|
|
#else
|
|
|
|
digitalWrite(mosiPin, d & bit);
|
|
|
|
digitalWrite(clkPin , HIGH);
|
|
|
|
digitalWrite(clkPin , LOW);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Issue single command to SSD1306, using I2C or hard/soft SPI as needed.
|
|
|
|
// Because command calls are often grouped, SPI transaction and selection
|
|
|
|
// must be started/ended in calling function for efficiency.
|
|
|
|
// This is a private function, not exposed (see ssd1306_command() instead).
|
|
|
|
void Adafruit_SSD1306::ssd1306_command1(uint8_t c) {
|
|
|
|
if(wire) { // I2C
|
|
|
|
wire->beginTransmission(i2caddr);
|
|
|
|
WIRE_WRITE((uint8_t)0x00); // Co = 0, D/C = 0
|
|
|
|
WIRE_WRITE(c);
|
|
|
|
wire->endTransmission();
|
|
|
|
} else { // SPI (hw or soft) -- transaction started in calling function
|
|
|
|
SSD1306_MODE_COMMAND
|
|
|
|
SPIwrite(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Issue list of commands to SSD1306, same rules as above re: transactions.
|
|
|
|
// This is a private function, not exposed.
|
|
|
|
void Adafruit_SSD1306::ssd1306_commandList(const uint8_t *c, uint8_t n) {
|
|
|
|
if(wire) { // I2C
|
|
|
|
wire->beginTransmission(i2caddr);
|
|
|
|
WIRE_WRITE((uint8_t)0x00); // Co = 0, D/C = 0
|
|
|
|
uint8_t bytesOut = 1;
|
|
|
|
while(n--) {
|
|
|
|
if(bytesOut >= WIRE_MAX) {
|
|
|
|
wire->endTransmission();
|
|
|
|
wire->beginTransmission(i2caddr);
|
|
|
|
WIRE_WRITE((uint8_t)0x00); // Co = 0, D/C = 0
|
|
|
|
bytesOut = 1;
|
|
|
|
}
|
|
|
|
WIRE_WRITE(pgm_read_byte(c++));
|
|
|
|
bytesOut++;
|
|
|
|
}
|
|
|
|
wire->endTransmission();
|
|
|
|
} else { // SPI -- transaction started in calling function
|
|
|
|
SSD1306_MODE_COMMAND
|
|
|
|
while(n--) SPIwrite(pgm_read_byte(c++));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// A public version of ssd1306_command1(), for existing user code that
|
|
|
|
// might rely on that function. This encapsulates the command transfer
|
|
|
|
// in a transaction start/end, similar to old library's handling of it.
|
|
|
|
/*!
|
|
|
|
@brief Issue a single low-level command directly to the SSD1306
|
|
|
|
display, bypassing the library.
|
|
|
|
@param c
|
|
|
|
Command to issue (0x00 to 0xFF, see datasheet).
|
|
|
|
@return None (void).
|
|
|
|
*/
|
|
|
|
void Adafruit_SSD1306::ssd1306_command(uint8_t c) {
|
|
|
|
TRANSACTION_START
|
|
|
|
ssd1306_command1(c);
|
|
|
|
TRANSACTION_END
|
|
|
|
}
|
|
|
|
|
|
|
|
// ALLOCATE & INIT DISPLAY -------------------------------------------------
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@brief Allocate RAM for image buffer, initialize peripherals and pins.
|
|
|
|
@param vcs
|
|
|
|
VCC selection. Pass SSD1306_SWITCHCAPVCC to generate the display
|
|
|
|
voltage (step up) from the 3.3V source, or SSD1306_EXTERNALVCC
|
|
|
|
otherwise. Most situations with Adafruit SSD1306 breakouts will
|
|
|
|
want SSD1306_SWITCHCAPVCC.
|
|
|
|
@param addr
|
|
|
|
I2C address of corresponding SSD1306 display (or pass 0 to use
|
|
|
|
default of 0x3C for 128x32 display, 0x3D for all others).
|
|
|
|
SPI displays (hardware or software) do not use addresses, but
|
|
|
|
this argument is still required (pass 0 or any value really,
|
|
|
|
it will simply be ignored). Default if unspecified is 0.
|
|
|
|
@param reset
|
|
|
|
If true, and if the reset pin passed to the constructor is
|
|
|
|
valid, a hard reset will be performed before initializing the
|
|
|
|
display. If using multiple SSD1306 displays on the same bus, and
|
|
|
|
if they all share the same reset pin, you should only pass true
|
|
|
|
on the first display being initialized, false on all others,
|
|
|
|
else the already-initialized displays would be reset. Default if
|
|
|
|
unspecified is true.
|
|
|
|
@param periphBegin
|
|
|
|
If true, and if a hardware peripheral is being used (I2C or SPI,
|
|
|
|
but not software SPI), call that peripheral's begin() function,
|
|
|
|
else (false) it has already been done in one's sketch code.
|
|
|
|
Cases where false might be used include multiple displays or
|
|
|
|
other devices sharing a common bus, or situations on some
|
|
|
|
platforms where a nonstandard begin() function is available
|
|
|
|
(e.g. a TwoWire interface on non-default pins, as can be done
|
|
|
|
on the ESP8266 and perhaps others).
|
|
|
|
@return true on successful allocation/init, false otherwise.
|
|
|
|
Well-behaved code should check the return value before
|
|
|
|
proceeding.
|
|
|
|
@note MUST call this function before any drawing or updates!
|
|
|
|
*/
|
|
|
|
boolean Adafruit_SSD1306::begin(uint8_t vcs, uint8_t addr, boolean reset,
|
|
|
|
boolean periphBegin) {
|
|
|
|
|
2021-04-21 10:01:40 +01:00
|
|
|
framebuffer = (uint8_t *)malloc(WIDTH * ((HEIGHT + 7) / 8));
|
|
|
|
if (!framebuffer) return false;
|
2019-07-23 10:09:07 +01:00
|
|
|
|
|
|
|
clearDisplay();
|
2019-08-19 12:21:54 +01:00
|
|
|
|
|
|
|
/*
|
2019-07-23 10:09:07 +01:00
|
|
|
if(HEIGHT > 32) {
|
|
|
|
drawBitmap((WIDTH - splash1_width) / 2, (HEIGHT - splash1_height) / 2,
|
|
|
|
splash1_data, splash1_width, splash1_height, 1);
|
|
|
|
} else {
|
|
|
|
drawBitmap((WIDTH - splash2_width) / 2, (HEIGHT - splash2_height) / 2,
|
|
|
|
splash2_data, splash2_width, splash2_height, 1);
|
|
|
|
}
|
2019-08-19 12:21:54 +01:00
|
|
|
*/
|
2019-07-23 10:09:07 +01:00
|
|
|
vccstate = vcs;
|
|
|
|
|
|
|
|
// Setup pin directions
|
|
|
|
if(wire) { // Using I2C
|
|
|
|
// If I2C address is unspecified, use default
|
|
|
|
// (0x3C for 32-pixel-tall displays, 0x3D for all others).
|
|
|
|
i2caddr = addr ? addr : ((HEIGHT == 32) ? 0x3C : 0x3D);
|
|
|
|
// TwoWire begin() function might be already performed by the calling
|
|
|
|
// function if it has unusual circumstances (e.g. TWI variants that
|
|
|
|
// can accept different SDA/SCL pins, or if two SSD1306 instances
|
|
|
|
// with different addresses -- only a single begin() is needed).
|
|
|
|
if(periphBegin) wire->begin();
|
|
|
|
} else { // Using one of the SPI modes, either soft or hardware
|
|
|
|
pinMode(dcPin, OUTPUT); // Set data/command pin as output
|
|
|
|
pinMode(csPin, OUTPUT); // Same for chip select
|
|
|
|
#ifdef HAVE_PORTREG
|
|
|
|
dcPort = (PortReg *)portOutputRegister(digitalPinToPort(dcPin));
|
|
|
|
dcPinMask = digitalPinToBitMask(dcPin);
|
|
|
|
csPort = (PortReg *)portOutputRegister(digitalPinToPort(csPin));
|
|
|
|
csPinMask = digitalPinToBitMask(csPin);
|
|
|
|
#endif
|
|
|
|
SSD1306_DESELECT
|
|
|
|
if(spi) { // Hardware SPI
|
|
|
|
// SPI peripheral begin same as wire check above.
|
|
|
|
if(periphBegin) spi->begin();
|
|
|
|
} else { // Soft SPI
|
|
|
|
pinMode(mosiPin, OUTPUT); // MOSI and SCLK outputs
|
|
|
|
pinMode(clkPin , OUTPUT);
|
|
|
|
#ifdef HAVE_PORTREG
|
|
|
|
mosiPort = (PortReg *)portOutputRegister(digitalPinToPort(mosiPin));
|
|
|
|
mosiPinMask = digitalPinToBitMask(mosiPin);
|
|
|
|
clkPort = (PortReg *)portOutputRegister(digitalPinToPort(clkPin));
|
|
|
|
clkPinMask = digitalPinToBitMask(clkPin);
|
|
|
|
*clkPort &= ~clkPinMask; // Clock low
|
|
|
|
#else
|
|
|
|
digitalWrite(clkPin, LOW); // Clock low
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset SSD1306 if requested and reset pin specified in constructor
|
|
|
|
if(reset && (rstPin >= 0)) {
|
|
|
|
pinMode( rstPin, OUTPUT);
|
|
|
|
digitalWrite(rstPin, HIGH);
|
|
|
|
delay(1); // VDD goes high at start, pause for 1 ms
|
|
|
|
digitalWrite(rstPin, LOW); // Bring reset low
|
|
|
|
delay(10); // Wait 10 ms
|
|
|
|
digitalWrite(rstPin, HIGH); // Bring out of reset
|
|
|
|
}
|
|
|
|
|
|
|
|
TRANSACTION_START
|
|
|
|
|
|
|
|
// Init sequence
|
|
|
|
static const uint8_t PROGMEM init1[] = {
|
|
|
|
SSD1306_DISPLAYOFF, // 0xAE
|
|
|
|
SSD1306_SETDISPLAYCLOCKDIV, // 0xD5
|
|
|
|
0x80, // the suggested ratio 0x80
|
|
|
|
SSD1306_SETMULTIPLEX }; // 0xA8
|
|
|
|
ssd1306_commandList(init1, sizeof(init1));
|
|
|
|
ssd1306_command1(HEIGHT - 1);
|
|
|
|
|
|
|
|
static const uint8_t PROGMEM init2[] = {
|
|
|
|
SSD1306_SETDISPLAYOFFSET, // 0xD3
|
|
|
|
0x0, // no offset
|
|
|
|
SSD1306_SETSTARTLINE | 0x0, // line #0
|
|
|
|
SSD1306_CHARGEPUMP }; // 0x8D
|
|
|
|
ssd1306_commandList(init2, sizeof(init2));
|
|
|
|
|
|
|
|
ssd1306_command1((vccstate == SSD1306_EXTERNALVCC) ? 0x10 : 0x14);
|
|
|
|
|
|
|
|
static const uint8_t PROGMEM init3[] = {
|
|
|
|
SSD1306_MEMORYMODE, // 0x20
|
|
|
|
0x00, // 0x0 act like ks0108
|
|
|
|
SSD1306_SEGREMAP | 0x1,
|
|
|
|
SSD1306_COMSCANDEC };
|
|
|
|
ssd1306_commandList(init3, sizeof(init3));
|
|
|
|
|
|
|
|
if((WIDTH == 128) && (HEIGHT == 32)) {
|
|
|
|
static const uint8_t PROGMEM init4a[] = {
|
|
|
|
SSD1306_SETCOMPINS, // 0xDA
|
|
|
|
0x02,
|
|
|
|
SSD1306_SETCONTRAST, // 0x81
|
|
|
|
0x8F };
|
|
|
|
ssd1306_commandList(init4a, sizeof(init4a));
|
|
|
|
} else if((WIDTH == 128) && (HEIGHT == 64)) {
|
|
|
|
static const uint8_t PROGMEM init4b[] = {
|
|
|
|
SSD1306_SETCOMPINS, // 0xDA
|
|
|
|
0x12,
|
|
|
|
SSD1306_SETCONTRAST }; // 0x81
|
|
|
|
ssd1306_commandList(init4b, sizeof(init4b));
|
|
|
|
ssd1306_command1((vccstate == SSD1306_EXTERNALVCC) ? 0x9F : 0xCF);
|
|
|
|
} else if((WIDTH == 96) && (HEIGHT == 16)) {
|
|
|
|
static const uint8_t PROGMEM init4c[] = {
|
|
|
|
SSD1306_SETCOMPINS, // 0xDA
|
|
|
|
0x2, // ada x12
|
|
|
|
SSD1306_SETCONTRAST }; // 0x81
|
|
|
|
ssd1306_commandList(init4c, sizeof(init4c));
|
|
|
|
ssd1306_command1((vccstate == SSD1306_EXTERNALVCC) ? 0x10 : 0xAF);
|
2019-10-30 09:14:16 +00:00
|
|
|
} else if((WIDTH == 64) && (HEIGHT == 48)) {
|
|
|
|
static const uint8_t PROGMEM init4d[] = {
|
|
|
|
SSD1306_SETCOMPINS, // 0xDA
|
|
|
|
0x12,
|
|
|
|
SSD1306_SETCONTRAST }; // 0x81
|
|
|
|
ssd1306_commandList(init4d, sizeof(init4d));
|
|
|
|
ssd1306_command1((vccstate == SSD1306_EXTERNALVCC) ? 0x9F : 0xCF);
|
2019-07-23 10:09:07 +01:00
|
|
|
} else {
|
|
|
|
// Other screen varieties -- TBD
|
|
|
|
}
|
|
|
|
|
|
|
|
ssd1306_command1(SSD1306_SETPRECHARGE); // 0xd9
|
|
|
|
ssd1306_command1((vccstate == SSD1306_EXTERNALVCC) ? 0x22 : 0xF1);
|
|
|
|
static const uint8_t PROGMEM init5[] = {
|
|
|
|
SSD1306_SETVCOMDETECT, // 0xDB
|
|
|
|
0x40,
|
|
|
|
SSD1306_DISPLAYALLON_RESUME, // 0xA4
|
|
|
|
SSD1306_NORMALDISPLAY, // 0xA6
|
|
|
|
SSD1306_DEACTIVATE_SCROLL,
|
|
|
|
SSD1306_DISPLAYON }; // Main screen turn on
|
|
|
|
ssd1306_commandList(init5, sizeof(init5));
|
|
|
|
|
|
|
|
TRANSACTION_END
|
|
|
|
|
|
|
|
return true; // Success
|
|
|
|
}
|
|
|
|
|
2019-08-19 12:21:54 +01:00
|
|
|
|
2019-10-19 11:46:00 +01:00
|
|
|
void Adafruit_SSD1306::DisplayInit(int8_t p,int8_t size,int8_t rot,int8_t font) {
|
|
|
|
// ignore update mode
|
|
|
|
//if (p==DISPLAY_INIT_MODE) {
|
|
|
|
setRotation(rot);
|
|
|
|
invertDisplay(false);
|
|
|
|
setTextWrap(false); // Allow text to run off edges
|
|
|
|
cp437(true);
|
|
|
|
setTextFont(font);
|
|
|
|
setTextSize(size);
|
|
|
|
setTextColor(WHITE,BLACK);
|
|
|
|
setCursor(0,0);
|
|
|
|
fillScreen(BLACK);
|
|
|
|
Updateframe();
|
|
|
|
//}
|
|
|
|
}
|
|
|
|
|
2019-08-19 12:21:54 +01:00
|
|
|
#if 0
|
|
|
|
|
2019-07-23 10:09:07 +01:00
|
|
|
// DRAWING FUNCTIONS -------------------------------------------------------
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@brief Set/clear/invert a single pixel. This is also invoked by the
|
|
|
|
Adafruit_GFX library in generating many higher-level graphics
|
|
|
|
primitives.
|
|
|
|
@param x
|
|
|
|
Column of display -- 0 at left to (screen width - 1) at right.
|
|
|
|
@param y
|
|
|
|
Row of display -- 0 at top to (screen height -1) at bottom.
|
|
|
|
@param color
|
|
|
|
Pixel color, one of: BLACK, WHITE or INVERT.
|
|
|
|
@return None (void).
|
|
|
|
@note Changes buffer contents only, no immediate effect on display.
|
|
|
|
Follow up with a call to display(), or with other graphics
|
|
|
|
commands as needed by one's own application.
|
|
|
|
*/
|
|
|
|
void Adafruit_SSD1306::drawPixel(int16_t x, int16_t y, uint16_t color) {
|
2021-04-21 10:01:40 +01:00
|
|
|
|
|
|
|
if (!framebuffer) return;
|
|
|
|
|
|
|
|
|
2019-07-23 10:09:07 +01:00
|
|
|
if((x >= 0) && (x < width()) && (y >= 0) && (y < height())) {
|
|
|
|
// Pixel is in-bounds. Rotate coordinates if needed.
|
|
|
|
switch(getRotation()) {
|
|
|
|
case 1:
|
|
|
|
ssd1306_swap(x, y);
|
|
|
|
x = WIDTH - x - 1;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
x = WIDTH - x - 1;
|
|
|
|
y = HEIGHT - y - 1;
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
ssd1306_swap(x, y);
|
|
|
|
y = HEIGHT - y - 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
switch(color) {
|
2021-04-21 10:01:40 +01:00
|
|
|
case WHITE: framebuffer[x + (y/8)*WIDTH] |= (1 << (y&7)); break;
|
|
|
|
case BLACK: framebuffer[x + (y/8)*WIDTH] &= ~(1 << (y&7)); break;
|
|
|
|
case INVERSE: framebuffer[x + (y/8)*WIDTH] ^= (1 << (y&7)); break;
|
2019-07-23 10:09:07 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@brief Clear contents of display buffer (set all pixels to off).
|
|
|
|
@return None (void).
|
|
|
|
@note Changes buffer contents only, no immediate effect on display.
|
|
|
|
Follow up with a call to display(), or with other graphics
|
|
|
|
commands as needed by one's own application.
|
|
|
|
*/
|
|
|
|
void Adafruit_SSD1306::clearDisplay(void) {
|
2021-04-21 10:01:40 +01:00
|
|
|
if (!framebuffer) return;
|
|
|
|
memset(framebuffer, 0, WIDTH * ((HEIGHT + 7) / 8));
|
2019-07-23 10:09:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@brief Draw a horizontal line. This is also invoked by the Adafruit_GFX
|
|
|
|
library in generating many higher-level graphics primitives.
|
|
|
|
@param x
|
|
|
|
Leftmost column -- 0 at left to (screen width - 1) at right.
|
|
|
|
@param y
|
|
|
|
Row of display -- 0 at top to (screen height -1) at bottom.
|
|
|
|
@param w
|
|
|
|
Width of line, in pixels.
|
|
|
|
@param color
|
|
|
|
Line color, one of: BLACK, WHITE or INVERT.
|
|
|
|
@return None (void).
|
|
|
|
@note Changes buffer contents only, no immediate effect on display.
|
|
|
|
Follow up with a call to display(), or with other graphics
|
|
|
|
commands as needed by one's own application.
|
|
|
|
*/
|
|
|
|
void Adafruit_SSD1306::drawFastHLine(
|
|
|
|
int16_t x, int16_t y, int16_t w, uint16_t color) {
|
|
|
|
boolean bSwap = false;
|
|
|
|
switch(rotation) {
|
|
|
|
case 1:
|
|
|
|
// 90 degree rotation, swap x & y for rotation, then invert x
|
|
|
|
bSwap = true;
|
|
|
|
ssd1306_swap(x, y);
|
|
|
|
x = WIDTH - x - 1;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
// 180 degree rotation, invert x and y, then shift y around for height.
|
|
|
|
x = WIDTH - x - 1;
|
|
|
|
y = HEIGHT - y - 1;
|
|
|
|
x -= (w-1);
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
// 270 degree rotation, swap x & y for rotation,
|
|
|
|
// then invert y and adjust y for w (not to become h)
|
|
|
|
bSwap = true;
|
|
|
|
ssd1306_swap(x, y);
|
|
|
|
y = HEIGHT - y - 1;
|
|
|
|
y -= (w-1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(bSwap) drawFastVLineInternal(x, y, w, color);
|
|
|
|
else drawFastHLineInternal(x, y, w, color);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@brief Draw a vertical line. This is also invoked by the Adafruit_GFX
|
|
|
|
library in generating many higher-level graphics primitives.
|
|
|
|
@param x
|
|
|
|
Column of display -- 0 at left to (screen width -1) at right.
|
|
|
|
@param y
|
|
|
|
Topmost row -- 0 at top to (screen height - 1) at bottom.
|
|
|
|
@param h
|
|
|
|
Height of line, in pixels.
|
|
|
|
@param color
|
|
|
|
Line color, one of: BLACK, WHITE or INVERT.
|
|
|
|
@return None (void).
|
|
|
|
@note Changes buffer contents only, no immediate effect on display.
|
|
|
|
Follow up with a call to display(), or with other graphics
|
|
|
|
commands as needed by one's own application.
|
|
|
|
*/
|
|
|
|
void Adafruit_SSD1306::drawFastVLine(
|
|
|
|
int16_t x, int16_t y, int16_t h, uint16_t color) {
|
|
|
|
boolean bSwap = false;
|
|
|
|
switch(rotation) {
|
|
|
|
case 1:
|
|
|
|
// 90 degree rotation, swap x & y for rotation,
|
|
|
|
// then invert x and adjust x for h (now to become w)
|
|
|
|
bSwap = true;
|
|
|
|
ssd1306_swap(x, y);
|
|
|
|
x = WIDTH - x - 1;
|
|
|
|
x -= (h-1);
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
// 180 degree rotation, invert x and y, then shift y around for height.
|
|
|
|
x = WIDTH - x - 1;
|
|
|
|
y = HEIGHT - y - 1;
|
|
|
|
y -= (h-1);
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
// 270 degree rotation, swap x & y for rotation, then invert y
|
|
|
|
bSwap = true;
|
|
|
|
ssd1306_swap(x, y);
|
|
|
|
y = HEIGHT - y - 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(bSwap) drawFastHLineInternal(x, y, h, color);
|
|
|
|
else drawFastVLineInternal(x, y, h, color);
|
|
|
|
}
|
2019-08-19 12:21:54 +01:00
|
|
|
#endif
|
|
|
|
|
2021-04-21 10:01:40 +01:00
|
|
|
void Adafruit_SSD1306::drawFastHLineInternal (
|
2019-08-19 12:21:54 +01:00
|
|
|
int16_t x, int16_t y, int16_t w, uint16_t color) {
|
2021-04-21 10:01:40 +01:00
|
|
|
if (!framebuffer) return;
|
2019-08-19 12:21:54 +01:00
|
|
|
if((y >= 0) && (y < HEIGHT)) { // Y coord in bounds?
|
|
|
|
if(x < 0) { // Clip left
|
|
|
|
w += x;
|
|
|
|
x = 0;
|
|
|
|
}
|
|
|
|
if((x + w) > WIDTH) { // Clip right
|
|
|
|
w = (WIDTH - x);
|
|
|
|
}
|
|
|
|
if(w > 0) { // Proceed only if width is positive
|
2021-04-21 10:01:40 +01:00
|
|
|
uint8_t *pBuf = &framebuffer[(y / 8) * WIDTH + x],
|
2019-08-19 12:21:54 +01:00
|
|
|
mask = 1 << (y & 7);
|
|
|
|
switch(color) {
|
|
|
|
case WHITE: while(w--) { *pBuf++ |= mask; }; break;
|
|
|
|
case BLACK: mask = ~mask; while(w--) { *pBuf++ &= mask; }; break;
|
|
|
|
case INVERSE: while(w--) { *pBuf++ ^= mask; }; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-07-23 10:09:07 +01:00
|
|
|
|
|
|
|
void Adafruit_SSD1306::drawFastVLineInternal(
|
|
|
|
int16_t x, int16_t __y, int16_t __h, uint16_t color) {
|
2021-04-21 10:01:40 +01:00
|
|
|
if (!framebuffer) return;
|
2019-07-23 10:09:07 +01:00
|
|
|
if((x >= 0) && (x < WIDTH)) { // X coord in bounds?
|
|
|
|
if(__y < 0) { // Clip top
|
|
|
|
__h += __y;
|
|
|
|
__y = 0;
|
|
|
|
}
|
|
|
|
if((__y + __h) > HEIGHT) { // Clip bottom
|
|
|
|
__h = (HEIGHT - __y);
|
|
|
|
}
|
|
|
|
if(__h > 0) { // Proceed only if height is now positive
|
|
|
|
// this display doesn't need ints for coordinates,
|
|
|
|
// use local byte registers for faster juggling
|
|
|
|
uint8_t y = __y, h = __h;
|
2021-04-21 10:01:40 +01:00
|
|
|
uint8_t *pBuf = &framebuffer[(y / 8) * WIDTH + x];
|
2019-07-23 10:09:07 +01:00
|
|
|
|
|
|
|
// do the first partial byte, if necessary - this requires some masking
|
|
|
|
uint8_t mod = (y & 7);
|
|
|
|
if(mod) {
|
|
|
|
// mask off the high n bits we want to set
|
|
|
|
mod = 8 - mod;
|
|
|
|
// note - lookup table results in a nearly 10% performance
|
|
|
|
// improvement in fill* functions
|
|
|
|
// uint8_t mask = ~(0xFF >> mod);
|
|
|
|
static const uint8_t PROGMEM premask[8] =
|
|
|
|
{ 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE };
|
|
|
|
uint8_t mask = pgm_read_byte(&premask[mod]);
|
|
|
|
// adjust the mask if we're not going to reach the end of this byte
|
|
|
|
if(h < mod) mask &= (0XFF >> (mod - h));
|
|
|
|
|
|
|
|
switch(color) {
|
|
|
|
case WHITE: *pBuf |= mask; break;
|
|
|
|
case BLACK: *pBuf &= ~mask; break;
|
|
|
|
case INVERSE: *pBuf ^= mask; break;
|
|
|
|
}
|
|
|
|
pBuf += WIDTH;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(h >= mod) { // More to go?
|
|
|
|
h -= mod;
|
|
|
|
// Write solid bytes while we can - effectively 8 rows at a time
|
|
|
|
if(h >= 8) {
|
|
|
|
if(color == INVERSE) {
|
|
|
|
// separate copy of the code so we don't impact performance of
|
|
|
|
// black/white write version with an extra comparison per loop
|
|
|
|
do {
|
|
|
|
*pBuf ^= 0xFF; // Invert byte
|
|
|
|
pBuf += WIDTH; // Advance pointer 8 rows
|
|
|
|
h -= 8; // Subtract 8 rows from height
|
|
|
|
} while(h >= 8);
|
|
|
|
} else {
|
|
|
|
// store a local value to work with
|
|
|
|
uint8_t val = (color != BLACK) ? 255 : 0;
|
|
|
|
do {
|
|
|
|
*pBuf = val; // Set byte
|
|
|
|
pBuf += WIDTH; // Advance pointer 8 rows
|
|
|
|
h -= 8; // Subtract 8 rows from height
|
|
|
|
} while(h >= 8);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(h) { // Do the final partial byte, if necessary
|
|
|
|
mod = h & 7;
|
|
|
|
// this time we want to mask the low bits of the byte,
|
|
|
|
// vs the high bits we did above
|
|
|
|
// uint8_t mask = (1 << mod) - 1;
|
|
|
|
// note - lookup table results in a nearly 10% performance
|
|
|
|
// improvement in fill* functions
|
|
|
|
static const uint8_t PROGMEM postmask[8] =
|
|
|
|
{ 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F };
|
|
|
|
uint8_t mask = pgm_read_byte(&postmask[mod]);
|
|
|
|
switch(color) {
|
|
|
|
case WHITE: *pBuf |= mask; break;
|
|
|
|
case BLACK: *pBuf &= ~mask; break;
|
|
|
|
case INVERSE: *pBuf ^= mask; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // endif positive height
|
|
|
|
} // endif x in bounds
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@brief Return color of a single pixel in display buffer.
|
|
|
|
@param x
|
|
|
|
Column of display -- 0 at left to (screen width - 1) at right.
|
|
|
|
@param y
|
|
|
|
Row of display -- 0 at top to (screen height -1) at bottom.
|
|
|
|
@return true if pixel is set (usually WHITE, unless display invert mode
|
|
|
|
is enabled), false if clear (BLACK).
|
|
|
|
@note Reads from buffer contents; may not reflect current contents of
|
|
|
|
screen if display() has not been called.
|
|
|
|
*/
|
|
|
|
boolean Adafruit_SSD1306::getPixel(int16_t x, int16_t y) {
|
2021-04-21 10:01:40 +01:00
|
|
|
if (!framebuffer) return 0;
|
|
|
|
|
2019-07-23 10:09:07 +01:00
|
|
|
if((x >= 0) && (x < width()) && (y >= 0) && (y < height())) {
|
|
|
|
// Pixel is in-bounds. Rotate coordinates if needed.
|
|
|
|
switch(getRotation()) {
|
|
|
|
case 1:
|
|
|
|
ssd1306_swap(x, y);
|
|
|
|
x = WIDTH - x - 1;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
x = WIDTH - x - 1;
|
|
|
|
y = HEIGHT - y - 1;
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
ssd1306_swap(x, y);
|
|
|
|
y = HEIGHT - y - 1;
|
|
|
|
break;
|
|
|
|
}
|
2021-04-21 10:01:40 +01:00
|
|
|
return (framebuffer[x + (y / 8) * WIDTH] & (1 << (y & 7)));
|
2019-07-23 10:09:07 +01:00
|
|
|
}
|
|
|
|
return false; // Pixel out of bounds
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@brief Get base address of display buffer for direct reading or writing.
|
|
|
|
@return Pointer to an unsigned 8-bit array, column-major, columns padded
|
|
|
|
to full byte boundary if needed.
|
|
|
|
*/
|
|
|
|
uint8_t *Adafruit_SSD1306::getBuffer(void) {
|
2021-04-21 10:01:40 +01:00
|
|
|
return framebuffer;
|
2019-07-23 10:09:07 +01:00
|
|
|
}
|
|
|
|
|
2019-08-19 12:21:54 +01:00
|
|
|
|
2019-07-23 10:09:07 +01:00
|
|
|
// REFRESH DISPLAY ---------------------------------------------------------
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@brief Push data currently in RAM to SSD1306 display.
|
|
|
|
@return None (void).
|
|
|
|
@note Drawing operations are not visible until this function is
|
|
|
|
called. Call after each graphics command, or after a whole set
|
|
|
|
of graphics commands, as best needed by one's own application.
|
|
|
|
*/
|
|
|
|
void Adafruit_SSD1306::display(void) {
|
2021-04-21 10:01:40 +01:00
|
|
|
if (!framebuffer) return;
|
2020-03-27 15:38:29 +00:00
|
|
|
int16_t col_start = 0;
|
|
|
|
int16_t col_end = WIDTH - 1;
|
|
|
|
if ((64 == WIDTH) && (48 == HEIGHT)) { // for 64x48, we need to shift by 32 in both directions
|
|
|
|
col_start += 32;
|
|
|
|
col_end += 32;
|
|
|
|
}
|
|
|
|
|
2019-07-23 10:09:07 +01:00
|
|
|
TRANSACTION_START
|
|
|
|
static const uint8_t PROGMEM dlist1[] = {
|
|
|
|
SSD1306_PAGEADDR,
|
|
|
|
0, // Page start address
|
|
|
|
0xFF, // Page end (not really, but works here)
|
2020-03-27 15:38:29 +00:00
|
|
|
SSD1306_COLUMNADDR };
|
2019-07-23 10:09:07 +01:00
|
|
|
ssd1306_commandList(dlist1, sizeof(dlist1));
|
2020-03-27 15:38:29 +00:00
|
|
|
ssd1306_command1(col_start); // Column start address
|
|
|
|
ssd1306_command1(col_end); // Column end address
|
2019-07-23 10:09:07 +01:00
|
|
|
|
|
|
|
#if defined(ESP8266)
|
|
|
|
// ESP8266 needs a periodic yield() call to avoid watchdog reset.
|
|
|
|
// With the limited size of SSD1306 displays, and the fast bitrate
|
|
|
|
// being used (1 MHz or more), I think one yield() immediately before
|
|
|
|
// a screen write and one immediately after should cover it. But if
|
|
|
|
// not, if this becomes a problem, yields() might be added in the
|
|
|
|
// 32-byte transfer condition below.
|
|
|
|
yield();
|
|
|
|
#endif
|
|
|
|
uint16_t count = WIDTH * ((HEIGHT + 7) / 8);
|
2021-04-21 10:01:40 +01:00
|
|
|
uint8_t *ptr = framebuffer;
|
2019-07-23 10:09:07 +01:00
|
|
|
if(wire) { // I2C
|
|
|
|
wire->beginTransmission(i2caddr);
|
|
|
|
WIRE_WRITE((uint8_t)0x40);
|
|
|
|
uint8_t bytesOut = 1;
|
|
|
|
while(count--) {
|
|
|
|
if(bytesOut >= WIRE_MAX) {
|
|
|
|
wire->endTransmission();
|
|
|
|
wire->beginTransmission(i2caddr);
|
|
|
|
WIRE_WRITE((uint8_t)0x40);
|
|
|
|
bytesOut = 1;
|
|
|
|
}
|
|
|
|
WIRE_WRITE(*ptr++);
|
|
|
|
bytesOut++;
|
|
|
|
}
|
|
|
|
wire->endTransmission();
|
|
|
|
} else { // SPI
|
|
|
|
SSD1306_MODE_DATA
|
|
|
|
while(count--) SPIwrite(*ptr++);
|
|
|
|
}
|
|
|
|
TRANSACTION_END
|
|
|
|
#if defined(ESP8266)
|
|
|
|
yield();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
// SCROLLING FUNCTIONS -----------------------------------------------------
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@brief Activate a right-handed scroll for all or part of the display.
|
|
|
|
@param start
|
|
|
|
First row.
|
|
|
|
@param stop
|
|
|
|
Last row.
|
|
|
|
@return None (void).
|
|
|
|
*/
|
|
|
|
// To scroll the whole display, run: display.startscrollright(0x00, 0x0F)
|
|
|
|
void Adafruit_SSD1306::startscrollright(uint8_t start, uint8_t stop) {
|
|
|
|
TRANSACTION_START
|
|
|
|
static const uint8_t PROGMEM scrollList1a[] = {
|
|
|
|
SSD1306_RIGHT_HORIZONTAL_SCROLL,
|
|
|
|
0X00 };
|
|
|
|
ssd1306_commandList(scrollList1a, sizeof(scrollList1a));
|
|
|
|
ssd1306_command1(start);
|
|
|
|
ssd1306_command1(0X00);
|
|
|
|
ssd1306_command1(stop);
|
|
|
|
static const uint8_t PROGMEM scrollList1b[] = {
|
|
|
|
0X00,
|
|
|
|
0XFF,
|
|
|
|
SSD1306_ACTIVATE_SCROLL };
|
|
|
|
ssd1306_commandList(scrollList1b, sizeof(scrollList1b));
|
|
|
|
TRANSACTION_END
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@brief Activate a left-handed scroll for all or part of the display.
|
|
|
|
@param start
|
|
|
|
First row.
|
|
|
|
@param stop
|
|
|
|
Last row.
|
|
|
|
@return None (void).
|
|
|
|
*/
|
|
|
|
// To scroll the whole display, run: display.startscrollleft(0x00, 0x0F)
|
|
|
|
void Adafruit_SSD1306::startscrollleft(uint8_t start, uint8_t stop) {
|
|
|
|
TRANSACTION_START
|
|
|
|
static const uint8_t PROGMEM scrollList2a[] = {
|
|
|
|
SSD1306_LEFT_HORIZONTAL_SCROLL,
|
|
|
|
0X00 };
|
|
|
|
ssd1306_commandList(scrollList2a, sizeof(scrollList2a));
|
|
|
|
ssd1306_command1(start);
|
|
|
|
ssd1306_command1(0X00);
|
|
|
|
ssd1306_command1(stop);
|
|
|
|
static const uint8_t PROGMEM scrollList2b[] = {
|
|
|
|
0X00,
|
|
|
|
0XFF,
|
|
|
|
SSD1306_ACTIVATE_SCROLL };
|
|
|
|
ssd1306_commandList(scrollList2b, sizeof(scrollList2b));
|
|
|
|
TRANSACTION_END
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@brief Activate a diagonal scroll for all or part of the display.
|
|
|
|
@param start
|
|
|
|
First row.
|
|
|
|
@param stop
|
|
|
|
Last row.
|
|
|
|
@return None (void).
|
|
|
|
*/
|
|
|
|
// display.startscrolldiagright(0x00, 0x0F)
|
|
|
|
void Adafruit_SSD1306::startscrolldiagright(uint8_t start, uint8_t stop) {
|
|
|
|
TRANSACTION_START
|
|
|
|
static const uint8_t PROGMEM scrollList3a[] = {
|
|
|
|
SSD1306_SET_VERTICAL_SCROLL_AREA,
|
|
|
|
0X00 };
|
|
|
|
ssd1306_commandList(scrollList3a, sizeof(scrollList3a));
|
|
|
|
ssd1306_command1(HEIGHT);
|
|
|
|
static const uint8_t PROGMEM scrollList3b[] = {
|
|
|
|
SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL,
|
|
|
|
0X00 };
|
|
|
|
ssd1306_commandList(scrollList3b, sizeof(scrollList3b));
|
|
|
|
ssd1306_command1(start);
|
|
|
|
ssd1306_command1(0X00);
|
|
|
|
ssd1306_command1(stop);
|
|
|
|
static const uint8_t PROGMEM scrollList3c[] = {
|
|
|
|
0X01,
|
|
|
|
SSD1306_ACTIVATE_SCROLL };
|
|
|
|
ssd1306_commandList(scrollList3c, sizeof(scrollList3c));
|
|
|
|
TRANSACTION_END
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@brief Activate alternate diagonal scroll for all or part of the display.
|
|
|
|
@param start
|
|
|
|
First row.
|
|
|
|
@param stop
|
|
|
|
Last row.
|
|
|
|
@return None (void).
|
|
|
|
*/
|
|
|
|
// To scroll the whole display, run: display.startscrolldiagleft(0x00, 0x0F)
|
|
|
|
void Adafruit_SSD1306::startscrolldiagleft(uint8_t start, uint8_t stop) {
|
|
|
|
TRANSACTION_START
|
|
|
|
static const uint8_t PROGMEM scrollList4a[] = {
|
|
|
|
SSD1306_SET_VERTICAL_SCROLL_AREA,
|
|
|
|
0X00 };
|
|
|
|
ssd1306_commandList(scrollList4a, sizeof(scrollList4a));
|
|
|
|
ssd1306_command1(HEIGHT);
|
|
|
|
static const uint8_t PROGMEM scrollList4b[] = {
|
|
|
|
SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL,
|
|
|
|
0X00 };
|
|
|
|
ssd1306_commandList(scrollList4b, sizeof(scrollList4b));
|
|
|
|
ssd1306_command1(start);
|
|
|
|
ssd1306_command1(0X00);
|
|
|
|
ssd1306_command1(stop);
|
|
|
|
static const uint8_t PROGMEM scrollList4c[] = {
|
|
|
|
0X01,
|
|
|
|
SSD1306_ACTIVATE_SCROLL };
|
|
|
|
ssd1306_commandList(scrollList4c, sizeof(scrollList4c));
|
|
|
|
TRANSACTION_END
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@brief Cease a previously-begun scrolling action.
|
|
|
|
@return None (void).
|
|
|
|
*/
|
|
|
|
void Adafruit_SSD1306::stopscroll(void) {
|
|
|
|
TRANSACTION_START
|
|
|
|
ssd1306_command1(SSD1306_DEACTIVATE_SCROLL);
|
|
|
|
TRANSACTION_END
|
|
|
|
}
|
|
|
|
|
|
|
|
// OTHER HARDWARE SETTINGS -------------------------------------------------
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@brief Enable or disable display invert mode (white-on-black vs
|
|
|
|
black-on-white).
|
|
|
|
@param i
|
|
|
|
If true, switch to invert mode (black-on-white), else normal
|
|
|
|
mode (white-on-black).
|
|
|
|
@return None (void).
|
|
|
|
@note This has an immediate effect on the display, no need to call the
|
|
|
|
display() function -- buffer contents are not changed, rather a
|
|
|
|
different pixel mode of the display hardware is used. When
|
|
|
|
enabled, drawing BLACK (value 0) pixels will actually draw white,
|
|
|
|
WHITE (value 1) will draw black.
|
|
|
|
*/
|
|
|
|
void Adafruit_SSD1306::invertDisplay(boolean i) {
|
|
|
|
TRANSACTION_START
|
|
|
|
ssd1306_command1(i ? SSD1306_INVERTDISPLAY : SSD1306_NORMALDISPLAY);
|
|
|
|
TRANSACTION_END
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@brief Dim the display.
|
|
|
|
@param dim
|
|
|
|
true to enable lower brightness mode, false for full brightness.
|
|
|
|
@return None (void).
|
|
|
|
@note This has an immediate effect on the display, no need to call the
|
|
|
|
display() function -- buffer contents are not changed.
|
|
|
|
*/
|
|
|
|
void Adafruit_SSD1306::dim(boolean dim) {
|
|
|
|
uint8_t contrast;
|
|
|
|
|
|
|
|
if(dim) {
|
|
|
|
contrast = 0; // Dimmed display
|
|
|
|
} else {
|
|
|
|
contrast = (vccstate == SSD1306_EXTERNALVCC) ? 0x9F : 0xCF;
|
|
|
|
}
|
|
|
|
// the range of contrast to too small to be really useful
|
|
|
|
// it is useful to dim the display
|
|
|
|
TRANSACTION_START
|
|
|
|
ssd1306_command1(SSD1306_SETCONTRAST);
|
|
|
|
ssd1306_command1(contrast);
|
|
|
|
TRANSACTION_END
|
|
|
|
}
|
|
|
|
|
2019-08-19 17:17:44 +01:00
|
|
|
void Adafruit_SSD1306::DisplayOnff(int8_t on) {
|
|
|
|
TRANSACTION_START
|
|
|
|
if(on) {
|
|
|
|
ssd1306_command1(SSD1306_DISPLAYON);
|
|
|
|
} else {
|
|
|
|
ssd1306_command1(SSD1306_DISPLAYOFF);
|
|
|
|
}
|
|
|
|
TRANSACTION_END
|
|
|
|
}
|
|
|
|
|
2019-08-19 12:21:54 +01:00
|
|
|
void Adafruit_SSD1306::Updateframe(void) {
|
|
|
|
display();
|
|
|
|
}
|