Finished 1st approach of max6921 component

This commit is contained in:
endym 2024-01-20 17:00:22 +01:00
parent 9018a355db
commit 61ce15db44
4 changed files with 58 additions and 309 deletions

View file

@ -0,0 +1,29 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import spi
from esphome.const import CONF_ID
from esphome import pins
DEPENDENCIES = ["spi"]
# MULTI_CONF = True
CODEOWNERS = ["@endym"]
CONF_LOAD_PIN = "load_pin"
max6921_ns = cg.esphome_ns.namespace("max6921")
MAX6921 = max6921_ns.class_("MAX6921", cg.Component, spi.SPIDevice)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(MAX6921),
cv.Required(CONF_LOAD_PIN): pins.gpio_input_pin_schema,
}
).extend(spi.spi_device_schema(cs_pin_required=False))
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await spi.register_spi_device(var, config)
pin = await cg.gpio_pin_expression(config[CONF_LOAD_PIN])
cg.add(var.set_load_pin(pin))

View file

@ -1,43 +0,0 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import display, spi
from esphome.const import CONF_ID, CONF_INTENSITY, CONF_LAMBDA, CONF_NUM_CHIPS
DEPENDENCIES = ["spi"]
max6921_ns = cg.esphome_ns.namespace("max6921")
MAX6921Component = max6921_ns.class_(
"MAX6921Component", cg.PollingComponent, spi.SPIDevice
)
MAX6921ComponentRef = MAX6921Component.operator("ref")
CONF_REVERSE_ENABLE = "reverse_enable"
CONFIG_SCHEMA = (
display.BASIC_DISPLAY_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(MAX6921Component),
cv.Optional(CONF_NUM_CHIPS, default=1): cv.int_range(min=1, max=255),
cv.Optional(CONF_INTENSITY, default=15): cv.int_range(min=0, max=15),
cv.Optional(CONF_REVERSE_ENABLE, default=False): cv.boolean,
}
)
.extend(cv.polling_component_schema("1s"))
.extend(spi.spi_device_schema())
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await spi.register_spi_device(var, config)
await display.register_display(var, config)
cg.add(var.set_num_chips(config[CONF_NUM_CHIPS]))
cg.add(var.set_intensity(config[CONF_INTENSITY]))
cg.add(var.set_reverse(config[CONF_REVERSE_ENABLE]))
if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(MAX6921ComponentRef, "it")], return_type=cg.void
)
cg.add(var.set_writer(lambda_))

View file

