diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index 0d58009f31eb2..65b09430d9a5b 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -63,6 +63,12 @@ config UART_USE_RUNTIME_CONFIGURE Say y if unsure. Disable this to reduce footprint for applications that do not require runtime UART configuration. +config SERIAL_SUPPORT_RS485 + bool + help + This is an option to be enabled by individual serial driver + to signal that the driver and hardware supports RS485. + config UART_ASYNC_API bool "Asynchronous UART API" depends on SERIAL_SUPPORT_ASYNC diff --git a/drivers/serial/Kconfig.stm32 b/drivers/serial/Kconfig.stm32 index 83ca475c1180e..51baa5abbafb0 100644 --- a/drivers/serial/Kconfig.stm32 +++ b/drivers/serial/Kconfig.stm32 @@ -8,6 +8,7 @@ config UART_STM32 default y depends on DT_HAS_ST_STM32_UART_ENABLED select SERIAL_HAS_DRIVER + select SERIAL_SUPPORT_RS485 select SERIAL_SUPPORT_INTERRUPT # the ASYNC implementation requires a DMA controller select SERIAL_SUPPORT_ASYNC \ diff --git a/drivers/serial/uart_stm32.c b/drivers/serial/uart_stm32.c index f4bca7130b229..05f9e1729a55f 100644 --- a/drivers/serial/uart_stm32.c +++ b/drivers/serial/uart_stm32.c @@ -2,7 +2,6 @@ * Copyright (c) 2016 Open-RnD Sp. z o.o. * Copyright (c) 2016 Linaro Limited. * Copyright (c) 2024 STMicroelectronics - * * SPDX-License-Identifier: Apache-2.0 */ @@ -66,6 +65,21 @@ LOG_MODULE_REGISTER(uart_stm32, CONFIG_UART_LOG_LEVEL); #define HAS_DRIVER_ENABLE 0 #endif +/* On platforms without HW DE support, SW DE is the only option. + * On platforms with HW DE support, SW DE is still available as fallback. + * To minimize SW footprint, only compile SW DE on platforms that need it. + */ +#if !HAS_DRIVER_ENABLE +#define UART_STM32_NEEDS_SW_DE 1 +#else +#define UART_STM32_NEEDS_SW_DE 0 +#endif + + + +/* Helper macro to check if SW DE might be needed */ +#define UART_STM32_SW_DE_NEEDED(config) ((config)->de_enable && (config)->de_pin.port != NULL) + /* Helper for checking if we can use hardware receive timeouts * instead of the work queue on a given UART */ @@ -112,6 +126,23 @@ uint32_t lpuartdiv_calc(const uint64_t clock_rate, const uint32_t baud_rate) #define STM32_ASYNC_STATUS_TIMEOUT (DMA_STATUS_BLOCK + 1) #endif +/* The RS485 DE management is now handled based on de_enable and de_pin fields: + * - If de_enable is false, no DE control is performed. + * - If de_enable is true and de_pin is not specified (port is NULL), the hardware + * method is assumed (HW control via driver enable functions). + * - If de_enable is true and de_pin is defined, a software (SW) method is used. + */ + +#if UART_STM32_NEEDS_SW_DE || HAS_DRIVER_ENABLE +static void rs485_de_time_expire_callback(struct k_timer *timer) +{ + const struct uart_stm32_config *config = k_timer_user_data_get(timer); + + /* SW method: deassert DE signal */ + gpio_pin_set(config->de_pin.port, config->de_pin.pin, !config->de_invert); +} +#endif + #ifdef CONFIG_PM static void uart_stm32_pm_policy_state_lock_get_unconditional(void) { @@ -622,14 +653,13 @@ static int uart_stm32_configure(const struct device *dev, } /* Driver supports only RTS/CTS and RS485 flow control */ - if (!(cfg->flow_ctrl == UART_CFG_FLOW_CTRL_NONE - || (cfg->flow_ctrl == UART_CFG_FLOW_CTRL_RTS_CTS && - IS_UART_HWFLOW_INSTANCE(usart)) + if (!(cfg->flow_ctrl == UART_CFG_FLOW_CTRL_NONE || + (cfg->flow_ctrl == UART_CFG_FLOW_CTRL_RTS_CTS && IS_UART_HWFLOW_INSTANCE(usart)) #if HAS_DRIVER_ENABLE - || (cfg->flow_ctrl == UART_CFG_FLOW_CTRL_RS485 && - IS_UART_DRIVER_ENABLE_INSTANCE(usart)) + || + (cfg->flow_ctrl == UART_CFG_FLOW_CTRL_RS485 && IS_UART_DRIVER_ENABLE_INSTANCE(usart)) #endif - )) { + )) { return -ENOTSUP; } @@ -1006,6 +1036,14 @@ static void uart_stm32_irq_tx_enable(const struct device *dev) unsigned int key; #endif + /* RS485 DE management: SW method when de_pin is specified */ +#if UART_STM32_NEEDS_SW_DE || HAS_DRIVER_ENABLE + if (config->de_enable && config->de_pin.port != NULL) { + /* SW method: set DE active */ + gpio_pin_set(config->de_pin.port, config->de_pin.pin, config->de_invert); + } +#endif + #ifdef CONFIG_PM key = irq_lock(); data->tx_poll_stream_on = false; @@ -1022,8 +1060,24 @@ static void uart_stm32_irq_tx_enable(const struct device *dev) static void uart_stm32_irq_tx_disable(const struct device *dev) { const struct uart_stm32_config *config = dev->config; -#ifdef CONFIG_PM struct uart_stm32_data *data = dev->data; + + /* RS485 DE management: SW deassertion when de_pin is specified */ +#if UART_STM32_NEEDS_SW_DE || HAS_DRIVER_ENABLE + if (config->de_enable && config->de_pin.port != NULL) { + /* SW method: handle DE deassertion */ + if (config->de_deassert_time_us) { + k_timer_start(&data->rs485_timer, + K_USEC(config->de_deassert_time_us), + K_NO_WAIT); + } else { + gpio_pin_set(config->de_pin.port, config->de_pin.pin, + !config->de_invert); + } + } +#endif + +#ifdef CONFIG_PM unsigned int key; key = irq_lock(); @@ -1034,9 +1088,6 @@ static void uart_stm32_irq_tx_disable(const struct device *dev) #ifdef CONFIG_PM data->tx_int_stream_on = false; uart_stm32_pm_policy_state_lock_put(dev); -#endif - -#ifdef CONFIG_PM irq_unlock(key); #endif } @@ -2115,7 +2166,7 @@ static int uart_stm32_async_rx_buf_rsp_u16(const struct device *dev, uint16_t *b #endif /* CONFIG_UART_ASYNC_API */ -static DEVICE_API(uart, uart_stm32_driver_api) = { +static const struct uart_driver_api uart_stm32_driver_api = { .poll_in = uart_stm32_poll_in, .poll_out = uart_stm32_poll_out, #ifdef CONFIG_UART_WIDE_DATA @@ -2250,7 +2301,12 @@ static int uart_stm32_registers_configure(const struct device *dev) return -EINVAL; } - uart_stm32_set_driver_enable(dev, true); + /* If hardware DE control is desired, de_pin is not set. + * Otherwise, SW method will be used. + */ + if (config->de_pin.port == NULL) { + uart_stm32_set_driver_enable(dev, true); + } LL_USART_SetDEAssertionTime(usart, config->de_assert_time); LL_USART_SetDEDeassertionTime(usart, config->de_deassert_time); @@ -2323,6 +2379,7 @@ static int uart_stm32_registers_configure(const struct device *dev) static int uart_stm32_init(const struct device *dev) { const struct uart_stm32_config *config = dev->config; + struct uart_stm32_data *data = dev->data; int err; err = uart_stm32_clocks_enable(dev); @@ -2341,9 +2398,18 @@ static int uart_stm32_init(const struct device *dev) return err; } -#if defined(CONFIG_PM) || \ - defined(CONFIG_UART_INTERRUPT_DRIVEN) || \ - defined(CONFIG_UART_ASYNC_API) + /* Init SW DE timer and pin for RS485 mode when needed */ +#if UART_STM32_NEEDS_SW_DE || HAS_DRIVER_ENABLE + if (config->de_enable && config->de_pin.port != NULL) { + /* SW method: configure DE pin and initialize timer */ + gpio_pin_set(config->de_pin.port, config->de_pin.pin, + config->de_invert); + k_timer_init(&data->rs485_timer, rs485_de_time_expire_callback, NULL); + k_timer_user_data_set(&data->rs485_timer, (void *)config); + } +#endif + +#if defined(CONFIG_PM) || defined(CONFIG_UART_INTERRUPT_DRIVEN) || defined(CONFIG_UART_ASYNC_API) config->irq_config_func(dev); #endif /* CONFIG_PM || CONFIG_UART_INTERRUPT_DRIVEN || CONFIG_UART_ASYNC_API */ @@ -2637,11 +2703,12 @@ static const struct uart_stm32_config uart_stm32_cfg_##index = { \ .tx_rx_swap = DT_INST_PROP(index, tx_rx_swap), \ .rx_invert = DT_INST_PROP(index, rx_invert), \ .tx_invert = DT_INST_PROP(index, tx_invert), \ - .de_enable = DT_INST_PROP(index, de_enable), \ - .de_assert_time = DT_INST_PROP(index, de_assert_time), \ - .de_deassert_time = DT_INST_PROP(index, de_deassert_time), \ - .de_invert = DT_INST_PROP(index, de_invert), \ - .fifo_enable = DT_INST_PROP(index, fifo_enable), \ + .de_enable = DT_INST_PROP(index, rs485_enabled), \ + .de_assert_time_us = DT_INST_PROP(index, rs485_assertion_time_de_us), \ + .de_deassert_time_us = DT_INST_PROP(index, rs485_deassertion_time_de_us), \ + .de_invert = DT_INST_PROP(index, rs485_de_active_low), \ + .de_pin = GPIO_DT_SPEC_INST_GET_OR(index, rs485_de_gpios, {0}), \ + .fifo_enable = DT_INST_PROP(index, fifo_enable), \ STM32_UART_IRQ_HANDLER_FUNC(index) \ STM32_UART_PM_WAKEUP(index) \ }; \ diff --git a/drivers/serial/uart_stm32.h b/drivers/serial/uart_stm32.h index d50dbc77c0d98..26b07196b6d15 100644 --- a/drivers/serial/uart_stm32.h +++ b/drivers/serial/uart_stm32.h @@ -15,6 +15,7 @@ #include #include #include +#include #include @@ -48,6 +49,11 @@ struct uart_stm32_config { bool fifo_enable; /* pin muxing */ const struct pinctrl_dev_config *pcfg; + struct gpio_dt_spec de_pin; + /* de signal assertion time in nanoseconds */ + uint32_t de_assert_time_us; + /* de signal deassertion time in nanoseconds */ + uint32_t de_deassert_time_us; #if defined(CONFIG_UART_INTERRUPT_DRIVEN) || defined(CONFIG_UART_ASYNC_API) || \ defined(CONFIG_PM) uart_irq_config_func_t irq_config_func; @@ -89,7 +95,6 @@ struct uart_stm32_data { uart_irq_callback_user_data_t user_cb; void *user_data; #endif - #ifdef CONFIG_UART_ASYNC_API const struct device *uart_dev; uart_callback_t async_cb; @@ -105,6 +110,11 @@ struct uart_stm32_data { bool pm_policy_state_on; bool rx_woken; #endif + /* SW DE timer - present when SW DE support may be needed */ +#if !defined(USART_CR3_DEM) || defined(USART_CR3_DEM) + struct k_timer rs485_timer; +#endif + }; #endif /* ZEPHYR_DRIVERS_SERIAL_UART_STM32_H_ */ diff --git a/dts/bindings/serial/st,stm32-uart-base.yaml b/dts/bindings/serial/st,stm32-uart-base.yaml index 758e054304366..b71a533681e05 100644 --- a/dts/bindings/serial/st,stm32-uart-base.yaml +++ b/dts/bindings/serial/st,stm32-uart-base.yaml @@ -9,6 +9,7 @@ include: property-blocklist: - clock-frequency - name: pinctrl-device.yaml + - name: uart-rs485-controller.yaml - name: reset-device.yaml - name: uart-controller-pin-inversion.yaml @@ -68,34 +69,6 @@ properties: the core from stop mode(s). Valid range: 0 - 31 - de-enable: - type: boolean - description: | - Enable activating an external transeiver through the DE pin which must also be configured - using pinctrl. - - de-assert-time: - type: int - default: 0 - description: | - Defines the time between the activation of the DE signal and the beginning of the start bit. - It is expressed in 16th of a bit time. - Valid range: 0 - 31 - - de-deassert-time: - type: int - default: 0 - description: | - Defines the time between the end of the stop bit and the deactivation of the DE signal. - It is expressed in 16th of a bit time. - Valid range: 0 - 31 - - de-invert: - type: boolean - description: | - Invert the binary logic of the de pin. When enabled, physical logic levels are inverted and - we use 1=Low, 0=High instead of 1=High, 0=Low. - fifo-enable: type: boolean description: | diff --git a/dts/bindings/serial/uart-rs485-controller.yaml b/dts/bindings/serial/uart-rs485-controller.yaml new file mode 100644 index 0000000000000..cba037207023a --- /dev/null +++ b/dts/bindings/serial/uart-rs485-controller.yaml @@ -0,0 +1,36 @@ +# Common fields for uart-rs485 controllers + +include: uart-controller.yaml + +properties: + rs485-enabled: + type: boolean + description: Enable rs485 mode + rs485-de-gpios: + type: phandle-array + description: Output for control RS485 driver + rs485-full-duplex-mode: + type: boolean + description: Enable rs485 in full duplex mode + rs485-de-active-low: + type: boolean + description: Driver enable active low polarity + rs485-re-active-high: + type: boolean + description: Receiver enable active high polarity + rs485-assertion-time-de-us: + type: int + default: 0 + description: Driver enable assertion time in nanoseconds + rs485-deassertion-time-de-us: + type: int + default: 0 + description: Driver enable deassertion time in nanoseconds + rs485-assertion-time-re-us: + type: int + default: 0 + description: Receiver enable assertion time in nanoseconds + rs485-deassertion-time-re-us: + type: int + default: 0 + description: Receiver enable deassertion time in nanoseconds diff --git a/include/zephyr/drivers/uart.h b/include/zephyr/drivers/uart.h index b4edee1310999..1ef4f911931f0 100644 --- a/include/zephyr/drivers/uart.h +++ b/include/zephyr/drivers/uart.h @@ -39,6 +39,7 @@ enum uart_line_ctrl { UART_LINE_CTRL_DTR = BIT(2), /**< Data Terminal Ready (DTR) */ UART_LINE_CTRL_DCD = BIT(3), /**< Data Carrier Detect (DCD) */ UART_LINE_CTRL_DSR = BIT(4), /**< Data Set Ready (DSR) */ + UART_LINE_CTRL_RS485 = BIT(5), /**< Enable/disable rs485 mode */ }; /** diff --git a/samples/drivers/uart/uart_rs485/CMakeLists.txt b/samples/drivers/uart/uart_rs485/CMakeLists.txt new file mode 100644 index 0000000000000..7cf0bad960dd7 --- /dev/null +++ b/samples/drivers/uart/uart_rs485/CMakeLists.txt @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(rs485_api_sample) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/drivers/uart/uart_rs485/README.rst b/samples/drivers/uart/uart_rs485/README.rst new file mode 100644 index 0000000000000..cdfff35574e70 --- /dev/null +++ b/samples/drivers/uart/uart_rs485/README.rst @@ -0,0 +1,77 @@ +.. zephyr:code-sample:: c + :name: UART RS485 Example + :relevant-api: uart_interface + + Send and receive data over RS485 using the UART API. + +Overview +******** + +The UART RS485 Example demonstrates how to send and receive data over a UART interface using RS485. It uses interrupt-driven communication to handle data transmission and reception efficiently. + +The source code shows how to: + +#. Retrieve a UART device from the :ref:`devicetree `. +#. Set up the UART callback function for handling both transmission and reception interrupts. +#. Use a semaphore to coordinate data transmission completion. + +Requirements +************ + +Your board must: + +#. Have a UART peripheral configured to support RS485. +#. Use a proper configuration in the devicetree for the UART device. + +Building and Running +******************** + +Build and flash the UART RS485 Example as follows, changing ``nucleo_f411re`` for your board: + +.. zephyr-app-commands:: + :zephyr-app: samples/drivers/uart/uart_rs485 + :board: nucleo_f411re + :goals: build flash + :compact: + +After flashing, the application will start sending data over the UART in RS485 mode, and messages about transmission and reception will be printed on the console. If a runtime error occurs, the sample exits without further console output. + +Build errors +************ + +You will see a build error if the UART device specified in the code does not match any supported devices in the board devicetree. + +On GCC-based toolchains, the error might look like this: + +.. code-block:: none + + error: 'DEVICE_DT_GET(DT_NODELABEL(usart1))' undeclared here (not in a function) + +Adding board support +******************** + +To add support for your board, add something like this to your devicetree: + +.. code-block:: DTS + + &usart1 { + pinctrl-0 = <&usart1_tx_pb6 &usart1_rx_pb7>; + pinctrl-names = "default"; + current-speed = <115200>; + rs485-enabled; + rs485-de-gpios = <&gpiob 7 GPIO_ACTIVE_HIGH>; + rs485-deassertion-time-de-us = <500>; + status = "okay"; + }; + +The above sets your board's ``usart1`` configuration, specifying the TX and RX pins, enabling RS485 mode, and configuring the DE GPIO pin and de-assertion timing. The RS485-related properties help in properly configuring the RS485 communication, including de-assertion timing for RS485 DE line. + +Tips: + +- See :dtcompatible:`st,stm32-uart` for more information on defining UART peripherals in devicetree. + +- If you're not sure what to do, check the devicetrees for supported boards that use the same SoC as your target. See :ref:`get-devicetree-outputs` for details. + +- See :zephyr_file:`include/zephyr/drivers/uart.h` for the flags you can use in the devicetree. + +- Proper RS485 settings are crucial for reliable communication. Ensure the correct timing values for the DE line in your devicetree. diff --git a/samples/drivers/uart/uart_rs485/boards/nucleo_f411re.overlay b/samples/drivers/uart/uart_rs485/boards/nucleo_f411re.overlay new file mode 100644 index 0000000000000..42beee4282d95 --- /dev/null +++ b/samples/drivers/uart/uart_rs485/boards/nucleo_f411re.overlay @@ -0,0 +1,9 @@ +&usart1 { + pinctrl-0 = <&usart1_tx_pb6 &usart1_rx_pb7>; + pinctrl-names = "default"; + current-speed = <115200>; + rs485-enabled; + rs485-de-gpios = <&gpiob 7 GPIO_ACTIVE_HIGH>; + rs485-deassertion-time-de-us = <500>; + status = "okay"; +}; diff --git a/samples/drivers/uart/uart_rs485/prj.conf b/samples/drivers/uart/uart_rs485/prj.conf new file mode 100644 index 0000000000000..b7a3a5cdfdf8d --- /dev/null +++ b/samples/drivers/uart/uart_rs485/prj.conf @@ -0,0 +1,2 @@ +CONFIG_GPIO=y +CONFIG_UART_INTERRUPT_DRIVEN=y diff --git a/samples/drivers/uart/uart_rs485/sample.yaml b/samples/drivers/uart/uart_rs485/sample.yaml new file mode 100644 index 0000000000000..576c723933fb1 --- /dev/null +++ b/samples/drivers/uart/uart_rs485/sample.yaml @@ -0,0 +1,11 @@ +sample: + name: UART RS48API +tests: + sample.drivers.uart.кrs485: + platform_allow: + - nucleo_f411re + tags: + - serial + - uart + - rs485 + harness: keyboard diff --git a/samples/drivers/uart/uart_rs485/src/main.c b/samples/drivers/uart/uart_rs485/src/main.c new file mode 100644 index 0000000000000..1948a71b80ffd --- /dev/null +++ b/samples/drivers/uart/uart_rs485/src/main.c @@ -0,0 +1,96 @@ +/* + * @brief RS485 API UART sample + * + * Copyright (c) 2024 Grigorovich Sergey + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(uart_interrupt_example, LOG_LEVEL_INF); + +/* 1000 ms = 1 second */ +#define SLEEP_TIME_MS 150 + +/* UART device */ +const struct device *uart1; + +/* Variables for UART and interrupt handling */ +static struct k_sem tx_done_sem; +static const char *tx_data = "Hello, RS485!\r\n"; +static volatile bool tx_busy; + +/* UART callback function */ +void uart_cb(const struct device *dev, void *user_data) +{ + static size_t tx_idx; + size_t len = strlen(tx_data); + + /* If there is space in FIFO and data remains to be sent */ + if (uart_irq_tx_ready(dev) && tx_idx < len) { + int bytes_written = + uart_fifo_fill(dev, (const uint8_t *)&tx_data[tx_idx], len - tx_idx); + + tx_idx += bytes_written; + LOG_DBG("TX Ready: Sent %d bytes, total sent: %d/%d", bytes_written, tx_idx, len); + } + + /* Check if transmission is completely finished */ + if (uart_irq_tx_complete(dev) && tx_idx >= len) { + uart_irq_tx_disable(dev); + tx_idx = 0; + tx_busy = false; + LOG_DBG("TX Complete"); + k_sem_give(&tx_done_sem); + } + + /* Handle received data (if necessary) */ + if (uart_irq_rx_ready(dev)) { + uint8_t rx_data; + + while (uart_fifo_read(dev, &rx_data, 1)) { + LOG_DBG("Received byte: 0x%02X", rx_data); + } + } +} + +int main(void) +{ + uart1 = DEVICE_DT_GET(DT_NODELABEL(usart1)); + + if (!device_is_ready(uart1)) { + LOG_ERR("UART device is not ready"); + return 0; + } + + /* Initialize semaphore */ + k_sem_init(&tx_done_sem, 0, 1); + + /* Set up UART interrupts */ + uart_irq_callback_set(uart1, uart_cb); + uart_irq_rx_enable(uart1); + uart_irq_tx_disable(uart1); + + LOG_INF("UART interrupt-driven example started."); + + while (1) { + /* Data transmission */ + if (!tx_busy) { + tx_busy = true; + uart_irq_tx_enable(uart1); + /* Initiate transmission of the first byte */ + uart_cb(uart1, NULL); + /* Wait for transmission to complete */ + k_sem_take(&tx_done_sem, K_FOREVER); + } + + k_msleep(SLEEP_TIME_MS); + } + + return 0; +}