diff --git a/module/fuses.bat b/module/fuses.bat index 78c7e0f..f767ae9 100644 --- a/module/fuses.bat +++ b/module/fuses.bat @@ -1,3 +1,3 @@ REM Internal 8 Mhz oscillator, no clock division REM http://www.engbedded.com/fusecalc/ -@avrdude -c usbtiny -p t2313 -e -U lfuse:w:0xe4:m -U hfuse:w:0xdf:m -U efuse:w:0xff:m \ No newline at end of file +@avrdude -c usbtiny -p m328p -e -U lfuse:w:0xE2:m -U hfuse:w:0xd9:m -U efuse:w:0x07:m \ No newline at end of file diff --git a/module/platformio.ini b/module/platformio.ini index 307c668..dc77577 100644 --- a/module/platformio.ini +++ b/module/platformio.ini @@ -13,7 +13,11 @@ env_default = board [env:board] platform = atmelavr -board = attiny4313 +framework = arduino +board = 328p8m board_build.f_cpu = 8000000L upload_speed = 115200 -upload_protocol = usbtiny \ No newline at end of file +upload_protocol = usbtiny + +lib_deps = + Adafruit GFX Library \ No newline at end of file diff --git a/module/res/CommIcon.psd b/module/res/CommIcon.psd new file mode 100644 index 0000000..06de1d1 Binary files /dev/null and b/module/res/CommIcon.psd differ diff --git a/module/res/WaitCursor.psd b/module/res/WaitCursor.psd new file mode 100644 index 0000000..6050037 Binary files /dev/null and b/module/res/WaitCursor.psd differ diff --git a/module/src/display.c b/module/src/display.c deleted file mode 100644 index 9615be8..0000000 --- a/module/src/display.c +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Stairs lighting - * Copyright 2017 (c) Mark van Renswoude - * - * https://git.x2software.net/pub/Stairs -*/ -#include "display.h" -#include -#include -#include -#include "lib/VeryTinySSD1306.h" -#include "lib/VeryTinyBigChars.h" -#include "lib/TinyMillis.h" -#include "global.h" - - -#define CharSpacing 4 -#define CharOuterWidth (SSD1306_CharWidth_32px + CharSpacing) -#define SingleCharCenteredX ((SSD1306_DisplayWidth - SSD1306_CharWidth_32px) / 2) -#define WaitAnimationInterval 250 - - -#define WaitAnimationMaxStep 3 -const uint16_t WaitAnimation [] PROGMEM = { - 0b0000000000000100, // Left - 0b0000000010000000, // Middle - 0b0001000000000000, // Right - 0b0000000010000000 // Middle -}; -/* -#define WaitAnimationMaxStep 7 -const uint16_t WaitAnimation [] PROGMEM = { - 0b0000000001000000, // Top - 0b0000100000000000, // Top-right - 0b0001000000000000, // Right - 0b0010000000000000, // Bottom-right - 0b0000000100000000, // Bottom - 0b0000000000001000, // Bottom-left - 0b0000000000000100, // Left - 0b0000000000000010 // Top-left -}; -*/ - - -bool isWait = false; -uint16_t lastWait = 0; -uint8_t waitAnimationStep = 0; - - - -inline void display_drawNumber(uint8_t* x, uint8_t value) -{ - if (value > 9) - { - ssd1306_drawdigit_32px(*x, value / 10); - *x += CharOuterWidth; - ssd1306_drawdigit_32px(*x, value % 10); - } - else - ssd1306_drawdigit_32px(*x, value); - - *x += CharOuterWidth; -} - - -void display_init(void) -{ - ssd1306_begin_default(); - ssd1306_clear(); - ssd1306_on(); - ssd1306_switchRenderFrame(); -} - - -void display_drawIndex(void) -{ - ssd1306_clear(); - - uint8_t moduleIndex = global_getModuleIndex(); - - if (moduleIndex == ModuleIndexUndefined) - { - ssd1306_drawminus_32px(SingleCharCenteredX); - } - else - { - uint8_t firstStep = (moduleIndex * 2) + 1; - uint8_t totalWidth = 3 * CharOuterWidth; - - if (firstStep == 9) - totalWidth += CharOuterWidth; - else if (firstStep > 9) - totalWidth += 2 * CharOuterWidth; - - - uint8_t currentX = (SSD1306_DisplayWidth - totalWidth) / 2; - display_drawNumber(¤tX, firstStep); - - ssd1306_drawminus_32px(currentX); - currentX += CharOuterWidth; - - display_drawNumber(¤tX, firstStep + 1); - } - - ssd1306_switchFrame(); -} - - -void display_drawWait(void) -{ - uint16_t currentTime = millis(); - - if (isWait && currentTime - lastWait < WaitAnimationInterval) - return; - - ssd1306_clear(); - ssd1306_drawchar_32px(SingleCharCenteredX, pgm_read_word(&WaitAnimation[waitAnimationStep])); - ssd1306_switchFrame(); - - waitAnimationStep++; - if (waitAnimationStep > WaitAnimationMaxStep) - waitAnimationStep = 0; - - isWait = true; - lastWait = currentTime; -} \ No newline at end of file diff --git a/module/src/display.cpp b/module/src/display.cpp new file mode 100644 index 0000000..df95c31 --- /dev/null +++ b/module/src/display.cpp @@ -0,0 +1,197 @@ +/* + * Stairs lighting + * Copyright 2017 (c) Mark van Renswoude + * + * https://git.x2software.net/pub/Stairs +*/ +#include "display.h" +#include +#include "global.h" +#include "icons.h" + +#define WaitAnimationInterval 150 + + +void Display::init() +{ + mDisplay = new Adafruit_SSD1306(128, 32, &Wire, -1); + mDisplay->begin(SSD1306_SWITCHCAPVCC, 0x3C); +} + + +void Display::show(Screen screen) +{ + switch (screen) + { + case Screen::Blank: + off(); + break; + + case Screen::WaitingForComm: + drawWaitingForComm(); + break; + + case Screen::ModuleIndex: + drawModuleIndex(); + break; + } +} + + +void Display::drawWaitingForComm() +{ + uint32_t currentTime = millis(); + if (mLastScreen == Screen::WaitingForComm && currentTime - mLastWaiting < WaitAnimationInterval) + return; + + checkOn(); + mDisplay->clearDisplay(); + drawTitle("Waiting for signal"); + + uint8_t xOffset = (mWaitAnimationStep == 1 || mWaitAnimationStep == 2) ? WaitCursorSegmentWidth : 0; + uint8_t yOffset = (mWaitAnimationStep == 2 || mWaitAnimationStep == 3) ? WaitCursorSegmentHeight : 0; + drawRotatedBitmap(xOffset, mDisplay->height() - (WaitCursorSegmentHeight * 2) + yOffset, WaitCursorSegment, WaitCursorSegmentWidth, WaitCursorSegmentHeight, mWaitAnimationStep); + + mDisplay->display(); + + mWaitAnimationStep++; + if (mWaitAnimationStep > 3) + mWaitAnimationStep = 0; + + mLastScreen = Screen::WaitingForComm; + mLastWaiting = currentTime; +} + + +void Display::drawModuleIndex() +{ + uint8_t moduleIndex = settings.getModuleIndex(); + if (mLastScreen == Screen::ModuleIndex && mLastModuleIndex == moduleIndex) + return; + + checkOn(); + mDisplay->clearDisplay(); + drawTitle("Steps"); + drawCommIcon(); + + mDisplay->setFont(&FreeSans12pt7b); + mDisplay->setCursor(0, mDisplay->height()); + + // ToDo show numbers based on module index + uint8_t firstStep = (moduleIndex * 2) + 1; + mDisplay->print(firstStep); + mDisplay->print(" - "); + mDisplay->print(firstStep + 1); + + mDisplay->display(); + + mLastScreen = Screen::ModuleIndex; + mLastModuleIndex = moduleIndex; +} + + +void Display::off() +{ + if (mLastScreen != Screen::Blank) + { + mDisplay->ssd1306_command(SSD1306_DISPLAYOFF); + mLastScreen = Screen::Blank; + } +} + + +void Display::checkOn() +{ + if (mLastScreen == Screen::Blank) + mDisplay->ssd1306_command(SSD1306_DISPLAYON); +} + + +void Display::drawTitle(const char* title) +{ + mDisplay->setTextSize(1); + mDisplay->setTextColor(WHITE); + + mDisplay->setFont(NULL); + mDisplay->setCursor(0, 0); + mDisplay->print(title); +} + + +void Display::drawCommIcon() +{ + // ToDo show actual state depending on last received message (should never be more than half a second ago) + mDisplay->drawBitmap(mDisplay->width() - IconCommWidth, 0, IconCommOff, IconCommWidth, IconCommHeight, 1); +} + + + +typedef void (*WritePixelProc)(Adafruit_SSD1306* display, int8_t boundsX, int8_t boundsY, int8_t boundsW, int8_t boundsH, int8_t relativeX, int8_t relativeY); + + +void writePixel0(Adafruit_SSD1306* display, int8_t boundsX, int8_t boundsY, int8_t boundsW, int8_t boundsH, int8_t relativeX, int8_t relativeY) +{ + display->writePixel(boundsX + relativeX, boundsY + relativeY, 1); +} + +void writePixel90(Adafruit_SSD1306* display, int8_t boundsX, int8_t boundsY, int8_t boundsW, int8_t boundsH, int8_t relativeX, int8_t relativeY) +{ + display->writePixel(boundsX + boundsW - relativeY, boundsY + relativeX, 1); +} + +void writePixel180(Adafruit_SSD1306* display, int8_t boundsX, int8_t boundsY, int8_t boundsW, int8_t boundsH, int8_t relativeX, int8_t relativeY) +{ + display->writePixel(boundsX + boundsW - relativeX, boundsY + boundsH - relativeY, 1); +} + +void writePixel270(Adafruit_SSD1306* display, int8_t boundsX, int8_t boundsY, int8_t boundsW, int8_t boundsH, int8_t relativeX, int8_t relativeY) +{ + display->writePixel(boundsX + relativeY, boundsY + boundsH - relativeX, 1); +} + + +void Display::drawRotatedBitmap(int8_t x, int8_t y, const uint8_t bitmap[], int8_t w, int8_t h, uint8_t rotation) +{ + int16_t byteWidth = (w + 7) / 8; + int8_t byte = 0; + + mDisplay->startWrite(); + + WritePixelProc writePixel; + switch (rotation) + { + case 1: // 90 degrees + writePixel = writePixel90; + break; + + case 2: // 180 degrees + writePixel = writePixel180; + break; + + case 3: // 270 degrees + writePixel = writePixel270; + break; + + default: // 0 degrees + writePixel = writePixel0; + break; + } + + for(int8_t relativeY = 0; relativeY < h; relativeY++) + { + for(int8_t relativeX = 0; relativeX < w; relativeX++) + { + if(relativeX & 7) + byte <<= 1; + else + byte = pgm_read_byte(&bitmap[relativeY * byteWidth + relativeX / 8]); + + if (byte & 0x80) + writePixel(mDisplay, x, y, w, h, relativeX, relativeY); + } + } + + mDisplay->endWrite(); +} + + diff --git a/module/src/display.h b/module/src/display.h index 3a82d42..a4c0b5b 100644 --- a/module/src/display.h +++ b/module/src/display.h @@ -7,8 +7,41 @@ #ifndef __display #define __display -void display_init(void); -void display_drawIndex(void); -void display_drawWait(void); +#include +#include +#include +#include "lib/Adafruit_SSD1306.h" + + +enum struct Screen: uint8_t +{ + Blank, + WaitingForComm, + ModuleIndex +}; + + +class Display +{ + private: + Adafruit_SSD1306* mDisplay; + Screen mLastScreen = Screen::Blank; + uint8_t mLastModuleIndex; + uint32_t mLastWaiting; + uint8_t mWaitAnimationStep = 0; + + void drawRotatedBitmap(int8_t x, int8_t y, const uint8_t bitmap[], int8_t w, int8_t h, uint8_t rotation); + + void checkOn(); + void drawTitle(const char* title); + void drawCommIcon(); + + void drawWaitingForComm(); + void drawModuleIndex(); + public: + void init(); + void show(Screen screen); + void off(); +}; #endif \ No newline at end of file diff --git a/module/src/global.c b/module/src/global.c deleted file mode 100644 index e38bb7e..0000000 --- a/module/src/global.c +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Stairs lighting - * Copyright 2017 (c) Mark van Renswoude - * - * https://git.x2software.net/pub/Stairs -*/ -#include "global.h" -#include - - -// EEPROM -// First byte is 0xAA to recognize uninitialised EEPROM -#define EEPROMHeader 0xAA - -// Second byte is a version indicator -#define EEPROMCurrentVersion 1 - -// Version 1 fields -// index: 2 -uint8_t moduleIndex = ModuleIndexUndefined; - - -void global_init(void) -{ - uint8_t address = 0; - if (eeprom_read_byte(&address) != EEPROMHeader) - { - eeprom_write_byte(&address, EEPROMHeader); address++; - eeprom_write_byte(&address, EEPROMCurrentVersion); address++; - eeprom_write_byte(&address, moduleIndex); address++; - } - else - { - address++; - uint8_t version = eeprom_read_byte(&address); address++; - if (version >= 1) - { - moduleIndex = eeprom_read_byte(&address); address++; - } - } -} - - -uint8_t global_getModuleIndex(void) -{ - return moduleIndex; -} - - -void global_setModuleIndex(uint8_t index) -{ - if (index == moduleIndex) - return; - - moduleIndex = index; - - uint8_t address = 2; - eeprom_write_byte(&address, moduleIndex); -} \ No newline at end of file diff --git a/module/src/global.cpp b/module/src/global.cpp new file mode 100644 index 0000000..1c827ce --- /dev/null +++ b/module/src/global.cpp @@ -0,0 +1,29 @@ +/* + * Stairs lighting + * Copyright 2017 (c) Mark van Renswoude + * + * https://git.x2software.net/pub/Stairs +*/ +#include "global.h" + + +int serialRead() +{ + return Serial.read(); +} + +int serialAvailable() +{ + return Serial.available(); +} + +size_t serialWrite(const byte what) +{ + return Serial.write(what); +} + + +Settings settings; +Display display; +RS485 comm(serialRead, serialAvailable, serialWrite, 20); +PCA9685 ledDriver; \ No newline at end of file diff --git a/module/src/global.h b/module/src/global.h index ad06eda..7771e52 100644 --- a/module/src/global.h +++ b/module/src/global.h @@ -7,13 +7,14 @@ #ifndef __global #define __global -#include +#include "lib/RS485_non_blocking.h" +#include "lib/PCA9685.h" +#include "settings.h" +#include "display.h" -#define ModuleIndexUndefined 0xff - -void global_init(void); - -uint8_t global_getModuleIndex(void); -void global_setModuleIndex(uint8_t index); +extern Settings settings; +extern Display display; +extern RS485 comm; +extern PCA9685 ledDriver; #endif \ No newline at end of file diff --git a/module/src/icons.h b/module/src/icons.h new file mode 100644 index 0000000..83d5bf7 --- /dev/null +++ b/module/src/icons.h @@ -0,0 +1,28 @@ +/* + * Stairs lighting + * Copyright 2017 (c) Mark van Renswoude + * + * https://git.x2software.net/pub/Stairs +*/ + +// http://javl.github.io/image2cpp/ +// Draw mode: horizontal +const uint8_t IconCommWidth = 12; +const uint8_t IconCommHeight = 8; + +const uint8_t PROGMEM IconCommOff[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +const uint8_t PROGMEM IconCommOn[] = { + 0x40, 0x20, 0x40, 0x20, 0x90, 0x90, 0xa6, 0x50, 0xa6, 0x50, 0x90, 0x90, 0x40, 0x20, 0x40, 0x20 +}; + + +const uint8_t WaitCursorSegmentWidth = 10; +const uint8_t WaitCursorSegmentHeight = 10; + +const uint8_t PROGMEM WaitCursorSegment[] = { + 0x07, 0x80, 0x1f, 0x80, 0x3e, 0x00, 0x78, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xe0, 0x00, 0xc0, 0x00, + 0xc0, 0x00, 0x00, 0x00 +}; \ No newline at end of file diff --git a/module/src/lib/Adafruit_SSD1306.cpp b/module/src/lib/Adafruit_SSD1306.cpp new file mode 100644 index 0000000..821bbfb --- /dev/null +++ b/module/src/lib/Adafruit_SSD1306.cpp @@ -0,0 +1,1109 @@ +/*! + * @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 + * Adafruit_GFX 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. + * + */ + +/* + Modifications for Stairs project: + - Removed the Adafruit splash screen. I'm not going to show it anyways, so all it + does is take up valuable program space. +*/ + +#ifdef __AVR__ + #include +#elif defined(ESP8266) || defined(ESP32) + #include +#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 +#endif + +#include +#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) : + Adafruit_GFX(w, h), spi(NULL), wire(twi ? twi : &Wire), buffer(NULL), + 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, + int8_t cs_pin) : Adafruit_GFX(w, h), spi(NULL), wire(NULL), buffer(NULL), + 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) : + Adafruit_GFX(w, h), spi(spi ? spi : &SPI), wire(NULL), buffer(NULL), + 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) : + Adafruit_GFX(SSD1306_LCDWIDTH, SSD1306_LCDHEIGHT), spi(NULL), wire(NULL), + buffer(NULL), mosiPin(mosi_pin), clkPin(sclk_pin), dcPin(dc_pin), + 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, + int8_t cs_pin) : Adafruit_GFX(SSD1306_LCDWIDTH, SSD1306_LCDHEIGHT), + spi(&SPI), wire(NULL), buffer(NULL), mosiPin(-1), clkPin(-1), + dcPin(dc_pin), csPin(cs_pin), rstPin(rst_pin) { +#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) : + Adafruit_GFX(SSD1306_LCDWIDTH, SSD1306_LCDHEIGHT), spi(NULL), wire(&Wire), + buffer(NULL), mosiPin(-1), clkPin(-1), dcPin(-1), csPin(-1), + rstPin(rst_pin) { +} + +/*! + @brief Destructor for Adafruit_SSD1306 object. +*/ +Adafruit_SSD1306::~Adafruit_SSD1306(void) { + if(buffer) { + free(buffer); + buffer = NULL; + } +} + +// 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) { + + if((!buffer) && !(buffer = (uint8_t *)malloc(WIDTH * ((HEIGHT + 7) / 8)))) + return false; + + clearDisplay(); + /* + 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); + } + */ + + 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); + } 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 +} + +// 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) { + 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) { + case WHITE: buffer[x + (y/8)*WIDTH] |= (1 << (y&7)); break; + case BLACK: buffer[x + (y/8)*WIDTH] &= ~(1 << (y&7)); break; + case INVERSE: buffer[x + (y/8)*WIDTH] ^= (1 << (y&7)); break; + } + } +} + +/*! + @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) { + memset(buffer, 0, WIDTH * ((HEIGHT + 7) / 8)); +} + +/*! + @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); +} + +void Adafruit_SSD1306::drawFastHLineInternal( + int16_t x, int16_t y, int16_t w, uint16_t color) { + + 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 + uint8_t *pBuf = &buffer[(y / 8) * WIDTH + x], + 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; + } + } + } +} + +/*! + @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); +} + +void Adafruit_SSD1306::drawFastVLineInternal( + int16_t x, int16_t __y, int16_t __h, uint16_t color) { + + 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; + uint8_t *pBuf = &buffer[(y / 8) * WIDTH + x]; + + // 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) { + 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; + } + return (buffer[x + (y / 8) * WIDTH] & (1 << (y & 7))); + } + 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) { + return buffer; +} + +// 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) { + TRANSACTION_START + static const uint8_t PROGMEM dlist1[] = { + SSD1306_PAGEADDR, + 0, // Page start address + 0xFF, // Page end (not really, but works here) + SSD1306_COLUMNADDR, + 0 }; // Column start address + ssd1306_commandList(dlist1, sizeof(dlist1)); + ssd1306_command1(WIDTH - 1); // Column end address + +#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); + uint8_t *ptr = buffer; + 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 +} + diff --git a/module/src/lib/Adafruit_SSD1306.h b/module/src/lib/Adafruit_SSD1306.h new file mode 100644 index 0000000..91e3b02 --- /dev/null +++ b/module/src/lib/Adafruit_SSD1306.h @@ -0,0 +1,185 @@ +/*! + * @file Adafruit_SSD1306.h + * + * This is part of 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! + * + * Written by Limor Fried/Ladyada for Adafruit Industries, with + * contributions from the open source community. + * + * BSD license, all text above, and the splash screen header file, + * must be included in any redistribution. + * + */ + +/* + Modifications for Stairs project: + - Removed the Adafruit splash screen. I'm not going to show it anyways, so all it + does is take up valuable program space. +*/ + +#ifndef _Adafruit_SSD1306_H_ +#define _Adafruit_SSD1306_H_ + +// ONE of the following three lines must be #defined: +//#define SSD1306_128_64 ///< DEPRECTAED: old way to specify 128x64 screen +#define SSD1306_128_32 ///< DEPRECATED: old way to specify 128x32 screen +//#define SSD1306_96_16 ///< DEPRECATED: old way to specify 96x16 screen +// This establishes the screen dimensions in old Adafruit_SSD1306 sketches +// (NEW CODE SHOULD IGNORE THIS, USE THE CONSTRUCTORS THAT ACCEPT WIDTH +// AND HEIGHT ARGUMENTS). + +#if defined(ARDUINO_STM32_FEATHER) + typedef class HardwareSPI SPIClass; +#endif + +#include +#include +#include + +#if defined(__AVR__) + typedef volatile uint8_t PortReg; + typedef uint8_t PortMask; + #define HAVE_PORTREG +#elif defined(__SAM3X8E__) + typedef volatile RwReg PortReg; + typedef uint32_t PortMask; + #define HAVE_PORTREG +#elif defined(__arm__) || defined(ARDUINO_FEATHER52) + typedef volatile uint32_t PortReg; + typedef uint32_t PortMask; + #define HAVE_PORTREG +#endif + +#define BLACK 0 ///< Draw 'off' pixels +#define WHITE 1 ///< Draw 'on' pixels +#define INVERSE 2 ///< Invert pixels + +#define SSD1306_MEMORYMODE 0x20 ///< See datasheet +#define SSD1306_COLUMNADDR 0x21 ///< See datasheet +#define SSD1306_PAGEADDR 0x22 ///< See datasheet +#define SSD1306_SETCONTRAST 0x81 ///< See datasheet +#define SSD1306_CHARGEPUMP 0x8D ///< See datasheet +#define SSD1306_SEGREMAP 0xA0 ///< See datasheet +#define SSD1306_DISPLAYALLON_RESUME 0xA4 ///< See datasheet +#define SSD1306_DISPLAYALLON 0xA5 ///< Not currently used +#define SSD1306_NORMALDISPLAY 0xA6 ///< See datasheet +#define SSD1306_INVERTDISPLAY 0xA7 ///< See datasheet +#define SSD1306_SETMULTIPLEX 0xA8 ///< See datasheet +#define SSD1306_DISPLAYOFF 0xAE ///< See datasheet +#define SSD1306_DISPLAYON 0xAF ///< See datasheet +#define SSD1306_COMSCANINC 0xC0 ///< Not currently used +#define SSD1306_COMSCANDEC 0xC8 ///< See datasheet +#define SSD1306_SETDISPLAYOFFSET 0xD3 ///< See datasheet +#define SSD1306_SETDISPLAYCLOCKDIV 0xD5 ///< See datasheet +#define SSD1306_SETPRECHARGE 0xD9 ///< See datasheet +#define SSD1306_SETCOMPINS 0xDA ///< See datasheet +#define SSD1306_SETVCOMDETECT 0xDB ///< See datasheet + +#define SSD1306_SETLOWCOLUMN 0x00 ///< Not currently used +#define SSD1306_SETHIGHCOLUMN 0x10 ///< Not currently used +#define SSD1306_SETSTARTLINE 0x40 ///< See datasheet + +#define SSD1306_EXTERNALVCC 0x01 ///< External display voltage source +#define SSD1306_SWITCHCAPVCC 0x02 ///< Gen. display voltage from 3.3V + +#define SSD1306_RIGHT_HORIZONTAL_SCROLL 0x26 ///< Init rt scroll +#define SSD1306_LEFT_HORIZONTAL_SCROLL 0x27 ///< Init left scroll +#define SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL 0x29 ///< Init diag scroll +#define SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL 0x2A ///< Init diag scroll +#define SSD1306_DEACTIVATE_SCROLL 0x2E ///< Stop scroll +#define SSD1306_ACTIVATE_SCROLL 0x2F ///< Start scroll +#define SSD1306_SET_VERTICAL_SCROLL_AREA 0xA3 ///< Set scroll range + +// Deprecated size stuff for backwards compatibility with old sketches +#if defined SSD1306_128_64 + #define SSD1306_LCDWIDTH 128 ///< DEPRECATED: width w/SSD1306_128_64 defined + #define SSD1306_LCDHEIGHT 64 ///< DEPRECATED: height w/SSD1306_128_64 defined +#endif +#if defined SSD1306_128_32 + #define SSD1306_LCDWIDTH 128 ///< DEPRECATED: width w/SSD1306_128_32 defined + #define SSD1306_LCDHEIGHT 32 ///< DEPRECATED: height w/SSD1306_128_32 defined +#endif +#if defined SSD1306_96_16 + #define SSD1306_LCDWIDTH 96 ///< DEPRECATED: width w/SSD1306_96_16 defined + #define SSD1306_LCDHEIGHT 16 ///< DEPRECATED: height w/SSD1306_96_16 defined +#endif + +/*! + @brief Class that stores state and functions for interacting with + SSD1306 OLED displays. +*/ +class Adafruit_SSD1306 : public Adafruit_GFX { + public: + // NEW CONSTRUCTORS -- recommended for new projects + Adafruit_SSD1306(uint8_t w, uint8_t h, TwoWire *twi=&Wire, int8_t rst_pin=-1, + uint32_t clkDuring=400000UL, uint32_t clkAfter=100000UL); + Adafruit_SSD1306(uint8_t w, uint8_t h, int8_t mosi_pin, int8_t sclk_pin, + int8_t dc_pin, int8_t rst_pin, int8_t cs_pin); + 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=8000000UL); + + // DEPRECATED CONSTRUCTORS - for back compatibility, avoid in new projects + Adafruit_SSD1306(int8_t mosi_pin, int8_t sclk_pin, int8_t dc_pin, + int8_t rst_pin, int8_t cs_pin); + Adafruit_SSD1306(int8_t dc_pin, int8_t rst_pin, int8_t cs_pin); + Adafruit_SSD1306(int8_t rst_pin = -1); + + ~Adafruit_SSD1306(void); + + boolean begin(uint8_t switchvcc=SSD1306_SWITCHCAPVCC, + uint8_t i2caddr=0, boolean reset=true, + boolean periphBegin=true); + void display(void); + void clearDisplay(void); + void invertDisplay(boolean i); + void dim(boolean dim); + void drawPixel(int16_t x, int16_t y, uint16_t color); + virtual void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color); + virtual void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color); + void startscrollright(uint8_t start, uint8_t stop); + void startscrollleft(uint8_t start, uint8_t stop); + void startscrolldiagright(uint8_t start, uint8_t stop); + void startscrolldiagleft(uint8_t start, uint8_t stop); + void stopscroll(void); + void ssd1306_command(uint8_t c); + boolean getPixel(int16_t x, int16_t y); + uint8_t *getBuffer(void); + + private: + inline void SPIwrite(uint8_t d) __attribute__((always_inline)); + void drawFastHLineInternal(int16_t x, int16_t y, int16_t w, + uint16_t color); + void drawFastVLineInternal(int16_t x, int16_t y, int16_t h, + uint16_t color); + void ssd1306_command1(uint8_t c); + void ssd1306_commandList(const uint8_t *c, uint8_t n); + + SPIClass *spi; + TwoWire *wire; + uint8_t *buffer; + int8_t i2caddr, vccstate, page_end; + int8_t mosiPin , clkPin , dcPin , csPin, rstPin; +#ifdef HAVE_PORTREG + PortReg *mosiPort , *clkPort , *dcPort , *csPort; + PortMask mosiPinMask, clkPinMask, dcPinMask, csPinMask; +#endif +#if defined(SPI_HAS_TRANSACTION) + SPISettings spiSettings; +#endif +#if ARDUINO >= 157 + uint32_t wireClk; // Wire speed for SSD1306 transfers + uint32_t restoreClk; // Wire speed following SSD1306 transfers +#endif +}; + +#endif // _Adafruit_SSD1306_H_ diff --git a/module/src/lib/PCA9685.cpp b/module/src/lib/PCA9685.cpp new file mode 100644 index 0000000..a5dbb83 --- /dev/null +++ b/module/src/lib/PCA9685.cpp @@ -0,0 +1,127 @@ +/* + * PCA9685 library for Arduino + * Copyright 2017 (c) Mark van Renswoude +*/ +#include +#include "./PCA9685.h" +#include + +#include + + +void PCA9685::setAddress(uint8_t address) +{ + this->mAddress = address; +} + + +uint8_t PCA9685::read(uint8_t registerAddress) +{ + uint8_t result = 0; + + Wire.beginTransmission(this->mAddress); + Wire.write(registerAddress); + Wire.endTransmission(); + + Wire.requestFrom(this->mAddress, (uint8_t)1); + if (Wire.available()) + result = Wire.read(); + + return result; +} + + +void PCA9685::write(uint8_t registerAddress, uint8_t value) +{ + Wire.beginTransmission(this->mAddress); + Wire.write(registerAddress); + Wire.write(value); + Wire.endTransmission(); +} + + +inline void PCA9685::write(uint8_t data) +{ + Wire.write(data); +} + + +static const float prescaleValue = 25000000 / 4096; + +void PCA9685::setPWMFrequency(float frequency) +{ + // Credit to the Adafruit PWM Servo Driver library for these calculations + // https://github.com/adafruit/Adafruit-PWM-Servo-Driver-Library/ + uint8_t prescale = floor(prescaleValue / (frequency * 0.9) - 0.5); + + // Sleep while changing the frequency + uint8_t oldMode = this->read(PCA9685::RegisterMode1) & !PCA9685::Mode1Sleep; + uint8_t newMode = (oldMode & !PCA9685::Mode1Restart) | PCA9685::Mode1Sleep; + + this->write(PCA9685::RegisterMode1, newMode); + this->write(PCA9685::RegisterPrescale, prescale); + this->write(PCA9685::RegisterMode1, oldMode); + + // According to the datasheet: + // It takes 500 us max. for the oscillator to be up and running once + // SLEEP bit has been set to logic 0 + // + // The Adafruit library uses 5 milliseconds, so I'll stick to that as well. + delay(5); + + // Restart and turn on auto-increment (required for SetPWM) + this->write(PCA9685::RegisterMode1, oldMode | PCA9685::Mode1Restart | PCA9685::Mode1AI | PCA9685::Mode1AllCall); +} + + +void PCA9685::setPWM(uint8_t pin, uint16_t value) +{ + if (value < 0) value = 0; + if (value > 4095) value = 4095; + + if (value == 4095) + this->setPWM(pin, 4096, 0); + else if (value == 0) + this->setPWM(pin, 0, 4096); + else + this->setPWM(pin, 0, value); +} + + +void PCA9685::setPWM(uint8_t pin, uint16_t on, uint16_t off) +{ + Wire.beginTransmission(this->mAddress); + this->write(PCA9685::RegisterLED0OnL + (4 * pin)); + this->write(on); + this->write(on >> 8); + this->write(off); + this->write(off >> 8); + Wire.endTransmission(); +} + + + +void PCA9685::setAll(uint16_t value) +{ + if (value < 0) value = 0; + if (value > 4095) value = 4095; + + if (value == 4095) + this->setAll(4096, 0); + else if (value == 0) + this->setAll(0, 4096); + else + this->setAll(0, value); +} + + +void PCA9685::setAll(uint16_t on, uint16_t off) +{ + Wire.beginTransmission(this->mAddress); + this->write(PCA9685::RegisterAllLEDOnL); + this->write(on); + this->write(on >> 8); + this->write(off); + this->write(off >> 8); + Wire.endTransmission(); +} \ No newline at end of file diff --git a/module/src/lib/PCA9685.h b/module/src/lib/PCA9685.h new file mode 100644 index 0000000..d5febfa --- /dev/null +++ b/module/src/lib/PCA9685.h @@ -0,0 +1,48 @@ +/* + * PCA9685 library for Arduino + * Copyright 2017 (c) Mark van Renswoude +*/ +#ifndef __PCA9685 +#define __PCA9685 + +#include + + +class PCA9685 +{ + private: + uint8_t mAddress; + + protected: + uint8_t read(uint8_t registerAddress); + void write(uint8_t registerAddress, uint8_t value); + inline void write(uint8_t data); + + public: + static const uint16_t Off = 0; + static const uint16_t On = 4095; + + static const uint8_t RegisterMode1 = 0x0; + static const uint8_t RegisterPrescale = 0xFE; + + static const uint8_t RegisterLED0OnL = 0x6; + static const uint8_t RegisterAllLEDOnL = 0xFA; + + static const uint8_t Mode1Restart = 0x80; + static const uint8_t Mode1AI = 0x20; + static const uint8_t Mode1Sleep = 0x10; + static const uint8_t Mode1AllCall = 0x01; + + // Call this if you already initialized the I2C library + void setAddress(uint8_t address); + + void setPWMFrequency(float frequency); + + void setPWM(uint8_t pin, uint16_t value); + void setPWM(uint8_t pin, uint16_t on, uint16_t off); + + void setAll(uint16_t value); + void setAll(uint16_t on, uint16_t off); +}; + +#endif \ No newline at end of file diff --git a/module/src/lib/RS485_non_blocking.cpp b/module/src/lib/RS485_non_blocking.cpp new file mode 100644 index 0000000..ed165c7 --- /dev/null +++ b/module/src/lib/RS485_non_blocking.cpp @@ -0,0 +1,232 @@ +/* + RS485 protocol library - non-blocking. + + Devised and written by Nick Gammon. + Date: 4 December 2012 + Version: 1.0 + + Can send from 1 to 255 bytes from one node to another with: + + * Packet start indicator (STX) + * Each data byte is doubled and inverted to check validity + * Packet end indicator (ETX) + * Packet CRC (checksum) + + + To allow flexibility with hardware (eg. Serial, SoftwareSerial, I2C) + you provide three "callback" functions which send or receive data. Examples are: + + size_t fWrite (const byte what) + { + return Serial.write (what); + } + + int fAvailable () + { + return Serial.available (); + } + + int fRead () + { + return Serial.read (); + } + + + PERMISSION TO DISTRIBUTE + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software + and associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + + LIMITATION OF LIABILITY + + The software is provided "as is", without warranty of any kind, express or implied, + including but not limited to the warranties of merchantability, fitness for a particular + purpose and noninfringement. In no event shall the authors or copyright holders be liable + for any claim, damages or other liability, whether in an action of contract, + tort or otherwise, arising from, out of or in connection with the software + or the use or other dealings in the software. + + */ + +#include "RS485_non_blocking.h" + +// allocate the requested buffer size +void RS485::begin () + { + data_ = (byte *) malloc (bufferSize_); + reset (); + errorCount_ = 0; + } // end of RS485::begin + +// get rid of the buffer +void RS485::stop () +{ + reset (); + free (data_); + data_ = NULL; +} // end of RS485::stop + +// called after an error to return to "not in a packet" +void RS485::reset () + { + haveSTX_ = false; + available_ = false; + inputPos_ = 0; + startTime_ = 0; + } // end of RS485::reset + +// calculate 8-bit CRC +byte RS485::crc8 (const byte *addr, byte len) +{ + byte crc = 0; + while (len--) + { + byte inbyte = *addr++; + for (byte i = 8; i; i--) + { + byte mix = (crc ^ inbyte) & 0x01; + crc >>= 1; + if (mix) + crc ^= 0x8C; + inbyte >>= 1; + } // end of for + } // end of while + return crc; +} // end of RS485::crc8 + +// send a byte complemented, repeated +// only values sent would be (in hex): +// 0F, 1E, 2D, 3C, 4B, 5A, 69, 78, 87, 96, A5, B4, C3, D2, E1, F0 +void RS485::sendComplemented (const byte what) +{ +byte c; + + // first nibble + c = what >> 4; + fWriteCallback_ ((c << 4) | (c ^ 0x0F)); + + // second nibble + c = what & 0x0F; + fWriteCallback_ ((c << 4) | (c ^ 0x0F)); + +} // end of RS485::sendComplemented + +// send a message of "length" bytes (max 255) to other end +// put STX at start, ETX at end, and add CRC +void RS485::sendMsg (const byte * data, const byte length) +{ + // no callback? Can't send + if (fWriteCallback_ == NULL) + return; + + fWriteCallback_ (STX); // STX + for (byte i = 0; i < length; i++) + sendComplemented (data [i]); + fWriteCallback_ (ETX); // ETX + sendComplemented (crc8 (data, length)); +} // end of RS485::sendMsg + +// called periodically from main loop to process data and +// assemble the finished packet in 'data_' + +// returns true if packet received. + +// You could implement a timeout by seeing if isPacketStarted() returns +// true, and if too much time has passed since getPacketStartTime() time. + +bool RS485::update () + { + // no data? can't go ahead (eg. begin() not called) + if (data_ == NULL) + return false; + + // no callbacks? Can't read + if (fAvailableCallback_ == NULL || fReadCallback_ == NULL) + return false; + + while (fAvailableCallback_ () > 0) + { + byte inByte = fReadCallback_ (); + + switch (inByte) + { + + case STX: // start of text + haveSTX_ = true; + haveETX_ = false; + inputPos_ = 0; + firstNibble_ = true; + startTime_ = millis (); + break; + + case ETX: // end of text (now expect the CRC check) + haveETX_ = true; + break; + + default: + // wait until packet officially starts + if (!haveSTX_) + break; + + // check byte is in valid form (4 bits followed by 4 bits complemented) + if ((inByte >> 4) != ((inByte & 0x0F) ^ 0x0F) ) + { + reset (); + errorCount_++; + break; // bad character + } // end if bad byte + + // convert back + inByte >>= 4; + + // high-order nibble? + if (firstNibble_) + { + currentByte_ = inByte; + firstNibble_ = false; + break; + } // end of first nibble + + // low-order nibble + currentByte_ <<= 4; + currentByte_ |= inByte; + firstNibble_ = true; + + // if we have the ETX this must be the CRC + if (haveETX_) + { + if (crc8 (data_, inputPos_) != currentByte_) + { + reset (); + errorCount_++; + break; // bad crc + } // end of bad CRC + + available_ = true; + return true; // show data ready + } // end if have ETX already + + // keep adding if not full + if (inputPos_ < bufferSize_) + data_ [inputPos_++] = currentByte_; + else + { + reset (); // overflow, start again + errorCount_++; + } + + break; + + } // end of switch + } // end of while incoming data + + return false; // not ready yet + } // end of RS485::update + diff --git a/module/src/lib/RS485_non_blocking.h b/module/src/lib/RS485_non_blocking.h new file mode 100644 index 0000000..46a6919 --- /dev/null +++ b/module/src/lib/RS485_non_blocking.h @@ -0,0 +1,107 @@ +/* + RS485 protocol library - non-blocking. + + Devised and written by Nick Gammon. + Date: 4 December 2012 + Version: 1.0 + + Licence: Released for public use. + +*/ + +#include "Arduino.h" + + +class RS485 + { + + typedef size_t (*WriteCallback) (const byte what); // send a byte to serial port + typedef int (*AvailableCallback) (); // return number of bytes available + typedef int (*ReadCallback) (); // read a byte from serial port + + enum { + STX = '\2', // start of text + ETX = '\3' // end of text + }; // end of enum + + // callback functions to do reading/writing + ReadCallback fReadCallback_; + AvailableCallback fAvailableCallback_; + WriteCallback fWriteCallback_; + + // where we save incoming stuff + byte * data_; + + // how much data is in the buffer + const int bufferSize_; + + // this is true once we have valid data in buf + bool available_; + + // an STX (start of text) signals a packet start + bool haveSTX_; + + // count of errors + unsigned long errorCount_; + + // variables below are set when we get an STX + bool haveETX_; + byte inputPos_; + byte currentByte_; + bool firstNibble_; + unsigned long startTime_; + + // helper private functions + byte crc8 (const byte *addr, byte len); + void sendComplemented (const byte what); + + public: + + // constructor + RS485 (ReadCallback fReadCallback, + AvailableCallback fAvailableCallback, + WriteCallback fWriteCallback, + const byte bufferSize) : + fReadCallback_ (fReadCallback), + fAvailableCallback_ (fAvailableCallback), + fWriteCallback_ (fWriteCallback), + data_ (NULL), + bufferSize_ (bufferSize) + {} + + // destructor - frees memory used + ~RS485 () { stop (); } + + // allocate memory for buf_ + void begin (); + + // free memory in buf_ + void stop (); + + // handle incoming data, return true if packet ready + bool update (); + + // reset to no incoming data (eg. after a timeout) + void reset (); + + // send data + void sendMsg (const byte * data, const byte length); + + // returns true if packet available + bool available () const { return available_; }; + + // once available, returns the address of the current message + byte * getData () const { return data_; } + byte getLength () const { return inputPos_; } + + // return how many errors we have had + unsigned long getErrorCount () const { return errorCount_; } + + // return when last packet started + unsigned long getPacketStartTime () const { return startTime_; } + + // return true if a packet has started to be received + bool isPacketStarted () const { return haveSTX_; } + + }; // end of class RS485 + diff --git a/module/src/lib/TinyMillis.c b/module/src/lib/TinyMillis.c deleted file mode 100644 index 277a4c3..0000000 --- a/module/src/lib/TinyMillis.c +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Stairs lighting - * Copyright 2017 (c) Mark van Renswoude - * - * https://git.x2software.net/pub/Stairs -*/ -#include "TinyMillis.h" -#include -#include -#include - - -volatile uint16_t tickCount; - - -ISR(TIMER1_COMPA_vect) -{ - tickCount++; -} - - -void millis_init(void) -{ - uint32_t overflow; - - overflow = ((F_CPU / 1000) / 8); - - TCCR1B |= (1 << WGM12) | (1 << CS11); - OCR1AH = (overflow >> 8); - OCR1AL = overflow; - - TIMSK |= (1 << OCIE1A); -} - - -uint16_t millis(void) -{ - uint16_t value; - - ATOMIC_BLOCK(ATOMIC_FORCEON) - { - value = tickCount; - } - - return value; -} diff --git a/module/src/lib/TinyMillis.h b/module/src/lib/TinyMillis.h deleted file mode 100644 index 2f8d701..0000000 --- a/module/src/lib/TinyMillis.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Stairs lighting - * Copyright 2017 (c) Mark van Renswoude - * - * https://git.x2software.net/pub/Stairs - * - * Counts the elapsed milliseconds similar to Arduino's millis(void). - * Accuracy is not assumed to be important, and assumes no long-running - * checks are done so it can get away with 16 bits (65 seconds max). -*/ -#ifndef __TinyMillis -#define __TinyMillis - -#include - -// call sei(void) afterwards -void millis_init(void); - -uint16_t millis(void); - -#endif \ No newline at end of file diff --git a/module/src/lib/TinyRS485.c b/module/src/lib/TinyRS485.c deleted file mode 100644 index cdef8eb..0000000 --- a/module/src/lib/TinyRS485.c +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Stairs lighting - * Copyright 2017 (c) Mark van Renswoude - * - * https://git.x2software.net/pub/Stairs - * - * Straight up port of Nick Gammon's non-blocking - * RS485 library to C and removing the Arduino - * framework dependency. - * - * Source: - * https://www.gammon.com.au/forum/?id=11428 - * Devised and written by Nick Gammon. - * Date: 4 December 2012 - * Version: 1.0 -*/ -#include "TinyRS485.h" -#include "TinyMillis.h" -#include - - -ReadCallback doRead; -AvailableCallback doAvailable; -WriteCallback doWrite; - - -// where we save incoming stuff -uint8_t* data; - -// how much data is in the buffer -uint8_t bufferSize; - -// this is true once we have valid data in buf -bool available; - -// an STX (start of text) signals a packet start -bool haveSTX; - -// count of errors -uint16_t errorCount; - -// variables below are set when we get an STX -bool haveETX; -uint8_t inputPos; -uint8_t currentByte; -bool firstNibble; -uint16_t startTime; - - -// helper private functions -uint8_t crc8 (uint8_t* addr, uint8_t len) -{ - uint8_t crc = 0; - while (len--) - { - uint8_t inbyte = *addr++; - for (uint8_t i = 8; i; i--) - { - uint8_t mix = (crc ^ inbyte) & 0x01; - crc >>= 1; - if (mix) - crc ^= 0x8C; - - inbyte >>= 1; - } - } - return crc; -} - - -// send a byte complemented, repeated -// only values sent would be (in hex): -// 0F, 1E, 2D, 3C, 4B, 5A, 69, 78, 87, 96, A5, B4, C3, D2, E1, F0 -void sendComplemented(uint8_t what) -{ - uint8_t c; - - // first nibble - c = what >> 4; - doWrite((c << 4) | (c ^ 0x0F)); - - // second nibble - c = what & 0x0F; - doWrite((c << 4) | (c ^ 0x0F)); -} - - - -void rs485_begin(ReadCallback readCallback, - AvailableCallback availableCallback, - WriteCallback writeCallback, - uint8_t bufferSize) -{ - doRead = readCallback; - doAvailable = availableCallback; - doWrite = writeCallback; - data = (uint8_t*)malloc(bufferSize); - rs485_reset(); - errorCount = 0; -} - - -void rs485_stop(void) -{ - rs485_reset(); - free(data); - data = NULL; -} - - -void rs485_reset(void) -{ - haveSTX = false; - available = false; - inputPos = 0; - startTime = 0; -} - - -// send a message of "length" bytes (max 255) to other end -// put STX at start, ETX at end, and add CRC -void rs485_sendMsg(uint8_t* data, uint8_t length) -{ - doWrite(STX); // STX - for (uint8_t i = 0; i < length; i++) - sendComplemented(data[i]); - - doWrite(ETX); // ETX - sendComplemented(crc8(data, length)); -} - - -bool rs485_available(void) -{ - return available; -} - - -uint8_t* rs485_getData(void) -{ - return data; -} - - -uint8_t rs485_getLength(void) -{ - return inputPos; -} - - -uint16_t rs485_getErrorCount(void) -{ - return errorCount; -} - - -uint16_t rs485_getPacketStartTime(void) -{ - return startTime; -} - - -bool rs485_isPacketStarted(void) -{ - return haveSTX; -} - - - -// called periodically from main loop to process data and -// assemble the finished packet in 'data_' - -// returns true if packet received. - -// You could implement a timeout by seeing if isPacketStarted() returns -// true, and if too much time has passed since getPacketStartTime() time. -bool rs485_update(void) -{ - // no data? can't go ahead (eg. begin() not called) - if (data == NULL) - return false; - - // no callbacks? Can't read - if (doAvailable == NULL || doRead == NULL) - return false; - - while (doAvailable() > 0) - { - uint8_t inByte = doRead(); - - switch (inByte) - { - case STX: // start of text - haveSTX = true; - haveETX = false; - inputPos = 0; - firstNibble = true; - startTime = millis(); - break; - - case ETX: // end of text (now expect the CRC check) - haveETX = true; - break; - - default: - // wait until packet officially starts - if (!haveSTX) - break; - - // check byte is in valid form (4 bits followed by 4 bits complemented) - if ((inByte >> 4) != ((inByte & 0x0F) ^ 0x0F)) - { - rs485_reset(); - errorCount++; - break; // bad character - } - - // convert back - inByte >>= 4; - - // high-order nibble? - if (firstNibble) - { - currentByte = inByte; - firstNibble = false; - break; - } - - // low-order nibble - currentByte <<= 4; - currentByte |= inByte; - firstNibble = true; - - // if we have the ETX this must be the CRC - if (haveETX) - { - if (crc8(data, inputPos) != currentByte) - { - rs485_reset(); - errorCount++; - break; // bad crc - } - - available = true; - return true; // show data ready - } - - // keep adding if not full - if (inputPos < bufferSize) - data[inputPos++] = currentByte; - else - { - rs485_reset(); // overflow, start again - errorCount++; - } - - break; - } - } - - return false; // not ready yet -} diff --git a/module/src/lib/TinyRS485.h b/module/src/lib/TinyRS485.h deleted file mode 100644 index e57fd2c..0000000 --- a/module/src/lib/TinyRS485.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Stairs lighting - * Copyright 2017 (c) Mark van Renswoude - * - * https://git.x2software.net/pub/Stairs - * - * Straight up port of Nick Gammon's non-blocking - * RS485 library to C and removing the Arduino - * framework dependency. - * - * Source: - * https://www.gammon.com.au/forum/?id=11428 - * Devised and written by Nick Gammon. - * Date: 4 December 2012 - * Version: 1.0 -*/ -#ifndef __TinyRS485 -#define __TinyRS485 - -#include -#include - - -typedef void (*WriteCallback) (uint8_t what); // send a byte to serial port -typedef uint8_t (*AvailableCallback) (void); // return number of bytes available -typedef uint8_t (*ReadCallback) (void); // read a byte from serial port - - -enum { - STX = '\2', // start of text - ETX = '\3' // end of text -}; // end of enum - - -// allocate the requested buffer size -void rs485_begin(ReadCallback readCallback, - AvailableCallback availableCallback, - WriteCallback writeCallback, - uint8_t bufferSize); - -// get rid of the buffer -void rs485_stop(void); - -// handle incoming data, return true if packet ready -bool rs485_update(void); - -// reset to no incoming data (eg. after a timeout) -void rs485_reset(void); - -// send data -void rs485_sendMsg(uint8_t* data, uint8_t length); - -// returns true if packet available -bool rs485_available(void); - -// once available, returns the address of the current message -uint8_t* rs485_getData(void); -uint8_t rs485_getLength(void); - -// return how many errors we have had -uint16_t rs485_getErrorCount(void); - -// return when last packet started -uint16_t rs485_getPacketStartTime(void); - -// return true if a packet has started to be received -bool rs485_isPacketStarted(void); - -#endif \ No newline at end of file diff --git a/module/src/lib/TinyUART.c b/module/src/lib/TinyUART.c deleted file mode 100644 index bb86d2f..0000000 --- a/module/src/lib/TinyUART.c +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Stairs lighting - * Copyright 2017 (c) Mark van Renswoude - * - * https://git.x2software.net/pub/Stairs - * - * Source: - * https://github.com/akafugu/helloworld/blob/master/attiny2313/uart.c -*/ -#include "TinyUART.h" -#include -#include -#include - - -volatile static uint8_t rx_buffer[BUFFER_SIZE] = "xxxxxxxxxxxxxxxx"; -volatile static uint8_t tx_buffer[BUFFER_SIZE] = "xxxxxxxxxxxxxxxx"; -volatile static uint8_t rx_head = 0; -volatile static uint8_t rx_tail = 0; -volatile static uint8_t tx_head = 0; -volatile static uint8_t tx_tail = 0; - - -void uart_init(void) -{ - // set baud rate - UBRRH = (uint8_t)(MYUBBR >> 8); - UBRRL = (uint8_t)(MYUBBR); - // enable receive and transmit - UCSRB = (1 << RXEN) | (1 << TXEN) | (1 << RXCIE); - // set frame format - UCSRC = (1 << USBS) | (3 << UCSZ0); // asynchron 8n1 -} - - - -void uart_send(uint8_t c) -{ - // wait for empty data register - while (!(UCSRA & (1< - -#define UART_NO_DATA 0x0100 - -#define BAUD 38400 -// 4.096MHz -// 4800: 52.3333333 -// 9600: 25.6666667 -// 14400: 16.7777778 -// 19600: 12.06 -// 28800: 7.8889 -// 38400: 5.6667 - -#define MYUBBR ((F_CPU / (BAUD * 16L)) - 1) -#define BUFFER_SIZE 16 - - -void uart_init(void); - -/* - * uart_send - * Sends a single char to UART without ISR - */ -void uart_send(uint8_t c); - - -uint8_t uart_available(void); - - -/* - * uart_receive - * Receives a single char without ISR - */ -uint8_t uart_receive(void); - - -/* - * uart_getc - * Gets a single char from the receive buffer. - * return uint16_r the received char or UART_NO_DATA - */ -uint16_t uart_getc(void); - - - -/* - * uart_getc_wait - * Blocking call to getc. Will not return until a char is received. - */ -uint8_t uart_getc_wait(void); - - -/* - * uart_putc - * Puts a single char. Will block until there is enough space in the - * send buffer. - */ -void uart_putc(uint8_t c); - - -/* - * uart_puts - * Sends a string. - */ -void uart_puts(const char *s); - - -/* - * uart_puts_P - * Sends a PROGMEM string. - */ -void uart_puts_P(const char *s); - -#endif \ No newline at end of file diff --git a/module/src/lib/VeryTinyBigChars.c b/module/src/lib/VeryTinyBigChars.c deleted file mode 100644 index 4b8ca78..0000000 --- a/module/src/lib/VeryTinyBigChars.c +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Stairs lighting - * Copyright 2017 (c) Mark van Renswoude - * - * https://git.x2software.net/pub/Stairs -*/ -#include "VeryTinyBigChars.h" -#include "VeryTinySSD1306.h" -#include - - -/* - * Each digit is composed of a 3x5 grid where each bit represents - * whether or not to draw that area. It is then stretched. - * - * Example: - * - * XXX - * X - * XXX - * X - * XXX - * - * The least significant bit is the top left corner, going down - * after that before wrapping to the middle column. - */ -const uint16_t ssd1306_digits_32px [] PROGMEM = { - 0b0111111000111111, // 0 - 0b0100001111110010, // 1 - 0b0101111010111101, // 2 - 0b0111111010110101, // 3 - 0b0111110010000111, // 4 - 0b0111011010110111, // 5 - 0b0111011010111111, // 6 - 0b0111110000100001, // 7 - 0b0111111010111111, // 8 - 0b0111111010110111 // 9 -}; - - -void ssd1306_drawchar_32px(uint8_t x, uint16_t matrix) -{ - uint8_t column[4]; - - // Vertical mode - ssd1306_setMemoryAddressingMode(0b01); - - if (ssd1306_currentRenderFrame() == 1) - { - ssd1306_setPageStartAddress(4); - ssd1306_setPageAddress(4, 7); - } - else - { - ssd1306_setPageStartAddress(0); - ssd1306_setPageAddress(0, 3); - } - - ssd1306_setCursor(x, 0); - - ssd1306_startData(); - - // Three columns, each shifted 5 bits - for (uint8_t shift = 0; shift < 3; shift++) - { - uint16_t columnValue = matrix >> (shift * 5); - - // It's a bit rough, but it works! First, third and fifth "row" are 6 bits, in between are 7 bits = 32 total - column[0] = 0; - column[1] = 0; - column[2] = 0; - column[3] = 0; - - // 0 = 0b0111111000111111 - - if (columnValue & 0b1) - column[0] |= 0b00111111; - - if (columnValue & 0b10) { - column[0] |= 0b11000000; - column[1] |= 0b00011111; - } - - if (columnValue & 0b100) { - column[1] |= 0b11100000; - column[2] |= 0b00000111; - } - - if (columnValue & 0b1000) { - column[2] |= 0b11111000; - column[3] |= 0b00000011; - } - - if (columnValue & 0b10000) - column[3] |= 0b11111100; - - // Each column is 6 pixels wide - for (uint8_t repeat = 0; repeat < SSD1306_CharColumnWidth_32px; repeat++) - { - ssd1306_sendData(column[0]); - ssd1306_sendData(column[1]); - ssd1306_sendData(column[2]); - ssd1306_sendData(column[3]); - } - } - - ssd1306_endData(); - - // Default: horizontal mode - ssd1306_setMemoryAddressingMode(0b00); - ssd1306_setPageStartAddress(0); - ssd1306_setPageAddress(0, 7); -} - - -void ssd1306_drawdigit_32px(uint8_t x, uint8_t value) -{ - if (value < 0 || value > 9) - return; - - ssd1306_drawchar_32px(x, pgm_read_word(&ssd1306_digits_32px[value])); -} - - -void ssd1306_drawplus_32px(uint8_t x) -{ - ssd1306_drawchar_32px(x, 0b0001000111000100); -} - - -void ssd1306_drawminus_32px(uint8_t x) -{ - ssd1306_drawchar_32px(x, 0b0001000010000100); -} \ No newline at end of file diff --git a/module/src/lib/VeryTinyBigChars.h b/module/src/lib/VeryTinyBigChars.h deleted file mode 100644 index b6c87eb..0000000 --- a/module/src/lib/VeryTinyBigChars.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Stairs lighting - * Copyright 2017 (c) Mark van Renswoude - * - * https://git.x2software.net/pub/Stairs -*/ -#ifndef __VeryTinyBigChars -#define __VeryTinyBigChars - -#include - - -#define SSD1306_CharColumnWidth_32px 6 -#define SSD1306_CharWidth_32px (3 * SSD1306_CharColumnWidth_32px) - -void ssd1306_drawchar_32px(uint8_t x, uint16_t matrix); - - -// Draws a digit from 0 to 9, 32 pixels tall -void ssd1306_drawdigit_32px(uint8_t x, uint8_t value); -void ssd1306_drawplus_32px(uint8_t x); -void ssd1306_drawminus_32px(uint8_t x); - -#endif \ No newline at end of file diff --git a/module/src/lib/VeryTinyI2C.c b/module/src/lib/VeryTinyI2C.c deleted file mode 100644 index 3ddd33c..0000000 --- a/module/src/lib/VeryTinyI2C.c +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Stairs lighting - * Copyright 2017 (c) Mark van Renswoude - * - * https://git.x2software.net/pub/Stairs - * - * Minimalistic I2C library for the ATTiny2313's limited space. - * Based on David Johnson-Davies' TinyI2C: - * https://github.com/technoblogy/tiny-i2c -*/ -#include "VeryTinyI2C.h" - - -// Constants -// Prepare register value to: Clear flags, and set USI to shift 8 bits i.e. count 16 clock edges. -const unsigned char USISR_8bit = 1< -#include -#include -#include - -// Defines -//#define TWI_FAST_MODE - -#ifdef TWI_FAST_MODE // TWI FAST mode timing limits. SCL = 100-400kHz -#define DELAY_T2TWI (_delay_us(2)) // >1.3us -#define DELAY_T4TWI (_delay_us(1)) // >0.6us -#else // TWI STANDARD mode timing limits. SCL <= 100kHz -#define DELAY_T2TWI (_delay_us(5)) // >4.7us -#define DELAY_T4TWI (_delay_us(4)) // >4.0us -#endif - -#define TWI_NACK_BIT 0 // Bit position for (N)ACK bit. - - -// These are valid for the ATTiny2313 -#define DDR_USI DDRB -#define DDR_USI_CL DDR_USI -#define PORT_USI PORTB -#define PORT_USI_CL PORT_USI -#define PIN_USI PINB -#define PIN_USI_CL PIN_USI -#define PORT_USI_SDA PB5 -#define PORT_USI_SCL PB7 -#define PIN_USI_SDA PINB5 -#define PIN_USI_SCL PINB7 - - -void i2c_init(void); -uint8_t i2c_read(void); -uint8_t i2c_readLast(void); -bool i2c_write(uint8_t data); -bool i2c_start(uint8_t address, int readcount); -bool i2c_restart(uint8_t address, int readcount); -void i2c_stop(void); - -#endif \ No newline at end of file diff --git a/module/src/lib/VeryTinySSD1306.c b/module/src/lib/VeryTinySSD1306.c deleted file mode 100644 index 09b48aa..0000000 --- a/module/src/lib/VeryTinySSD1306.c +++ /dev/null @@ -1,517 +0,0 @@ -/* - * Stairs lighting - * Copyright 2017 (c) Mark van Renswoude - * - * https://git.x2software.net/pub/Stairs - * - * Port of Stephen Denne's Tiny4kOLED to C - * and without the Arduino framework dependency. - * https://github.com/technoblogy/tiny-i2c -*/ -#include "VeryTinySSD1306.h" -#include -#include "VeryTinyI2C.h" - -#define SSD1306_PAGES 4 - -#define SSD1306_COMMAND 0x00 -#define SSD1306_DATA 0x40 - -// ---------------------------------------------------------------------------- - -// Some code based on "IIC_without_ACK" by http://www.14blog.com/archives/1358 - -const uint8_t ssd1306_init_sequence [] PROGMEM = { // Initialization Sequence -// 0xAE, // Display OFF (sleep mode) -// 0x20, 0b10, // Set Memory Addressing Mode - // 00=Horizontal Addressing Mode; 01=Vertical Addressing Mode; - // 10=Page Addressing Mode (RESET); 11=Invalid -// 0xB0, // Set Page Start Address for Page Addressing Mode, 0-7 - 0xC8, // Set COM Output Scan Direction -// 0x00, // ---set low column address -// 0x10, // ---set high column address -// 0x40, // --set start line address -// 0x81, 0x7F, // Set contrast control register - 0xA1, // Set Segment Re-map. A0=address mapped; A1=address 127 mapped. -// 0xA6, // Set display mode. A6=Normal; A7=Inverse - 0xA8, 0x1F, // Set multiplex ratio(1 to 64) -// 0xA4, // Output RAM to Display - // 0xA4=Output follows RAM content; 0xA5,Output ignores RAM content -// 0xD3, 0x00, // Set display offset. 00 = no offset -// 0xD5, 0x80, // --set display clock divide ratio/oscillator frequency -// 0xD9, 0x22, // Set pre-charge period - 0xDA, 0x02, // Set com pins hardware configuration -// 0xDB, 0x20, // --set vcomh 0x20 = 0.77xVcc - 0x8D, 0x14 // Set DC-DC enable -}; - - -const DCfont *oledFont = 0; -uint8_t oledX = 0, oledY = 0; -uint8_t renderingFrame = 0xB0, drawingFrame = 0x40; - - -void ssd1306_send_command_start(void) -{ - i2c_start(SSD1306, 0); - i2c_write(SSD1306_COMMAND); -} - -void ssd1306_send_data_start(void) -{ - i2c_start(SSD1306, 0); - i2c_write(SSD1306_DATA); -} - -void ssd1306_send_command_byte(uint8_t byte) -{ - if (i2c_write(byte) == 0) - { - i2c_stop(); - ssd1306_send_command_start(); - i2c_write(byte); - } -} - -void ssd1306_send_data_byte(uint8_t byte) -{ - if (i2c_write(byte) == 0) - { - i2c_stop(); - ssd1306_send_data_start(); - i2c_write(byte); - } -} - -void ssd1306_send_command(uint8_t command) -{ - ssd1306_send_command_start(); - i2c_write(command); - i2c_stop(); -} - -void ssd1306_send_command2(uint8_t command1, uint8_t command2) -{ - ssd1306_send_command_start(); - i2c_write(command1); - i2c_write(command2); - i2c_stop(); -} - -void ssd1306_send_command3(uint8_t command1, uint8_t command2, uint8_t command3) -{ - ssd1306_send_command_start(); - i2c_write(command1); - i2c_write(command2); - i2c_write(command3); - i2c_stop(); -} - -void ssd1306_send_command6(uint8_t command1, uint8_t command2, uint8_t command3, uint8_t command4, uint8_t command5, uint8_t command6) -{ - ssd1306_send_command_start(); - i2c_write(command1); - i2c_write(command2); - i2c_write(command3); - i2c_write(command4); - i2c_write(command5); - i2c_write(command6); - i2c_stop(); -} - -void ssd1306_send_command7(uint8_t command1, uint8_t command2, uint8_t command3, uint8_t command4, uint8_t command5, uint8_t command6, uint8_t command7) -{ - ssd1306_send_command_start(); - i2c_write(command1); - i2c_write(command2); - i2c_write(command3); - i2c_write(command4); - i2c_write(command5); - i2c_write(command6); - i2c_write(command7); - i2c_stop(); -} - -void ssd1306_begin_default(void) -{ - ssd1306_begin(sizeof(ssd1306_init_sequence), ssd1306_init_sequence); -} - -void ssd1306_begin(uint8_t init_sequence_length, const uint8_t init_sequence []) -{ - i2c_init(); - - ssd1306_send_command_start(); - for (uint8_t i = 0; i < init_sequence_length; i++) - { - ssd1306_send_command_byte(pgm_read_byte(&init_sequence[i])); - } - i2c_stop(); -} - -void ssd1306_setFont(const DCfont *font) -{ - oledFont = font; -} - -void ssd1306_setCursor(uint8_t x, uint8_t y) -{ - ssd1306_send_command3(renderingFrame | (y & 0x07), 0x10 | ((x & 0xf0) >> 4), x & 0x0f); - oledX = x; - oledY = y; -} - -void ssd1306_clear(void) -{ - ssd1306_fill(0x00); -} - -void ssd1306_fill(uint8_t fill) -{ - for (uint8_t m = 0; m < SSD1306_PAGES; m++) - { - ssd1306_setCursor(0, m); - ssd1306_fillToEOL(fill); - } - ssd1306_setCursor(0, 0); -} - -void ssd1306_newLine_height(uint8_t fontHeight) -{ - oledY+=fontHeight; - if (oledY > SSD1306_PAGES - fontHeight) - { - oledY = SSD1306_PAGES - fontHeight; - } - ssd1306_setCursor(0, oledY); -} - -void ssd1306_newLine(void) -{ - ssd1306_newLine_height(oledFont->height); -} - -uint8_t ssd1306_write(uint8_t c) -{ - if (!oledFont) - return 1; - - if (c == '\r') - return 1; - - uint8_t h = oledFont->height; - - if (c == '\n') - { - ssd1306_newLine_height(h); - return 1; - } - - uint8_t w = oledFont->width; - - if (oledX > ((uint8_t)128 - w)) - { - ssd1306_newLine_height(h); - } - - uint16_t offset = ((uint16_t)c - oledFont->first) * w * h; - uint8_t line = h; - do - { - ssd1306_send_data_start(); - for (uint8_t i = 0; i < w; i++) - { - ssd1306_send_data_byte(pgm_read_byte(&(oledFont->bitmap[offset++]))); - } - i2c_stop(); - if (h == 1) - { - oledX+=w; - } - else - { - if (line > 1) - { - ssd1306_setCursor(oledX, oledY + 1); - } - else - { - ssd1306_setCursor(oledX + w, oledY - (h - 1)); - } - } - } - while (--line); - return 1; -} - -void ssd1306_bitmap(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, const uint8_t bitmap[]) -{ - uint16_t j = 0; - for (uint8_t y = y0; y < y1; y++) - { - ssd1306_setCursor(x0,y); - ssd1306_send_data_start(); - for (uint8_t x = x0; x < x1; x++) - { - ssd1306_send_data_byte(pgm_read_byte(&bitmap[j++])); - } - i2c_stop(); - } - ssd1306_setCursor(0, 0); -} - -void ssd1306_clearToEOL(void) -{ - ssd1306_fillToEOL(0x00); -} - -void ssd1306_fillToEOL(uint8_t fill) -{ - ssd1306_fillLength(fill, 128 - oledX); -} - -void ssd1306_fillLength(uint8_t fill, uint8_t length) -{ - oledX += length; - ssd1306_send_data_start(); - do - { - ssd1306_send_data_byte(fill); - } - while (--length); - i2c_stop(); -} - -void ssd1306_startData(void) -{ - ssd1306_send_data_start(); -} - -void ssd1306_sendData(const uint8_t data) -{ - ssd1306_send_data_byte(data); -} - -void ssd1306_endData(void) -{ - i2c_stop(); -} - -// Double Buffering Commands - -void ssd1306_switchRenderFrame(void) -{ - renderingFrame ^= 0x04; -} - -void ssd1306_switchDisplayFrame(void) -{ - drawingFrame ^= 0x20; - ssd1306_send_command(drawingFrame); -} - -void ssd1306_switchFrame(void) -{ - ssd1306_switchDisplayFrame(); - ssd1306_switchRenderFrame(); -} - -uint8_t ssd1306_currentRenderFrame(void) -{ - return (renderingFrame >> 2) & 0x01; -} - -uint8_t ssd1306_currentDisplayFrame(void) -{ - return (drawingFrame >> 5) & 0x01; -} - -// 1. Fundamental Command Table - -void ssd1306_setContrast(uint8_t contrast) -{ - ssd1306_send_command2(0x81,contrast); -} - -void ssd1306_setEntireDisplayOn(bool enable) -{ - if (enable) - ssd1306_send_command(0xA5); - else - ssd1306_send_command(0xA4); -} - -void ssd1306_setInverse(bool enable) -{ - if (enable) - ssd1306_send_command(0xA7); - else - ssd1306_send_command(0xA6); -} - -void ssd1306_off(void) -{ - ssd1306_send_command(0xAE); -} - -void ssd1306_on(void) -{ - ssd1306_send_command(0xAF); -} - -// 2. Scrolling Command Table - -void ssd1306_scrollRight(uint8_t startPage, uint8_t interval, uint8_t endPage) -{ - ssd1306_send_command7(0x26, 0x00, startPage, interval, endPage, 0x00, 0xFF); -} - -void ssd1306_scrollLeft(uint8_t startPage, uint8_t interval, uint8_t endPage) -{ - ssd1306_send_command7(0x27, 0x00, startPage, interval, endPage, 0x00, 0xFF); -} - -void ssd1306_scrollRightOffset(uint8_t startPage, uint8_t interval, uint8_t endPage, uint8_t offset) -{ - ssd1306_send_command6(0x29, 0x00, startPage, interval, endPage, offset); -} - -void ssd1306_scrollLeftOffset(uint8_t startPage, uint8_t interval, uint8_t endPage, uint8_t offset) -{ - ssd1306_send_command6(0x2A, 0x00, startPage, interval, endPage, offset); -} - -void ssd1306_deactivateScroll(void) -{ - ssd1306_send_command(0x2E); -} - -void ssd1306_activateScroll(void) -{ - ssd1306_send_command(0x2F); -} - -void ssd1306_setVerticalScrollArea(uint8_t top, uint8_t rows) -{ - ssd1306_send_command3(0xA3, top, rows); -} - -// 3. Addressing Setting Command Table - -void ssd1306_setColumnStartAddress(uint8_t startAddress) -{ - ssd1306_send_command2(startAddress & 0x0F, startAddress >> 4); -} - -void ssd1306_setMemoryAddressingMode(uint8_t mode) -{ - ssd1306_send_command2(0x20, mode & 0x03); -} - -void ssd1306_setColumnAddress(uint8_t startAddress, uint8_t endAddress) -{ - ssd1306_send_command3(0x21, startAddress & 0x7F, endAddress & 0x7F); -} - -void ssd1306_setPageAddress(uint8_t startPage, uint8_t endPage) -{ - ssd1306_send_command3(0x22, startPage & 0x07, endPage & 0x07); -} - -void ssd1306_setPageStartAddress(uint8_t startPage) -{ - ssd1306_send_command(0xB0 | (startPage & 0x07)); -} - -// 4. Hardware Configuration (Panel resolution and layout related) Command Table - -void ssd1306_setDisplayStartLine(uint8_t startLine) -{ - ssd1306_send_command(0x40 | (startLine & 0x3F)); -} - -void ssd1306_setSegmentRemap(uint8_t remap) -{ - ssd1306_send_command(0xA0 | (remap & 0x01)); -} - -void ssd1306_setMultiplexRatio(uint8_t mux) -{ - ssd1306_send_command2(0xA8, (mux - 1) & 0x3F); -} - -void ssd1306_setComOutputDirection(uint8_t direction) -{ - ssd1306_send_command(0xC0 | ((direction & 0x01)<<3)); -} - -void ssd1306_setDisplayOffset(uint8_t offset) -{ - ssd1306_send_command2(0xD3, offset & 0x3F); -} - -void ssd1306_setComPinsHardwareConfiguration(uint8_t alternative, uint8_t enableLeftRightRemap) -{ - ssd1306_send_command2(0xDA, ((enableLeftRightRemap & 0x01) << 5) | ((alternative & 0x01) << 4) | 0x02 ); -} - -// 5. Timing and Driving Scheme Setting Command table - -void ssd1306_setDisplayClock(uint8_t divideRatio, uint8_t oscillatorFrequency) -{ - ssd1306_send_command2(0xD5, ((oscillatorFrequency & 0x0F) << 4) | ((divideRatio -1) & 0x0F)); -} - -void ssd1306_setPrechargePeriod(uint8_t phaseOnePeriod, uint8_t phaseTwoPeriod) -{ - ssd1306_send_command2(0xD9, ((phaseTwoPeriod & 0x0F) << 4) | (phaseOnePeriod & 0x0F)); -} - -void ssd1306_setVcomhDeselectLevel(uint8_t level) -{ - ssd1306_send_command2(0xDB, (level & 0x07) << 4); -} - -void ssd1306_nop(void) -{ - ssd1306_send_command(0xE3); -} - -// 6. Advance Graphic Command table - -void ssd1306_fadeOut(uint8_t interval) -{ - ssd1306_send_command2(0x23, (0x20 | (interval & 0x0F))); -} - -void ssd1306_blink(uint8_t interval) -{ - ssd1306_send_command2(0x23, (0x30 | (interval & 0x0F))); -} - -void ssd1306_disableFadeOutAndBlinking(void) -{ - ssd1306_send_command2(0x23, 0x00); -} - -void ssd1306_enableZoomIn(void) -{ - ssd1306_send_command2(0xD6, 0x01); -} - -void ssd1306_disableZoomIn(void) -{ - ssd1306_send_command2(0xD6, 0x00); -} - -// Charge Pump Settings - -void ssd1306_enableChargePump(void) -{ - ssd1306_send_command2(0x8D, 0x14); -} - -void ssd1306_disableChargePump(void) -{ - ssd1306_send_command2(0x8D, 0x10); -} diff --git a/module/src/lib/VeryTinySSD1306.h b/module/src/lib/VeryTinySSD1306.h deleted file mode 100644 index 0acfd26..0000000 --- a/module/src/lib/VeryTinySSD1306.h +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Stairs lighting - * Copyright 2017 (c) Mark van Renswoude - * - * https://git.x2software.net/pub/Stairs - * - * Port of Stephen Denne's Tiny4kOLED to C, - * without the Arduino framework dependency but with - * a hardcoded dependency on VeryTinyI2C. - * - * Original: - * https://github.com/technoblogy/tiny-i2c -*/ -#ifndef __VeryTinySSD1306 -#define __VeryTinySSD1306 - -#include -#include - - -typedef struct { - uint8_t *bitmap; // character bitmaps data - uint8_t width; // character width in pixels - uint8_t height; // character height in pages (8 pixels) - uint8_t first, last; // ASCII extents -} DCfont; - - -#define SSD1306_DisplayWidth 128 -#define SSD1306_DisplayHeight 32 - -#ifndef SSD1306 -#define SSD1306 0x3C // Slave address -#endif - - -void ssd1306_begin_default(void); -void ssd1306_begin(uint8_t init_sequence_length, const uint8_t init_sequence []); -void ssd1306_switchRenderFrame(void); -void ssd1306_switchDisplayFrame(void); -void ssd1306_switchFrame(void); -uint8_t ssd1306_currentRenderFrame(void); -uint8_t ssd1306_currentDisplayFrame(void); -void ssd1306_setFont(const DCfont *font); -void ssd1306_setCursor(uint8_t x, uint8_t y); -void ssd1306_newLine(); -void ssd1306_fill(uint8_t fill); -void ssd1306_fillToEOL(uint8_t fill); -void ssd1306_fillLength(uint8_t fill, uint8_t length); -void ssd1306_clear(void); -void ssd1306_clearToEOL(void); -void ssd1306_bitmap(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, const uint8_t bitmap[]); -void ssd1306_startData(void); -void ssd1306_sendData(const uint8_t data); -void ssd1306_endData(void); - -// 1. Fundamental Command Table - -void ssd1306_setContrast(uint8_t contrast); -void ssd1306_setEntireDisplayOn(bool enable); -void ssd1306_setInverse(bool enable); -void ssd1306_off(void); -void ssd1306_on(void); - -// 2. Scrolling Command Table - -void ssd1306_scrollRight(uint8_t startPage, uint8_t interval, uint8_t endPage); -void ssd1306_scrollLeft(uint8_t startPage, uint8_t interval, uint8_t endPage); -void ssd1306_scrollRightOffset(uint8_t startPage, uint8_t interval, uint8_t endPage, uint8_t offset); -void ssd1306_scrollLeftOffset(uint8_t startPage, uint8_t interval, uint8_t endPage, uint8_t offset); -void ssd1306_deactivateScroll(void); -void ssd1306_activateScroll(void); -void ssd1306_setVerticalScrollArea(uint8_t top, uint8_t rows); - -// 3. Addressing Setting Command Table -void ssd1306_setColumnStartAddress(uint8_t startAddress); -void ssd1306_setMemoryAddressingMode(uint8_t mode); -void ssd1306_setColumnAddress(uint8_t startAddress, uint8_t endAddress); -void ssd1306_setPageAddress(uint8_t startPage, uint8_t endPage); -void ssd1306_setPageStartAddress(uint8_t startPage); - -// 4. Hardware Configuration (Panel resolution and layout related) Command Table - -void ssd1306_setDisplayStartLine(uint8_t startLine); -void ssd1306_setSegmentRemap(uint8_t remap); -void ssd1306_setMultiplexRatio(uint8_t mux); -void ssd1306_setComOutputDirection(uint8_t direction); -void ssd1306_setDisplayOffset(uint8_t offset); -void ssd1306_setComPinsHardwareConfiguration(uint8_t alternative, uint8_t enableLeftRightRemap); - -// 5. Timing and Driving Scheme Setting Command table - -void ssd1306_setDisplayClock(uint8_t divideRatio, uint8_t oscillatorFrequency); -void ssd1306_setPrechargePeriod(uint8_t phaseOnePeriod, uint8_t phaseTwoPeriod); -void ssd1306_setVcomhDeselectLevel(uint8_t level); -void ssd1306_nop(void); - -// 6. Advance Graphic Command table - -void ssd1306_fadeOut(uint8_t interval); -void ssd1306_blink(uint8_t interval); -void ssd1306_disableFadeOutAndBlinking(void); -void ssd1306_enableZoomIn(void); -void ssd1306_disableZoomIn(void); - -// Charge Pump Settings - -void ssd1306_enableChargePump(void); -void ssd1306_disableChargePump(void); - -uint8_t ssd1306_write(uint8_t c); - -#endif \ No newline at end of file diff --git a/module/src/main.c b/module/src/main.c deleted file mode 100644 index cd8674c..0000000 --- a/module/src/main.c +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Stairs lighting - * Copyright 2017 (c) Mark van Renswoude - * - * https://git.x2software.net/pub/Stairs -*/ -#include -#include -#include -#include "lib/TinyMillis.h" -#include "lib/TinyUART.h" -#include "lib/TinyRS485.h" -#include "global.h" -#include "display.h" - -#define STEPS_DELAY_SHORT 200 -#define STEPS_DELAY 800 -#define STEPS_DELAY_LONG 2000 - - -uint8_t uartRead(void) -{ - return uart_getc(); -} - -uint8_t uartAvailable(void) -{ - return uart_available(); -} - -void uartWrite(uint8_t what) -{ - uart_putc(what); -} - - -typedef enum -{ - WaitingForComm, - Connected -} State; - - -State currentState = WaitingForComm; - - -int main(void) -{ - // Allow peripherals to start up before sending messages - _delay_ms(40); - - global_init(); - millis_init(); - sei(); - - uart_init(); - rs485_begin(uartRead, uartAvailable, uartWrite, 20); - - display_init(); - - for (;;) - { - if (rs485_update()) - { - // TODO get data - currentState = Connected; - } - - switch (currentState) - { - case WaitingForComm: - display_drawWait(); - break; - - case Connected: - display_drawIndex(); - break; - } - } - - - return 0; -} diff --git a/module/src/main.cpp b/module/src/main.cpp new file mode 100644 index 0000000..c6c19f6 --- /dev/null +++ b/module/src/main.cpp @@ -0,0 +1,56 @@ +/* + * Stairs lighting + * Copyright 2017 (c) Mark van Renswoude + * + * https://git.x2software.net/pub/Stairs +*/ +#include +#include +#include "global.h" +#include "display.h" + + +enum struct State: uint8_t +{ + WaitingForComm, + Connected +}; + + +State currentState = State::WaitingForComm; + + +void setup() +{ + settings.init(); + + // Set up I2C devices: the SSD1306 OLED display and PCA9685 LED PWM driver + Wire.begin(); + display.init(); + ledDriver.setAddress(0x40); + + // At 16 Mhz we pick a baud rate with a very acceptable 0.2% error rate + // Source: http://www.robotroom.com/Asynchronous-Serial-Communication-2.html + Serial.begin(76800); + comm.begin(); +} + +void loop() +{ + if (comm.update()) + { + // TODO get data + currentState = State::Connected; + } + + switch (currentState) + { + case State::WaitingForComm: + display.show(Screen::WaitingForComm); + break; + + case State::Connected: + display.show(Screen::ModuleIndex); + break; + } +} diff --git a/module/src/settings.cpp b/module/src/settings.cpp new file mode 100644 index 0000000..82cf595 --- /dev/null +++ b/module/src/settings.cpp @@ -0,0 +1,56 @@ +/* + * Stairs lighting + * Copyright 2017 (c) Mark van Renswoude + * + * https://git.x2software.net/pub/Stairs +*/ +#include "settings.h" +#include + + +const uint8_t ModuleIndexUndefined = 0xff; + + + +// First byte is 0xAA to recognize uninitialised EEPROM +const uint8_t EEPROMHeader = 0xAA; +const uint8_t EEPROMCurrentVersion = 1; + +const uint8_t EEPROMAddressHeader = 0; +const uint8_t EEPROMAddressVersion = 1; +const uint8_t EEPROMAddressModuleIndex = 2; + + +void Settings::init() +{ + if (EEPROM.read(EEPROMAddressHeader) != EEPROMHeader) + { + EEPROM.put(EEPROMAddressHeader, EEPROMHeader); + EEPROM.put(EEPROMAddressVersion, EEPROMCurrentVersion); + EEPROM.put(EEPROMAddressModuleIndex, mModuleIndex); + } + else + { + uint8_t version = EEPROM.read(EEPROMAddressVersion); + if (version >= 1) + { + mModuleIndex = EEPROM.read(EEPROMAddressModuleIndex); + } + } +} + + +uint8_t Settings::getModuleIndex(void) +{ + return mModuleIndex; +} + + +void Settings::setModuleIndex(uint8_t index) +{ + if (index == mModuleIndex) + return; + + mModuleIndex = index; + EEPROM.put(EEPROMAddressModuleIndex, mModuleIndex); +} \ No newline at end of file diff --git a/module/src/settings.h b/module/src/settings.h new file mode 100644 index 0000000..ce95509 --- /dev/null +++ b/module/src/settings.h @@ -0,0 +1,27 @@ +/* + * Stairs lighting + * Copyright 2017 (c) Mark van Renswoude + * + * https://git.x2software.net/pub/Stairs +*/ +#ifndef __settings +#define __settings + +#include + +extern const uint8_t ModuleIndexUndefined; + + +class Settings +{ + private: + uint8_t mModuleIndex = ModuleIndexUndefined; + + public: + void init(); + + uint8_t getModuleIndex(); + void setModuleIndex(uint8_t index); +}; + +#endif \ No newline at end of file