@ -1,3 +1,5 @@
// 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"
@ -8,236 +10,33 @@ namespace max6921 {
static const char *const TAG = "max6921";
static const uint8_t MAX6921_REGISTER_NOOP = 0x00;
static const uint8_t MAX6921_REGISTER_DECODE_MODE = 0x09;
static const uint8_t MAX6921_REGISTER_INTENSITY = 0x0A;
static const uint8_t MAX6921_REGISTER_SCAN_LIMIT = 0x0B;
static const uint8_t MAX6921_REGISTER_SHUTDOWN = 0x0C;
static const uint8_t MAX6921_REGISTER_TEST = 0x0F;
static const uint8_t MAX6921_UNKNOWN_CHAR = 0b11111111;
float MAX6921::get_setup_priority() const { return setup_priority::HARDWARE; }
const uint8_t MAX6921_ASCII_TO_RAW[95] PROGMEM = {
0b00000000, // ' ', ord 0x20
0b10110000, // '!', ord 0x21
0b00100010, // '"', ord 0x22
MAX6921_UNKNOWN_CHAR, // '#', ord 0x23
MAX6921_UNKNOWN_CHAR, // '$', ord 0x24
0b01001001, // '%', ord 0x25
MAX6921_UNKNOWN_CHAR, // '&', ord 0x26
0b00000010, // ''', ord 0x27
0b01001110, // '(', ord 0x28
0b01111000, // ')', ord 0x29
0b01000000, // '*', ord 0x2A
MAX6921_UNKNOWN_CHAR, // '+', ord 0x2B
0b00010000, // ',', ord 0x2C
0b00000001, // '-', ord 0x2D
0b10000000, // '.', ord 0x2E
MAX6921_UNKNOWN_CHAR, // '/', ord 0x2F
0b01111110, // '0', ord 0x30
0b00110000, // '1', ord 0x31
0b01101101, // '2', ord 0x32
0b01111001, // '3', ord 0x33
0b00110011, // '4', ord 0x34
0b01011011, // '5', ord 0x35
0b01011111, // '6', ord 0x36
0b01110000, // '7', ord 0x37
0b01111111, // '8', ord 0x38
0b01111011, // '9', ord 0x39
0b01001000, // ':', ord 0x3A
0b01011000, // ';', ord 0x3B
MAX6921_UNKNOWN_CHAR, // '<', ord 0x3C
0b00001001, // '=', ord 0x3D
MAX6921_UNKNOWN_CHAR, // '>', ord 0x3E
0b01100101, // '?', ord 0x3F
0b01101111, // '@', ord 0x40
0b01110111, // 'A', ord 0x41
0b00011111, // 'B', ord 0x42
0b01001110, // 'C', ord 0x43
0b00111101, // 'D', ord 0x44
0b01001111, // 'E', ord 0x45
0b01000111, // 'F', ord 0x46
0b01011110, // 'G', ord 0x47
0b00110111, // 'H', ord 0x48
0b00110000, // 'I', ord 0x49
0b00111100, // 'J', ord 0x4A
MAX6921_UNKNOWN_CHAR, // 'K', ord 0x4B
0b00001110, // 'L', ord 0x4C
MAX6921_UNKNOWN_CHAR, // 'M', ord 0x4D
0b00010101, // 'N', ord 0x4E
0b01111110, // 'O', ord 0x4F
0b01100111, // 'P', ord 0x50
0b11111110, // 'Q', ord 0x51
0b00000101, // 'R', ord 0x52
0b01011011, // 'S', ord 0x53
0b00000111, // 'T', ord 0x54
0b00111110, // 'U', ord 0x55
0b00111110, // 'V', ord 0x56
0b00111111, // 'W', ord 0x57
MAX6921_UNKNOWN_CHAR, // 'X', ord 0x58
0b00100111, // 'Y', ord 0x59
0b01101101, // 'Z', ord 0x5A
0b01001110, // '[', ord 0x5B
MAX6921_UNKNOWN_CHAR, // '\', ord 0x5C
0b01111000, // ']', ord 0x5D
MAX6921_UNKNOWN_CHAR, // '^', ord 0x5E
0b00001000, // '_', ord 0x5F
0b00100000, // '`', ord 0x60
0b01110111, // 'a', ord 0x61
0b00011111, // 'b', ord 0x62
0b00001101, // 'c', ord 0x63
0b00111101, // 'd', ord 0x64
0b01001111, // 'e', ord 0x65
0b01000111, // 'f', ord 0x66
0b01011110, // 'g', ord 0x67
0b00010111, // 'h', ord 0x68
0b00010000, // 'i', ord 0x69
0b00111100, // 'j', ord 0x6A
MAX6921_UNKNOWN_CHAR, // 'k', ord 0x6B
0b00001110, // 'l', ord 0x6C
MAX6921_UNKNOWN_CHAR, // 'm', ord 0x6D
0b00010101, // 'n', ord 0x6E
0b00011101, // 'o', ord 0x6F
0b01100111, // 'p', ord 0x70
MAX6921_UNKNOWN_CHAR, // 'q', ord 0x71
0b00000101, // 'r', ord 0x72
0b01011011, // 's', ord 0x73
0b00000111, // 't', ord 0x74
0b00011100, // 'u', ord 0x75
0b00011100, // 'v', ord 0x76
MAX6921_UNKNOWN_CHAR, // 'w', ord 0x77
MAX6921_UNKNOWN_CHAR, // 'x', ord 0x78
0b00100111, // 'y', ord 0x79
MAX6921_UNKNOWN_CHAR, // 'z', ord 0x7A
0b00110001, // '{', ord 0x7B
0b00000110, // '|', ord 0x7C
0b00000111, // '}', ord 0x7D
0b01100011, // '~', ord 0x7E (degree symbol)
};
float MAX6921Component::get_setup_priority() const { return setup_priority::PROCESSOR; }
void MAX6921Component::setup() {
void MAX6921::setup() {
ESP_LOGCONFIG(TAG, "Setting up MAX6921...");
this->spi_setup();
this->buffer_ = new uint8_t[this->num_chips_ * 8]; // NOLINT
for (uint8_t i = 0; i < this->num_chips_ * 8; i++)
this->buffer_[i] = 0;
// let's assume the user has all 8 digits connected, only important in daisy chained setups anyway
this->send_to_all_(MAX6921_REGISTER_SCAN_LIMIT, 7);
// let's use our own ASCII -> led pattern encoding
this->send_to_all_(MAX6921_REGISTER_DECODE_MODE, 0);
this->send_to_all_(MAX6921_REGISTER_INTENSITY, this->intensity_);
this->display();
// power up
this->send_to_all_(MAX6921_REGISTER_TEST, 0);
this->send_to_all_(MAX6921_REGISTER_SHUTDOWN, 1);
this->load_pin_->setup();
this->disable_load();
}
void MAX6921Component::dump_config() {
void MAX6921::dump_config() {
ESP_LOGCONFIG(TAG, "MAX6921:");
ESP_LOGCONFIG(TAG, " Number of Chips: %u", this->num_chips_);
ESP_LOGCONFIG(TAG, " Intensity: %u", this->intensity_);
LOG_PIN(" CS Pin: ", this->cs_);
LOG_UPDATE_INTERVAL(this);
LOG_PIN(" LOAD Pin: ", this->cs_);
}
void MAX6921Component::display() {
for (uint8_t i = 0; i < 8; i++) {
this->enable();
for (uint8_t j = 0; j < this->num_chips_; j++) {
if (reverse_) {
this->send_byte_(8 - i, buffer_[(num_chips_ - j - 1) * 8 + i]);
} else {
this->send_byte_(8 - i, buffer_[j * 8 + i]);
}
}
this->disable();
}
/*
* Clocks data into MAX6921 via SPI.
* Data must contain 3 bytes with following format:
* bit | 23 | 22 | ... | 4 | 3 | 2 | 1 | 0
* ----------------------------------------
* DOUT | 19 | 18 | ... | 0 | x | x | x | x
*/
void MAX6921::write_data(uint8_t *ptr, size_t length) {
assert(length == 3);
this->disable_load(); // set LOAD to low
this->transfer_array(ptr, length);
this->enable_load(); // set LOAD to high to update output latch
}
void MAX6921Component::send_byte_(uint8_t a_register, uint8_t data) {
this->write_byte(a_register);
this->write_byte(data);
}
void MAX6921Component::send_to_all_(uint8_t a_register, uint8_t data) {
this->enable();
for (uint8_t i = 0; i < this->num_chips_; i++)
this->send_byte_(a_register, data);
this->disable();
}
void MAX6921Component::update() {
if (this->intensity_changed_) {
this->send_to_all_(MAX6921_REGISTER_INTENSITY, this->intensity_);
this->intensity_changed_ = false;
}
for (uint8_t i = 0; i < this->num_chips_ * 8; i++)
this->buffer_[i] = 0;
if (this->writer_.has_value())
(*this->writer_)(*this);
this->display();
}
uint8_t MAX6921Component::print(uint8_t start_pos, const char *str) {
uint8_t pos = start_pos;
for (; *str != '\0'; str++) {
uint8_t data = MAX6921_UNKNOWN_CHAR;
if (*str >= ' ' && *str <= '~')
data = progmem_read_byte(&MAX6921_ASCII_TO_RAW[*str - ' ']);
if (data == MAX6921_UNKNOWN_CHAR) {
ESP_LOGW(TAG, "Encountered character '%c' with no MAX6921 representation while translating string!", *str);
}
if (*str == '.') {
if (pos != start_pos)
pos--;
this->buffer_[pos] |= 0b10000000;
} else {
if (pos >= this->num_chips_ * 8) {
ESP_LOGE(TAG, "MAX6921 String is too long for the display!");
break;
}
this->buffer_[pos] = data;
}
pos++;
}
return pos - start_pos;
}
uint8_t MAX6921Component::print(const char *str) { return this->print(0, str); }
uint8_t MAX6921Component::printf(uint8_t pos, const char *format, ...) {
va_list arg;
va_start(arg, format);
char buffer[64];
int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
va_end(arg);
if (ret > 0)
return this->print(pos, buffer);
return 0;
}
uint8_t MAX6921Component::printf(const char *format, ...) {
va_list arg;
va_start(arg, format);
char buffer[64];
int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
va_end(arg);
if (ret > 0)
return this->print(buffer);
return 0;
}
void MAX6921Component::set_writer(max6921_writer_t &&writer) { this->writer_ = writer; }
void MAX6921Component::set_intensity(uint8_t intensity) {
intensity &= 0xF;
if (intensity != this->intensity_) {
this->intensity_changed_ = true;
this->intensity_ = intensity;
}
}
void MAX6921Component::set_num_chips(uint8_t num_chips) { this->num_chips_ = num_chips; }
uint8_t MAX6921Component::strftime(uint8_t pos, const char *format, ESPTime time) {
char buffer[64];
size_t ret = time.strftime(buffer, sizeof(buffer), format);
if (ret > 0)
return this->print(pos, buffer);
return 0;
}
uint8_t MAX6921Component::strftime(const char *format, ESPTime time) { return this->strftime(0, format, time); }
} // namespace max6921
} // namespace esphome

View file

@ -8,56 +8,20 @@
namespace esphome {
namespace max6921 {
class MAX6921Component;
using max6921_writer_t = std::function<void(MAX6921Component &)>;
class MAX6921Component : public PollingComponent,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_1MHZ> {
class MAX6921 : public Component,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_1MHZ> {
public:
void set_writer(max6921_writer_t &&writer);
void setup() override;
void dump_config() override;
void update() override;
float get_setup_priority() const override;
void display();
void set_intensity(uint8_t intensity);
void set_num_chips(uint8_t num_chips);
void set_reverse(bool reverse) { this->reverse_ = reverse; };
/// Evaluate the printf-format and print the result at the given position.
uint8_t printf(uint8_t pos, const char *format, ...) __attribute__((format(printf, 3, 4)));
/// Evaluate the printf-format and print the result at position 0.
uint8_t printf(const char *format, ...) __attribute__((format(printf, 2, 3)));
/// Print `str` at the given position.
uint8_t print(uint8_t pos, const char *str);
/// Print `str` at position 0.
uint8_t print(const char *str);
/// Evaluate the strftime-format and print the result at the given position.
uint8_t strftime(uint8_t pos, const char *format, ESPTime time) __attribute__((format(strftime, 3, 0)));
/// Evaluate the strftime-format and print the result at position 0.
uint8_t strftime(const char *format, ESPTime time) __attribute__((format(strftime, 2, 0)));
void write_data(uint8_t *ptr, size_t length);
void set_load_pin(GPIOPin *load) { this->load_pin_ = load; }
protected:
void send_byte_(uint8_t a_register, uint8_t data);
void send_to_all_(uint8_t a_register, uint8_t data);
uint8_t intensity_{15}; // Intensity of the display from 0 to 15 (most)
bool intensity_changed_{}; // True if we need to re-send the intensity
uint8_t num_chips_{1};
uint8_t *buffer_;
bool reverse_{false};
optional<max6921_writer_t> writer_{};
GPIOPin *load_pin_{};
void enable_load() { this->load_pin_->digital_write(true); }
void disable_load() { this->load_pin_->digital_write(false); }
};
} // namespace max6921