mirror of
https://github.com/esphome/esphome.git
synced 2024-11-29 10:14:13 +01:00
feat: Add support for MCP23016 IO Expander (#1012)
* feat: Add support for MCP23016 IO extander * fix: Fix style
This commit is contained in:
parent
835079ad43
commit
c1dfed5c08
4 changed files with 230 additions and 0 deletions
50
esphome/components/mcp23016/__init__.py
Normal file
50
esphome/components/mcp23016/__init__.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
DEPENDENCIES = ['i2c']
|
||||||
|
MULTI_CONF = True
|
||||||
|
|
||||||
|
mcp23016_ns = cg.esphome_ns.namespace('mcp23016')
|
||||||
|
MCP23016GPIOMode = mcp23016_ns.enum('MCP23016GPIOMode')
|
||||||
|
MCP23016_GPIO_MODES = {
|
||||||
|
'INPUT': MCP23016GPIOMode.MCP23016_INPUT,
|
||||||
|
'OUTPUT': MCP23016GPIOMode.MCP23016_OUTPUT,
|
||||||
|
}
|
||||||
|
|
||||||
|
MCP23016 = mcp23016_ns.class_('MCP23016', cg.Component, i2c.I2CDevice)
|
||||||
|
MCP23016GPIOPin = mcp23016_ns.class_('MCP23016GPIOPin', cg.GPIOPin)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.Schema({
|
||||||
|
cv.Required(CONF_ID): cv.declare_id(MCP23016),
|
||||||
|
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x20))
|
||||||
|
|
||||||
|
|
||||||
|
def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
yield cg.register_component(var, config)
|
||||||
|
yield i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
|
|
||||||
|
CONF_MCP23016 = 'mcp23016'
|
||||||
|
MCP23016_OUTPUT_PIN_SCHEMA = cv.Schema({
|
||||||
|
cv.Required(CONF_MCP23016): cv.use_id(MCP23016),
|
||||||
|
cv.Required(CONF_NUMBER): cv.int_,
|
||||||
|
cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum(MCP23016_GPIO_MODES, upper=True),
|
||||||
|
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
||||||
|
})
|
||||||
|
MCP23016_INPUT_PIN_SCHEMA = cv.Schema({
|
||||||
|
cv.Required(CONF_MCP23016): cv.use_id(MCP23016),
|
||||||
|
cv.Required(CONF_NUMBER): cv.int_,
|
||||||
|
cv.Optional(CONF_MODE, default="INPUT"): cv.enum(MCP23016_GPIO_MODES, upper=True),
|
||||||
|
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@pins.PIN_SCHEMA_REGISTRY.register(CONF_MCP23016,
|
||||||
|
(MCP23016_OUTPUT_PIN_SCHEMA, MCP23016_INPUT_PIN_SCHEMA))
|
||||||
|
def mcp23016_pin_to_code(config):
|
||||||
|
parent = yield cg.get_variable(config[CONF_MCP23016])
|
||||||
|
yield MCP23016GPIOPin.new(parent, config[CONF_NUMBER], config[CONF_MODE], config[CONF_INVERTED])
|
91
esphome/components/mcp23016/mcp23016.cpp
Normal file
91
esphome/components/mcp23016/mcp23016.cpp
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
#include "mcp23016.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace mcp23016 {
|
||||||
|
|
||||||
|
static const char *TAG = "mcp23016";
|
||||||
|
|
||||||
|
void MCP23016::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up MCP23016...");
|
||||||
|
uint8_t iocon;
|
||||||
|
if (!this->read_reg_(MCP23016_IOCON0, &iocon)) {
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// all pins input
|
||||||
|
this->write_reg_(MCP23016_IODIR0, 0xFF);
|
||||||
|
this->write_reg_(MCP23016_IODIR1, 0xFF);
|
||||||
|
}
|
||||||
|
bool MCP23016::digital_read(uint8_t pin) {
|
||||||
|
uint8_t bit = pin % 8;
|
||||||
|
uint8_t reg_addr = pin < 8 ? MCP23016_GP0 : MCP23016_GP1;
|
||||||
|
uint8_t value = 0;
|
||||||
|
this->read_reg_(reg_addr, &value);
|
||||||
|
return value & (1 << bit);
|
||||||
|
}
|
||||||
|
void MCP23016::digital_write(uint8_t pin, bool value) {
|
||||||
|
uint8_t reg_addr = pin < 8 ? MCP23016_OLAT0 : MCP23016_OLAT1;
|
||||||
|
this->update_reg_(pin, value, reg_addr);
|
||||||
|
}
|
||||||
|
void MCP23016::pin_mode(uint8_t pin, uint8_t mode) {
|
||||||
|
uint8_t iodir = pin < 8 ? MCP23016_IODIR0 : MCP23016_IODIR1;
|
||||||
|
switch (mode) {
|
||||||
|
case MCP23016_INPUT:
|
||||||
|
this->update_reg_(pin, true, iodir);
|
||||||
|
break;
|
||||||
|
case MCP23016_OUTPUT:
|
||||||
|
this->update_reg_(pin, false, iodir);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
float MCP23016::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||||
|
bool MCP23016::read_reg_(uint8_t reg, uint8_t *value) {
|
||||||
|
if (this->is_failed())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return this->read_byte(reg, value);
|
||||||
|
}
|
||||||
|
bool MCP23016::write_reg_(uint8_t reg, uint8_t value) {
|
||||||
|
if (this->is_failed())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return this->write_byte(reg, value);
|
||||||
|
}
|
||||||
|
void MCP23016::update_reg_(uint8_t pin, bool pin_value, uint8_t reg_addr) {
|
||||||
|
uint8_t bit = pin % 8;
|
||||||
|
uint8_t reg_value = 0;
|
||||||
|
if (reg_addr == MCP23016_OLAT0) {
|
||||||
|
reg_value = this->olat_0_;
|
||||||
|
} else if (reg_addr == MCP23016_OLAT1) {
|
||||||
|
reg_value = this->olat_1_;
|
||||||
|
} else {
|
||||||
|
this->read_reg_(reg_addr, ®_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pin_value)
|
||||||
|
reg_value |= 1 << bit;
|
||||||
|
else
|
||||||
|
reg_value &= ~(1 << bit);
|
||||||
|
|
||||||
|
this->write_reg_(reg_addr, reg_value);
|
||||||
|
|
||||||
|
if (reg_addr == MCP23016_OLAT0) {
|
||||||
|
this->olat_0_ = reg_value;
|
||||||
|
} else if (reg_addr == MCP23016_OLAT1) {
|
||||||
|
this->olat_1_ = reg_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MCP23016GPIOPin::MCP23016GPIOPin(MCP23016 *parent, uint8_t pin, uint8_t mode, bool inverted)
|
||||||
|
: GPIOPin(pin, mode, inverted), parent_(parent) {}
|
||||||
|
void MCP23016GPIOPin::setup() { this->pin_mode(this->mode_); }
|
||||||
|
void MCP23016GPIOPin::pin_mode(uint8_t mode) { this->parent_->pin_mode(this->pin_, mode); }
|
||||||
|
bool MCP23016GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
|
||||||
|
void MCP23016GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
|
||||||
|
|
||||||
|
} // namespace mcp23016
|
||||||
|
} // namespace esphome
|
71
esphome/components/mcp23016/mcp23016.h
Normal file
71
esphome/components/mcp23016/mcp23016.h
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/esphal.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace mcp23016 {
|
||||||
|
|
||||||
|
/// Modes for MCP23016 pins
|
||||||
|
enum MCP23016GPIOMode : uint8_t {
|
||||||
|
MCP23016_INPUT = INPUT, // 0x00
|
||||||
|
MCP23016_OUTPUT = OUTPUT // 0x01
|
||||||
|
};
|
||||||
|
|
||||||
|
enum MCP23016GPIORegisters {
|
||||||
|
// 0 side
|
||||||
|
MCP23016_GP0 = 0x00,
|
||||||
|
MCP23016_OLAT0 = 0x02,
|
||||||
|
MCP23016_IPOL0 = 0x04,
|
||||||
|
MCP23016_IODIR0 = 0x06,
|
||||||
|
MCP23016_INTCAP0 = 0x08,
|
||||||
|
MCP23016_IOCON0 = 0x0A,
|
||||||
|
// 1 side
|
||||||
|
MCP23016_GP1 = 0x01,
|
||||||
|
MCP23016_OLAT1 = 0x03,
|
||||||
|
MCP23016_IPOL1 = 0x04,
|
||||||
|
MCP23016_IODIR1 = 0x07,
|
||||||
|
MCP23016_INTCAP1 = 0x08,
|
||||||
|
MCP23016_IOCON1 = 0x0B,
|
||||||
|
};
|
||||||
|
|
||||||
|
class MCP23016 : public Component, public i2c::I2CDevice {
|
||||||
|
public:
|
||||||
|
MCP23016() = default;
|
||||||
|
|
||||||
|
void setup() override;
|
||||||
|
|
||||||
|
bool digital_read(uint8_t pin);
|
||||||
|
void digital_write(uint8_t pin, bool value);
|
||||||
|
void pin_mode(uint8_t pin, uint8_t mode);
|
||||||
|
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// read a given register
|
||||||
|
bool read_reg_(uint8_t reg, uint8_t *value);
|
||||||
|
// write a value to a given register
|
||||||
|
bool write_reg_(uint8_t reg, uint8_t value);
|
||||||
|
// update registers with given pin value.
|
||||||
|
void update_reg_(uint8_t pin, bool pin_value, uint8_t reg_a);
|
||||||
|
|
||||||
|
uint8_t olat_0_{0x00};
|
||||||
|
uint8_t olat_1_{0x00};
|
||||||
|
};
|
||||||
|
|
||||||
|
class MCP23016GPIOPin : public GPIOPin {
|
||||||
|
public:
|
||||||
|
MCP23016GPIOPin(MCP23016 *parent, uint8_t pin, uint8_t mode, bool inverted = false);
|
||||||
|
|
||||||
|
void setup() override;
|
||||||
|
void pin_mode(uint8_t mode) override;
|
||||||
|
bool digital_read() override;
|
||||||
|
void digital_write(bool value) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
MCP23016 *parent_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace mcp23016
|
||||||
|
} // namespace esphome
|
|
@ -867,6 +867,13 @@ binary_sensor:
|
||||||
number: 7
|
number: 7
|
||||||
mode: INPUT_PULLUP
|
mode: INPUT_PULLUP
|
||||||
inverted: False
|
inverted: False
|
||||||
|
- platform: gpio
|
||||||
|
name: "MCP23 binary sensor"
|
||||||
|
pin:
|
||||||
|
mcp23016: mcp23016_hub
|
||||||
|
number: 7
|
||||||
|
mode: INPUT
|
||||||
|
inverted: False
|
||||||
|
|
||||||
- platform: remote_receiver
|
- platform: remote_receiver
|
||||||
name: "Raw Remote Receiver Test"
|
name: "Raw Remote Receiver Test"
|
||||||
|
@ -990,6 +997,13 @@ output:
|
||||||
number: 0
|
number: 0
|
||||||
mode: OUTPUT
|
mode: OUTPUT
|
||||||
inverted: False
|
inverted: False
|
||||||
|
- platform: gpio
|
||||||
|
id: id25
|
||||||
|
pin:
|
||||||
|
mcp23016: mcp23016_hub
|
||||||
|
number: 0
|
||||||
|
mode: OUTPUT
|
||||||
|
inverted: False
|
||||||
- platform: my9231
|
- platform: my9231
|
||||||
id: my_0
|
id: my_0
|
||||||
channel: 0
|
channel: 0
|
||||||
|
@ -1569,6 +1583,10 @@ mcp23008:
|
||||||
- id: 'mcp23008_hub'
|
- id: 'mcp23008_hub'
|
||||||
address: 0x22
|
address: 0x22
|
||||||
|
|
||||||
|
mcp23016:
|
||||||
|
- id: 'mcp23016_hub'
|
||||||
|
address: 0x23
|
||||||
|
|
||||||
stepper:
|
stepper:
|
||||||
- platform: a4988
|
- platform: a4988
|
||||||
id: my_stepper
|
id: my_stepper
|
||||||
|
|
Loading…
Reference in a new issue