diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 77ccaf52c1..4703a72f37 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -28,6 +28,8 @@ from .const import ( KEY_ESP8266, KEY_FLASH_SIZE, KEY_PIN_INITIAL_STATES, + KEY_SERIAL1_REQUIRED, + KEY_SERIAL_REQUIRED, KEY_WAVEFORM_REQUIRED, esp8266_ns, ) @@ -271,6 +273,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 +289,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 diff --git a/esphome/components/esp8266/const.py b/esphome/components/esp8266/const.py index 14425cde68..fec4c7a2e8 100644 --- a/esphome/components/esp8266/const.py +++ b/esphome/components/esp8266/const.py @@ -8,6 +8,8 @@ CONF_RESTORE_FROM_FLASH = "restore_from_flash" CONF_EARLY_PIN_INIT = "early_pin_init" 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 +31,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 diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index f1d714a810..c1b3069bf9 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -339,19 +339,15 @@ async def to_code(config): # 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") - # Exclude Serial1 from Arduino build - cg.add_build_flag("-DNO_GLOBAL_SERIAL1") + enable_serial() elif has_serial_logging and hw_uart == UART1: cg.add_define("USE_ESP8266_LOGGER_SERIAL1") - # Exclude Serial from Arduino build - cg.add_build_flag("-DNO_GLOBAL_SERIAL") - else: - # No serial logging - exclude both - cg.add_build_flag("-DNO_GLOBAL_SERIAL") - cg.add_build_flag("-DNO_GLOBAL_SERIAL1") + enable_serial1() if ( (CORE.is_esp8266 or CORE.is_rp2040) diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index 9baa6ebd81..9e8917419d 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -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) diff --git a/esphome/components/uart/uart_component.h b/esphome/components/uart/uart_component.h index fd528e228f..ea6e1562f4 100644 --- a/esphome/components/uart/uart_component.h +++ b/esphome/components/uart/uart_component.h @@ -189,10 +189,10 @@ class UARTComponent { size_t rx_buffer_size_; size_t rx_full_threshold_{1}; size_t rx_timeout_{0}; - uint32_t baud_rate_; - uint8_t stop_bits_; - uint8_t data_bits_; - UARTParityOptions parity_; + uint32_t baud_rate_{0}; + uint8_t stop_bits_{0}; + uint8_t data_bits_{0}; + UARTParityOptions parity_{UART_CONFIG_PARITY_NONE}; #ifdef USE_UART_DEBUGGER CallbackManager debug_callback_{}; #endif diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index c78daa7462..504d494e2e 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -75,6 +75,7 @@ void ESP8266UartComponent::setup() { // is 1 we still want to use Serial. SerialConfig config = static_cast(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_); diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 1fddc426d4..69599aa4f5 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -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