[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:
tomaszduda23
2025-11-03 08:42:49 -08:00
committed by GitHub
parent cb039b42aa
commit 06d0787ee0
12 changed files with 276 additions and 6 deletions

View File

@@ -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},
}
)

View File

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

View 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

View 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

View File

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

View File

@@ -0,0 +1,4 @@
packages:
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
<<: !include common.yaml

View File

@@ -0,0 +1,4 @@
packages:
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
<<: !include common.yaml

View File

@@ -0,0 +1,4 @@
packages:
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
<<: !include common.yaml

View File

@@ -0,0 +1,4 @@
packages:
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
<<: !include common.yaml

View File

@@ -0,0 +1,4 @@
packages:
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
<<: !include common.yaml

View File

@@ -0,0 +1,4 @@
packages:
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
<<: !include common.yaml

View 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