Compare commits

..

No commits in common. "main" and "temp2" have entirely different histories.
main ... temp2

9 changed files with 247 additions and 725 deletions

View file

@ -6,24 +6,14 @@ by SN74HC595 shift registers.
## Hardware ## Hardware
Connect the shift registers like this, or adapt the pin config as needed: Connect the shift registers like this, or adapt the pin config as needed:
- Data input (DIO) to ESP: - Data input (DIO) to ESP GPIO19
- GPIO23 (first display), - Shift clock input (SCK) to ESP GPIO18
- GPIO19 (second display), - Latch / register clock input (RCK) to ESP GPIO5
- GPIO22 (third display),
- GPIO21 (fourth display)
- Shift clock input (SCK) to ESP GPIO18 (all displays)
- Latch / register clock input (RCK) to ESP GPIO5 (all display)
Keep in mind that the ESP32 is a 3.3V device when connecting to a 5V display. The shift registers should handle the 3.3V Keep in mind that the ESP32 is a 3.3V device when connecting to a 5V display.
signals from the ESP, annd in some cases, 3.3V can also be used to power the displays at a lower brightness.
## Setup ## Setup
> Note: the QSPI driver in esphome is (as of 2024-01-28) not yet in the released package.
> Install a suitable version directly from github with:
> `pip install "esphome @ git+https://github.com/esphome/esphome@1fef769496ed89c0062d8e70f5964b8318ba4550"`
Set the board type in the YAML (default should also work on most ESP32 devices) Set the board type in the YAML (default should also work on most ESP32 devices)
Adapt the config as needed for the fallback AP and if wanted, api key. Adapt the config as needed for the fallback AP and if wanted, api key.
Create a `secrets.yaml` containing this: Create a `secrets.yaml` containing this:
@ -45,4 +35,4 @@ The driver in `components/` is directly based on the existing MAX7219 driver in
subject to the GPLv3 and MIT licenses, as outlined in the LICENSE document (also copied from ESPHome). subject to the GPLv3 and MIT licenses, as outlined in the LICENSE document (also copied from ESPHome).
## Open Issues ## Open Issues
The actual segment mapping is not tested well, due to missing hardware.

View file

@ -1,34 +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"]
qspi_74hc595_4x_display_ns = cg.esphome_ns.namespace("qspi_74hc595_4x_display")
QSPI_74HC595_4X_DISPLAYComponent = qspi_74hc595_4x_display_ns.class_(
"QSPI_74HC595_4X_DISPLAYComponent", cg.PollingComponent, spi.SPIDevice
)
QSPI_74HC595_4X_DISPLAYComponentRef = QSPI_74HC595_4X_DISPLAYComponent.operator("ref")
CONFIG_SCHEMA = (
display.BASIC_DISPLAY_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(QSPI_74HC595_4X_DISPLAYComponent),
}
)
.extend(cv.polling_component_schema("1s"))
.extend(spi.spi_device_schema(quad=True))
)
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)
if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(QSPI_74HC595_4X_DISPLAYComponentRef, "it")], return_type=cg.void
)
cg.add(var.set_writer(lambda_))

View file

