From 5f2808ec2f8efb2735439ec78ed038232e218d5c Mon Sep 17 00:00:00 2001 From: Michiel van Turnhout Date: Mon, 14 Oct 2019 05:27:50 -0400 Subject: [PATCH] support for the sx1509 i2c device (#651) * added ANALOG_OUTPUT as first functionality * added gpio * seperated the code for different functions * fixed code * Revert "fixed code" This reverts commit 0c6eacb225522f7d738fa371d0db976a97f2f3e8. * add timings for breathe and blink * made the sx1509_float_output am output component * add keypad * implementation for sx1509 keypad * keypad code cleanup and first device tests * debounce * keypad working now. * update for timings. does not compile yet * added all options for breathe and blink fixed var namings * blink and breath still not ok * fixed ms for timings * sync with repo * fixed issue with gpio pin output * code cleanup * lint * more lint * remove log from header * Update esphome/components/sx1509/__init__.py Co-Authored-By: Otto Winter * review * feedback * fixed review issues did some extended testing with mqtt spy * code cleanup (comments) * fixed row col swap for binarysensor_keypad * flake and lint * travis * travis * travis * Update esphome/components/sx1509/sx1509.cpp Co-Authored-By: Otto Winter * review * separated platforms * code cleanup * travis relative paths in python * remove blink/breathe code cleanup * cpp lint * feedback * travis * lint line to long * check keypad settings to be valid * clang * keypad config * text * Remove wrong .gitignore from .gitignore * Remove .pio folder from .gitignore (merge) * Formatting * Formatting * Add i2c log in dump_config * Remove unused variables * Disable static for header files We don't need internal linkage * Use consistent member default argument style * Run clang-format Co-authored-by: null Co-authored-by: Otto Winter --- esphome/components/sx1509/__init__.py | 77 ++++++ .../sx1509/binary_sensor/__init__.py | 28 ++ .../sx1509_binary_keypad_sensor.h | 19 ++ esphome/components/sx1509/output/__init__.py | 25 ++ .../sx1509/output/sx1509_float_output.cpp | 30 +++ .../sx1509/output/sx1509_float_output.h | 27 ++ esphome/components/sx1509/sx1509.cpp | 253 ++++++++++++++++++ esphome/components/sx1509/sx1509.h | 89 ++++++ esphome/components/sx1509/sx1509_gpio_pin.cpp | 20 ++ esphome/components/sx1509/sx1509_gpio_pin.h | 24 ++ esphome/components/sx1509/sx1509_registers.h | 109 ++++++++ 11 files changed, 701 insertions(+) create mode 100644 esphome/components/sx1509/__init__.py create mode 100644 esphome/components/sx1509/binary_sensor/__init__.py create mode 100644 esphome/components/sx1509/binary_sensor/sx1509_binary_keypad_sensor.h create mode 100644 esphome/components/sx1509/output/__init__.py create mode 100644 esphome/components/sx1509/output/sx1509_float_output.cpp create mode 100644 esphome/components/sx1509/output/sx1509_float_output.h create mode 100644 esphome/components/sx1509/sx1509.cpp create mode 100644 esphome/components/sx1509/sx1509.h create mode 100644 esphome/components/sx1509/sx1509_gpio_pin.cpp create mode 100644 esphome/components/sx1509/sx1509_gpio_pin.h create mode 100644 esphome/components/sx1509/sx1509_registers.h diff --git a/esphome/components/sx1509/__init__.py b/esphome/components/sx1509/__init__.py new file mode 100644 index 0000000000..11fcfe3955 --- /dev/null +++ b/esphome/components/sx1509/__init__.py @@ -0,0 +1,77 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import i2c +from esphome.const import CONF_ID, CONF_NUMBER, CONF_MODE, CONF_INVERTED + +CONF_KEYPAD = 'keypad' +CONF_KEY_ROWS = 'key_rows' +CONF_KEY_COLUMNS = 'key_columns' +CONF_SLEEP_TIME = 'sleep_time' +CONF_SCAN_TIME = 'scan_time' +CONF_DEBOUNCE_TIME = 'debounce_time' + +DEPENDENCIES = ['i2c'] +MULTI_CONF = True + +sx1509_ns = cg.esphome_ns.namespace('sx1509') +SX1509GPIOMode = sx1509_ns.enum('SX1509GPIOMode') +SX1509_GPIO_MODES = { + 'INPUT': SX1509GPIOMode.SX1509_INPUT, + 'INPUT_PULLUP': SX1509GPIOMode.SX1509_INPUT_PULLUP, + 'OUTPUT': SX1509GPIOMode.SX1509_OUTPUT +} + +SX1509Component = sx1509_ns.class_('SX1509Component', cg.Component, i2c.I2CDevice) +SX1509GPIOPin = sx1509_ns.class_('SX1509GPIOPin', cg.GPIOPin) + +KEYPAD_SCHEMA = cv.Schema({ + cv.Required(CONF_KEY_ROWS): cv.int_range(min=1, max=8), + cv.Required(CONF_KEY_COLUMNS): cv.int_range(min=1, max=8), + cv.Optional(CONF_SLEEP_TIME): cv.int_range(min=128, max=8192), + cv.Optional(CONF_SCAN_TIME): cv.int_range(min=1, max=128), + cv.Optional(CONF_DEBOUNCE_TIME): cv.int_range(min=1, max=64), +}) + +CONFIG_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.declare_id(SX1509Component), + cv.Optional(CONF_KEYPAD): cv.Schema(KEYPAD_SCHEMA), +}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x3E)) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield i2c.register_i2c_device(var, config) + if CONF_KEYPAD in config: + keypad = config[CONF_KEYPAD] + cg.add(var.set_rows_cols(keypad[CONF_KEY_ROWS], keypad[CONF_KEY_COLUMNS])) + if CONF_SLEEP_TIME in keypad and CONF_SCAN_TIME in keypad and CONF_DEBOUNCE_TIME in keypad: + cg.add(var.set_sleep_time(keypad[CONF_SLEEP_TIME])) + cg.add(var.set_scan_time(keypad[CONF_SCAN_TIME])) + cg.add(var.set_debounce_time(keypad[CONF_DEBOUNCE_TIME])) + + +CONF_SX1509 = 'sx1509' +CONF_SX1509_ID = 'sx1509_id' + +SX1509_OUTPUT_PIN_SCHEMA = cv.Schema({ + cv.Required(CONF_SX1509): cv.use_id(SX1509Component), + cv.Required(CONF_NUMBER): cv.int_, + cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum(SX1509_GPIO_MODES, upper=True), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, +}) +SX1509_INPUT_PIN_SCHEMA = cv.Schema({ + cv.Required(CONF_SX1509): cv.use_id(SX1509Component), + cv.Required(CONF_NUMBER): cv.int_, + cv.Optional(CONF_MODE, default="INPUT"): cv.enum(SX1509_GPIO_MODES, upper=True), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, +}) + + +@pins.PIN_SCHEMA_REGISTRY.register(CONF_SX1509, + (SX1509_OUTPUT_PIN_SCHEMA, SX1509_INPUT_PIN_SCHEMA)) +def sx1509_pin_to_code(config): + parent = yield cg.get_variable(config[CONF_SX1509]) + yield SX1509GPIOPin.new(parent, config[CONF_NUMBER], config[CONF_MODE], + config[CONF_INVERTED]) diff --git a/esphome/components/sx1509/binary_sensor/__init__.py b/esphome/components/sx1509/binary_sensor/__init__.py new file mode 100644 index 0000000000..e780505edb --- /dev/null +++ b/esphome/components/sx1509/binary_sensor/__init__.py @@ -0,0 +1,28 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import CONF_ID +from .. import SX1509Component, sx1509_ns, CONF_SX1509_ID + +CONF_ROW = 'row' +CONF_COLUMN = 'col' + +DEPENDENCIES = ['sx1509'] + +SX1509BinarySensor = sx1509_ns.class_('SX1509BinarySensor', binary_sensor.BinarySensor) + +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(SX1509BinarySensor), + cv.GenerateID(CONF_SX1509_ID): cv.use_id(SX1509Component), + cv.Required(CONF_ROW): cv.int_range(min=0, max=4), + cv.Required(CONF_COLUMN): cv.int_range(min=0, max=4), +}) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield binary_sensor.register_binary_sensor(var, config) + hub = yield cg.get_variable(config[CONF_SX1509_ID]) + cg.add(var.set_row_col(config[CONF_ROW], config[CONF_COLUMN])) + + cg.add(hub.register_keypad_binary_sensor(var)) diff --git a/esphome/components/sx1509/binary_sensor/sx1509_binary_keypad_sensor.h b/esphome/components/sx1509/binary_sensor/sx1509_binary_keypad_sensor.h new file mode 100644 index 0000000000..2eef19782c --- /dev/null +++ b/esphome/components/sx1509/binary_sensor/sx1509_binary_keypad_sensor.h @@ -0,0 +1,19 @@ +#pragma once + +#include "esphome/components/sx1509/sx1509.h" +#include "esphome/components/binary_sensor/binary_sensor.h" + +namespace esphome { +namespace sx1509 { + +class SX1509BinarySensor : public sx1509::SX1509Processor, public binary_sensor::BinarySensor { + public: + void set_row_col(uint8_t row, uint8_t col) { this->key_ = (1 << (col + 8)) | (1 << row); } + void process(uint16_t data) override { this->publish_state(static_cast(data == key_)); } + + protected: + uint16_t key_{0}; +}; + +} // namespace sx1509 +} // namespace esphome diff --git a/esphome/components/sx1509/output/__init__.py b/esphome/components/sx1509/output/__init__.py new file mode 100644 index 0000000000..80aec0afd4 --- /dev/null +++ b/esphome/components/sx1509/output/__init__.py @@ -0,0 +1,25 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import output +from esphome.const import CONF_PIN, CONF_ID +from .. import SX1509Component, sx1509_ns, CONF_SX1509_ID + +DEPENDENCIES = ['sx1509'] + +SX1509FloatOutputChannel = sx1509_ns.class_('SX1509FloatOutputChannel', + output.FloatOutput, cg.Component) + +CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend({ + cv.Required(CONF_ID): cv.declare_id(SX1509FloatOutputChannel), + cv.GenerateID(CONF_SX1509_ID): cv.use_id(SX1509Component), + cv.Required(CONF_PIN): cv.int_range(min=0, max=15), +}).extend(cv.COMPONENT_SCHEMA) + + +def to_code(config): + parent = yield cg.get_variable(config[CONF_SX1509_ID]) + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield output.register_output(var, config) + cg.add(var.set_pin(config[CONF_PIN])) + cg.add(var.set_parent(parent)) diff --git a/esphome/components/sx1509/output/sx1509_float_output.cpp b/esphome/components/sx1509/output/sx1509_float_output.cpp new file mode 100644 index 0000000000..7ff1bbb61b --- /dev/null +++ b/esphome/components/sx1509/output/sx1509_float_output.cpp @@ -0,0 +1,30 @@ +#include "sx1509_float_output.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace sx1509 { + +static const char *TAG = "sx1509_float_channel"; + +void SX1509FloatOutputChannel::write_state(float state) { + const uint16_t max_duty = 255; + const float duty_rounded = roundf(state * max_duty); + auto duty = static_cast(duty_rounded); + this->parent_->set_pin_value(this->pin_, duty); +} + +void SX1509FloatOutputChannel::setup() { + ESP_LOGD(TAG, "setup pin %d", this->pin_); + this->parent_->pin_mode(this->pin_, SX1509_ANALOG_OUTPUT); + this->turn_off(); +} + +void SX1509FloatOutputChannel::dump_config() { + ESP_LOGCONFIG(TAG, "SX1509 PWM:"); + ESP_LOGCONFIG(TAG, " sx1509 pin: %d", this->pin_); + LOG_FLOAT_OUTPUT(this); +} + +} // namespace sx1509 +} // namespace esphome diff --git a/esphome/components/sx1509/output/sx1509_float_output.h b/esphome/components/sx1509/output/sx1509_float_output.h new file mode 100644 index 0000000000..39e51839ea --- /dev/null +++ b/esphome/components/sx1509/output/sx1509_float_output.h @@ -0,0 +1,27 @@ +#pragma once + +#include "esphome/components/sx1509/sx1509.h" +#include "esphome/components/output/float_output.h" + +namespace esphome { +namespace sx1509 { + +class SX1509Component; + +class SX1509FloatOutputChannel : public output::FloatOutput, public Component { + public: + void set_parent(SX1509Component *parent) { this->parent_ = parent; } + void set_pin(uint8_t pin) { pin_ = pin; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + protected: + void write_state(float state) override; + + SX1509Component *parent_; + uint8_t pin_; +}; + +} // namespace sx1509 +} // namespace esphome diff --git a/esphome/components/sx1509/sx1509.cpp b/esphome/components/sx1509/sx1509.cpp new file mode 100644 index 0000000000..2806a1cac2 --- /dev/null +++ b/esphome/components/sx1509/sx1509.cpp @@ -0,0 +1,253 @@ +#include "sx1509.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace sx1509 { + +static const char *TAG = "sx1509"; + +void SX1509Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up SX1509Component..."); + + ESP_LOGV(TAG, " Resetting devices..."); + if (!this->write_byte(REG_RESET, 0x12)) { + this->mark_failed(); + return; + } + this->write_byte(REG_RESET, 0x34); + + uint16_t data; + this->read_byte_16(REG_INTERRUPT_MASK_A, &data); + if (data == 0xFF00) { + clock_(INTERNAL_CLOCK_2MHZ); + } else { + this->mark_failed(); + return; + } + delayMicroseconds(500); + if (this->has_keypad_) + this->setup_keypad_(); +} + +void SX1509Component::dump_config() { + ESP_LOGCONFIG(TAG, "SX1509:"); + if (this->is_failed()) { + ESP_LOGE(TAG, "Setting up SX1509 failed!"); + } + LOG_I2C_DEVICE(this); +} + +void SX1509Component::loop() { + if (this->has_keypad_) { + uint16_t key_data = this->read_key_data(); + for (auto *binary_sensor : this->keypad_binary_sensors_) + binary_sensor->process(key_data); + } +} + +bool SX1509Component::digital_read(uint8_t pin) { + if (this->ddr_mask_ & (1 << pin)) { + uint16_t temp_reg_data; + this->read_byte_16(REG_DATA_B, &temp_reg_data); + if (temp_reg_data & (1 << pin)) + return true; + } + return false; +} + +void SX1509Component::digital_write(uint8_t pin, bool bit_value) { + if ((~this->ddr_mask_) & (1 << pin)) { + // If the pin is an output, write high/low + uint16_t temp_reg_data = 0; + this->read_byte_16(REG_DATA_B, &temp_reg_data); + if (bit_value) + temp_reg_data |= (1 << pin); + else + temp_reg_data &= ~(1 << pin); + this->write_byte_16(REG_DATA_B, temp_reg_data); + } else { + // Otherwise the pin is an input, pull-up/down + uint16_t temp_pullup; + this->read_byte_16(REG_PULL_UP_B, &temp_pullup); + uint16_t temp_pull_down; + this->read_byte_16(REG_PULL_DOWN_B, &temp_pull_down); + + if (bit_value) { + // if HIGH, do pull-up, disable pull-down + temp_pullup |= (1 << pin); + temp_pull_down &= ~(1 << pin); + this->write_byte_16(REG_PULL_UP_B, temp_pullup); + this->write_byte_16(REG_PULL_DOWN_B, temp_pull_down); + } else { + // If LOW do pull-down, disable pull-up + temp_pull_down |= (1 << pin); + temp_pullup &= ~(1 << pin); + this->write_byte_16(REG_PULL_UP_B, temp_pullup); + this->write_byte_16(REG_PULL_DOWN_B, temp_pull_down); + } + } +} + +void SX1509Component::pin_mode(uint8_t pin, uint8_t mode) { + this->read_byte_16(REG_DIR_B, &this->ddr_mask_); + if ((mode == SX1509_OUTPUT) || (mode == SX1509_ANALOG_OUTPUT)) + this->ddr_mask_ &= ~(1 << pin); + else + this->ddr_mask_ |= (1 << pin); + this->write_byte_16(REG_DIR_B, this->ddr_mask_); + + if (mode == INPUT_PULLUP) + digital_write(pin, HIGH); + + if (mode == SX1509_ANALOG_OUTPUT) { + setup_led_driver_(pin); + } +} + +void SX1509Component::setup_led_driver_(uint8_t pin) { + uint16_t temp_word; + uint8_t temp_byte; + + this->read_byte_16(REG_INPUT_DISABLE_B, &temp_word); + temp_word |= (1 << pin); + this->write_byte_16(REG_INPUT_DISABLE_B, temp_word); + + this->read_byte_16(REG_PULL_UP_B, &temp_word); + temp_word &= ~(1 << pin); + this->write_byte_16(REG_PULL_UP_B, temp_word); + + this->ddr_mask_ &= ~(1 << pin); // 0=output + this->write_byte_16(REG_DIR_B, this->ddr_mask_); + + this->read_byte(REG_CLOCK, &temp_byte); + temp_byte |= (1 << 6); // Internal 2MHz oscillator part 1 (set bit 6) + temp_byte &= ~(1 << 5); // Internal 2MHz oscillator part 2 (clear bit 5) + this->write_byte(REG_CLOCK, temp_byte); + + this->read_byte(REG_MISC, &temp_byte); + temp_byte &= ~(1 << 7); // set linear mode bank B + temp_byte &= ~(1 << 3); // set linear mode bank A + temp_byte |= 0x70; // Frequency of the LED Driver clock ClkX of all IOs: + this->write_byte(REG_MISC, temp_byte); + + this->read_byte_16(REG_LED_DRIVER_ENABLE_B, &temp_word); + temp_word |= (1 << pin); + this->write_byte_16(REG_LED_DRIVER_ENABLE_B, temp_word); + + this->read_byte_16(REG_DATA_B, &temp_word); + temp_word &= ~(1 << pin); + this->write_byte_16(REG_DATA_B, temp_word); +} + +void SX1509Component::clock_(byte osc_source, byte osc_pin_function, byte osc_freq_out, byte osc_divider) { + osc_source = (osc_source & 0b11) << 5; // 2-bit value, bits 6:5 + osc_pin_function = (osc_pin_function & 1) << 4; // 1-bit value bit 4 + osc_freq_out = (osc_freq_out & 0b1111); // 4-bit value, bits 3:0 + byte reg_clock = osc_source | osc_pin_function | osc_freq_out; + this->write_byte(REG_CLOCK, reg_clock); + + osc_divider = constrain(osc_divider, 1, 7); + this->clk_x_ = 2000000; + osc_divider = (osc_divider & 0b111) << 4; // 3-bit value, bits 6:4 + + uint8_t reg_misc; + this->read_byte(REG_MISC, ®_misc); + reg_misc &= ~(0b111 << 4); + reg_misc |= osc_divider; + this->write_byte(REG_MISC, reg_misc); +} + +void SX1509Component::setup_keypad_() { + uint8_t temp_byte; + + // setup row/col pins for INPUT OUTPUT + this->read_byte_16(REG_DIR_B, &this->ddr_mask_); + for (int i = 0; i < this->rows_; i++) + this->ddr_mask_ &= ~(1 << i); + for (int i = 8; i < (this->cols_ * 2); i++) + this->ddr_mask_ |= (1 << i); + this->write_byte_16(REG_DIR_B, this->ddr_mask_); + + this->read_byte(REG_OPEN_DRAIN_A, &temp_byte); + for (int i = 0; i < this->rows_; i++) + temp_byte |= (1 << i); + this->write_byte(REG_OPEN_DRAIN_A, temp_byte); + + this->read_byte(REG_PULL_UP_B, &temp_byte); + for (int i = 0; i < this->cols_; i++) + temp_byte |= (1 << i); + this->write_byte(REG_PULL_UP_B, temp_byte); + + if (debounce_time_ >= scan_time_) { + debounce_time_ = scan_time_ >> 1; // Force debounce_time to be less than scan_time + } + set_debounce_keypad_(debounce_time_, rows_, cols_); + uint8_t scan_time_bits = 0; + for (uint8_t i = 7; i > 0; i--) { + if (scan_time_ & (1 << i)) { + scan_time_bits = i; + break; + } + } + scan_time_bits &= 0b111; // Scan time is bits 2:0 + temp_byte = sleep_time_ | scan_time_bits; + this->write_byte(REG_KEY_CONFIG_1, temp_byte); + rows_ = (rows_ - 1) & 0b111; // 0 = off, 0b001 = 2 rows, 0b111 = 8 rows, etc. + cols_ = (cols_ - 1) & 0b111; // 0b000 = 1 column, ob111 = 8 columns, etc. + this->write_byte(REG_KEY_CONFIG_2, (rows_ << 3) | cols_); +} + +uint16_t SX1509Component::read_key_data() { + uint16_t key_data; + this->read_byte_16(REG_KEY_DATA_1, &key_data); + return (0xFFFF ^ key_data); +} + +void SX1509Component::set_debounce_config_(uint8_t config_value) { + // First make sure clock is configured + uint8_t temp_byte; + this->read_byte(REG_MISC, &temp_byte); + temp_byte |= (1 << 4); // Just default to no divider if not set + this->write_byte(REG_MISC, temp_byte); + this->read_byte(REG_CLOCK, &temp_byte); + temp_byte |= (1 << 6); // default to internal osc. + this->write_byte(REG_CLOCK, temp_byte); + + config_value &= 0b111; // 3-bit value + this->write_byte(REG_DEBOUNCE_CONFIG, config_value); +} + +void SX1509Component::set_debounce_time_(uint8_t time) { + uint8_t config_value = 0; + + for (int i = 7; i >= 0; i--) { + if (time & (1 << i)) { + config_value = i + 1; + break; + } + } + config_value = constrain(config_value, 0, 7); + + set_debounce_config_(config_value); +} + +void SX1509Component::set_debounce_enable_(uint8_t pin) { + uint16_t debounce_enable; + this->read_byte_16(REG_DEBOUNCE_ENABLE_B, &debounce_enable); + debounce_enable |= (1 << pin); + this->write_byte_16(REG_DEBOUNCE_ENABLE_B, debounce_enable); +} + +void SX1509Component::set_debounce_pin_(uint8_t pin) { set_debounce_enable_(pin); } + +void SX1509Component::set_debounce_keypad_(uint8_t time, uint8_t num_rows, uint8_t num_cols) { + set_debounce_time_(time); + for (uint16_t i = 0; i < num_rows; i++) + set_debounce_pin_(i); + for (uint16_t i = 0; i < (8 + num_cols); i++) + set_debounce_pin_(i); +} + +} // namespace sx1509 +} // namespace esphome diff --git a/esphome/components/sx1509/sx1509.h b/esphome/components/sx1509/sx1509.h new file mode 100644 index 0000000000..55d5e54091 --- /dev/null +++ b/esphome/components/sx1509/sx1509.h @@ -0,0 +1,89 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/core/component.h" +#include "sx1509_gpio_pin.h" +#include "sx1509_registers.h" + +namespace esphome { +namespace sx1509 { + +// These are used for clock config: +const uint8_t INTERNAL_CLOCK_2MHZ = 2; +const uint8_t EXTERNAL_CLOCK = 1; +const uint8_t SOFTWARE_RESET = 0; +const uint8_t HARDWARE_RESET = 1; + +const uint8_t ANALOG_OUTPUT = 0x03; // To set a pin mode for PWM output + +// PinModes for SX1509 pins +enum SX1509GPIOMode : uint8_t { + SX1509_INPUT = INPUT, // 0x00 + SX1509_INPUT_PULLUP = INPUT_PULLUP, // 0x02 + SX1509_ANALOG_OUTPUT = ANALOG_OUTPUT, // 0x03 + SX1509_OUTPUT = OUTPUT, // 0x01 +}; + +const uint8_t REG_I_ON[16] = {REG_I_ON_0, REG_I_ON_1, REG_I_ON_2, REG_I_ON_3, REG_I_ON_4, REG_I_ON_5, + REG_I_ON_6, REG_I_ON_7, REG_I_ON_8, REG_I_ON_9, REG_I_ON_10, REG_I_ON_11, + REG_I_ON_12, REG_I_ON_13, REG_I_ON_14, REG_I_ON_15}; + +// for all components that implement the process(uint16_t data ) +class SX1509Processor { + public: + virtual void process(uint16_t data){}; +}; + +class SX1509Component : public Component, public i2c::I2CDevice { + public: + SX1509Component() = default; + + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } + void loop() override; + + bool digital_read(uint8_t pin); + uint16_t read_key_data(); + void set_pin_value(uint8_t pin, uint8_t i_on) { this->write_byte(REG_I_ON[pin], i_on); }; + void pin_mode(uint8_t pin, uint8_t mode); + void digital_write(uint8_t pin, bool bit_value); + u_long get_clock() { return this->clk_x_; }; + void set_rows_cols(uint8_t rows, uint8_t cols) { + this->rows_ = rows; + this->cols_ = cols; + this->has_keypad_ = true; + }; + void set_sleep_time(uint16_t sleep_time) { this->sleep_time_ = sleep_time; }; + void set_scan_time(uint8_t scan_time) { this->scan_time_ = scan_time; }; + void set_debounce_time(uint8_t debounce_time = 1) { this->debounce_time_ = debounce_time; }; + void register_keypad_binary_sensor(SX1509Processor *binary_sensor) { + this->keypad_binary_sensors_.push_back(binary_sensor); + }; + + protected: + u_long clk_x_ = 2000000; + uint8_t frequency_ = 0; + uint16_t ddr_mask_ = 0x00; + uint16_t input_mask_ = 0x00; + uint16_t port_mask_ = 0x00; + bool has_keypad_ = false; + uint8_t rows_ = 0; + uint8_t cols_ = 0; + uint16_t sleep_time_ = 128; + uint8_t scan_time_ = 1; + uint8_t debounce_time_ = 1; + std::vector keypad_binary_sensors_; + + void setup_keypad_(); + void set_debounce_config_(uint8_t config_value); + void set_debounce_time_(uint8_t time); + void set_debounce_pin_(uint8_t pin); + void set_debounce_enable_(uint8_t pin); + void set_debounce_keypad_(uint8_t time, uint8_t num_rows, uint8_t num_cols); + void setup_led_driver_(uint8_t pin); + void clock_(uint8_t osc_source = 2, uint8_t osc_pin_function = 1, uint8_t osc_freq_out = 0, uint8_t osc_divider = 0); +}; + +} // namespace sx1509 +} // namespace esphome diff --git a/esphome/components/sx1509/sx1509_gpio_pin.cpp b/esphome/components/sx1509/sx1509_gpio_pin.cpp new file mode 100644 index 0000000000..1d1c87b4e6 --- /dev/null +++ b/esphome/components/sx1509/sx1509_gpio_pin.cpp @@ -0,0 +1,20 @@ +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "sx1509_gpio_pin.h" + +namespace esphome { +namespace sx1509 { + +static const char *TAG = "sx1509_gpio_pin"; + +void SX1509GPIOPin::setup() { + ESP_LOGD(TAG, "setup pin %d", this->pin_); + this->parent_->pin_mode(this->pin_, this->mode_); +} + +void SX1509GPIOPin::pin_mode(uint8_t mode) { this->parent_->pin_mode(this->pin_, mode); } +bool SX1509GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } +void SX1509GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } + +} // namespace sx1509 +} // namespace esphome diff --git a/esphome/components/sx1509/sx1509_gpio_pin.h b/esphome/components/sx1509/sx1509_gpio_pin.h new file mode 100644 index 0000000000..39f841a2a4 --- /dev/null +++ b/esphome/components/sx1509/sx1509_gpio_pin.h @@ -0,0 +1,24 @@ +#pragma once + +#include "sx1509.h" + +namespace esphome { +namespace sx1509 { + +class SX1509Component; + +class SX1509GPIOPin : public GPIOPin { + public: + SX1509GPIOPin(SX1509Component *parent, uint8_t pin, uint8_t mode, bool inverted = false) + : GPIOPin(pin, mode, inverted), parent_(parent){}; + void setup() override; + void pin_mode(uint8_t mode) override; + bool digital_read() override; + void digital_write(bool value) override; + + protected: + SX1509Component *parent_; +}; + +} // namespace sx1509 +} // namespace esphome diff --git a/esphome/components/sx1509/sx1509_registers.h b/esphome/components/sx1509/sx1509_registers.h new file mode 100644 index 0000000000..d73f397f16 --- /dev/null +++ b/esphome/components/sx1509/sx1509_registers.h @@ -0,0 +1,109 @@ +/****************************************************************************** +sx1509_registers.h +Register definitions for SX1509. +Jim Lindblom @ SparkFun Electronics +Original Creation Date: September 21, 2015 +https://github.com/sparkfun/SparkFun_SX1509_Arduino_Library + +Here you'll find the Arduino code used to interface with the SX1509 I2C +16 I/O expander. There are functions to take advantage of everything the +SX1509 provides - input/output setting, writing pins high/low, reading +the input value of pins, LED driver utilities (blink, breath, pwm), and +keypad engine utilites. + +Development environment specifics: + IDE: Arduino 1.6.5 + Hardware Platform: Arduino Uno + SX1509 Breakout Version: v2.0 + +This code is beerware; if you see me (or any other SparkFun employee) at the +local, and you've found our code helpful, please buy us a round! + +Distributed as-is; no warranty is given. +******************************************************************************/ +#pragma once + +namespace esphome { +namespace sx1509 { + +const uint8_t REG_INPUT_DISABLE_B = + 0x00; // RegInputDisableB Input buffer disable register _ I/O[15_8] (Bank B) 0000 0000 +const uint8_t REG_INPUT_DISABLE_A = + 0x01; // RegInputDisableA Input buffer disable register _ I/O[7_0] (Bank A) 0000 0000 +const uint8_t REG_LONG_SLEW_B = + 0x02; // RegLongSlewB Output buffer long slew register _ I/O[15_8] (Bank B) 0000 0000 +const uint8_t REG_LONG_SLEW_A = 0x03; // RegLongSlewA Output buffer long slew register _ I/O[7_0] (Bank A) 0000 0000 +const uint8_t REG_LOW_DRIVE_B = + 0x04; // RegLowDriveB Output buffer low drive register _ I/O[15_8] (Bank B) 0000 0000 +const uint8_t REG_LOW_DRIVE_A = 0x05; // RegLowDriveA Output buffer low drive register _ I/O[7_0] (Bank A) 0000 0000 +const uint8_t REG_PULL_UP_B = 0x06; // RegPullUpB Pull_up register _ I/O[15_8] (Bank B) 0000 0000 +const uint8_t REG_PULL_UP_A = 0x07; // RegPullUpA Pull_up register _ I/O[7_0] (Bank A) 0000 0000 +const uint8_t REG_PULL_DOWN_B = 0x08; // RegPullDownB Pull_down register _ I/O[15_8] (Bank B) 0000 0000 +const uint8_t REG_PULL_DOWN_A = 0x09; // RegPullDownA Pull_down register _ I/O[7_0] (Bank A) 0000 0000 +const uint8_t REG_OPEN_DRAIN_B = 0x0A; // RegOpenDrainB Open drain register _ I/O[15_8] (Bank B) 0000 0000 +const uint8_t REG_OPEN_DRAIN_A = 0x0B; // RegOpenDrainA Open drain register _ I/O[7_0] (Bank A) 0000 0000 +const uint8_t REG_POLARITY_B = 0x0C; // RegPolarityB Polarity register _ I/O[15_8] (Bank B) 0000 0000 +const uint8_t REG_POLARITY_A = 0x0D; // RegPolarityA Polarity register _ I/O[7_0] (Bank A) 0000 0000 +const uint8_t REG_DIR_B = 0x0E; // RegDirB Direction register _ I/O[15_8] (Bank B) 1111 1111 +const uint8_t REG_DIR_A = 0x0F; // RegDirA Direction register _ I/O[7_0] (Bank A) 1111 1111 +const uint8_t REG_DATA_B = 0x10; // RegDataB Data register _ I/O[15_8] (Bank B) 1111 1111* +const uint8_t REG_DATA_A = 0x11; // RegDataA Data register _ I/O[7_0] (Bank A) 1111 1111* +const uint8_t REG_INTERRUPT_MASK_B = + 0x12; // RegInterruptMaskB Interrupt mask register _ I/O[15_8] (Bank B) 1111 1111 +const uint8_t REG_INTERRUPT_MASK_A = + 0x13; // RegInterruptMaskA Interrupt mask register _ I/O[7_0] (Bank A) 1111 1111 +const uint8_t REG_SENSE_HIGH_B = 0x14; // RegSenseHighB Sense register for I/O[15:12] 0000 0000 +const uint8_t REG_SENSE_LOW_B = 0x15; // RegSenseLowB Sense register for I/O[11:8] 0000 0000 +const uint8_t REG_SENSE_HIGH_A = 0x16; // RegSenseHighA Sense register for I/O[7:4] 0000 0000 +const uint8_t REG_SENSE_LOW_A = 0x17; // RegSenseLowA Sense register for I/O[3:0] 0000 0000 +const uint8_t REG_INTERRUPT_SOURCE_B = + 0x18; // RegInterruptSourceB Interrupt source register _ I/O[15_8] (Bank B) 0000 0000 +const uint8_t REG_INTERRUPT_SOURCE_A = + 0x19; // RegInterruptSourceA Interrupt source register _ I/O[7_0] (Bank A) 0000 0000 +const uint8_t REG_EVENT_STATUS_B = 0x1A; // RegEventStatusB Event status register _ I/O[15_8] (Bank B) 0000 0000 +const uint8_t REG_EVENT_STATUS_A = 0x1B; // RegEventStatusA Event status register _ I/O[7_0] (Bank A) 0000 0000 +const uint8_t REG_LEVEL_SHIFTER_1 = 0x1C; // RegLevelShifter1 Level shifter register 0000 0000 +const uint8_t REG_LEVEL_SHIFTER_2 = 0x1D; // RegLevelShifter2 Level shifter register 0000 0000 +const uint8_t REG_CLOCK = 0x1E; // RegClock Clock management register 0000 0000 +const uint8_t REG_MISC = 0x1F; // RegMisc Miscellaneous device settings register 0000 0000 +const uint8_t REG_LED_DRIVER_ENABLE_B = + 0x20; // RegLEDDriverEnableB LED driver enable register _ I/O[15_8] (Bank B) 0000 0000 +const uint8_t REG_LED_DRIVER_ENABLE_A = + 0x21; // RegLEDDriverEnableA LED driver enable register _ I/O[7_0] (Bank A) 0000 0000 +// Debounce and Keypad Engine +const uint8_t REG_DEBOUNCE_CONFIG = 0x22; // RegDebounceConfig Debounce configuration register 0000 0000 +const uint8_t REG_DEBOUNCE_ENABLE_B = + 0x23; // RegDebounceEnableB Debounce enable register _ I/O[15_8] (Bank B) 0000 0000 +const uint8_t REG_DEBOUNCE_ENABLE_A = + 0x24; // RegDebounceEnableA Debounce enable register _ I/O[7_0] (Bank A) 0000 0000 +const uint8_t REG_KEY_CONFIG_1 = 0x25; // RegKeyConfig1 Key scan configuration register 0000 0000 +const uint8_t REG_KEY_CONFIG_2 = 0x26; // RegKeyConfig2 Key scan configuration register 0000 0000 +const uint8_t REG_KEY_DATA_1 = 0x27; // RegKeyData1 Key value (column) 1111 1111 +const uint8_t REG_KEY_DATA_2 = 0x28; // RegKeyData2 Key value (row) 1111 1111 +// LED Driver (PWM, blinking, breathing) +const uint8_t REG_I_ON_0 = 0x2A; // RegIOn0 ON intensity register for I/O[0] 1111 1111 +const uint8_t REG_I_ON_1 = 0x2D; // RegIOn1 ON intensity register for I/O[1] 1111 1111 +const uint8_t REG_I_ON_2 = 0x30; // RegIOn2 ON intensity register for I/O[2] 1111 1111 +const uint8_t REG_I_ON_3 = 0x33; // RegIOn3 ON intensity register for I/O[3] 1111 1111 +const uint8_t REG_I_ON_4 = 0x36; // RegIOn4 ON intensity register for I/O[4] 1111 1111 +const uint8_t REG_I_ON_5 = 0x3B; // RegIOn5 ON intensity register for I/O[5] 1111 1111 +const uint8_t REG_I_ON_6 = 0x40; // RegIOn6 ON intensity register for I/O[6] 1111 1111 +const uint8_t REG_I_ON_7 = 0x45; // RegIOn7 ON intensity register for I/O[7] 1111 1111 +const uint8_t REG_I_ON_8 = 0x4A; // RegIOn8 ON intensity register for I/O[8] 1111 1111 +const uint8_t REG_I_ON_9 = 0x4D; // RegIOn9 ON intensity register for I/O[9] 1111 1111 +const uint8_t REG_I_ON_10 = 0x50; // RegIOn10 ON intensity register for I/O[10] 1111 1111 +const uint8_t REG_I_ON_11 = 0x53; // RegIOn11 ON intensity register for I/O[11] 1111 1111 +const uint8_t REG_I_ON_12 = 0x56; // RegIOn12 ON intensity register for I/O[12] 1111 1111 +const uint8_t REG_I_ON_13 = 0x5B; // RegIOn13 ON intensity register for I/O[13] 1111 1111 +const uint8_t REG_I_ON_14 = 0x60; // RegIOn14 ON intensity register for I/O[14] 1111 1111 +const uint8_t REG_I_ON_15 = 0x65; // RegIOn15 ON intensity register for I/O[15] 1111 1111 +// Miscellaneous +const uint8_t REG_HIGH_INPUT_B = 0x69; // RegHighInputB High input enable register _ I/O[15_8] (Bank B) 0000 0000 +const uint8_t REG_HIGH_INPUT_A = 0x6A; // RegHighInputA High input enable register _ I/O[7_0] (Bank A) 0000 0000 +// Software Reset +const uint8_t REG_RESET = 0x7D; // RegReset Software reset register 0000 0000 +const uint8_t REG_TEST_1 = 0x7E; // RegTest1 Test register 0000 0000 +const uint8_t REG_TEST_2 = 0x7F; // RegTest2 Test register 0000 0000 + +} // namespace sx1509 +} // namespace esphome