mirror of
https://github.com/sascha-hemi/esphome-ikea-led-matrix.git
synced 2026-03-20 23:04:11 +01:00
Initial panel driver in working state
This commit is contained in:
145
.gitignore
vendored
Normal file
145
.gitignore
vendored
Normal 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
|
||||
0
components/frekvens_panel/__init__.py
Normal file
0
components/frekvens_panel/__init__.py
Normal file
54
components/frekvens_panel/display.py
Normal file
54
components/frekvens_panel/display.py
Normal 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_))
|
||||
195
components/frekvens_panel/frekvens-driver.cpp
Normal file
195
components/frekvens_panel/frekvens-driver.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
55
components/frekvens_panel/frekvens-driver.h
Normal file
55
components/frekvens_panel/frekvens-driver.h
Normal 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
|
||||
74
components/frekvens_panel/frekvens-panel.cpp
Normal file
74
components/frekvens_panel/frekvens-panel.cpp
Normal 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
|
||||
63
components/frekvens_panel/frekvens-panel.h
Normal file
63
components/frekvens_panel/frekvens-panel.h
Normal 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
|
||||
Reference in New Issue
Block a user