[nrf52, i2c] i2c support for nrf52 (#8150)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Co-authored-by: Ludovic BOUÉ <lboue@users.noreply.github.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
This commit is contained in:
@@ -2,11 +2,18 @@ import logging
|
||||
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.zephyr import (
|
||||
zephyr_add_overlay,
|
||||
zephyr_add_prj_conf,
|
||||
zephyr_data,
|
||||
)
|
||||
from esphome.components.zephyr.const import KEY_BOARD
|
||||
from esphome.config_helpers import filter_source_files_from_platform
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ADDRESS,
|
||||
CONF_FREQUENCY,
|
||||
CONF_I2C,
|
||||
CONF_I2C_ID,
|
||||
CONF_ID,
|
||||
CONF_SCAN,
|
||||
@@ -15,10 +22,12 @@ from esphome.const import (
|
||||
CONF_TIMEOUT,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_NRF52,
|
||||
PLATFORM_RP2040,
|
||||
PlatformFramework,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.cpp_generator import MockObj
|
||||
import esphome.final_validate as fv
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
@@ -28,6 +37,7 @@ I2CBus = i2c_ns.class_("I2CBus")
|
||||
InternalI2CBus = i2c_ns.class_("InternalI2CBus", I2CBus)
|
||||
ArduinoI2CBus = i2c_ns.class_("ArduinoI2CBus", InternalI2CBus, cg.Component)
|
||||
IDFI2CBus = i2c_ns.class_("IDFI2CBus", InternalI2CBus, cg.Component)
|
||||
ZephyrI2CBus = i2c_ns.class_("ZephyrI2CBus", I2CBus, cg.Component)
|
||||
I2CDevice = i2c_ns.class_("I2CDevice")
|
||||
|
||||
|
||||
@@ -41,6 +51,8 @@ def _bus_declare_type(value):
|
||||
return cv.declare_id(ArduinoI2CBus)(value)
|
||||
if CORE.using_esp_idf:
|
||||
return cv.declare_id(IDFI2CBus)(value)
|
||||
if CORE.using_zephyr:
|
||||
return cv.declare_id(ZephyrI2CBus)(value)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@@ -62,23 +74,70 @@ CONFIG_SCHEMA = cv.All(
|
||||
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)
|
||||
cv.SplitDefault(
|
||||
CONF_FREQUENCY,
|
||||
esp32="50kHz",
|
||||
esp8266="50kHz",
|
||||
rp2040="50kHz",
|
||||
nrf52="100kHz",
|
||||
): cv.All(
|
||||
cv.frequency,
|
||||
cv.Range(min=0, min_included=False),
|
||||
),
|
||||
cv.Optional(CONF_TIMEOUT): cv.All(
|
||||
cv.only_with_framework(["arduino", "esp-idf"]),
|
||||
cv.positive_time_period,
|
||||
),
|
||||
cv.Optional(CONF_TIMEOUT): cv.positive_time_period,
|
||||
cv.Optional(CONF_SCAN, default=True): cv.boolean,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]),
|
||||
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040, PLATFORM_NRF52]),
|
||||
validate_config,
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
full_config = fv.full_config.get()[CONF_I2C]
|
||||
if CORE.using_zephyr and len(full_config) > 1:
|
||||
raise cv.Invalid("Second i2c is not implemented on Zephyr yet")
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.BUS)
|
||||
async def to_code(config):
|
||||
cg.add_global(i2c_ns.using)
|
||||
cg.add_define("USE_I2C")
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
if CORE.using_zephyr:
|
||||
zephyr_add_prj_conf("I2C", True)
|
||||
i2c = "i2c0"
|
||||
if zephyr_data()[KEY_BOARD] in ["xiao_ble"]:
|
||||
i2c = "i2c1"
|
||||
zephyr_add_overlay(
|
||||
f"""
|
||||
&pinctrl {{
|
||||
{i2c}_default: {i2c}_default {{
|
||||
group1 {{
|
||||
psels = <NRF_PSEL(TWIM_SDA, {config[CONF_SDA] // 32}, {config[CONF_SDA] % 32})>,
|
||||
<NRF_PSEL(TWIM_SCL, {config[CONF_SCL] // 32}, {config[CONF_SCL] % 32})>;
|
||||
}};
|
||||
}};
|
||||
{i2c}_sleep: {i2c}_sleep {{
|
||||
group1 {{
|
||||
psels = <NRF_PSEL(TWIM_SDA, {config[CONF_SDA] // 32}, {config[CONF_SDA] % 32})>,
|
||||
<NRF_PSEL(TWIM_SCL, {config[CONF_SCL] // 32}, {config[CONF_SCL] % 32})>;
|
||||
low-power-enable;
|
||||
}};
|
||||
}};
|
||||
}};
|
||||
"""
|
||||
)
|
||||
var = cg.new_Pvariable(
|
||||
config[CONF_ID], MockObj(f"DEVICE_DT_GET(DT_NODELABEL({i2c}))")
|
||||
)
|
||||
else:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
cg.add(var.set_sda_pin(config[CONF_SDA]))
|
||||
@@ -197,5 +256,6 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
PlatformFramework.LN882X_ARDUINO,
|
||||
},
|
||||
"i2c_bus_esp_idf.cpp": {PlatformFramework.ESP32_IDF},
|
||||
"i2c_bus_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "i2c.h"
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <memory>
|
||||
|
||||
@@ -23,6 +24,8 @@ void I2CBus::i2c_scan_() {
|
||||
} else if (err == ERROR_UNKNOWN) {
|
||||
scan_results_.emplace_back(address, false);
|
||||
}
|
||||
// it takes 16sec to scan on nrf52. It prevents board reset.
|
||||
arch_feed_wdt();
|
||||
}
|
||||
#if defined(USE_ESP32) && defined(USE_LOGGER)
|
||||
esp_log_level_set("*", previous);
|
||||
|
||||
133
esphome/components/i2c/i2c_bus_zephyr.cpp
Normal file
133
esphome/components/i2c/i2c_bus_zephyr.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
#ifdef USE_ZEPHYR
|
||||
|
||||
#include "i2c_bus_zephyr.h"
|
||||
#include <zephyr/drivers/i2c.h>
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::i2c {
|
||||
|
||||
static const char *const TAG = "i2c.zephyr";
|
||||
|
||||
void ZephyrI2CBus::setup() {
|
||||
if (!device_is_ready(this->i2c_dev_)) {
|
||||
ESP_LOGE(TAG, "I2C dev is not ready.");
|
||||
mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
int ret = i2c_configure(this->i2c_dev_, this->dev_config_);
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "I2C: Failed to configure device");
|
||||
}
|
||||
|
||||
this->recovery_result_ = i2c_recover_bus(this->i2c_dev_);
|
||||
if (this->recovery_result_ != 0) {
|
||||
ESP_LOGE(TAG, "I2C recover bus failed, err %d", this->recovery_result_);
|
||||
}
|
||||
if (this->scan_) {
|
||||
ESP_LOGV(TAG, "Scanning I2C bus for active devices...");
|
||||
this->i2c_scan_();
|
||||
}
|
||||
}
|
||||
|
||||
void ZephyrI2CBus::dump_config() {
|
||||
auto get_speed = [](uint32_t dev_config) {
|
||||
switch (I2C_SPEED_GET(dev_config)) {
|
||||
case I2C_SPEED_STANDARD:
|
||||
return "100 kHz";
|
||||
case I2C_SPEED_FAST:
|
||||
return "400 kHz";
|
||||
case I2C_SPEED_FAST_PLUS:
|
||||
return "1 MHz";
|
||||
case I2C_SPEED_HIGH:
|
||||
return "3.4 MHz";
|
||||
case I2C_SPEED_ULTRA:
|
||||
return "5 MHz";
|
||||
}
|
||||
return "unknown";
|
||||
};
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"I2C Bus:\n"
|
||||
" SDA Pin: GPIO%u\n"
|
||||
" SCL Pin: GPIO%u\n"
|
||||
" Frequency: %s\n"
|
||||
" Name: %s",
|
||||
this->sda_pin_, this->scl_pin_, get_speed(this->dev_config_), this->i2c_dev_->name);
|
||||
|
||||
if (this->recovery_result_ != 0) {
|
||||
ESP_LOGCONFIG(TAG, " Recovery: failed, err %d", this->recovery_result_);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Recovery: bus successfully recovered");
|
||||
}
|
||||
if (this->scan_) {
|
||||
ESP_LOGI(TAG, "Results from I2C bus scan:");
|
||||
if (scan_results_.empty()) {
|
||||
ESP_LOGI(TAG, "Found no I2C devices!");
|
||||
} else {
|
||||
for (const auto &s : scan_results_) {
|
||||
if (s.second) {
|
||||
ESP_LOGI(TAG, "Found I2C device at address 0x%02X", s.first);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ZephyrI2CBus::write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count,
|
||||
uint8_t *read_buffer, size_t read_count) {
|
||||
if (!device_is_ready(this->i2c_dev_)) {
|
||||
return ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
i2c_msg msgs[2]{};
|
||||
size_t cnt = 0;
|
||||
uint8_t dst = 0x00; // dummy data to not use random value
|
||||
|
||||
if (read_count == 0 && write_count == 0) {
|
||||
msgs[cnt].buf = &dst;
|
||||
msgs[cnt].len = 0U;
|
||||
msgs[cnt++].flags = I2C_MSG_WRITE;
|
||||
} else {
|
||||
if (write_count) {
|
||||
// the same struct is used for read/write — const cast is fine; data isn't modified
|
||||
msgs[cnt].buf = const_cast<uint8_t *>(write_buffer);
|
||||
msgs[cnt].len = write_count;
|
||||
msgs[cnt++].flags = I2C_MSG_WRITE;
|
||||
}
|
||||
if (read_count) {
|
||||
msgs[cnt].buf = const_cast<uint8_t *>(read_buffer);
|
||||
msgs[cnt].len = read_count;
|
||||
msgs[cnt++].flags = I2C_MSG_READ | I2C_MSG_RESTART;
|
||||
}
|
||||
}
|
||||
|
||||
msgs[cnt - 1].flags |= I2C_MSG_STOP;
|
||||
|
||||
auto err = i2c_transfer(this->i2c_dev_, msgs, cnt, address);
|
||||
|
||||
if (err == -EIO) {
|
||||
return ERROR_NOT_ACKNOWLEDGED;
|
||||
}
|
||||
|
||||
if (err != 0) {
|
||||
ESP_LOGE(TAG, "i2c transfer error %d", err);
|
||||
return ERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
void ZephyrI2CBus::set_frequency(uint32_t frequency) {
|
||||
this->dev_config_ &= ~I2C_SPEED_MASK;
|
||||
if (frequency >= 400000) {
|
||||
this->dev_config_ |= I2C_SPEED_SET(I2C_SPEED_FAST);
|
||||
} else {
|
||||
this->dev_config_ |= I2C_SPEED_SET(I2C_SPEED_STANDARD);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::i2c
|
||||
|
||||
#endif
|
||||
38
esphome/components/i2c/i2c_bus_zephyr.h
Normal file
38
esphome/components/i2c/i2c_bus_zephyr.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ZEPHYR
|
||||
|
||||
#include "i2c_bus.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
struct device;
|
||||
|
||||
namespace esphome::i2c {
|
||||
|
||||
class ZephyrI2CBus : public InternalI2CBus, public Component {
|
||||
public:
|
||||
explicit ZephyrI2CBus(const device *i2c_dev) : i2c_dev_(i2c_dev) {}
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
ErrorCode write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count, uint8_t *read_buffer,
|
||||
size_t read_count) 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) { this->sda_pin_ = sda_pin; }
|
||||
void set_scl_pin(uint8_t scl_pin) { this->scl_pin_ = scl_pin; }
|
||||
void set_frequency(uint32_t frequency);
|
||||
|
||||
int get_port() const override { return 0; }
|
||||
|
||||
protected:
|
||||
const device *i2c_dev_;
|
||||
int recovery_result_ = 0;
|
||||
uint8_t sda_pin_{};
|
||||
uint8_t scl_pin_{};
|
||||
uint32_t dev_config_{};
|
||||
};
|
||||
|
||||
} // namespace esphome::i2c
|
||||
|
||||
#endif
|
||||
@@ -1,4 +1,5 @@
|
||||
from pathlib import Path
|
||||
import textwrap
|
||||
from typing import TypedDict
|
||||
|
||||
import esphome.codegen as cg
|
||||
@@ -90,7 +91,7 @@ def zephyr_add_prj_conf(
|
||||
|
||||
|
||||
def zephyr_add_overlay(content):
|
||||
zephyr_data()[KEY_OVERLAY] += content
|
||||
zephyr_data()[KEY_OVERLAY] += textwrap.dedent(content)
|
||||
|
||||
|
||||
def add_extra_build_file(filename: str, path: Path) -> bool:
|
||||
|
||||
4
tests/components/ads1115/test.nrf52-adafruit.yaml
Normal file
4
tests/components/ads1115/test.nrf52-adafruit.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
4
tests/components/ads1115/test.nrf52-mcumgr.yaml
Normal file
4
tests/components/ads1115/test.nrf52-mcumgr.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
4
tests/components/aht10/test.nrf52-adafruit.yaml
Normal file
4
tests/components/aht10/test.nrf52-adafruit.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
4
tests/components/i2c/test.nrf52-adafruit.yaml
Normal file
4
tests/components/i2c/test.nrf52-adafruit.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
4
tests/components/i2c/test.nrf52-mcumgr.yaml
Normal file
4
tests/components/i2c/test.nrf52-mcumgr.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
4
tests/components/i2c/test.nrf52-xiao-ble.yaml
Normal file
4
tests/components/i2c/test.nrf52-xiao-ble.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
11
tests/test_build_components/common/i2c/nrf52.yaml
Normal file
11
tests/test_build_components/common/i2c/nrf52.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
# Common I2C configuration for NRF52 tests
|
||||
|
||||
substitutions:
|
||||
scl_pin: P0.04
|
||||
sda_pin: P0.05
|
||||
|
||||
i2c:
|
||||
- id: i2c_bus
|
||||
scl: ${scl_pin}
|
||||
sda: ${sda_pin}
|
||||
scan: true
|
||||
Reference in New Issue
Block a user