mirror of
https://github.com/esphome/esphome.git
synced 2025-09-06 21:32:21 +01:00
ESP-IDF support and generic target platforms (#2303)
* Socket refactor and SSL * esp-idf temp * Fixes * Echo component and noise * Add noise API transport support * Updates * ESP-IDF * Complete * Fixes * Fixes * Versions update * New i2c APIs * Complete i2c refactor * SPI migration * Revert ESP Preferences migration, too complex for now * OTA support * Remove echo again * Remove ssl again * GPIOFlags updates * Rename esphal and ICACHE_RAM_ATTR * Make ESP32 arduino compilable again * Fix GPIO flags * Complete pin registry refactor and fixes * Fixes to make test1 compile * Remove sdkconfig file * Ignore sdkconfig file * Fixes in reviewing * Make test2 compile * Make test4 compile * Make test5 compile * Run clang-format * Fix lint errors * Use esp-idf APIs instead of btStart * Another round of fixes * Start implementing ESP8266 * Make test3 compile * Guard esp8266 code * Lint * Reformat * Fixes * Fixes v2 * more fixes * ESP-IDF tidy target * Convert ARDUINO_ARCH_ESPxx * Update WiFiSignalSensor * Update time ifdefs * OTA needs millis from hal * RestartSwitch needs delay from hal * ESP-IDF Uart * Fix OTA blank password * Allow setting sdkconfig * Fix idf partitions and allow setting sdkconfig from yaml * Re-add read/write compat APIs and fix esp8266 uart * Fix esp8266 store log strings in flash * Fix ESP32 arduino preferences not initialized * Update ifdefs * Change how sdkconfig change is detected * Add checks to ci-custom and fix them * Run clang-format * Add esp-idf clang-tidy target and fix errors * Fixes from clang-tidy idf round 2 * Fixes from compiling tests with esp-idf * Run clang-format * Switch test5.yaml to esp-idf * Implement ESP8266 Preferences * Lint * Re-do PIO package version selection a bit * Fix arduinoespressif32 package version * Fix unit tests * Lint * Lint fixes * Fix readv/writev not defined * Fix graphing component * Re-add all old options from core/config.py Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
@@ -2,30 +2,56 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.const import (
|
||||
CONF_CHANNEL,
|
||||
CONF_FREQUENCY,
|
||||
CONF_ID,
|
||||
CONF_INPUT,
|
||||
CONF_OUTPUT,
|
||||
CONF_SCAN,
|
||||
CONF_SCL,
|
||||
CONF_SDA,
|
||||
CONF_ADDRESS,
|
||||
CONF_I2C_ID,
|
||||
CONF_MULTIPLEXER,
|
||||
)
|
||||
from esphome.core import coroutine_with_priority
|
||||
from esphome.core import coroutine_with_priority, CORE
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
i2c_ns = cg.esphome_ns.namespace("i2c")
|
||||
I2CComponent = i2c_ns.class_("I2CComponent", cg.Component)
|
||||
I2CBus = i2c_ns.class_("I2CBus")
|
||||
ArduinoI2CBus = i2c_ns.class_("ArduinoI2CBus", I2CBus, cg.Component)
|
||||
IDFI2CBus = i2c_ns.class_("IDFI2CBus", I2CBus, cg.Component)
|
||||
I2CDevice = i2c_ns.class_("I2CDevice")
|
||||
I2CMultiplexer = i2c_ns.class_("I2CMultiplexer", I2CDevice)
|
||||
|
||||
|
||||
CONF_SDA_PULLUP_ENABLED = "sda_pullup_enabled"
|
||||
CONF_SCL_PULLUP_ENABLED = "scl_pullup_enabled"
|
||||
MULTI_CONF = True
|
||||
|
||||
|
||||
def _bus_declare_type(value):
|
||||
if CORE.using_arduino:
|
||||
return cv.declare_id(ArduinoI2CBus)(value)
|
||||
if CORE.using_esp_idf:
|
||||
return cv.declare_id(IDFI2CBus)(value)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
pin_with_input_and_output_support = cv.All(
|
||||
pins.internal_gpio_pin_number({CONF_INPUT: True}),
|
||||
pins.internal_gpio_pin_number({CONF_OUTPUT: True}),
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(I2CComponent),
|
||||
cv.Optional(CONF_SDA, default="SDA"): pins.input_pin,
|
||||
cv.Optional(CONF_SCL, default="SCL"): pins.input_pin,
|
||||
cv.GenerateID(): _bus_declare_type,
|
||||
cv.Optional(CONF_SDA, default="SDA"): pin_with_input_and_output_support,
|
||||
cv.SplitDefault(CONF_SDA_PULLUP_ENABLED, esp32_idf=True): cv.All(
|
||||
cv.only_with_esp_idf, cv.boolean
|
||||
),
|
||||
cv.Optional(CONF_SCL, default="SCL"): pin_with_input_and_output_support,
|
||||
cv.SplitDefault(CONF_SCL_PULLUP_ENABLED, esp32_idf=True): cv.All(
|
||||
cv.only_with_esp_idf, cv.boolean
|
||||
),
|
||||
cv.Optional(CONF_FREQUENCY, default="50kHz"): cv.All(
|
||||
cv.frequency, cv.Range(min=0, min_included=False)
|
||||
),
|
||||
@@ -33,13 +59,6 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
I2CMULTIPLEXER_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(I2CMultiplexer),
|
||||
cv.Required(CONF_CHANNEL): cv.uint8_t,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(1.0)
|
||||
async def to_code(config):
|
||||
@@ -48,10 +67,16 @@ async def to_code(config):
|
||||
await cg.register_component(var, config)
|
||||
|
||||
cg.add(var.set_sda_pin(config[CONF_SDA]))
|
||||
if CONF_SDA_PULLUP_ENABLED in config:
|
||||
cg.add(var.set_sda_pullup_enabled(config[CONF_SDA_PULLUP_ENABLED]))
|
||||
cg.add(var.set_scl_pin(config[CONF_SCL]))
|
||||
if CONF_SCL_PULLUP_ENABLED in config:
|
||||
cg.add(var.set_scl_pullup_enabled(config[CONF_SCL_PULLUP_ENABLED]))
|
||||
|
||||
cg.add(var.set_frequency(int(config[CONF_FREQUENCY])))
|
||||
cg.add(var.set_scan(config[CONF_SCAN]))
|
||||
cg.add_library("Wire", None)
|
||||
if CORE.using_arduino:
|
||||
cg.add_library("Wire", None)
|
||||
|
||||
|
||||
def i2c_device_schema(default_address):
|
||||
@@ -62,8 +87,11 @@ def i2c_device_schema(default_address):
|
||||
:return: The i2c device schema, `extend` this in your config schema.
|
||||
"""
|
||||
schema = {
|
||||
cv.GenerateID(CONF_I2C_ID): cv.use_id(I2CComponent),
|
||||
cv.Optional(CONF_MULTIPLEXER): I2CMULTIPLEXER_SCHEMA,
|
||||
cv.GenerateID(CONF_I2C_ID): cv.use_id(I2CBus),
|
||||
cv.Optional("multiplexer"): cv.invalid(
|
||||
"This option has been removed, please see "
|
||||
"the tca9584a docs for the updated way to use multiplexers"
|
||||
),
|
||||
}
|
||||
if default_address is None:
|
||||
schema[cv.Required(CONF_ADDRESS)] = cv.i2c_address
|
||||
@@ -80,10 +108,5 @@ async def register_i2c_device(var, config):
|
||||
This is a coroutine, you need to await it with a 'yield' expression!
|
||||
"""
|
||||
parent = await cg.get_variable(config[CONF_I2C_ID])
|
||||
cg.add(var.set_i2c_parent(parent))
|
||||
cg.add(var.set_i2c_bus(parent))
|
||||
cg.add(var.set_i2c_address(config[CONF_ADDRESS]))
|
||||
if CONF_MULTIPLEXER in config:
|
||||
multiplexer = await cg.get_variable(config[CONF_MULTIPLEXER][CONF_ID])
|
||||
cg.add(
|
||||
var.set_i2c_multiplexer(multiplexer, config[CONF_MULTIPLEXER][CONF_CHANNEL])
|
||||
)
|
||||
|
@@ -1,303 +1,40 @@
|
||||
#include "i2c.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include <memory>
|
||||
|
||||
namespace esphome {
|
||||
namespace i2c {
|
||||
|
||||
static const char *const TAG = "i2c";
|
||||
|
||||
I2CComponent::I2CComponent() {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
static uint8_t next_i2c_bus_num = 0;
|
||||
if (next_i2c_bus_num == 0)
|
||||
this->wire_ = &Wire;
|
||||
else
|
||||
this->wire_ = new TwoWire(next_i2c_bus_num); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
next_i2c_bus_num++;
|
||||
#else
|
||||
this->wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer)
|
||||
#endif
|
||||
bool I2CDevice::write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len) {
|
||||
// we have to copy in order to be able to change byte order
|
||||
std::unique_ptr<uint16_t[]> temp{new uint16_t[len]};
|
||||
for (size_t i = 0; i < len; i++)
|
||||
temp[i] = htoi2cs(data[i]);
|
||||
return write_register(a_register, reinterpret_cast<const uint8_t *>(data), len * 2) == ERROR_OK;
|
||||
}
|
||||
|
||||
void I2CComponent::setup() {
|
||||
this->wire_->begin(this->sda_pin_, this->scl_pin_);
|
||||
this->wire_->setClock(this->frequency_);
|
||||
}
|
||||
void I2CComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "I2C Bus:");
|
||||
ESP_LOGCONFIG(TAG, " SDA Pin: GPIO%u", this->sda_pin_);
|
||||
ESP_LOGCONFIG(TAG, " SCL Pin: GPIO%u", this->scl_pin_);
|
||||
ESP_LOGCONFIG(TAG, " Frequency: %u Hz", this->frequency_);
|
||||
if (this->scan_) {
|
||||
ESP_LOGI(TAG, "Scanning i2c bus for active devices...");
|
||||
uint8_t found = 0;
|
||||
for (uint8_t address = 1; address < 120; address++) {
|
||||
this->wire_->beginTransmission(address);
|
||||
uint8_t error = this->wire_->endTransmission();
|
||||
|
||||
if (error == 0) {
|
||||
ESP_LOGI(TAG, "Found i2c device at address 0x%02X", address);
|
||||
found++;
|
||||
} else if (error == 4) {
|
||||
ESP_LOGI(TAG, "Unknown error at address 0x%02X", address);
|
||||
}
|
||||
|
||||
delay(1);
|
||||
}
|
||||
if (found == 0) {
|
||||
ESP_LOGI(TAG, "Found no i2c devices!");
|
||||
}
|
||||
}
|
||||
}
|
||||
float I2CComponent::get_setup_priority() const { return setup_priority::BUS; }
|
||||
|
||||
void I2CComponent::raw_begin_transmission(uint8_t address) {
|
||||
ESP_LOGVV(TAG, "Beginning Transmission to 0x%02X:", address);
|
||||
this->wire_->beginTransmission(address);
|
||||
}
|
||||
bool I2CComponent::raw_end_transmission(uint8_t address, bool send_stop) {
|
||||
uint8_t status = this->wire_->endTransmission(send_stop);
|
||||
ESP_LOGVV(TAG, " Transmission ended. Status code: 0x%02X", status);
|
||||
|
||||
switch (status) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
ESP_LOGW(TAG, "Too much data to fit in transmitter buffer for address 0x%02X", address);
|
||||
break;
|
||||
case 2:
|
||||
ESP_LOGW(TAG, "Received NACK on transmit of address 0x%02X", address);
|
||||
break;
|
||||
case 3:
|
||||
ESP_LOGW(TAG, "Received NACK on transmit of data for address 0x%02X", address);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown transmit error %u for address 0x%02X", status, address);
|
||||
break;
|
||||
}
|
||||
|
||||
return status == 0;
|
||||
}
|
||||
bool I2CComponent::raw_request_from(uint8_t address, uint8_t len) {
|
||||
ESP_LOGVV(TAG, "Requesting %u bytes from 0x%02X:", len, address);
|
||||
uint8_t ret = this->wire_->requestFrom(address, len);
|
||||
if (ret != len) {
|
||||
ESP_LOGW(TAG, "Requesting %u bytes from 0x%02X failed!", len, address);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void HOT I2CComponent::raw_write(uint8_t address, const uint8_t *data, uint8_t len) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
ESP_LOGVV(TAG, " Writing 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]);
|
||||
this->wire_->write(data[i]);
|
||||
App.feed_wdt();
|
||||
}
|
||||
}
|
||||
void HOT I2CComponent::raw_write_16(uint8_t address, const uint16_t *data, uint8_t len) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
ESP_LOGVV(TAG, " Writing 0b" BYTE_TO_BINARY_PATTERN BYTE_TO_BINARY_PATTERN " (0x%04X)",
|
||||
BYTE_TO_BINARY(data[i] >> 8), BYTE_TO_BINARY(data[i]), data[i]);
|
||||
this->wire_->write(data[i] >> 8);
|
||||
this->wire_->write(data[i]);
|
||||
App.feed_wdt();
|
||||
}
|
||||
}
|
||||
|
||||
bool I2CComponent::raw_receive(uint8_t address, uint8_t *data, uint8_t len) {
|
||||
if (!this->raw_request_from(address, len))
|
||||
return false;
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
data[i] = this->wire_->read();
|
||||
ESP_LOGVV(TAG, " Received 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]);
|
||||
App.feed_wdt();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool I2CComponent::raw_receive_16(uint8_t address, uint16_t *data, uint8_t len) {
|
||||
if (!this->raw_request_from(address, len * 2))
|
||||
return false;
|
||||
auto *data_8 = reinterpret_cast<uint8_t *>(data);
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
data_8[i * 2 + 1] = this->wire_->read();
|
||||
data_8[i * 2] = this->wire_->read();
|
||||
ESP_LOGVV(TAG, " Received 0b" BYTE_TO_BINARY_PATTERN BYTE_TO_BINARY_PATTERN " (0x%04X)",
|
||||
BYTE_TO_BINARY(data_8[i * 2 + 1]), BYTE_TO_BINARY(data_8[i * 2]), data[i]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool I2CComponent::read_bytes(uint8_t address, uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion) {
|
||||
if (!this->write_bytes(address, a_register, nullptr, 0))
|
||||
return false;
|
||||
|
||||
if (conversion > 0)
|
||||
delay(conversion);
|
||||
return this->raw_receive(address, data, len);
|
||||
}
|
||||
bool I2CComponent::read_bytes_raw(uint8_t address, uint8_t *data, uint8_t len) {
|
||||
return this->raw_receive(address, data, len);
|
||||
}
|
||||
bool I2CComponent::read_bytes_16(uint8_t address, uint8_t a_register, uint16_t *data, uint8_t len,
|
||||
uint32_t conversion) {
|
||||
if (!this->write_bytes(address, a_register, nullptr, 0))
|
||||
return false;
|
||||
|
||||
if (conversion > 0)
|
||||
delay(conversion);
|
||||
return this->raw_receive_16(address, data, len);
|
||||
}
|
||||
bool I2CComponent::read_byte(uint8_t address, uint8_t a_register, uint8_t *data, uint32_t conversion) {
|
||||
return this->read_bytes(address, a_register, data, 1, conversion);
|
||||
}
|
||||
bool I2CComponent::read_byte_16(uint8_t address, uint8_t a_register, uint16_t *data, uint32_t conversion) {
|
||||
return this->read_bytes_16(address, a_register, data, 1, conversion);
|
||||
}
|
||||
bool I2CComponent::write_bytes(uint8_t address, uint8_t a_register, const uint8_t *data, uint8_t len) {
|
||||
this->raw_begin_transmission(address);
|
||||
this->raw_write(address, &a_register, 1);
|
||||
this->raw_write(address, data, len);
|
||||
return this->raw_end_transmission(address);
|
||||
}
|
||||
bool I2CComponent::write_bytes_raw(uint8_t address, const uint8_t *data, uint8_t len) {
|
||||
this->raw_begin_transmission(address);
|
||||
this->raw_write(address, data, len);
|
||||
return this->raw_end_transmission(address);
|
||||
}
|
||||
bool I2CComponent::write_bytes_16(uint8_t address, uint8_t a_register, const uint16_t *data, uint8_t len) {
|
||||
this->raw_begin_transmission(address);
|
||||
this->raw_write(address, &a_register, 1);
|
||||
this->raw_write_16(address, data, len);
|
||||
return this->raw_end_transmission(address);
|
||||
}
|
||||
bool I2CComponent::write_byte(uint8_t address, uint8_t a_register, uint8_t data) {
|
||||
return this->write_bytes(address, a_register, &data, 1);
|
||||
}
|
||||
bool I2CComponent::write_byte_16(uint8_t address, uint8_t a_register, uint16_t data) {
|
||||
return this->write_bytes_16(address, a_register, &data, 1);
|
||||
}
|
||||
|
||||
void I2CDevice::set_i2c_address(uint8_t address) { this->address_ = address; }
|
||||
#ifdef USE_I2C_MULTIPLEXER
|
||||
void I2CDevice::set_i2c_multiplexer(I2CMultiplexer *multiplexer, uint8_t channel) {
|
||||
ESP_LOGVV(TAG, " Setting Multiplexer %p for channel %d", multiplexer, channel);
|
||||
this->multiplexer_ = multiplexer;
|
||||
this->channel_ = channel;
|
||||
}
|
||||
|
||||
void I2CDevice::check_multiplexer_() {
|
||||
if (this->multiplexer_ != nullptr) {
|
||||
ESP_LOGVV(TAG, "Multiplexer setting channel to %d", this->channel_);
|
||||
this->multiplexer_->set_channel(this->channel_);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void I2CDevice::raw_begin_transmission() { // NOLINT
|
||||
#ifdef USE_I2C_MULTIPLEXER
|
||||
this->check_multiplexer_();
|
||||
#endif
|
||||
this->parent_->raw_begin_transmission(this->address_);
|
||||
}
|
||||
bool I2CDevice::raw_end_transmission(bool send_stop) { // NOLINT
|
||||
#ifdef USE_I2C_MULTIPLEXER
|
||||
this->check_multiplexer_();
|
||||
#endif
|
||||
return this->parent_->raw_end_transmission(this->address_, send_stop);
|
||||
}
|
||||
void I2CDevice::raw_write(const uint8_t *data, uint8_t len) { // NOLINT
|
||||
#ifdef USE_I2C_MULTIPLEXER
|
||||
this->check_multiplexer_();
|
||||
#endif
|
||||
this->parent_->raw_write(this->address_, data, len);
|
||||
}
|
||||
bool I2CDevice::read_bytes(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion) { // NOLINT
|
||||
#ifdef USE_I2C_MULTIPLEXER
|
||||
this->check_multiplexer_();
|
||||
#endif
|
||||
return this->parent_->read_bytes(this->address_, a_register, data, len, conversion);
|
||||
}
|
||||
bool I2CDevice::read_bytes_raw(uint8_t *data, uint8_t len) { // NOLINT
|
||||
#ifdef USE_I2C_MULTIPLEXER
|
||||
this->check_multiplexer_();
|
||||
#endif
|
||||
return this->parent_->read_bytes_raw(this->address_, data, len);
|
||||
}
|
||||
bool I2CDevice::read_byte(uint8_t a_register, uint8_t *data, uint32_t conversion) { // NOLINT
|
||||
#ifdef USE_I2C_MULTIPLEXER
|
||||
this->check_multiplexer_();
|
||||
#endif
|
||||
return this->parent_->read_byte(this->address_, a_register, data, conversion);
|
||||
}
|
||||
bool I2CDevice::write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len) { // NOLINT
|
||||
#ifdef USE_I2C_MULTIPLEXER
|
||||
this->check_multiplexer_();
|
||||
#endif
|
||||
return this->parent_->write_bytes(this->address_, a_register, data, len);
|
||||
}
|
||||
bool I2CDevice::write_bytes_raw(const uint8_t *data, uint8_t len) { // NOLINT
|
||||
#ifdef USE_I2C_MULTIPLEXER
|
||||
this->check_multiplexer_();
|
||||
#endif
|
||||
return this->parent_->write_bytes_raw(this->address_, data, len);
|
||||
}
|
||||
bool I2CDevice::write_byte(uint8_t a_register, uint8_t data) { // NOLINT
|
||||
#ifdef USE_I2C_MULTIPLEXER
|
||||
this->check_multiplexer_();
|
||||
#endif
|
||||
return this->parent_->write_byte(this->address_, a_register, data);
|
||||
}
|
||||
bool I2CDevice::read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len, uint32_t conversion) { // NOLINT
|
||||
#ifdef USE_I2C_MULTIPLEXER
|
||||
this->check_multiplexer_();
|
||||
#endif
|
||||
return this->parent_->read_bytes_16(this->address_, a_register, data, len, conversion);
|
||||
}
|
||||
bool I2CDevice::read_byte_16(uint8_t a_register, uint16_t *data, uint32_t conversion) { // NOLINT
|
||||
#ifdef USE_I2C_MULTIPLEXER
|
||||
this->check_multiplexer_();
|
||||
#endif
|
||||
return this->parent_->read_byte_16(this->address_, a_register, data, conversion);
|
||||
}
|
||||
bool I2CDevice::write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len) { // NOLINT
|
||||
#ifdef USE_I2C_MULTIPLEXER
|
||||
this->check_multiplexer_();
|
||||
#endif
|
||||
return this->parent_->write_bytes_16(this->address_, a_register, data, len);
|
||||
}
|
||||
bool I2CDevice::write_byte_16(uint8_t a_register, uint16_t data) { // NOLINT
|
||||
#ifdef USE_I2C_MULTIPLEXER
|
||||
this->check_multiplexer_();
|
||||
#endif
|
||||
return this->parent_->write_byte_16(this->address_, a_register, data);
|
||||
}
|
||||
void I2CDevice::set_i2c_parent(I2CComponent *parent) { this->parent_ = parent; }
|
||||
|
||||
I2CRegister &I2CRegister::operator=(uint8_t value) {
|
||||
this->parent_->write_byte(this->register_, value);
|
||||
this->parent_->write_register(this->register_, &value, 1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
I2CRegister &I2CRegister::operator&=(uint8_t value) {
|
||||
this->parent_->write_byte(this->register_, this->get() & value);
|
||||
value &= get();
|
||||
this->parent_->write_register(this->register_, &value, 1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
I2CRegister &I2CRegister::operator|=(uint8_t value) {
|
||||
this->parent_->write_byte(this->register_, this->get() | value);
|
||||
value |= get();
|
||||
this->parent_->write_register(this->register_, &value, 1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
uint8_t I2CRegister::get() {
|
||||
uint8_t I2CRegister::get() const {
|
||||
uint8_t value = 0x00;
|
||||
this->parent_->read_byte(this->register_, &value);
|
||||
this->parent_->read_register(this->register_, &value, 1);
|
||||
return value;
|
||||
}
|
||||
I2CRegister &I2CRegister::operator=(const std::vector<uint8_t> &value) {
|
||||
this->parent_->write_bytes(this->register_, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
} // namespace i2c
|
||||
} // namespace esphome
|
||||
|
@@ -1,198 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include <Wire.h>
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "i2c_bus.h"
|
||||
#include "esphome/core/optional.h"
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace i2c {
|
||||
|
||||
#define LOG_I2C_DEVICE(this) ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_);
|
||||
|
||||
/** The I2CComponent is the base of ESPHome's i2c communication.
|
||||
*
|
||||
* It handles setting up the bus (with pins, clock frequency) and provides nice helper functions to
|
||||
* make reading from the i2c bus easier (see read_bytes, write_bytes) and safe (with read timeouts).
|
||||
*
|
||||
* For the user, it has a few setters (see set_sda_pin, set_scl_pin, set_frequency)
|
||||
* to setup some parameters for the bus. Additionally, the i2c component has a scan feature that will
|
||||
* scan the entire 7-bit i2c address range for devices that respond to transmissions to make finding
|
||||
* the address of an i2c device easier.
|
||||
*
|
||||
* On the ESP32, you can even have multiple I2C bus for communication, simply create multiple
|
||||
* I2CComponents, each with different SDA and SCL pins and use `set_parent` on all I2CDevices that use
|
||||
* the non-first I2C bus.
|
||||
*/
|
||||
class I2CComponent : public Component {
|
||||
public:
|
||||
I2CComponent();
|
||||
void set_sda_pin(uint8_t sda_pin) { sda_pin_ = sda_pin; }
|
||||
void set_scl_pin(uint8_t scl_pin) { scl_pin_ = scl_pin; }
|
||||
void set_frequency(uint32_t frequency) { frequency_ = frequency; }
|
||||
void set_scan(bool scan) { scan_ = scan; }
|
||||
|
||||
/** Read len amount of bytes from a register into data. Optionally with a conversion time after
|
||||
* writing the register value to the bus.
|
||||
*
|
||||
* @param address The address to send the request to.
|
||||
* @param a_register The register number to write to the bus before reading.
|
||||
* @param data An array to store len amount of 8-bit bytes into.
|
||||
* @param len The amount of bytes to request and write into data.
|
||||
* @param conversion The time in ms between writing the register value and reading out the value.
|
||||
* @return If the operation was successful.
|
||||
*/
|
||||
bool read_bytes(uint8_t address, uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion = 0);
|
||||
bool read_bytes_raw(uint8_t address, uint8_t *data, uint8_t len);
|
||||
|
||||
/** Read len amount of 16-bit words (MSB first) from a register into data.
|
||||
*
|
||||
* @param address The address to send the request to.
|
||||
* @param a_register The register number to write to the bus before reading.
|
||||
* @param data An array to store len amount of 16-bit words into.
|
||||
* @param len The amount of 16-bit words to request and write into data.
|
||||
* @param conversion The time in ms between writing the register value and reading out the value.
|
||||
* @return If the operation was successful.
|
||||
*/
|
||||
bool read_bytes_16(uint8_t address, uint8_t a_register, uint16_t *data, uint8_t len, uint32_t conversion = 0);
|
||||
|
||||
/// Read a single byte from a register into the data variable. Return true if successful.
|
||||
bool read_byte(uint8_t address, uint8_t a_register, uint8_t *data, uint32_t conversion = 0);
|
||||
|
||||
/// Read a single 16-bit words (MSB first) from a register into the data variable. Return true if successful.
|
||||
bool read_byte_16(uint8_t address, uint8_t a_register, uint16_t *data, uint32_t conversion = 0);
|
||||
|
||||
/** Write len amount of 8-bit bytes to the specified register for address.
|
||||
*
|
||||
* @param address The address to use for the transmission.
|
||||
* @param a_register The register to write the values to.
|
||||
* @param data An array from which len bytes of data will be written to the bus.
|
||||
* @param len The amount of bytes to write to the bus.
|
||||
* @return If the operation was successful.
|
||||
*/
|
||||
bool write_bytes(uint8_t address, uint8_t a_register, const uint8_t *data, uint8_t len);
|
||||
bool write_bytes_raw(uint8_t address, const uint8_t *data, uint8_t len);
|
||||
|
||||
/** Write len amount of 16-bit words (MSB first) to the specified register for address.
|
||||
*
|
||||
* @param address The address to use for the transmission.
|
||||
* @param a_register The register to write the values to.
|
||||
* @param data An array from which len 16-bit words of data will be written to the bus.
|
||||
* @param len The amount of bytes to write to the bus.
|
||||
* @return If the operation was successful.
|
||||
*/
|
||||
bool write_bytes_16(uint8_t address, uint8_t a_register, const uint16_t *data, uint8_t len);
|
||||
|
||||
/// Write a single byte of data into the specified register of address. Return true if successful.
|
||||
bool write_byte(uint8_t address, uint8_t a_register, uint8_t data);
|
||||
|
||||
/// Write a single 16-bit word of data into the specified register of address. Return true if successful.
|
||||
bool write_byte_16(uint8_t address, uint8_t a_register, uint16_t data);
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
/// Begin a write transmission to an address.
|
||||
void raw_begin_transmission(uint8_t address);
|
||||
|
||||
/// End a write transmission to an address, return true if successful.
|
||||
bool raw_end_transmission(uint8_t address, bool send_stop = true);
|
||||
|
||||
/** Request data from an address with a number of (8-bit) bytes.
|
||||
*
|
||||
* @param address The address to request the bytes from.
|
||||
* @param len The number of bytes to receive, must not be 0.
|
||||
* @return True if all requested bytes were read, false otherwise.
|
||||
*/
|
||||
bool raw_request_from(uint8_t address, uint8_t len);
|
||||
|
||||
/// Write len amount of bytes from data to address. begin_transmission_ must be called before this.
|
||||
void raw_write(uint8_t address, const uint8_t *data, uint8_t len);
|
||||
|
||||
/// Write len amount of 16-bit words from data to address. begin_transmission_ must be called before this.
|
||||
void raw_write_16(uint8_t address, const uint16_t *data, uint8_t len);
|
||||
|
||||
/// Request len amount of bytes from address and write the result it into data. Returns true iff was successful.
|
||||
bool raw_receive(uint8_t address, uint8_t *data, uint8_t len);
|
||||
|
||||
/// Request len amount of 16-bit words from address and write the result into data. Returns true iff was successful.
|
||||
bool raw_receive_16(uint8_t address, uint16_t *data, uint8_t len);
|
||||
|
||||
/// Setup the i2c. bus
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
/// Set a very high setup priority to make sure it's loaded before all other hardware.
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
TwoWire *wire_;
|
||||
uint8_t sda_pin_;
|
||||
uint8_t scl_pin_;
|
||||
uint32_t frequency_;
|
||||
bool scan_;
|
||||
};
|
||||
|
||||
class I2CDevice;
|
||||
class I2CMultiplexer;
|
||||
class I2CRegister {
|
||||
public:
|
||||
I2CRegister(I2CDevice *parent, uint8_t a_register) : parent_(parent), register_(a_register) {}
|
||||
|
||||
I2CRegister &operator=(uint8_t value);
|
||||
I2CRegister &operator=(const std::vector<uint8_t> &value);
|
||||
I2CRegister &operator&=(uint8_t value);
|
||||
I2CRegister &operator|=(uint8_t value);
|
||||
|
||||
uint8_t get();
|
||||
explicit operator uint8_t() const { return get(); }
|
||||
|
||||
uint8_t get() const;
|
||||
|
||||
protected:
|
||||
I2CDevice *parent_;
|
||||
uint8_t register_;
|
||||
};
|
||||
|
||||
/** All components doing communication on the I2C bus should subclass I2CDevice.
|
||||
*
|
||||
* This class stores 1. the address of the i2c device and has a helper function to allow
|
||||
* users to manually set the address and 2. stores a reference to the "parent" I2CComponent.
|
||||
*
|
||||
*
|
||||
* All this class basically does is to expose all helper functions from I2CComponent.
|
||||
*/
|
||||
// like ntohs/htons but without including networking headers.
|
||||
// ("i2c" byte order is big-endian)
|
||||
inline uint16_t i2ctohs(uint16_t i2cshort) {
|
||||
union {
|
||||
uint16_t x;
|
||||
uint8_t y[2];
|
||||
} conv;
|
||||
conv.x = i2cshort;
|
||||
return ((uint16_t) conv.y[0] << 8) | ((uint16_t) conv.y[1] << 0);
|
||||
}
|
||||
|
||||
inline uint16_t htoi2cs(uint16_t hostshort) { return i2ctohs(hostshort); }
|
||||
|
||||
class I2CDevice {
|
||||
public:
|
||||
I2CDevice() = default;
|
||||
I2CDevice(I2CComponent *parent, uint8_t address) : address_(address), parent_(parent) {}
|
||||
|
||||
/// Manually set the i2c address of this device.
|
||||
void set_i2c_address(uint8_t address);
|
||||
#ifdef USE_I2C_MULTIPLEXER
|
||||
/// Manually set the i2c multiplexer of this device.
|
||||
void set_i2c_multiplexer(I2CMultiplexer *multiplexer, uint8_t channel);
|
||||
#endif
|
||||
/// Manually set the parent i2c bus for this device.
|
||||
void set_i2c_parent(I2CComponent *parent);
|
||||
void set_i2c_address(uint8_t address) { address_ = address; }
|
||||
void set_i2c_bus(I2CBus *bus) { bus_ = bus; }
|
||||
|
||||
I2CRegister reg(uint8_t a_register) { return {this, a_register}; }
|
||||
|
||||
/// Begin a write transmission.
|
||||
void raw_begin_transmission();
|
||||
ErrorCode read(uint8_t *data, size_t len) { return bus_->read(address_, data, len); }
|
||||
ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len) {
|
||||
ErrorCode err = this->write(&a_register, 1);
|
||||
if (err != ERROR_OK)
|
||||
return err;
|
||||
return this->read(data, len);
|
||||
}
|
||||
|
||||
/// End a write transmission, return true if successful.
|
||||
bool raw_end_transmission(bool send_stop = true);
|
||||
ErrorCode write(const uint8_t *data, uint8_t len) { return bus_->write(address_, data, len); }
|
||||
ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len) {
|
||||
WriteBuffer buffers[2];
|
||||
buffers[0].data = &a_register;
|
||||
buffers[0].len = 1;
|
||||
buffers[1].data = data;
|
||||
buffers[1].len = len;
|
||||
return bus_->writev(address_, buffers, 2);
|
||||
}
|
||||
|
||||
/// Write len amount of bytes from data. begin_transmission_ must be called before this.
|
||||
void raw_write(const uint8_t *data, uint8_t len);
|
||||
// Compat APIs
|
||||
|
||||
/** Read len amount of bytes from a register into data. Optionally with a conversion time after
|
||||
* writing the register value to the bus.
|
||||
*
|
||||
* @param a_register The register number to write to the bus before reading.
|
||||
* @param data An array to store len amount of 8-bit bytes into.
|
||||
* @param len The amount of bytes to request and write into data.
|
||||
* @param conversion The time in ms between writing the register value and reading out the value.
|
||||
* @return If the operation was successful.
|
||||
*/
|
||||
bool read_bytes(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion = 0);
|
||||
bool read_bytes_raw(uint8_t *data, uint8_t len);
|
||||
bool read_bytes(uint8_t a_register, uint8_t *data, uint8_t len) {
|
||||
return read_register(a_register, data, len) == ERROR_OK;
|
||||
}
|
||||
bool read_bytes_raw(uint8_t *data, uint8_t len) { return read(data, len) == ERROR_OK; }
|
||||
|
||||
template<size_t N> optional<std::array<uint8_t, N>> read_bytes(uint8_t a_register) {
|
||||
std::array<uint8_t, N> res;
|
||||
@@ -209,18 +90,15 @@ class I2CDevice {
|
||||
return res;
|
||||
}
|
||||
|
||||
/** Read len amount of 16-bit words (MSB first) from a register into data.
|
||||
*
|
||||
* @param a_register The register number to write to the bus before reading.
|
||||
* @param data An array to store len amount of 16-bit words into.
|
||||
* @param len The amount of 16-bit words to request and write into data.
|
||||
* @param conversion The time in ms between writing the register value and reading out the value.
|
||||
* @return If the operation was successful.
|
||||
*/
|
||||
bool read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len, uint32_t conversion = 0);
|
||||
bool read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len) {
|
||||
if (read_register(a_register, reinterpret_cast<uint8_t *>(data), len * 2) != ERROR_OK)
|
||||
return false;
|
||||
for (size_t i = 0; i < len; i++)
|
||||
data[i] = i2ctohs(data[i]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Read a single byte from a register into the data variable. Return true if successful.
|
||||
bool read_byte(uint8_t a_register, uint8_t *data, uint32_t conversion = 0);
|
||||
bool read_byte(uint8_t a_register, uint8_t *data) { return read_register(a_register, data, 1) == ERROR_OK; }
|
||||
|
||||
optional<uint8_t> read_byte(uint8_t a_register) {
|
||||
uint8_t data;
|
||||
@@ -229,66 +107,30 @@ class I2CDevice {
|
||||
return data;
|
||||
}
|
||||
|
||||
/// Read a single 16-bit words (MSB first) from a register into the data variable. Return true if successful.
|
||||
bool read_byte_16(uint8_t a_register, uint16_t *data, uint32_t conversion = 0);
|
||||
bool read_byte_16(uint8_t a_register, uint16_t *data) { return read_bytes_16(a_register, data, 1); }
|
||||
|
||||
/** Write len amount of 8-bit bytes to the specified register.
|
||||
*
|
||||
* @param a_register The register to write the values to.
|
||||
* @param data An array from which len bytes of data will be written to the bus.
|
||||
* @param len The amount of bytes to write to the bus.
|
||||
* @return If the operation was successful.
|
||||
*/
|
||||
bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len);
|
||||
bool write_bytes_raw(const uint8_t *data, uint8_t len);
|
||||
|
||||
/** Write a vector of data to a register.
|
||||
*
|
||||
* @param a_register The register to write to.
|
||||
* @param data The data to write.
|
||||
* @return If the operation was successful.
|
||||
*/
|
||||
bool write_bytes(uint8_t a_register, const std::vector<uint8_t> &data) {
|
||||
return this->write_bytes(a_register, data.data(), data.size());
|
||||
bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len) {
|
||||
return write_register(a_register, data, len) == ERROR_OK;
|
||||
}
|
||||
|
||||
bool write_bytes(uint8_t a_register, const std::vector<uint8_t> &data) {
|
||||
return write_bytes(a_register, data.data(), data.size());
|
||||
}
|
||||
bool write_bytes_raw(const std::vector<uint8_t> &data) { return this->write_bytes_raw(data.data(), data.size()); }
|
||||
|
||||
template<size_t N> bool write_bytes(uint8_t a_register, const std::array<uint8_t, N> &data) {
|
||||
return this->write_bytes(a_register, data.data(), data.size());
|
||||
}
|
||||
template<size_t N> bool write_bytes_raw(const std::array<uint8_t, N> &data) {
|
||||
return this->write_bytes_raw(data.data(), data.size());
|
||||
return write_bytes(a_register, data.data(), data.size());
|
||||
}
|
||||
|
||||
/** Write len amount of 16-bit words (MSB first) to the specified register.
|
||||
*
|
||||
* @param a_register The register to write the values to.
|
||||
* @param data An array from which len 16-bit words of data will be written to the bus.
|
||||
* @param len The amount of bytes to write to the bus.
|
||||
* @return If the operation was successful.
|
||||
*/
|
||||
bool write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len);
|
||||
|
||||
/// Write a single byte of data into the specified register. Return true if successful.
|
||||
bool write_byte(uint8_t a_register, uint8_t data);
|
||||
bool write_byte(uint8_t a_register, uint8_t data) { return write_bytes(a_register, &data, 1); }
|
||||
|
||||
/// Write a single 16-bit word of data into the specified register. Return true if successful.
|
||||
bool write_byte_16(uint8_t a_register, uint16_t data);
|
||||
bool write_byte_16(uint8_t a_register, uint16_t data) { return write_bytes_16(a_register, &data, 1); }
|
||||
|
||||
protected:
|
||||
// Checks for multiplexer set and set channel
|
||||
void check_multiplexer_();
|
||||
uint8_t address_{0x00};
|
||||
I2CComponent *parent_{nullptr};
|
||||
#ifdef USE_I2C_MULTIPLEXER
|
||||
I2CMultiplexer *multiplexer_{nullptr};
|
||||
uint8_t channel_;
|
||||
#endif
|
||||
};
|
||||
class I2CMultiplexer : public I2CDevice {
|
||||
public:
|
||||
I2CMultiplexer() = default;
|
||||
virtual void set_channel(uint8_t channelno);
|
||||
I2CBus *bus_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace i2c
|
||||
} // namespace esphome
|
||||
|
46
esphome/components/i2c/i2c_bus.h
Normal file
46
esphome/components/i2c/i2c_bus.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
namespace esphome {
|
||||
namespace i2c {
|
||||
|
||||
enum ErrorCode {
|
||||
ERROR_OK = 0,
|
||||
ERROR_INVALID_ARGUMENT = 1,
|
||||
ERROR_NOT_ACKNOWLEDGED = 2,
|
||||
ERROR_TIMEOUT = 3,
|
||||
ERROR_NOT_INITIALIZED = 4,
|
||||
ERROR_TOO_LARGE = 5,
|
||||
ERROR_UNKNOWN = 6,
|
||||
};
|
||||
|
||||
struct ReadBuffer {
|
||||
uint8_t *data;
|
||||
size_t len;
|
||||
};
|
||||
struct WriteBuffer {
|
||||
const uint8_t *data;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
class I2CBus {
|
||||
public:
|
||||
virtual ErrorCode read(uint8_t address, uint8_t *buffer, size_t len) {
|
||||
ReadBuffer buf;
|
||||
buf.data = buffer;
|
||||
buf.len = len;
|
||||
return readv(address, &buf, 1);
|
||||
}
|
||||
virtual ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) = 0;
|
||||
virtual ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len) {
|
||||
WriteBuffer buf;
|
||||
buf.data = buffer;
|
||||
buf.len = len;
|
||||
return writev(address, &buf, 1);
|
||||
}
|
||||
virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) = 0;
|
||||
};
|
||||
|
||||
} // namespace i2c
|
||||
} // namespace esphome
|
97
esphome/components/i2c/i2c_bus_arduino.cpp
Normal file
97
esphome/components/i2c/i2c_bus_arduino.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "i2c_bus_arduino.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cstring>
|
||||
|
||||
namespace esphome {
|
||||
namespace i2c {
|
||||
|
||||
static const char *const TAG = "i2c.arduino";
|
||||
|
||||
void ArduinoI2CBus::setup() {
|
||||
#ifdef USE_ESP32
|
||||
static uint8_t next_bus_num = 0;
|
||||
if (next_bus_num == 0)
|
||||
wire_ = &Wire;
|
||||
else
|
||||
wire_ = new TwoWire(next_bus_num); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
next_bus_num++;
|
||||
#else
|
||||
wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer)
|
||||
#endif
|
||||
|
||||
wire_->begin(sda_pin_, scl_pin_);
|
||||
wire_->setClock(frequency_);
|
||||
initialized_ = true;
|
||||
}
|
||||
void ArduinoI2CBus::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "I2C Bus:");
|
||||
ESP_LOGCONFIG(TAG, " SDA Pin: GPIO%u", this->sda_pin_);
|
||||
ESP_LOGCONFIG(TAG, " SCL Pin: GPIO%u", this->scl_pin_);
|
||||
ESP_LOGCONFIG(TAG, " Frequency: %u Hz", this->frequency_);
|
||||
if (this->scan_) {
|
||||
ESP_LOGI(TAG, "Scanning i2c bus for active devices...");
|
||||
uint8_t found = 0;
|
||||
for (uint8_t address = 1; address < 120; address++) {
|
||||
auto err = readv(address, nullptr, 0);
|
||||
|
||||
if (err == ERROR_OK) {
|
||||
ESP_LOGI(TAG, "Found i2c device at address 0x%02X", address);
|
||||
found++;
|
||||
} else if (err == ERROR_UNKNOWN) {
|
||||
ESP_LOGI(TAG, "Unknown error at address 0x%02X", address);
|
||||
}
|
||||
}
|
||||
if (found == 0) {
|
||||
ESP_LOGI(TAG, "Found no i2c devices!");
|
||||
}
|
||||
}
|
||||
}
|
||||
ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) {
|
||||
if (!initialized_)
|
||||
return ERROR_NOT_INITIALIZED;
|
||||
size_t to_request = 0;
|
||||
for (size_t i = 0; i < cnt; i++)
|
||||
to_request += buffers[i].len;
|
||||
size_t ret = wire_->requestFrom((int) address, (int) to_request, 1);
|
||||
if (ret != to_request) {
|
||||
return ERROR_TIMEOUT;
|
||||
}
|
||||
for (size_t i = 0; i < cnt; i++) {
|
||||
const auto &buf = buffers[i];
|
||||
for (size_t j = 0; j < buf.len; j++)
|
||||
buf.data[j] = wire_->read();
|
||||
}
|
||||
return ERROR_OK;
|
||||
}
|
||||
ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) {
|
||||
if (!initialized_)
|
||||
return ERROR_NOT_INITIALIZED;
|
||||
|
||||
wire_->beginTransmission(address);
|
||||
for (size_t i = 0; i < cnt; i++) {
|
||||
const auto &buf = buffers[i];
|
||||
if (buf.len == 0)
|
||||
continue;
|
||||
size_t ret = wire_->write(buf.data, buf.len);
|
||||
if (ret != buf.len) {
|
||||
return ERROR_UNKNOWN;
|
||||
}
|
||||
}
|
||||
uint8_t status = wire_->endTransmission(true);
|
||||
if (status == 0) {
|
||||
return ERROR_OK;
|
||||
} else if (status == 1) {
|
||||
// transmit buffer not large enough
|
||||
return ERROR_UNKNOWN;
|
||||
} else if (status == 2 || status == 3) {
|
||||
return ERROR_NOT_ACKNOWLEDGED;
|
||||
}
|
||||
return ERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
} // namespace i2c
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP_IDF
|
37
esphome/components/i2c/i2c_bus_arduino.h
Normal file
37
esphome/components/i2c/i2c_bus_arduino.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "i2c_bus.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include <Wire.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace i2c {
|
||||
|
||||
class ArduinoI2CBus : public I2CBus, public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override;
|
||||
ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) override;
|
||||
float get_setup_priority() const override { return setup_priority::BUS; }
|
||||
|
||||
void set_scan(bool scan) { scan_ = scan; }
|
||||
void set_sda_pin(uint8_t sda_pin) { sda_pin_ = sda_pin; }
|
||||
void set_scl_pin(uint8_t scl_pin) { scl_pin_ = scl_pin; }
|
||||
void set_frequency(uint32_t frequency) { frequency_ = frequency; }
|
||||
|
||||
protected:
|
||||
TwoWire *wire_;
|
||||
bool scan_;
|
||||
uint8_t sda_pin_;
|
||||
uint8_t scl_pin_;
|
||||
uint32_t frequency_;
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
} // namespace i2c
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
147
esphome/components/i2c/i2c_bus_esp_idf.cpp
Normal file
147
esphome/components/i2c/i2c_bus_esp_idf.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
#ifdef USE_ESP_IDF
|
||||
|
||||
#include "i2c_bus_esp_idf.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cstring>
|
||||
|
||||
namespace esphome {
|
||||
namespace i2c {
|
||||
|
||||
static const char *const TAG = "i2c.idf";
|
||||
|
||||
void IDFI2CBus::setup() {
|
||||
static i2c_port_t next_port = 0;
|
||||
port_ = next_port++;
|
||||
|
||||
i2c_config_t conf{};
|
||||
memset(&conf, 0, sizeof(conf));
|
||||
conf.mode = I2C_MODE_MASTER;
|
||||
conf.sda_io_num = sda_pin_;
|
||||
conf.sda_pullup_en = sda_pullup_enabled_;
|
||||
conf.scl_io_num = scl_pin_;
|
||||
conf.scl_pullup_en = scl_pullup_enabled_;
|
||||
conf.master.clk_speed = frequency_;
|
||||
esp_err_t err = i2c_param_config(port_, &conf);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "i2c_param_config failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
err = i2c_driver_install(port_, I2C_MODE_MASTER, 0, 0, ESP_INTR_FLAG_IRAM);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "i2c_driver_install failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
initialized_ = true;
|
||||
}
|
||||
void IDFI2CBus::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "I2C Bus:");
|
||||
ESP_LOGCONFIG(TAG, " SDA Pin: GPIO%u", this->sda_pin_);
|
||||
ESP_LOGCONFIG(TAG, " SCL Pin: GPIO%u", this->scl_pin_);
|
||||
ESP_LOGCONFIG(TAG, " Frequency: %u Hz", this->frequency_);
|
||||
if (this->scan_) {
|
||||
ESP_LOGI(TAG, "Scanning i2c bus for active devices...");
|
||||
uint8_t found = 0;
|
||||
for (uint8_t address = 1; address < 120; address++) {
|
||||
auto err = readv(address, nullptr, 0);
|
||||
|
||||
if (err == ERROR_OK) {
|
||||
ESP_LOGI(TAG, "Found i2c device at address 0x%02X", address);
|
||||
found++;
|
||||
} else if (err == ERROR_UNKNOWN) {
|
||||
ESP_LOGI(TAG, "Unknown error at address 0x%02X", address);
|
||||
}
|
||||
}
|
||||
if (found == 0) {
|
||||
ESP_LOGI(TAG, "Found no i2c devices!");
|
||||
}
|
||||
}
|
||||
}
|
||||
ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) {
|
||||
if (!initialized_)
|
||||
return ERROR_NOT_INITIALIZED;
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
esp_err_t err = i2c_master_start(cmd);
|
||||
if (err != ESP_OK) {
|
||||
i2c_cmd_link_delete(cmd);
|
||||
return ERROR_UNKNOWN;
|
||||
}
|
||||
err = i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_READ, true);
|
||||
if (err != ESP_OK) {
|
||||
i2c_cmd_link_delete(cmd);
|
||||
return ERROR_UNKNOWN;
|
||||
}
|
||||
for (size_t i = 0; i < cnt; i++) {
|
||||
const auto &buf = buffers[i];
|
||||
if (buf.len == 0)
|
||||
continue;
|
||||
err = i2c_master_read(cmd, buf.data, buf.len, i == cnt - 1 ? I2C_MASTER_LAST_NACK : I2C_MASTER_ACK);
|
||||
if (err != ESP_OK) {
|
||||
i2c_cmd_link_delete(cmd);
|
||||
return ERROR_UNKNOWN;
|
||||
}
|
||||
}
|
||||
err = i2c_master_stop(cmd);
|
||||
if (err != ESP_OK) {
|
||||
i2c_cmd_link_delete(cmd);
|
||||
return ERROR_UNKNOWN;
|
||||
}
|
||||
err = i2c_master_cmd_begin(port_, cmd, 20 / portTICK_PERIOD_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
if (err == ESP_FAIL) {
|
||||
// transfer not acked
|
||||
return ERROR_NOT_ACKNOWLEDGED;
|
||||
} else if (err == ESP_ERR_TIMEOUT) {
|
||||
return ERROR_TIMEOUT;
|
||||
} else if (err != ESP_OK) {
|
||||
return ERROR_UNKNOWN;
|
||||
}
|
||||
return ERROR_OK;
|
||||
}
|
||||
ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) {
|
||||
if (!initialized_)
|
||||
return ERROR_NOT_INITIALIZED;
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
esp_err_t err = i2c_master_start(cmd);
|
||||
if (err != ESP_OK) {
|
||||
i2c_cmd_link_delete(cmd);
|
||||
return ERROR_UNKNOWN;
|
||||
}
|
||||
err = i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_WRITE, true);
|
||||
if (err != ESP_OK) {
|
||||
i2c_cmd_link_delete(cmd);
|
||||
return ERROR_UNKNOWN;
|
||||
}
|
||||
for (size_t i = 0; i < cnt; i++) {
|
||||
const auto &buf = buffers[i];
|
||||
if (buf.len == 0)
|
||||
continue;
|
||||
err = i2c_master_write(cmd, buf.data, buf.len, true);
|
||||
if (err != ESP_OK) {
|
||||
i2c_cmd_link_delete(cmd);
|
||||
return ERROR_UNKNOWN;
|
||||
}
|
||||
}
|
||||
err = i2c_master_stop(cmd);
|
||||
if (err != ESP_OK) {
|
||||
i2c_cmd_link_delete(cmd);
|
||||
return ERROR_UNKNOWN;
|
||||
}
|
||||
err = i2c_master_cmd_begin(port_, cmd, 20 / portTICK_PERIOD_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
if (err == ESP_FAIL) {
|
||||
// transfer not acked
|
||||
return ERROR_NOT_ACKNOWLEDGED;
|
||||
} else if (err == ESP_ERR_TIMEOUT) {
|
||||
return ERROR_TIMEOUT;
|
||||
} else if (err != ESP_OK) {
|
||||
return ERROR_UNKNOWN;
|
||||
}
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
} // namespace i2c
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP_IDF
|
41
esphome/components/i2c/i2c_bus_esp_idf.h
Normal file
41
esphome/components/i2c/i2c_bus_esp_idf.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
|
||||
#include "i2c_bus.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include <driver/i2c.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace i2c {
|
||||
|
||||
class IDFI2CBus : public I2CBus, public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override;
|
||||
ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) override;
|
||||
float get_setup_priority() const override { return setup_priority::BUS; }
|
||||
|
||||
void set_scan(bool scan) { scan_ = scan; }
|
||||
void set_sda_pin(uint8_t sda_pin) { sda_pin_ = sda_pin; }
|
||||
void set_sda_pullup_enabled(bool sda_pullup_enabled) { sda_pullup_enabled_ = sda_pullup_enabled; }
|
||||
void set_scl_pin(uint8_t scl_pin) { scl_pin_ = scl_pin; }
|
||||
void set_scl_pullup_enabled(bool scl_pullup_enabled) { scl_pullup_enabled_ = scl_pullup_enabled; }
|
||||
void set_frequency(uint32_t frequency) { frequency_ = frequency; }
|
||||
|
||||
protected:
|
||||
i2c_port_t port_;
|
||||
bool scan_;
|
||||
uint8_t sda_pin_;
|
||||
bool sda_pullup_enabled_;
|
||||
uint8_t scl_pin_;
|
||||
bool scl_pullup_enabled_;
|
||||
uint32_t frequency_;
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
} // namespace i2c
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP_IDF
|
Reference in New Issue
Block a user