mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Uart improvments (#1024)
* uart: Add support for specifying the number of bits and parity. ESP8266SwSerial doesn't really check parity but just read the parity bit and ignore it when receiving data. Signed-off-by: 0hax <0hax@protonmail.com> * uart: support begin and end methods. A component may need to reset uart buffer/status by using begin() and end() methods. This is useful for example when a component needs to be sure it is not reading garbage from previously received data over uart. For end() methods with software serial, disabling interrupt is currently impossible because of a bug in esp8266 Core: https://github.com/esp8266/Arduino/issues/6049 Signed-off-by: 0hax <0hax@protonmail.com> * esphal: add support for detaching an interrupt. That's needed when a component needs to enable/disable interrupt on a gpio. Signed-off-by: 0hax <0hax@protonmail.com> * uart: rename CONF_NR_BITS to CONF_DATA_BITS_NUMBER. Signed-off-by: 0hax <0hax@protonmail.com> * uart: use static const uint32_t instead of #define. Signed-off-by: 0hax <0hax@protonmail.com> * uart: use an enum to handle parity. Signed-off-by: 0hax <0hax@protonmail.com> * uart: split between esp32 and esp8266. Signed-off-by: 0hax <0hax@protonmail.com> * uart: check_uart_settings for parity and number of data bits. Signed-off-by: 0hax <0hax@protonmail.com> * name param data_bits * add new params to test Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>
This commit is contained in:
		| @@ -28,13 +28,25 @@ def validate_rx_pin(value): | ||||
|     return value | ||||
|  | ||||
|  | ||||
| UARTParityOptions = uart_ns.enum('UARTParityOptions') | ||||
| UART_PARITY_OPTIONS = { | ||||
|     'NONE': UARTParityOptions.UART_CONFIG_PARITY_NONE, | ||||
|     'EVEN': UARTParityOptions.UART_CONFIG_PARITY_EVEN, | ||||
|     'ODD': UARTParityOptions.UART_CONFIG_PARITY_ODD, | ||||
| } | ||||
|  | ||||
| CONF_STOP_BITS = 'stop_bits' | ||||
| CONF_DATA_BITS = 'data_bits' | ||||
| CONF_PARITY = 'parity' | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All(cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(UARTComponent), | ||||
|     cv.Required(CONF_BAUD_RATE): cv.int_range(min=1), | ||||
|     cv.Optional(CONF_TX_PIN): pins.output_pin, | ||||
|     cv.Optional(CONF_RX_PIN): validate_rx_pin, | ||||
|     cv.Optional(CONF_STOP_BITS, default=1): cv.one_of(1, 2, int=True), | ||||
|     cv.Optional(CONF_DATA_BITS, default=8): cv.int_range(min=5, max=8), | ||||
|     cv.Optional(CONF_PARITY, default="NONE"): cv.enum(UART_PARITY_OPTIONS, upper=True) | ||||
| }).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN)) | ||||
|  | ||||
|  | ||||
| @@ -50,6 +62,8 @@ def to_code(config): | ||||
|     if CONF_RX_PIN in config: | ||||
|         cg.add(var.set_rx_pin(config[CONF_RX_PIN])) | ||||
|     cg.add(var.set_stop_bits(config[CONF_STOP_BITS])) | ||||
|     cg.add(var.set_data_bits(config[CONF_DATA_BITS])) | ||||
|     cg.add(var.set_parity(config[CONF_PARITY])) | ||||
|  | ||||
|  | ||||
| # A schema to use for all UART devices, all UART integrations must extend this! | ||||
|   | ||||
| @@ -13,345 +13,6 @@ namespace uart { | ||||
|  | ||||
| static const char *TAG = "uart"; | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
| uint8_t next_uart_num = 1; | ||||
| #endif | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
| void UARTComponent::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up UART..."); | ||||
|   // Use Arduino HardwareSerial UARTs if all used pins match the ones | ||||
|   // preconfigured by the platform. For example if RX disabled but TX pin | ||||
|   // is 1 we still want to use Serial. | ||||
|   if (this->tx_pin_.value_or(1) == 1 && this->rx_pin_.value_or(3) == 3) { | ||||
|     this->hw_serial_ = &Serial; | ||||
|   } else { | ||||
|     this->hw_serial_ = new HardwareSerial(next_uart_num++); | ||||
|   } | ||||
|   int8_t tx = this->tx_pin_.has_value() ? *this->tx_pin_ : -1; | ||||
|   int8_t rx = this->rx_pin_.has_value() ? *this->rx_pin_ : -1; | ||||
|   uint32_t config = SERIAL_8N1; | ||||
|   if (this->stop_bits_ == 2) | ||||
|     config = SERIAL_8N2; | ||||
|   this->hw_serial_->begin(this->baud_rate_, config, rx, tx); | ||||
| } | ||||
|  | ||||
| void UARTComponent::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "UART Bus:"); | ||||
|   if (this->tx_pin_.has_value()) { | ||||
|     ESP_LOGCONFIG(TAG, "  TX Pin: GPIO%d", *this->tx_pin_); | ||||
|   } | ||||
|   if (this->rx_pin_.has_value()) { | ||||
|     ESP_LOGCONFIG(TAG, "  RX Pin: GPIO%d", *this->rx_pin_); | ||||
|   } | ||||
|   ESP_LOGCONFIG(TAG, "  Baud Rate: %u baud", this->baud_rate_); | ||||
|   ESP_LOGCONFIG(TAG, "  Stop bits: %u", this->stop_bits_); | ||||
|   this->check_logger_conflict_(); | ||||
| } | ||||
|  | ||||
| void UARTComponent::write_byte(uint8_t data) { | ||||
|   this->hw_serial_->write(data); | ||||
|   ESP_LOGVV(TAG, "    Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data), data); | ||||
| } | ||||
| void UARTComponent::write_array(const uint8_t *data, size_t len) { | ||||
|   this->hw_serial_->write(data, len); | ||||
|   for (size_t i = 0; i < len; i++) { | ||||
|     ESP_LOGVV(TAG, "    Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); | ||||
|   } | ||||
| } | ||||
| void UARTComponent::write_str(const char *str) { | ||||
|   this->hw_serial_->write(str); | ||||
|   ESP_LOGVV(TAG, "    Wrote \"%s\"", str); | ||||
| } | ||||
| bool UARTComponent::read_byte(uint8_t *data) { | ||||
|   if (!this->check_read_timeout_()) | ||||
|     return false; | ||||
|   *data = this->hw_serial_->read(); | ||||
|   ESP_LOGVV(TAG, "    Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(*data), *data); | ||||
|   return true; | ||||
| } | ||||
| bool UARTComponent::peek_byte(uint8_t *data) { | ||||
|   if (!this->check_read_timeout_()) | ||||
|     return false; | ||||
|   *data = this->hw_serial_->peek(); | ||||
|   return true; | ||||
| } | ||||
| bool UARTComponent::read_array(uint8_t *data, size_t len) { | ||||
|   if (!this->check_read_timeout_(len)) | ||||
|     return false; | ||||
|   this->hw_serial_->readBytes(data, len); | ||||
|   for (size_t i = 0; i < len; i++) { | ||||
|     ESP_LOGVV(TAG, "    Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
| bool UARTComponent::check_read_timeout_(size_t len) { | ||||
|   if (this->available() >= len) | ||||
|     return true; | ||||
|  | ||||
|   uint32_t start_time = millis(); | ||||
|   while (this->available() < len) { | ||||
|     if (millis() - start_time > 1000) { | ||||
|       ESP_LOGE(TAG, "Reading from UART timed out at byte %u!", this->available()); | ||||
|       return false; | ||||
|     } | ||||
|     yield(); | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
| int UARTComponent::available() { return this->hw_serial_->available(); } | ||||
| void UARTComponent::flush() { | ||||
|   ESP_LOGVV(TAG, "    Flushing..."); | ||||
|   this->hw_serial_->flush(); | ||||
| } | ||||
| #endif  // ESP32 | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP8266 | ||||
| void UARTComponent::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up UART bus..."); | ||||
|   // Use Arduino HardwareSerial UARTs if all used pins match the ones | ||||
|   // preconfigured by the platform. For example if RX disabled but TX pin | ||||
|   // is 1 we still want to use Serial. | ||||
|   uint32_t mode = UART_NB_BIT_8 | UART_PARITY_NONE; | ||||
|   if (this->stop_bits_ == 1) | ||||
|     mode |= UART_NB_STOP_BIT_1; | ||||
|   else | ||||
|     mode |= UART_NB_STOP_BIT_2; | ||||
|   SerialConfig config = static_cast<SerialConfig>(mode); | ||||
|   if (this->tx_pin_.value_or(1) == 1 && this->rx_pin_.value_or(3) == 3) { | ||||
|     this->hw_serial_ = &Serial; | ||||
|     this->hw_serial_->begin(this->baud_rate_, config); | ||||
|   } else if (this->tx_pin_.value_or(15) == 15 && this->rx_pin_.value_or(13) == 13) { | ||||
|     this->hw_serial_ = &Serial; | ||||
|     this->hw_serial_->begin(this->baud_rate_, config); | ||||
|     this->hw_serial_->swap(); | ||||
|   } else if (this->tx_pin_.value_or(2) == 2 && this->rx_pin_.value_or(8) == 8) { | ||||
|     this->hw_serial_ = &Serial1; | ||||
|     this->hw_serial_->begin(this->baud_rate_, config); | ||||
|   } else { | ||||
|     this->sw_serial_ = new ESP8266SoftwareSerial(); | ||||
|     int8_t tx = this->tx_pin_.has_value() ? *this->tx_pin_ : -1; | ||||
|     int8_t rx = this->rx_pin_.has_value() ? *this->rx_pin_ : -1; | ||||
|     this->sw_serial_->setup(tx, rx, this->baud_rate_, this->stop_bits_); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void UARTComponent::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "UART Bus:"); | ||||
|   if (this->tx_pin_.has_value()) { | ||||
|     ESP_LOGCONFIG(TAG, "  TX Pin: GPIO%d", *this->tx_pin_); | ||||
|   } | ||||
|   if (this->rx_pin_.has_value()) { | ||||
|     ESP_LOGCONFIG(TAG, "  RX Pin: GPIO%d", *this->rx_pin_); | ||||
|   } | ||||
|   ESP_LOGCONFIG(TAG, "  Baud Rate: %u baud", this->baud_rate_); | ||||
|   ESP_LOGCONFIG(TAG, "  Stop bits: %u", this->stop_bits_); | ||||
|   if (this->hw_serial_ != nullptr) { | ||||
|     ESP_LOGCONFIG(TAG, "  Using hardware serial interface."); | ||||
|   } else { | ||||
|     ESP_LOGCONFIG(TAG, "  Using software serial"); | ||||
|   } | ||||
|   this->check_logger_conflict_(); | ||||
| } | ||||
|  | ||||
| void UARTComponent::write_byte(uint8_t data) { | ||||
|   if (this->hw_serial_ != nullptr) { | ||||
|     this->hw_serial_->write(data); | ||||
|   } else { | ||||
|     this->sw_serial_->write_byte(data); | ||||
|   } | ||||
|   ESP_LOGVV(TAG, "    Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data), data); | ||||
| } | ||||
| void UARTComponent::write_array(const uint8_t *data, size_t len) { | ||||
|   if (this->hw_serial_ != nullptr) { | ||||
|     this->hw_serial_->write(data, len); | ||||
|   } else { | ||||
|     for (size_t i = 0; i < len; i++) | ||||
|       this->sw_serial_->write_byte(data[i]); | ||||
|   } | ||||
|   for (size_t i = 0; i < len; i++) { | ||||
|     ESP_LOGVV(TAG, "    Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); | ||||
|   } | ||||
| } | ||||
| void UARTComponent::write_str(const char *str) { | ||||
|   if (this->hw_serial_ != nullptr) { | ||||
|     this->hw_serial_->write(str); | ||||
|   } else { | ||||
|     const auto *data = reinterpret_cast<const uint8_t *>(str); | ||||
|     for (size_t i = 0; data[i] != 0; i++) | ||||
|       this->sw_serial_->write_byte(data[i]); | ||||
|   } | ||||
|   ESP_LOGVV(TAG, "    Wrote \"%s\"", str); | ||||
| } | ||||
| bool UARTComponent::read_byte(uint8_t *data) { | ||||
|   if (!this->check_read_timeout_()) | ||||
|     return false; | ||||
|   if (this->hw_serial_ != nullptr) { | ||||
|     *data = this->hw_serial_->read(); | ||||
|   } else { | ||||
|     *data = this->sw_serial_->read_byte(); | ||||
|   } | ||||
|   ESP_LOGVV(TAG, "    Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(*data), *data); | ||||
|   return true; | ||||
| } | ||||
| bool UARTComponent::peek_byte(uint8_t *data) { | ||||
|   if (!this->check_read_timeout_()) | ||||
|     return false; | ||||
|   if (this->hw_serial_ != nullptr) { | ||||
|     *data = this->hw_serial_->peek(); | ||||
|   } else { | ||||
|     *data = this->sw_serial_->peek_byte(); | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
| bool UARTComponent::read_array(uint8_t *data, size_t len) { | ||||
|   if (!this->check_read_timeout_(len)) | ||||
|     return false; | ||||
|   if (this->hw_serial_ != nullptr) { | ||||
|     this->hw_serial_->readBytes(data, len); | ||||
|   } else { | ||||
|     for (size_t i = 0; i < len; i++) | ||||
|       data[i] = this->sw_serial_->read_byte(); | ||||
|   } | ||||
|   for (size_t i = 0; i < len; i++) { | ||||
|     ESP_LOGVV(TAG, "    Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
| bool UARTComponent::check_read_timeout_(size_t len) { | ||||
|   if (this->available() >= int(len)) | ||||
|     return true; | ||||
|  | ||||
|   uint32_t start_time = millis(); | ||||
|   while (this->available() < int(len)) { | ||||
|     if (millis() - start_time > 100) { | ||||
|       ESP_LOGE(TAG, "Reading from UART timed out at byte %u!", this->available()); | ||||
|       return false; | ||||
|     } | ||||
|     yield(); | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
| int UARTComponent::available() { | ||||
|   if (this->hw_serial_ != nullptr) { | ||||
|     return this->hw_serial_->available(); | ||||
|   } else { | ||||
|     return this->sw_serial_->available(); | ||||
|   } | ||||
| } | ||||
| void UARTComponent::flush() { | ||||
|   ESP_LOGVV(TAG, "    Flushing..."); | ||||
|   if (this->hw_serial_ != nullptr) { | ||||
|     this->hw_serial_->flush(); | ||||
|   } else { | ||||
|     this->sw_serial_->flush(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void ESP8266SoftwareSerial::setup(int8_t tx_pin, int8_t rx_pin, uint32_t baud_rate, uint8_t stop_bits) { | ||||
|   this->bit_time_ = F_CPU / baud_rate; | ||||
|   if (tx_pin != -1) { | ||||
|     auto pin = GPIOPin(tx_pin, OUTPUT); | ||||
|     pin.setup(); | ||||
|     this->tx_pin_ = pin.to_isr(); | ||||
|     this->tx_pin_->digital_write(true); | ||||
|   } | ||||
|   if (rx_pin != -1) { | ||||
|     auto pin = GPIOPin(rx_pin, INPUT); | ||||
|     pin.setup(); | ||||
|     this->rx_pin_ = pin.to_isr(); | ||||
|     this->rx_buffer_ = new uint8_t[this->rx_buffer_size_]; | ||||
|     pin.attach_interrupt(ESP8266SoftwareSerial::gpio_intr, this, FALLING); | ||||
|   } | ||||
|   this->stop_bits_ = stop_bits; | ||||
| } | ||||
| void ICACHE_RAM_ATTR ESP8266SoftwareSerial::gpio_intr(ESP8266SoftwareSerial *arg) { | ||||
|   uint32_t wait = arg->bit_time_ + arg->bit_time_ / 3 - 500; | ||||
|   const uint32_t start = ESP.getCycleCount(); | ||||
|   uint8_t rec = 0; | ||||
|   // Manually unroll the loop | ||||
|   rec |= arg->read_bit_(&wait, start) << 0; | ||||
|   rec |= arg->read_bit_(&wait, start) << 1; | ||||
|   rec |= arg->read_bit_(&wait, start) << 2; | ||||
|   rec |= arg->read_bit_(&wait, start) << 3; | ||||
|   rec |= arg->read_bit_(&wait, start) << 4; | ||||
|   rec |= arg->read_bit_(&wait, start) << 5; | ||||
|   rec |= arg->read_bit_(&wait, start) << 6; | ||||
|   rec |= arg->read_bit_(&wait, start) << 7; | ||||
|   // Stop bit | ||||
|   arg->wait_(&wait, start); | ||||
|   if (arg->stop_bits_ == 2) | ||||
|     arg->wait_(&wait, start); | ||||
|  | ||||
|   arg->rx_buffer_[arg->rx_in_pos_] = rec; | ||||
|   arg->rx_in_pos_ = (arg->rx_in_pos_ + 1) % arg->rx_buffer_size_; | ||||
|   // Clear RX pin so that the interrupt doesn't re-trigger right away again. | ||||
|   arg->rx_pin_->clear_interrupt(); | ||||
| } | ||||
| void ICACHE_RAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) { | ||||
|   if (this->tx_pin_ == nullptr) { | ||||
|     ESP_LOGE(TAG, "UART doesn't have TX pins set!"); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   { | ||||
|     InterruptLock lock; | ||||
|     uint32_t wait = this->bit_time_; | ||||
|     const uint32_t start = ESP.getCycleCount(); | ||||
|     // Start bit | ||||
|     this->write_bit_(false, &wait, start); | ||||
|     this->write_bit_(data & (1 << 0), &wait, start); | ||||
|     this->write_bit_(data & (1 << 1), &wait, start); | ||||
|     this->write_bit_(data & (1 << 2), &wait, start); | ||||
|     this->write_bit_(data & (1 << 3), &wait, start); | ||||
|     this->write_bit_(data & (1 << 4), &wait, start); | ||||
|     this->write_bit_(data & (1 << 5), &wait, start); | ||||
|     this->write_bit_(data & (1 << 6), &wait, start); | ||||
|     this->write_bit_(data & (1 << 7), &wait, start); | ||||
|     // Stop bit | ||||
|     this->write_bit_(true, &wait, start); | ||||
|     if (this->stop_bits_ == 2) | ||||
|       this->wait_(&wait, start); | ||||
|   } | ||||
| } | ||||
| void ICACHE_RAM_ATTR ESP8266SoftwareSerial::wait_(uint32_t *wait, const uint32_t &start) { | ||||
|   while (ESP.getCycleCount() - start < *wait) | ||||
|     ; | ||||
|   *wait += this->bit_time_; | ||||
| } | ||||
| bool ICACHE_RAM_ATTR ESP8266SoftwareSerial::read_bit_(uint32_t *wait, const uint32_t &start) { | ||||
|   this->wait_(wait, start); | ||||
|   return this->rx_pin_->digital_read(); | ||||
| } | ||||
| void ICACHE_RAM_ATTR ESP8266SoftwareSerial::write_bit_(bool bit, uint32_t *wait, const uint32_t &start) { | ||||
|   this->tx_pin_->digital_write(bit); | ||||
|   this->wait_(wait, start); | ||||
| } | ||||
| uint8_t ESP8266SoftwareSerial::read_byte() { | ||||
|   if (this->rx_in_pos_ == this->rx_out_pos_) | ||||
|     return 0; | ||||
|   uint8_t data = this->rx_buffer_[this->rx_out_pos_]; | ||||
|   this->rx_out_pos_ = (this->rx_out_pos_ + 1) % this->rx_buffer_size_; | ||||
|   return data; | ||||
| } | ||||
| uint8_t ESP8266SoftwareSerial::peek_byte() { | ||||
|   if (this->rx_in_pos_ == this->rx_out_pos_) | ||||
|     return 0; | ||||
|   return this->rx_buffer_[this->rx_out_pos_]; | ||||
| } | ||||
| void ESP8266SoftwareSerial::flush() { | ||||
|   // Flush is a NO-OP with software serial, all bytes are written immediately. | ||||
| } | ||||
| int ESP8266SoftwareSerial::available() { | ||||
|   int avail = int(this->rx_in_pos_) - int(this->rx_out_pos_); | ||||
|   if (avail < 0) | ||||
|     return avail + this->rx_buffer_size_; | ||||
|   return avail; | ||||
| } | ||||
| #endif  // ESP8266 | ||||
|  | ||||
| size_t UARTComponent::write(uint8_t data) { | ||||
|   this->write_byte(data); | ||||
|   return 1; | ||||
| @@ -382,7 +43,7 @@ void UARTComponent::check_logger_conflict_() { | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void UARTDevice::check_uart_settings(uint32_t baud_rate, uint8_t stop_bits) { | ||||
| void UARTDevice::check_uart_settings(uint32_t baud_rate, uint8_t stop_bits, UARTParityOptions parity, uint8_t nr_bits) { | ||||
|   if (this->parent_->baud_rate_ != baud_rate) { | ||||
|     ESP_LOGE(TAG, "  Invalid baud_rate: Integration requested baud_rate %u but you have %u!", baud_rate, | ||||
|              this->parent_->baud_rate_); | ||||
| @@ -391,6 +52,27 @@ void UARTDevice::check_uart_settings(uint32_t baud_rate, uint8_t stop_bits) { | ||||
|     ESP_LOGE(TAG, "  Invalid stop bits: Integration requested stop_bits %u but you have %u!", stop_bits, | ||||
|              this->parent_->stop_bits_); | ||||
|   } | ||||
|   if (this->parent_->nr_bits_ != nr_bits) { | ||||
|     ESP_LOGE(TAG, "  Invalid number of data bits: Integration requested %u data bits but you have %u!", nr_bits, | ||||
|              this->parent_->nr_bits_); | ||||
|   } | ||||
|   if (this->parent_->parity_ != parity) { | ||||
|     ESP_LOGE(TAG, "  Invalid parity: Integration requested parity %s but you have %s!", parity_to_str(parity), | ||||
|              parity_to_str(this->parent_->parity_)); | ||||
|   } | ||||
| } | ||||
|  | ||||
| const char *parity_to_str(UARTParityOptions parity) { | ||||
|   switch (parity) { | ||||
|     case UART_CONFIG_PARITY_NONE: | ||||
|       return "NONE"; | ||||
|     case UART_CONFIG_PARITY_EVEN: | ||||
|       return "EVEN"; | ||||
|     case UART_CONFIG_PARITY_ODD: | ||||
|       return "ODD"; | ||||
|     default: | ||||
|       return "UNKNOWN"; | ||||
|   } | ||||
| } | ||||
|  | ||||
| }  // namespace uart | ||||
|   | ||||
| @@ -7,10 +7,19 @@ | ||||
| namespace esphome { | ||||
| namespace uart { | ||||
|  | ||||
| enum UARTParityOptions { | ||||
|   UART_CONFIG_PARITY_NONE, | ||||
|   UART_CONFIG_PARITY_EVEN, | ||||
|   UART_CONFIG_PARITY_ODD, | ||||
| }; | ||||
|  | ||||
| const char *parity_to_str(UARTParityOptions parity); | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP8266 | ||||
| class ESP8266SoftwareSerial { | ||||
|  public: | ||||
|   void setup(int8_t tx_pin, int8_t rx_pin, uint32_t baud_rate, uint8_t stop_bits); | ||||
|   void setup(int8_t tx_pin, int8_t rx_pin, uint32_t baud_rate, uint8_t stop_bits, uint32_t nr_bits, | ||||
|              UARTParityOptions parity); | ||||
|  | ||||
|   uint8_t read_byte(); | ||||
|   uint8_t peek_byte(); | ||||
| @@ -21,6 +30,11 @@ class ESP8266SoftwareSerial { | ||||
|  | ||||
|   int available(); | ||||
|  | ||||
|   void begin(); | ||||
|   void end(); | ||||
|   GPIOPin *gpio_tx_pin_{nullptr}; | ||||
|   GPIOPin *gpio_rx_pin_{nullptr}; | ||||
|  | ||||
|  protected: | ||||
|   static void gpio_intr(ESP8266SoftwareSerial *arg); | ||||
|  | ||||
| @@ -34,6 +48,8 @@ class ESP8266SoftwareSerial { | ||||
|   volatile size_t rx_in_pos_{0}; | ||||
|   size_t rx_out_pos_{0}; | ||||
|   uint8_t stop_bits_; | ||||
|   uint8_t nr_bits_; | ||||
|   UARTParityOptions parity_; | ||||
|   ISRInternalGPIOPin *tx_pin_{nullptr}; | ||||
|   ISRInternalGPIOPin *rx_pin_{nullptr}; | ||||
| }; | ||||
| @@ -43,6 +59,8 @@ class UARTComponent : public Component, public Stream { | ||||
|  public: | ||||
|   void set_baud_rate(uint32_t baud_rate) { baud_rate_ = baud_rate; } | ||||
|  | ||||
|   uint32_t get_config(); | ||||
|  | ||||
|   void setup() override; | ||||
|  | ||||
|   void dump_config() override; | ||||
| @@ -53,6 +71,8 @@ class UARTComponent : public Component, public Stream { | ||||
|   void write_array(const std::vector<uint8_t> &data) { this->write_array(&data[0], data.size()); } | ||||
|  | ||||
|   void write_str(const char *str); | ||||
|   void end(); | ||||
|   void begin(); | ||||
|  | ||||
|   bool peek_byte(uint8_t *data); | ||||
|  | ||||
| @@ -74,6 +94,8 @@ class UARTComponent : public Component, public Stream { | ||||
|   void set_tx_pin(uint8_t tx_pin) { this->tx_pin_ = tx_pin; } | ||||
|   void set_rx_pin(uint8_t rx_pin) { this->rx_pin_ = rx_pin; } | ||||
|   void set_stop_bits(uint8_t stop_bits) { this->stop_bits_ = stop_bits; } | ||||
|   void set_data_bits(uint8_t nr_bits) { this->nr_bits_ = nr_bits; } | ||||
|   void set_parity(UARTParityOptions parity) { this->parity_ = parity; } | ||||
|  | ||||
|  protected: | ||||
|   void check_logger_conflict_(); | ||||
| @@ -88,6 +110,8 @@ class UARTComponent : public Component, public Stream { | ||||
|   optional<uint8_t> rx_pin_; | ||||
|   uint32_t baud_rate_; | ||||
|   uint8_t stop_bits_; | ||||
|   uint8_t nr_bits_; | ||||
|   UARTParityOptions parity_; | ||||
| }; | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
| @@ -130,9 +154,12 @@ class UARTDevice : public Stream { | ||||
|   size_t write(uint8_t data) override { return this->parent_->write(data); } | ||||
|   int read() override { return this->parent_->read(); } | ||||
|   int peek() override { return this->parent_->peek(); } | ||||
|   void end() { this->parent_->end(); } | ||||
|   void begin() { this->parent_->begin(); } | ||||
|  | ||||
|   /// Check that the configuration of the UART bus matches the provided values and otherwise print a warning | ||||
|   void check_uart_settings(uint32_t baud_rate, uint8_t stop_bits = 1); | ||||
|   void check_uart_settings(uint32_t baud_rate, uint8_t stop_bits = 1, | ||||
|                            UARTParityOptions parity = UART_CONFIG_PARITY_NONE, uint8_t nr_bits = 8); | ||||
|  | ||||
|  protected: | ||||
|   UARTComponent *parent_{nullptr}; | ||||
|   | ||||
							
								
								
									
										162
									
								
								esphome/components/uart/uart_esp32.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								esphome/components/uart/uart_esp32.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,162 @@ | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
| #include "uart.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/application.h" | ||||
| #include "esphome/core/defines.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace uart { | ||||
| static const char *TAG = "uart_esp32"; | ||||
| uint8_t next_uart_num = 1; | ||||
|  | ||||
| static const uint32_t UART_PARITY_EVEN = 0 << 0; | ||||
| static const uint32_t UART_PARITY_ODD = 1 << 0; | ||||
| static const uint32_t UART_PARITY_EN = 1 << 1; | ||||
| static const uint32_t UART_NB_BIT_5 = 0 << 2; | ||||
| static const uint32_t UART_NB_BIT_6 = 1 << 2; | ||||
| static const uint32_t UART_NB_BIT_7 = 2 << 2; | ||||
| static const uint32_t UART_NB_BIT_8 = 3 << 2; | ||||
| static const uint32_t UART_NB_STOP_BIT_1 = 1 << 4; | ||||
| static const uint32_t UART_NB_STOP_BIT_2 = 3 << 4; | ||||
| static const uint32_t UART_TICK_APB_CLOCK = 1 << 27; | ||||
|  | ||||
| uint32_t UARTComponent::get_config() { | ||||
|   uint32_t config = 0; | ||||
|  | ||||
|   /* | ||||
|    * All bits numbers below come from | ||||
|    * framework-arduinoespressif32/cores/esp32/esp32-hal-uart.h | ||||
|    * And more specifically conf0 union in uart_dev_t. | ||||
|    * | ||||
|    * Below is bit used from conf0 union. | ||||
|    * <name>:<bits position>  <values> | ||||
|    * parity:0                0:even 1:odd | ||||
|    * parity_en:1             Set this bit to enable uart parity check. | ||||
|    * bit_num:2-4             0:5bits 1:6bits 2:7bits 3:8bits | ||||
|    * stop_bit_num:4-6        stop bit. 1:1bit  2:1.5bits  3:2bits | ||||
|    * tick_ref_always_on:27   select the clock.1:apb clock:ref_tick | ||||
|    */ | ||||
|  | ||||
|   if (this->parity_ == UART_CONFIG_PARITY_EVEN) | ||||
|     config |= UART_PARITY_EVEN | UART_PARITY_EN; | ||||
|   else if (this->parity_ == UART_CONFIG_PARITY_ODD) | ||||
|     config |= UART_PARITY_ODD | UART_PARITY_EN; | ||||
|  | ||||
|   switch (this->nr_bits_) { | ||||
|     case 5: | ||||
|       config |= UART_NB_BIT_5; | ||||
|       break; | ||||
|     case 6: | ||||
|       config |= UART_NB_BIT_6; | ||||
|       break; | ||||
|     case 7: | ||||
|       config |= UART_NB_BIT_7; | ||||
|       break; | ||||
|     case 8: | ||||
|       config |= UART_NB_BIT_8; | ||||
|       break; | ||||
|   } | ||||
|  | ||||
|   if (this->stop_bits_ == 1) | ||||
|     config |= UART_NB_STOP_BIT_1; | ||||
|   else | ||||
|     config |= UART_NB_STOP_BIT_2; | ||||
|  | ||||
|   config |= UART_TICK_APB_CLOCK; | ||||
|  | ||||
|   return config; | ||||
| } | ||||
|  | ||||
| void UARTComponent::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up UART..."); | ||||
|   // Use Arduino HardwareSerial UARTs if all used pins match the ones | ||||
|   // preconfigured by the platform. For example if RX disabled but TX pin | ||||
|   // is 1 we still want to use Serial. | ||||
|   if (this->tx_pin_.value_or(1) == 1 && this->rx_pin_.value_or(3) == 3) { | ||||
|     this->hw_serial_ = &Serial; | ||||
|   } else { | ||||
|     this->hw_serial_ = new HardwareSerial(next_uart_num++); | ||||
|   } | ||||
|   int8_t tx = this->tx_pin_.has_value() ? *this->tx_pin_ : -1; | ||||
|   int8_t rx = this->rx_pin_.has_value() ? *this->rx_pin_ : -1; | ||||
|   this->hw_serial_->begin(this->baud_rate_, get_config(), rx, tx); | ||||
| } | ||||
|  | ||||
| void UARTComponent::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "UART Bus:"); | ||||
|   if (this->tx_pin_.has_value()) { | ||||
|     ESP_LOGCONFIG(TAG, "  TX Pin: GPIO%d", *this->tx_pin_); | ||||
|   } | ||||
|   if (this->rx_pin_.has_value()) { | ||||
|     ESP_LOGCONFIG(TAG, "  RX Pin: GPIO%d", *this->rx_pin_); | ||||
|   } | ||||
|   ESP_LOGCONFIG(TAG, "  Baud Rate: %u baud", this->baud_rate_); | ||||
|   ESP_LOGCONFIG(TAG, "  Bits: %u", this->nr_bits_); | ||||
|   ESP_LOGCONFIG(TAG, "  Parity: %s", parity_to_str(this->parity_)); | ||||
|   ESP_LOGCONFIG(TAG, "  Stop bits: %u", this->stop_bits_); | ||||
|   this->check_logger_conflict_(); | ||||
| } | ||||
|  | ||||
| void UARTComponent::write_byte(uint8_t data) { | ||||
|   this->hw_serial_->write(data); | ||||
|   ESP_LOGVV(TAG, "    Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data), data); | ||||
| } | ||||
| void UARTComponent::write_array(const uint8_t *data, size_t len) { | ||||
|   this->hw_serial_->write(data, len); | ||||
|   for (size_t i = 0; i < len; i++) { | ||||
|     ESP_LOGVV(TAG, "    Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); | ||||
|   } | ||||
| } | ||||
| void UARTComponent::write_str(const char *str) { | ||||
|   this->hw_serial_->write(str); | ||||
|   ESP_LOGVV(TAG, "    Wrote \"%s\"", str); | ||||
| } | ||||
| void UARTComponent::end() { this->hw_serial_->end(); } | ||||
| void UARTComponent::begin() { this->hw_serial_->begin(this->baud_rate_, get_config()); } | ||||
| bool UARTComponent::read_byte(uint8_t *data) { | ||||
|   if (!this->check_read_timeout_()) | ||||
|     return false; | ||||
|   *data = this->hw_serial_->read(); | ||||
|   ESP_LOGVV(TAG, "    Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(*data), *data); | ||||
|   return true; | ||||
| } | ||||
| bool UARTComponent::peek_byte(uint8_t *data) { | ||||
|   if (!this->check_read_timeout_()) | ||||
|     return false; | ||||
|   *data = this->hw_serial_->peek(); | ||||
|   return true; | ||||
| } | ||||
| bool UARTComponent::read_array(uint8_t *data, size_t len) { | ||||
|   if (!this->check_read_timeout_(len)) | ||||
|     return false; | ||||
|   this->hw_serial_->readBytes(data, len); | ||||
|   for (size_t i = 0; i < len; i++) { | ||||
|     ESP_LOGVV(TAG, "    Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
| bool UARTComponent::check_read_timeout_(size_t len) { | ||||
|   if (this->available() >= len) | ||||
|     return true; | ||||
|  | ||||
|   uint32_t start_time = millis(); | ||||
|   while (this->available() < len) { | ||||
|     if (millis() - start_time > 1000) { | ||||
|       ESP_LOGE(TAG, "Reading from UART timed out at byte %u!", this->available()); | ||||
|       return false; | ||||
|     } | ||||
|     yield(); | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
| int UARTComponent::available() { return this->hw_serial_->available(); } | ||||
| void UARTComponent::flush() { | ||||
|   ESP_LOGVV(TAG, "    Flushing..."); | ||||
|   this->hw_serial_->flush(); | ||||
| } | ||||
|  | ||||
| }  // namespace uart | ||||
| }  // namespace esphome | ||||
| #endif  // ESP32 | ||||
							
								
								
									
										330
									
								
								esphome/components/uart/uart_esp8266.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										330
									
								
								esphome/components/uart/uart_esp8266.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,330 @@ | ||||
| #ifdef ARDUINO_ARCH_ESP8266 | ||||
| #include "uart.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/application.h" | ||||
| #include "esphome/core/defines.h" | ||||
| # | ||||
| namespace esphome { | ||||
| namespace uart { | ||||
|  | ||||
| static const char *TAG = "uart_esp8266"; | ||||
| uint32_t UARTComponent::get_config() { | ||||
|   uint32_t config = 0; | ||||
|  | ||||
|   if (this->parity_ == UART_CONFIG_PARITY_NONE) | ||||
|     config |= UART_PARITY_NONE; | ||||
|   else if (this->parity_ == UART_CONFIG_PARITY_EVEN) | ||||
|     config |= UART_PARITY_EVEN; | ||||
|   else if (this->parity_ == UART_CONFIG_PARITY_ODD) | ||||
|     config |= UART_PARITY_ODD; | ||||
|  | ||||
|   switch (this->nr_bits_) { | ||||
|     case 5: | ||||
|       config |= UART_NB_BIT_5; | ||||
|       break; | ||||
|     case 6: | ||||
|       config |= UART_NB_BIT_6; | ||||
|       break; | ||||
|     case 7: | ||||
|       config |= UART_NB_BIT_7; | ||||
|       break; | ||||
|     case 8: | ||||
|       config |= UART_NB_BIT_8; | ||||
|       break; | ||||
|   } | ||||
|  | ||||
|   if (this->stop_bits_ == 1) | ||||
|     config |= UART_NB_STOP_BIT_1; | ||||
|   else | ||||
|     config |= UART_NB_STOP_BIT_2; | ||||
|  | ||||
|   return config; | ||||
| } | ||||
|  | ||||
| void UARTComponent::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up UART bus..."); | ||||
|   // Use Arduino HardwareSerial UARTs if all used pins match the ones | ||||
|   // preconfigured by the platform. For example if RX disabled but TX pin | ||||
|   // is 1 we still want to use Serial. | ||||
|   SerialConfig config = static_cast<SerialConfig>(get_config()); | ||||
|  | ||||
|   if (this->tx_pin_.value_or(1) == 1 && this->rx_pin_.value_or(3) == 3) { | ||||
|     this->hw_serial_ = &Serial; | ||||
|     this->hw_serial_->begin(this->baud_rate_, config); | ||||
|   } else if (this->tx_pin_.value_or(15) == 15 && this->rx_pin_.value_or(13) == 13) { | ||||
|     this->hw_serial_ = &Serial; | ||||
|     this->hw_serial_->begin(this->baud_rate_, config); | ||||
|     this->hw_serial_->swap(); | ||||
|   } else if (this->tx_pin_.value_or(2) == 2 && this->rx_pin_.value_or(8) == 8) { | ||||
|     this->hw_serial_ = &Serial1; | ||||
|     this->hw_serial_->begin(this->baud_rate_, config); | ||||
|   } else { | ||||
|     this->sw_serial_ = new ESP8266SoftwareSerial(); | ||||
|     int8_t tx = this->tx_pin_.has_value() ? *this->tx_pin_ : -1; | ||||
|     int8_t rx = this->rx_pin_.has_value() ? *this->rx_pin_ : -1; | ||||
|     this->sw_serial_->setup(tx, rx, this->baud_rate_, this->stop_bits_, this->nr_bits_, this->parity_); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void UARTComponent::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "UART Bus:"); | ||||
|   if (this->tx_pin_.has_value()) { | ||||
|     ESP_LOGCONFIG(TAG, "  TX Pin: GPIO%d", *this->tx_pin_); | ||||
|   } | ||||
|   if (this->rx_pin_.has_value()) { | ||||
|     ESP_LOGCONFIG(TAG, "  RX Pin: GPIO%d", *this->rx_pin_); | ||||
|   } | ||||
|   ESP_LOGCONFIG(TAG, "  Baud Rate: %u baud", this->baud_rate_); | ||||
|   ESP_LOGCONFIG(TAG, "  Bits: %u", this->nr_bits_); | ||||
|   ESP_LOGCONFIG(TAG, "  Parity: %s", parity_to_str(this->parity_)); | ||||
|   ESP_LOGCONFIG(TAG, "  Stop bits: %u", this->stop_bits_); | ||||
|   if (this->hw_serial_ != nullptr) { | ||||
|     ESP_LOGCONFIG(TAG, "  Using hardware serial interface."); | ||||
|   } else { | ||||
|     ESP_LOGCONFIG(TAG, "  Using software serial"); | ||||
|   } | ||||
|   this->check_logger_conflict_(); | ||||
| } | ||||
|  | ||||
| void UARTComponent::write_byte(uint8_t data) { | ||||
|   if (this->hw_serial_ != nullptr) { | ||||
|     this->hw_serial_->write(data); | ||||
|   } else { | ||||
|     this->sw_serial_->write_byte(data); | ||||
|   } | ||||
|   ESP_LOGVV(TAG, "    Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data), data); | ||||
| } | ||||
| void UARTComponent::write_array(const uint8_t *data, size_t len) { | ||||
|   if (this->hw_serial_ != nullptr) { | ||||
|     this->hw_serial_->write(data, len); | ||||
|   } else { | ||||
|     for (size_t i = 0; i < len; i++) | ||||
|       this->sw_serial_->write_byte(data[i]); | ||||
|   } | ||||
|   for (size_t i = 0; i < len; i++) { | ||||
|     ESP_LOGVV(TAG, "    Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); | ||||
|   } | ||||
| } | ||||
| void UARTComponent::write_str(const char *str) { | ||||
|   if (this->hw_serial_ != nullptr) { | ||||
|     this->hw_serial_->write(str); | ||||
|   } else { | ||||
|     const auto *data = reinterpret_cast<const uint8_t *>(str); | ||||
|     for (size_t i = 0; data[i] != 0; i++) | ||||
|       this->sw_serial_->write_byte(data[i]); | ||||
|   } | ||||
|   ESP_LOGVV(TAG, "    Wrote \"%s\"", str); | ||||
| } | ||||
| void UARTComponent::end() { | ||||
|   if (this->hw_serial_ != nullptr) | ||||
|     this->hw_serial_->end(); | ||||
|   else if (this->sw_serial_ != nullptr) | ||||
|     this->sw_serial_->end(); | ||||
| } | ||||
| void UARTComponent::begin() { | ||||
|   if (this->hw_serial_ != nullptr) | ||||
|     this->hw_serial_->begin(this->baud_rate_, static_cast<SerialConfig>(get_config())); | ||||
|   else if (this->sw_serial_ != nullptr) | ||||
|     this->sw_serial_->begin(); | ||||
| } | ||||
| bool UARTComponent::read_byte(uint8_t *data) { | ||||
|   if (!this->check_read_timeout_()) | ||||
|     return false; | ||||
|   if (this->hw_serial_ != nullptr) { | ||||
|     *data = this->hw_serial_->read(); | ||||
|   } else { | ||||
|     *data = this->sw_serial_->read_byte(); | ||||
|   } | ||||
|   ESP_LOGVV(TAG, "    Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(*data), *data); | ||||
|   return true; | ||||
| } | ||||
| bool UARTComponent::peek_byte(uint8_t *data) { | ||||
|   if (!this->check_read_timeout_()) | ||||
|     return false; | ||||
|   if (this->hw_serial_ != nullptr) { | ||||
|     *data = this->hw_serial_->peek(); | ||||
|   } else { | ||||
|     *data = this->sw_serial_->peek_byte(); | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
| bool UARTComponent::read_array(uint8_t *data, size_t len) { | ||||
|   if (!this->check_read_timeout_(len)) | ||||
|     return false; | ||||
|   if (this->hw_serial_ != nullptr) { | ||||
|     this->hw_serial_->readBytes(data, len); | ||||
|   } else { | ||||
|     for (size_t i = 0; i < len; i++) | ||||
|       data[i] = this->sw_serial_->read_byte(); | ||||
|   } | ||||
|   for (size_t i = 0; i < len; i++) { | ||||
|     ESP_LOGVV(TAG, "    Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
| bool UARTComponent::check_read_timeout_(size_t len) { | ||||
|   if (this->available() >= int(len)) | ||||
|     return true; | ||||
|  | ||||
|   uint32_t start_time = millis(); | ||||
|   while (this->available() < int(len)) { | ||||
|     if (millis() - start_time > 100) { | ||||
|       ESP_LOGE(TAG, "Reading from UART timed out at byte %u!", this->available()); | ||||
|       return false; | ||||
|     } | ||||
|     yield(); | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
| int UARTComponent::available() { | ||||
|   if (this->hw_serial_ != nullptr) { | ||||
|     return this->hw_serial_->available(); | ||||
|   } else { | ||||
|     return this->sw_serial_->available(); | ||||
|   } | ||||
| } | ||||
| void UARTComponent::flush() { | ||||
|   ESP_LOGVV(TAG, "    Flushing..."); | ||||
|   if (this->hw_serial_ != nullptr) { | ||||
|     this->hw_serial_->flush(); | ||||
|   } else { | ||||
|     this->sw_serial_->flush(); | ||||
|   } | ||||
| } | ||||
| void ESP8266SoftwareSerial::end() { | ||||
|   /* Because of this bug: https://github.com/esp8266/Arduino/issues/6049 | ||||
|    * detach_interrupt can't called. | ||||
|    * So simply reset rx_in_pos and rx_out_pos even if it's totally racy with | ||||
|    * the interrupt. | ||||
|    */ | ||||
|   // this->gpio_rx_pin_->detach_interrupt(); | ||||
|   this->rx_in_pos_ = 0; | ||||
|   this->rx_out_pos_ = 0; | ||||
| } | ||||
| void ESP8266SoftwareSerial::begin() { | ||||
|   /* attach_interrupt() is also not safe because gpio_intr() may | ||||
|    * endup with arg == nullptr. | ||||
|    */ | ||||
|   // this->gpio_rx_pin_->attach_interrupt(ESP8266SoftwareSerial::gpio_intr, this, FALLING); | ||||
| } | ||||
| void ESP8266SoftwareSerial::setup(int8_t tx_pin, int8_t rx_pin, uint32_t baud_rate, uint8_t stop_bits, uint32_t nr_bits, | ||||
|                                   UARTParityOptions parity) { | ||||
|   this->bit_time_ = F_CPU / baud_rate; | ||||
|   this->stop_bits_ = stop_bits; | ||||
|   this->nr_bits_ = nr_bits; | ||||
|   this->parity_ = parity; | ||||
|   if (tx_pin != -1) { | ||||
|     auto pin = GPIOPin(tx_pin, OUTPUT); | ||||
|     this->gpio_tx_pin_ = &pin; | ||||
|     pin.setup(); | ||||
|     this->tx_pin_ = pin.to_isr(); | ||||
|     this->tx_pin_->digital_write(true); | ||||
|   } | ||||
|   if (rx_pin != -1) { | ||||
|     auto pin = GPIOPin(rx_pin, INPUT); | ||||
|     pin.setup(); | ||||
|     this->gpio_rx_pin_ = &pin; | ||||
|     this->rx_pin_ = pin.to_isr(); | ||||
|     this->rx_buffer_ = new uint8_t[this->rx_buffer_size_]; | ||||
|     pin.attach_interrupt(ESP8266SoftwareSerial::gpio_intr, this, FALLING); | ||||
|   } | ||||
| } | ||||
| void ICACHE_RAM_ATTR ESP8266SoftwareSerial::gpio_intr(ESP8266SoftwareSerial *arg) { | ||||
|   uint32_t wait = arg->bit_time_ + arg->bit_time_ / 3 - 500; | ||||
|   const uint32_t start = ESP.getCycleCount(); | ||||
|   uint8_t rec = 0; | ||||
|   // Manually unroll the loop | ||||
|   for (int i = 0; i < arg->nr_bits_; i++) | ||||
|     rec |= arg->read_bit_(&wait, start) << i; | ||||
|  | ||||
|   /* If parity is enabled, just read it and ignore it. */ | ||||
|   /* TODO: Should we check parity? Or is it too slow for nothing added..*/ | ||||
|   if (arg->parity_ == UART_CONFIG_PARITY_EVEN) | ||||
|     arg->read_bit_(&wait, start); | ||||
|   else if (arg->parity_ == UART_CONFIG_PARITY_ODD) | ||||
|     arg->read_bit_(&wait, start); | ||||
|  | ||||
|   // Stop bit | ||||
|   arg->wait_(&wait, start); | ||||
|   if (arg->stop_bits_ == 2) | ||||
|     arg->wait_(&wait, start); | ||||
|  | ||||
|   arg->rx_buffer_[arg->rx_in_pos_] = rec; | ||||
|   arg->rx_in_pos_ = (arg->rx_in_pos_ + 1) % arg->rx_buffer_size_; | ||||
|   // Clear RX pin so that the interrupt doesn't re-trigger right away again. | ||||
|   arg->rx_pin_->clear_interrupt(); | ||||
| } | ||||
| void ICACHE_RAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) { | ||||
|   if (this->tx_pin_ == nullptr) { | ||||
|     ESP_LOGE(TAG, "UART doesn't have TX pins set!"); | ||||
|     return; | ||||
|   } | ||||
|   bool parity_bit = false; | ||||
|   bool need_parity_bit = true; | ||||
|   if (this->parity_ == UART_CONFIG_PARITY_EVEN) | ||||
|     parity_bit = true; | ||||
|   else if (this->parity_ == UART_CONFIG_PARITY_ODD) | ||||
|     parity_bit = false; | ||||
|   else | ||||
|     need_parity_bit = false; | ||||
|  | ||||
|   { | ||||
|     InterruptLock lock; | ||||
|     uint32_t wait = this->bit_time_; | ||||
|     const uint32_t start = ESP.getCycleCount(); | ||||
|     // Start bit | ||||
|     this->write_bit_(false, &wait, start); | ||||
|     for (int i = 0; i < this->nr_bits_; i++) { | ||||
|       bool bit = data & (1 << i); | ||||
|       this->write_bit_(bit, &wait, start); | ||||
|       if (need_parity_bit) | ||||
|         parity_bit ^= bit; | ||||
|     } | ||||
|     if (need_parity_bit) | ||||
|       this->write_bit_(parity_bit, &wait, start); | ||||
|     // Stop bit | ||||
|     this->write_bit_(true, &wait, start); | ||||
|     if (this->stop_bits_ == 2) | ||||
|       this->wait_(&wait, start); | ||||
|   } | ||||
| } | ||||
| void ICACHE_RAM_ATTR ESP8266SoftwareSerial::wait_(uint32_t *wait, const uint32_t &start) { | ||||
|   while (ESP.getCycleCount() - start < *wait) | ||||
|     ; | ||||
|   *wait += this->bit_time_; | ||||
| } | ||||
| bool ICACHE_RAM_ATTR ESP8266SoftwareSerial::read_bit_(uint32_t *wait, const uint32_t &start) { | ||||
|   this->wait_(wait, start); | ||||
|   return this->rx_pin_->digital_read(); | ||||
| } | ||||
| void ICACHE_RAM_ATTR ESP8266SoftwareSerial::write_bit_(bool bit, uint32_t *wait, const uint32_t &start) { | ||||
|   this->tx_pin_->digital_write(bit); | ||||
|   this->wait_(wait, start); | ||||
| } | ||||
| uint8_t ESP8266SoftwareSerial::read_byte() { | ||||
|   if (this->rx_in_pos_ == this->rx_out_pos_) | ||||
|     return 0; | ||||
|   uint8_t data = this->rx_buffer_[this->rx_out_pos_]; | ||||
|   this->rx_out_pos_ = (this->rx_out_pos_ + 1) % this->rx_buffer_size_; | ||||
|   return data; | ||||
| } | ||||
| uint8_t ESP8266SoftwareSerial::peek_byte() { | ||||
|   if (this->rx_in_pos_ == this->rx_out_pos_) | ||||
|     return 0; | ||||
|   return this->rx_buffer_[this->rx_out_pos_]; | ||||
| } | ||||
| void ESP8266SoftwareSerial::flush() { | ||||
|   // Flush is a NO-OP with software serial, all bytes are written immediately. | ||||
| } | ||||
| int ESP8266SoftwareSerial::available() { | ||||
|   int avail = int(this->rx_in_pos_) - int(this->rx_out_pos_); | ||||
|   if (avail < 0) | ||||
|     return avail + this->rx_buffer_size_; | ||||
|   return avail; | ||||
| } | ||||
|  | ||||
| }  // namespace uart | ||||
| }  // namespace esphome | ||||
| #endif  // ESP8266 | ||||
| @@ -12,6 +12,7 @@ typedef struct {        // NOLINT | ||||
|  | ||||
| void ICACHE_RAM_ATTR __attachInterruptArg(uint8_t pin, void (*)(void *), void *fp,  // NOLINT | ||||
|                                           int mode); | ||||
| void ICACHE_RAM_ATTR __detachInterrupt(uint8_t pin);  // NOLINT | ||||
| }; | ||||
| #endif | ||||
|  | ||||
| @@ -226,6 +227,15 @@ void ICACHE_RAM_ATTR interrupt_handler(void *arg) { | ||||
| } | ||||
| #endif | ||||
|  | ||||
| void GPIOPin::detach_interrupt() const { this->detach_interrupt_(); } | ||||
| void GPIOPin::detach_interrupt_() const { | ||||
| #ifdef ARDUINO_ARCH_ESP8266 | ||||
|   __detachInterrupt(get_pin()); | ||||
| #endif | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|   detachInterrupt(get_pin()); | ||||
| #endif | ||||
| } | ||||
| void GPIOPin::attach_interrupt_(void (*func)(void *), void *arg, int mode) const { | ||||
|   if (this->inverted_) { | ||||
|     if (mode == RISING) { | ||||
|   | ||||
| @@ -94,11 +94,13 @@ class GPIOPin { | ||||
|   bool is_inverted() const; | ||||
|  | ||||
|   template<typename T> void attach_interrupt(void (*func)(T *), T *arg, int mode) const; | ||||
|   void detach_interrupt() const; | ||||
|  | ||||
|   ISRInternalGPIOPin *to_isr() const; | ||||
|  | ||||
|  protected: | ||||
|   void attach_interrupt_(void (*func)(void *), void *arg, int mode) const; | ||||
|   void detach_interrupt_() const; | ||||
|  | ||||
|   const uint8_t pin_; | ||||
|   const uint8_t mode_; | ||||
| @@ -114,5 +116,4 @@ class GPIOPin { | ||||
| template<typename T> void GPIOPin::attach_interrupt(void (*func)(T *), T *arg, int mode) const { | ||||
|   this->attach_interrupt_(reinterpret_cast<void (*)(void *)>(func), arg, mode); | ||||
| } | ||||
|  | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -132,6 +132,9 @@ uart: | ||||
|   rx_pin: GPIO23 | ||||
|   baud_rate: 115200 | ||||
|   id: uart0 | ||||
|   parity: NONE | ||||
|   data_bits: 8 | ||||
|   stop_bits: 1 | ||||
|  | ||||
| ota: | ||||
|   safe_mode: True | ||||
|   | ||||
		Reference in New Issue
	
	Block a user