mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00: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:
		| @@ -108,6 +108,7 @@ esphome/components/mcp23x17_base/* @jesserockz | |||||||
| esphome/components/mcp23xxx_base/* @jesserockz | esphome/components/mcp23xxx_base/* @jesserockz | ||||||
| esphome/components/mcp2515/* @danielschramm @mvturnho | esphome/components/mcp2515/* @danielschramm @mvturnho | ||||||
| esphome/components/mcp3204/* @rsumner | esphome/components/mcp3204/* @rsumner | ||||||
|  | esphome/components/mcp4728/* @berfenger | ||||||
| esphome/components/mcp47a1/* @jesserockz | esphome/components/mcp47a1/* @jesserockz | ||||||
| esphome/components/mcp9808/* @k7hpn | esphome/components/mcp9808/* @k7hpn | ||||||
| esphome/components/md5/* @esphome/core | 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 |   - platform: mcp4725 | ||||||
|     id: mcp4725_dac_output |     id: mcp4725_dac_output | ||||||
|     i2c_id: i2c_bus |     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: | e131: | ||||||
|  |  | ||||||
| @@ -2013,6 +2035,9 @@ switch: | |||||||
|       - output.set_level: |       - output.set_level: | ||||||
|           id: mcp4725_dac_output |           id: mcp4725_dac_output | ||||||
|           level: !lambda "return 0.5;" |           level: !lambda "return 0.5;" | ||||||
|  |       - output.set_level: | ||||||
|  |           id: mcp4728_dac_output_a | ||||||
|  |           level: !lambda "return 0.5;" | ||||||
|     turn_off_action: |     turn_off_action: | ||||||
|       - switch.turn_on: living_room_lights_off |       - switch.turn_on: living_room_lights_off | ||||||
|     restore_state: False |     restore_state: False | ||||||
| @@ -2393,6 +2418,12 @@ rc522_i2c: | |||||||
|           ESP_LOGD("main", "Found tag %s", x.c_str()); |           ESP_LOGD("main", "Found tag %s", x.c_str()); | ||||||
|     i2c_id: i2c_bus |     i2c_id: i2c_bus | ||||||
|  |  | ||||||
|  | mcp4728: | ||||||
|  |   - id: mcp4728_dac | ||||||
|  |     store_in_eeprom: False | ||||||
|  |     address: 0x60 | ||||||
|  |     i2c_id: i2c_bus | ||||||
|  |  | ||||||
| gps: | gps: | ||||||
|   uart_id: uart0 |   uart_id: uart0 | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user