2022-09-12 17:30:15 +02:00
|
|
|
#include "tm1638.h"
|
|
|
|
#include "sevenseg.h"
|
|
|
|
#include "esphome/core/log.h"
|
|
|
|
#include "esphome/core/helpers.h"
|
|
|
|
#include "esphome/core/hal.h"
|
|
|
|
|
|
|
|
namespace esphome {
|
|
|
|
namespace tm1638 {
|
|
|
|
|
|
|
|
static const char *const TAG = "display.tm1638";
|
|
|
|
static const uint8_t TM1638_REGISTER_FIXEDADDRESS = 0x44;
|
|
|
|
static const uint8_t TM1638_REGISTER_AUTOADDRESS = 0x40;
|
|
|
|
static const uint8_t TM1638_REGISTER_READBUTTONS = 0x42;
|
|
|
|
static const uint8_t TM1638_REGISTER_DISPLAYOFF = 0x80;
|
|
|
|
static const uint8_t TM1638_REGISTER_DISPLAYON = 0x88;
|
|
|
|
static const uint8_t TM1638_REGISTER_7SEG_0 = 0xC0;
|
|
|
|
static const uint8_t TM1638_REGISTER_LED_0 = 0xC1;
|
|
|
|
static const uint8_t TM1638_UNKNOWN_CHAR = 0b11111111;
|
|
|
|
|
|
|
|
static const uint8_t TM1638_SHIFT_DELAY = 4; // clock pause between commands, default 4ms
|
|
|
|
|
|
|
|
void TM1638Component::setup() {
|
|
|
|
ESP_LOGD(TAG, "Setting up TM1638...");
|
|
|
|
|
|
|
|
this->clk_pin_->setup(); // OUTPUT
|
|
|
|
this->dio_pin_->setup(); // OUTPUT
|
|
|
|
this->stb_pin_->setup(); // OUTPUT
|
|
|
|
|
|
|
|
this->clk_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
|
|
|
this->dio_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
|
|
|
this->stb_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
|
|
|
|
|
|
|
this->clk_pin_->digital_write(false);
|
|
|
|
this->dio_pin_->digital_write(false);
|
|
|
|
this->stb_pin_->digital_write(false);
|
|
|
|
|
|
|
|
this->set_intensity(intensity_);
|
|
|
|
|
|
|
|
this->reset_(); // all LEDs off
|
|
|
|
|
|
|
|
for (uint8_t i = 0; i < 8; i++) // zero fill print buffer
|
|
|
|
this->buffer_[i] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TM1638Component::dump_config() {
|
|
|
|
ESP_LOGCONFIG(TAG, "TM1638:");
|
|
|
|
ESP_LOGCONFIG(TAG, " Intensity: %u", this->intensity_);
|
|
|
|
LOG_PIN(" CLK Pin: ", this->clk_pin_);
|
|
|
|
LOG_PIN(" DIO Pin: ", this->dio_pin_);
|
|
|
|
LOG_PIN(" STB Pin: ", this->stb_pin_);
|
|
|
|
LOG_UPDATE_INTERVAL(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TM1638Component::loop() {
|
|
|
|
if (this->listeners_.empty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
uint8_t keys = this->get_keys();
|
|
|
|
for (auto &listener : this->listeners_)
|
|
|
|
listener->keys_update(keys);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t TM1638Component::get_keys() {
|
|
|
|
uint8_t buttons = 0;
|
|
|
|
|
|
|
|
this->stb_pin_->digital_write(false);
|
|
|
|
|
|
|
|
this->shift_out_(TM1638_REGISTER_READBUTTONS);
|
|
|
|
|
|
|
|
this->dio_pin_->pin_mode(gpio::FLAG_INPUT);
|
|
|
|
|
|
|
|
delayMicroseconds(10);
|
|
|
|
|
|
|
|
for (uint8_t i = 0; i < 4; i++) { // read the 4 button registers
|
|
|
|
uint8_t v = this->shift_in_();
|
|
|
|
buttons |= v << i; // shift bits to correct slots in the byte
|
|
|
|
}
|
|
|
|
|
|
|
|
this->dio_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
|
|
|
|
|
|
|
this->stb_pin_->digital_write(true);
|
|
|
|
|
|
|
|
return buttons;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TM1638Component::update() { // this is called at the interval specified in the config.yaml
|
|
|
|
if (this->writer_.has_value()) {
|
|
|
|
(*this->writer_)(*this);
|
|
|
|
}
|
|
|
|
|
|
|
|
this->display();
|
|
|
|
}
|
|
|
|
|
|
|
|
float TM1638Component::get_setup_priority() const { return setup_priority::PROCESSOR; }
|
|
|
|
|
|
|
|
void TM1638Component::display() {
|
|
|
|
for (uint8_t i = 0; i < 8; i++) {
|
|
|
|
this->set_7seg_(i, buffer_[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TM1638Component::reset_() {
|
|
|
|
uint8_t num_commands = 16; // 16 addresses, 8 for 7seg and 8 for LEDs
|
|
|
|
uint8_t commands[num_commands];
|
|
|
|
|
|
|
|
for (uint8_t i = 0; i < num_commands; i++) {
|
|
|
|
commands[i] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
this->send_command_sequence_(commands, num_commands, TM1638_REGISTER_7SEG_0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////// LEDs /////////////////
|
|
|
|
|
|
|
|
void TM1638Component::set_led(int led_pos, bool led_on_off) {
|
|
|
|
this->send_command_(TM1638_REGISTER_FIXEDADDRESS);
|
|
|
|
|
|
|
|
uint8_t commands[2];
|
|
|
|
|
|
|
|
commands[0] = TM1638_REGISTER_LED_0 + (led_pos << 1);
|
|
|
|
commands[1] = led_on_off;
|
|
|
|
|
|
|
|
this->send_commands_(commands, 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TM1638Component::set_7seg_(int seg_pos, uint8_t seg_bits) {
|
|
|
|
this->send_command_(TM1638_REGISTER_FIXEDADDRESS);
|
|
|
|
|
|
|
|
uint8_t commands[2] = {};
|
|
|
|
|
|
|
|
commands[0] = TM1638_REGISTER_7SEG_0 + (seg_pos << 1);
|
|
|
|
commands[1] = seg_bits;
|
|
|
|
|
|
|
|
this->send_commands_(commands, 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TM1638Component::set_intensity(uint8_t brightness_level) {
|
|
|
|
this->intensity_ = brightness_level;
|
|
|
|
|
|
|
|
this->send_command_(TM1638_REGISTER_FIXEDADDRESS);
|
|
|
|
|
|
|
|
if (brightness_level > 0) {
|
2023-03-20 04:38:41 +01:00
|
|
|
this->send_command_((uint8_t) (TM1638_REGISTER_DISPLAYON | intensity_));
|
2022-09-12 17:30:15 +02:00
|
|
|
} else {
|
|
|
|
this->send_command_(TM1638_REGISTER_DISPLAYOFF);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////// DISPLAY PRINT /////////////////
|
|
|
|
|
|
|
|
uint8_t TM1638Component::print(uint8_t start_pos, const char *str) {
|
|
|
|
uint8_t pos = start_pos;
|
|
|
|
|
|
|
|
bool last_was_dot = false;
|
|
|
|
|
|
|
|
for (; *str != '\0'; str++) {
|
|
|
|
uint8_t data = TM1638_UNKNOWN_CHAR;
|
|
|
|
|
|
|
|
if (*str >= ' ' && *str <= '~') {
|
|
|
|
data = progmem_read_byte(&TM1638Translation::SEVEN_SEG[*str - 32]); // subract 32 to account for ASCII offset
|
|
|
|
} else if (data == TM1638_UNKNOWN_CHAR) {
|
|
|
|
ESP_LOGW(TAG, "Encountered character '%c' with no TM1638 representation while translating string!", *str);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*str == '.') // handle dots
|
|
|
|
{
|
|
|
|
if (pos != start_pos &&
|
|
|
|
!last_was_dot) // if we are not at the first position, backup by one unless last char was a dot
|
|
|
|
{
|
|
|
|
pos--;
|
|
|
|
}
|
|
|
|
this->buffer_[pos] |= 0b10000000; // turn on the dot on the previous position
|
|
|
|
last_was_dot = true; // set a bit in case the next chracter is also a dot
|
|
|
|
} else // if not a dot, then just write the character to display
|
|
|
|
{
|
|
|
|
if (pos >= 8) {
|
|
|
|
ESP_LOGI(TAG, "TM1638 String is too long for the display!");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
this->buffer_[pos] = data;
|
|
|
|
last_was_dot = false; // clear dot tracking bit
|
|
|
|
}
|
|
|
|
|
|
|
|
pos++;
|
|
|
|
}
|
|
|
|
return pos - start_pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////// PRINT /////////////////
|
|
|
|
|
|
|
|
uint8_t TM1638Component::print(const char *str) { return this->print(0, str); }
|
|
|
|
|
|
|
|
uint8_t TM1638Component::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 TM1638Component::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;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef USE_TIME
|
|
|
|
uint8_t TM1638Component::strftime(uint8_t pos, const char *format, time::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 TM1638Component::strftime(const char *format, time::ESPTime time) { return this->strftime(0, format, time); }
|
|
|
|
#endif
|
|
|
|
|
|
|
|
//////////////// SPI ////////////////
|
|
|
|
|
|
|
|
void TM1638Component::send_command_(uint8_t value) {
|
|
|
|
this->stb_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
|
|
|
this->stb_pin_->digital_write(false);
|
|
|
|
this->shift_out_(value);
|
|
|
|
this->stb_pin_->digital_write(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TM1638Component::send_commands_(uint8_t const commands[], uint8_t num_commands) {
|
|
|
|
this->stb_pin_->digital_write(false);
|
|
|
|
|
|
|
|
for (uint8_t i = 0; i < num_commands; i++) {
|
|
|
|
uint8_t command = commands[i];
|
|
|
|
this->shift_out_(command);
|
|
|
|
}
|
|
|
|
this->stb_pin_->digital_write(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TM1638Component::send_command_leave_open_(uint8_t value) {
|
|
|
|
this->stb_pin_->digital_write(false);
|
|
|
|
this->shift_out_(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TM1638Component::send_command_sequence_(uint8_t commands[], uint8_t num_commands, uint8_t starting_address) {
|
|
|
|
this->send_command_(TM1638_REGISTER_AUTOADDRESS);
|
|
|
|
this->send_command_leave_open_(starting_address);
|
|
|
|
|
|
|
|
for (uint8_t i = 0; i < num_commands; i++) {
|
|
|
|
this->shift_out_(commands[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
this->stb_pin_->digital_write(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t TM1638Component::shift_in_() {
|
|
|
|
uint8_t value = 0;
|
|
|
|
|
|
|
|
for (int i = 0; i < 8; ++i) {
|
|
|
|
value |= dio_pin_->digital_read() << i;
|
|
|
|
delayMicroseconds(TM1638_SHIFT_DELAY);
|
|
|
|
this->clk_pin_->digital_write(true);
|
|
|
|
delayMicroseconds(TM1638_SHIFT_DELAY);
|
|
|
|
this->clk_pin_->digital_write(false);
|
|
|
|
delayMicroseconds(TM1638_SHIFT_DELAY);
|
|
|
|
}
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TM1638Component::shift_out_(uint8_t val) {
|
|
|
|
for (int i = 0; i < 8; i++) {
|
|
|
|
this->dio_pin_->digital_write((val & (1 << i)));
|
|
|
|
delayMicroseconds(TM1638_SHIFT_DELAY);
|
|
|
|
|
|
|
|
this->clk_pin_->digital_write(true);
|
|
|
|
delayMicroseconds(TM1638_SHIFT_DELAY);
|
|
|
|
|
|
|
|
this->clk_pin_->digital_write(false);
|
|
|
|
delayMicroseconds(TM1638_SHIFT_DELAY);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace tm1638
|
|
|
|
} // namespace esphome
|