[esp32_can] Support 20 kbit/s on ESP32 revision 2 or later (#20426)

This commit is contained in:
Christopher Reimer
2026-10-13 22:34:03 -04:00
committed by GitHub
parent 7b591b26d6
commit bb1119d83f
4 changed files with 91 additions and 0 deletions
+22
View File
@@ -6,6 +6,8 @@ import esphome.codegen as cg
from esphome.components import canbus
from esphome.components.canbus import CONF_BIT_RATE, CanbusComponent, CanSpeed
from esphome.components.esp32 import (
CONF_MINIMUM_CHIP_REVISION,
KEY_ESP32,
VARIANT_ESP32,
VARIANT_ESP32C3,
VARIANT_ESP32C5,
@@ -20,6 +22,8 @@ from esphome.components.esp32 import (
)
import esphome.config_validation as cv
from esphome.const import (
CONF_ADVANCED,
CONF_FRAMEWORK,
CONF_ID,
CONF_MODE,
CONF_RX_PIN,
@@ -27,6 +31,7 @@ from esphome.const import (
CONF_TX_PIN,
CONF_TX_QUEUE_LEN,
)
import esphome.final_validate as fv
from esphome.types import ConfigType
CODEOWNERS = ["@Sympatron"]
@@ -49,6 +54,7 @@ CAN_MODES = {
# See ESP-IDF Programming Guide --> API Reference --> Two-Wire Automotive Interface (TWAI)
CAN_SPEEDS_ESP32 = {
"20KBPS": CanSpeed.CAN_20KBPS,
"25KBPS": CanSpeed.CAN_25KBPS,
"50KBPS": CanSpeed.CAN_50KBPS,
"100KBPS": CanSpeed.CAN_100KBPS,
@@ -114,6 +120,22 @@ CONFIG_SCHEMA = canbus.CANBUS_SCHEMA.extend(
)
def _final_validate(config: ConfigType) -> None:
if get_esp32_variant() == VARIANT_ESP32 and config[CONF_BIT_RATE] == "20KBPS":
esp32_config = fv.full_config.get()[KEY_ESP32]
min_revision = esp32_config[CONF_FRAMEWORK][CONF_ADVANCED].get(
CONF_MINIMUM_CHIP_REVISION
)
if min_revision is None or min_revision < "2.0":
raise cv.Invalid(
"20KBPS on ESP32 requires esp32.framework.advanced.minimum_chip_revision: '2.0' or higher",
path=[CONF_BIT_RATE],
)
FINAL_VALIDATE_SCHEMA = _final_validate
def get_default_tx_enqueue_timeout(bit_rate: str) -> int:
bit_rate_numeric = canbus.get_rate(bit_rate)
bits_per_packet = 140 # ~max CAN message length
@@ -33,6 +33,10 @@ static bool get_bitrate(canbus::CanSpeed bitrate, twai_timing_config_t *t_config
case canbus::CAN_16KBPS:
*t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_16KBITS();
return true;
#endif
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || \
defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || \
defined(USE_ESP32_VARIANT_ESP32S3) || (defined(USE_ESP32_VARIANT_ESP32) && CONFIG_ESP32_REV_MIN_FULL >= 200)
case canbus::CAN_20KBPS:
*t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_20KBITS();
return true;
@@ -0,0 +1,11 @@
esp32:
framework:
advanced:
minimum_chip_revision: "2.0"
canbus:
- platform: esp32_can
rx_pin: GPIO14
tx_pin: GPIO12
can_id: 4
bit_rate: 20kbps
@@ -0,0 +1,54 @@
"""Tests for ESP32 CAN bitrate validation."""
import pytest
from esphome.components.esp32 import KEY_ESP32, KEY_VARIANT
from esphome.components.esp32_can import canbus
import esphome.config_validation as cv
from esphome.core import CORE
import esphome.final_validate as fv
@pytest.mark.parametrize(
("revision", "supported"),
[
(None, False),
("0.0", False),
("1.0", False),
("1.1", False),
("2.0", True),
("3.0", True),
("3.1", True),
],
)
def test_esp32_20kbps_minimum_revision(revision: str | None, supported: bool) -> None:
"""Require the chip revision needed by the ESP-IDF timing macro."""
CORE.data[KEY_ESP32] = {KEY_VARIANT: "ESP32"}
advanced = {} if revision is None else {"minimum_chip_revision": revision}
token = fv.full_config.set({"esp32": {"framework": {"advanced": advanced}}})
try:
config = {"bit_rate": canbus.validate_bit_rate("20kbps")}
if supported:
canbus.FINAL_VALIDATE_SCHEMA(config)
else:
with pytest.raises(cv.Invalid, match="minimum_chip_revision") as error:
canbus.FINAL_VALIDATE_SCHEMA(config)
assert error.value.path == ["bit_rate"]
finally:
fv.full_config.reset(token)
@pytest.mark.parametrize("variant", ["ESP32", "ESP32C3", "ESP32S3"])
def test_other_bitrates(variant: str) -> None:
"""Existing bitrates must not require a minimum chip revision."""
CORE.data[KEY_ESP32] = {KEY_VARIANT: variant}
config = {"bit_rate": canbus.validate_bit_rate("25kbps")}
canbus.FINAL_VALIDATE_SCHEMA(config)
@pytest.mark.parametrize("variant", ["ESP32C3", "ESP32S2", "ESP32S3", "ESP32C6"])
def test_other_variants_20kbps(variant: str) -> None:
"""Other supported ESP32 variants must retain 20 kbit/s support."""
CORE.data[KEY_ESP32] = {KEY_VARIANT: variant}
config = {"bit_rate": canbus.validate_bit_rate("20kbps")}
canbus.FINAL_VALIDATE_SCHEMA(config)