From 3c9712d6838cc7376034a7a202f251ed32757177 Mon Sep 17 00:00:00 2001 From: phjr Date: Sun, 28 Jun 2020 00:08:15 +0200 Subject: [PATCH] add support for SN74HC595 shift register (#1083) * add support for SN74HC595 shift register * fix linter errors * more linter reported issues fixed * hopefully last linter error cleanup * one more linter fix * looks like the linter is always keeping stuff for later, is this the final fix? * add test Co-authored-by: Guillermo Ruffino --- esphome/components/sn74hc595/__init__.py | 54 +++++++++++++++++ esphome/components/sn74hc595/sn74hc595.cpp | 70 ++++++++++++++++++++++ esphome/components/sn74hc595/sn74hc595.h | 55 +++++++++++++++++ tests/test1.yaml | 22 ++++++- 4 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 esphome/components/sn74hc595/__init__.py create mode 100644 esphome/components/sn74hc595/sn74hc595.cpp create mode 100644 esphome/components/sn74hc595/sn74hc595.h diff --git a/esphome/components/sn74hc595/__init__.py b/esphome/components/sn74hc595/__init__.py new file mode 100644 index 0000000000..de80c38214 --- /dev/null +++ b/esphome/components/sn74hc595/__init__.py @@ -0,0 +1,54 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.const import CONF_ID, CONF_NUMBER, CONF_INVERTED, CONF_DATA_PIN, CONF_CLOCK_PIN + +DEPENDENCIES = [] +MULTI_CONF = True + +sn74hc595_ns = cg.esphome_ns.namespace('sn74hc595') + +SN74HC595Component = sn74hc595_ns.class_('SN74HC595Component', cg.Component) +SN74HC595GPIOPin = sn74hc595_ns.class_('SN74HC595GPIOPin', cg.GPIOPin) + +CONF_SN74HC595 = 'sn74hc595' +CONF_LATCH_PIN = 'latch_pin' +CONF_OE_PIN = 'oe_pin' +CONF_SR_COUNT = 'sr_count' +CONFIG_SCHEMA = cv.Schema({ + cv.Required(CONF_ID): cv.declare_id(SN74HC595Component), + cv.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_LATCH_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_OE_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_SR_COUNT, default=1): cv.int_range(1, 4) +}).extend(cv.COMPONENT_SCHEMA) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + data_pin = yield cg.gpio_pin_expression(config[CONF_DATA_PIN]) + cg.add(var.set_data_pin(data_pin)) + clock_pin = yield cg.gpio_pin_expression(config[CONF_CLOCK_PIN]) + cg.add(var.set_clock_pin(clock_pin)) + latch_pin = yield cg.gpio_pin_expression(config[CONF_LATCH_PIN]) + cg.add(var.set_latch_pin(latch_pin)) + oe_pin = yield cg.gpio_pin_expression(config[CONF_OE_PIN]) + cg.add(var.set_oe_pin(oe_pin)) + cg.add(var.set_sr_count(config[CONF_SR_COUNT])) + + +SN74HC595_OUTPUT_PIN_SCHEMA = cv.Schema({ + cv.Required(CONF_SN74HC595): cv.use_id(SN74HC595Component), + cv.Required(CONF_NUMBER): cv.int_, + cv.Optional(CONF_INVERTED, default=False): cv.boolean, +}) +SN74HC595_INPUT_PIN_SCHEMA = cv.Schema({}) + + +@pins.PIN_SCHEMA_REGISTRY.register(CONF_SN74HC595, + (SN74HC595_OUTPUT_PIN_SCHEMA, SN74HC595_INPUT_PIN_SCHEMA)) +def sn74hc595_pin_to_code(config): + parent = yield cg.get_variable(config[CONF_SN74HC595]) + yield SN74HC595GPIOPin.new(parent, config[CONF_NUMBER], config[CONF_INVERTED]) diff --git a/esphome/components/sn74hc595/sn74hc595.cpp b/esphome/components/sn74hc595/sn74hc595.cpp new file mode 100644 index 0000000000..edf8989149 --- /dev/null +++ b/esphome/components/sn74hc595/sn74hc595.cpp @@ -0,0 +1,70 @@ +#include "sn74hc595.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace sn74hc595 { + +static const char *TAG = "sn74hc595"; + +void SN74HC595Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up SN74HC595..."); + + if (this->have_oe_pin_) { // disable output + pinMode(this->oe_pin_->get_pin(), OUTPUT); + digitalWrite(this->oe_pin_->get_pin(), HIGH); + } + + // initialize output pins + pinMode(this->clock_pin_->get_pin(), OUTPUT); + pinMode(this->data_pin_->get_pin(), OUTPUT); + pinMode(this->latch_pin_->get_pin(), OUTPUT); + digitalWrite(this->clock_pin_->get_pin(), LOW); + digitalWrite(this->data_pin_->get_pin(), LOW); + digitalWrite(this->latch_pin_->get_pin(), LOW); + + // send state to shift register + this->write_gpio_(); +} + +void SN74HC595Component::dump_config() { ESP_LOGCONFIG(TAG, "SN74HC595:"); } + +bool SN74HC595Component::digital_read_(uint8_t pin) { return bitRead(this->output_bits_, pin); } + +void SN74HC595Component::digital_write_(uint8_t pin, bool value) { + bitWrite(this->output_bits_, pin, value); + this->write_gpio_(); +} + +bool SN74HC595Component::write_gpio_() { + for (int i = this->sr_count_ - 1; i >= 0; i--) { + uint8_t data = (uint8_t)(this->output_bits_ >> (8 * i) & 0xff); + shiftOut(this->data_pin_->get_pin(), this->clock_pin_->get_pin(), MSBFIRST, data); + } + + // pulse latch to activate new values + digitalWrite(this->latch_pin_->get_pin(), HIGH); + digitalWrite(this->latch_pin_->get_pin(), LOW); + + // enable output if configured + if (this->have_oe_pin_) { + digitalWrite(this->oe_pin_->get_pin(), LOW); + } + + return true; +} + +float SN74HC595Component::get_setup_priority() const { return setup_priority::IO; } + +void SN74HC595GPIOPin::setup() {} + +bool SN74HC595GPIOPin::digital_read() { return this->parent_->digital_read_(this->pin_) != this->inverted_; } + +void SN74HC595GPIOPin::digital_write(bool value) { + this->parent_->digital_write_(this->pin_, value != this->inverted_); +} + +SN74HC595GPIOPin::SN74HC595GPIOPin(SN74HC595Component *parent, uint8_t pin, bool inverted) + : GPIOPin(pin, OUTPUT, inverted), parent_(parent) {} + +} // namespace sn74hc595 +} // namespace esphome diff --git a/esphome/components/sn74hc595/sn74hc595.h b/esphome/components/sn74hc595/sn74hc595.h new file mode 100644 index 0000000000..d6f9a68bc8 --- /dev/null +++ b/esphome/components/sn74hc595/sn74hc595.h @@ -0,0 +1,55 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/esphal.h" + +namespace esphome { +namespace sn74hc595 { + +class SN74HC595Component : public Component { + public: + SN74HC595Component() = default; + + void setup() override; + float get_setup_priority() const override; + void dump_config() override; + + void set_data_pin(GPIOPin *pin) { data_pin_ = pin; } + void set_clock_pin(GPIOPin *pin) { clock_pin_ = pin; } + void set_latch_pin(GPIOPin *pin) { latch_pin_ = pin; } + void set_oe_pin(GPIOPin *pin) { + oe_pin_ = pin; + have_oe_pin_ = true; + } + void set_sr_count(uint8_t count) { sr_count_ = count; } + + protected: + friend class SN74HC595GPIOPin; + bool digital_read_(uint8_t pin); + void digital_write_(uint8_t pin, bool value); + bool write_gpio_(); + + GPIOPin *data_pin_; + GPIOPin *clock_pin_; + GPIOPin *latch_pin_; + GPIOPin *oe_pin_; + uint8_t sr_count_; + bool have_oe_pin_{false}; + uint32_t output_bits_{0x00}; +}; + +/// Helper class to expose a SC74HC595 pin as an internal output GPIO pin. +class SN74HC595GPIOPin : public GPIOPin { + public: + SN74HC595GPIOPin(SN74HC595Component *parent, uint8_t pin, bool inverted = false); + + void setup() override; + bool digital_read() override; + void digital_write(bool value) override; + + protected: + SN74HC595Component *parent_; +}; + +} // namespace sn74hc595 +} // namespace esphome diff --git a/tests/test1.yaml b/tests/test1.yaml index 5343d48970..8f06de2e40 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1187,13 +1187,13 @@ light: if (initial_run) { it[0] = current_color; } - + - wled: port: 11111 - + - adalight: uart_id: adalight_uart - + - automation: name: Custom Effect sequence: @@ -1465,6 +1465,14 @@ switch: id: my_stepper position: 0 + - platform: gpio + name: "SN74HC595 Pin #0" + pin: + sn74hc595: sn74hc595_hub + # Use pin number 0 + number: 0 + inverted: False + fan: - platform: binary output: gpio_26 @@ -1715,3 +1723,11 @@ text_sensor: name: "BSSID" mac_address: name: "Mac Address" + +sn74hc595: + - id: 'sn74hc595_hub' + data_pin: GPIO21 + clock_pin: GPIO23 + latch_pin: GPIO22 + oe_pin: GPIO32 + sr_count: 2