@ -1,265 +0,0 @@
#include "qspi_74hc595_4x_display.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome
{
namespace qspi_74hc595_4x_display
{
static const char *const TAG = "qspi_74hc595_4x_display";
static const uint32_t QSPI_74HC595_4X_DISPLAY_UNKNOWN_CHAR = 0x11111111;
static const uint32_t QSPI_74HC595_4X_DISPLAY_ASCII_TO_RAW[95] = {
// BADCFE.G
// 0xBADCFE.G
0x00000000, // ' ', ord 0x20
0x10010010, // '!', ord 0x21
0x10001000, // '"', ord 0x22
0x10011100, // '#', ord 0x23
0x01111000, // '$', ord 0x24
0x01100001, // '%', ord 0x25
0x11100100, // '&', ord 0x26
0x00001000, // ''', ord 0x27
0x01101100, // '(', ord 0x28
0x11110000, // ')', ord 0x29
0x01000000, // '*', ord 0x2A
0x00000100, // '+', ord 0x2B
0x00010000, // ',', ord 0x2C
0x00000001, // '-', ord 0x2D
0x00000010, // '.', ord 0x2E
0x10000101, // '/', ord 0x2F
0x11111100, // '0', ord 0x30
0x10010000, // '1', ord 0x31
0x11100101, // '2', ord 0x32
0x11110001, // '3', ord 0x33
0x10011001, // '4', ord 0x34
0x01111001, // '5', ord 0x35
0x01111101, // '6', ord 0x36
0x11010000, // '7', ord 0x37
0x11111101, // '8', ord 0x38
0x11111001, // '9', ord 0x39
0x01100000, // ':', ord 0x3A
0x01110000, // ';', ord 0x3B
0x00100100, // '<', ord 0x3C
0x00100001, // '=', ord 0x3D
0x00110000, // '>', ord 0x3E
0x11000101, // '?', ord 0x3F
0x11101101, // '@', ord 0x40
0x11011101, // 'A', ord 0x41
0x00111101, // 'B', ord 0x42
0x01101100, // 'C', ord 0x43
0x10110101, // 'D', ord 0x44
0x01101101, // 'E', ord 0x45
0x01001101, // 'F', ord 0x46
0x01111100, // 'G', ord 0x47
0x10011101, // 'H', ord 0x48
0x10010000, // 'I', ord 0x49
0x10110100, // 'J', ord 0x4A
QSPI_74HC595_4X_DISPLAY_UNKNOWN_CHAR, // 'K', ord 0x4B
0x00101100, // 'L', ord 0x4C
0x11011100, // 'M', ord 0x4D
0x00010101, // 'N', ord 0x4E
0x11111100, // 'O', ord 0x4F
0x11001101, // 'P', ord 0x50
0x11111110, // 'Q', ord 0x51
0x00000101, // 'R', ord 0x52
0x01111001, // 'S', ord 0x53
0x00001101, // 'T', ord 0x54
0x10111100, // 'U', ord 0x55
0x10111100, // 'V', ord 0x56
0x10111101, // 'W', ord 0x57
QSPI_74HC595_4X_DISPLAY_UNKNOWN_CHAR, // 'X', ord 0x58
0x10001101, // 'Y', ord 0x59
0x11100101, // 'Z', ord 0x5A
0x01101100, // '[', ord 0x5B
0x00011001, // '\', ord 0x5C
0x11110000, // ']', ord 0x5D
0x11001000, // '^', ord 0x5E
0x00100000, // '_', ord 0x5F
0x10000000, // '`', ord 0x60
0x11011101, // 'a', ord 0x61
0x00111101, // 'b', ord 0x62
0x00100101, // 'c', ord 0x63
0x10110101, // 'd', ord 0x64
0x01101101, // 'e', ord 0x65
0x01001101, // 'f', ord 0x66
0x01111100, // 'g', ord 0x67
0x00011101, // 'h', ord 0x68
0x00010000, // 'i', ord 0x69
0x10110100, // 'j', ord 0x6A
QSPI_74HC595_4X_DISPLAY_UNKNOWN_CHAR, // 'k', ord 0x6B
0x00101100, // 'l', ord 0x6C
0x11011100, // 'm', ord 0x6D
0x00010101, // 'n', ord 0x6E
0x00110101, // 'o', ord 0x6F
0x11001101, // 'p', ord 0x70
0x11011001, // 'q', ord 0x71
0x00000101, // 'r', ord 0x72
0x01111001, // 's', ord 0x73
0x00001101, // 't', ord 0x74
0x00110100, // 'u', ord 0x75
0x00110100, // 'v', ord 0x76
QSPI_74HC595_4X_DISPLAY_UNKNOWN_CHAR, // 'w', ord 0x77
QSPI_74HC595_4X_DISPLAY_UNKNOWN_CHAR, // 'x', ord 0x78
0x10001101, // 'y', ord 0x79
QSPI_74HC595_4X_DISPLAY_UNKNOWN_CHAR, // 'z', ord 0x7A
0x10010001, // '{', ord 0x7B
0x00001100, // '|', ord 0x7C
0x00001101, // '}', ord 0x7D
0x11001001, // '~', ord 0x7E (degree symbol)
};
static constexpr uint8_t QSPI_74HC595_4X_DISPLAY_DIGIT_MAP[8] = {2, 3, 0, 1, 6, 7, 4, 5};
static constexpr uint8_t QSPI_74HC595_4X_DISPLAY_ZEROS[32] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
float QSPI_74HC595_4X_DISPLAYComponent::get_setup_priority() const
{
return setup_priority::PROCESSOR;
}
void QSPI_74HC595_4X_DISPLAYComponent::setup()
{
ESP_LOGCONFIG(TAG, "Setting up QSPI_74HC595_4X_DISPLAY...");
this->spi_setup();
memset(this->buffer_, 0xff, sizeof(this->buffer_));
// this->buffer_[0*2] = ~QSPI_74HC595_4X_DISPLAY_ASCII_TO_RAW['.'-' '];
// this->buffer_[1*2] = ~QSPI_74HC595_4X_DISPLAY_ASCII_TO_RAW['*'-' '];
// this->buffer_[2*2] = ~QSPI_74HC595_4X_DISPLAY_ASCII_TO_RAW['`'-' '];
// this->buffer_[3*2] = ~QSPI_74HC595_4X_DISPLAY_ASCII_TO_RAW[','-' '];
// this->buffer_[4*2] = ~QSPI_74HC595_4X_DISPLAY_ASCII_TO_RAW['_'-' '];
// this->buffer_[5*2] = ~QSPI_74HC595_4X_DISPLAY_ASCII_TO_RAW['+'-' '];
// this->buffer_[6*2] = ~QSPI_74HC595_4X_DISPLAY_ASCII_TO_RAW['\''-' '];
// this->buffer_[7*2] = ~QSPI_74HC595_4X_DISPLAY_ASCII_TO_RAW['-'-' '];
for (uint8_t i = 0; i < 8; i++)
{
this->buffer_[(i * 2) + 1] = 0xF << (QSPI_74HC595_4X_DISPLAY_DIGIT_MAP[i] * 4);
}
}
void QSPI_74HC595_4X_DISPLAYComponent::dump_config()
{
ESP_LOGCONFIG(TAG, "QSPI_74HC595_4X_DISPLAY:");
LOG_PIN(" CS Pin: ", this->cs_);
LOG_UPDATE_INTERVAL(this);
}
void QSPI_74HC595_4X_DISPLAYComponent::display()
{
uint32_t delay = static_cast<uint64_t>(this->get_update_interval()) * 1000 / 8;
const uint8_t *buf_ptr = reinterpret_cast<const uint8_t *>(this->buffer_);
for (uint8_t i = 0; i < 8; i++)
{
uint32_t start = micros();
this->enable();
this->write_cmd_addr_data(0, 0, 0, 0, buf_ptr, 8, 4);
buf_ptr += 8;
this->disable();
delay_microseconds_safe(delay - (micros() - start));
}
// zero out everything to have a somewhat uniform duty cycle for all digits
this->enable();
this->write_cmd_addr_data(0, 0, 0, 0, QSPI_74HC595_4X_DISPLAY_ZEROS, 32, 4);
this->disable();
}
void QSPI_74HC595_4X_DISPLAYComponent::update()
{
for (uint8_t i = 0; i < 8; i++)
this->buffer_[i * 2] = 0xFFFFFFFF;
if (this->writer_.has_value())
(*this->writer_)(*this);
this->display();
}
uint8_t QSPI_74HC595_4X_DISPLAYComponent::print(uint8_t start_pos, const char *str)
{
uint8_t pos = start_pos;
for (; *str != '\0'; str++)
{
uint32_t data = QSPI_74HC595_4X_DISPLAY_UNKNOWN_CHAR;
if (*str >= ' ' && *str <= '~')
data = QSPI_74HC595_4X_DISPLAY_ASCII_TO_RAW[*str - ' '];
if (data == QSPI_74HC595_4X_DISPLAY_UNKNOWN_CHAR)
{
ESP_LOGW(
TAG,
"Encountered character '%c' with no QSPI_74HC595_4X_DISPLAY representation while translating string!",
*str);
}
if (*str == '.')
{
if (pos != start_pos)
pos--;
this->buffer_[(pos % 8) * 2] &= ~(0x00000010 << (pos / 8));
}
else
{
if (pos >= 32)
{
ESP_LOGE(TAG, "QSPI_74HC595_4X_DISPLAY String is too long for the display!");
break;
}
this->buffer_[(pos % 8) * 2] &= ~(data << (pos / 8));
}
pos++;
}
return pos - start_pos;
}
uint8_t QSPI_74HC595_4X_DISPLAYComponent::print(const char *str)
{
return this->print(0, str);
}
uint8_t QSPI_74HC595_4X_DISPLAYComponent::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 QSPI_74HC595_4X_DISPLAYComponent::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 QSPI_74HC595_4X_DISPLAYComponent::set_writer(qspi_74hc595_4x_display_writer_t &&writer)
{
this->writer_ = writer;
}
uint8_t QSPI_74HC595_4X_DISPLAYComponent::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 QSPI_74HC595_4X_DISPLAYComponent::strftime(const char *format, ESPTime time)
{
return this->strftime(0, format, time);
}
} // namespace qspi_74hc595_4x_display
} // namespace esphome

