From c4828e0f0e1765c400119a31efa6664cd1de867b Mon Sep 17 00:00:00 2001 From: Prashant Sinha Date: Sun, 12 Sep 2021 12:23:26 +0200 Subject: [PATCH] Initial panel driver in working state --- .gitignore | 145 +++++++++++++ components/frekvens_panel/__init__.py | 0 components/frekvens_panel/display.py | 54 +++++ components/frekvens_panel/frekvens-driver.cpp | 195 ++++++++++++++++++ components/frekvens_panel/frekvens-driver.h | 55 +++++ components/frekvens_panel/frekvens-panel.cpp | 74 +++++++ components/frekvens_panel/frekvens-panel.h | 63 ++++++ 7 files changed, 586 insertions(+) create mode 100644 .gitignore create mode 100644 components/frekvens_panel/__init__.py create mode 100644 components/frekvens_panel/display.py create mode 100644 components/frekvens_panel/frekvens-driver.cpp create mode 100644 components/frekvens_panel/frekvens-driver.h create mode 100644 components/frekvens_panel/frekvens-panel.cpp create mode 100644 components/frekvens_panel/frekvens-panel.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4906f4c --- /dev/null +++ b/.gitignore @@ -0,0 +1,145 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/python +# Edit at https://www.toptal.com/developers/gitignore?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# End of https://www.toptal.com/developers/gitignore/api/python diff --git a/components/frekvens_panel/__init__.py b/components/frekvens_panel/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/components/frekvens_panel/display.py b/components/frekvens_panel/display.py new file mode 100644 index 0000000..f481b58 --- /dev/null +++ b/components/frekvens_panel/display.py @@ -0,0 +1,54 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import display +from esphome.const import ( + CONF_ID, + CONF_LAMBDA, + CONF_PAGES, + CONF_CONTRAST, +) + +DEPENDENCIES = [] + +CONF_LATCH_PIN = 'latch_pin' +CONF_CLOCK_PIN = 'clock_pin' +CONF_DATA_PIN = 'data_pin' + +frekvenspanel_ns = cg.esphome_ns.namespace("frekvenspanel") +Panel = frekvenspanel_ns.class_( + "Panel", cg.PollingComponent, display.DisplayBuffer +) + + +CONFIG_SCHEMA = cv.All( + display.FULL_DISPLAY_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(Panel), + cv.Required(CONF_LATCH_PIN): cv.int_, + cv.Required(CONF_CLOCK_PIN): cv.int_, + cv.Required(CONF_DATA_PIN): cv.int_, + cv.Optional(CONF_CONTRAST, default=0x7F): cv.int_, + } + ) + .extend(cv.polling_component_schema("1s")), + cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + + await cg.register_component(var, config) + await display.register_display(var, config) + + cg.add(var.set_pins( + config[CONF_LATCH_PIN], + config[CONF_CLOCK_PIN], + config[CONF_DATA_PIN], + )) + + if CONF_LAMBDA in config: + lambda_ = await cg.process_lambda( + config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + ) + cg.add(var.set_writer(lambda_)) diff --git a/components/frekvens_panel/frekvens-driver.cpp b/components/frekvens_panel/frekvens-driver.cpp new file mode 100644 index 0000000..56397ca --- /dev/null +++ b/components/frekvens_panel/frekvens-driver.cpp @@ -0,0 +1,195 @@ +// Copied From https://github.com/frumperino/FrekvensPanel +// By /u/frumperino +// goodwires.org + +#include "frekvens-driver.h" + +void init(int p_latch, int p_clock, int p_data, int p_enable); + +FrekvensPanel::FrekvensPanel(int p_latch, int p_clock, int p_data, int bitDepth, + int numPanels) : Adafruit_GFX(16, numPanels * 16) +{ + init(p_latch, p_clock, p_data, bitDepth, numPanels); +} + +FrekvensPanel::FrekvensPanel(int p_latch, int p_clock, int p_data) : +FrekvensPanel(p_latch, p_clock, p_data, 0, 1) { } + +void FrekvensPanel::init(int p_latch, int p_clock, int p_data, int bitDepth, int numPanels) +{ + _pLatch = p_latch; + _pClock = p_clock; + _pData = p_data; + pinMode(_pLatch, OUTPUT); + pinMode(_pClock, OUTPUT); + pinMode(_pData, OUTPUT); + _bitDepth = bitDepth; + _numPages = (1 << bitDepth); + _pageMask = _numPages - 1; + _numPanels = numPanels; + _pageStride = 16 * numPanels; + _numWords = _numPages * _pageStride; + _numPixels = 16 * 16 * numPanels; + _addressMask = _numPixels-1; + _width = 16; + _height = 16 * _numPanels; + buf = (unsigned short*) malloc(_numWords * sizeof(unsigned short)); +} + +void FrekvensPanel::clear() +{ + for (int i=0;i<_numWords;i++) + { + buf[i] = 0; + } +} + +void FrekvensPanel::writeDeepPixel(unsigned short x, unsigned short y, unsigned short value) +{ + if (x > 7) { y += 0x10; x &= 0x07; } + unsigned int address = (x + (y << 3)) & _addressMask; + unsigned short ba = address >> 4; + unsigned short br = address & 0x0F; + unsigned short ms = (1 << br); + unsigned short mc = 0xFFFF ^ ms; + unsigned short* wp = &buf[ba]; + unsigned short ofs = (x+y) & _pageMask; + for (unsigned int i=0;i<_numPages;i++) + { + ofs++; + ofs &= _pageMask; + char b = ((value >> ofs) & 0x01); + if (b) + { + *wp |= ms; + } + else + { + *wp &= mc; + } + wp+=_pageStride; + } +} + +void FrekvensPanel::scan() +{ + if (_numPages == 1) + { + // single bit plane + unsigned short* p = &buf[0]; + for (int i=0;i<_pageStride;i++) + { + unsigned short w = *p++; + for (int j=0;j<16;j++) + { + digitalWrite(_pData, w & 0x01); + w >>= 1; + digitalWrite(_pClock, HIGH); + delayMicroseconds(1); + digitalWrite(_pClock, LOW); + } + } + digitalWrite(_pLatch,HIGH); + delayMicroseconds(1); + digitalWrite(_pLatch,LOW); + } + else + { + // multiple bit planes + unsigned short* p = &buf[_pageStride * _activePage]; + for (int i=0;i<_pageStride;i++) + { + unsigned short w = *p++; + for (int j=0;j<16;j++) + { + digitalWrite(_pData, w & 0x01); + w >>= 1; + digitalWrite(_pClock, HIGH); + delayMicroseconds(1); + digitalWrite(_pClock, LOW); + } + } + digitalWrite(_pLatch,HIGH); + delayMicroseconds(1); + digitalWrite(_pLatch,LOW); + _activePage++; + _activePage %= _numPages; + } +} + +unsigned short FrekvensPanel::width() +{ + return this->_width; +} + +unsigned short FrekvensPanel::height() +{ + return this->_height; +} + +boolean FrekvensPanel::getPixel(int16_t x, int16_t y) +{ + if (x > 7) { y += 0x10; x &= 0x07; } + unsigned short address = (x + (y << 3)) & _addressMask; + unsigned short ba = address >> 4; + unsigned short br = address & 0x0F; + unsigned short* wp = &buf[ba]; + return ((*wp) >> br) & 0x01; +} + +void FrekvensPanel::drawPixel(int16_t x, int16_t y, uint16_t color) +{ + if ((x >= 0) && (y >= 0) && (x < _width) && ( y < _height)) + { + if (x > 7) { y += 0x10; x &= 0x07; } + unsigned short address = (x + (y << 3)) & _addressMask; + unsigned short ba = address >> 4; + unsigned short br = address & 0x0F; + unsigned short ms = (1 << br); + unsigned short* wp = &buf[ba]; + if (color & 0x01) + { + *wp |= ms; + } + else + { + *wp &= (0xFFFF ^ ms); + } + } +} + +void FrekvensPanel::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) +{ + for (int i=0;i>= 1; + unsigned short* p = &buf[i * _pageStride]; + for (int j=0;j<_pageStride;j++) + { + *p++ = w; + } + } +} + + + + + diff --git a/components/frekvens_panel/frekvens-driver.h b/components/frekvens_panel/frekvens-driver.h new file mode 100644 index 0000000..e9ee0c1 --- /dev/null +++ b/components/frekvens_panel/frekvens-driver.h @@ -0,0 +1,55 @@ +// Copied From https://github.com/frumperino/FrekvensPanel +// by /u/frumperino +// goodwires.org + +#ifndef __FREKVENSPANEL_H +#define __FREKVENSPANEL_H + +#include +#include + +class FrekvensPanel : public Adafruit_GFX +{ +private: + unsigned short _numPanels : 4; + unsigned short _numPages : 4; + unsigned short _activePage : 4; + unsigned short _bitDepth : 4; + unsigned short _pLatch; + unsigned short _pClock; + unsigned short _pData; + unsigned short* buf; + unsigned short _pageMask; + unsigned short _addressMask; + unsigned short _numWords; + unsigned short _pageStride; + unsigned short _numPixels; + unsigned short _width; + unsigned short _height; + +public: + FrekvensPanel(int p_latch, int p_clock, int p_data, int bitDepth, + int numPanels); + FrekvensPanel(int p_latch, int p_clock, int p_data); + + void init(int p_latch, int p_clock, int p_data, int bitDepth, int numPanels); + void clear(); + void scan(); + void writeDeepPixel(unsigned short x, unsigned short y, unsigned short value); + + boolean getPixel(int16_t x, int16_t y); + + unsigned short width(); + unsigned short height(); + + void drawPixel(int16_t x, int16_t y, uint16_t color) override; + void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override; + void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override; + void fillScreen(uint16_t color) override; + +private: + +}; + + +#endif //__FREKVENSPANEL_H diff --git a/components/frekvens_panel/frekvens-panel.cpp b/components/frekvens_panel/frekvens-panel.cpp new file mode 100644 index 0000000..372d32d --- /dev/null +++ b/components/frekvens_panel/frekvens-panel.cpp @@ -0,0 +1,74 @@ +#include "esphome/core/log.h" +#include "esphome/core/application.h" +#include "esphome/core/helpers.h" + +#include "frekvens-panel.h" + + +namespace esphome { +namespace frekvenspanel { + +static const char *const TAG = "frekvenspanel"; + + +void Panel::initialize() { + this->init_internal_(this->get_buffer_length_()); + this->panel = new FrekvensPanel(this->p_latch, this->p_clock, this->p_data); +} + +int Panel::get_width_internal() { return 16; } +int Panel::get_height_internal() { return 16; } + +size_t Panel::get_buffer_length_() { + return size_t(this->get_width_internal()) * size_t(this->get_height_internal()); +} + +void HOT Panel::display() { + uint8_t x, y, cell; + ESP_LOGI(TAG, "Displaying..."); + + this->panel->clear(); + for (x = 0; x < this->get_width_internal(); x++) { + for (y = 0; y < this->get_height_internal(); y++) { + cell = this->buffer_[(this->get_width_internal() * y) + x]; + this->panel->drawPixel(x, y, cell); + } + } + this->panel->scan(); +} + +void HOT Panel::draw_absolute_pixel_internal(int x, int y, Color color) { + if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0) { + return; + } + + uint16_t pos = x + (y) * this->get_width_internal(); + uint8_t subpos = y % 8; + + if (color.is_on()) { + this->buffer_[pos] |= 1; + } else { + this->buffer_[pos] &= ~1; + } +} + +void Panel::dump_config() { + LOG_DISPLAY("", "Panel", this); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_UPDATE_INTERVAL(this); +} + +void Panel::update() { + this->do_update_(); + this->display(); +} + +void Panel::fill(Color color) { + uint8_t fill = color.is_on() ? 0xFF : 0x00; + for (uint32_t i = 0; i < this->get_buffer_length_(); i++) + this->buffer_[i] = fill; +} + +} // namespace frekvenspanel +} // namespace esphome diff --git a/components/frekvens_panel/frekvens-panel.h b/components/frekvens_panel/frekvens-panel.h new file mode 100644 index 0000000..77066b1 --- /dev/null +++ b/components/frekvens_panel/frekvens-panel.h @@ -0,0 +1,63 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/display/display_buffer.h" + +#include "frekvens-driver.h" + + +namespace esphome { +namespace frekvenspanel { + +class Panel : public PollingComponent, + public display::DisplayBuffer { + public: + int p_latch; + int p_clock; + int p_data; + + void set_pins(int latch, int clock, int data) { + this->p_latch = latch; + this->p_clock = clock; + this->p_data = data; + } + + float get_setup_priority() const override { return setup_priority::PROCESSOR; } + + void data(uint8_t value); + + void initialize(); + void dump_config() override; + void HOT display(); + + void update() override; + + void fill(Color color) override; + + void setup() override { + this->initialize(); + } + + protected: + void draw_absolute_pixel_internal(int x, int y, Color color) override; + + void init_reset_(); + + size_t get_buffer_length_(); + + void start_command_(); + void end_command_(); + void start_data_(); + void end_data_(); + + int get_width_internal() override; + int get_height_internal() override; + + GPIOPin *reset_pin_; + GPIOPin *dc_pin_; + + FrekvensPanel *panel; +}; + +} // namespace pcd8544 +} // namespace esphome