mirror of
https://github.com/esphome/esphome.git
synced 2024-12-04 20:48:21 +01:00
[new] restructure (new display class), impl. demo mode action
This commit is contained in:
parent
b9ee2b2deb
commit
ef9f16a9dc
6 changed files with 963 additions and 494 deletions
72
esphome/components/max6921/automation.h
Normal file
72
esphome/components/max6921/automation.h
Normal file
|
@ -0,0 +1,72 @@
|
|||
#pragma once
|
||||
|
||||
#include "display.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "max6921.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace max6921 {
|
||||
|
||||
class Display;
|
||||
|
||||
template<typename... Ts> class SetBrightnessAction : public Action<Ts...>, public Parented<MAX6921Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, brightness)
|
||||
|
||||
void play(Ts... x) override { this->parent_->set_brightness(this->brightness_.value(x...)); }
|
||||
};
|
||||
|
||||
#if 0
|
||||
template<typename... Ts> class SetDemoModeAction : public Action<Ts...>, public Parented<MAX6921Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(DemoMode, mode)
|
||||
TEMPLATABLE_VALUE(uint8_t, cycle_num)
|
||||
|
||||
void play(Ts... x) override {
|
||||
this->parent_->set_demo_mode(this->mode_.value(x...), this->cycle_num_.optional_value(x...));
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
template<typename... Ts> class SetDemoModeAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit SetDemoModeAction(MAX6921Component *max9621) : max9621_(max9621) {}
|
||||
|
||||
TEMPLATABLE_VALUE(std::string, mode)
|
||||
TEMPLATABLE_VALUE(uint8_t, cycle_num)
|
||||
|
||||
// overlay to cover string inputs
|
||||
// void set_mode(const std::string mode);
|
||||
// void set_mode(const std::string mode) {
|
||||
// if (str_equals_case_insensitive(mode, "off")) {
|
||||
// this->set_mode(DEMO_MODE_OFF);
|
||||
// } else if (str_equals_case_insensitive(mode, "scroll_font")) {
|
||||
// this->set_mode(DEMO_MODE_SCROLL_FONT);
|
||||
// } else {
|
||||
// ESP_LOGW(TAG, "Invalid demo mode %s", mode.c_str());
|
||||
// }
|
||||
// }
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto cycle_num = this->cycle_num_.value(x...);
|
||||
this->max9621_->display_->set_demo_mode(this->mode_.value(x...), cycle_num);
|
||||
}
|
||||
|
||||
protected:
|
||||
// TemplatableValue<const std::string, Ts...> mode{};
|
||||
MAX6921Component *max9621_;
|
||||
// DemoMode mode_;
|
||||
};
|
||||
|
||||
|
||||
template<typename... Ts> class SetTextAction : public Action<Ts...>, public Parented<MAX6921Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(std::string, text)
|
||||
|
||||
void play(Ts... x) override { this->parent_->set_text(this->text_.value(x...)); }
|
||||
};
|
||||
|
||||
|
||||
} // namespace max9621
|
||||
} // namespace esphome
|
640
esphome/components/max6921/display.cpp
Normal file
640
esphome/components/max6921/display.cpp
Normal file
|
@ -0,0 +1,640 @@
|
|||
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "display.h"
|
||||
#include "max6921.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace max6921 {
|
||||
|
||||
|
||||
static const char *const TAG = "max6921.display";
|
||||
|
||||
// segments of 7-segment character
|
||||
static const uint8_t SEG_A = (1<<0);
|
||||
static const uint8_t SEG_B = (1<<1);
|
||||
static const uint8_t SEG_C = (1<<2);
|
||||
static const uint8_t SEG_D = (1<<3);
|
||||
static const uint8_t SEG_E = (1<<4);
|
||||
static const uint8_t SEG_F = (1<<5);
|
||||
static const uint8_t SEG_G = (1<<6);
|
||||
static const uint8_t SEG_DP = (1<<7);
|
||||
static const uint8_t SEG_UNSUPPORTED_CHAR = 0;
|
||||
|
||||
// ASCII table from 0x20..0x7E
|
||||
const uint8_t ASCII_TO_SEG[FONT_SIZE] PROGMEM = {
|
||||
0, // ' ', (0x20)
|
||||
SEG_UNSUPPORTED_CHAR, // '!', (0x21)
|
||||
SEG_B|SEG_F, // '"', (0x22)
|
||||
SEG_UNSUPPORTED_CHAR, // '#', (0x23)
|
||||
SEG_UNSUPPORTED_CHAR, // '$', (0x24)
|
||||
SEG_UNSUPPORTED_CHAR, // '%', (0x25)
|
||||
SEG_UNSUPPORTED_CHAR, // '&', (0x26)
|
||||
SEG_F, // ''', (0x27)
|
||||
SEG_A|SEG_D|SEG_E|SEG_F, // '(', (0x28)
|
||||
SEG_A|SEG_B|SEG_C|SEG_D, // ')', (0x29)
|
||||
SEG_UNSUPPORTED_CHAR, // '*', (0x2A)
|
||||
SEG_UNSUPPORTED_CHAR, // '+', (0x2B)
|
||||
SEG_DP, // ',', (0x2C)
|
||||
SEG_G, // '-', (0x2D)
|
||||
SEG_DP, // '.', (0x2E)
|
||||
SEG_UNSUPPORTED_CHAR, // '/', (0x2F)
|
||||
SEG_A|SEG_B|SEG_C|SEG_D|SEG_E|SEG_F, // '0', (0x30)
|
||||
SEG_B|SEG_C, // '1', (0x31)
|
||||
SEG_A|SEG_B|SEG_D|SEG_E|SEG_G, // '2', (0x32)
|
||||
SEG_A|SEG_B|SEG_C|SEG_D|SEG_G, // '3', (0x33)
|
||||
SEG_B|SEG_C|SEG_F|SEG_G, // '4', (0x34)
|
||||
SEG_A|SEG_C|SEG_D|SEG_F|SEG_G, // '5', (0x35)
|
||||
SEG_A|SEG_C|SEG_D|SEG_E|SEG_F|SEG_G, // '6', (0x36)
|
||||
SEG_A|SEG_B|SEG_C, // '7', (0x37)
|
||||
SEG_A|SEG_B|SEG_C|SEG_D|SEG_E|SEG_F|SEG_G, // '8', (0x38)
|
||||
SEG_A|SEG_B|SEG_C|SEG_D|SEG_F|SEG_G, // '9', (0x39)
|
||||
SEG_UNSUPPORTED_CHAR, // ':', (0x3A)
|
||||
SEG_UNSUPPORTED_CHAR, // ';', (0x3B)
|
||||
SEG_UNSUPPORTED_CHAR, // '<', (0x3C)
|
||||
SEG_D|SEG_G, // '=', (0x3D)
|
||||
SEG_UNSUPPORTED_CHAR, // '>', (0x3E)
|
||||
SEG_A|SEG_B|SEG_E|SEG_G, // '?', (0x3F)
|
||||
SEG_A|SEG_B|SEG_D|SEG_E|SEG_F|SEG_G, // '@', (0x40)
|
||||
SEG_A|SEG_B|SEG_C|SEG_E|SEG_F|SEG_G, // 'A', (0x41)
|
||||
SEG_C|SEG_D|SEG_E|SEG_F|SEG_G, // 'B', (0x42)
|
||||
SEG_A|SEG_D|SEG_E|SEG_F, // 'C', (0x43)
|
||||
SEG_B|SEG_C|SEG_D|SEG_E|SEG_G, // 'D', (0x44)
|
||||
SEG_A|SEG_D|SEG_E|SEG_F|SEG_G, // 'E', (0x45)
|
||||
SEG_A|SEG_E|SEG_F|SEG_G, // 'F', (0x46)
|
||||
SEG_A|SEG_C|SEG_D|SEG_E|SEG_F, // 'G', (0x47)
|
||||
SEG_B|SEG_C|SEG_E|SEG_F|SEG_G, // 'H', (0x48)
|
||||
SEG_B|SEG_C, // 'I', (0x49)
|
||||
SEG_B|SEG_C|SEG_D|SEG_E, // 'J', (0x4A)
|
||||
SEG_UNSUPPORTED_CHAR, // 'K', (0x4B)
|
||||
SEG_D|SEG_E|SEG_F, // 'L', (0x4C)
|
||||
SEG_UNSUPPORTED_CHAR, // 'M', (0x4D)
|
||||
SEG_C|SEG_E|SEG_G, // 'N', (0x4E)
|
||||
SEG_A|SEG_B|SEG_C|SEG_D|SEG_E|SEG_F, // 'O', (0x4F)
|
||||
SEG_A|SEG_B|SEG_E|SEG_F|SEG_G, // 'P', (0x50)
|
||||
SEG_UNSUPPORTED_CHAR, // 'Q', (0x51)
|
||||
SEG_E|SEG_G, // 'R', (0x52)
|
||||
SEG_A|SEG_C|SEG_D|SEG_F|SEG_G, // 'S', (0x53)
|
||||
SEG_UNSUPPORTED_CHAR, // 'T', (0x54)
|
||||
SEG_B|SEG_C|SEG_D|SEG_E|SEG_F, // 'U', (0x55)
|
||||
SEG_UNSUPPORTED_CHAR, // 'V', (0x56)
|
||||
SEG_UNSUPPORTED_CHAR, // 'W', (0x57)
|
||||
SEG_UNSUPPORTED_CHAR, // 'X', (0x58)
|
||||
SEG_B|SEG_E|SEG_F|SEG_G, // 'Y', (0x59)
|
||||
SEG_UNSUPPORTED_CHAR, // 'Z', (0x5A)
|
||||
SEG_A|SEG_D|SEG_E|SEG_F, // '[', (0x5B)
|
||||
SEG_UNSUPPORTED_CHAR, // '\', (0x5C)
|
||||
SEG_A|SEG_B|SEG_C|SEG_D, // ']', (0x5D)
|
||||
SEG_UNSUPPORTED_CHAR, // '^', (0x5E)
|
||||
SEG_D, // '_', (0x5F)
|
||||
SEG_F, // '`', (0x60)
|
||||
SEG_A|SEG_B|SEG_C|SEG_E|SEG_F|SEG_G, // 'a', (0x61)
|
||||
SEG_C|SEG_D|SEG_E|SEG_F|SEG_G, // 'b', (0x62)
|
||||
SEG_D|SEG_E|SEG_G, // 'c', (0x63)
|
||||
SEG_B|SEG_C|SEG_D|SEG_E|SEG_G, // 'd', (0x64)
|
||||
SEG_A|SEG_D|SEG_E|SEG_F|SEG_G, // 'e', (0x65)
|
||||
SEG_A|SEG_E|SEG_F|SEG_G, // 'f', (0x66)
|
||||
SEG_A|SEG_C|SEG_D|SEG_E|SEG_F, // 'g', (0x67)
|
||||
SEG_C|SEG_E|SEG_F|SEG_G, // 'h', (0x68)
|
||||
SEG_C, // 'i', (0x69)
|
||||
SEG_B|SEG_C|SEG_D|SEG_E, // 'j', (0x6A)
|
||||
SEG_UNSUPPORTED_CHAR, // 'k', (0x6B)
|
||||
SEG_D|SEG_E|SEG_F, // 'l', (0x6C)
|
||||
SEG_UNSUPPORTED_CHAR, // 'm', (0x6D)
|
||||
SEG_C|SEG_E|SEG_G, // 'n', (0x6E)
|
||||
SEG_C|SEG_D|SEG_E|SEG_G, // 'o', (0x6F)
|
||||
SEG_A|SEG_B|SEG_E|SEG_F|SEG_G, // 'p', (0x70)
|
||||
SEG_UNSUPPORTED_CHAR, // 'q', (0x71)
|
||||
SEG_E|SEG_G, // 'r', (0x72)
|
||||
SEG_A|SEG_C|SEG_D|SEG_F|SEG_G, // 's', (0x73)
|
||||
SEG_UNSUPPORTED_CHAR, // 't', (0x74)
|
||||
SEG_C|SEG_D|SEG_E, // 'u', (0x75)
|
||||
SEG_UNSUPPORTED_CHAR, // 'v', (0x76)
|
||||
SEG_UNSUPPORTED_CHAR, // 'w', (0x77)
|
||||
SEG_UNSUPPORTED_CHAR, // 'x', (0x78)
|
||||
SEG_B|SEG_E|SEG_F|SEG_G, // 'y', (0x79)
|
||||
SEG_UNSUPPORTED_CHAR, // 'z', (0x7A)
|
||||
SEG_B|SEG_C|SEG_G, // '{', (0x7B)
|
||||
SEG_UNSUPPORTED_CHAR, // '|', (0x7C)
|
||||
SEG_E|SEG_F|SEG_G, // '}', (0x7D)
|
||||
SEG_UNSUPPORTED_CHAR, // '~', (0x7E)
|
||||
};
|
||||
|
||||
|
||||
void Display::setup(std::vector<uint8_t>& seg_to_out_map, std::vector<uint8_t>& pos_to_out_map) {
|
||||
this->seg_to_out_map_ = seg_to_out_map;
|
||||
this->pos_to_out_map_ = pos_to_out_map;
|
||||
this->num_digits_ = pos_to_out_map.size();
|
||||
ESP_LOGCONFIG(TAG, "Display digits: %u", this->num_digits_);
|
||||
|
||||
// setup font...
|
||||
init_font__();
|
||||
|
||||
// display output buffer...
|
||||
this->out_buf_size_ = this->num_digits_ * 3;
|
||||
this->out_buf_ = new uint8_t[this->out_buf_size_]; // NOLINT
|
||||
memset(this->out_buf_, 0, this->out_buf_size_);
|
||||
|
||||
// find smallest segment DOUT number...
|
||||
this->seg_out_smallest_ = 19;
|
||||
for (uint8_t i=0; i<this->seg_to_out_map_.size(); i++) {
|
||||
if (this->seg_to_out_map_[i] < this->seg_out_smallest_)
|
||||
this->seg_out_smallest_ = this->seg_to_out_map_[i];
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "Display smallest DOUT number: %u", this->seg_out_smallest_);
|
||||
|
||||
// calculate refresh period for 60Hz
|
||||
this->refresh_period_us_ = 1000000 / 60 / this->num_digits_;
|
||||
ESP_LOGCONFIG(TAG, "Set display refresh period: %" PRIu32 "us for %u digits @ 60Hz",
|
||||
this->refresh_period_us_, this->num_digits_);
|
||||
|
||||
/* Setup display refresh.
|
||||
* Using a timer is not an option, because the WiFi component uses timer as
|
||||
* well, which leads to unstable refresh cycles (flickering). Therefore a
|
||||
* thread on 2nd MCU core is used.
|
||||
*/
|
||||
xTaskCreatePinnedToCore(&Display::display_refresh_task_,
|
||||
"display_refresh_task", // name
|
||||
2048, // stack size
|
||||
this, // pass component pointer as task parameter pv
|
||||
1, // priority (one above IDLE task)
|
||||
nullptr, // handle
|
||||
1 // core
|
||||
);
|
||||
|
||||
ESP_LOGCONFIG(TAG, "Display mode: %u", this->mode);
|
||||
ESP_LOGCONFIG(TAG, "Display scroll mode: %u", this->scroll_mode);
|
||||
}
|
||||
|
||||
void HOT Display::display_refresh_task_(void *pv) {
|
||||
Display *display = (Display*)pv;
|
||||
static uint count = display->num_digits_;
|
||||
static uint current_pos = 1;
|
||||
|
||||
if (display->refresh_period_us_ == 0) {
|
||||
ESP_LOGE(TAG, "Invalid display refresh period -> using default 2ms");
|
||||
display->refresh_period_us_ = 2000;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
// one-time verbose output for all positions after any content change...
|
||||
if (display->disp_text_.content_changed) {
|
||||
count = 0;
|
||||
display->disp_text_.content_changed = false;
|
||||
}
|
||||
if (count < display->num_digits_) {
|
||||
count++;
|
||||
ESP_LOGVV(TAG, "%s(): SPI transfer for position %u: 0x%02x%02x%02x",
|
||||
__func__, current_pos,
|
||||
display->out_buf_[(current_pos-1)*3],
|
||||
display->out_buf_[(current_pos-1)*3+1],
|
||||
display->out_buf_[(current_pos-1)*3+2]);
|
||||
}
|
||||
|
||||
// write MAX9621 data of current display position...
|
||||
display->max6921_->write_data(&display->out_buf_[(current_pos-1)*3], 3);
|
||||
|
||||
// next display position...
|
||||
if (++current_pos > display->num_digits_)
|
||||
current_pos = 1;
|
||||
|
||||
delayMicroseconds(display->refresh_period_us_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Display::dump_config() {
|
||||
char seg_name[3];
|
||||
|
||||
// display segment to DOUTx mapping...
|
||||
for (uint i=0; i<this->seg_to_out_map_.size(); i++) {
|
||||
if (i < 7) {
|
||||
seg_name[0] = 'a' + i;
|
||||
seg_name[1] = 0;
|
||||
} else
|
||||
strncpy(seg_name, "dp", sizeof(seg_name));
|
||||
ESP_LOGCONFIG(TAG, " Display segment %2s: OUT%u", seg_name, this->seg_to_out_map_[i]);
|
||||
}
|
||||
// display position to DOUTx mapping...
|
||||
for (uint i=0; i<this->seg_to_out_map_.size(); i++) {
|
||||
ESP_LOGCONFIG(TAG, " Display position %2u: OUT%u", i, this->pos_to_out_map_[i]);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Brightness: %.1f", get_brightness());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Checks if the given character activates the point segment only.
|
||||
*
|
||||
* @param c character to check
|
||||
*
|
||||
* @return true, if character activates the point segment only, otherwise false
|
||||
*/
|
||||
bool Display::isPointSegOnly(char c) {
|
||||
return ((c == ',') || (c == '.'));
|
||||
}
|
||||
|
||||
|
||||
void Display::init_font__(void) {
|
||||
uint8_t seg_data;
|
||||
|
||||
this->ascii_out_data_ = new uint8_t[ARRAY_ELEM_COUNT(ASCII_TO_SEG)]; // NOLINT
|
||||
|
||||
for (size_t ascii_idx=0; ascii_idx<ARRAY_ELEM_COUNT(ASCII_TO_SEG); ascii_idx++) {
|
||||
this->ascii_out_data_[ascii_idx] = 0;
|
||||
seg_data = progmem_read_byte(&ASCII_TO_SEG[ascii_idx]);
|
||||
if (seg_data & SEG_A)
|
||||
this->ascii_out_data_[ascii_idx] |= (1 << (this->seg_to_out_map_[0] % 8));
|
||||
if (seg_data & SEG_B)
|
||||
this->ascii_out_data_[ascii_idx] |= (1 << (this->seg_to_out_map_[1] % 8));
|
||||
if (seg_data & SEG_C)
|
||||
this->ascii_out_data_[ascii_idx] |= (1 << (this->seg_to_out_map_[2] % 8));
|
||||
if (seg_data & SEG_D)
|
||||
this->ascii_out_data_[ascii_idx] |= (1 << (this->seg_to_out_map_[3] % 8));
|
||||
if (seg_data & SEG_E)
|
||||
this->ascii_out_data_[ascii_idx] |= (1 << (this->seg_to_out_map_[4] % 8));
|
||||
if (seg_data & SEG_F)
|
||||
this->ascii_out_data_[ascii_idx] |= (1 << (this->seg_to_out_map_[5] % 8));
|
||||
if (seg_data & SEG_G)
|
||||
this->ascii_out_data_[ascii_idx] |= (1 << (this->seg_to_out_map_[6] % 8));
|
||||
if (seg_data & SEG_DP)
|
||||
this->ascii_out_data_[ascii_idx] |= (1 << (this->seg_to_out_map_[7] % 8));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clears the whole display buffer or only the given position.
|
||||
*
|
||||
* @param pos display position 0..n (optional, default=whole display)
|
||||
*/
|
||||
void Display::clear(int pos) {
|
||||
if (pos < 0)
|
||||
memset(this->out_buf_, 0, this->out_buf_size_); // clear whole display buffer
|
||||
else if (pos < this->num_digits_)
|
||||
memset(&this->out_buf_[pos*3], 0, 3); // clear display buffer at given position
|
||||
else
|
||||
ESP_LOGW(TAG, "Invalid display position %i (max=%u)", pos, this->num_digits_ - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates the display.
|
||||
*/
|
||||
void Display::update(void) {
|
||||
// handle display brightness...
|
||||
if (this->brightness_cfg_changed_) {
|
||||
uint32_t inverted_duty = this->brightness_max_duty_ - \
|
||||
this->brightness_max_duty_ * \
|
||||
this->brightness_cfg_value_; // calc duty for low-active BLANK pin
|
||||
ESP_LOGD(TAG, "Change display brightness to %.1f (off-time duty=%u/%u)",
|
||||
brightness_cfg_value_, inverted_duty, this->brightness_max_duty_);
|
||||
ledcWrite(this->brightness_pwm_channel_, inverted_duty);
|
||||
this->brightness_cfg_changed_ = false;
|
||||
}
|
||||
|
||||
// handle display scroll modes...
|
||||
switch (this->scroll_mode) {
|
||||
case DISP_SCROLL_MODE_LEFT:
|
||||
update_out_buf_(this->disp_text_);
|
||||
scroll_left_(this->disp_text_);
|
||||
break;
|
||||
case DISP_SCROLL_MODE_OFF:
|
||||
default:
|
||||
this->mode = DISP_MODE_PRINT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Display::set_demo_mode(demo_mode_t mode, uint8_t cycle_num) {
|
||||
uint text_idx, font_idx;
|
||||
|
||||
ESP_LOGD(TAG, "demo_mode=%i, cycle_num=%u", mode, cycle_num);
|
||||
// this->display.demo_mode_cycle_num = cycle_num;
|
||||
// this->display.demo_mode = mode;
|
||||
|
||||
switch (mode) {
|
||||
case DEMO_MODE_SCROLL_FONT:
|
||||
// generate scroll text based on font...
|
||||
for (text_idx=0,font_idx=0; font_idx<ARRAY_ELEM_COUNT(ASCII_TO_SEG); font_idx++) {
|
||||
if (this->ascii_out_data_[font_idx] > 0) // displayable character?
|
||||
this->disp_text_.text[text_idx++] = ' ' + font_idx; // add character to string
|
||||
if (text_idx >= sizeof(this->disp_text_.text) - 1) { // max. text buffer lenght reached?
|
||||
ESP_LOGD(TAG, "Font too large for internal text buffer");
|
||||
break;
|
||||
}
|
||||
}
|
||||
this->disp_text_.text[text_idx] = 0;
|
||||
ESP_LOGV(TAG, "%s(): text: %s", __func__, this->disp_text_.text);
|
||||
|
||||
set_scroll_mode(&this->disp_text_, DISP_SCROLL_MODE_LEFT, cycle_num);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
set_mode(DISP_MODE_OTHER);
|
||||
clear();
|
||||
}
|
||||
|
||||
void Display::set_demo_mode(const std::string& mode, uint8_t cycle_num) {
|
||||
if (str_equals_case_insensitive(mode, "off")) {
|
||||
this->set_demo_mode(DEMO_MODE_OFF, cycle_num);
|
||||
} else if (str_equals_case_insensitive(mode, "scroll_font")) {
|
||||
this->set_demo_mode(DEMO_MODE_SCROLL_FONT, cycle_num);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Invalid demo mode: %s", mode.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Shows the given text on the display at given position.
|
||||
*
|
||||
* @param start_pos display position 0..n
|
||||
* @param str text to display
|
||||
*
|
||||
* @return number of characters displayed
|
||||
*/
|
||||
int Display::set_text(uint8_t start_pos, const char *str) {
|
||||
ESP_LOGVV(TAG, "%s(): str=%s, prev=%s", __func__, str, this->disp_text_.text);
|
||||
if (strncmp(str, this->disp_text_.text, sizeof(this->disp_text_.text)) == 0) // text not changed?
|
||||
// if (strncmp(str, &this->disp_text_.text[this->disp_text_.visible_idx],
|
||||
// this->disp_text_.visible_len) == 0) // text not changed?
|
||||
return strlen(str); // yes -> exit function
|
||||
ESP_LOGV(TAG, "%s(): text changed: str=%s, prev=%s", __func__, str, this->disp_text_.text);
|
||||
|
||||
// store new text...
|
||||
this->disp_text_.set(start_pos, this->num_digits_ - 1, str);
|
||||
|
||||
// update visible text...
|
||||
this->disp_text_.visible_idx = 0;
|
||||
switch (this->scroll_mode) {
|
||||
case DISP_SCROLL_MODE_LEFT:
|
||||
this->disp_text_.visible_len = 1;
|
||||
break;
|
||||
case DISP_SCROLL_MODE_OFF:
|
||||
default:
|
||||
this->disp_text_.visible_len = std::min(strlen(this->disp_text_.text), this->num_digits_-start_pos);
|
||||
break;
|
||||
}
|
||||
|
||||
return update_out_buf_(this->disp_text_);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates the display buffer containing the MAX6921 OUT data according
|
||||
* to current visible text.
|
||||
*
|
||||
* @param text display text object
|
||||
|
||||
* @return number of visible characters
|
||||
*/
|
||||
int Display::update_out_buf_(DisplayText& disp_text) {
|
||||
uint visible_idx_offset = 0;
|
||||
|
||||
for (uint pos=0; pos<this->num_digits_; pos++) {
|
||||
char pos_char;
|
||||
uint32_t out_data;
|
||||
bool bGetNextChar, bClearPos = true;
|
||||
do {
|
||||
// determine character for current display position...
|
||||
if ((pos < disp_text.start_pos) || // empty position before text or
|
||||
((disp_text.start_pos == 0) && (pos >= disp_text.visible_len))) // empty positions after text?
|
||||
pos_char = ' ';
|
||||
else
|
||||
pos_char = disp_text.text[disp_text.visible_idx + visible_idx_offset++];
|
||||
|
||||
// special handling for point segment...
|
||||
bGetNextChar = false;
|
||||
if (isPointSegOnly(pos_char)) { // is point segment only?
|
||||
if (disp_text.visible_idx+visible_idx_offset-1 > 0) { // not the 1st text character?
|
||||
if (isPointSegOnly(disp_text.text[disp_text.visible_idx + visible_idx_offset - 2])) { // previous text character wasn't a point?
|
||||
if (pos == 0) { // 1st (most left) display position?
|
||||
bGetNextChar = true; // yes -> ignore point, get next character
|
||||
} else {
|
||||
--pos; // no -> add point to previous display position
|
||||
bClearPos = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (bGetNextChar);
|
||||
if (bClearPos)
|
||||
clear(pos);
|
||||
|
||||
// create segment data...
|
||||
if ((pos_char >= ' ') &&
|
||||
((pos_char - ' ') < ARRAY_ELEM_COUNT(ASCII_TO_SEG))) { // supported char?
|
||||
out_data = this->ascii_out_data_[pos_char - ' ']; // yes ->
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Encountered unsupported character '%c (0x%02x)'!", pos_char, pos_char);
|
||||
out_data = SEG_UNSUPPORTED_CHAR;
|
||||
}
|
||||
ESP_LOGVV(TAG, "%s(): segment data: 0x%06x", __func__, out_data);
|
||||
#if 0
|
||||
// At the moment an unsupport character is equal to blank (' ').
|
||||
// To distinguish an unsupported character from blank we would need to
|
||||
// increase font data type from uint8_t to uint16_t!
|
||||
if (out_data == SEG_UNSUPPORTED_CHAR) {
|
||||
ESP_LOGW(TAG, "Encountered character '%c (0x%02x)' with no display representation!", *vi_text, *vi_text);
|
||||
}
|
||||
#endif
|
||||
|
||||
// shift data to the smallest segment OUT position...
|
||||
out_data <<= (this->seg_out_smallest_);
|
||||
ESP_LOGVV(TAG, "%s(): segment data shifted to first segment bit (OUT%u): 0x%06x",
|
||||
__func__, this->seg_out_smallest_, out_data);
|
||||
|
||||
// add position data...
|
||||
out_data |= (1 << this->pos_to_out_map_[pos]);
|
||||
ESP_LOGVV(TAG, "%s(): OUT data with position: 0x%06x", __func__, out_data);
|
||||
|
||||
// write to appropriate position of display buffer...
|
||||
this->out_buf_[pos*3+0] |= (uint8_t)((out_data >> 16) & 0xFF);
|
||||
this->out_buf_[pos*3+1] |= (uint8_t)((out_data >> 8) & 0xFF);
|
||||
this->out_buf_[pos*3+2] |= (uint8_t)(out_data & 0xFF);
|
||||
ESP_LOGVV(TAG, "%s(): display buffer of position %u: 0x%02x%02x%02x",
|
||||
__func__, pos+1, this->out_buf_[pos*3+0], this->out_buf_[pos*3+1],
|
||||
this->out_buf_[pos*3+2]);
|
||||
|
||||
ESP_LOGV(TAG, "%s(): pos=%u, char='%c' (0x%02x), vi-idx=%u, vi-idx-off=%u, vi-len=%u",
|
||||
__func__, pos, pos_char, pos_char, disp_text.visible_idx, visible_idx_offset, disp_text.visible_len);
|
||||
}
|
||||
|
||||
disp_text.content_changed = true;
|
||||
|
||||
return this->num_digits_ - disp_text.start_pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Constructor.
|
||||
*/
|
||||
DisplayScrollMode::DisplayScrollMode() {
|
||||
this->scroll_mode = DISP_SCROLL_MODE_OFF;
|
||||
this->disp_text_ = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Inits the text object according to scroll mode.
|
||||
*/
|
||||
void DisplayScrollMode::init_scroll_mode_(void) {
|
||||
switch (this->scroll_mode) {
|
||||
case DISP_SCROLL_MODE_LEFT:
|
||||
this->disp_text_->start_pos = this->disp_text_->max_pos; // start at right side
|
||||
this->disp_text_->visible_idx = 0;
|
||||
this->disp_text_->visible_len = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the scroll mode.
|
||||
*
|
||||
* @param disp_text display text object
|
||||
* @param mode scroll mode
|
||||
* @param cycle_num number of scroll cycles (optional, default=endless)
|
||||
*/
|
||||
void DisplayScrollMode::set_scroll_mode(DisplayText *disp_text, display_scroll_mode_t mode, uint8_t cycle_num)
|
||||
{
|
||||
if (!disp_text) {
|
||||
ESP_LOGE(TAG, "Invalid display text object");
|
||||
return;
|
||||
}
|
||||
if (mode >= DISP_SCROLL_MODE_LAST_ENUM) {
|
||||
ESP_LOGE(TAG, "Invalid display scroll mode: %i", mode);
|
||||
return;
|
||||
}
|
||||
this->disp_text_ = disp_text;
|
||||
this->scroll_mode = mode;
|
||||
this->cycle_num = cycle_num;
|
||||
init_scroll_mode_();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates the mode "scroll left".
|
||||
*
|
||||
* @param text display text object
|
||||
*/
|
||||
void DisplayScrollMode::scroll_left_(DisplayText& disp_text) {
|
||||
ESP_LOGV(TAG, "%s(): ENTRY: start-idx=%u, text-idx=%u, text-len=%u", __func__,
|
||||
disp_text.start_pos, disp_text.visible_idx, disp_text.visible_len);
|
||||
|
||||
// update visible text...
|
||||
if (disp_text.start_pos > 0) { // no start at left side of display (scroll in from right side)?
|
||||
--disp_text.start_pos; // decrement display start position
|
||||
++disp_text.visible_len; // increment visible text length
|
||||
} else {
|
||||
++disp_text.visible_idx; // increment visible start index
|
||||
if ((disp_text.visible_idx + disp_text.visible_len) >= strlen(disp_text.text)) // visible part reached at end of text?
|
||||
--disp_text.visible_len; // decrement visible text length (scroll out to left side)
|
||||
}
|
||||
|
||||
// update scroll mode...
|
||||
if (disp_text.visible_len == 0) {
|
||||
init_scroll_mode_();
|
||||
if (this->cycle_num > 0) {
|
||||
if (--this->cycle_num == 0) {
|
||||
this->scroll_mode = DISP_SCROLL_MODE_OFF;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "%s(): EXIT: start-idx=%u, text-idx=%u, text-len=%u", __func__,
|
||||
disp_text.start_pos, disp_text.visible_idx, disp_text.visible_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Constructor.
|
||||
*/
|
||||
DisplayText::DisplayText() {
|
||||
this->text[0] = 0;
|
||||
this->visible_idx = 0;
|
||||
this->visible_len = 0;
|
||||
this->content_changed = false;
|
||||
this->start_pos = 0;
|
||||
this->max_pos = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Stores the given text.
|
||||
*
|
||||
* @param start_pos display start position (0..n)
|
||||
* @param max_pos display max. position
|
||||
* @param str text to store
|
||||
*
|
||||
* @return number of stored characters
|
||||
*/
|
||||
int DisplayText::set(uint start_pos, uint max_pos, const char *str) {
|
||||
// check start position...
|
||||
if (start_pos >= max_pos) {
|
||||
ESP_LOGW(TAG, "Invalid start position: %u");
|
||||
this->start_pos = 0;
|
||||
}
|
||||
else
|
||||
this->start_pos = start_pos;
|
||||
this->max_pos = max_pos;
|
||||
|
||||
strncpy(this->text, str, sizeof(this->text) - 1);
|
||||
this->text[sizeof(this->text)-1] = 0;
|
||||
return strlen(this->text);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configures the PWM for display brightness control.
|
||||
*
|
||||
* @param pwm_pin_no PWM pin number
|
||||
* @param channel PWM channel
|
||||
* @param resolution PWM resolution
|
||||
* @param freq PWM frequency
|
||||
*
|
||||
* @return frequency supported by hardware (0 = no support)
|
||||
*/
|
||||
uint32_t DisplayBrightness::config_brightness_pwm(uint8_t pwm_pin_no, uint8_t channel,
|
||||
uint8_t resolution, uint32_t freq) {
|
||||
uint32_t freq_supported;
|
||||
|
||||
if ((freq_supported = ledcSetup(channel, freq, resolution)) != 0) {
|
||||
ledcAttachPin(pwm_pin_no, channel);
|
||||
this->brightness_pwm_channel_ = channel;
|
||||
this->brightness_max_duty_ = pow(2,resolution); // max. duty value for given resolution
|
||||
ESP_LOGD(TAG, "Prepare brightness PWM: pin=%u, channel=%u, resolution=%ubit, freq=%uHz",
|
||||
pwm_pin_no, channel, resolution, freq_supported);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Failed to configure brightness PWM");
|
||||
}
|
||||
|
||||
return freq_supported;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the display brightness.
|
||||
*
|
||||
* @param percent brightness in percent (0.0-1.0)
|
||||
*/
|
||||
void DisplayBrightness::set_brightness(float percent) {
|
||||
if ((percent >= 0.0) && (percent <= 1.0)) {
|
||||
this->brightness_cfg_value_ = percent;
|
||||
this->brightness_cfg_changed_ = true;
|
||||
} else
|
||||
ESP_LOGW(TAG, "Invalid brightness value: %f", percent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Constructor.
|
||||
*/
|
||||
DisplayMode::DisplayMode() {
|
||||
this->mode = DISP_MODE_PRINT;
|
||||
}
|
||||
|
||||
void DisplayMode::set_mode(display_mode_t display_mode) {
|
||||
if (display_mode >= DISP_MODE_LAST_ENUM) {
|
||||
ESP_LOGE(TAG, "Invalid display mode: %i", display_mode);
|
||||
return;
|
||||
}
|
||||
this->mode = display_mode;
|
||||
ESP_LOGD(TAG, "Set display mode: %i", this->mode);
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace max6921
|
||||
} // namespace esphome
|
121
esphome/components/max6921/display.h
Normal file
121
esphome/components/max6921/display.h
Normal file
|
@ -0,0 +1,121 @@
|
|||
#pragma once
|
||||
|
||||
#include <esp32-hal-gpio.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "esphome/core/time.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace max6921 {
|
||||
|
||||
class MAX6921Component;
|
||||
|
||||
#define FONT_SIZE 95
|
||||
#define DISPLAY_TEXT_LEN FONT_SIZE // at least font size for demo mode "scroll font"
|
||||
|
||||
enum display_mode_t {
|
||||
DISP_MODE_PRINT, // input by it-functions
|
||||
DISP_MODE_OTHER, // input by actions
|
||||
DISP_MODE_LAST_ENUM
|
||||
};
|
||||
|
||||
enum display_scroll_mode_t {
|
||||
DISP_SCROLL_MODE_OFF, // show text at given position, cut if too long
|
||||
DISP_SCROLL_MODE_LEFT, // scroll left, start with 1st char at right position
|
||||
DISP_SCROLL_MODE_LAST_ENUM
|
||||
};
|
||||
|
||||
enum demo_mode_t {
|
||||
DEMO_MODE_OFF,
|
||||
DEMO_MODE_SCROLL_FONT,
|
||||
};
|
||||
|
||||
class DisplayBrightness
|
||||
{
|
||||
public:
|
||||
uint32_t config_brightness_pwm(uint8_t pwm_pin_no, uint8_t channel,
|
||||
uint8_t resolution, uint32_t freq_wanted);
|
||||
float get_brightness(void) { return this->brightness_cfg_value_; }
|
||||
void set_brightness(float percent);
|
||||
|
||||
protected:
|
||||
float brightness_cfg_value_; // brightness in percent (0.0-1.0)
|
||||
bool brightness_cfg_changed_;
|
||||
uint32_t brightness_max_duty_;
|
||||
uint8_t brightness_pwm_channel_;
|
||||
};
|
||||
|
||||
class DisplayText
|
||||
{
|
||||
public:
|
||||
bool content_changed;
|
||||
uint max_pos; // max. display position
|
||||
uint start_pos; // current display start position (0..n)
|
||||
char text[DISPLAY_TEXT_LEN + 1]; // current text to display (may be larger then display)
|
||||
uint visible_idx; // current index of start of visible part
|
||||
uint visible_len; // current length of visible text
|
||||
DisplayText();
|
||||
int set(uint start_pos, uint max_pos, const char *str);
|
||||
};
|
||||
|
||||
class DisplayMode
|
||||
{
|
||||
public:
|
||||
display_mode_t mode; // display mode
|
||||
DisplayMode();
|
||||
void set_mode(display_mode_t display_mode);
|
||||
|
||||
protected:
|
||||
|
||||
};
|
||||
|
||||
class DisplayScrollMode
|
||||
{
|
||||
public:
|
||||
display_scroll_mode_t scroll_mode; // scroll mode
|
||||
uint8_t cycle_num;
|
||||
DisplayScrollMode();
|
||||
void set_scroll_mode(DisplayText *disp_text, display_scroll_mode_t scroll_mode, uint8_t cycle_num=0);
|
||||
|
||||
protected:
|
||||
DisplayText *disp_text_;
|
||||
void init_scroll_mode_(void);
|
||||
void scroll_left_(DisplayText& disp_text);
|
||||
};
|
||||
|
||||
class Display : public DisplayBrightness,
|
||||
public DisplayMode,
|
||||
public DisplayScrollMode
|
||||
{
|
||||
public:
|
||||
Display(MAX6921Component *max6921) { max6921_ = max6921; }
|
||||
void clear(int pos=-1);
|
||||
void dump_config();
|
||||
bool isPointSegOnly(char c);
|
||||
void setup(std::vector<uint8_t>& seg_to_out_map, std::vector<uint8_t>& pos_to_out_map);
|
||||
void set_demo_mode(demo_mode_t mode, uint8_t cycle_num);
|
||||
void set_demo_mode(const std::string& mode, uint8_t cycle_num);
|
||||
int set_text(uint8_t start_pos, const char *str);
|
||||
void update(void);
|
||||
|
||||
protected:
|
||||
MAX6921Component *max6921_;
|
||||
std::vector<uint8_t> seg_to_out_map_; // mapping of display segments to MAX6921 OUT pins
|
||||
std::vector<uint8_t> pos_to_out_map_; // mapping of display positions to MAX6921 OUT pins
|
||||
uint num_digits_; // number of display positions
|
||||
uint8_t *ascii_out_data_;
|
||||
uint8_t *out_buf_; // current MAX9621 data (3 bytes for every display position)
|
||||
size_t out_buf_size_;
|
||||
uint seg_out_smallest_;
|
||||
uint32_t refresh_period_us_;
|
||||
DisplayText disp_text_;
|
||||
static void display_refresh_task_(void *pv);
|
||||
int update_out_buf_(DisplayText& disp_text);
|
||||
|
||||
private:
|
||||
void init_font__(void);
|
||||
};
|
||||
|
||||
|
||||
} // namespace max6921
|
||||
} // namespace esphome
|
|
@ -2,12 +2,17 @@ from esphome import pins, automation
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import display, spi
|
||||
from esphome.const import CONF_ID, CONF_BRIGHTNESS, CONF_LAMBDA
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_BRIGHTNESS,
|
||||
CONF_LAMBDA,
|
||||
CONF_MODE,
|
||||
# CONF_POSITION,
|
||||
)
|
||||
|
||||
|
||||
DEPENDENCIES = ["spi", "esp32"]
|
||||
CODEOWNERS = ["@endym"]
|
||||
CONF_DEMO_MODE = "demo_mode"
|
||||
CONF_LOAD_PIN = "load_pin"
|
||||
CONF_BLANK_PIN = "blank_pin"
|
||||
CONF_OUT_PIN_MAPPING = "out_pin_mapping"
|
||||
|
@ -34,6 +39,9 @@ CONF_POS_9_PIN = "pos_9_pin"
|
|||
CONF_POS_10_PIN = "pos_10_pin"
|
||||
CONF_POS_11_PIN = "pos_11_pin"
|
||||
CONF_POS_12_PIN = "pos_12_pin"
|
||||
# CONF_DEMO_MODE = "demo_mode"
|
||||
CONF_CYCLE_NUM = "cycle_num"
|
||||
CONF_TEXT = "text"
|
||||
|
||||
|
||||
max6921_ns = cg.esphome_ns.namespace("max6921")
|
||||
|
@ -42,6 +50,8 @@ MAX6921Component = max6921_ns.class_(
|
|||
)
|
||||
MAX6921ComponentRef = MAX6921Component.operator("ref")
|
||||
SetBrightnessAction = max6921_ns.class_("SetBrightnessAction", automation.Action)
|
||||
SetDemoModeAction = max6921_ns.class_("SetDemoModeAction", automation.Action)
|
||||
SetTextAction = max6921_ns.class_("SetTextAction", automation.Action)
|
||||
|
||||
|
||||
# optional "demo_mode" configuration
|
||||
|
@ -116,9 +126,9 @@ CONFIG_SCHEMA = (
|
|||
cv.Required(CONF_BLANK_PIN): pins.internal_gpio_output_pin_schema,
|
||||
cv.Required(CONF_OUT_PIN_MAPPING): OUT_PIN_MAPPING_SCHEMA,
|
||||
cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.templatable(cv.percentage),
|
||||
cv.Optional(CONF_DEMO_MODE, default=CONF_DEMO_MODE_OFF): cv.enum(
|
||||
DEMO_MODES
|
||||
),
|
||||
# cv.Optional(CONF_DEMO_MODE, default=CONF_DEMO_MODE_OFF): cv.enum(
|
||||
# DEMO_MODES
|
||||
# ),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("500ms"))
|
||||
|
@ -154,7 +164,7 @@ async def to_code(config):
|
|||
)
|
||||
)
|
||||
cg.add(var.set_brightness(config[CONF_BRIGHTNESS]))
|
||||
cg.add(var.set_demo_mode(config[CONF_DEMO_MODE]))
|
||||
# cg.add(var.set_demo_mode(config[CONF_DEMO_MODE]))
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
lambda_ = await cg.process_lambda(
|
||||
|
@ -192,3 +202,66 @@ async def max6921_set_brightness_to_code(config, action_id, template_arg, args):
|
|||
template_ = await cg.templatable(config[CONF_BRIGHTNESS], args, float)
|
||||
cg.add(var.set_brightness(template_))
|
||||
return var
|
||||
|
||||
|
||||
"""
|
||||
def validate_action_set_text(value):
|
||||
print(f"validate_action_set_text: {value}")
|
||||
return value
|
||||
|
||||
|
||||
ACTION_SET_TEXT_SCHEMA = cv.All(
|
||||
automation.maybe_simple_id(
|
||||
ACTION_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_TEXT): cv.templatable(cv.string),
|
||||
cv.Optional(CONF_POSITION): cv.templatable(cv.int_range(min=0, max=13))
|
||||
}
|
||||
)
|
||||
)
|
||||
),
|
||||
validate_action_set_text,
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"max6921.set_text", SetTextAction, ACTION_SET_TEXT_SCHEMA
|
||||
)
|
||||
async def max6921_set_text_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_TEXT], args, cg.std_string)
|
||||
cg.add(var.set_text(template_))
|
||||
return var
|
||||
"""
|
||||
|
||||
|
||||
ACTION_SET_DEMO_MODE_SCHEMA = cv.All(
|
||||
automation.maybe_simple_id(
|
||||
ACTION_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
# cv.Required(CONF_MODE): cv.templatable(cv.enum(DEMO_MODES, lower=True)),
|
||||
cv.Required(CONF_MODE): cv.templatable(cv.string),
|
||||
cv.Optional(CONF_CYCLE_NUM, default=0): cv.templatable(cv.uint8_t),
|
||||
}
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"max6921.set_demo_mode", SetDemoModeAction, ACTION_SET_DEMO_MODE_SCHEMA
|
||||
)
|
||||
async def max6921_set_demo_mode_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
# template_ = await cg.templatable(config[CONF_MODE], args, DemoMode)
|
||||
template_ = await cg.templatable(config[CONF_MODE], args, cg.std_string)
|
||||
cg.add(var.set_mode(template_))
|
||||
if CONF_CYCLE_NUM in config:
|
||||
template_ = await cg.templatable(config[CONF_CYCLE_NUM], args, cg.uint8)
|
||||
cg.add(var.set_cycle_num(template_))
|
||||
return var
|
||||
|
|
|
@ -1,408 +1,51 @@
|
|||
// Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/MAX6921-MAX6931.pdf
|
||||
|
||||
#include "max6921.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include <cinttypes>
|
||||
#include "display.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "max6921.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace max6921 {
|
||||
|
||||
#define ARRAY_ELEM_COUNT(array) (sizeof(array)/sizeof(array[0]))
|
||||
|
||||
static const char *const TAG = "max6921";
|
||||
|
||||
// max. display intensity (brightness)...
|
||||
static const uint MAX_DISPLAY_INTENSITY = 16;
|
||||
|
||||
// segments of 7-segment character
|
||||
static const uint8_t SEG_A = (1<<0);
|
||||
static const uint8_t SEG_B = (1<<1);
|
||||
static const uint8_t SEG_C = (1<<2);
|
||||
static const uint8_t SEG_D = (1<<3);
|
||||
static const uint8_t SEG_E = (1<<4);
|
||||
static const uint8_t SEG_F = (1<<5);
|
||||
static const uint8_t SEG_G = (1<<6);
|
||||
static const uint8_t SEG_DP = (1<<7);
|
||||
static const uint8_t SEG_UNSUPPORTED_CHAR = 0;
|
||||
|
||||
// ASCII table from 0x20..0x7E
|
||||
const uint8_t ASCII_TO_SEG[95] PROGMEM = {
|
||||
0, // ' ', (0x20)
|
||||
SEG_B|SEG_C|SEG_DP, // '!', (0x21)
|
||||
SEG_B|SEG_F, // '"', (0x22)
|
||||
SEG_UNSUPPORTED_CHAR, // '#', (0x23)
|
||||
SEG_UNSUPPORTED_CHAR, // '$', (0x24)
|
||||
SEG_UNSUPPORTED_CHAR, // '%', (0x25)
|
||||
SEG_UNSUPPORTED_CHAR, // '&', (0x26)
|
||||
SEG_F, // ''', (0x27)
|
||||
SEG_A|SEG_D|SEG_E|SEG_F, // '(', (0x28)
|
||||
SEG_A|SEG_B|SEG_C|SEG_D, // ')', (0x29)
|
||||
SEG_UNSUPPORTED_CHAR, // '*', (0x2A)
|
||||
SEG_UNSUPPORTED_CHAR, // '+', (0x2B)
|
||||
SEG_DP, // ',', (0x2C)
|
||||
SEG_G, // '-', (0x2D)
|
||||
SEG_DP, // '.', (0x2E)
|
||||
SEG_UNSUPPORTED_CHAR, // '/', (0x2F)
|
||||
SEG_A|SEG_B|SEG_C|SEG_D|SEG_E|SEG_F, // '0', (0x30)
|
||||
SEG_B|SEG_C, // '1', (0x31)
|
||||
SEG_A|SEG_B|SEG_D|SEG_E|SEG_G, // '2', (0x32)
|
||||
SEG_A|SEG_B|SEG_C|SEG_D|SEG_G, // '3', (0x33)
|
||||
SEG_B|SEG_C|SEG_F|SEG_G, // '4', (0x34)
|
||||
SEG_A|SEG_C|SEG_D|SEG_F|SEG_G, // '5', (0x35)
|
||||
SEG_A|SEG_C|SEG_D|SEG_E|SEG_F|SEG_G, // '6', (0x36)
|
||||
SEG_A|SEG_B|SEG_C, // '7', (0x37)
|
||||
SEG_A|SEG_B|SEG_C|SEG_D|SEG_E|SEG_F|SEG_G, // '8', (0x38)
|
||||
SEG_A|SEG_B|SEG_C|SEG_D|SEG_F|SEG_G, // '9', (0x39)
|
||||
SEG_UNSUPPORTED_CHAR, // ':', (0x3A)
|
||||
SEG_UNSUPPORTED_CHAR, // ';', (0x3B)
|
||||
SEG_UNSUPPORTED_CHAR, // '<', (0x3C)
|
||||
SEG_D|SEG_G, // '=', (0x3D)
|
||||
SEG_UNSUPPORTED_CHAR, // '>', (0x3E)
|
||||
SEG_A|SEG_B|SEG_E|SEG_G, // '?', (0x3F)
|
||||
SEG_A|SEG_B|SEG_D|SEG_E|SEG_F|SEG_G, // '@', (0x40)
|
||||
SEG_A|SEG_B|SEG_C|SEG_E|SEG_F|SEG_G, // 'A', (0x41)
|
||||
SEG_C|SEG_D|SEG_E|SEG_F|SEG_G, // 'B', (0x42)
|
||||
SEG_A|SEG_D|SEG_E|SEG_F, // 'C', (0x43)
|
||||
SEG_B|SEG_C|SEG_D|SEG_E|SEG_G, // 'D', (0x44)
|
||||
SEG_A|SEG_D|SEG_E|SEG_F|SEG_G, // 'E', (0x45)
|
||||
SEG_A|SEG_E|SEG_F|SEG_G, // 'F', (0x46)
|
||||
SEG_A|SEG_C|SEG_D|SEG_E|SEG_F, // 'G', (0x47)
|
||||
SEG_B|SEG_C|SEG_E|SEG_F|SEG_G, // 'H', (0x48)
|
||||
SEG_B|SEG_C, // 'I', (0x49)
|
||||
SEG_B|SEG_C|SEG_D|SEG_E, // 'J', (0x4A)
|
||||
SEG_UNSUPPORTED_CHAR, // 'K', (0x4B)
|
||||
SEG_D|SEG_E|SEG_F, // 'L', (0x4C)
|
||||
SEG_UNSUPPORTED_CHAR, // 'M', (0x4D)
|
||||
SEG_C|SEG_E|SEG_G, // 'N', (0x4E)
|
||||
SEG_A|SEG_B|SEG_C|SEG_D|SEG_E|SEG_F, // 'O', (0x4F)
|
||||
SEG_A|SEG_B|SEG_E|SEG_F|SEG_G, // 'P', (0x50)
|
||||
SEG_UNSUPPORTED_CHAR, // 'Q', (0x51)
|
||||
SEG_E|SEG_G, // 'R', (0x52)
|
||||
SEG_A|SEG_C|SEG_D|SEG_F|SEG_G, // 'S', (0x53)
|
||||
SEG_UNSUPPORTED_CHAR, // 'T', (0x54)
|
||||
SEG_B|SEG_C|SEG_D|SEG_E|SEG_F, // 'U', (0x55)
|
||||
SEG_UNSUPPORTED_CHAR, // 'V', (0x56)
|
||||
SEG_UNSUPPORTED_CHAR, // 'W', (0x57)
|
||||
SEG_UNSUPPORTED_CHAR, // 'X', (0x58)
|
||||
SEG_B|SEG_E|SEG_F|SEG_G, // 'Y', (0x59)
|
||||
SEG_UNSUPPORTED_CHAR, // 'Z', (0x5A)
|
||||
SEG_A|SEG_D|SEG_E|SEG_F, // '[', (0x5B)
|
||||
SEG_UNSUPPORTED_CHAR, // '\', (0x5C)
|
||||
SEG_A|SEG_B|SEG_C|SEG_D, // ']', (0x5D)
|
||||
SEG_UNSUPPORTED_CHAR, // '^', (0x5E)
|
||||
SEG_D, // '_', (0x5F)
|
||||
SEG_F, // '`', (0x60)
|
||||
SEG_A|SEG_B|SEG_C|SEG_E|SEG_F|SEG_G, // 'a', (0x61)
|
||||
SEG_C|SEG_D|SEG_E|SEG_F|SEG_G, // 'b', (0x62)
|
||||
SEG_D|SEG_E|SEG_G, // 'c', (0x63)
|
||||
SEG_B|SEG_C|SEG_D|SEG_E|SEG_G, // 'd', (0x64)
|
||||
SEG_A|SEG_D|SEG_E|SEG_F|SEG_G, // 'e', (0x65)
|
||||
SEG_A|SEG_E|SEG_F|SEG_G, // 'f', (0x66)
|
||||
SEG_A|SEG_C|SEG_D|SEG_E|SEG_F, // 'g', (0x67)
|
||||
SEG_C|SEG_E|SEG_F|SEG_G, // 'h', (0x68)
|
||||
SEG_C, // 'i', (0x69)
|
||||
SEG_B|SEG_C|SEG_D|SEG_E, // 'j', (0x6A)
|
||||
SEG_UNSUPPORTED_CHAR, // 'k', (0x6B)
|
||||
SEG_D|SEG_E|SEG_F, // 'l', (0x6C)
|
||||
SEG_UNSUPPORTED_CHAR, // 'm', (0x6D)
|
||||
SEG_C|SEG_E|SEG_G, // 'n', (0x6E)
|
||||
SEG_C|SEG_D|SEG_E|SEG_G, // 'o', (0x6F)
|
||||
SEG_A|SEG_B|SEG_E|SEG_F|SEG_G, // 'p', (0x70)
|
||||
SEG_UNSUPPORTED_CHAR, // 'q', (0x71)
|
||||
SEG_E|SEG_G, // 'r', (0x72)
|
||||
SEG_A|SEG_C|SEG_D|SEG_F|SEG_G, // 's', (0x73)
|
||||
SEG_UNSUPPORTED_CHAR, // 't', (0x74)
|
||||
SEG_C|SEG_D|SEG_E, // 'u', (0x75)
|
||||
SEG_UNSUPPORTED_CHAR, // 'v', (0x76)
|
||||
SEG_UNSUPPORTED_CHAR, // 'w', (0x77)
|
||||
SEG_UNSUPPORTED_CHAR, // 'x', (0x78)
|
||||
SEG_B|SEG_E|SEG_F|SEG_G, // 'y', (0x79)
|
||||
SEG_UNSUPPORTED_CHAR, // 'z', (0x7A)
|
||||
SEG_B|SEG_C|SEG_G, // '{', (0x7B)
|
||||
SEG_UNSUPPORTED_CHAR, // '|', (0x7C)
|
||||
SEG_E|SEG_F|SEG_G, // '}', (0x7D)
|
||||
SEG_UNSUPPORTED_CHAR, // '~', (0x7E)
|
||||
};
|
||||
|
||||
// MAX6921Component *global_max6921; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
|
||||
void MAX6921Component::init_display_(void) {
|
||||
uint32_t freq;
|
||||
const uint32_t PWM_FREQ_WANTED = 5000;
|
||||
const uint8_t PWM_RESOLUTION = 8;
|
||||
|
||||
// setup PWM for blank pin (intensity)...
|
||||
this->display_.brightness.pwm_channel = 0;
|
||||
freq = ledcSetup(this->display_.brightness.pwm_channel, PWM_FREQ_WANTED, PWM_RESOLUTION);
|
||||
if (freq != 0) {
|
||||
ledcAttachPin(this->display_.brightness.pwm_pin->get_pin(), this->display_.brightness.pwm_channel);
|
||||
this->display_.brightness.max_duty = pow(2,PWM_RESOLUTION); // max. duty value for given resolution
|
||||
this->display_.brightness.duty_quotient = this->display_.brightness.max_duty / MAX_DISPLAY_INTENSITY; // pre-calc fixed duty quotient (256 / 16)
|
||||
ESP_LOGD(TAG, "Prepare intensity PWM: pin=%u, channel=%u, freq=%uHz, resolution=%ubit, duty quotient=%u",
|
||||
this->display_.brightness.pwm_pin->get_pin(),
|
||||
this->display_.brightness.pwm_channel, freq, PWM_RESOLUTION,
|
||||
this->display_.brightness.duty_quotient);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to configure PWM -> set to max. intensity");
|
||||
pinMode(this->display_.brightness.pwm_pin->get_pin(), OUTPUT);
|
||||
this->disable_blank(); // enable display (max. intensity)
|
||||
}
|
||||
|
||||
// display output buffer...
|
||||
this->display_.out_buf_size_ = this->display_.num_digits * 3;
|
||||
this->display_.out_buf_ = new uint8_t[this->display_.out_buf_size_]; // NOLINT
|
||||
memset(this->display_.out_buf_, 0, this->display_.out_buf_size_);
|
||||
|
||||
// display text buffer...
|
||||
this->display_.current_text_buf_size = this->display_.num_digits * 2 + 1; // twice number of digits, because of possible points + string terminator
|
||||
this->display_.current_text = new char[this->display_.current_text_buf_size]; // NOLINT
|
||||
this->display_.current_text[0] = 0;
|
||||
this->display_.text_changed = false;
|
||||
|
||||
// display position...
|
||||
this->display_.current_pos = 1;
|
||||
|
||||
// find smallest segment DOUT number...
|
||||
this->display_.seg_out_smallest = 19;
|
||||
for (uint8_t i=0; i<this->display_.seg_to_out_map.size(); i++) {
|
||||
if (this->display_.seg_to_out_map[i] < this->display_.seg_out_smallest)
|
||||
this->display_.seg_out_smallest = this->display_.seg_to_out_map[i];
|
||||
}
|
||||
|
||||
// calculate refresh period for 60Hz
|
||||
this->display_.refresh_period_us = 1000000 / 60 / this->display_.num_digits;
|
||||
ESP_LOGD(TAG, "Set display refresh period: %" PRIu32 "us for %u digits @ 60Hz",
|
||||
this->display_.refresh_period_us, this->display_.num_digits);
|
||||
}
|
||||
|
||||
void MAX6921Component::init_font_(void) {
|
||||
uint8_t seg_data;
|
||||
|
||||
this->ascii_out_data_ = new uint8_t[ARRAY_ELEM_COUNT(ASCII_TO_SEG)]; // NOLINT
|
||||
|
||||
for (size_t ascii_idx=0; ascii_idx<ARRAY_ELEM_COUNT(ASCII_TO_SEG); ascii_idx++) {
|
||||
this->ascii_out_data_[ascii_idx] = 0;
|
||||
seg_data = progmem_read_byte(&ASCII_TO_SEG[ascii_idx]);
|
||||
if (seg_data & SEG_A)
|
||||
this->ascii_out_data_[ascii_idx] |= (1 << (this->display_.seg_to_out_map[0] % 8));
|
||||
if (seg_data & SEG_B)
|
||||
this->ascii_out_data_[ascii_idx] |= (1 << (this->display_.seg_to_out_map[1] % 8));
|
||||
if (seg_data & SEG_C)
|
||||
this->ascii_out_data_[ascii_idx] |= (1 << (this->display_.seg_to_out_map[2] % 8));
|
||||
if (seg_data & SEG_D)
|
||||
this->ascii_out_data_[ascii_idx] |= (1 << (this->display_.seg_to_out_map[3] % 8));
|
||||
if (seg_data & SEG_E)
|
||||
this->ascii_out_data_[ascii_idx] |= (1 << (this->display_.seg_to_out_map[4] % 8));
|
||||
if (seg_data & SEG_F)
|
||||
this->ascii_out_data_[ascii_idx] |= (1 << (this->display_.seg_to_out_map[5] % 8));
|
||||
if (seg_data & SEG_G)
|
||||
this->ascii_out_data_[ascii_idx] |= (1 << (this->display_.seg_to_out_map[6] % 8));
|
||||
if (seg_data & SEG_DP)
|
||||
this->ascii_out_data_[ascii_idx] |= (1 << (this->display_.seg_to_out_map[7] % 8));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clears the whole display buffer or only the given position.
|
||||
*
|
||||
* @param pos display position 0..n (optional, default=whole display)
|
||||
*/
|
||||
void MAX6921Component::clear_display(int pos) {
|
||||
if (pos < 0)
|
||||
memset(this->display_.out_buf_, 0, this->display_.out_buf_size_); // clear whole display buffer
|
||||
else if (pos < this->display_.num_digits)
|
||||
memset(&this->display_.out_buf_[pos*3], 0, 3); // clear display buffer at given position
|
||||
else
|
||||
ESP_LOGW(TAG, "Invalid display position %i (max=%u)", pos, this->display_.num_digits - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Shows the given text on the display at given position.
|
||||
*
|
||||
* @param start_pos display position 0..n
|
||||
* @param str text to display
|
||||
*
|
||||
* @return number of characters displayed
|
||||
*/
|
||||
int MAX6921Component::set_display(uint8_t start_pos, const char *str) {
|
||||
uint8_t pos = start_pos;
|
||||
|
||||
for (; *str != '\0'; str++) {
|
||||
uint32_t out_data;
|
||||
if (pos >= this->display_.num_digits) {
|
||||
ESP_LOGE(TAG, "MAX6921 string too long or invalid position for the display!");
|
||||
break;
|
||||
}
|
||||
|
||||
// create segment data...
|
||||
ESP_LOGVV(TAG, "%s(): pos: %u, char: '%c' (0x%02x)", __func__, pos+1, *str, *str);
|
||||
if ((*str >= ' ') &&
|
||||
((*str - ' ') < ARRAY_ELEM_COUNT(ASCII_TO_SEG))) { // supported char?
|
||||
out_data = this->ascii_out_data_[*str - ' ']; // yes ->
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Encountered unsupported character '%c (0x%02x)'!", *str, *str);
|
||||
out_data = SEG_UNSUPPORTED_CHAR;
|
||||
}
|
||||
ESP_LOGVV(TAG, "%s(): segment data: 0x%06x", __func__, out_data);
|
||||
#if 0
|
||||
// At the moment an unsupport character is equal to blank (' ').
|
||||
// To distinguish an unsupported character from blank we would need to
|
||||
// increase font data type from uint8_t to uint16_t!
|
||||
if (out_data == SEG_UNSUPPORTED_CHAR) {
|
||||
ESP_LOGW(TAG, "Encountered character '%c (0x%02x)' with no display representation!", *str, *str);
|
||||
}
|
||||
#endif
|
||||
if (((*str == ',') || (*str == '.')) && (pos > start_pos)) // is point/comma?
|
||||
pos--; // yes -> modify display buffer of previous position
|
||||
else
|
||||
this->clear_display(pos); // no -> clear display buffer of current position (for later OR operation)
|
||||
|
||||
// shift data to the smallest segment OUT position...
|
||||
out_data <<= (this->display_.seg_out_smallest);
|
||||
ESP_LOGVV(TAG, "%s(): segment data shifted to first segment bit (OUT%u): 0x%06x",
|
||||
__func__, this->display_.seg_out_smallest, out_data);
|
||||
|
||||
// add position data...
|
||||
out_data |= (1 << this->display_.pos_to_out_map[pos]);
|
||||
ESP_LOGVV(TAG, "%s(): OUT data with position: 0x%06x", __func__, out_data);
|
||||
|
||||
// write to appropriate position of display buffer...
|
||||
this->display_.out_buf_[pos*3+0] |= (uint8_t)((out_data >> 16) & 0xFF);
|
||||
this->display_.out_buf_[pos*3+1] |= (uint8_t)((out_data >> 8) & 0xFF);
|
||||
this->display_.out_buf_[pos*3+2] |= (uint8_t)(out_data & 0xFF);
|
||||
ESP_LOGVV(TAG, "%s(): display buffer of position %u: 0x%02x%02x%02x",
|
||||
__func__, pos+1, this->display_.out_buf_[pos*3+0],
|
||||
this->display_.out_buf_[pos*3+1], this->display_.out_buf_[pos*3+2]);
|
||||
|
||||
pos++;
|
||||
}
|
||||
this->display_.text_changed = true;
|
||||
|
||||
return pos - start_pos;
|
||||
}
|
||||
|
||||
void MAX6921Component::update_display_(void) {
|
||||
// handle display intensity...
|
||||
if (this->display_.brightness.config_changed) {
|
||||
// calc duty for low-active BLANK pin...
|
||||
uint32_t inverted_duty = this->display_.brightness.max_duty - \
|
||||
this->display_.brightness.max_duty * \
|
||||
this->display_.brightness.config_value;
|
||||
ESP_LOGD(TAG, "Change display brightness to %.1f (off-time duty=%u/%u)",
|
||||
this->display_.brightness.config_value, inverted_duty,
|
||||
this->display_.brightness.max_duty);
|
||||
ledcWrite(this->display_.brightness.pwm_channel, inverted_duty);
|
||||
this->display_.brightness.config_changed = false;
|
||||
}
|
||||
|
||||
// handle demo modes...
|
||||
switch (this->get_demo_mode()) {
|
||||
case DEMO_MODE_SCROLL_FONT:
|
||||
this->update_demo_mode_scroll_font_();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MAX6921Component::update_demo_mode_scroll_font_(void) {
|
||||
static char *text = new char[this->display_.num_digits + 2 + 1]; // comma and point do not use an extra display position
|
||||
static uint8_t start_pos = this->display_.num_digits - 1; // start at right side
|
||||
static uint start_font_idx = 0;
|
||||
uint font_idx, text_idx = 0, char_with_point_num = 0;
|
||||
|
||||
// build text...
|
||||
font_idx = start_font_idx;
|
||||
ESP_LOGV(TAG, "%s(): ENTRY: start-pos=%u, start-font-idx=%u",
|
||||
__func__, start_pos, start_font_idx);
|
||||
do {
|
||||
if (text_idx < start_pos) { // before start position?
|
||||
text[text_idx++] = ' '; // add blank to string
|
||||
} else {
|
||||
if (this->ascii_out_data_[font_idx] > 0) { // displayable character?
|
||||
text[text_idx] = ' ' + font_idx; // add character to string
|
||||
if ((text[text_idx] == '.') || (text[text_idx] == ',')) { // point-only character?
|
||||
if (text_idx > 0) { // yes -> point after a character?
|
||||
++text_idx;
|
||||
++char_with_point_num;
|
||||
}
|
||||
} else
|
||||
++text_idx;
|
||||
}
|
||||
font_idx = (font_idx + 1) % ARRAY_ELEM_COUNT(ASCII_TO_SEG); // next font character
|
||||
}
|
||||
ESP_LOGV(TAG, "%s(): LOOP: pos=%u, font-idx=%u, char='%c'",
|
||||
__func__, (text_idx>0)?text_idx-1:0, font_idx, (text_idx>0)?text[text_idx-1]:' ');
|
||||
} while ((text_idx - char_with_point_num) < this->display_.num_digits);
|
||||
text[text_idx] = 0;
|
||||
// determine next start font index...
|
||||
if (start_pos == 0) {
|
||||
start_font_idx = text[1] - ' ';
|
||||
}
|
||||
// update display start position...
|
||||
if (start_pos > 0)
|
||||
--start_pos;
|
||||
ESP_LOGV(TAG, "%s(): EXIT: start-pos=%u, start-font-idx=%u, text={%s}",
|
||||
__func__, start_pos, start_font_idx, text);
|
||||
|
||||
this->set_display(0, text);
|
||||
}
|
||||
|
||||
float MAX6921Component::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
void MAX6921Component::setup() {
|
||||
const uint32_t PWM_FREQ_WANTED = 5000;
|
||||
const uint8_t PWM_RESOLUTION = 8;
|
||||
|
||||
ESP_LOGCONFIG(TAG, "Setting up MAX6921...");
|
||||
// global_max6921 = this;
|
||||
|
||||
this->spi_setup();
|
||||
this->load_pin_->setup();
|
||||
this->load_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->disable_load(); // disable output latch
|
||||
this->init_display_();
|
||||
this->init_font_();
|
||||
this->disable_load_(); // disable output latch
|
||||
|
||||
this->display_ = new Display(this);
|
||||
this->display_->setup(this->seg_to_out_map__, this->pos_to_out_map__);
|
||||
|
||||
/* Setup display refresh.
|
||||
* Using a timer is not an option, because the WiFi component uses timer as
|
||||
* well, which leads to unstable refresh cycles (flickering). Therefore a
|
||||
* thread on 2nd MCU core is used.
|
||||
*/
|
||||
xTaskCreatePinnedToCore(&MAX6921Component::display_refresh_task,
|
||||
"display_refresh_task", // name
|
||||
2048, // stack size
|
||||
this, // pass component pointer as task parameter pv
|
||||
1, // priority (one above IDLE task)
|
||||
nullptr, // handle
|
||||
1 // core
|
||||
);
|
||||
// setup display brightness (PWM for BLANK pin)...
|
||||
if (this->display_->config_brightness_pwm(this->blank_pin_->get_pin(), 0,
|
||||
PWM_RESOLUTION, PWM_FREQ_WANTED) == 0) {
|
||||
ESP_LOGE(TAG, "Failed to configure PWM -> set to max. brightness");
|
||||
pinMode(this->blank_pin_->get_pin(), OUTPUT);
|
||||
this->disable_blank_(); // enable display (max. brightness)
|
||||
}
|
||||
|
||||
this->setup_finished = true;
|
||||
}
|
||||
|
||||
void MAX6921Component::dump_config() {
|
||||
char seg_name[3];
|
||||
ESP_LOGCONFIG(TAG, "MAX6921:");
|
||||
LOG_PIN(" LOAD Pin: ", this->load_pin_);
|
||||
ESP_LOGCONFIG(TAG, " BLANK Pin: GPIO%u", this->display_.brightness.pwm_pin->get_pin());
|
||||
// display segment to DOUTx mapping...
|
||||
for (uint i=0; i<this->display_.seg_to_out_map.size(); i++) {
|
||||
if (i < 7) {
|
||||
seg_name[0] = 'a' + i;
|
||||
seg_name[1] = 0;
|
||||
} else
|
||||
strncpy(seg_name, "dp", sizeof(seg_name));
|
||||
ESP_LOGCONFIG(TAG, " Display segment %2s: OUT%u", seg_name, this->display_.seg_to_out_map[i]);
|
||||
}
|
||||
// display position to DOUTx mapping...
|
||||
for (uint i=0; i<this->display_.seg_to_out_map.size(); i++) {
|
||||
ESP_LOGCONFIG(TAG, " Display position %2u: OUT%u", i, this->display_.pos_to_out_map[i]);
|
||||
}
|
||||
// ESP_LOGCONFIG(TAG, " Number of digits: %u", this->display_.num_digits);
|
||||
ESP_LOGCONFIG(TAG, " Brightness: %.1f", this->display_.brightness.config_value);
|
||||
ESP_LOGCONFIG(TAG, " Demo mode: %u", this->display_.demo_mode);
|
||||
ESP_LOGCONFIG(TAG, " BLANK Pin: GPIO%u", this->blank_pin_->get_pin());
|
||||
this->display_->dump_config();
|
||||
}
|
||||
|
||||
void MAX6921Component::set_brightness(float brightness) {
|
||||
|
@ -410,13 +53,16 @@ void MAX6921Component::set_brightness(float brightness) {
|
|||
ESP_LOGD(TAG, "Set brightness: setup not finished -> discard brightness value");
|
||||
return;
|
||||
}
|
||||
if ((brightness == 0.0) || (brightness != this->display_.brightness.config_value)) {
|
||||
this->display_.brightness.config_value = brightness;
|
||||
ESP_LOGD(TAG, "Set brightness: %.1f", this->display_.brightness.config_value);
|
||||
this->display_.brightness.config_changed = true;
|
||||
if ((brightness == 0.0) || (brightness != this->display_->get_brightness())) {
|
||||
this->display_->set_brightness(brightness);
|
||||
ESP_LOGD(TAG, "Set brightness: %.1f", this->display_->get_brightness());
|
||||
}
|
||||
}
|
||||
|
||||
void MAX6921Component::set_text(const std::string& text) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clocks data into MAX6921 via SPI (MSB first).
|
||||
* Data must contain 3 bytes with following format:
|
||||
|
@ -429,72 +75,32 @@ void HOT MAX6921Component::write_data(uint8_t *ptr, size_t length) {
|
|||
static bool first_call_logged = false;
|
||||
|
||||
assert(length == 3);
|
||||
this->disable_load(); // set LOAD to low
|
||||
this->disable_load_(); // set LOAD to low
|
||||
memcpy(data, ptr, sizeof(data)); // make copy of data, because transfer buffer will be overwritten with SPI answer
|
||||
if (!first_call_logged)
|
||||
ESP_LOGVV(TAG, "SPI(%u): 0x%02x%02x%02x", length, data[0], data[1], data[2]);
|
||||
first_call_logged = true;
|
||||
this->transfer_array(data, sizeof(data));
|
||||
this->enable_load(); // set LOAD to high to update output latch
|
||||
this->enable_load_(); // set LOAD to high to update output latch
|
||||
}
|
||||
|
||||
void MAX6921Component::update() {
|
||||
this->update_display_();
|
||||
this->display_->update();
|
||||
|
||||
if (this->writer_.has_value())
|
||||
(*this->writer_)(*this);
|
||||
}
|
||||
|
||||
void HOT MAX6921Component::display_refresh_task(void *pv) {
|
||||
MAX6921Component *max6921_comp = (MAX6921Component*)pv;
|
||||
static uint count = max6921_comp->display_.num_digits;
|
||||
|
||||
if (max6921_comp->display_.refresh_period_us == 0) {
|
||||
ESP_LOGE(TAG, "Invalid display refresh period -> using default 2ms");
|
||||
max6921_comp->display_.refresh_period_us = 2000;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
// one-time debug output after any text change for all digits...
|
||||
if (max6921_comp->display_.text_changed) {
|
||||
count = 0;
|
||||
max6921_comp->display_.text_changed = false;
|
||||
}
|
||||
if (count < max6921_comp->display_.num_digits) {
|
||||
count++;
|
||||
ESP_LOGVV(TAG, "%s(): SPI transfer for position %u: 0x%02x%02x%02x", __func__,
|
||||
max6921_comp->display_.current_pos,
|
||||
max6921_comp->display_.out_buf_[(max6921_comp->display_.current_pos-1)*3],
|
||||
max6921_comp->display_.out_buf_[(max6921_comp->display_.current_pos-1)*3+1],
|
||||
max6921_comp->display_.out_buf_[(max6921_comp->display_.current_pos-1)*3+2]);
|
||||
}
|
||||
|
||||
// write MAX9621 data of current position...
|
||||
max6921_comp->write_data(&max6921_comp->display_.out_buf_[(max6921_comp->display_.current_pos-1)*3], 3);
|
||||
|
||||
// next display position...
|
||||
if (++max6921_comp->display_.current_pos > max6921_comp->display_.num_digits)
|
||||
max6921_comp->display_.current_pos = 1;
|
||||
|
||||
delayMicroseconds(max6921_comp->display_.refresh_period_us);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Evaluates lambda function
|
||||
* start_pos: 0..n = left..right display position
|
||||
* str : display text
|
||||
* vi_text : display text
|
||||
*/
|
||||
uint8_t MAX6921Component::print(uint8_t start_pos, const char *str) {
|
||||
if ((this->get_demo_mode() != DEMO_MODE_OFF) || // demo mode enabled or
|
||||
(strcmp(str, this->display_.current_text) == 0)) // display text not changed?
|
||||
if (this->display_->mode != DISP_MODE_PRINT) // not in "it.print" mode?
|
||||
return strlen(str); // yes -> abort
|
||||
ESP_LOGV(TAG, "%s(): text changed: str=%s, prev=%s", __func__, str, this->display_.current_text);
|
||||
strncpy(this->display_.current_text, str, this->display_.current_text_buf_size-1);
|
||||
this->display_.current_text[this->display_.current_text_buf_size-1] = 0;
|
||||
|
||||
return this->set_display(start_pos, str);
|
||||
return this->display_->set_text(start_pos, str);
|
||||
}
|
||||
|
||||
uint8_t MAX6921Component::print(const char *str) { return this->print(0, str); }
|
||||
|
|
|
@ -1,68 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include <esp32-hal-gpio.h>
|
||||
#include <string>
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/time.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
#include <esp32-hal-gpio.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace max6921 {
|
||||
|
||||
#define ARRAY_ELEM_COUNT(array) (sizeof(array)/sizeof(array[0]))
|
||||
|
||||
|
||||
class MAX6921Component;
|
||||
class Display;
|
||||
|
||||
using max6921_writer_t = std::function<void(MAX6921Component &)>;
|
||||
|
||||
|
||||
enum demo_mode_t {
|
||||
DEMO_MODE_OFF,
|
||||
DEMO_MODE_SCROLL_FONT,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
InternalGPIOPin *pwm_pin;
|
||||
float config_value; // brightness in percent (0.0-1.0)
|
||||
bool config_changed;
|
||||
uint32_t max_duty;
|
||||
uint32_t duty_quotient;
|
||||
uint8_t pwm_channel;
|
||||
}display_brightness_t;
|
||||
|
||||
typedef struct {
|
||||
std::vector<uint8_t> seg_to_out_map;
|
||||
std::vector<uint8_t> pos_to_out_map;
|
||||
uint num_digits;
|
||||
display_brightness_t brightness;
|
||||
uint8_t *out_buf_;
|
||||
size_t out_buf_size_;
|
||||
uint seg_out_smallest;
|
||||
char *current_text;
|
||||
uint current_text_buf_size;
|
||||
uint current_pos;
|
||||
bool text_changed;
|
||||
uint32_t refresh_period_us;
|
||||
demo_mode_t demo_mode;
|
||||
}display_t;
|
||||
|
||||
|
||||
class MAX6921Component : public PollingComponent,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_4MHZ> {
|
||||
public:
|
||||
Display *display_;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
uint8_t print(uint8_t pos, const char *str);
|
||||
uint8_t print(const char *str);
|
||||
void set_blank_pin(InternalGPIOPin *pin) { this->display_.brightness.pwm_pin = pin; }
|
||||
void set_blank_pin(InternalGPIOPin *pin) { blank_pin_ = pin; }
|
||||
void set_brightness(float brightness);
|
||||
void set_demo_mode(demo_mode_t mode) { this->display_.demo_mode = mode; }
|
||||
void set_load_pin(GPIOPin *load) { this->load_pin_ = load; }
|
||||
void set_num_digits(uint8_t num_digits) { this->display_.num_digits = num_digits; }
|
||||
void set_seg_to_out_pin_map(const std::vector<uint8_t> &pin_map) { this->display_.seg_to_out_map = pin_map; }
|
||||
void set_pos_to_out_pin_map(const std::vector<uint8_t> &pin_map) {
|
||||
this->display_.pos_to_out_map = pin_map;
|
||||
this->display_.num_digits = pin_map.size();
|
||||
}
|
||||
void set_seg_to_out_pin_map(const std::vector<uint8_t> &pin_map) { this->seg_to_out_map__ = pin_map; }
|
||||
void set_pos_to_out_pin_map(const std::vector<uint8_t> &pin_map) { this->pos_to_out_map__ = pin_map; }
|
||||
void set_text(const std::string& text);
|
||||
void set_writer(max6921_writer_t &&writer);
|
||||
void setup() override;
|
||||
uint8_t strftime(uint8_t pos, const char *format, ESPTime time) __attribute__((format(strftime, 3, 0)));
|
||||
|
@ -72,31 +42,18 @@ class MAX6921Component : public PollingComponent,
|
|||
|
||||
protected:
|
||||
GPIOPin *load_pin_{};
|
||||
InternalGPIOPin *blank_pin_;
|
||||
bool setup_finished{false};
|
||||
display_t display_;
|
||||
uint8_t *ascii_out_data_;
|
||||
void clear_display(int pos=-1);
|
||||
void disable_blank() { digitalWrite(this->display_.brightness.pwm_pin->get_pin(), LOW); } // display on
|
||||
void IRAM_ATTR HOT disable_load() { this->load_pin_->digital_write(false); }
|
||||
static void display_refresh_task(void *pv);
|
||||
void enable_blank() { digitalWrite(this->display_.brightness.pwm_pin->get_pin(), HIGH); } // display off
|
||||
void IRAM_ATTR HOT enable_load() { this->load_pin_->digital_write(true); }
|
||||
demo_mode_t get_demo_mode(void) { return this->display_.demo_mode; }
|
||||
int set_display(uint8_t pos, const char *str);
|
||||
void disable_blank_() { digitalWrite(this->blank_pin_->get_pin(), LOW); } // display on
|
||||
void IRAM_ATTR HOT disable_load_() { this->load_pin_->digital_write(false); }
|
||||
void enable_blank_() { digitalWrite(this->blank_pin_->get_pin(), HIGH); } // display off
|
||||
void IRAM_ATTR HOT enable_load_() { this->load_pin_->digital_write(true); }
|
||||
void update_demo_mode_scroll_font_(void);
|
||||
optional<max6921_writer_t> writer_{};
|
||||
|
||||
private:
|
||||
void init_display_(void);
|
||||
void init_font_(void);
|
||||
void update_display_();
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetBrightnessAction : public Action<Ts...>, public Parented<MAX6921Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, brightness)
|
||||
|
||||
void play(Ts... x) override { this->parent_->set_brightness(this->brightness_.value(x...)); }
|
||||
std::vector<uint8_t> seg_to_out_map__; // mapping of display segments to MAX6921 OUT pins
|
||||
std::vector<uint8_t> pos_to_out_map__; // mapping of display positions to MAX6921 OUT pins
|
||||
};
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue