ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
uart_component_esp8266.cpp
Go to the documentation of this file.
1#ifdef USE_ESP8266
3#include <esp8266_peri.h>
7#include "esphome/core/log.h"
8#ifdef USE_UART_WAKE_LOOP_ON_RX
9#include "esphome/core/wake.h"
10#endif
11
12#ifdef USE_LOGGER
14#endif
15
16namespace esphome::uart {
17
18ESPHOME_LOG_TAG(TAG, "uart");
19// Edge decoder up to this baud rate; at 38400 WiFi interrupt latency corrupts bytes.
20static constexpr uint32_t SW_SERIAL_EDGE_MODE_MAX_BAUD = 19200;
21// Edge decoder time base: 1/16 us from the 1 MHz system timer, which keeps its rate during a
22// CpuFrequencyBoost; the cycle counter does not.
23static constexpr uint32_t RX_TICKS_PER_SECOND = 16000000;
24__attribute__((always_inline)) static inline uint32_t rx_now() { return micros() << 4; }
25bool ESP8266UartComponent::serial0_in_use = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
26
27bool ESP8266UartComponent::serial0_swapped = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
28
30 uint32_t config = 0;
31
32 if (this->parity_ == UART_CONFIG_PARITY_NONE) {
33 config |= UART_PARITY_NONE;
34 } else if (this->parity_ == UART_CONFIG_PARITY_EVEN) {
35 config |= UART_PARITY_EVEN;
36 } else if (this->parity_ == UART_CONFIG_PARITY_ODD) {
37 config |= UART_PARITY_ODD;
38 }
39
40 switch (this->data_bits_) {
41 case 5:
42 config |= UART_NB_BIT_5;
43 break;
44 case 6:
45 config |= UART_NB_BIT_6;
46 break;
47 case 7:
48 config |= UART_NB_BIT_7;
49 break;
50 case 8:
51 config |= UART_NB_BIT_8;
52 break;
53 }
54
55 if (this->stop_bits_ == 1) {
56 config |= UART_NB_STOP_BIT_1;
57 } else {
58 config |= UART_NB_STOP_BIT_2;
59 }
60
61 if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted())
62 config |= BIT(22);
63 if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted())
64 config |= BIT(19);
65
66 return config;
67}
68
70 auto setup_pin_if_needed = [](InternalGPIOPin *pin) {
71 if (!pin) {
72 return;
73 }
75 if ((pin->get_flags() & mask) != gpio::Flags::FLAG_NONE) {
76 pin->setup();
77 }
78 };
79
80 setup_pin_if_needed(this->rx_pin_);
81 if (this->rx_pin_ != this->tx_pin_) {
82 setup_pin_if_needed(this->tx_pin_);
83 }
84
85 // Use Arduino HardwareSerial UARTs if all used pins match the ones
86 // preconfigured by the platform. For example if RX disabled but TX pin
87 // is 1 we still want to use Serial.
88 SerialConfig config = static_cast<SerialConfig>(get_config());
89
90#ifdef USE_ESP8266_UART_SERIAL
91 if (!ESP8266UartComponent::serial0_in_use && (tx_pin_ == nullptr || tx_pin_->get_pin() == 1) &&
92 (rx_pin_ == nullptr || rx_pin_->get_pin() == 3)
93#ifdef USE_LOGGER
94 // we will use UART0 if logger isn't using it in swapped mode
95 && (logger::global_logger->get_hw_serial() == nullptr ||
97#endif
98 ) {
99 this->hw_serial_ = &Serial;
100 this->hw_serial_->begin(this->baud_rate_, config);
101 this->hw_serial_->setRxBufferSize(this->rx_buffer_size_);
102 ESP8266UartComponent::serial0_in_use = true;
103 } else if (!ESP8266UartComponent::serial0_in_use && (tx_pin_ == nullptr || tx_pin_->get_pin() == 15) &&
104 (rx_pin_ == nullptr || rx_pin_->get_pin() == 13)
105#ifdef USE_LOGGER
106 // we will use UART0 swapped if logger isn't using it in regular mode
107 && (logger::global_logger->get_hw_serial() == nullptr ||
109#endif
110 ) {
111 this->hw_serial_ = &Serial;
112 this->hw_serial_->begin(this->baud_rate_, config);
113 this->hw_serial_->setRxBufferSize(this->rx_buffer_size_);
114 this->hw_serial_->swap();
115 ESP8266UartComponent::serial0_in_use = true;
116 ESP8266UartComponent::serial0_swapped = true;
117 } else
118#endif // USE_ESP8266_UART_SERIAL
119#ifdef USE_ESP8266_UART_SERIAL1
120 if ((tx_pin_ == nullptr || tx_pin_->get_pin() == 2) && (rx_pin_ == nullptr || rx_pin_->get_pin() == 8)) {
121 this->hw_serial_ = &Serial1;
122 this->hw_serial_->begin(this->baud_rate_, config);
123 this->hw_serial_->setRxBufferSize(this->rx_buffer_size_);
124 } else
125#endif // USE_ESP8266_UART_SERIAL1
126 {
127 this->sw_serial_ = new ESP8266SoftwareSerial(); // NOLINT
128 this->sw_serial_->setup(tx_pin_, rx_pin_, this->baud_rate_, this->stop_bits_, this->data_bits_, this->parity_,
129 this->rx_buffer_size_);
130 }
131}
132
134 ESP_LOGCONFIG(TAG, "Loading UART bus settings");
135 if (this->hw_serial_ != nullptr) {
136 SerialConfig config = static_cast<SerialConfig>(get_config());
137 this->hw_serial_->begin(this->baud_rate_, config);
138 this->hw_serial_->setRxBufferSize(this->rx_buffer_size_);
139#ifdef USE_ESP8266_UART_SERIAL
140 // begin() puts UART0 back on GPIO1/GPIO3, so swap it again like setup() did for GPIO15/GPIO13
141 if (this->hw_serial_ == &Serial && ESP8266UartComponent::serial0_swapped)
142 this->hw_serial_->swap();
143#endif // USE_ESP8266_UART_SERIAL
144 } else {
145 this->sw_serial_->setup(this->tx_pin_, this->rx_pin_, this->baud_rate_, this->stop_bits_, this->data_bits_,
146 this->parity_, this->rx_buffer_size_);
147 }
148 if (dump_config) {
149 ESP_LOGCONFIG(TAG, "UART bus was reloaded.");
150 this->dump_config();
151 }
152}
153
155 ESP_LOGCONFIG(TAG, "UART Bus:");
156 LOG_PIN(" TX Pin: ", this->tx_pin_);
157 LOG_PIN(" RX Pin: ", this->rx_pin_);
158 if (this->rx_pin_ != nullptr) {
159 ESP_LOGCONFIG(TAG, " RX Buffer Size: %u", this->rx_buffer_size_); // NOLINT
160 }
161 ESP_LOGCONFIG(TAG,
162 " Baud Rate: %u baud\n"
163 " Data Bits: %u\n"
164 " Parity: %s\n"
165 " Stop bits: %u",
166 this->baud_rate_, this->data_bits_, LOG_STR_ARG(parity_to_str(this->parity_)), this->stop_bits_);
167 if (this->hw_serial_ != nullptr) {
168 ESP_LOGCONFIG(TAG, " Using hardware serial interface.");
169 } else {
170 ESP_LOGCONFIG(TAG, " Using software serial"
171#ifdef USE_UART_WAKE_LOOP_ON_RX
172 "\n Wake on data RX: ENABLED"
173#endif
174 );
175 if (this->rx_pin_ != nullptr) {
176 ESP_LOGCONFIG(TAG, " RX decoder: %s",
177 this->baud_rate_ <= SW_SERIAL_EDGE_MODE_MAX_BAUD ? LOG_STR_LITERAL("edge")
178 : LOG_STR_LITERAL("start bit sampler"));
179 }
180 }
181 this->check_logger_conflict();
182}
183
185#ifdef USE_LOGGER
186 if (this->hw_serial_ == nullptr || logger::global_logger->get_baud_rate() == 0) {
187 return;
188 }
189
190 if (this->hw_serial_ == logger::global_logger->get_hw_serial()) {
191 ESP_LOGW(TAG, " You're using the same serial port for logging and the UART component. Please "
192 "disable logging over the serial port by setting logger->baud_rate to 0.");
193 }
194#endif
195}
196
197void ESP8266UartComponent::write_array(const uint8_t *data, size_t len) {
198 if (this->hw_serial_ != nullptr) {
199 this->hw_serial_->write(data, len);
200 } else {
201 for (size_t i = 0; i < len; i++)
202 this->sw_serial_->write_byte(data[i]);
203 }
204#ifdef USE_UART_DEBUGGER
205 for (size_t i = 0; i < len; i++) {
206 this->debug_callback_.call(UART_DIRECTION_TX, data[i]);
207 }
208#endif
209}
211 if (!this->check_read_timeout_())
212 return false;
213 if (this->hw_serial_ != nullptr) {
214 *data = this->hw_serial_->peek();
215 } else {
216 *data = this->sw_serial_->peek_byte();
217 }
218 return true;
219}
220bool ESP8266UartComponent::read_array(uint8_t *data, size_t len) {
221 if (!this->check_read_timeout_(len))
222 return false;
223 if (this->hw_serial_ != nullptr) {
224 this->hw_serial_->readBytes(data, len);
225 } else {
226 for (size_t i = 0; i < len; i++)
227 data[i] = this->sw_serial_->read_byte();
228 }
229#ifdef USE_UART_DEBUGGER
230 for (size_t i = 0; i < len; i++) {
231 this->debug_callback_.call(UART_DIRECTION_RX, data[i]);
232 }
233#endif
234 return true;
235}
237 if (this->hw_serial_ != nullptr) {
238 return this->hw_serial_->available();
239 } else {
240 return this->sw_serial_->available();
241 }
242}
244 if (this->hw_serial_ != nullptr) {
245 return this->hw_serial_->availableForWrite();
246 }
247 return SIZE_MAX; // software serial bit-bangs each byte synchronously; there is no buffer to fill
248}
250 ESP_LOGVV(TAG, " Flushing");
251 if (this->hw_serial_ != nullptr) {
252 this->hw_serial_->flush();
253 } else {
254 this->sw_serial_->flush();
255 }
257}
259 uint8_t stop_bits, uint32_t data_bits, UARTParityOptions parity,
260 size_t rx_buffer_size) {
261 // load_settings() re-enters here
262 if (this->gpio_rx_pin_ != nullptr)
264 this->bit_time_ = F_CPU / baud_rate;
265 this->stop_bits_ = stop_bits;
266 this->data_bits_ = data_bits;
267 this->parity_ = parity;
268 // to_isr() allocates, so call it only for a new or changed pin
269 if (tx_pin != nullptr) {
270 if (tx_pin != this->gpio_tx_pin_)
271 this->tx_pin_ = tx_pin->to_isr();
272 gpio_tx_pin_ = tx_pin;
275 }
276 if (rx_pin != nullptr) {
277 if (rx_pin != this->gpio_rx_pin_)
278 this->rx_pin_ = rx_pin->to_isr();
279 gpio_rx_pin_ = rx_pin;
281 uint8_t *buffer = this->rx_.buffer();
282 if (this->rx_.buffer_size() != rx_buffer_size) {
283 delete[] buffer; // NOLINT
284 buffer = new uint8_t[rx_buffer_size]; // NOLINT
285 }
286 this->rx_.setup((RX_TICKS_PER_SECOND + baud_rate / 2) / baud_rate, data_bits, parity != UART_CONFIG_PARITY_NONE,
287 stop_bits, buffer, rx_buffer_size);
288 this->rx_.reset(rx_now(), this->rx_pin_.digital_read());
289 if (baud_rate <= SW_SERIAL_EDGE_MODE_MAX_BAUD) {
291 } else {
293 }
294 }
295}
296// A byte can arrive while a CpuFrequencyBoost has an 80 MHz build at 160 MHz; the clock select bit doubles
297// the bit time then. The whole byte is read inside the ISR, so the clock cannot change partway through.
298__attribute__((always_inline)) static inline uint32_t rx_bit_time(uint32_t bit_time) {
299#if F_CPU != 160000000L
300 // NOLINTNEXTLINE(clang-analyzer-core.FixedAddressDereference) -- CPU2X is MMIO at a fixed address
301 return bit_time << (CPU2X & 1);
302#else
303 return bit_time;
304#endif
305}
306
308 const uint32_t bit_time = rx_bit_time(arg->bit_time_);
309 uint32_t wait = bit_time + bit_time / 3 - 500;
310 const uint32_t start = arch_get_cpu_cycle_count();
311 uint8_t rec = 0;
312 // Manually unroll the loop
313 for (int i = 0; i < arg->data_bits_; i++)
314 rec |= arg->read_bit_(&wait, start, bit_time) << i;
315
316 /* If parity is enabled, just read it and ignore it. */
317 /* TODO: Should we check parity? Or is it too slow for nothing added..*/
319 arg->read_bit_(&wait, start, bit_time);
320
321 // Stop bit
322 arg->wait_(&wait, start, bit_time);
323 if (arg->stop_bits_ == 2)
324 arg->wait_(&wait, start, bit_time);
325
326 arg->rx_.push_byte(rec);
327 // Clear RX pin so that the interrupt doesn't re-trigger right away again.
329#ifdef USE_UART_WAKE_LOOP_ON_RX
330 // Wake the main loop so the consuming component drains the byte promptly
331 // instead of waiting for the next loop_interval_ tick. Important for timing
332 // sensitive setups that poll read() in a tight loop (e.g. fingerprint_grow).
334#endif
335}
337 const uint32_t now = rx_now();
338 const bool level = arg->rx_pin_.digital_read();
339 [[maybe_unused]] const bool wake = arg->rx_.on_edge(now, level);
340#ifdef USE_UART_WAKE_LOOP_ON_RX
341 if (wake)
343#endif
344}
346 if (!this->rx_.finalize_due(rx_now())) {
347#ifdef USE_UART_WAKE_LOOP_ON_RX
348 // Tail not elapsed; wake again only once drained
349 if (this->rx_.available() == 0)
351#endif
352 return;
353 }
355 this->rx_.finalize(rx_now());
356}
357void IRAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) {
358 if (this->gpio_tx_pin_ == nullptr) {
359 ESP_LOGE(TAG, "UART doesn't have TX pins set!");
360 return;
361 }
362 bool parity_bit = false;
363 bool need_parity_bit = true;
364 if (this->parity_ == UART_CONFIG_PARITY_EVEN) {
365 parity_bit = false;
366 } else if (this->parity_ == UART_CONFIG_PARITY_ODD) {
367 parity_bit = true;
368 } else {
369 need_parity_bit = false;
370 }
371
372 {
373 // Transmit runs from the main loop and never overlaps a CpuFrequencyBoost
375 const uint32_t bit_time = this->bit_time_;
376 uint32_t wait = bit_time;
377 const uint32_t start = arch_get_cpu_cycle_count();
378 // Start bit
379 this->write_bit_(false, &wait, start, bit_time);
380 for (int i = 0; i < this->data_bits_; i++) {
381 bool bit = data & (1 << i);
382 this->write_bit_(bit, &wait, start, bit_time);
383 if (need_parity_bit)
384 parity_bit ^= bit;
385 }
386 if (need_parity_bit)
387 this->write_bit_(parity_bit, &wait, start, bit_time);
388 // Stop bit
389 this->write_bit_(true, &wait, start, bit_time);
390 if (this->stop_bits_ == 2)
391 this->wait_(&wait, start, bit_time);
392 }
393}
394void IRAM_ATTR ESP8266SoftwareSerial::wait_(uint32_t *wait, const uint32_t &start, uint32_t bit_time) {
395 while (arch_get_cpu_cycle_count() - start < *wait)
396 ;
397 *wait += bit_time;
398}
399bool IRAM_ATTR ESP8266SoftwareSerial::read_bit_(uint32_t *wait, const uint32_t &start, uint32_t bit_time) {
400 this->wait_(wait, start, bit_time);
401 return this->rx_pin_.digital_read();
402}
403void IRAM_ATTR ESP8266SoftwareSerial::write_bit_(bool bit, uint32_t *wait, const uint32_t &start, uint32_t bit_time) {
404 this->tx_pin_.digital_write(bit);
405 this->wait_(wait, start, bit_time);
406}
408 this->rx_sync_();
409 return this->rx_.read_byte();
410}
412 this->rx_sync_();
413 return this->rx_.peek_byte();
414}
416 // Flush is a NO-OP with software serial, all bytes are written immediately.
417}
419 this->rx_sync_();
420 return this->rx_.available();
421}
422
423} // namespace esphome::uart
424#endif // USE_ESP8266
void digital_write(bool value)
Definition gpio.cpp:227
Helper class to disable interrupts.
Definition helpers.h:2164
void attach_interrupt(void(*func)(T *), T *arg, gpio::InterruptType type) const
Definition gpio.h:29
ISRInternalGPIOPin to_isr() const
Definition gpio.cpp:91
Stream * get_hw_serial() const
Definition logger.h:159
UARTSelection get_uart() const
Get the UART used by the logger.
Definition logger.h:171
void write_bit_(bool bit, uint32_t *wait, const uint32_t &start, uint32_t bit_time)
static void gpio_intr_edge(ESP8266SoftwareSerial *arg)
bool read_bit_(uint32_t *wait, const uint32_t &start, uint32_t bit_time)
void wait_(uint32_t *wait, const uint32_t &start, uint32_t bit_time)
void setup(InternalGPIOPin *tx_pin, InternalGPIOPin *rx_pin, uint32_t baud_rate, uint8_t stop_bits, uint32_t data_bits, UARTParityOptions parity, size_t rx_buffer_size)
static void gpio_intr(ESP8266SoftwareSerial *arg)
Above SW_SERIAL_EDGE_MODE_MAX_BAUD; reads the whole byte in the ISR.
void load_settings()
Load the UART settings.
bool read_array(uint8_t *data, size_t len) override
void write_array(const uint8_t *data, size_t len) override
void finalize(uint32_t now)
Call with the ISR masked.
void setup(uint32_t bit_ticks, uint8_t data_bits, bool parity, uint8_t stop_bits, uint8_t *buffer, size_t buffer_size)
Configure framing and buffer; drops all state. Follow with reset().
bool ESPHOME_ALWAYS_INLINE on_edge(uint32_t now, bool level)
ISR.
void reset(uint32_t now, bool level)
Forget any partial frame; level is the current line level.
bool ESPHOME_ALWAYS_INLINE push_byte(uint8_t data)
Drops the byte when full; holds buffer_size - 1 bytes.
bool check_read_timeout_(size_t len=1)
CallbackManager< void(UARTDirection, uint8_t)> debug_callback_
struct @66::@67 __attribute__
Wake the main loop task from an ISR. ISR-safe.
Definition main_task.h:45
uint32_t buffer
Definition gif.cpp:50
@ INTERRUPT_FALLING_EDGE
Definition gpio_pin.h:53
@ INTERRUPT_ANY_EDGE
Definition gpio_pin.h:54
@ UART_SELECTION_UART0_SWAP
Definition logger.h:127
@ UART_SELECTION_UART0
Definition logger.h:112
Logger * global_logger
Definition logger.cpp:276
ESPHOME_LOG_TAG(TAG, "uart.binary_sensor")
const LogString * parity_to_str(UARTParityOptions parity)
Definition uart.cpp:43
UARTFlushResult
Result of a flush() call.
@ UART_FLUSH_RESULT_ASSUMED_SUCCESS
Platform cannot report result; success is assumed.
uint32_t arch_get_cpu_cycle_count()
Definition hal.cpp:71
void wake_loop_threadsafe()
Non-ISR: always inline.
const void size_t len
Definition hal.h:68
uint32_t IRAM_ATTR HOT micros()
Definition hal.cpp:43
void ESPHOME_ALWAYS_INLINE wake_loop_isrsafe()
ISR-safe: no task_woken arg because ESP8266 has no FreeRTOS. Caller must be IRAM_ATTR.
static void uint32_t
SemaphoreHandle_t lock
Platform-specific main loop wake primitives.