View file

@ -1,56 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/time.h"
#include "esphome/components/spi/spi.h"
namespace esphome {
namespace qspi_74hc595_4x_display {
class QSPI_74HC595_4X_DISPLAYComponent;
using qspi_74hc595_4x_display_writer_t = std::function<void(QSPI_74HC595_4X_DISPLAYComponent &)>;
class QSPI_74HC595_4X_DISPLAYComponent : public PollingComponent,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_10MHZ> {
public:
void set_writer(qspi_74hc595_4x_display_writer_t &&writer);
void setup() override;
void dump_config() override;
void update() override;
float get_setup_priority() const override;
void display();
/// 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)));
protected:
void send_byte_(uint8_t a_register, uint8_t data);
void send_to_all_(uint8_t a_register, uint8_t data);
uint32_t buffer_[16]; // 8* segment+digit words
optional<qspi_74hc595_4x_display_writer_t> writer_{};
};
} // namespace qspi_74hc595_4x_display
} // namespace esphome

View file

@ -12,6 +12,8 @@ SPI_74HC595_DISPLAYComponent = spi_74hc595_display_ns.class_(
SPI_74HC595_DISPLAYComponentRef = SPI_74HC595_DISPLAYComponent.operator("ref") SPI_74HC595_DISPLAYComponentRef = SPI_74HC595_DISPLAYComponent.operator("ref")
CONF_REVERSE = "reverse" CONF_REVERSE = "reverse"
CONF_SEGMENT_FIRST = "segment_first"
CONF_COMMON_CATHODE = "common_cathode"
CONFIG_SCHEMA = ( CONFIG_SCHEMA = (
display.BASIC_DISPLAY_SCHEMA.extend( display.BASIC_DISPLAY_SCHEMA.extend(
@ -19,6 +21,8 @@ CONFIG_SCHEMA = (
cv.GenerateID(): cv.declare_id(SPI_74HC595_DISPLAYComponent), cv.GenerateID(): cv.declare_id(SPI_74HC595_DISPLAYComponent),
cv.Optional(CONF_NUM_CHIPS, default=1): cv.int_range(min=1, max=255), cv.Optional(CONF_NUM_CHIPS, default=1): cv.int_range(min=1, max=255),
cv.Optional(CONF_REVERSE, default=False): cv.boolean, cv.Optional(CONF_REVERSE, default=False): cv.boolean,
cv.Optional(CONF_SEGMENT_FIRST, default=False): cv.boolean,
cv.Optional(CONF_COMMON_CATHODE, default=False): cv.boolean,
} }
) )
.extend(cv.polling_component_schema("1s")) .extend(cv.polling_component_schema("1s"))
@ -33,6 +37,8 @@ async def to_code(config):
cg.add(var.set_num_chips(config[CONF_NUM_CHIPS])) cg.add(var.set_num_chips(config[CONF_NUM_CHIPS]))
cg.add(var.set_reverse(config[CONF_REVERSE])) cg.add(var.set_reverse(config[CONF_REVERSE]))
cg.add(var.set_segment_first(config[CONF_SEGMENT_FIRST]))
cg.add(var.set_common_cathode(config[CONF_COMMON_CATHODE]))
if CONF_LAMBDA in config: if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(

View file

@ -1,12 +1,10 @@
#include "spi_74hc595_display.h" #include "spi_74hc595_display.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "esphome/core/hal.h"
namespace esphome namespace esphome {
{ namespace spi_74hc595_display {
namespace spi_74hc595_display
{
static const char *const TAG = "spi_74hc595_display"; static const char *const TAG = "spi_74hc595_display";
@ -14,261 +12,224 @@ static const uint8_t SPI_74HC595_DISPLAY_UNKNOWN_CHAR = 0b11111111;
const uint8_t SPI_74HC595_DISPLAY_ASCII_TO_RAW[95] PROGMEM = { const uint8_t SPI_74HC595_DISPLAY_ASCII_TO_RAW[95] PROGMEM = {
//.GFEDCBA //.GFEDCBA
0b00000000, // ' ', ord 0x20 0b00000000, // ' ', ord 0x20
0b10000110, // '!', ord 0x21 0b10000110, // '!', ord 0x21
0b00100010, // '"', ord 0x22 0b00100010, // '"', ord 0x22
0b00110110, // '#', ord 0x23 0b00110110, // '#', ord 0x23
0b00101101, // '$', ord 0x24 0b00101101, // '$', ord 0x24
0b01001001, // '%', ord 0x25 0b01001001, // '%', ord 0x25
0b00011011, // '&', ord 0x26 0b00011011, // '&', ord 0x26
0b00100000, // ''', ord 0x27 0b00100000, // ''', ord 0x27
0b00111001, // '(', ord 0x28 0b00111001, // '(', ord 0x28
0b00001111, // ')', ord 0x29 0b00001111, // ')', ord 0x29
0b00000001, // '*', ord 0x2A 0b00000001, // '*', ord 0x2A
0b00010000, // '+', ord 0x2B 0b00010000, // '+', ord 0x2B
0b00000100, // ',', ord 0x2C 0b00000100, // ',', ord 0x2C
0b01000000, // '-', ord 0x2D 0b01000000, // '-', ord 0x2D
0b10000000, // '.', ord 0x2E 0b10000000, // '.', ord 0x2E
0b01010010, // '/', ord 0x2F 0b01010010, // '/', ord 0x2F
0b00111111, // '0', ord 0x30 0b00111111, // '0', ord 0x30
0b00000110, // '1', ord 0x31 0b00000110, // '1', ord 0x31
0b01011011, // '2', ord 0x32 0b01011011, // '2', ord 0x32
0b01001111, // '3', ord 0x33 0b01001111, // '3', ord 0x33
0b01100110, // '4', ord 0x34 0b01100110, // '4', ord 0x34
0b01101101, // '5', ord 0x35 0b01101101, // '5', ord 0x35
0b01111101, // '6', ord 0x36 0b01111101, // '6', ord 0x36
0b00000111, // '7', ord 0x37 0b00000111, // '7', ord 0x37
0b01111111, // '8', ord 0x38 0b01111111, // '8', ord 0x38
0b01101111, // '9', ord 0x39 0b01101111, // '9', ord 0x39
0b00001001, // ':', ord 0x3A 0b00001001, // ':', ord 0x3A
0b00001101, // ';', ord 0x3B 0b00001101, // ';', ord 0x3B
0b00011000, // '<', ord 0x3C 0b00011000, // '<', ord 0x3C
0b01001000, // '=', ord 0x3D 0b01001000, // '=', ord 0x3D
0b00001100, // '>', ord 0x3E 0b00001100, // '>', ord 0x3E
0b01010011, // '?', ord 0x3F 0b01010011, // '?', ord 0x3F
0b01111011, // '@', ord 0x40 0b01111011, // '@', ord 0x40
0b01110111, // 'A', ord 0x41 0b01110111, // 'A', ord 0x41
0b01111100, // 'B', ord 0x42 0b01111100, // 'B', ord 0x42
0b00111001, // 'C', ord 0x43 0b00111001, // 'C', ord 0x43
0b01011110, // 'D', ord 0x44 0b01011110, // 'D', ord 0x44
0b01111001, // 'E', ord 0x45 0b01111001, // 'E', ord 0x45
0b01110001, // 'F', ord 0x46 0b01110001, // 'F', ord 0x46
0b00111101, // 'G', ord 0x47 0b00111101, // 'G', ord 0x47
0b01110110, // 'H', ord 0x48 0b01110110, // 'H', ord 0x48
0b00000110, // 'I', ord 0x49 0b00000110, // 'I', ord 0x49
0b00011110, // 'J', ord 0x4A 0b00011110, // 'J', ord 0x4A
SPI_74HC595_DISPLAY_UNKNOWN_CHAR, // 'K', ord 0x4B SPI_74HC595_DISPLAY_UNKNOWN_CHAR, // 'K', ord 0x4B
0b00111000, // 'L', ord 0x4C 0b00111000, // 'L', ord 0x4C
0b00110111, // 'M', ord 0x4D 0b00110111, // 'M', ord 0x4D
0b01010100, // 'N', ord 0x4E 0b01010100, // 'N', ord 0x4E
0b00111111, // 'O', ord 0x4F 0b00111111, // 'O', ord 0x4F
0b01110011, // 'P', ord 0x50 0b01110011, // 'P', ord 0x50
0b10111111, // 'Q', ord 0x51 0b10111111, // 'Q', ord 0x51
0b01010000, // 'R', ord 0x52 0b01010000, // 'R', ord 0x52
0b01101101, // 'S', ord 0x53 0b01101101, // 'S', ord 0x53
0b01110000, // 'T', ord 0x54 0b01110000, // 'T', ord 0x54
0b00111110, // 'U', ord 0x55 0b00111110, // 'U', ord 0x55
0b00111110, // 'V', ord 0x56 0b00111110, // 'V', ord 0x56
0b01111110, // 'W', ord 0x57 0b01111110, // 'W', ord 0x57
SPI_74HC595_DISPLAY_UNKNOWN_CHAR, // 'X', ord 0x58 SPI_74HC595_DISPLAY_UNKNOWN_CHAR, // 'X', ord 0x58
0b01110010, // 'Y', ord 0x59 0b01110010, // 'Y', ord 0x59
0b01011011, // 'Z', ord 0x5A 0b01011011, // 'Z', ord 0x5A
0b00111001, // '[', ord 0x5B 0b00111001, // '[', ord 0x5B
0b01100100, // '\', ord 0x5C 0b01100100, // '\', ord 0x5C
0b00001111, // ']', ord 0x5D 0b00001111, // ']', ord 0x5D
0b00100011, // '^', ord 0x5E 0b00100011, // '^', ord 0x5E
0b00001000, // '_', ord 0x5F 0b00001000, // '_', ord 0x5F
0b00000010, // '`', ord 0x60 0b00000010, // '`', ord 0x60
0b01110111, // 'a', ord 0x61 0b01110111, // 'a', ord 0x61
0b01111100, // 'b', ord 0x62 0b01111100, // 'b', ord 0x62
0b01011000, // 'c', ord 0x63 0b01011000, // 'c', ord 0x63
0b01011110, // 'd', ord 0x64 0b01011110, // 'd', ord 0x64
0b01111001, // 'e', ord 0x65 0b01111001, // 'e', ord 0x65
0b01110001, // 'f', ord 0x66 0b01110001, // 'f', ord 0x66
0b00111101, // 'g', ord 0x67 0b00111101, // 'g', ord 0x67
0b01110100, // 'h', ord 0x68 0b01110100, // 'h', ord 0x68
0b00000100, // 'i', ord 0x69 0b00000100, // 'i', ord 0x69
0b00011110, // 'j', ord 0x6A 0b00011110, // 'j', ord 0x6A
SPI_74HC595_DISPLAY_UNKNOWN_CHAR, // 'k', ord 0x6B SPI_74HC595_DISPLAY_UNKNOWN_CHAR, // 'k', ord 0x6B
0b00111000, // 'l', ord 0x6C 0b00111000, // 'l', ord 0x6C
0b00110111, // 'm', ord 0x6D 0b00110111, // 'm', ord 0x6D
0b01010100, // 'n', ord 0x6E 0b01010100, // 'n', ord 0x6E
0b01011100, // 'o', ord 0x6F 0b01011100, // 'o', ord 0x6F
0b01110011, // 'p', ord 0x70 0b01110011, // 'p', ord 0x70
0b01100111, // 'q', ord 0x71 0b01100111, // 'q', ord 0x71
0b01010000, // 'r', ord 0x72 0b01010000, // 'r', ord 0x72
0b01101101, // 's', ord 0x73 0b01101101, // 's', ord 0x73
0b01110000, // 't', ord 0x74 0b01110000, // 't', ord 0x74
0b00011100, // 'u', ord 0x75 0b00011100, // 'u', ord 0x75
0b00011100, // 'v', ord 0x76 0b00011100, // 'v', ord 0x76
SPI_74HC595_DISPLAY_UNKNOWN_CHAR, // 'w', ord 0x77 SPI_74HC595_DISPLAY_UNKNOWN_CHAR, // 'w', ord 0x77
SPI_74HC595_DISPLAY_UNKNOWN_CHAR, // 'x', ord 0x78 SPI_74HC595_DISPLAY_UNKNOWN_CHAR, // 'x', ord 0x78
0b01110010, // 'y', ord 0x79 0b01110010, // 'y', ord 0x79
SPI_74HC595_DISPLAY_UNKNOWN_CHAR, // 'z', ord 0x7A SPI_74HC595_DISPLAY_UNKNOWN_CHAR, // 'z', ord 0x7A
0b01000110, // '{', ord 0x7B 0b01000110, // '{', ord 0x7B
0b00110000, // '|', ord 0x7C 0b00110000, // '|', ord 0x7C
0b01110000, // '}', ord 0x7D 0b01110000, // '}', ord 0x7D
0b01100011, // '~', ord 0x7E (degree symbol) 0b01100011, // '~', ord 0x7E (degree symbol)
}; };
const uint8_t SPI_74HC595_DISPLAY_DIGIT_MASK[8] PROGMEM = { float SPI_74HC595_DISPLAYComponent::get_setup_priority() const { return setup_priority::PROCESSOR; }
(1U << 4), (1U << 5), (1U << 6), (1U << 7), (1U << 0), (1U << 1), (1U << 2), (1U << 3),
};
float SPI_74HC595_DISPLAYComponent::get_setup_priority() const void SPI_74HC595_DISPLAYComponent::setup() {
{ ESP_LOGCONFIG(TAG, "Setting up SPI_74HC595_DISPLAY...");
return setup_priority::PROCESSOR; 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;
} }
void SPI_74HC595_DISPLAYComponent::setup() void SPI_74HC595_DISPLAYComponent::dump_config() {
{ ESP_LOGCONFIG(TAG, "SPI_74HC595_DISPLAY:");
ESP_LOGCONFIG(TAG, "Setting up SPI_74HC595_DISPLAY..."); ESP_LOGCONFIG(TAG, " Number of Chips: %u", this->num_chips_);
this->spi_setup(); LOG_PIN(" CS Pin: ", this->cs_);
this->buffer_ = new uint8_t[this->num_chips_ * 8]; // NOLINT LOG_UPDATE_INTERVAL(this);
for (uint8_t i = 0; i < this->num_chips_ * 8; i++)
{
this->buffer_[i] = 0;
}
} }
void SPI_74HC595_DISPLAYComponent::dump_config() void SPI_74HC595_DISPLAYComponent::display() {
{ uint32_t delay = static_cast<uint64_t>(this->get_update_interval())*1000 / 8;
ESP_LOGCONFIG(TAG, "SPI_74HC595_DISPLAY:"); uint8_t flip_digit=(this->common_cathode_)?0xFF:0x00;
ESP_LOGCONFIG(TAG, " Number of Chips: %u", this->num_chips_); uint8_t flip_segment = ~flip_digit;
LOG_PIN(" CS Pin: ", this->cs_); for (uint8_t i = 0; i < 8; i++) {
LOG_UPDATE_INTERVAL(this);
}
void SPI_74HC595_DISPLAYComponent::display()
{
uint32_t delay = static_cast<uint64_t>(this->get_update_interval()) * 1000 / 8;
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_(SPI_74HC595_DISPLAY_DIGIT_MASK[i], ~buffer_[(num_chips_ - j - 1) * 8 + i]);
}
else
{
this->send_byte_(SPI_74HC595_DISPLAY_DIGIT_MASK[i], ~buffer_[j * 8 + i]);
}
}
this->disable();
delay_microseconds_safe(delay);
}
// zero out everything to have a somewhat uniform duty cycle for all digits
this->enable(); this->enable();
for (uint8_t j = 0; j < this->num_chips_; j++) for (uint8_t j = 0; j < this->num_chips_; j++) {
{ if (reverse_) {
this->send_byte_(0, 0); this->send_byte_((1U << i)^flip_digit, buffer_[(num_chips_ - j - 1) * 8 + i]^flip_segment);
} else {
this->send_byte_((1U << i)^flip_digit, (buffer_[j * 8 + i])^flip_segment);
}
} }
this->disable(); this->disable();
delay_microseconds_safe(delay);
}
// zero out everything to have a somewhat uniform duty cycle for all digits
this->enable();
for (uint8_t j = 0; j < this->num_chips_; j++) {
this->send_byte_(0, 0);
}
this->disable();
} }
void SPI_74HC595_DISPLAYComponent::send_byte_(uint8_t a_digit, uint8_t data) void SPI_74HC595_DISPLAYComponent::send_byte_(uint8_t a_digit, uint8_t data) {
{ if(this->segment_first_){
this->write_byte(data); this->write_byte(data);
this->write_byte(a_digit); this->write_byte(a_digit);
} else {
this->write_byte(a_digit);
this->write_byte(data);
}
} }
void SPI_74HC595_DISPLAYComponent::update() void SPI_74HC595_DISPLAYComponent::update() {
{ for (uint8_t i = 0; i < this->num_chips_ * 8; i++)
for (uint8_t i = 0; i < this->num_chips_ * 8; i++) this->buffer_[i] = 0;
this->buffer_[i] = 0; if (this->writer_.has_value())
if (this->writer_.has_value()) (*this->writer_)(*this);
(*this->writer_)(*this); this->display();
this->display();
} }
uint8_t SPI_74HC595_DISPLAYComponent::print(uint8_t start_pos, const char *str) uint8_t SPI_74HC595_DISPLAYComponent::print(uint8_t start_pos, const char *str) {
{ uint8_t pos = start_pos;
uint8_t pos = start_pos; for (; *str != '\0'; str++) {
for (; *str != '\0'; str++) uint8_t data = SPI_74HC595_DISPLAY_UNKNOWN_CHAR;
{ if (*str >= ' ' && *str <= '~')
uint8_t data = SPI_74HC595_DISPLAY_UNKNOWN_CHAR; data = progmem_read_byte(&SPI_74HC595_DISPLAY_ASCII_TO_RAW[*str - ' ']);
if (*str >= ' ' && *str <= '~')
data = progmem_read_byte(&SPI_74HC595_DISPLAY_ASCII_TO_RAW[*str - ' ']);
if (data == SPI_74HC595_DISPLAY_UNKNOWN_CHAR) if (data == SPI_74HC595_DISPLAY_UNKNOWN_CHAR) {
{ ESP_LOGW(TAG, "Encountered character '%c' with no SPI_74HC595_DISPLAY representation while translating string!", *str);
ESP_LOGW(TAG,
"Encountered character '%c' with no SPI_74HC595_DISPLAY 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, "SPI_74HC595_DISPLAY String is too long for the display!");
break;
}
this->buffer_[pos] = data;
}
pos++;
} }
return pos - start_pos; if (*str == '.') {
if (pos != start_pos)
pos--;
this->buffer_[pos] |= 0b10000000;
} else {
if (pos >= this->num_chips_ * 8) {
ESP_LOGE(TAG, "SPI_74HC595_DISPLAY String is too long for the display!");
break;
}
this->buffer_[pos] = data;
}
pos++;
}
return pos - start_pos;
} }
uint8_t SPI_74HC595_DISPLAYComponent::print(const char *str) uint8_t SPI_74HC595_DISPLAYComponent::print(const char *str) { return this->print(0, str); }
{
return this->print(0, str); uint8_t SPI_74HC595_DISPLAYComponent::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 SPI_74HC595_DISPLAYComponent::printf(uint8_t pos, const char *format, ...) uint8_t SPI_74HC595_DISPLAYComponent::printf(const char *format, ...) {
{ va_list arg;
va_list arg; va_start(arg, format);
va_start(arg, format); char buffer[64];
char buffer[64]; int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
int ret = vsnprintf(buffer, sizeof(buffer), format, arg); va_end(arg);
va_end(arg); if (ret > 0)
if (ret > 0) return this->print(buffer);
return this->print(pos, buffer); return 0;
return 0;
} }
uint8_t SPI_74HC595_DISPLAYComponent::printf(const char *format, ...) void SPI_74HC595_DISPLAYComponent::set_writer(spi_74hc595_display_writer_t &&writer) { this->writer_ = writer; }
{
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 SPI_74HC595_DISPLAYComponent::set_writer(spi_74hc595_display_writer_t &&writer) void SPI_74HC595_DISPLAYComponent::set_num_chips(uint8_t num_chips) { this->num_chips_ = num_chips; }
{
this->writer_ = writer;
}
void SPI_74HC595_DISPLAYComponent::set_num_chips(uint8_t num_chips) uint8_t SPI_74HC595_DISPLAYComponent::strftime(uint8_t pos, const char *format, ESPTime time) {
{ char buffer[64];
this->num_chips_ = num_chips; size_t ret = time.strftime(buffer, sizeof(buffer), format);
if (ret > 0)
return this->print(pos, buffer);
return 0;
} }
uint8_t SPI_74HC595_DISPLAYComponent::strftime(const char *format, ESPTime time) { return this->strftime(0, format, time); }
uint8_t SPI_74HC595_DISPLAYComponent::strftime(uint8_t pos, const char *format, ESPTime time) } // namespace spi_74hc595_display
{ } // namespace esphome
char buffer[64];
size_t ret = time.strftime(buffer, sizeof(buffer), format);
if (ret > 0)
return this->print(pos, buffer);
return 0;
}
uint8_t SPI_74HC595_DISPLAYComponent::strftime(const char *format, ESPTime time)
{
return this->strftime(0, format, time);
}
} // namespace spi_74hc595_display
} // namespace esphome

View file

@ -30,6 +30,8 @@ class SPI_74HC595_DISPLAYComponent : public PollingComponent,
void set_num_chips(uint8_t num_chips); void set_num_chips(uint8_t num_chips);
void set_reverse(bool reverse) { this->reverse_ = reverse; }; void set_reverse(bool reverse) { this->reverse_ = reverse; };
void set_segment_first(bool segment_first) { this->segment_first_ = segment_first; };
void set_common_cathode(bool common_cathode) { this->common_cathode_ = common_cathode; };
/// Evaluate the printf-format and print the result at the given position. /// 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))); uint8_t printf(uint8_t pos, const char *format, ...) __attribute__((format(printf, 3, 4)));
@ -54,6 +56,8 @@ class SPI_74HC595_DISPLAYComponent : public PollingComponent,
uint8_t num_chips_{1}; uint8_t num_chips_{1};
uint8_t *buffer_; uint8_t *buffer_;
bool reverse_{false}; bool reverse_{false};
bool segment_first_{false};
bool common_cathode_{false};
optional<spi_74hc595_display_writer_t> writer_{}; optional<spi_74hc595_display_writer_t> writer_{};
}; };

