mirror of
https://github.com/esphome/esphome.git
synced 2026-01-10 04:00:51 -07:00
[esp8266] Add enable_serial/enable_serial1 helpers to exclude unused Serial objects (#12736)
This commit is contained in:
@@ -23,12 +23,18 @@ from esphome.helpers import copy_file_if_changed
|
||||
from .boards import BOARDS, ESP8266_LD_SCRIPTS
|
||||
from .const import (
|
||||
CONF_EARLY_PIN_INIT,
|
||||
CONF_ENABLE_SERIAL,
|
||||
CONF_ENABLE_SERIAL1,
|
||||
CONF_RESTORE_FROM_FLASH,
|
||||
KEY_BOARD,
|
||||
KEY_ESP8266,
|
||||
KEY_FLASH_SIZE,
|
||||
KEY_PIN_INITIAL_STATES,
|
||||
KEY_SERIAL1_REQUIRED,
|
||||
KEY_SERIAL_REQUIRED,
|
||||
KEY_WAVEFORM_REQUIRED,
|
||||
enable_serial,
|
||||
enable_serial1,
|
||||
esp8266_ns,
|
||||
)
|
||||
from .gpio import PinInitialState, add_pin_initial_states_array
|
||||
@@ -171,6 +177,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_BOARD_FLASH_MODE, default="dout"): cv.one_of(
|
||||
*BUILD_FLASH_MODES, lower=True
|
||||
),
|
||||
cv.Optional(CONF_ENABLE_SERIAL): cv.boolean,
|
||||
cv.Optional(CONF_ENABLE_SERIAL1): cv.boolean,
|
||||
}
|
||||
),
|
||||
set_core_data,
|
||||
@@ -231,6 +239,12 @@ async def to_code(config):
|
||||
if config[CONF_EARLY_PIN_INIT]:
|
||||
cg.add_define("USE_ESP8266_EARLY_PIN_INIT")
|
||||
|
||||
# Allow users to force-enable Serial objects for use in lambdas or external libraries
|
||||
if config.get(CONF_ENABLE_SERIAL):
|
||||
enable_serial()
|
||||
if config.get(CONF_ENABLE_SERIAL1):
|
||||
enable_serial1()
|
||||
|
||||
# Arduino 2 has a non-standards conformant new that returns a nullptr instead of failing when
|
||||
# out of memory and exceptions are disabled. Since Arduino 2.6.0, this flag can be used to make
|
||||
# new abort instead. Use it so that OOM fails early (on allocation) instead of on dereference of
|
||||
@@ -271,6 +285,7 @@ async def to_code(config):
|
||||
|
||||
CORE.add_job(add_pin_initial_states_array)
|
||||
CORE.add_job(finalize_waveform_config)
|
||||
CORE.add_job(finalize_serial_config)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.WORKAROUNDS)
|
||||
@@ -286,6 +301,24 @@ async def finalize_waveform_config() -> None:
|
||||
cg.add_build_flag("-DUSE_ESP8266_WAVEFORM_STUBS")
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.WORKAROUNDS)
|
||||
async def finalize_serial_config() -> None:
|
||||
"""Exclude unused Arduino Serial objects from the build.
|
||||
|
||||
This runs at WORKAROUNDS priority (-999) to ensure all components
|
||||
have had a chance to call enable_serial() or enable_serial1() first.
|
||||
|
||||
The Arduino ESP8266 core defines two global Serial objects (32 bytes each).
|
||||
By adding NO_GLOBAL_SERIAL or NO_GLOBAL_SERIAL1 build flags, we prevent
|
||||
unused Serial objects from being linked, saving 32 bytes each.
|
||||
"""
|
||||
esp8266_data = CORE.data.get(KEY_ESP8266, {})
|
||||
if not esp8266_data.get(KEY_SERIAL_REQUIRED, False):
|
||||
cg.add_build_flag("-DNO_GLOBAL_SERIAL")
|
||||
if not esp8266_data.get(KEY_SERIAL1_REQUIRED, False):
|
||||
cg.add_build_flag("-DNO_GLOBAL_SERIAL1")
|
||||
|
||||
|
||||
# Called by writer.py
|
||||
def copy_files() -> None:
|
||||
dir = Path(__file__).parent
|
||||
|
||||
@@ -6,8 +6,12 @@ KEY_BOARD = "board"
|
||||
KEY_PIN_INITIAL_STATES = "pin_initial_states"
|
||||
CONF_RESTORE_FROM_FLASH = "restore_from_flash"
|
||||
CONF_EARLY_PIN_INIT = "early_pin_init"
|
||||
CONF_ENABLE_SERIAL = "enable_serial"
|
||||
CONF_ENABLE_SERIAL1 = "enable_serial1"
|
||||
KEY_FLASH_SIZE = "flash_size"
|
||||
KEY_WAVEFORM_REQUIRED = "waveform_required"
|
||||
KEY_SERIAL_REQUIRED = "serial_required"
|
||||
KEY_SERIAL1_REQUIRED = "serial1_required"
|
||||
|
||||
# esp8266 namespace is already defined by arduino, manually prefix esphome
|
||||
esp8266_ns = cg.global_ns.namespace("esphome").namespace("esp8266")
|
||||
@@ -29,3 +33,35 @@ def require_waveform() -> None:
|
||||
require_waveform()
|
||||
"""
|
||||
CORE.data.setdefault(KEY_ESP8266, {})[KEY_WAVEFORM_REQUIRED] = True
|
||||
|
||||
|
||||
def enable_serial() -> None:
|
||||
"""Mark that Arduino Serial (UART0) is required.
|
||||
|
||||
Call this from components that use the global Serial object.
|
||||
If no component calls this, Serial is excluded from the build
|
||||
to save 32 bytes of RAM.
|
||||
|
||||
Example:
|
||||
from esphome.components.esp8266.const import enable_serial
|
||||
|
||||
async def to_code(config):
|
||||
enable_serial()
|
||||
"""
|
||||
CORE.data.setdefault(KEY_ESP8266, {})[KEY_SERIAL_REQUIRED] = True
|
||||
|
||||
|
||||
def enable_serial1() -> None:
|
||||
"""Mark that Arduino Serial1 (UART1) is required.
|
||||
|
||||
Call this from components that use the global Serial1 object.
|
||||
If no component calls this, Serial1 is excluded from the build
|
||||
to save 32 bytes of RAM.
|
||||
|
||||
Example:
|
||||
from esphome.components.esp8266.const import enable_serial1
|
||||
|
||||
async def to_code(config):
|
||||
enable_serial1()
|
||||
"""
|
||||
CORE.data.setdefault(KEY_ESP8266, {})[KEY_SERIAL1_REQUIRED] = True
|
||||
|
||||
@@ -337,6 +337,18 @@ async def to_code(config):
|
||||
is_at_least_very_verbose = this_severity >= very_verbose_severity
|
||||
has_serial_logging = baud_rate != 0
|
||||
|
||||
# Add defines for which Serial object is needed (allows linker to exclude unused)
|
||||
if CORE.is_esp8266:
|
||||
from esphome.components.esp8266.const import enable_serial, enable_serial1
|
||||
|
||||
hw_uart = config.get(CONF_HARDWARE_UART, UART0)
|
||||
if has_serial_logging and hw_uart in (UART0, UART0_SWAP):
|
||||
cg.add_define("USE_ESP8266_LOGGER_SERIAL")
|
||||
enable_serial()
|
||||
elif has_serial_logging and hw_uart == UART1:
|
||||
cg.add_define("USE_ESP8266_LOGGER_SERIAL1")
|
||||
enable_serial1()
|
||||
|
||||
if (
|
||||
(CORE.is_esp8266 or CORE.is_rp2040)
|
||||
and has_serial_logging
|
||||
|
||||
@@ -7,26 +7,21 @@ namespace esphome::logger {
|
||||
static const char *const TAG = "logger";
|
||||
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
case UART_SELECTION_UART0_SWAP:
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
if (this->uart_ == UART_SELECTION_UART0_SWAP) {
|
||||
Serial.swap();
|
||||
}
|
||||
Serial.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
|
||||
break;
|
||||
case UART_SELECTION_UART1:
|
||||
this->hw_serial_ = &Serial1;
|
||||
Serial1.begin(this->baud_rate_);
|
||||
Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
uart_set_debug(UART_NO);
|
||||
#if defined(USE_ESP8266_LOGGER_SERIAL)
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
if (this->uart_ == UART_SELECTION_UART0_SWAP) {
|
||||
Serial.swap();
|
||||
}
|
||||
Serial.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
|
||||
#elif defined(USE_ESP8266_LOGGER_SERIAL1)
|
||||
this->hw_serial_ = &Serial1;
|
||||
Serial1.begin(this->baud_rate_);
|
||||
Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
|
||||
#else
|
||||
// No serial logging - disable debug output
|
||||
uart_set_debug(UART_NO);
|
||||
#endif
|
||||
|
||||
global_logger = this;
|
||||
|
||||
@@ -39,15 +34,16 @@ void HOT Logger::write_msg_(const char *msg, size_t len) {
|
||||
}
|
||||
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
return LOG_STR("UART0");
|
||||
case UART_SELECTION_UART1:
|
||||
return LOG_STR("UART1");
|
||||
case UART_SELECTION_UART0_SWAP:
|
||||
default:
|
||||
return LOG_STR("UART0_SWAP");
|
||||
#if defined(USE_ESP8266_LOGGER_SERIAL)
|
||||
if (this->uart_ == UART_SELECTION_UART0_SWAP) {
|
||||
return LOG_STR("UART0_SWAP");
|
||||
}
|
||||
return LOG_STR("UART0");
|
||||
#elif defined(USE_ESP8266_LOGGER_SERIAL1)
|
||||
return LOG_STR("UART1");
|
||||
#else
|
||||
return LOG_STR("NONE");
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace esphome::logger
|
||||
|
||||
@@ -378,6 +378,28 @@ async def to_code(config):
|
||||
if CONF_DEBUG in config:
|
||||
await debug_to_code(config[CONF_DEBUG], var)
|
||||
|
||||
# ESP8266: Enable the Arduino Serial objects that might be used based on pin config
|
||||
# The C++ code selects hardware serial at runtime based on these pin combinations:
|
||||
# - Serial (UART0): TX=1 or null, RX=3 or null
|
||||
# - Serial (UART0 swap): TX=15 or null, RX=13 or null
|
||||
# - Serial1: TX=2 or null, RX=8 or null
|
||||
if CORE.is_esp8266:
|
||||
from esphome.components.esp8266.const import enable_serial, enable_serial1
|
||||
|
||||
tx_num = config[CONF_TX_PIN][CONF_NUMBER] if CONF_TX_PIN in config else None
|
||||
rx_num = config[CONF_RX_PIN][CONF_NUMBER] if CONF_RX_PIN in config else None
|
||||
|
||||
# Check if this config could use Serial (UART0 regular or swap)
|
||||
if (tx_num is None or tx_num in (1, 15)) and (
|
||||
rx_num is None or rx_num in (3, 13)
|
||||
):
|
||||
enable_serial()
|
||||
cg.add_define("USE_ESP8266_UART_SERIAL")
|
||||
# Check if this config could use Serial1
|
||||
if (tx_num is None or tx_num == 2) and (rx_num is None or rx_num == 8):
|
||||
enable_serial1()
|
||||
cg.add_define("USE_ESP8266_UART_SERIAL1")
|
||||
|
||||
CORE.add_job(final_step)
|
||||
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ void ESP8266UartComponent::setup() {
|
||||
// is 1 we still want to use Serial.
|
||||
SerialConfig config = static_cast<SerialConfig>(get_config());
|
||||
|
||||
#ifdef USE_ESP8266_UART_SERIAL
|
||||
if (!ESP8266UartComponent::serial0_in_use && (tx_pin_ == nullptr || tx_pin_->get_pin() == 1) &&
|
||||
(rx_pin_ == nullptr || rx_pin_->get_pin() == 3)
|
||||
#ifdef USE_LOGGER
|
||||
@@ -100,11 +101,16 @@ void ESP8266UartComponent::setup() {
|
||||
this->hw_serial_->setRxBufferSize(this->rx_buffer_size_);
|
||||
this->hw_serial_->swap();
|
||||
ESP8266UartComponent::serial0_in_use = true;
|
||||
} else if ((tx_pin_ == nullptr || tx_pin_->get_pin() == 2) && (rx_pin_ == nullptr || rx_pin_->get_pin() == 8)) {
|
||||
} else
|
||||
#endif // USE_ESP8266_UART_SERIAL
|
||||
#ifdef USE_ESP8266_UART_SERIAL1
|
||||
if ((tx_pin_ == nullptr || tx_pin_->get_pin() == 2) && (rx_pin_ == nullptr || rx_pin_->get_pin() == 8)) {
|
||||
this->hw_serial_ = &Serial1;
|
||||
this->hw_serial_->begin(this->baud_rate_, config);
|
||||
this->hw_serial_->setRxBufferSize(this->rx_buffer_size_);
|
||||
} else {
|
||||
} else
|
||||
#endif // USE_ESP8266_UART_SERIAL1
|
||||
{
|
||||
this->sw_serial_ = new ESP8266SoftwareSerial(); // NOLINT
|
||||
this->sw_serial_->setup(tx_pin_, rx_pin_, this->baud_rate_, this->stop_bits_, this->data_bits_, this->parity_,
|
||||
this->rx_buffer_size_);
|
||||
|
||||
@@ -248,7 +248,11 @@
|
||||
#define USE_ADC_SENSOR_VCC
|
||||
#define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 1, 2)
|
||||
#define USE_CAPTIVE_PORTAL
|
||||
#define USE_ESP8266_LOGGER_SERIAL
|
||||
#define USE_ESP8266_LOGGER_SERIAL1
|
||||
#define USE_ESP8266_PREFERENCES_FLASH
|
||||
#define USE_ESP8266_UART_SERIAL
|
||||
#define USE_ESP8266_UART_SERIAL1
|
||||
#define USE_HTTP_REQUEST_ESP8266_HTTPS
|
||||
#define USE_HTTP_REQUEST_RESPONSE
|
||||
#define USE_I2C
|
||||
|
||||
Reference in New Issue
Block a user