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