View file

@ -5,7 +5,7 @@ esphome:
esp32: esp32:
board: esp32dev board: esp32dev
framework: framework:
type: esp-idf type: arduino
# Enable logging # Enable logging
logger: logger:
@ -44,147 +44,63 @@ time:
- platform: sntp - platform: sntp
id: sntp_time id: sntp_time
timezone: Europe/Berlin timezone: Europe/Berlin
on_time:
- seconds: /10
then:
- logger.log:
level: INFO
format: "%2d %2d %2d %2d"
args:
- 'uint32_t(id(runtime).state/3600) - uint32_t(id(runtime).state/86400)*1440'
- 'uint32_t(id(runtime).state/60) - uint32_t(id(runtime).state/3600)*60'
- 'uint32_t(id(runtime).state) - uint32_t(id(runtime).state/60)*60'
- 'uint32_t((id(runtime).state - uint32_t(id(runtime).state))*25)'
spi: spi:
- id: quad_spi_bus clk_pin: GPIO18
interface: spi3 mosi_pin: GPIO19
clk_pin: 18
data_pins:
- 23
- 19
- 22
- 21
sensor: sensor:
- platform: uptime - platform: uptime
id: runtime id: runtime
update_interval: 100ms update_interval: 100ms
globals:
- id: countdown_end
type: time_t
- id: uptime_start
type: time_t
number:
- platform: template
name: "Countdown set H"
id: cd_set_h
initial_value: 0
min_value: 0
max_value: 99
step: 1
optimistic: true
mode: box
- platform: template
name: "Countdown set M"
id: cd_set_m
initial_value: 0
min_value: 0
max_value: 59
step: 1
optimistic: true
mode: box
- platform: template
name: "Countdown set S"
id: cd_set_s
initial_value: 0
min_value: 0
max_value: 59
step: 1
optimistic: true
mode: box
button:
- platform: template
name: "Countdown set"
on_press:
- lambda: |-
id(countdown_end) =
id(sntp_time).timestamp_now()
+ uint32_t(id(cd_set_h).state)*3600
+ uint32_t(id(cd_set_m).state)*60
+ uint32_t(id(cd_set_s).state);
- platform: template
name: "Countdown Minutes 1m"
on_press:
- lambda: |-
id(countdown_end) = id(sntp_time).timestamp_now() + 60;
- platform: template
name: "Countdown Minutes 5m"
on_press:
- lambda: |-
id(countdown_end) = id(sntp_time).timestamp_now() + 300;
- platform: template
name: "Countdown Minutes 10m"
on_press:
- lambda: |-
id(countdown_end) = id(sntp_time).timestamp_now() + 600;
- platform: template
name: "Countdown Hours 1h"
on_press:
- lambda: |-
id(countdown_end) = id(sntp_time).timestamp_now() + 3600;
- platform: template
name: "Countdown Hours 2h"
on_press:
- lambda: |-
id(countdown_end) = id(sntp_time).timestamp_now() + 7200;
- platform: template
name: "Runtime Reset"
on_press:
- lambda: |-
id(uptime_start) = id(runtime).state;
display: display:
- platform: qspi_74hc595_4x_display - platform: spi_74hc595_display
cs_pin: GPIO5 cs_pin: GPIO5
data_rate: 10MHz data_rate: 1MHz #10MHz
update_interval: 10ms num_chips: 1 #3
spi_id: quad_spi_bus update_interval: 4s #16ms
common_cathode: false
segment_first: false
reverse: false
lambda: |- lambda: |-
static uint32_t last_micros=0;
static uint32_t clock_frames=0; static uint32_t clock_frames=0;
static uint32_t old_clock=0; static uint32_t old_clock=0;
if(id(sntp_time).now().second!=old_clock){ if(id(sntp_time).now().second!=old_clock){
last_micros = micros(); clock_frames=0;
old_clock=id(sntp_time).now().second; old_clock=id(sntp_time).now().second;
} else { } else {
clock_frames = (micros()-last_micros)/10000; clock_frames++;
} }
/////////// first display //it.printf(0, ".*`,_+'-");
it.printf(0, ".*`,_+'-"); it.printf(0, "88888888");
//it.strftime(0, "%H%M%s", id(sntp_time).now());
//it.printf(6, "%2d", clock_frames);
/////////// second display //it.strftime(8, "%H%M%s", id(sntp_time).now());
it.strftime(8, "%H%M%S", id(sntp_time).now()); //it.printf(14, "%2d", clock_frames);
it.printf(14, "%02d", clock_frames%100); /*it.strftime(8, "%H%M%s", id(sntp_time).now());*/
/*it.printf(14, "%1d", 0)*/
/////////// third display /*it.printf(16, "%02d%02d%02d%02d",
{ uint32_t(id(runtime).state/3600) - uint32_t(id(runtime).state/86400)*1440,
double delta = id(runtime).state - id(uptime_start); uint32_t(id(runtime).state/60) - uint32_t(id(runtime).state/3600)*60,
uint32_t hours = delta/3600; uint32_t(id(runtime).state) - uint32_t(id(runtime).state/60)*60,
delta -= hours*3600; uint32_t((id(runtime).state - uint32_t(id(runtime).state))*25) );
uint32_t minutes = delta/60; */
delta -= minutes*60;
uint32_t seconds = delta;
uint32_t frames = (100*delta) - (100*seconds);
it.printf(16, "%02d%02d%02d%02d", hours, minutes, seconds, frames);
}
/////////// fourth display
if (id(countdown_end) > id(sntp_time).timestamp_now()) {
time_t delta=difftime(id(countdown_end), id(sntp_time).timestamp_now());
time_t hours = delta/3600;
delta -= hours*3600;
time_t minutes = (delta)/60;
delta -= minutes*60;
time_t seconds = delta;
it.printf(24, "%02ld%02ld%02ld%02d", hours, minutes, seconds, (99-clock_frames%100));
}
# the first display should display a test string to figure out the digit and segment map: # the first display should display a test string to figure out the digit and segment map:
# digit 0: >.< character (dot segment on) # digit 0: >.< character (dot segment on)