mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 05:24:53 +01:00
Add device support: MCP4728 (#3174)
* Added MCP4728 output component. * Added tests to test1.yaml * Added codeowners * Lint fixes * Implemented code review changes * Lint fixes * Added i2c communication check on setup() * Fixed tests * Lint fix * Update esphome/components/mcp4728/mcp4728_output.cpp Changed log function Co-authored-by: Otto Winter <otto@otto-winter.com> Co-authored-by: Otto Winter <otto@otto-winter.com>
This commit is contained in:
parent
6919930aaa
commit
d1feaa935d
6 changed files with 336 additions and 0 deletions
|
@ -108,6 +108,7 @@ esphome/components/mcp23x17_base/* @jesserockz
|
|||
esphome/components/mcp23xxx_base/* @jesserockz
|
||||
esphome/components/mcp2515/* @danielschramm @mvturnho
|
||||
esphome/components/mcp3204/* @rsumner
|
||||
esphome/components/mcp4728/* @berfenger
|
||||
esphome/components/mcp47a1/* @jesserockz
|
||||
esphome/components/mcp9808/* @k7hpn
|
||||
esphome/components/md5/* @esphome/core
|
||||
|
|
29
esphome/components/mcp4728/__init__.py
Normal file
29
esphome/components/mcp4728/__init__.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
CODEOWNERS = ["@berfenger"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
MULTI_CONF = True
|
||||
CONF_STORE_IN_EEPROM = "store_in_eeprom"
|
||||
|
||||
mcp4728_ns = cg.esphome_ns.namespace("mcp4728")
|
||||
MCP4728Component = mcp4728_ns.class_("MCP4728Component", cg.Component, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(MCP4728Component),
|
||||
cv.Optional(CONF_STORE_IN_EEPROM, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(i2c.i2c_device_schema(0x60))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID], config[CONF_STORE_IN_EEPROM])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
121
esphome/components/mcp4728/mcp4728_output.cpp
Normal file
121
esphome/components/mcp4728/mcp4728_output.cpp
Normal file
|
@ -0,0 +1,121 @@
|
|||
#include "mcp4728_output.h"
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mcp4728 {
|
||||
|
||||
static const char *const TAG = "mcp4728";
|
||||
|
||||
void MCP4728Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up MCP4728 (0x%02X)...", this->address_);
|
||||
auto err = this->write(nullptr, 0);
|
||||
if (err != i2c::ERROR_OK) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void MCP4728Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MCP4728:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with MCP4728 failed!");
|
||||
}
|
||||
}
|
||||
|
||||
void MCP4728Component::loop() {
|
||||
if (this->update_) {
|
||||
this->update_ = false;
|
||||
if (this->store_in_eeprom_) {
|
||||
if (!this->seq_write_()) {
|
||||
this->status_set_error();
|
||||
} else {
|
||||
this->status_clear_error();
|
||||
}
|
||||
} else {
|
||||
if (!this->multi_write_()) {
|
||||
this->status_set_error();
|
||||
} else {
|
||||
this->status_clear_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MCP4728Component::set_channel_value_(MCP4728ChannelIdx channel, uint16_t value) {
|
||||
uint8_t cn = 0;
|
||||
if (channel == MCP4728_CHANNEL_A) {
|
||||
cn = 'A';
|
||||
} else if (channel == MCP4728_CHANNEL_B) {
|
||||
cn = 'B';
|
||||
} else if (channel == MCP4728_CHANNEL_C) {
|
||||
cn = 'C';
|
||||
} else {
|
||||
cn = 'D';
|
||||
}
|
||||
ESP_LOGV(TAG, "Setting MCP4728 channel %c to %d!", cn, value);
|
||||
reg_[channel].data = value;
|
||||
this->update_ = true;
|
||||
}
|
||||
|
||||
bool MCP4728Component::multi_write_() {
|
||||
i2c::ErrorCode err[4];
|
||||
for (uint8_t i = 0; i < 4; ++i) {
|
||||
uint8_t wd[3];
|
||||
wd[0] = ((uint8_t) CMD::MULTI_WRITE | (i << 1)) & 0xFE;
|
||||
wd[1] = ((uint8_t) reg_[i].vref << 7) | ((uint8_t) reg_[i].pd << 5) | ((uint8_t) reg_[i].gain << 4) |
|
||||
(reg_[i].data >> 8);
|
||||
wd[2] = reg_[i].data & 0xFF;
|
||||
err[i] = this->write(wd, sizeof(wd));
|
||||
}
|
||||
bool ok = true;
|
||||
for (auto &e : err) {
|
||||
if (e != i2c::ERROR_OK) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool MCP4728Component::seq_write_() {
|
||||
uint8_t wd[9];
|
||||
wd[0] = (uint8_t) CMD::SEQ_WRITE;
|
||||
for (uint8_t i = 0; i < 4; i++) {
|
||||
wd[i * 2 + 1] = ((uint8_t) reg_[i].vref << 7) | ((uint8_t) reg_[i].pd << 5) | ((uint8_t) reg_[i].gain << 4) |
|
||||
(reg_[i].data >> 8);
|
||||
wd[i * 2 + 2] = reg_[i].data & 0xFF;
|
||||
}
|
||||
auto err = this->write(wd, sizeof(wd));
|
||||
return err == i2c::ERROR_OK;
|
||||
}
|
||||
|
||||
void MCP4728Component::select_vref_(MCP4728ChannelIdx channel, MCP4728Vref vref) {
|
||||
reg_[channel].vref = vref;
|
||||
|
||||
this->update_ = true;
|
||||
}
|
||||
|
||||
void MCP4728Component::select_power_down_(MCP4728ChannelIdx channel, MCP4728PwrDown pd) {
|
||||
reg_[channel].pd = pd;
|
||||
|
||||
this->update_ = true;
|
||||
}
|
||||
|
||||
void MCP4728Component::select_gain_(MCP4728ChannelIdx channel, MCP4728Gain gain) {
|
||||
reg_[channel].gain = gain;
|
||||
|
||||
this->update_ = true;
|
||||
}
|
||||
|
||||
void MCP4728Channel::write_state(float state) {
|
||||
const uint16_t max_duty = 4095;
|
||||
const float duty_rounded = roundf(state * max_duty);
|
||||
auto duty = static_cast<uint16_t>(duty_rounded);
|
||||
this->parent_->set_channel_value_(this->channel_, duty);
|
||||
}
|
||||
|
||||
} // namespace mcp4728
|
||||
} // namespace esphome
|
91
esphome/components/mcp4728/mcp4728_output.h
Normal file
91
esphome/components/mcp4728/mcp4728_output.h
Normal file
|
@ -0,0 +1,91 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mcp4728 {
|
||||
|
||||
enum class CMD {
|
||||
FAST_WRITE = 0x00,
|
||||
MULTI_WRITE = 0x40,
|
||||
SINGLE_WRITE = 0x58,
|
||||
SEQ_WRITE = 0x50,
|
||||
SELECT_VREF = 0x80,
|
||||
SELECT_GAIN = 0xC0,
|
||||
SELECT_POWER_DOWN = 0xA0
|
||||
};
|
||||
|
||||
enum MCP4728Vref { MCP4728_VREF_VDD = 0, MCP4728_VREF_INTERNAL_2_8V = 1 };
|
||||
|
||||
enum MCP4728PwrDown {
|
||||
MCP4728_PD_NORMAL = 0,
|
||||
MCP4728_PD_GND_1KOHM = 1,
|
||||
MCP4728_PD_GND_100KOHM = 2,
|
||||
MCP4728_PD_GND_500KOHM = 3
|
||||
};
|
||||
|
||||
enum MCP4728Gain { MCP4728_GAIN_X1 = 0, MCP4728_GAIN_X2 = 1 };
|
||||
|
||||
enum MCP4728ChannelIdx { MCP4728_CHANNEL_A = 0, MCP4728_CHANNEL_B = 1, MCP4728_CHANNEL_C = 2, MCP4728_CHANNEL_D = 3 };
|
||||
|
||||
struct DACInputData {
|
||||
MCP4728Vref vref;
|
||||
MCP4728PwrDown pd;
|
||||
MCP4728Gain gain;
|
||||
uint16_t data;
|
||||
};
|
||||
|
||||
class MCP4728Channel;
|
||||
|
||||
/// MCP4728 float output component.
|
||||
class MCP4728Component : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
MCP4728Component(bool store_in_eeprom) : store_in_eeprom_(store_in_eeprom) {}
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
void loop() override;
|
||||
|
||||
protected:
|
||||
friend MCP4728Channel;
|
||||
void set_channel_value_(MCP4728ChannelIdx channel, uint16_t value);
|
||||
bool multi_write_();
|
||||
bool seq_write_();
|
||||
void select_vref_(MCP4728ChannelIdx channel, MCP4728Vref vref);
|
||||
void select_power_down_(MCP4728ChannelIdx channel, MCP4728PwrDown pd);
|
||||
void select_gain_(MCP4728ChannelIdx channel, MCP4728Gain gain);
|
||||
|
||||
private:
|
||||
DACInputData reg_[4];
|
||||
bool store_in_eeprom_ = false;
|
||||
bool update_ = false;
|
||||
};
|
||||
|
||||
class MCP4728Channel : public output::FloatOutput {
|
||||
public:
|
||||
MCP4728Channel(MCP4728Component *parent, MCP4728ChannelIdx channel, MCP4728Vref vref, MCP4728Gain gain,
|
||||
MCP4728PwrDown pwrdown)
|
||||
: parent_(parent), channel_(channel), vref_(vref), gain_(gain), pwrdown_(pwrdown) {
|
||||
// update VREF
|
||||
parent->select_vref_(channel, vref_);
|
||||
// update PD
|
||||
parent->select_power_down_(channel, pwrdown_);
|
||||
// update GAIN
|
||||
parent->select_gain_(channel, gain_);
|
||||
}
|
||||
|
||||
protected:
|
||||
void write_state(float state) override;
|
||||
|
||||
MCP4728Component *parent_;
|
||||
MCP4728ChannelIdx channel_;
|
||||
MCP4728Vref vref_;
|
||||
MCP4728Gain gain_;
|
||||
MCP4728PwrDown pwrdown_;
|
||||
};
|
||||
|
||||
} // namespace mcp4728
|
||||
} // namespace esphome
|
63
esphome/components/mcp4728/output.py
Normal file
63
esphome/components/mcp4728/output.py
Normal file
|
@ -0,0 +1,63 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import output
|
||||
from esphome.const import CONF_CHANNEL, CONF_ID, CONF_GAIN
|
||||
from . import MCP4728Component, mcp4728_ns
|
||||
|
||||
DEPENDENCIES = ["mcp4728"]
|
||||
|
||||
MCP4728Channel = mcp4728_ns.class_("MCP4728Channel", output.FloatOutput)
|
||||
CONF_MCP4728_ID = "mcp4728_id"
|
||||
CONF_VREF = "vref"
|
||||
CONF_POWER_DOWN = "power_down"
|
||||
|
||||
MCP4728Vref = mcp4728_ns.enum("MCP4728Vref")
|
||||
VREF_OPTIONS = {
|
||||
"vdd": MCP4728Vref.MCP4728_VREF_VDD,
|
||||
"internal": MCP4728Vref.MCP4728_VREF_INTERNAL_2_8V,
|
||||
}
|
||||
|
||||
MCP4728Gain = mcp4728_ns.enum("MCP4728Gain")
|
||||
GAIN_OPTIONS = {"X1": MCP4728Gain.MCP4728_GAIN_X1, "X2": MCP4728Gain.MCP4728_GAIN_X2}
|
||||
|
||||
MCP4728PwrDown = mcp4728_ns.enum("MCP4728PwrDown")
|
||||
PWRDOWN_OPTIONS = {
|
||||
"normal": MCP4728PwrDown.MCP4728_PD_NORMAL,
|
||||
"gnd_1k": MCP4728PwrDown.MCP4728_PD_GND_1KOHM,
|
||||
"gnd_100k": MCP4728PwrDown.MCP4728_PD_GND_100KOHM,
|
||||
"gnd_500k": MCP4728PwrDown.MCP4728_PD_GND_500KOHM,
|
||||
}
|
||||
|
||||
MCP4728ChannelIdx = mcp4728_ns.enum("MCP4728ChannelIdx")
|
||||
CHANNEL_OPTIONS = {
|
||||
"A": MCP4728ChannelIdx.MCP4728_CHANNEL_A,
|
||||
"B": MCP4728ChannelIdx.MCP4728_CHANNEL_B,
|
||||
"C": MCP4728ChannelIdx.MCP4728_CHANNEL_C,
|
||||
"D": MCP4728ChannelIdx.MCP4728_CHANNEL_D,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(MCP4728Channel),
|
||||
cv.GenerateID(CONF_MCP4728_ID): cv.use_id(MCP4728Component),
|
||||
cv.Required(CONF_CHANNEL): cv.enum(CHANNEL_OPTIONS, upper=True),
|
||||
cv.Optional(CONF_VREF, default="vdd"): cv.enum(VREF_OPTIONS, upper=False),
|
||||
cv.Optional(CONF_POWER_DOWN, default="normal"): cv.enum(
|
||||
PWRDOWN_OPTIONS, upper=False
|
||||
),
|
||||
cv.Optional(CONF_GAIN, default="X1"): cv.enum(GAIN_OPTIONS, upper=True),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
paren = await cg.get_variable(config[CONF_MCP4728_ID])
|
||||
var = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
paren,
|
||||
config[CONF_CHANNEL],
|
||||
config[CONF_VREF],
|
||||
config[CONF_GAIN],
|
||||
config[CONF_POWER_DOWN],
|
||||
)
|
||||
await output.register_output(var, config)
|
|
@ -1501,6 +1501,28 @@ output:
|
|||
- platform: mcp4725
|
||||
id: mcp4725_dac_output
|
||||
i2c_id: i2c_bus
|
||||
- platform: mcp4728
|
||||
id: mcp4728_dac_output_a
|
||||
channel: A
|
||||
vref: vdd
|
||||
power_down: normal
|
||||
- platform: mcp4728
|
||||
id: mcp4728_dac_output_b
|
||||
channel: B
|
||||
vref: internal
|
||||
gain: X1
|
||||
power_down: gnd_1k
|
||||
- platform: mcp4728
|
||||
id: mcp4728_dac_output_c
|
||||
channel: C
|
||||
vref: vdd
|
||||
power_down: gnd_100k
|
||||
- platform: mcp4728
|
||||
id: mcp4728_dac_output_d
|
||||
channel: D
|
||||
vref: internal
|
||||
gain: X2
|
||||
power_down: gnd_500k
|
||||
|
||||
e131:
|
||||
|
||||
|
@ -2013,6 +2035,9 @@ switch:
|
|||
- output.set_level:
|
||||
id: mcp4725_dac_output
|
||||
level: !lambda "return 0.5;"
|
||||
- output.set_level:
|
||||
id: mcp4728_dac_output_a
|
||||
level: !lambda "return 0.5;"
|
||||
turn_off_action:
|
||||
- switch.turn_on: living_room_lights_off
|
||||
restore_state: False
|
||||
|
@ -2393,6 +2418,12 @@ rc522_i2c:
|
|||
ESP_LOGD("main", "Found tag %s", x.c_str());
|
||||
i2c_id: i2c_bus
|
||||
|
||||
mcp4728:
|
||||
- id: mcp4728_dac
|
||||
store_in_eeprom: False
|
||||
address: 0x60
|
||||
i2c_id: i2c_bus
|
||||
|
||||
gps:
|
||||
uart_id: uart0
|
||||
|
||||
|
|
Loading…
Reference in a new issue