diff --git a/I2CManager_STM32.h b/I2CManager_STM32.h index 79a3726..a55fd2e 100644 --- a/I2CManager_STM32.h +++ b/I2CManager_STM32.h @@ -24,6 +24,7 @@ #include #include "I2CManager.h" +#include "I2CManager_NonBlocking.h" // to satisfy intellisense //#include //#include diff --git a/IO_ExternalEEPROM.h b/IO_ExternalEEPROM.h new file mode 100644 index 0000000..d2c90e5 --- /dev/null +++ b/IO_ExternalEEPROM.h @@ -0,0 +1,141 @@ +/* + * © 2023, Neil McKechnie. All rights reserved. + * + * This file is part of DCC++EX API + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +/* + * This device driver monitors the state of turnout objects and writes updates, + * on change of state, to an external 24C128 (16kByte) or 24C256 (32kByte) + * EEPROM device connected via I2C. + * + * When the device is restarted, it repositions the turnouts in accordance + * with the last saved position. + * + * To create a device instance, + * IO_ExternalEEPROM::create(0, 0, i2cAddress); + * + * + */ + +#ifndef IO_EXTERNALEEPROM_H +#define IO_EXTERNALEEPROM_H + +#include "IODevice.h" +#include "I2CManager.h" +#include "Turnouts.h" + +class ExternalEEPROM : public IODevice { +private: + // Here we define the device-specific variables. + int _sizeInKBytes = 128; + Turnout *_turnout = 0; + int _lastTurnoutHash = 0; + I2CRB _rb; + uint8_t _buffer[32]; // 32 is max for Wire write + +public: + // Static function to handle "IO_ExampleSerial::create(...)" calls. + static void create(I2CAddress i2cAddress, int sizeInKBytes) { + if (checkNoOverlap(0, 0, i2cAddress)) new ExternalEEPROM(i2cAddress, sizeInKBytes); + } + +protected: + // Constructor. + ExternalEEPROM(I2CAddress i2cAddress, int sizeInKBytes) { + _I2CAddress = i2cAddress; + _sizeInKBytes = sizeInKBytes; + + // Set up I2C structures. + _rb.setWriteParams(_I2CAddress, _buffer, 32); + + addDevice(this); + } + + // Device-specific initialisation + void _begin() override { + I2CManager.begin(); + I2CManager.setClock(1000000); // Max supported speed + + if (I2CManager.exists(_I2CAddress)) { + // Initialise or read contents of EEPROM + // and set turnout states accordingly. + // Read 32 bytes from address 0x0000. + I2CManager.read(_I2CAddress, _buffer, 32, 2, 0, 0); + // Dump data + DIAG(F("EEPROM First 32 bytes:")); + for (int i=0; i<32; i+=8) + DIAG(F("%d: %x %x %x %x %x %x %x %x"), + i, _buffer[i], _buffer[i+1], _buffer[i+2], _buffer[i+3], + _buffer[i+4], _buffer[i+5], _buffer[i+6], _buffer[i+7]); + +#if defined(DIAG_IO) + _display(); +#endif + } else { + DIAG(F("ExternalEEPROM not found, I2C:%s"), _I2CAddress.toString()); + _deviceState = DEVSTATE_FAILED; + } + } + + // Loop function to do background scanning of the turnouts + void _loop(unsigned long currentMicros) { + (void)currentMicros; // Suppress compiler warnings + + if (_rb.isBusy()) return; // Can't do anything until previous request has completed. + if (_rb.status == I2C_STATUS_NEGATIVE_ACKNOWLEDGE) { + // Device not responding, probably still writing data, so requeue request + I2CManager.queueRequest(&_rb); + return; + } + + if (_lastTurnoutHash != Turnout::turnoutlistHash) { + _lastTurnoutHash = Turnout::turnoutlistHash; + // Turnout list has changed, so pointer held from last run may be invalid + _turnout = 0; // Start at the beginning of the list again. +//#if defined(DIAG_IO) + DIAG(F("Turnout Hash Changed!")); +//#endif + } + + // Locate next turnout, or first one if there is no current one. + if (_turnout) + _turnout = _turnout->next(); + else + _turnout = Turnout::first(); + + // Retrieve turnout state + int turnoutID = _turnout->getId(); + int turnoutState = _turnout->isThrown(); + (void)turnoutID; // Suppress compiler warning + (void)turnoutState; // Suppress compiler warning + + // TODO: Locate turnoutID in EEPROM (or EEPROM copy) and check if state has changed. + // TODO: If it has, then initiate a write of the updated state to EEPROM + + delayUntil(currentMicros+5000); // Write cycle time is 5ms max for FT24C256 + } + + // Display information about the device. + void _display() { + DIAG(F("ExternalEEPROM %dkBytes I2C:%s %S"), _sizeInKBytes, _I2CAddress.toString(), + _deviceState== DEVSTATE_FAILED ? F("OFFLINE") : F("")); + } + + +}; + +#endif // IO_EXTERNALEEPROM_H \ No newline at end of file diff --git a/IO_GPIOBase.h b/IO_GPIOBase.h index 0f7019c..66b9ff6 100644 --- a/IO_GPIOBase.h +++ b/IO_GPIOBase.h @@ -86,6 +86,11 @@ GPIOBase::GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, I2CAddress _hasCallback = true; // Add device to list of devices. addDevice(this); + + _portMode = 0; // default to input mode + _portPullup = -1; // default to pullup enabled + _portInputState = -1; // default to all inputs high (inactive) + _portInUse = 0; // No ports in use initially. } template @@ -100,10 +105,6 @@ void GPIOBase::_begin() { #if defined(DIAG_IO) _display(); #endif - _portMode = 0; // default to input mode - _portPullup = -1; // default to pullup enabled - _portInputState = -1; - _portInUse = 0; _setupDevice(); _deviceState = DEVSTATE_NORMAL; } else { diff --git a/IO_TFTDisplay.h b/IO_TFTDisplay.h new file mode 100644 index 0000000..1de7b85 --- /dev/null +++ b/IO_TFTDisplay.h @@ -0,0 +1,256 @@ +/* + * © 2023, Neil McKechnie. All rights reserved. + * + * This file is part of DCC++EX API + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +/* + * This driver provides a way of driving a ST7735 TFT display through SCREEN(disp,line,"text"). + * If the line specified is off the screen then the text in the bottom line will be + * overwritten. There is however a special case that if line 255 is specified, + * the existing text will scroll up and the new line added to the bottom + * line of the screen. + * + * To install, use the following command in myHal.cpp: + + * TFTDisplay::create(address, width, height); + * + * where address is the I2C address (0x3c or 0x3d), + * width is the width in pixels of the display, and + * height is the height in pixels of the display. + * + */ + + +#ifndef IO_TFTDISPLAY_H +#define IO_TFTDDISPLAY_H + +#include "IODevice.h" +#include "DisplayInterface.h" +#include "version.h" + + +template +class TFTDisplay : public IODevice, public DisplayInterface { +private: + uint8_t _displayNo = 0; + // Here we define the device-specific variables. + uint8_t _height; // in pixels + uint8_t _width; // in pixels + T *_displayDriver; + uint8_t _rowNo = 0; // Row number being written by caller + uint8_t _colNo = 0; // Position in line being written by caller + uint8_t _numRows; + uint8_t _numCols; + char *_buffer = NULL; + uint8_t *_rowGeneration = NULL; + uint8_t *_lastRowGeneration = NULL; + uint8_t _rowNoToScreen = 0; + uint8_t _charPosToScreen = 0; + DisplayInterface *_nextDisplay = NULL; + uint8_t _selectedDisplayNo = 0; + +public: + // Static function to handle "TFTDisplay::create(...)" calls. + static void create(I2CAddress i2cAddress, int width = 128, int height=64) { + /* if (checkNoOverlap(i2cAddress)) */ new TFTDisplay(0, i2cAddress, width, height); + } + static void create(uint8_t displayNo, I2CAddress i2cAddress, int width = 128, int height=64) { + /* if (checkNoOverlap(i2cAddress)) */ new TFTDisplay(displayNo, i2cAddress, width, height); + } + +protected: + // Constructor + TFTDisplay(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) { + _displayDriver = new T(i2cAddress, width, height); + _displayNo = displayNo; + _I2CAddress = i2cAddress; + _width = width; + _height = height; + _numCols = (_width+5) / 6; // character block 6 x 8, round up + _numRows = _height / 8; // Round down + + _charPosToScreen = _numCols; + + // Allocate arrays + _buffer = (char *)calloc(_numRows*_numCols, sizeof(char)); + _rowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t)); + _lastRowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t)); + // Fill buffer with spaces + memset(_buffer, ' ', _numCols*_numRows); + + _displayDriver->clearNative(); + + // Is this the main display? + if (_displayNo == 0) { + // Set first two lines on screen + this->setRow(0, 0); + print(F("DCC-EX v")); + print(F(VERSION)); + setRow(0, 1); + print(F("Lic GPLv3")); + } + + // Store pointer to this object into CS display hook, so that we + // will intercept any subsequent calls to displayHandler methods. + // Make a note of the existing display reference, to that we can + // pass on anything we're not interested in. + _nextDisplay = DisplayInterface::displayHandler; + DisplayInterface::displayHandler = this; + + addDevice(this); + } + + + void screenUpdate() { + // Loop through the buffer and if a row has changed + // (rowGeneration[row] is changed) then start writing the + // characters from the buffer, one character per entry, + // to the screen until that row has been refreshed. + + // First check if the OLED driver is still busy from a previous + // call. If so, don't to anything until the next entry. + if (!_displayDriver->isBusy()) { + // Check if we've just done the end of a row or just started + if (_charPosToScreen >= _numCols) { + // Move to next line + if (++_rowNoToScreen >= _numRows) + _rowNoToScreen = 0; // Wrap to first row + + if (_rowGeneration[_rowNoToScreen] != _lastRowGeneration[_rowNoToScreen]) { + // Row content has changed, so start outputting it + _lastRowGeneration[_rowNoToScreen] = _rowGeneration[_rowNoToScreen]; + _displayDriver->setRowNative(_rowNoToScreen); + _charPosToScreen = 0; // Prepare to output first character on next entry + } else { + // Row not changed, don't bother writing it. + } + } else { + // output character at current position + _displayDriver->writeNative(_buffer[_rowNoToScreen*_numCols+_charPosToScreen++]); + } + } + return; + } + + ///////////////////////////////////////////////// + // IODevice Class Member Overrides + ///////////////////////////////////////////////// + + // Device-specific initialisation + void _begin() override { + // Initialise device + if (_displayDriver->begin()) { + + DIAG(F("TFTDisplay installed on address %s as screen %d"), + _I2CAddress.toString(), _displayNo); + + // Force all rows to be redrawn + for (uint8_t row=0; row<_numRows; row++) + _rowGeneration[row]++; + + // Start with top line (looks better) + _rowNoToScreen = _numRows; + _charPosToScreen = _numCols; + } + } + + void _loop(unsigned long) override { + screenUpdate(); + } + + ///////////////////////////////////////////////// + // DisplayInterface functions + // + ///////////////////////////////////////////////// + +public: + void loop() override { + screenUpdate(); + if (_nextDisplay) + _nextDisplay->loop(); // continue to next display + return; + } + + // Position on nominated line number (0 to number of lines -1) + // Clear the line in the buffer ready for updating + // The displayNo referenced here is remembered and any following + // calls to write() will be directed to that display. + void setRow(uint8_t displayNo, byte line) override { + _selectedDisplayNo = displayNo; + if (displayNo == _displayNo) { + if (line == 255) { + // LCD(255,"xxx") or SCREEN(displayNo,255, "xxx") - + // scroll the contents of the buffer and put the new line + // at the bottom of the screen + for (int row=1; row<_numRows; row++) { + strncpy(&_buffer[(row-1)*_numCols], &_buffer[row*_numCols], _numCols); + _rowGeneration[row-1]++; + } + line = _numRows-1; + } else if (line >= _numRows) + line = _numRows - 1; // Overwrite bottom line. + + _rowNo = line; + // Fill line with blanks + for (_colNo = 0; _colNo < _numCols; _colNo++) + _buffer[_rowNo*_numCols+_colNo] = ' '; + _colNo = 0; + // Mark that the buffer has been touched. It will be + // sent to the screen on the next loop entry, by which time + // the line should have been written to the buffer. + _rowGeneration[_rowNo]++; + + } + if (_nextDisplay) + _nextDisplay->setRow(displayNo, line); // Pass to next display + + } + + // Write one character to the screen referenced in the last setRow() call. + size_t write(uint8_t c) override { + if (_selectedDisplayNo == _displayNo) { + // Write character to buffer (if there's space) + if (_colNo < _numCols) { + _buffer[_rowNo*_numCols+_colNo++] = c; + } + } + if (_nextDisplay) + _nextDisplay->write(c); + return 1; + } + + // Write blanks to all of the screen (blocks until complete) + void clear (uint8_t displayNo) override { + if (displayNo == _displayNo) { + // Clear buffer + for (_rowNo = 0; _rowNo < _numRows; _rowNo++) { + setRow(displayNo, _rowNo); + } + _rowNo = 0; + } + if (_nextDisplay) + _nextDisplay->clear(displayNo); // Pass to next display + } + + // Display information about the device. + void _display() { + DIAG(F("TFTDisplay %d Configured addr %s"), _displayNo, _I2CAddress.toString()); + } + +}; + +#endif // IO_TFTDDISPLAY_H \ No newline at end of file diff --git a/IO_Wire.h b/IO_Wire.h new file mode 100644 index 0000000..624c911 --- /dev/null +++ b/IO_Wire.h @@ -0,0 +1,136 @@ +/* + * © 2023, Neil McKechnie. All rights reserved. + * + * This file is part of DCC++EX API + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +/* + * The purpose of this module is to provide an interface to the DCC + * I2CManager that is compatible with code written for the Arduino + * 'Wire' interface. + * + * To use it, just replace + * #include "Wire.h" or #include + * with + * #include "IO_Wire.h" + * + * Note that the CS only supports I2C master mode, so the calls related to + * slave mode are not implemented here. + * + */ + +#ifndef IO_WIRE +#define IO_WIRE + +#include "IODevice.h" + +#ifndef I2C_USE_WIRE + +class IO_Wire : public IODevice, public Stream { +public: + IO_Wire() { + addDevice(this); + }; + void begin() { + I2CManager.begin(); + } + void setClock(uint32_t speed) { + I2CManager.setClock(speed); + } + void beginTransmission(uint8_t address) { + i2cAddress = address; + outputLength = 0; + } + size_t write(byte value) override { + if (outputLength < sizeof(outputBuffer)) { + outputBuffer[outputLength++] = value; + return 1; + } else + return 0; + } + size_t write(const uint8_t *buffer, size_t size) override { + for (size_t i=0; i +#endif +#endif \ No newline at end of file diff --git a/Release - Architecture Doc/CommandStation-EX-Arch-v1-0.svg b/Release - Architecture Doc/CommandStation-EX-Arch-v1-0.svg index b99995d..481b970 100644 --- a/Release - Architecture Doc/CommandStation-EX-Arch-v1-0.svg +++ b/Release - Architecture Doc/CommandStation-EX-Arch-v1-0.svg @@ -485,10 +485,10 @@ Accessories (Output.cpp) - + Process.14 - Other Utilities (LCDDisplay.cpp) + Other Utilities (Display.cpp) @@ -522,7 +522,7 @@ Other Utilities(LCDDisplay.cpp) + x="14.29" dy="1.2em" class="st5">(Display.cpp) Dynamic connector diff --git a/ST7735-TFT.h b/ST7735-TFT.h new file mode 100644 index 0000000..5893d3c --- /dev/null +++ b/ST7735-TFT.h @@ -0,0 +1,517 @@ +/* Tiny TFT Graphics Library v5 - see http://www.technoblogy.com/show?3WAI + David Johnson-Davies - www.technoblogy.com - 26th October 2022 + + CC BY 4.0 + Licensed under a Creative Commons Attribution 4.0 International license: + http://creativecommons.org/licenses/by/4.0/ +*/ + +#include "FSH.h" +#include "DisplayInterface.h" + + +#if defined(MEGATINYCORE) +// ATtiny402/412 PORTA positions. Change these for the chip you're using +int const dc = 7; +int const mosi = 1; +int const sck = 3; +int const cs = 6; + +// ATtiny 0-, 1-, and 2-series port manipulations - assumes all pins in same port +#define PORT_TOGGLE(x) PORTA.OUTTGL = (x) +#define PORT_LOW(x) PORTA.OUTCLR = (x) +#define PORT_HIGH(x) PORTA.OUTSET = (x) +#define PORT_OUTPUT(x) PORTA.DIRSET = (x) + +#else +// ATtiny45/85 PORTB positions. Change these for the chip you're using +int const dc = 0; +int const mosi = 1; +int const sck = 2; +int const cs = 3; + +// Classic ATtiny port manipulations - assumes all pins in same port +#define PORT_TOGGLE(x) PINB = (x) +#define PORT_LOW(x) PORTB = PORTB & ~((x)); +#define PORT_HIGH(x) PORTB = PORTB | ((x)) +#define PORT_OUTPUT(x) DDRB = (x) + +#endif + +// Display parameters - uncomment the line for the one you want to use + +// Adafruit 1.44" 128x128 display +// int const xsize = 128, ysize = 128, xoff = 2, yoff = 1, invert = 0, rotate = 3, bgr = 1; + +// AliExpress 1.44" 128x128 display +// int const xsize = 128, ysize = 128, xoff = 2, yoff = 1, invert = 0, rotate = 3, bgr = 1; + +// Adafruit 0.96" 160x80 display +// int const xsize = 160, ysize = 80, xoff = 0, yoff = 24, invert = 0, rotate = 6, bgr = 0; + +// AliExpress 0.96" 160x80 display +// int const xsize = 160, ysize = 80, xoff = 1, yoff = 26, invert = 1, rotate = 0, bgr = 1; + +// Adafruit 1.8" 160x128 display +// int const xsize = 160, ysize = 128, xoff = 0, yoff = 0, invert = 0, rotate = 0, bgr = 1; + +// AliExpress 1.8" 160x128 display (red PCB) +int const xsize = 160, ysize = 128, xoff = 0, yoff = 0, invert = 0, rotate = 0, bgr = 1; + +// AliExpress 1.8" 160x128 display (blue PCB) +// int const xsize = 160, ysize = 128, xoff = 0, yoff = 0, invert = 0, rotate = 6, bgr = 0; + +// Adafruit 1.14" 240x135 display +// int const xsize = 240, ysize = 135, xoff = 40, yoff = 53, invert = 1, rotate = 6, bgr = 0; + +// AliExpress 1.14" 240x135 display +// int const xsize = 240, ysize = 135, xoff = 40, yoff = 52, invert = 1, rotate = 0, bgr = 0; + +// Adafruit 1.3" 240x240 display +// int const xsize = 240, ysize = 240, xoff = 0, yoff = 80, invert = 1, rotate = 5, bgr = 0; + +// Adafruit 1.54" 240x240 display +// int const xsize = 240, ysize = 240, xoff = 0, yoff = 80, invert = 1, rotate = 5, bgr = 0; + +// AliExpress 1.54" 240x240 display +// int const xsize = 240, ysize = 240, xoff = 0, yoff = 80, invert = 1, rotate = 5, bgr = 0; + +// Adafruit 1.9" 320x170 display +// int const xsize = 320, ysize = 170, xoff = 0, yoff = 35, invert = 1, rotate = 0, bgr = 0; + +// AliExpress 1.9" 320x170 display +// int const xsize = 320, ysize = 170, xoff = 0, yoff = 35, invert = 1, rotate = 0, bgr = 0; + +// Adafruit 1.47" 320x172 rounded rectangle display +// int const xsize = 320, ysize = 172, xoff = 0, yoff = 34, invert = 1, rotate = 0, bgr = 0; + +// AliExpress 1.47" 320x172 rounded rectangle display +// int const xsize = 320, ysize = 172, xoff = 0, yoff = 34, invert = 1, rotate = 0, bgr = 0; + +// Adafruit 2.0" 320x240 display +// int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 1, rotate = 6, bgr = 0; + +// AliExpress 2.0" 320x240 display +// int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 1, rotate = 0, bgr = 0; + +// Adafruit 2.2" 320x240 display +// int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 0, rotate = 4, bgr = 1; + +// AliExpress 2.4" 320x240 display +// int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 0, rotate = 2, bgr = 1; + +// Character set for text - stored in program memory +const uint8_t CharMap[96][6] FLASH = { +{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, +{ 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00 }, +{ 0x00, 0x07, 0x00, 0x07, 0x00, 0x00 }, +{ 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00 }, +{ 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00 }, +{ 0x23, 0x13, 0x08, 0x64, 0x62, 0x00 }, +{ 0x36, 0x49, 0x56, 0x20, 0x50, 0x00 }, +{ 0x00, 0x08, 0x07, 0x03, 0x00, 0x00 }, +{ 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00 }, +{ 0x00, 0x41, 0x22, 0x1C, 0x00, 0x00 }, +{ 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 0x00 }, +{ 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00 }, +{ 0x00, 0x80, 0x70, 0x30, 0x00, 0x00 }, +{ 0x08, 0x08, 0x08, 0x08, 0x08, 0x00 }, +{ 0x00, 0x00, 0x60, 0x60, 0x00, 0x00 }, +{ 0x20, 0x10, 0x08, 0x04, 0x02, 0x00 }, +{ 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00 }, +{ 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00 }, +{ 0x72, 0x49, 0x49, 0x49, 0x46, 0x00 }, +{ 0x21, 0x41, 0x49, 0x4D, 0x33, 0x00 }, +{ 0x18, 0x14, 0x12, 0x7F, 0x10, 0x00 }, +{ 0x27, 0x45, 0x45, 0x45, 0x39, 0x00 }, +{ 0x3C, 0x4A, 0x49, 0x49, 0x31, 0x00 }, +{ 0x41, 0x21, 0x11, 0x09, 0x07, 0x00 }, +{ 0x36, 0x49, 0x49, 0x49, 0x36, 0x00 }, +{ 0x46, 0x49, 0x49, 0x29, 0x1E, 0x00 }, +{ 0x00, 0x00, 0x14, 0x00, 0x00, 0x00 }, +{ 0x00, 0x40, 0x34, 0x00, 0x00, 0x00 }, +{ 0x00, 0x08, 0x14, 0x22, 0x41, 0x00 }, +{ 0x14, 0x14, 0x14, 0x14, 0x14, 0x00 }, +{ 0x00, 0x41, 0x22, 0x14, 0x08, 0x00 }, +{ 0x02, 0x01, 0x59, 0x09, 0x06, 0x00 }, +{ 0x3E, 0x41, 0x5D, 0x59, 0x4E, 0x00 }, +{ 0x7C, 0x12, 0x11, 0x12, 0x7C, 0x00 }, +{ 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00 }, +{ 0x3E, 0x41, 0x41, 0x41, 0x22, 0x00 }, +{ 0x7F, 0x41, 0x41, 0x41, 0x3E, 0x00 }, +{ 0x7F, 0x49, 0x49, 0x49, 0x41, 0x00 }, +{ 0x7F, 0x09, 0x09, 0x09, 0x01, 0x00 }, +{ 0x3E, 0x41, 0x41, 0x51, 0x73, 0x00 }, +{ 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00 }, +{ 0x00, 0x41, 0x7F, 0x41, 0x00, 0x00 }, +{ 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00 }, +{ 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00 }, +{ 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00 }, +{ 0x7F, 0x02, 0x1C, 0x02, 0x7F, 0x00 }, +{ 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00 }, +{ 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00 }, +{ 0x7F, 0x09, 0x09, 0x09, 0x06, 0x00 }, +{ 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00 }, +{ 0x7F, 0x09, 0x19, 0x29, 0x46, 0x00 }, +{ 0x26, 0x49, 0x49, 0x49, 0x32, 0x00 }, +{ 0x03, 0x01, 0x7F, 0x01, 0x03, 0x00 }, +{ 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00 }, +{ 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00 }, +{ 0x3F, 0x40, 0x38, 0x40, 0x3F, 0x00 }, +{ 0x63, 0x14, 0x08, 0x14, 0x63, 0x00 }, +{ 0x03, 0x04, 0x78, 0x04, 0x03, 0x00 }, +{ 0x61, 0x59, 0x49, 0x4D, 0x43, 0x00 }, +{ 0x00, 0x7F, 0x41, 0x41, 0x41, 0x00 }, +{ 0x02, 0x04, 0x08, 0x10, 0x20, 0x00 }, +{ 0x00, 0x41, 0x41, 0x41, 0x7F, 0x00 }, +{ 0x04, 0x02, 0x01, 0x02, 0x04, 0x00 }, +{ 0x40, 0x40, 0x40, 0x40, 0x40, 0x00 }, +{ 0x00, 0x03, 0x07, 0x08, 0x00, 0x00 }, +{ 0x20, 0x54, 0x54, 0x78, 0x40, 0x00 }, +{ 0x7F, 0x28, 0x44, 0x44, 0x38, 0x00 }, +{ 0x38, 0x44, 0x44, 0x44, 0x28, 0x00 }, +{ 0x38, 0x44, 0x44, 0x28, 0x7F, 0x00 }, +{ 0x38, 0x54, 0x54, 0x54, 0x18, 0x00 }, +{ 0x00, 0x08, 0x7E, 0x09, 0x02, 0x00 }, +{ 0x18, 0xA4, 0xA4, 0x9C, 0x78, 0x00 }, +{ 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00 }, +{ 0x00, 0x44, 0x7D, 0x40, 0x00, 0x00 }, +{ 0x20, 0x40, 0x40, 0x3D, 0x00, 0x00 }, +{ 0x7F, 0x10, 0x28, 0x44, 0x00, 0x00 }, +{ 0x00, 0x41, 0x7F, 0x40, 0x00, 0x00 }, +{ 0x7C, 0x04, 0x78, 0x04, 0x78, 0x00 }, +{ 0x7C, 0x08, 0x04, 0x04, 0x78, 0x00 }, +{ 0x38, 0x44, 0x44, 0x44, 0x38, 0x00 }, +{ 0xFC, 0x18, 0x24, 0x24, 0x18, 0x00 }, +{ 0x18, 0x24, 0x24, 0x18, 0xFC, 0x00 }, +{ 0x7C, 0x08, 0x04, 0x04, 0x08, 0x00 }, +{ 0x48, 0x54, 0x54, 0x54, 0x24, 0x00 }, +{ 0x04, 0x04, 0x3F, 0x44, 0x24, 0x00 }, +{ 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00 }, +{ 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00 }, +{ 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00 }, +{ 0x44, 0x28, 0x10, 0x28, 0x44, 0x00 }, +{ 0x4C, 0x90, 0x90, 0x90, 0x7C, 0x00 }, +{ 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00 }, +{ 0x00, 0x08, 0x36, 0x41, 0x00, 0x00 }, +{ 0x00, 0x00, 0x77, 0x00, 0x00, 0x00 }, +{ 0x00, 0x41, 0x36, 0x08, 0x00, 0x00 }, +{ 0x00, 0x06, 0x09, 0x06, 0x00, 0x00 }, // degree symbol = '~' +{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 } +}; + +// TFT colour display ********************************************** + +int const CASET = 0x2A; // Define column address +int const RASET = 0x2B; // Define row address +int const RAMWR = 0x2C; // Write to display RAM + +int const White = 0xFFFF; +int const Black = 0; + +// Current plot position and colours +int xpos, ypos; +int fore = White; +int back = Black; +int scale = 1; // Text scale + +// Send a byte to the display + +void Data (uint8_t d) { + for (uint8_t bit = 0x80; bit; bit >>= 1) { + PORT_TOGGLE(1<>8); Data(d1); Data(d2>>8); Data(d2); +} + +void InitDisplay () { + PORT_OUTPUT(1<>3; +} + +// Move current plot position to x,y +void MoveTo (int x, int y) { + xpos = x; ypos = y; +} + +// Plot point at x,y +void PlotPoint (int x, int y) { + PORT_TOGGLE(1<>8); Data(fore & 0xff); + PORT_TOGGLE(1< -dy) { err = err - dy; xpos = xpos + sx; } + if (e2 < dx) { err = err + dx; ypos = ypos + sy; } + } +} + +void FillRect (int w, int h) { + PORT_TOGGLE(1<>8; + uint8_t lo = fore & 0xff; + for (int i=0; i= y) { + MoveTo(x1-x, y1+y); FillRect(x<<1, 1); + MoveTo(x1-y, y1+x); FillRect(y<<1, 1); + MoveTo(x1-y, y1-x); FillRect(y<<1, 1); + MoveTo(x1-x, y1-y); FillRect(x<<1, 1); + if (err > 0) { + x = x - 1; dx = dx + 2; + err = err - (radius<<1) + dx; + } else { + y = y + 1; err = err + dy; + dy = dy + 2; + } + } + xpos = x1; ypos = y1; +} + +void DrawCircle (int radius) { + int x1 = xpos, y1 = ypos, dx = 1, dy = 1; + int x = radius - 1, y = 0; + int err = dx - (radius<<1); + while (x >= y) { + PlotPoint(x1-x, y1+y); PlotPoint(x1+x, y1+y); + PlotPoint(x1-y, y1+x); PlotPoint(x1+y, y1+x); + PlotPoint(x1-y, y1-x); PlotPoint(x1+y, y1-x); + PlotPoint(x1-x, y1-y); PlotPoint(x1+x, y1-y); + if (err > 0) { + x = x - 1; dx = dx + 2; + err = err - (radius<<1) + dx; + } else { + y = y + 1; err = err + dy; + dy = dy + 2; + } + } +} + +// Plot an ASCII character with bottom left corner at x,y +void PlotChar (char c) { + int colour; + PORT_TOGGLE(1<>(7-yy) & 1) colour = fore; else colour = back; + for (int yr=0; yr>8); Data(colour & 0xFF); + } + } + } + } + PORT_TOGGLE(1<0; d = d/10) { + char j = (n/d) % 10; + if (j!=0 || lead || d==1) { PlotChar(j + '0'); lead = true; } + } +} + +void TestChart () { + DrawRect(xsize, ysize); + scale = 8; + fore = Colour(255, 0, 0); + MoveTo((xsize-40)/2, (ysize-64)/2); PlotChar('F'); + scale = 1; +} + +// Demos ********************************************** + +void BarChart () { + int x0 = 0, y0 = 0, w = xsize, h = ysize, x1 = 15, y1 = 11; + MoveTo(x0+(w-x1-90)/2+x1, y0+h-8); PlotText(PSTR("Sensor Readings")); + // Horizontal axis + int xinc = (w-x1)/20; + MoveTo(x0+x1, y0+y1); DrawTo(x0+w-1, y0+y1); + for (int i=0; i<=20; i=i+4) { + int mark = x1+i*xinc; + MoveTo(x0+mark, y0+y1); DrawTo(x0+mark, y0+y1-2); + // Draw histogram + if (i != 20) { + int bar = xinc*4/3; + for (int b=2; b>=0; b--) { + fore = Colour(255, 127*b, 0); // Red, Orange, Yellow + MoveTo(x0+mark+bar*b-b+1, y0+y1+1); FillRect(bar, 5+random(h-y1-20)); + } + fore = White; + } + if (i > 9) MoveTo(x0+mark-7, y0+y1-11); else MoveTo(x0+mark-3, y0+y1-11); + PlotInt(i); + } + // Vertical axis + int yinc = (h-y1)/20; + MoveTo(x0+x1, y0+y1); DrawTo(x0+x1, y0+h-1); + for (int i=0; i<=20; i=i+5) { + int mark = y1+i*yinc; + MoveTo(x0+x1, y0+mark); DrawTo(x0+x1-2, y0+mark); + if (i > 9) MoveTo(x0+x1-15, y0+mark-4); else MoveTo(x0+x1-9, y0+mark-4); + PlotInt(i); + } +} + +void Waterfall () { + int x0 = 0, y0 = 0, w = xsize, h = ysize, x1 = 15, y1 = 11; + int factor = 5160/h*10; + MoveTo(x0+(w-x1-60)/2+x1, y0+h-8); PlotText(PSTR("Luminance")); + // Horizontal axis + int xinc = (w-x1-15)/30; + MoveTo(x0+x1, y0+y1); DrawTo(x0+x1+xinc*20, y0+y1); + for (int i=0; i<=20; i=i+5) { + int mark = x1+i*xinc; + MoveTo(x0+mark, y0+y1); DrawTo(x0+mark, y0+y1-2); + if (i > 9) MoveTo(x0+mark-7, y0+y1-11); else MoveTo(x0+mark-3, y0+y1-11); + PlotInt(i); + } + // Vertical axis + int yinc = (h-y1)/20; + MoveTo(x0+x1, y0+y1); DrawTo(x0+x1, y0+h-1); + for (int i=0; i<=20; i=i+5) { + int mark = y1+i*yinc; + MoveTo(x0+x1, y0+mark); DrawTo(x0+x1-2, y0+mark); + if (i > 9) MoveTo(x0+x1-15, y0+mark-4); else MoveTo(x0+x1-9, y0+mark-4); + PlotInt(i); + } + // Diagonal axis + yinc = xinc/2; + // MoveTo(x0+x1, y0+y1); DrawTo(x0+x1+10*xinc, y0+y1+10*xinc); + MoveTo(x0+x1+20*xinc, y0+y1); DrawTo(x0+x1+30*xinc, y0+y1+10*xinc); + for (int i=0; i<=20; i=i+5) { + MoveTo(x0+x1+20*xinc+i*xinc/2, y0+y1+i*xinc/2); + DrawTo(x0+x1+20*xinc+i*xinc/2+3, y0+y1+i*xinc/2); + MoveTo(x0+x1+20*xinc+i*xinc/2+6, y0+y1+i*xinc/2-4); PlotInt(i); + } + // Plot data + for (int y=20; y>=0; y--) { + for (int i=0; i<=20; i++) { + int fn0 = 180-(i-10)*(i-10)-(y-10)*(y-10); + int fn1 = 180-(i+1-10)*(i+1-10)-(y-10)*(y-10); + fore = Colour(255, 255, 0); + MoveTo(x0+x1+y*yinc+i*xinc, y0+y1+y*yinc+fn0*fn0/factor); + DrawTo(x0+x1+y*yinc+(i+1)*xinc, y0+y1+y*yinc+fn1*fn1/factor); + fore = White; + } + } +} + +// Setup ********************************************** + +void setup() { + InitDisplay(); + ClearDisplay(); + DisplayOn(); + MoveTo(0,0); + // TestChart(); +} + +void loop () { + BarChart(); + // Waterfall(); + for (;;); +} \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 40dc89e..1fc7291 100644 --- a/platformio.ini +++ b/platformio.ini @@ -31,6 +31,7 @@ include_dir = . [env] build_flags = -Wall -Wextra monitor_filters = time +; lib_deps = adafruit/Adafruit ST7735 and ST7789 Library @ ^1.10.0 [env:samd21-dev-usb] platform = atmelsam @@ -59,7 +60,7 @@ framework = arduino lib_deps = ${env.lib_deps} monitor_speed = 115200 monitor_echo = yes -build_flags = -std=c++17 -DI2C_EXTENDED_ADDRESS ; -DI2C_USE_WIRE -DDIAG_LOOPTIMES -DDIAG_IO +build_flags = -std=c++17 ; -DI2C_USE_WIRE -DDIAG_LOOPTIMES -DDIAG_IO [env:mega2560-debug] platform = atmelavr @@ -71,7 +72,7 @@ lib_deps = SPI monitor_speed = 115200 monitor_echo = yes -build_flags = -DI2C_EXTENDED_ADDRESS -DDIAG_IO -DDIAG_LOOPTIMES +build_flags = -DDIAG_IO=2 -DDIAG_LOOPTIMES [env:mega2560-no-HAL] platform = atmelavr @@ -83,7 +84,7 @@ lib_deps = SPI monitor_speed = 115200 monitor_echo = yes -build_flags = -DIO_NO_HAL +build_flags = -DIO_NO_HAL [env:mega2560-I2C-wire] platform = atmelavr @@ -107,7 +108,7 @@ lib_deps = SPI monitor_speed = 115200 monitor_echo = yes -build_flags = -mcall-prologues +build_flags = ; -DDIAG_LOOPTIMES [env:mega328] platform = atmelavr @@ -143,7 +144,7 @@ lib_deps = monitor_speed = 115200 monitor_echo = yes upload_speed = 19200 -build_flags = -DDIAG_IO +build_flags = [env:uno] platform = atmelavr @@ -187,7 +188,7 @@ platform = ststm32 board = nucleo_f446re framework = arduino lib_deps = ${env.lib_deps} -build_flags = -std=c++17 -Os -g2 -Wunused-variable +build_flags = -std=c++17 -Os -g2 -Wunused-variable -DDIAG_LOOPTIMES ; -DDIAG_IO monitor_speed = 115200 monitor_echo = yes diff --git a/version.h b/version.h index d790206..35058da 100644 --- a/version.h +++ b/version.h @@ -5,22 +5,24 @@ #define VERSION "4.2.18" -// 4.2.18 I2C Multiplexer support through Extended Addresses, -// added for Wire, 4209 and AVR I2C drivers. -// I2C retries when fail. -// I2C timeout handling and recovery completed. -// I2C SAMD Driver Read code completed. -// PCF8575 I2C GPIO driver added. -// EX-RAIL ANOUT function for triggering analogue -// HAL drivers (e.g. analogue outputs, DFPlayer, PWM). -// Installable HAL OLED Display Driver, with -// support for multiple displays. -// Layered HAL Drivers PCA9685pwm and Servo added for -// (1) native PWM on PCA9685 module and -// (2) animations of servo movement via PCA9685pwm. -// This is intended to support EXIOExpander and also -// replace the existing PCA9685 driver. -// Add to reinitialise failed drivers. +// 4.2.18 - I2C Multiplexer support through Extended Addresses, +// added for Wire, 4209 and AVR I2C drivers. +// - I2C retries when an operation fails. +// - I2C timeout handling and recovery completed. +// - I2C SAMD Driver Read code completed. +// - PCF8575 I2C GPIO driver added. +// - EX-RAIL ANOUT function for triggering analogue +// HAL drivers (e.g. analogue outputs, DFPlayer, PWM). +// - Installable HALDisplay Driver, with support +// for multiple displays. +// - Layered HAL Drivers PCA9685pwm and Servo added for +// native PWM on PCA9685 module and +// for animations of servo movement via PCA9685pwm. +// This is intended to support EXIOExpander and also +// replace the existing PCA9685 driver. +// - Add to reinitialise failed drivers. +// - Add UserAddin facility to allow a user-specific C++ +// function to be added in myHal.cpp. // 4.2.17 LCN bugfix // 4.2.16 Move EX-IOExpander servo support to the EX-IOExpander software // 4.2.15 Add basic experimental PWM support to EX-IOExpander