ESPHome 2026.10.2
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
28 uint32_t config = 0;
29
30 if (this->parity_ == UART_CONFIG_PARITY_NONE) {
31 config |= UART_PARITY_NONE;
32 } else if (this->parity_ == UART_CONFIG_PARITY_EVEN) {
33 config |= UART_PARITY_EVEN;
34 } else if (this->parity_ == UART_CONFIG_PARITY_ODD) {
35 config |= UART_PARITY_ODD;
36 }
37
38 switch (this->data_bits_) {
39 case 5:
40 config |= UART_NB_BIT_5;
41 break;
42 case 6:
43 config |= UART_NB_BIT_6;
44 break;
45 case 7:
46 config |= UART_NB_BIT_7;
47 break;
48 case 8:
49 config |= UART_NB_BIT_8;
50 break;
51 }
52
53 if (this->stop_bits_ == 1) {
54 config |= UART_NB_STOP_BIT_1;
55 } else {
56 config |= UART_NB_STOP_BIT_2;
57 }
58
59 if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted())
60 config |= BIT(22);
61 if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted())
62 config |= BIT(19);
63
64 return config;
65}
66
68 auto setup_pin_if_needed = [](InternalGPIOPin *pin) {
69 if (!pin) {
70 return;
71 }
73 if ((pin->get_flags() & mask) != gpio::Flags::FLAG_NONE) {
74 pin->setup();
75 }
76 };
77
78 setup_pin_if_needed(this->rx_pin_);
79 if (this->rx_pin_ != this->tx_pin_) {
80 setup_pin_if_needed(this->tx_pin_);
81 }
82
83 // Use Arduino HardwareSerial UARTs if all used pins match the ones
84 // preconfigured by the platform. For example if RX disabled but TX pin
85 // is 1 we still want to use Serial.
86 SerialConfig config = static_cast<SerialConfig>(get_config());
87
88#ifdef USE_ESP8266_UART_SERIAL
89 if (!ESP8266UartComponent::serial0_in_use && (tx_pin_ == nullptr || tx_pin_->get_pin() == 1) &&
90 (rx_pin_ == nullptr || rx_pin_->get_pin() == 3)
91#ifdef USE_LOGGER
92 // we will use UART0 if logger isn't using it in swapped mode
93 && (logger::global_logger->get_hw_serial() == nullptr ||
95#endif
96 ) {
97 this->hw_serial_ = &Serial;
98 this->hw_serial_->begin(this->baud_rate_, config);
99 this->hw_serial_->setRxBufferSize(this->rx_buffer_size_);
100 ESP8266UartComponent::serial0_in_use = true;
101 } else if (!ESP8266UartComponent::serial0_in_use && (tx_pin_ == nullptr || tx_pin_->get_pin() == 15) &&
102 (rx_pin_ == nullptr || rx_pin_->get_pin() == 13)
103#ifdef USE_LOGGER
104 // we will use UART0 swapped if logger isn't using it in regular mode
105 && (logger::global_logger->get_hw_serial() == nullptr ||
107#endif
108 ) {
109 this->hw_serial_ = &Serial;
110 this->hw_serial_->begin(this->baud_rate_, config);
111 this->hw_serial_->setRxBufferSize(this->rx_buffer_size_);
112 this->hw_serial_->swap();
113 ESP8266UartComponent::serial0_in_use = true;
114 } else
115#endif // USE_ESP8266_UART_SERIAL
116#ifdef USE_ESP8266_UART_SERIAL1
117 if ((tx_pin_ == nullptr || tx_pin_->get_pin() == 2) && (rx_pin_ == nullptr || rx_pin_->get_pin() == 8)) {
118 this->hw_serial_ = &Serial1;
119 this->hw_serial_->begin(this->baud_rate_, config);
120 this->hw_serial_->setRxBufferSize(this->rx_buffer_size_);
121 } else
122#endif // USE_ESP8266_UART_SERIAL1
123 {
124 this->sw_serial_ = new ESP8266SoftwareSerial(); // NOLINT
125 this->sw_serial_->setup(tx_pin_, rx_pin_, this->baud_rate_, this->stop_bits_, this->data_bits_, this->parity_,
126 this->rx_buffer_size_);
127 }
128}
129
131 ESP_LOGCONFIG(TAG, "Loading UART bus settings");
132 if (this->hw_serial_ != nullptr) {
133 SerialConfig config = static_cast<SerialConfig>(get_config());
134 this->hw_serial_->begin(this->baud_rate_, config);
135 this->hw_serial_->setRxBufferSize(this->rx_buffer_size_);
136 } else {
137 this->sw_serial_->setup(this->tx_pin_, this->rx_pin_, this->baud_rate_, this->stop_bits_, this->data_bits_,
138 this->parity_, this->rx_buffer_size_);
139 }
140 if (dump_config) {
141 ESP_LOGCONFIG(TAG, "UART bus was reloaded.");
142 this->dump_config();
143 }
144}
145
147 ESP_LOGCONFIG(TAG, "UART Bus:");
148 LOG_PIN(" TX Pin: ", this->tx_pin_);
149 LOG_PIN(" RX Pin: ", this->rx_pin_);
150 if (this->rx_pin_ != nullptr) {
151 ESP_LOGCONFIG(TAG, " RX Buffer Size: %u", this->rx_buffer_size_); // NOLINT
152 }
153 ESP_LOGCONFIG(TAG,
154 " Baud Rate: %u baud\n"
155 " Data Bits: %u\n"
156 " Parity: %s\n"
157 " Stop bits: %u",
158 this->baud_rate_, this->data_bits_, LOG_STR_ARG(parity_to_str(this->parity_)), this->stop_bits_);
159 if (this->hw_serial_ != nullptr) {
160 ESP_LOGCONFIG(TAG, " Using hardware serial interface.");
161 } else {
162 ESP_LOGCONFIG(TAG, " Using software serial"
163#ifdef USE_UART_WAKE_LOOP_ON_RX
164 "\n Wake on data RX: ENABLED"
165#endif
166 );
167 if (this->rx_pin_ != nullptr) {
168 ESP_LOGCONFIG(TAG, " RX decoder: %s",
169 this->baud_rate_ <= SW_SERIAL_EDGE_MODE_MAX_BAUD ? LOG_STR_LITERAL("edge")
170 : LOG_STR_LITERAL("start bit sampler"));
171 }
172 }
173 this->check_logger_conflict();
174}
175
177#ifdef USE_LOGGER
178 if (this->hw_serial_ == nullptr || logger::global_logger->get_baud_rate() == 0) {
179 return;
180 }
181
182 if (this->hw_serial_ == logger::global_logger->get_hw_serial()) {
183 ESP_LOGW(TAG, " You're using the same serial port for logging and the UART component. Please "
184 "disable logging over the serial port by setting logger->baud_rate to 0.");
185 }
186#endif
187}
188
189void ESP8266UartComponent::write_array(const uint8_t *data, size_t len) {
190 if (this->hw_serial_ != nullptr) {
191 this->hw_serial_->write(data, len);
192 } else {
193 for (size_t i = 0; i < len; i++)
194 this->sw_serial_->write_byte(data[i]);
195 }
196#ifdef USE_UART_DEBUGGER
197 for (size_t i = 0; i < len; i++) {
198 this->debug_callback_.call(UART_DIRECTION_TX, data[i]);
199 }
200#endif
201}
203 if (!this->check_read_timeout_())
204 return false;
205 if (this->hw_serial_ != nullptr) {
206 *data = this->hw_serial_->peek();
207 } else {
208 *data = this->sw_serial_->peek_byte();
209 }
210 return true;
211}
212bool ESP8266UartComponent::read_array(uint8_t *data, size_t len) {
213 if (!this->check_read_timeout_(len))
214 return false;
215 if (this->hw_serial_ != nullptr) {
216 this->hw_serial_->readBytes(data, len);
217 } else {
218 for (size_t i = 0; i < len; i++)
219 data[i] = this->sw_serial_->read_byte();
220 }
221#ifdef USE_UART_DEBUGGER
222 for (size_t i = 0; i < len; i++) {
223 this->debug_callback_.call(UART_DIRECTION_RX, data[i]);
224 }
225#endif
226 return true;
227}
229 if (this->hw_serial_ != nullptr) {
230 return this->hw_serial_->available();
231 } else {
232 return this->sw_serial_->available();
233 }
234}
236 if (this->hw_serial_ != nullptr) {
237 return this->hw_serial_->availableForWrite();
238 }
239 return SIZE_MAX; // software serial bit-bangs each byte synchronously; there is no buffer to fill
240}
242 ESP_LOGVV(TAG, " Flushing");
243 if (this->hw_serial_ != nullptr) {
244 this->hw_serial_->flush();
245 } else {
246 this->sw_serial_->flush();
247 }
249}
251 uint8_t stop_bits, uint32_t data_bits, UARTParityOptions parity,
252 size_t rx_buffer_size) {
253 // load_settings() re-enters here
254 if (this->gpio_rx_pin_ != nullptr)
256 this->bit_time_ = F_CPU / baud_rate;
257 this->stop_bits_ = stop_bits;
258 this->data_bits_ = data_bits;
259 this->parity_ = parity;
260 // to_isr() allocates, so call it only for a new or changed pin
261 if (tx_pin != nullptr) {
262 if (tx_pin != this->gpio_tx_pin_)
263 this->tx_pin_ = tx_pin->to_isr();
264 gpio_tx_pin_ = tx_pin;
267 }
268 if (rx_pin != nullptr) {
269 if (rx_pin != this->gpio_rx_pin_)
270 this->rx_pin_ = rx_pin->to_isr();
271 gpio_rx_pin_ = rx_pin;
273 uint8_t *buffer = this->rx_.buffer();
274 if (this->rx_.buffer_size() != rx_buffer_size) {
275 delete[] buffer; // NOLINT
276 buffer = new uint8_t[rx_buffer_size]; // NOLINT
277 }
278 this->rx_.setup((RX_TICKS_PER_SECOND + baud_rate / 2) / baud_rate, data_bits, parity != UART_CONFIG_PARITY_NONE,
279 stop_bits, buffer, rx_buffer_size);
280 this->rx_.reset(rx_now(), this->rx_pin_.digital_read());
281 if (baud_rate <= SW_SERIAL_EDGE_MODE_MAX_BAUD) {
283 } else {
285 }
286 }
287}
288// A byte can arrive while a CpuFrequencyBoost has an 80 MHz build at 160 MHz; the clock select bit doubles
289// the bit time then. The whole byte is read inside the ISR, so the clock cannot change partway through.
290__attribute__((always_inline)) static inline uint32_t rx_bit_time(uint32_t bit_time) {
291#if F_CPU != 160000000L
292 // NOLINTNEXTLINE(clang-analyzer-core.FixedAddressDereference) -- CPU2X is MMIO at a fixed address
293 return bit_time << (CPU2X & 1);
294#else
295 return bit_time;
296#endif
297}
298
300 const uint32_t bit_time = rx_bit_time(arg->bit_time_);
301 uint32_t wait = bit_time + bit_time / 3 - 500;
302 const uint32_t start = arch_get_cpu_cycle_count();
303 uint8_t rec = 0;
304 // Manually unroll the loop
305 for (int i = 0; i < arg->data_bits_; i++)
306 rec |= arg->read_bit_(&wait, start, bit_time) << i;
307
308 /* If parity is enabled, just read it and ignore it. */
309 /* TODO: Should we check parity? Or is it too slow for nothing added..*/
311 arg->read_bit_(&wait, start, bit_time);
312
313 // Stop bit
314 arg->wait_(&wait, start, bit_time);
315 if (arg->stop_bits_ == 2)
316 arg->wait_(&wait, start, bit_time);
317
318 arg->rx_.push_byte(rec);
319 // Clear RX pin so that the interrupt doesn't re-trigger right away again.
321#ifdef USE_UART_WAKE_LOOP_ON_RX
322 // Wake the main loop so the consuming component drains the byte promptly
323 // instead of waiting for the next loop_interval_ tick. Important for timing
324 // sensitive setups that poll read() in a tight loop (e.g. fingerprint_grow).
326#endif
327}
329 const uint32_t now = rx_now();
330 const bool level = arg->rx_pin_.digital_read();
331 [[maybe_unused]] const bool wake = arg->rx_.on_edge(now, level);
332#ifdef USE_UART_WAKE_LOOP_ON_RX
333 if (wake)
335#endif
336}
338 if (!this->rx_.finalize_due(rx_now())) {
339#ifdef USE_UART_WAKE_LOOP_ON_RX
340 // Tail not elapsed; wake again only once drained
341 if (this->rx_.available() == 0)
343#endif
344 return;
345 }
347 this->rx_.finalize(rx_now());
348}
349void IRAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) {
350 if (this->gpio_tx_pin_ == nullptr) {
351 ESP_LOGE(TAG, "UART doesn't have TX pins set!");
352 return;
353 }
354 bool parity_bit = false;
355 bool need_parity_bit = true;
356 if (this->parity_ == UART_CONFIG_PARITY_EVEN) {
357 parity_bit = false;
358 } else if (this->parity_ == UART_CONFIG_PARITY_ODD) {
359 parity_bit = true;
360 } else {
361 need_parity_bit = false;
362 }
363
364 {
365 // Transmit runs from the main loop and never overlaps a CpuFrequencyBoost
367 const uint32_t bit_time = this->bit_time_;
368 uint32_t wait = bit_time;
369 const uint32_t start = arch_get_cpu_cycle_count();
370 // Start bit
371 this->write_bit_(false, &wait, start, bit_time);
372 for (int i = 0; i < this->data_bits_; i++) {
373 bool bit = data & (1 << i);
374 this->write_bit_(bit, &wait, start, bit_time);
375 if (need_parity_bit)
376 parity_bit ^= bit;
377 }
378 if (need_parity_bit)
379 this->write_bit_(parity_bit, &wait, start, bit_time);
380 // Stop bit
381 this->write_bit_(true, &wait, start, bit_time);
382 if (this->stop_bits_ == 2)
383 this->wait_(&wait, start, bit_time);
384 }
385}
386void IRAM_ATTR ESP8266SoftwareSerial::wait_(uint32_t *wait, const uint32_t &start, uint32_t bit_time) {
387 while (arch_get_cpu_cycle_count() - start < *wait)
388 ;
389 *wait += bit_time;
390}
391bool IRAM_ATTR ESP8266SoftwareSerial::read_bit_(uint32_t *wait, const uint32_t &start, uint32_t bit_time) {
392 this->wait_(wait, start, bit_time);
393 return this->rx_pin_.digital_read();
394}
395void IRAM_ATTR ESP8266SoftwareSerial::write_bit_(bool bit, uint32_t *wait, const uint32_t &start, uint32_t bit_time) {
396 this->tx_pin_.digital_write(bit);
397 this->wait_(wait, start, bit_time);
398}
400 this->rx_sync_();
401 return this->rx_.read_byte();
402}
404 this->rx_sync_();
405 return this->rx_.peek_byte();
406}
408 // Flush is a NO-OP with software serial, all bytes are written immediately.
409}
411 this->rx_sync_();
412 return this->rx_.available();
413}
414
415} // namespace esphome::uart
416#endif // USE_ESP8266
virtual void setup()=0
void digital_write(bool value)
Definition gpio.cpp:151
virtual uint8_t get_pin() const =0
virtual void detach_interrupt() const =0
void attach_interrupt(void(*func)(T *), T *arg, gpio::InterruptType type) const
Definition gpio.h:100
virtual bool is_inverted() const =0
virtual ISRInternalGPIOPin to_isr() const =0
Helper class to disable interrupts.
Definition helpers.h:2095
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.h:49
@ INTERRUPT_ANY_EDGE
Definition gpio.h:50
@ FLAG_OPEN_DRAIN
Definition gpio.h:27
@ FLAG_NONE
Definition gpio.h:24
@ FLAG_PULLUP
Definition gpio.h:28
@ FLAG_PULLDOWN
Definition gpio.h:29
@ UART_SELECTION_UART0_SWAP
Definition logger.h:127
@ UART_SELECTION_UART0
Definition logger.h:112
Logger * global_logger
Definition logger.cpp:274
ESPHOME_LOG_TAG(TAG, "uart")
const LogString * parity_to_str(UARTParityOptions parity)
Definition uart.cpp:36
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:64
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.