ESPHome 2026.6.0-dev
Loading...
Searching...
No Matches
uart_component_esp8266.cpp
Go to the documentation of this file.
1#ifdef USE_ESP8266
6#include "esphome/core/log.h"
7#ifdef USE_UART_WAKE_LOOP_ON_RX
8#include "esphome/core/wake.h"
9#endif
10
11#ifdef USE_LOGGER
13#endif
14
15namespace esphome::uart {
16
17static const char *const TAG = "uart.arduino_esp8266";
18bool ESP8266UartComponent::serial0_in_use = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
19
21 uint32_t config = 0;
22
23 if (this->parity_ == UART_CONFIG_PARITY_NONE) {
24 config |= UART_PARITY_NONE;
25 } else if (this->parity_ == UART_CONFIG_PARITY_EVEN) {
26 config |= UART_PARITY_EVEN;
27 } else if (this->parity_ == UART_CONFIG_PARITY_ODD) {
28 config |= UART_PARITY_ODD;
29 }
30
31 switch (this->data_bits_) {
32 case 5:
33 config |= UART_NB_BIT_5;
34 break;
35 case 6:
36 config |= UART_NB_BIT_6;
37 break;
38 case 7:
39 config |= UART_NB_BIT_7;
40 break;
41 case 8:
42 config |= UART_NB_BIT_8;
43 break;
44 }
45
46 if (this->stop_bits_ == 1) {
47 config |= UART_NB_STOP_BIT_1;
48 } else {
49 config |= UART_NB_STOP_BIT_2;
50 }
51
52 if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted())
53 config |= BIT(22);
54 if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted())
55 config |= BIT(19);
56
57 return config;
58}
59
61 auto setup_pin_if_needed = [](InternalGPIOPin *pin) {
62 if (!pin) {
63 return;
64 }
66 if ((pin->get_flags() & mask) != gpio::Flags::FLAG_NONE) {
67 pin->setup();
68 }
69 };
70
71 setup_pin_if_needed(this->rx_pin_);
72 if (this->rx_pin_ != this->tx_pin_) {
73 setup_pin_if_needed(this->tx_pin_);
74 }
75
76 // Use Arduino HardwareSerial UARTs if all used pins match the ones
77 // preconfigured by the platform. For example if RX disabled but TX pin
78 // is 1 we still want to use Serial.
79 SerialConfig config = static_cast<SerialConfig>(get_config());
80
81#ifdef USE_ESP8266_UART_SERIAL
82 if (!ESP8266UartComponent::serial0_in_use && (tx_pin_ == nullptr || tx_pin_->get_pin() == 1) &&
83 (rx_pin_ == nullptr || rx_pin_->get_pin() == 3)
84#ifdef USE_LOGGER
85 // we will use UART0 if logger isn't using it in swapped mode
86 && (logger::global_logger->get_hw_serial() == nullptr ||
88#endif
89 ) {
90 this->hw_serial_ = &Serial;
91 this->hw_serial_->begin(this->baud_rate_, config);
92 this->hw_serial_->setRxBufferSize(this->rx_buffer_size_);
93 ESP8266UartComponent::serial0_in_use = true;
94 } else if (!ESP8266UartComponent::serial0_in_use && (tx_pin_ == nullptr || tx_pin_->get_pin() == 15) &&
95 (rx_pin_ == nullptr || rx_pin_->get_pin() == 13)
96#ifdef USE_LOGGER
97 // we will use UART0 swapped if logger isn't using it in regular mode
98 && (logger::global_logger->get_hw_serial() == nullptr ||
100#endif
101 ) {
102 this->hw_serial_ = &Serial;
103 this->hw_serial_->begin(this->baud_rate_, config);
104 this->hw_serial_->setRxBufferSize(this->rx_buffer_size_);
105 this->hw_serial_->swap();
106 ESP8266UartComponent::serial0_in_use = true;
107 } else
108#endif // USE_ESP8266_UART_SERIAL
109#ifdef USE_ESP8266_UART_SERIAL1
110 if ((tx_pin_ == nullptr || tx_pin_->get_pin() == 2) && (rx_pin_ == nullptr || rx_pin_->get_pin() == 8)) {
111 this->hw_serial_ = &Serial1;
112 this->hw_serial_->begin(this->baud_rate_, config);
113 this->hw_serial_->setRxBufferSize(this->rx_buffer_size_);
114 } else
115#endif // USE_ESP8266_UART_SERIAL1
116 {
117 this->sw_serial_ = new ESP8266SoftwareSerial(); // NOLINT
118 this->sw_serial_->setup(tx_pin_, rx_pin_, this->baud_rate_, this->stop_bits_, this->data_bits_, this->parity_,
119 this->rx_buffer_size_);
120 }
121}
122
124 ESP_LOGCONFIG(TAG, "Loading UART bus settings");
125 if (this->hw_serial_ != nullptr) {
126 SerialConfig config = static_cast<SerialConfig>(get_config());
127 this->hw_serial_->begin(this->baud_rate_, config);
128 this->hw_serial_->setRxBufferSize(this->rx_buffer_size_);
129 } else {
130 this->sw_serial_->setup(this->tx_pin_, this->rx_pin_, this->baud_rate_, this->stop_bits_, this->data_bits_,
131 this->parity_, this->rx_buffer_size_);
132 }
133 if (dump_config) {
134 ESP_LOGCONFIG(TAG, "UART bus was reloaded.");
135 this->dump_config();
136 }
137}
138
140 ESP_LOGCONFIG(TAG, "UART Bus:");
141 LOG_PIN(" TX Pin: ", this->tx_pin_);
142 LOG_PIN(" RX Pin: ", this->rx_pin_);
143 if (this->rx_pin_ != nullptr) {
144 ESP_LOGCONFIG(TAG, " RX Buffer Size: %u", this->rx_buffer_size_); // NOLINT
145 }
146 ESP_LOGCONFIG(TAG,
147 " Baud Rate: %u baud\n"
148 " Data Bits: %u\n"
149 " Parity: %s\n"
150 " Stop bits: %u",
151 this->baud_rate_, this->data_bits_, LOG_STR_ARG(parity_to_str(this->parity_)), this->stop_bits_);
152 if (this->hw_serial_ != nullptr) {
153 ESP_LOGCONFIG(TAG, " Using hardware serial interface.");
154 } else {
155 ESP_LOGCONFIG(TAG, " Using software serial"
156#ifdef USE_UART_WAKE_LOOP_ON_RX
157 "\n Wake on data RX: ENABLED"
158#endif
159 );
160 }
161 this->check_logger_conflict();
162}
163
165#ifdef USE_LOGGER
166 if (this->hw_serial_ == nullptr || logger::global_logger->get_baud_rate() == 0) {
167 return;
168 }
169
170 if (this->hw_serial_ == logger::global_logger->get_hw_serial()) {
171 ESP_LOGW(TAG, " You're using the same serial port for logging and the UART component. Please "
172 "disable logging over the serial port by setting logger->baud_rate to 0.");
173 }
174#endif
175}
176
177void ESP8266UartComponent::write_array(const uint8_t *data, size_t len) {
178 if (this->hw_serial_ != nullptr) {
179 this->hw_serial_->write(data, len);
180 } else {
181 for (size_t i = 0; i < len; i++)
182 this->sw_serial_->write_byte(data[i]);
183 }
184#ifdef USE_UART_DEBUGGER
185 for (size_t i = 0; i < len; i++) {
186 this->debug_callback_.call(UART_DIRECTION_TX, data[i]);
187 }
188#endif
189}
191 if (!this->check_read_timeout_())
192 return false;
193 if (this->hw_serial_ != nullptr) {
194 *data = this->hw_serial_->peek();
195 } else {
196 *data = this->sw_serial_->peek_byte();
197 }
198 return true;
199}
200bool ESP8266UartComponent::read_array(uint8_t *data, size_t len) {
201 if (!this->check_read_timeout_(len))
202 return false;
203 if (this->hw_serial_ != nullptr) {
204 this->hw_serial_->readBytes(data, len);
205 } else {
206 for (size_t i = 0; i < len; i++)
207 data[i] = this->sw_serial_->read_byte();
208 }
209#ifdef USE_UART_DEBUGGER
210 for (size_t i = 0; i < len; i++) {
211 this->debug_callback_.call(UART_DIRECTION_RX, data[i]);
212 }
213#endif
214 return true;
215}
217 if (this->hw_serial_ != nullptr) {
218 return this->hw_serial_->available();
219 } else {
220 return this->sw_serial_->available();
221 }
222}
224 ESP_LOGVV(TAG, " Flushing");
225 if (this->hw_serial_ != nullptr) {
226 this->hw_serial_->flush();
227 } else {
228 this->sw_serial_->flush();
229 }
231}
233 uint8_t stop_bits, uint32_t data_bits, UARTParityOptions parity,
234 size_t rx_buffer_size) {
235 this->bit_time_ = F_CPU / baud_rate;
236 this->rx_buffer_size_ = rx_buffer_size;
237 this->stop_bits_ = stop_bits;
238 this->data_bits_ = data_bits;
239 this->parity_ = parity;
240 if (tx_pin != nullptr) {
241 gpio_tx_pin_ = tx_pin;
245 }
246 if (rx_pin != nullptr) {
247 gpio_rx_pin_ = rx_pin;
250 rx_buffer_ = new uint8_t[this->rx_buffer_size_]; // NOLINT
252 }
253}
255 uint32_t wait = arg->bit_time_ + arg->bit_time_ / 3 - 500;
256 const uint32_t start = arch_get_cpu_cycle_count();
257 uint8_t rec = 0;
258 // Manually unroll the loop
259 for (int i = 0; i < arg->data_bits_; i++)
260 rec |= arg->read_bit_(&wait, start) << i;
261
262 /* If parity is enabled, just read it and ignore it. */
263 /* TODO: Should we check parity? Or is it too slow for nothing added..*/
265 arg->read_bit_(&wait, start);
266
267 // Stop bit
268 arg->wait_(&wait, start);
269 if (arg->stop_bits_ == 2)
270 arg->wait_(&wait, start);
271
272 arg->rx_buffer_[arg->rx_in_pos_] = rec;
273 arg->rx_in_pos_ = (arg->rx_in_pos_ + 1) % arg->rx_buffer_size_;
274 // Clear RX pin so that the interrupt doesn't re-trigger right away again.
276#ifdef USE_UART_WAKE_LOOP_ON_RX
277 // Wake the main loop so the consuming component drains the byte promptly
278 // instead of waiting for the next loop_interval_ tick. Important for timing
279 // sensitive setups that poll read() in a tight loop (e.g. fingerprint_grow).
281#endif
282}
283void IRAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) {
284 if (this->gpio_tx_pin_ == nullptr) {
285 ESP_LOGE(TAG, "UART doesn't have TX pins set!");
286 return;
287 }
288 bool parity_bit = false;
289 bool need_parity_bit = true;
290 if (this->parity_ == UART_CONFIG_PARITY_EVEN) {
291 parity_bit = false;
292 } else if (this->parity_ == UART_CONFIG_PARITY_ODD) {
293 parity_bit = true;
294 } else {
295 need_parity_bit = false;
296 }
297
298 {
300 uint32_t wait = this->bit_time_;
301 const uint32_t start = arch_get_cpu_cycle_count();
302 // Start bit
303 this->write_bit_(false, &wait, start);
304 for (int i = 0; i < this->data_bits_; i++) {
305 bool bit = data & (1 << i);
306 this->write_bit_(bit, &wait, start);
307 if (need_parity_bit)
308 parity_bit ^= bit;
309 }
310 if (need_parity_bit)
311 this->write_bit_(parity_bit, &wait, start);
312 // Stop bit
313 this->write_bit_(true, &wait, start);
314 if (this->stop_bits_ == 2)
315 this->wait_(&wait, start);
316 }
317}
318void IRAM_ATTR ESP8266SoftwareSerial::wait_(uint32_t *wait, const uint32_t &start) {
319 while (arch_get_cpu_cycle_count() - start < *wait)
320 ;
321 *wait += this->bit_time_;
322}
323bool IRAM_ATTR ESP8266SoftwareSerial::read_bit_(uint32_t *wait, const uint32_t &start) {
324 this->wait_(wait, start);
325 return this->rx_pin_.digital_read();
326}
327void IRAM_ATTR ESP8266SoftwareSerial::write_bit_(bool bit, uint32_t *wait, const uint32_t &start) {
328 this->tx_pin_.digital_write(bit);
329 this->wait_(wait, start);
330}
332 if (this->rx_in_pos_ == this->rx_out_pos_)
333 return 0;
334 uint8_t data = this->rx_buffer_[this->rx_out_pos_];
335 this->rx_out_pos_ = (this->rx_out_pos_ + 1) % this->rx_buffer_size_;
336 return data;
337}
339 if (this->rx_in_pos_ == this->rx_out_pos_)
340 return 0;
341 return this->rx_buffer_[this->rx_out_pos_];
342}
344 // Flush is a NO-OP with software serial, all bytes are written immediately.
345}
347 // Read volatile rx_in_pos_ once to avoid TOCTOU race with ISR.
348 // When in >= out, data is contiguous: [out..in).
349 // When in < out, data wraps: [out..buf_size) + [0..in).
350 size_t in = this->rx_in_pos_;
351 if (in >= this->rx_out_pos_)
352 return in - this->rx_out_pos_;
353 return this->rx_buffer_size_ - this->rx_out_pos_ + in;
354}
355
356} // namespace esphome::uart
357#endif // USE_ESP8266
virtual void setup()=0
void digital_write(bool value)
Definition gpio.cpp:148
virtual uint8_t get_pin() const =0
void attach_interrupt(void(*func)(T *), T *arg, gpio::InterruptType type) const
Definition gpio.h:107
virtual bool is_inverted() const =0
virtual ISRInternalGPIOPin to_isr() const =0
Helper class to disable interrupts.
Definition helpers.h:1952
Stream * get_hw_serial() const
Definition logger.h:154
UARTSelection get_uart() const
Get the UART used by the logger.
Definition logger.cpp:206
void wait_(uint32_t *wait, const uint32_t &start)
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)
void write_bit_(bool bit, uint32_t *wait, const uint32_t &start)
bool read_bit_(uint32_t *wait, const uint32_t &start)
static void gpio_intr(ESP8266SoftwareSerial *arg)
bool read_array(uint8_t *data, size_t len) override
void write_array(const uint8_t *data, size_t len) override
bool check_read_timeout_(size_t len=1)
CallbackManager< void(UARTDirection, uint8_t)> debug_callback_
@ INTERRUPT_FALLING_EDGE
Definition gpio.h:51
@ FLAG_OPEN_DRAIN
Definition gpio.h:29
@ FLAG_NONE
Definition gpio.h:26
@ FLAG_PULLUP
Definition gpio.h:30
@ FLAG_PULLDOWN
Definition gpio.h:31
@ UART_SELECTION_UART0_SWAP
Definition logger.h:122
@ UART_SELECTION_UART0
Definition logger.h:107
Logger * global_logger
Definition logger.cpp:275
const char *const TAG
Definition spi.cpp:7
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
const void size_t len
Definition hal.h:64
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.