From fb2b7ade41dc3f5fae8a68e034b6506bf5902b0b Mon Sep 17 00:00:00 2001 From: 0hax <43876620+0hax@users.noreply.github.com> Date: Sun, 24 May 2020 23:59:07 +0200 Subject: [PATCH] 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 --- esphome/components/uart/__init__.py | 14 + esphome/components/uart/uart.cpp | 362 ++--------------------- esphome/components/uart/uart.h | 31 +- esphome/components/uart/uart_esp32.cpp | 162 ++++++++++ esphome/components/uart/uart_esp8266.cpp | 330 +++++++++++++++++++++ esphome/core/esphal.cpp | 10 + esphome/core/esphal.h | 3 +- tests/test1.yaml | 3 + 8 files changed, 572 insertions(+), 343 deletions(-) create mode 100644 esphome/components/uart/uart_esp32.cpp create mode 100644 esphome/components/uart/uart_esp8266.cpp diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index 4312bd5d10..06208dc621 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -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! diff --git a/esphome/components/uart/uart.cpp b/esphome/components/uart/uart.cpp index 08fc0a326e..cf2d00c929 100644 --- a/esphome/components/uart/uart.cpp +++ b/esphome/components/uart/uart.cpp @@ -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(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(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 diff --git a/esphome/components/uart/uart.h b/esphome/components/uart/uart.h index 76e7496b80..5528da1d5f 100644 --- a/esphome/components/uart/uart.h +++ b/esphome/components/uart/uart.h @@ -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 &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 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}; diff --git a/esphome/components/uart/uart_esp32.cpp b/esphome/components/uart/uart_esp32.cpp new file mode 100644 index 0000000000..cb6ac843d1 --- /dev/null +++ b/esphome/components/uart/uart_esp32.cpp @@ -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. + * : + * 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 diff --git a/esphome/components/uart/uart_esp8266.cpp b/esphome/components/uart/uart_esp8266.cpp new file mode 100644 index 0000000000..59a08677b5 --- /dev/null +++ b/esphome/components/uart/uart_esp8266.cpp @@ -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(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(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(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 diff --git a/esphome/core/esphal.cpp b/esphome/core/esphal.cpp index 13d54e726d..2389a2a7f6 100644 --- a/esphome/core/esphal.cpp +++ b/esphome/core/esphal.cpp @@ -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) { diff --git a/esphome/core/esphal.h b/esphome/core/esphal.h index 493f7f5e37..453e094acc 100644 --- a/esphome/core/esphal.h +++ b/esphome/core/esphal.h @@ -94,11 +94,13 @@ class GPIOPin { bool is_inverted() const; template 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 void GPIOPin::attach_interrupt(void (*func)(T *), T *arg, int mode) const { this->attach_interrupt_(reinterpret_cast(func), arg, mode); } - } // namespace esphome diff --git a/tests/test1.yaml b/tests/test1.yaml index 6e63e8de31..2171be844a 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -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