mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Add support for PCA9535 16 bit I/O expander (#5634)
This commit is contained in:
		| @@ -217,7 +217,7 @@ esphome/components/number/* @esphome/core | ||||
| esphome/components/ota/* @esphome/core | ||||
| esphome/components/output/* @esphome/core | ||||
| esphome/components/pca6416a/* @Mat931 | ||||
| esphome/components/pca9554/* @hwstar | ||||
| esphome/components/pca9554/* @clydebarrow @hwstar | ||||
| esphome/components/pcf85063/* @brogon | ||||
| esphome/components/pcf8563/* @KoenBreeman | ||||
| esphome/components/pid/* @OttoWinter | ||||
|   | ||||
| @@ -10,10 +10,12 @@ from esphome.const import ( | ||||
|     CONF_INVERTED, | ||||
|     CONF_OUTPUT, | ||||
| ) | ||||
| from esphome.core import CORE, ID | ||||
|  | ||||
| CODEOWNERS = ["@hwstar"] | ||||
| CODEOWNERS = ["@hwstar", "@clydebarrow"] | ||||
| DEPENDENCIES = ["i2c"] | ||||
| MULTI_CONF = True | ||||
| CONF_PIN_COUNT = "pin_count" | ||||
| pca9554_ns = cg.esphome_ns.namespace("pca9554") | ||||
|  | ||||
| PCA9554Component = pca9554_ns.class_("PCA9554Component", cg.Component, i2c.I2CDevice) | ||||
| @@ -23,7 +25,12 @@ PCA9554GPIOPin = pca9554_ns.class_( | ||||
|  | ||||
| CONF_PCA9554 = "pca9554" | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema({cv.Required(CONF_ID): cv.declare_id(PCA9554Component)}) | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.Required(CONF_ID): cv.declare_id(PCA9554Component), | ||||
|             cv.Optional(CONF_PIN_COUNT, default=8): cv.one_of(4, 8, 16), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
|     .extend( | ||||
|         i2c.i2c_device_schema(0x20) | ||||
| @@ -33,6 +40,7 @@ CONFIG_SCHEMA = ( | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     cg.add(var.set_pin_count(config[CONF_PIN_COUNT])) | ||||
|     await cg.register_component(var, config) | ||||
|     await i2c.register_i2c_device(var, config) | ||||
|  | ||||
| @@ -45,11 +53,32 @@ def validate_mode(value): | ||||
|     return value | ||||
|  | ||||
|  | ||||
| def validate_pin(config): | ||||
|     pca_id = config[CONF_PCA9554].id | ||||
|  | ||||
|     # when pin config validation is performed, the entire YAML has been read, but depending on the component order, | ||||
|     # the pca9554 component may not yet have been processed, so its id property could be either the original string, | ||||
|     # or an ID object. | ||||
|     def matcher(p): | ||||
|         id = p[CONF_ID] | ||||
|         if isinstance(id, ID): | ||||
|             return id.id == pca_id | ||||
|         return id == pca_id | ||||
|  | ||||
|     pca = list(filter(matcher, CORE.raw_config[CONF_PCA9554])) | ||||
|     if not pca: | ||||
|         raise cv.Invalid(f"No pca9554 component found with id matching {pca_id}") | ||||
|     count = pca[0][CONF_PIN_COUNT] | ||||
|     if config[CONF_NUMBER] >= count: | ||||
|         raise cv.Invalid(f"Pin number must be in range 0-{count - 1}") | ||||
|     return config | ||||
|  | ||||
|  | ||||
| PCA9554_PIN_SCHEMA = cv.All( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(PCA9554GPIOPin), | ||||
|         cv.Required(CONF_PCA9554): cv.use_id(PCA9554Component), | ||||
|         cv.Required(CONF_NUMBER): cv.int_range(min=0, max=8), | ||||
|         cv.Required(CONF_NUMBER): cv.int_range(min=0, max=15), | ||||
|         cv.Optional(CONF_MODE, default={}): cv.All( | ||||
|             { | ||||
|                 cv.Optional(CONF_INPUT, default=False): cv.boolean, | ||||
| @@ -58,7 +87,8 @@ PCA9554_PIN_SCHEMA = cv.All( | ||||
|             validate_mode, | ||||
|         ), | ||||
|         cv.Optional(CONF_INVERTED, default=False): cv.boolean, | ||||
|     } | ||||
|     }, | ||||
|     validate_pin, | ||||
| ) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
| namespace esphome { | ||||
| namespace pca9554 { | ||||
|  | ||||
| // for 16 bit expanders, these addresses will be doubled. | ||||
| const uint8_t INPUT_REG = 0; | ||||
| const uint8_t OUTPUT_REG = 1; | ||||
| const uint8_t INVERT_REG = 2; | ||||
| @@ -13,9 +14,10 @@ static const char *const TAG = "pca9554"; | ||||
|  | ||||
| void PCA9554Component::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up PCA9554/PCA9554A..."); | ||||
|   this->reg_width_ = (this->pin_count_ + 7) / 8; | ||||
|   // Test to see if device exists | ||||
|   if (!this->read_inputs_()) { | ||||
|     ESP_LOGE(TAG, "PCA9554 not available under 0x%02X", this->address_); | ||||
|     ESP_LOGE(TAG, "PCA95xx not detected at 0x%02X", this->address_); | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
| @@ -44,6 +46,7 @@ void PCA9554Component::loop() { | ||||
|  | ||||
| void PCA9554Component::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "PCA9554:"); | ||||
|   ESP_LOGCONFIG(TAG, "  I/O Pins: %d", this->pin_count_); | ||||
|   LOG_I2C_DEVICE(this) | ||||
|   if (this->is_failed()) { | ||||
|     ESP_LOGE(TAG, "Communication with PCA9554 failed!"); | ||||
| @@ -85,25 +88,33 @@ void PCA9554Component::pin_mode(uint8_t pin, gpio::Flags flags) { | ||||
| } | ||||
|  | ||||
| bool PCA9554Component::read_inputs_() { | ||||
|   uint8_t inputs; | ||||
|   uint8_t inputs[2]; | ||||
|  | ||||
|   if (this->is_failed()) { | ||||
|     ESP_LOGD(TAG, "Device marked failed"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   if ((this->last_error_ = this->read_register(INPUT_REG, &inputs, 1, true)) != esphome::i2c::ERROR_OK) { | ||||
|   if ((this->last_error_ = this->read_register(INPUT_REG * this->reg_width_, inputs, this->reg_width_, true)) != | ||||
|       esphome::i2c::ERROR_OK) { | ||||
|     this->status_set_warning(); | ||||
|     ESP_LOGE(TAG, "read_register_(): I2C I/O error: %d", (int) this->last_error_); | ||||
|     return false; | ||||
|   } | ||||
|   this->status_clear_warning(); | ||||
|   this->input_mask_ = inputs; | ||||
|   this->input_mask_ = inputs[0]; | ||||
|   if (this->reg_width_ == 2) { | ||||
|     this->input_mask_ |= inputs[1] << 8; | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| bool PCA9554Component::write_register_(uint8_t reg, uint8_t value) { | ||||
|   if ((this->last_error_ = this->write_register(reg, &value, 1, true)) != esphome::i2c::ERROR_OK) { | ||||
| bool PCA9554Component::write_register_(uint8_t reg, uint16_t value) { | ||||
|   uint8_t outputs[2]; | ||||
|   outputs[0] = (uint8_t) value; | ||||
|   outputs[1] = (uint8_t) (value >> 8); | ||||
|   if ((this->last_error_ = this->write_register(reg * this->reg_width_, outputs, this->reg_width_, true)) != | ||||
|       esphome::i2c::ERROR_OK) { | ||||
|     this->status_set_warning(); | ||||
|     ESP_LOGE(TAG, "write_register_(): I2C I/O error: %d", (int) this->last_error_); | ||||
|     return false; | ||||
|   | ||||
| @@ -28,19 +28,25 @@ class PCA9554Component : public Component, public i2c::I2CDevice { | ||||
|  | ||||
|   void dump_config() override; | ||||
|  | ||||
|   void set_pin_count(size_t pin_count) { this->pin_count_ = pin_count; } | ||||
|  | ||||
|  protected: | ||||
|   bool read_inputs_(); | ||||
|  | ||||
|   bool write_register_(uint8_t reg, uint8_t value); | ||||
|   bool write_register_(uint8_t reg, uint16_t value); | ||||
|  | ||||
|   /// number of bits the expander has | ||||
|   size_t pin_count_{8}; | ||||
|   /// width of registers | ||||
|   size_t reg_width_{1}; | ||||
|   /// Mask for the pin config - 1 means OUTPUT, 0 means INPUT | ||||
|   uint8_t config_mask_{0x00}; | ||||
|   uint16_t config_mask_{0x00}; | ||||
|   /// The mask to write as output state - 1 means HIGH, 0 means LOW | ||||
|   uint8_t output_mask_{0x00}; | ||||
|   uint16_t output_mask_{0x00}; | ||||
|   /// The state of the actual input pin states - 1 means HIGH, 0 means LOW | ||||
|   uint8_t input_mask_{0x00}; | ||||
|   uint16_t input_mask_{0x00}; | ||||
|   /// Flags to check if read previously during this loop | ||||
|   uint8_t was_previously_read_ = {0x00}; | ||||
|   uint16_t was_previously_read_ = {0x00}; | ||||
|   /// Storage for last I2C error seen | ||||
|   esphome::i2c::ErrorCode last_error_; | ||||
| }; | ||||
|   | ||||
| @@ -3182,6 +3182,7 @@ pcf8574: | ||||
|  | ||||
| pca9554: | ||||
|   - id: pca9554_hub | ||||
|     pin_count: 8 | ||||
|     address: 0x3F | ||||
|     i2c_id: i2c_bus | ||||
|  | ||||
| @@ -3331,7 +3332,12 @@ rtttl: | ||||
| canbus: | ||||
|   - platform: mcp2515 | ||||
|     id: mcp2515_can | ||||
|     cs_pin: GPIO17 | ||||
|     cs_pin: | ||||
|       pca9554: pca9554_hub | ||||
|       number: 7 | ||||
|       mode: | ||||
|         output: true | ||||
|       inverted: true | ||||
|     can_id: 4 | ||||
|     bit_rate: 50kbps | ||||
|     on_frame: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user