diff --git a/cores/esp32/HardwareSerial.cpp b/cores/esp32/HardwareSerial.cpp index 76135691411..6d762da21fb 100644 --- a/cores/esp32/HardwareSerial.cpp +++ b/cores/esp32/HardwareSerial.cpp @@ -607,6 +607,24 @@ bool HardwareSerial::setMode(SerialMode mode) { return uartSetMode(_uart, mode); } +// Sets the UART Clock Source based on the compatible SoC options +// This method must be called before starting UART using begin(), otherwise it won't have any effect. +// Clock Source Options are: +// UART_CLK_SRC_DEFAULT :: any SoC - it will set whatever IDF defines as the default UART Clock Source +// UART_CLK_SRC_APB :: ESP32, ESP32-S2, ESP32-C3 and ESP32-S3 +// UART_CLK_SRC_PLL :: ESP32-C2, ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2 and ESP32-P4 +// UART_CLK_SRC_XTAL :: ESP32-C2, ESP32-C3, ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2, ESP32-S3 and ESP32-P4 +// UART_CLK_SRC_RTC :: ESP32-C2, ESP32-C3, ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2, ESP32-S3 and ESP32-P4 +// UART_CLK_SRC_REF_TICK :: ESP32 and ESP32-S2 +// Note: CLK_SRC_PLL Freq depends on the SoC - ESP32-C2 has 40MHz, ESP32-H2 has 48MHz and ESP32-C5, C6, C61 and P4 has 80MHz +// Note: ESP32-C6, C61, ESP32-P4 and ESP32-C5 have LP UART that will use only RTC_FAST or XTAL/2 as Clock Source +bool HardwareSerial::setClockSource(SerialClkSrc clkSrc) { + if (_uart) { + log_e("No Clock Source change was done. This function must be called before beginning UART%d.", _uart_nr); + return false; + } + return uartSetClockSource(_uart_nr, (uart_sclk_t)clkSrc); +} // minimum total RX Buffer size is the UART FIFO space (128 bytes for most SoC) + 1. IDF imposition. // LP UART has FIFO of 16 bytes size_t HardwareSerial::setRxBufferSize(size_t new_size) { diff --git a/cores/esp32/HardwareSerial.h b/cores/esp32/HardwareSerial.h index b1f6df17724..e974f112701 100644 --- a/cores/esp32/HardwareSerial.h +++ b/cores/esp32/HardwareSerial.h @@ -96,6 +96,29 @@ typedef enum { UART_PARITY_ERROR } hardwareSerial_error_t; +typedef enum { + UART_CLK_SRC_DEFAULT = UART_SCLK_DEFAULT, +#if SOC_UART_SUPPORT_APB_CLK + UART_CLK_SRC_APB = UART_SCLK_APB, +#endif +#if SOC_UART_SUPPORT_PLL_F40M_CLK + UART_CLK_SRC_PLL = UART_SCLK_PLL_F40M, +#elif SOC_UART_SUPPORT_PLL_F80M_CLK + UART_CLK_SRC_PLL = UART_SCLK_PLL_F80M, +#elif CONFIG_IDF_TARGET_ESP32H2 + UART_CLK_SRC_PLL = UART_SCLK_PLL_F48M, +#endif +#if SOC_UART_SUPPORT_XTAL_CLK + UART_CLK_SRC_XTAL = UART_SCLK_XTAL, +#endif +#if SOC_UART_SUPPORT_RTC_CLK + UART_CLK_SRC_RTC = UART_SCLK_RTC, +#endif +#if SOC_UART_SUPPORT_REF_TICK + UART_CLK_SRC_REF_TICK = UART_SCLK_REF_TICK, +#endif +} SerialClkSrc; + #ifndef ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE #ifndef CONFIG_ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE #define ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE 2048 @@ -344,6 +367,17 @@ class HardwareSerial : public Stream { // UART_MODE_RS485_COLLISION_DETECT = 0x03 mode: RS485 collision detection UART mode (used for test purposes) // UART_MODE_RS485_APP_CTRL = 0x04 mode: application control RS485 UART mode (used for test purposes) bool setMode(SerialMode mode); + // Used to set the UART clock source mode. It must be set before calling begin(), otherwise it won't have any effect. + // Not all clock source are available to every SoC. The compatible option are listed here: + // UART_CLK_SRC_DEFAULT :: any SoC - it will set whatever IDF defines as the default UART Clock Source + // UART_CLK_SRC_APB :: ESP32, ESP32-S2, ESP32-C3 and ESP32-S3 + // UART_CLK_SRC_PLL :: ESP32-C2, ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2 and ESP32-P4 + // UART_CLK_SRC_XTAL :: ESP32-C2, ESP32-C3, ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2, ESP32-S3 and ESP32-P4 + // UART_CLK_SRC_RTC :: ESP32-C2, ESP32-C3, ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2, ESP32-S3 and ESP32-P4 + // UART_CLK_SRC_REF_TICK :: ESP32 and ESP32-S2 + // Note: CLK_SRC_PLL Freq depends on the SoC - ESP32-C2 has 40MHz, ESP32-H2 has 48MHz and ESP32-C5, C6, C61 and P4 has 80MHz + // Note: ESP32-C6, C61, ESP32-P4 and ESP32-C5 have LP UART that will use only RTC_FAST or XTAL/2 as Clock Source + bool setClockSource(SerialClkSrc clkSrc); size_t setRxBufferSize(size_t new_size); size_t setTxBufferSize(size_t new_size); diff --git a/cores/esp32/esp32-hal-uart.c b/cores/esp32/esp32-hal-uart.c index 75e2da013ea..d2e98a2341a 100644 --- a/cores/esp32/esp32-hal-uart.c +++ b/cores/esp32/esp32-hal-uart.c @@ -58,6 +58,7 @@ struct uart_struct_t { uint16_t _rx_buffer_size, _tx_buffer_size; // UART RX and TX buffer sizes bool _inverted; // UART inverted signal uint8_t _rxfifo_full_thrhd; // UART RX FIFO full threshold + int8_t _uart_clock_source; // UART Clock Source used when it is started using uartBegin() }; #if CONFIG_DISABLE_HAL_LOCKS @@ -66,21 +67,21 @@ struct uart_struct_t { #define UART_MUTEX_UNLOCK() static uart_t _uart_bus_array[] = { - {0, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0}, + {0, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0, -1}, #if SOC_UART_NUM > 1 - {1, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0}, + {1, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0, -1}, #endif #if SOC_UART_NUM > 2 - {2, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0}, + {2, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0, -1}, #endif #if SOC_UART_NUM > 3 - {3, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0}, + {3, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0, -1}, #endif #if SOC_UART_NUM > 4 - {4, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0}, + {4, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0, -1}, #endif #if SOC_UART_NUM > 5 - {5, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0}, + {5, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0, -1}, #endif }; @@ -95,21 +96,21 @@ static uart_t _uart_bus_array[] = { xSemaphoreGive(uart->lock) static uart_t _uart_bus_array[] = { - {NULL, 0, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0}, + {NULL, 0, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0, -1}, #if SOC_UART_NUM > 1 - {NULL, 1, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0}, + {NULL, 1, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0, -1}, #endif #if SOC_UART_NUM > 2 - {NULL, 2, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0}, + {NULL, 2, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0, -1}, #endif #if SOC_UART_NUM > 3 - {NULL, 3, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0}, + {NULL, 3, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0, -1}, #endif #if SOC_UART_NUM > 4 - {NULL, 4, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0}, + {NULL, 4, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0, -1}, #endif #if SOC_UART_NUM > 5 - {NULL, 5, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0}, + {NULL, 5, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0, -1}, #endif }; @@ -664,30 +665,40 @@ uart_t *uartBegin( rxfifo_full_thrhd = uart_config.rx_flow_ctrl_thresh; // makes sure that it will be set correctly in the struct uart_config.baud_rate = baudrate; #if SOC_UART_LP_NUM >= 1 - if (uart_nr >= SOC_UART_HP_NUM) { // it is a LP UART NUM - uart_config.lp_source_clk = LP_UART_SCLK_DEFAULT; // use default LP clock - log_v("Setting UART%d to use LP clock", uart_nr); + if (uart_nr >= SOC_UART_HP_NUM) { // it is a LP UART NUM + if (uart->_uart_clock_source > 0) { + uart_config.lp_source_clk = (soc_periph_lp_uart_clk_src_t)uart->_uart_clock_source; // use user defined LP UART clock + log_v("Setting UART%d to user defined LP clock source (%d) ", uart_nr, uart->_uart_clock_source); + } else { + uart_config.lp_source_clk = LP_UART_SCLK_DEFAULT; // use default LP clock + log_v("Setting UART%d to Default LP clock source", uart_nr); + } } else -#endif +#endif // SOC_UART_LP_NUM >= 1 { - // there is an issue when returning from light sleep with the C6 and H2: the uart baud rate is not restored - // therefore, uart clock source will set to XTAL for all SoC that support it. This fix solves the C6|H2 issue. + if (uart->_uart_clock_source >= 0) { + uart_config.source_clk = (soc_module_clk_t)uart->_uart_clock_source; // use user defined HP UART clock + log_v("Setting UART%d to user defined HP clock source (%d) ", uart_nr, uart->_uart_clock_source); + } else { + // there is an issue when returning from light sleep with the C6 and H2: the uart baud rate is not restored + // therefore, uart clock source will set to XTAL for all SoC that support it. This fix solves the C6|H2 issue. #if SOC_UART_SUPPORT_XTAL_CLK - uart_config.source_clk = UART_SCLK_XTAL; // valid for C2, S3, C3, C6, H2 and P4 - log_v("Setting UART%d to use XTAL clock", uart_nr); + uart_config.source_clk = UART_SCLK_XTAL; // valid for C2, S3, C3, C6, H2 and P4 + log_v("Setting UART%d to use XTAL clock", uart_nr); #elif SOC_UART_SUPPORT_REF_TICK - if (baudrate <= REF_TICK_BAUDRATE_LIMIT) { - uart_config.source_clk = UART_SCLK_REF_TICK; // valid for ESP32, S2 - MAX supported baud rate is 250 Kbps - log_v("Setting UART%d to use REF_TICK clock", uart_nr); - } else { - uart_config.source_clk = UART_SCLK_APB; // baudrate may change with the APB Frequency! - log_v("Setting UART%d to use APB clock", uart_nr); - } + if (baudrate <= REF_TICK_BAUDRATE_LIMIT) { + uart_config.source_clk = UART_SCLK_REF_TICK; // valid for ESP32, S2 - MAX supported baud rate is 250 Kbps + log_v("Setting UART%d to use REF_TICK clock", uart_nr); + } else { + uart_config.source_clk = UART_SCLK_APB; // baudrate may change with the APB Frequency! + log_v("Setting UART%d to use APB clock", uart_nr); + } #else - // Default CLK Source: CLK_APB for ESP32|S2|S3|C3 -- CLK_PLL_F40M for C2 -- CLK_PLL_F48M for H2 -- CLK_PLL_F80M for C6 - uart_config.source_clk = UART_SCLK_DEFAULT; // baudrate may change with the APB Frequency! - log_v("Setting UART%d to use DEFAULT clock", uart_nr); -#endif + // Default CLK Source: CLK_APB for ESP32|S2|S3|C3 -- CLK_PLL_F40M for C2 -- CLK_PLL_F48M for H2 -- CLK_PLL_F80M for C6|P4 + uart_config.source_clk = UART_SCLK_DEFAULT; // baudrate may change with the APB Frequency! + log_v("Setting UART%d to use DEFAULT clock", uart_nr); +#endif // SOC_UART_SUPPORT_XTAL_CLK + } } UART_MUTEX_LOCK(); @@ -716,6 +727,14 @@ uart_t *uartBegin( uart->_tx_buffer_size = tx_buffer_size; uart->has_peek = false; uart->peek_byte = 0; +#if SOC_UART_LP_NUM >= 1 + if (uart_nr >= SOC_UART_HP_NUM) { + uart->_uart_clock_source = uart_config.lp_source_clk; + } else +#endif + { + uart->_uart_clock_source = uart_config.source_clk; + } } UART_MUTEX_UNLOCK(); @@ -975,22 +994,52 @@ bool uartSetBaudRate(uart_t *uart, uint32_t baud_rate) { return false; } bool retCode = true; - UART_MUTEX_LOCK(); -#if SOC_UART_SUPPORT_XTAL_CLK // ESP32-S3, ESP32-C3, ESP32-C5, ESP32-C6, ESP32-H2 and ESP32-P4 - soc_module_clk_t newClkSrc = UART_SCLK_XTAL; + soc_module_clk_t newClkSrc = UART_SCLK_DEFAULT; + int8_t previousClkSrc = uart->_uart_clock_source; #if SOC_UART_LP_NUM >= 1 if (uart->num >= SOC_UART_HP_NUM) { // it is a LP UART NUM - newClkSrc = LP_UART_SCLK_DEFAULT; // use default LP clock + if (uart->_uart_clock_source > 0) { + newClkSrc = (soc_periph_lp_uart_clk_src_t)uart->_uart_clock_source; // use user defined LP UART clock + log_v("Setting UART%d to user defined LP clock source (%d) ", uart->num, newClkSrc); + } else { + newClkSrc = LP_UART_SCLK_DEFAULT; // use default LP clock + log_v("Setting UART%d to Default LP clock source", uart->num); + } + } else +#endif // SOC_UART_LP_NUM >= 1 + { + if (uart->_uart_clock_source >= 0) { + newClkSrc = (soc_module_clk_t)uart->_uart_clock_source; // use user defined HP UART clock + log_v("Setting UART%d to use HP clock source (%d) ", uart->num, newClkSrc); + } else { + // there is an issue when returning from light sleep with the C6 and H2: the uart baud rate is not restored + // therefore, uart clock source will set to XTAL for all SoC that support it. This fix solves the C6|H2 issue. +#if SOC_UART_SUPPORT_XTAL_CLK + newClkSrc = UART_SCLK_XTAL; // valid for C2, S3, C3, C6, H2 and P4 + log_v("Setting UART%d to use XTAL clock", uart->num); +#elif SOC_UART_SUPPORT_REF_TICK + if (baud_rate <= REF_TICK_BAUDRATE_LIMIT) { + newClkSrc = UART_SCLK_REF_TICK; // valid for ESP32, S2 - MAX supported baud rate is 250 Kbps + log_v("Setting UART%d to use REF_TICK clock", uart->num); + } else { + newClkSrc = UART_SCLK_APB; // baudrate may change with the APB Frequency! + log_v("Setting UART%d to use APB clock", uart->num); + } +#else + // Default CLK Source: CLK_APB for ESP32|S2|S3|C3 -- CLK_PLL_F40M for C2 -- CLK_PLL_F48M for H2 -- CLK_PLL_F80M for C6|P4 + // using newClkSrc = UART_SCLK_DEFAULT as defined in the variable declaration + log_v("Setting UART%d to use DEFAULT clock", uart->num); +#endif // SOC_UART_SUPPORT_XTAL_CLK + } } -#endif - // ESP32-P4 demands an atomic operation for setting the clock source - HP_UART_SRC_CLK_ATOMIC() { - uart_ll_set_sclk(UART_LL_GET_HW(uart->num), newClkSrc); + UART_MUTEX_LOCK(); + // if necessary, set the correct UART Clock Source before changing the baudrate + if (previousClkSrc < 0 || previousClkSrc != newClkSrc) { + HP_UART_SRC_CLK_ATOMIC() { + uart_ll_set_sclk(UART_LL_GET_HW(uart->num), newClkSrc); + } + uart->_uart_clock_source = newClkSrc; } -#else // ESP32, ESP32-S2 - soc_module_clk_t newClkSrc = baud_rate <= REF_TICK_BAUDRATE_LIMIT ? SOC_MOD_CLK_REF_TICK : SOC_MOD_CLK_APB; - uart_ll_set_sclk(UART_LL_GET_HW(uart->num), newClkSrc); -#endif if (uart_set_baudrate(uart->num, baud_rate) == ESP_OK) { log_v("Setting UART%d baud rate to %ld.", uart->num, baud_rate); uart->_baudrate = baud_rate; @@ -1084,6 +1133,31 @@ bool uartSetMode(uart_t *uart, uart_mode_t mode) { return retCode; } +// this function will set the uart clock source +// it must be called before uartBegin(), otherwise it won't change any thing. +bool uartSetClockSource(uint8_t uartNum, uart_sclk_t clkSrc) { + if (uartNum >= SOC_UART_NUM) { + log_e("UART%d is invalid. This device has %d UARTs, from 0 to %d.", uartNum, SOC_UART_NUM, SOC_UART_NUM - 1); + return false; + } + uart_t *uart = &_uart_bus_array[uartNum]; +#if SOC_UART_LP_NUM >= 1 + if (uart->num >= SOC_UART_HP_NUM) { + switch (clkSrc) { + case UART_SCLK_XTAL: uart->_uart_clock_source = LP_UART_SCLK_XTAL_D2; break; + case UART_SCLK_RTC: uart->_uart_clock_source = LP_UART_SCLK_LP_FAST; break; + case UART_SCLK_DEFAULT: + default: uart->_uart_clock_source = LP_UART_SCLK_DEFAULT; + } + } else +#endif + { + uart->_uart_clock_source = clkSrc; + } + //log_i("UART%d set clock source to %d", uart->num, uart->_uart_clock_source); + return true; +} + void uartSetDebug(uart_t *uart) { // LP UART is not supported for debug if (uart == NULL || uart->num >= SOC_UART_HP_NUM) { diff --git a/cores/esp32/esp32-hal-uart.h b/cores/esp32/esp32-hal-uart.h index 4d686fdd23d..41b005aa151 100644 --- a/cores/esp32/esp32-hal-uart.h +++ b/cores/esp32/esp32-hal-uart.h @@ -97,6 +97,19 @@ bool uartSetHwFlowCtrlMode(uart_t *uart, uart_hw_flowcontrol_t mode, uint8_t thr // UART_MODE_RS485_APP_CTRL = 0x04 mode: application control RS485 UART mode (used for test purposes) bool uartSetMode(uart_t *uart, uart_mode_t mode); +// Used to set the UART clock source mode. It must be set before calling uartBegin(), otherwise it won't have any effect. +// Not all clock source are available to every SoC. The compatible option are listed here: +// UART_SCLK_DEFAULT :: any SoC - it will set whatever IDF defines as the default UART Clock Source +// UART_SCLK_APB :: ESP32, ESP32-S2, ESP32-C3 and ESP32-S3 +// UART_SCLK_PLL_F80M :: ESP32-C5, ESP32-C6, ESP32-C61 and ESP32-P4 +// UART_SCLK_PLL_F40M :: ESP32-C2 +// UART_SCLK_PLL_F48M :: ESP32-H2 +// UART_SCLK_XTAL :: ESP32-C2, ESP32-C3, ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2, ESP32-S3 and ESP32-P4 +// UART_SCLK_RTC :: ESP32-C2, ESP32-C3, ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2, ESP32-S3 and ESP32-P4 +// UART_SCLK_REF_TICK :: ESP32 and ESP32-S2 +// Note: ESP32-C6, C61, ESP32-P4 and ESP32-C5 have LP UART that will use only LP_UART_SCLK_LP_FAST (RTC_FAST) or LP_UART_SCLK_XTAL_D2 (XTAL/2) as Clock Source +bool uartSetClockSource(uint8_t uartNum, uart_sclk_t clkSrc); + void uartStartDetectBaudrate(uart_t *uart); unsigned long uartDetectBaudrate(uart_t *uart);