Initial panel driver in working state

This commit is contained in:
Prashant Sinha
2021-09-12 12:23:26 +02:00
commit c4828e0f0e
7 changed files with 586 additions and 0 deletions

145
.gitignore vendored Normal file
View File

@@ -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

View File

View File

@@ -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_))

View File

@@ -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<h;i++)
{
drawPixel(x,y+i,color);
}
}
void FrekvensPanel::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color)
{
for (int i=0;i<w;i++)
{
drawPixel(x+i,y,color);
}
}
void FrekvensPanel::fillScreen(uint16_t color)
{
for (int i=0;i<_numPages;i++)
{
unsigned short w = color & 0x01 ? 0xFFFF : 0x0000;
color >>= 1;
unsigned short* p = &buf[i * _pageStride];
for (int j=0;j<_pageStride;j++)
{
*p++ = w;
}
}
}

View File

@@ -0,0 +1,55 @@
// Copied From https://github.com/frumperino/FrekvensPanel
// by /u/frumperino
// goodwires.org
#ifndef __FREKVENSPANEL_H
#define __FREKVENSPANEL_H
#include <Arduino.h>
#include <Adafruit_GFX.h>
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

View File

@@ -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

View File

@@ -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