esphome/esphome/components/tm1638/tm1638.cpp
2022-09-12 08:30:15 -07:00

288 lines
7.9 KiB
C++

#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) {
this->send_command_((uint8_t)(TM1638_REGISTER_DISPLAYON | intensity_));
} 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