From de7917181572004a9bc154393828a79dc59d88f9 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 7 Nov 2022 10:01:40 +1300 Subject: [PATCH] RP2040 uart support (#3990) --- esphome/components/logger/__init__.py | 14 +- esphome/components/logger/logger.cpp | 26 ++- esphome/components/logger/logger.h | 17 +- esphome/components/uart/__init__.py | 3 + .../components/uart/uart_component_rp2040.cpp | 184 ++++++++++++++++++ .../components/uart/uart_component_rp2040.h | 43 ++++ 6 files changed, 270 insertions(+), 17 deletions(-) create mode 100644 esphome/components/uart/uart_component_rp2040.cpp create mode 100644 esphome/components/uart/uart_component_rp2040.h diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 333e109379..9f0fa22fc0 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -77,7 +77,7 @@ UART_SELECTION_ESP8266 = [UART0, UART0_SWAP, UART1] ESP_IDF_UARTS = [USB_CDC, USB_SERIAL_JTAG] -UART_SELECTION_RP2040 = [UART0, UART1] +UART_SELECTION_RP2040 = [USB_CDC, UART0, UART1] HARDWARE_UART_TO_UART_SELECTION = { UART0: logger_ns.UART_SELECTION_UART0, @@ -99,10 +99,9 @@ is_log_level = cv.one_of(*LOG_LEVELS, upper=True) def uart_selection(value): - if value.upper() in ESP_IDF_UARTS: - if not CORE.using_esp_idf: - raise cv.Invalid(f"Only esp-idf framework supports {value}.") if CORE.is_esp32: + if value.upper() in ESP_IDF_UARTS and not CORE.using_esp_idf: + raise cv.Invalid(f"Only esp-idf framework supports {value}.") variant = get_esp32_variant() if variant in UART_SELECTION_ESP32: return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value) @@ -137,7 +136,12 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int, cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.validate_bytes, cv.Optional(CONF_DEASSERT_RTS_DTR, default=False): cv.boolean, - cv.Optional(CONF_HARDWARE_UART, default=UART0): uart_selection, + cv.SplitDefault( + CONF_HARDWARE_UART, + esp8266=UART0, + esp32=UART0, + rp2040=USB_CDC, + ): uart_selection, cv.Optional(CONF_LEVEL, default="DEBUG"): is_log_level, cv.Optional(CONF_LOGS, default={}): cv.Schema( { diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 271f99ba58..8a3c6a951d 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -1,15 +1,15 @@ #include "logger.h" #ifdef USE_ESP_IDF -#include "freertos/FreeRTOS.h" #include -#endif +#include "freertos/FreeRTOS.h" +#endif // USE_ESP_IDF #if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) #include -#endif -#include "esphome/core/log.h" +#endif // USE_ESP32_FRAMEWORK_ARDUINO || USE_ESP_IDF #include "esphome/core/hal.h" +#include "esphome/core/log.h" namespace esphome { namespace logger { @@ -161,8 +161,13 @@ void Logger::pre_setup() { #ifdef USE_ESP8266 case UART_SELECTION_UART0_SWAP: #endif +#ifdef USE_RP2040 + this->hw_serial_ = &Serial1; + Serial1.begin(this->baud_rate_); +#else this->hw_serial_ = &Serial; Serial.begin(this->baud_rate_); +#endif #ifdef USE_ESP8266 if (this->uart_ == UART_SELECTION_UART0_SWAP) { Serial.swap(); @@ -171,8 +176,13 @@ void Logger::pre_setup() { #endif break; case UART_SELECTION_UART1: +#ifdef USE_RP2040 + this->hw_serial_ = &Serial2; + Serial2.begin(this->baud_rate_); +#else this->hw_serial_ = &Serial1; Serial1.begin(this->baud_rate_); +#endif #ifdef USE_ESP8266 Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); #endif @@ -183,6 +193,12 @@ void Logger::pre_setup() { this->hw_serial_ = &Serial2; Serial2.begin(this->baud_rate_); break; +#endif +#ifdef USE_RP2040 + case UART_SELECTION_USB_CDC: + this->hw_serial_ = &Serial; + Serial.begin(this->baud_rate_); + break; #endif } #endif // USE_ARDUINO @@ -271,7 +287,7 @@ const char *const UART_SELECTIONS[] = { const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"}; #endif #ifdef USE_RP2040 -const char *const UART_SELECTIONS[] = {"UART0", "UART1"}; +const char *const UART_SELECTIONS[] = {"UART0", "UART1", "USB_CDC"}; #endif // USE_ESP8266 void Logger::dump_config() { ESP_LOGCONFIG(TAG, "Logger:"); diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 0251faf987..0e12acacd8 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -18,7 +18,7 @@ #ifdef USE_ESP_IDF #include -#endif +#endif // USE_ESP_IDF namespace esphome { @@ -34,19 +34,22 @@ enum UARTSelection { #if defined(USE_ESP32) #if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) UART_SELECTION_UART2, -#endif +#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3 #ifdef USE_ESP_IDF #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) UART_SELECTION_USB_CDC, -#endif +#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) UART_SELECTION_USB_SERIAL_JTAG, -#endif -#endif -#endif +#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3 +#endif // USE_ESP_IDF +#endif // USE_ESP32 #ifdef USE_ESP8266 UART_SELECTION_UART0_SWAP, -#endif +#endif // USE_ESP8266 +#ifdef USE_RP2040 + UART_SELECTION_USB_CDC, +#endif // USE_RP2040 }; class Logger : public Component { diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index a63b220fc7..01a1049568 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -41,6 +41,7 @@ ESP32ArduinoUARTComponent = uart_ns.class_( ESP8266UartComponent = uart_ns.class_( "ESP8266UartComponent", UARTComponent, cg.Component ) +RP2040UartComponent = uart_ns.class_("RP2040UartComponent", UARTComponent, cg.Component) UARTDevice = uart_ns.class_("UARTDevice") UARTWriteAction = uart_ns.class_("UARTWriteAction", automation.Action) @@ -89,6 +90,8 @@ def _uart_declare_type(value): return cv.declare_id(ESP32ArduinoUARTComponent)(value) if CORE.using_esp_idf: return cv.declare_id(IDFUARTComponent)(value) + if CORE.is_rp2040: + return cv.declare_id(RP2040UartComponent)(value) raise NotImplementedError diff --git a/esphome/components/uart/uart_component_rp2040.cpp b/esphome/components/uart/uart_component_rp2040.cpp new file mode 100644 index 0000000000..e2c47080ac --- /dev/null +++ b/esphome/components/uart/uart_component_rp2040.cpp @@ -0,0 +1,184 @@ +#ifdef USE_RP2040 +#include "uart_component_rp2040.h" +#include "esphome/core/application.h" +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#include + +#ifdef USE_LOGGER +#include "esphome/components/logger/logger.h" +#endif + +namespace esphome { +namespace uart { + +static const char *const TAG = "uart.arduino_rp2040"; + +uint16_t RP2040UartComponent::get_config() { + uint16_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->data_bits_) { + case 5: + config |= SERIAL_DATA_5; + break; + case 6: + config |= SERIAL_DATA_6; + break; + case 7: + config |= SERIAL_DATA_7; + break; + case 8: + config |= SERIAL_DATA_8; + break; + } + + if (this->stop_bits_ == 1) { + config |= SERIAL_STOP_BIT_1; + } else { + config |= SERIAL_STOP_BIT_2; + } + + return config; +} + +void RP2040UartComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up UART bus..."); + + uint16_t config = get_config(); + + constexpr uint32_t valid_tx_uart_0 = __bitset({0, 12, 16, 28}); + constexpr uint32_t valid_tx_uart_1 = __bitset({4, 8, 20, 24}); + + constexpr uint32_t valid_rx_uart_0 = __bitset({1, 13, 17, 29}); + constexpr uint32_t valid_rx_uart_1 = __bitset({5, 9, 21, 25}); + + int8_t tx_hw = -1; + int8_t rx_hw = -1; + + if (this->tx_pin_ != nullptr) { + if (this->tx_pin_->is_inverted()) { + ESP_LOGD(TAG, "An inverted TX pin %u can only be used with SerialPIO", this->tx_pin_->get_pin()); + } else { + if (((1 << this->tx_pin_->get_pin()) & valid_tx_uart_0) != 0) { + tx_hw = 0; + } else if (((1 << this->tx_pin_->get_pin()) & valid_tx_uart_1) != 0) { + tx_hw = 1; + } else { + ESP_LOGD(TAG, "TX pin %u can only be used with SerialPIO", this->tx_pin_->get_pin()); + } + } + } + + if (this->rx_pin_ != nullptr) { + if (this->rx_pin_->is_inverted()) { + ESP_LOGD(TAG, "An inverted RX pin %u can only be used with SerialPIO", this->rx_pin_->get_pin()); + } else { + if (((1 << this->rx_pin_->get_pin()) & valid_rx_uart_0) != 0) { + rx_hw = 0; + } else if (((1 << this->rx_pin_->get_pin()) & valid_rx_uart_1) != 0) { + rx_hw = 1; + } else { + ESP_LOGD(TAG, "RX pin %u can only be used with SerialPIO", this->rx_pin_->get_pin()); + } + } + } + +#ifdef USE_LOGGER + if (tx_hw == rx_hw && logger::global_logger->get_uart() == tx_hw) { + ESP_LOGD(TAG, "Using SerialPIO as UART%d is taken by the logger", tx_hw); + tx_hw = -1; + rx_hw = -1; + } +#endif + + if (tx_hw == -1 || rx_hw == -1 || tx_hw != rx_hw) { + ESP_LOGV(TAG, "Using SerialPIO"); + pin_size_t tx = this->tx_pin_ == nullptr ? SerialPIO::NOPIN : this->tx_pin_->get_pin(); + pin_size_t rx = this->rx_pin_ == nullptr ? SerialPIO::NOPIN : this->rx_pin_->get_pin(); + auto *serial = new SerialPIO(tx, rx, this->rx_buffer_size_); // NOLINT(cppcoreguidelines-owning-memory) + serial->begin(this->baud_rate_, config); + if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) + gpio_set_outover(tx, GPIO_OVERRIDE_INVERT); + if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted()) + gpio_set_inover(rx, GPIO_OVERRIDE_INVERT); + this->serial_ = serial; + } else { + ESP_LOGV(TAG, "Using Hardware Serial"); + SerialUART *serial; + if (tx_hw == 0) { + serial = &Serial1; + } else { + serial = &Serial2; + } + serial->setTX(this->tx_pin_->get_pin()); + serial->setRX(this->rx_pin_->get_pin()); + serial->setFIFOSize(this->rx_buffer_size_); + serial->begin(this->baud_rate_, config); + this->serial_ = serial; + this->hw_serial_ = true; + } +} + +void RP2040UartComponent::dump_config() { + ESP_LOGCONFIG(TAG, "UART Bus:"); + LOG_PIN(" TX Pin: ", tx_pin_); + LOG_PIN(" RX Pin: ", rx_pin_); + if (this->rx_pin_ != nullptr) { + ESP_LOGCONFIG(TAG, " RX Buffer Size: %u", this->rx_buffer_size_); + } + ESP_LOGCONFIG(TAG, " Baud Rate: %u baud", this->baud_rate_); + ESP_LOGCONFIG(TAG, " Data Bits: %u", this->data_bits_); + ESP_LOGCONFIG(TAG, " Parity: %s", LOG_STR_ARG(parity_to_str(this->parity_))); + ESP_LOGCONFIG(TAG, " Stop bits: %u", this->stop_bits_); + if (this->hw_serial_) { + ESP_LOGCONFIG(TAG, " Using hardware serial"); + } else { + ESP_LOGCONFIG(TAG, " Using SerialPIO"); + } +} + +void RP2040UartComponent::write_array(const uint8_t *data, size_t len) { + this->serial_->write(data, len); +#ifdef USE_UART_DEBUGGER + for (size_t i = 0; i < len; i++) { + this->debug_callback_.call(UART_DIRECTION_TX, data[i]); + } +#endif +} +bool RP2040UartComponent::peek_byte(uint8_t *data) { + if (!this->check_read_timeout_()) + return false; + *data = this->serial_->peek(); + return true; +} +bool RP2040UartComponent::read_array(uint8_t *data, size_t len) { + if (!this->check_read_timeout_(len)) + return false; + this->serial_->readBytes(data, len); +#ifdef USE_UART_DEBUGGER + for (size_t i = 0; i < len; i++) { + this->debug_callback_.call(UART_DIRECTION_RX, data[i]); + } +#endif + return true; +} +int RP2040UartComponent::available() { return this->serial_->available(); } +void RP2040UartComponent::flush() { + ESP_LOGVV(TAG, " Flushing..."); + this->serial_->flush(); +} + +} // namespace uart +} // namespace esphome + +#endif // USE_RP2040 diff --git a/esphome/components/uart/uart_component_rp2040.h b/esphome/components/uart/uart_component_rp2040.h new file mode 100644 index 0000000000..163315dee7 --- /dev/null +++ b/esphome/components/uart/uart_component_rp2040.h @@ -0,0 +1,43 @@ +#pragma once + +#ifdef USE_RP2040 + +#include +#include + +#include +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "uart_component.h" + +namespace esphome { +namespace uart { + +class RP2040UartComponent : public UARTComponent, public Component { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::BUS; } + + void write_array(const uint8_t *data, size_t len) override; + + bool peek_byte(uint8_t *data) override; + bool read_array(uint8_t *data, size_t len) override; + + int available() override; + void flush() override; + + uint16_t get_config(); + + protected: + void check_logger_conflict() override {} + bool hw_serial_{false}; + + HardwareSerial *serial_{nullptr}; +}; + +} // namespace uart +} // namespace esphome + +#endif // USE_RP2040