diff --git a/drivers/serial/uart_stm32.c b/drivers/serial/uart_stm32.c index d0c511b3b4aba..858ceecbb3820 100644 --- a/drivers/serial/uart_stm32.c +++ b/drivers/serial/uart_stm32.c @@ -99,6 +99,10 @@ uint32_t lpuartdiv_calc(const uint64_t clock_rate, const uint32_t baud_rate) #endif /* USART_PRESC_PRESCALER */ #endif /* HAS_LPUART */ +#ifdef CONFIG_UART_ASYNC_API +#define STM32_ASYNC_STATUS_TIMEOUT (DMA_STATUS_BLOCK + 1) +#endif + #ifdef CONFIG_PM static void uart_stm32_pm_policy_state_lock_get(const struct device *dev) { @@ -1117,11 +1121,16 @@ static inline void async_evt_rx_rdy(struct uart_stm32_data *data) .data.rx.offset = data->dma_rx.offset }; - /* update the current pos for new data */ - data->dma_rx.offset = data->dma_rx.counter; + /* When cyclic DMA is used, buffer positions are not updated - call callback every time*/ + if (data->dma_rx.dma_cfg.cyclic == 0) { + /* update the current pos for new data */ + data->dma_rx.offset = data->dma_rx.counter; - /* send event only for new data */ - if (event.data.rx.len > 0) { + /* send event only for new data */ + if (event.data.rx.len > 0) { + async_user_callback(data, &event); + } + } else { async_user_callback(data, &event); } } @@ -1204,20 +1213,45 @@ static inline void async_timer_start(struct k_work_delayable *work, } } -static void uart_stm32_dma_rx_flush(const struct device *dev) +static void uart_stm32_dma_rx_flush(const struct device *dev, int status) { struct dma_status stat; struct uart_stm32_data *data = dev->data; - if (dma_get_status(data->dma_rx.dma_dev, - data->dma_rx.dma_channel, &stat) == 0) { - size_t rx_rcv_len = data->dma_rx.buffer_length - - stat.pending_length; - if (rx_rcv_len > data->dma_rx.offset) { - data->dma_rx.counter = rx_rcv_len; + size_t rx_rcv_len = 0; - async_evt_rx_rdy(data); + switch (status) { + case DMA_STATUS_COMPLETE: + /* fully complete */ + data->dma_rx.counter = data->dma_rx.buffer_length; + break; + case DMA_STATUS_BLOCK: + /* half complete */ + data->dma_rx.counter = data->dma_rx.buffer_length / 2; + + break; + default: /* likely STM32_ASYNC_STATUS_TIMEOUT */ + if (dma_get_status(data->dma_rx.dma_dev, data->dma_rx.dma_channel, &stat) == 0) { + rx_rcv_len = data->dma_rx.buffer_length - stat.pending_length; + data->dma_rx.counter = rx_rcv_len; } + break; + } + + async_evt_rx_rdy(data); + + switch (status) { /* update offset*/ + case DMA_STATUS_COMPLETE: + /* fully complete */ + data->dma_rx.offset = 0; + break; + case DMA_STATUS_BLOCK: + /* half complete */ + data->dma_rx.offset = data->dma_rx.buffer_length / 2; + break; + default: /* likely STM32_ASYNC_STATUS_TIMEOUT */ + data->dma_rx.offset += rx_rcv_len - data->dma_rx.offset; + break; } } @@ -1269,7 +1303,7 @@ static void uart_stm32_isr(const struct device *dev) LOG_DBG("idle interrupt occurred"); if (data->dma_rx.timeout == 0) { - uart_stm32_dma_rx_flush(dev); + uart_stm32_dma_rx_flush(dev, STM32_ASYNC_STATUS_TIMEOUT); } else { /* Start the RX timer not null */ async_timer_start(&data->dma_rx.timeout_work, @@ -1417,7 +1451,7 @@ static int uart_stm32_async_rx_disable(const struct device *dev) LL_USART_DisableIT_IDLE(usart); - uart_stm32_dma_rx_flush(dev); + uart_stm32_dma_rx_flush(dev, STM32_ASYNC_STATUS_TIMEOUT); async_evt_rx_buf_release(data); @@ -1517,27 +1551,32 @@ void uart_stm32_dma_rx_cb(const struct device *dma_dev, void *user_data, (void)k_work_cancel_delayable(&data->dma_rx.timeout_work); - /* true since this functions occurs when buffer if full */ - data->dma_rx.counter = data->dma_rx.buffer_length; + /* If we are in NORMAL MODE */ + if (data->dma_rx.dma_cfg.cyclic == 0) { - async_evt_rx_rdy(data); - - if (data->rx_next_buffer != NULL) { - async_evt_rx_buf_release(data); + /* true since this functions occurs when buffer is full */ + data->dma_rx.counter = data->dma_rx.buffer_length; + async_evt_rx_rdy(data); + if (data->rx_next_buffer != NULL) { + async_evt_rx_buf_release(data); - /* replace the buffer when the current - * is full and not the same as the next - * one. - */ - uart_stm32_dma_replace_buffer(uart_dev); + /* replace the buffer when the current + * is full and not the same as the next + * one. + */ + uart_stm32_dma_replace_buffer(uart_dev); + } else { + /* Buffer full without valid next buffer, + * an UART_RX_DISABLED event must be generated, + * but uart_stm32_async_rx_disable() cannot be + * called in ISR context. So force the RX timeout + * to minimum value and let the RX timeout to do the job. + */ + k_work_reschedule(&data->dma_rx.timeout_work, K_TICKS(1)); + } } else { - /* Buffer full without valid next buffer, - * an UART_RX_DISABLED event must be generated, - * but uart_stm32_async_rx_disable() cannot be - * called in ISR context. So force the RX timeout - * to minimum value and let the RX timeout to do the job. - */ - k_work_reschedule(&data->dma_rx.timeout_work, K_TICKS(1)); + /* CIRCULAR MODE */ + uart_stm32_dma_rx_flush(data->uart_dev, status); } } @@ -1722,7 +1761,7 @@ static void uart_stm32_async_rx_timeout(struct k_work *work) if (data->dma_rx.counter == data->dma_rx.buffer_length) { uart_stm32_async_rx_disable(dev); } else { - uart_stm32_dma_rx_flush(dev); + uart_stm32_dma_rx_flush(dev, STM32_ASYNC_STATUS_TIMEOUT); } } @@ -1829,9 +1868,10 @@ static int uart_stm32_async_init(const struct device *dev) data->dma_rx.blk_cfg.dest_addr_adj = DMA_ADDR_ADJ_NO_CHANGE; } - /* RX disable circular buffer */ - data->dma_rx.blk_cfg.source_reload_en = 0; - data->dma_rx.blk_cfg.dest_reload_en = 0; + /* Enable/disable RX circular buffer */ + data->dma_rx.blk_cfg.source_reload_en = data->dma_rx.dma_cfg.cyclic; + data->dma_rx.blk_cfg.dest_reload_en = data->dma_rx.dma_cfg.cyclic; + data->dma_rx.blk_cfg.fifo_mode_control = data->dma_rx.fifo_threshold; data->dma_rx.dma_cfg.head_block = &data->dma_rx.blk_cfg; @@ -1868,6 +1908,10 @@ static int uart_stm32_async_init(const struct device *dev) data->dma_tx.blk_cfg.dest_addr_adj = DMA_ADDR_ADJ_NO_CHANGE; } + /* Enable/disable TX circular buffer */ + data->dma_tx.blk_cfg.source_reload_en = data->dma_tx.dma_cfg.cyclic; + data->dma_tx.blk_cfg.dest_reload_en = data->dma_tx.dma_cfg.cyclic; + data->dma_tx.blk_cfg.fifo_mode_control = data->dma_tx.fifo_threshold; data->dma_tx.dma_cfg.head_block = &data->dma_tx.blk_cfg; @@ -2225,6 +2269,8 @@ static int uart_stm32_pm_action(const struct device *dev, .dma_slot = STM32_DMA_SLOT(index, dir, slot),\ .channel_direction = STM32_DMA_CONFIG_DIRECTION( \ STM32_DMA_CHANNEL_CONFIG(index, dir)),\ + .cyclic = STM32_DMA_CONFIG_CYCLIC( \ + STM32_DMA_CHANNEL_CONFIG(index, dir)), \ .channel_priority = STM32_DMA_CONFIG_PRIORITY( \ STM32_DMA_CHANNEL_CONFIG(index, dir)), \ .source_data_size = STM32_DMA_CONFIG_##src_dev##_DATA_SIZE(\ diff --git a/include/zephyr/drivers/dma/dma_stm32.h b/include/zephyr/drivers/dma/dma_stm32.h index cd661f209652b..38f395b0108fc 100644 --- a/include/zephyr/drivers/dma/dma_stm32.h +++ b/include/zephyr/drivers/dma/dma_stm32.h @@ -56,6 +56,8 @@ DT_INST_DMAS_CELL_BY_IDX(id, idx, channel_config) /* macros for channel-config */ +/* enable circular buffer */ +#define STM32_DMA_CONFIG_CYCLIC(config) ((config >> 5) & 0x1) /* direction defined on bits 6-7 */ /* 0 -> MEM_TO_MEM, 1 -> MEM_TO_PERIPH, 2 -> PERIPH_TO_MEM */ #define STM32_DMA_CONFIG_DIRECTION(config) ((config >> 6) & 0x3) diff --git a/samples/boards/st/uart/circular_dma/CMakeLists.txt b/samples/boards/st/uart/circular_dma/CMakeLists.txt new file mode 100644 index 0000000000000..375342e59434a --- /dev/null +++ b/samples/boards/st/uart/circular_dma/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(circular_dma) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/boards/st/uart/circular_dma/README.rst b/samples/boards/st/uart/circular_dma/README.rst new file mode 100644 index 0000000000000..9614365eeb843 --- /dev/null +++ b/samples/boards/st/uart/circular_dma/README.rst @@ -0,0 +1,49 @@ +.. zephyr:code-sample:: uart + :name: UART circular mode + :relevant-api: uart_interface + + Read data from the console and echo it back using a circular dma configuration. + +Overview +******** + +This sample demonstrates how to use STM32 UART serial driver with DMA in circular mode. +It reads data from the console and echoes it back. + +By default, the UART peripheral that is normally assigned to the Zephyr shell +is used, hence the majority of boards should be able to run this sample. + +Building and Running +******************** + +Build and flash the sample as follows, changing ``nucleo_g071rb`` for +your board: + +.. zephyr-app-commands:: + :zephyr-app: samples/boards/st/uart/circular_dma + :board: nucleo_g071rb + :goals: build flash + :compact: + +Sample Output +============= + +.. code-block:: console + + Enter message to fill RX buffer size and press enter : + # Type e.g. : + # "Lorem Ipsum is simply dummy text of the printing and typesetting industry. + # Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, + # when an unknown printer took a galley of type and scrambled it to make a type specimen book. + # It has survived not only five centuries, but also the leap into electronic typesetting, + # remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset + # sheets containing Lorem Ipsum passages, and more recently with desktop publishing software + # like Aldus PageMaker including versions of Lorem Ipsum." + + Lorem Ipsum is simply dummy text of the printing and typesetting industry. + Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, + when an unknown printer took a galley of type and scrambled it to make a type specimen book. + It has survived not only five centuries, but also the leap into electronic typesetting, + remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset + sheets containing Lorem Ipsum passages, and more recently with desktop publishing software + like Aldus PageMaker including versions of Lorem Ipsum. diff --git a/samples/boards/st/uart/circular_dma/boards/nucleo_c031c6.overlay b/samples/boards/st/uart/circular_dma/boards/nucleo_c031c6.overlay new file mode 100644 index 0000000000000..de7d0772e510d --- /dev/null +++ b/samples/boards/st/uart/circular_dma/boards/nucleo_c031c6.overlay @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024 STMicroelectronics + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&usart2 { + dmas = <&dmamux1 0 53 (STM32_DMA_PERIPH_TX)>, + <&dmamux1 3 52 (STM32_DMA_PERIPH_RX | STM32_DMA_MEM_8BITS | STM32_DMA_MODE_CYCLIC)>; + dma-names = "tx", "rx"; + fifo-enable; +}; + +&dma1 { + status = "okay"; +}; + +&dmamux1 { + status = "okay"; +}; diff --git a/samples/boards/st/uart/circular_dma/boards/nucleo_f091rc.overlay b/samples/boards/st/uart/circular_dma/boards/nucleo_f091rc.overlay new file mode 100644 index 0000000000000..274b8d1d5b733 --- /dev/null +++ b/samples/boards/st/uart/circular_dma/boards/nucleo_f091rc.overlay @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2024 STMicroelectronics + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&usart2 { + dmas = <&dma1 4 (STM32_DMA_PERIPH_TX)>, + <&dma1 5 (STM32_DMA_PERIPH_RX | STM32_DMA_MEM_8BITS | STM32_DMA_MODE_CYCLIC)>; + dma-names = "tx", "rx"; + fifo-enable; +}; + +&dma1 { + status = "okay"; +}; diff --git a/samples/boards/st/uart/circular_dma/boards/nucleo_g071rb.overlay b/samples/boards/st/uart/circular_dma/boards/nucleo_g071rb.overlay new file mode 100644 index 0000000000000..de7d0772e510d --- /dev/null +++ b/samples/boards/st/uart/circular_dma/boards/nucleo_g071rb.overlay @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024 STMicroelectronics + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&usart2 { + dmas = <&dmamux1 0 53 (STM32_DMA_PERIPH_TX)>, + <&dmamux1 3 52 (STM32_DMA_PERIPH_RX | STM32_DMA_MEM_8BITS | STM32_DMA_MODE_CYCLIC)>; + dma-names = "tx", "rx"; + fifo-enable; +}; + +&dma1 { + status = "okay"; +}; + +&dmamux1 { + status = "okay"; +}; diff --git a/samples/boards/st/uart/circular_dma/prj.conf b/samples/boards/st/uart/circular_dma/prj.conf new file mode 100644 index 0000000000000..ad49326a82a7f --- /dev/null +++ b/samples/boards/st/uart/circular_dma/prj.conf @@ -0,0 +1,3 @@ +CONFIG_SERIAL=y +CONFIG_UART_ASYNC_API=y +CONFIG_RING_BUFFER=y diff --git a/samples/boards/st/uart/circular_dma/sample.yaml b/samples/boards/st/uart/circular_dma/sample.yaml new file mode 100644 index 0000000000000..85384ae1bb44f --- /dev/null +++ b/samples/boards/st/uart/circular_dma/sample.yaml @@ -0,0 +1,11 @@ +sample: + name: UART driver sample +tests: + sample.boards.stm32.uart.circular_dma: + integration_platforms: + - nucleo_g071rb + tags: + - serial + - uart + filter: dt_chosen_enabled("zephyr,shell-uart") + harness: keyboard diff --git a/samples/boards/st/uart/circular_dma/src/main.c b/samples/boards/st/uart/circular_dma/src/main.c new file mode 100644 index 0000000000000..f230a500dc210 --- /dev/null +++ b/samples/boards/st/uart/circular_dma/src/main.c @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2024 STMicroelectronics + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +#define RING_BUF_SIZE 1000 +#define RX_BUF_SIZE 10 + +#define RECEIVE_TIMEOUT 0 + +#define STACK_SIZE 1024 + +#define UART_DEVICE_NODE DT_CHOSEN(zephyr_shell_uart) + +static const struct device *const uart_dev = DEVICE_DT_GET(UART_DEVICE_NODE); + +/* uart configuration structure */ +const struct uart_config uart_cfg = {.baudrate = 115200, + .parity = UART_CFG_PARITY_NONE, + .stop_bits = UART_CFG_STOP_BITS_1, + .data_bits = UART_CFG_DATA_BITS_8, + .flow_ctrl = UART_CFG_FLOW_CTRL_NONE}; + +/* define a ring buffer to get raw bytes*/ +RING_BUF_DECLARE(ring_buf, RING_BUF_SIZE); + +/* define uart rx buffer */ +static uint8_t rx_buffer[RX_BUF_SIZE]; + +/* define thread stack size */ +static K_THREAD_STACK_DEFINE(uart_rx_stack, STACK_SIZE); + +/* struct uart_event async_evt */ +static struct k_thread uart_rx_thread_data = {0}; + +void print_uart(char *buf, int len) +{ + for (int i = 0; i < len; i++) { + + if ((buf[i] == '\n' || buf[i] == '\r')) { + uart_poll_out(uart_dev, '\n'); + } else { + uart_poll_out(uart_dev, buf[i]); + } + } +} + +/* Data processing thread */ +static void uart_rx_thread(void *p1, void *p2, void *p3) +{ + uint8_t rx_data[RX_BUF_SIZE]; + size_t len; + + while (1) { + + /* Check if there's data in the ring buffer */ + len = ring_buf_get(&ring_buf, rx_data, sizeof(rx_data)); + + if (len > 0) { + + /* Process `len` bytes of data */ + print_uart(rx_data, len); + } + } +} + +void uart_cb(const struct device *dev, struct uart_event *evt, void *user_data) +{ + switch (evt->type) { + case UART_RX_RDY: + /* Data received; place into ring buffer */ + ring_buf_put(&ring_buf, evt->data.rx.buf + evt->data.rx.offset, evt->data.rx.len); + + break; + + case UART_RX_DISABLED: + /* Re-enable RX */ + uart_rx_enable(uart_dev, rx_buffer, sizeof(rx_buffer), RECEIVE_TIMEOUT); + + break; + + default: + break; + } +} + +int main(void) +{ + if (!uart_dev) { + printk("Failed to get UART device"); + return 1; + } + + /* uart configuration parameters */ + int err = uart_configure(uart_dev, &uart_cfg); + + if (err == -ENOSYS) { + printk("Configuration is not supported by device or driver," + " check the UART settings configuration\n"); + return -ENOSYS; + } + + /* Configure uart callback */ + uart_callback_set(uart_dev, uart_cb, NULL); + + /* enable uart reception */ + uart_rx_enable(uart_dev, rx_buffer, sizeof(rx_buffer), RECEIVE_TIMEOUT); + + printk("\n Enter message to fill RX buffer size :\n"); + + /* start uart data processing thread */ + k_tid_t tid = k_thread_create(&uart_rx_thread_data, uart_rx_stack, + K_THREAD_STACK_SIZEOF(uart_rx_stack), uart_rx_thread, NULL, + NULL, NULL, 5, 0, K_NO_WAIT); + k_thread_name_set(tid, "RX_thread"); +}