1
0
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:
Otto Winter
2021-09-20 11:47:51 +02:00
committed by GitHub
parent 1e8e471dec
commit ac0d921413
583 changed files with 9008 additions and 5420 deletions

View File

@@ -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])
)

View File

@@ -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

View File

@@ -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

View 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

View 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

View 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

View 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

View 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