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