/** * @file CBSP_AsyncComm.c * @author owalter * @brief Continuus receive UART-driver based on circular DMA * @version 0.1 * @date 28.11.2022 * * driver based on example imlementation of https://github.com/MaJerle/stm32-usart-uart-dma-rx-tx * * @copyright Copyright © 2022 habemus! electronic + transfer GmbH. All rights reserved. */ #include "AsyncComm.h" #include "stm32h7xx_ll_dma.h" #include "stm32h7xx_ll_usart.h" #ifndef USE_HAL_DRIVER #include "stm32l4xx_ll_gpio.h" #endif void usart_rx_check(const uart_desc_t* uart); void usart_process_data(const uart_desc_t* uart, const void* data, size_t len); void usart_send_string(const uart_desc_t* uart, const char* str); /** * \brief USART DMA check thread * \param[in] arg: Thread argument */ _Noreturn void usart_rx_dma_thread(void* arg) { uart_desc_t* uart = arg; void* d; /* Notify user to start sending data */ // usart_send_string(uart, "USART DMA example: DMA HT & TC + USART IDLE LINE IRQ + RTOS processing\r\n"); // usart_send_string(uart, "Start sending data to STM32\r\n"); while (1) { /* Block thread and wait for event to process USART data */ osMessageQueueGet(uart->data->queue, &d, NULL, osWaitForever); /* Simply call processing function */ usart_rx_check(uart); (void)d; } } /** * \brief Check for new data received with DMA * * User must select context to call this function from: * - Only interrupts (DMA HT, DMA TC, UART IDLE) with same preemption priority level * - Only thread context (outside interrupts) * * If called from both context-es, exclusive access protection must be implemented * This mode is not advised as it usually means architecture design problems * * When IDLE interrupt is not present, application must rely only on thread context, * by manually calling function as quickly as possible, to make sure * data are read from raw buffer and processed. * * Not doing reads fast enough may cause DMA to overflow unread received bytes, * hence application will lost useful data. * * Solutions to this are: * - Improve architecture design to achieve faster reads * - Increase raw buffer size and allow DMA to write more data before this function is called */ void usart_rx_check(const uart_desc_t* uart) { size_t pos; /* Calculate current position in buffer and check for new data available */ pos = ARRAY_LEN(uart->data->dma_rx_buffer) - LL_DMA_GetDataLength(uart->dma_rx, uart->dma_rx_ch); if (pos != uart->data->old_pos) { /* Check change in received data */ if (pos > uart->data->old_pos) { /* Current position is over previous one */ /* * Processing is done in "linear" mode. * * Application processing is fast with single data block, * length is simply calculated by subtracting pointers * * [ 0 ] * [ 1 ] <- old_pos |------------------------------------| * [ 2 ] | | * [ 3 ] | Single block (len = pos - old_pos) | * [ 4 ] | | * [ 5 ] |------------------------------------| * [ 6 ] <- pos * [ 7 ] * [ N - 1 ] */ usart_process_data(uart, &uart->data->dma_rx_buffer[uart->data->old_pos], pos - uart->data->old_pos); } else { /* * Processing is done in "overflow" mode.. * * Application must process data twice, * since there are 2 linear memory blocks to handle * * [ 0 ] |---------------------------------| * [ 1 ] | Second block (len = pos) | * [ 2 ] |---------------------------------| * [ 3 ] <- pos * [ 4 ] <- old_pos |---------------------------------| * [ 5 ] | | * [ 6 ] | First block (len = N - old_pos) | * [ 7 ] | | * [ N - 1 ] |---------------------------------| */ usart_process_data(uart, &uart->data->dma_rx_buffer[uart->data->old_pos], ARRAY_LEN(uart->data->dma_rx_buffer) - uart->data->old_pos); if (pos > 0) { usart_process_data(uart, &uart->data->dma_rx_buffer[0], pos); } } uart->data->old_pos = pos; /* Save current position as old for next transfers */ } } /** * \brief Check if DMA is active and if not try to send data * \return `1` if transfer just started, `0` if on-going or no data to transmit */ uint8_t usart_start_tx_dma_transfer(const uart_desc_t* uart) { uint32_t primask; uint8_t started = 0; /* * First check if transfer is currently in-active, * by examining the value of usart_tx_dma_current_len variable. * * This variable is set before DMA transfer is started and cleared in DMA TX complete interrupt. * * It is not necessary to disable the interrupts before checking the variable: * * When usart_tx_dma_current_len == 0 * - This function is called by either application or TX DMA interrupt * - When called from interrupt, it was just reset before the call, * indicating transfer just completed and ready for more * - When called from an application, transfer was previously already in-active * and immediate call from interrupt cannot happen at this moment * * When usart_tx_dma_current_len != 0 * - This function is called only by an application. * - It will never be called from interrupt with usart_tx_dma_current_len != 0 condition * * Disabling interrupts before checking for next transfer is advised * only if multiple operating system threads can access to this function w/o * exclusive access protection (mutex) configured, * or if application calls this function from multiple interrupts. * * This example assumes worst use case scenario, * hence interrupts are disabled prior every check */ primask = __get_PRIMASK(); __disable_irq(); if (uart->data->tx_dma_current_len == 0 && (uart->data->tx_dma_current_len = lwrb_get_linear_block_read_length(&uart->data->tx_rb)) > 0) { /* Disable channel if enabled */ LL_DMA_DisableStream(uart->dma_tx, uart->dma_tx_ch); /* Clear all flags */ uart->dma_tx_clear_tc_fn(uart->dma_tx); uart->dma_tx_clear_ht_fn(uart->dma_tx); uart->dma_tx_clear_gi_fn(uart->dma_tx); uart->dma_tx_clear_te_fn(uart->dma_tx); uart->dma_tx_clear_fe_fn(uart->dma_tx); /* Prepare DMA data and length */ LL_DMA_SetDataLength(uart->dma_tx, uart->dma_tx_ch, uart->data->tx_dma_current_len); LL_DMA_SetMemoryAddress(uart->dma_tx, uart->dma_tx_ch, (uint32_t)lwrb_get_linear_block_read_address(&uart->data->tx_rb)); /* Start transfer */ LL_DMA_EnableStream(uart->dma_tx, uart->dma_tx_ch); uart->data->tx_running = 1; started = 1; } __set_PRIMASK(primask); return started; } /** * \brief Process received data over UART * \note Either process them directly or copy to other bigger buffer * \param[in] data: Data to process * \param[in] len: Length in units of bytes */ void usart_process_data(const uart_desc_t* uart, const void* data, size_t len) { /* * This function is called on DMA TC or HT events, and on UART IDLE (if enabled) event. */ uart->uart_external_receive_callback(data, len); } /** * \brief Send data over UART * \note copy data into ringbuffer and trigger UART transmission * \param[in] data: Data to process * \param[in] len: Length in units of bytes */ void usart_send_data(const uart_desc_t* uart, const void* data, size_t len) { lwrb_write(&uart->data->tx_rb, data, len); /* Write data to TX buffer */ usart_start_tx_dma_transfer(uart); /* Then try to start transfer */ } /** * \brief Send string to USART * \param[in] str: String to send */ void usart_send_string(const uart_desc_t* uart, const char* str) { lwrb_write(&uart->data->tx_rb, str, strnlen(str, 255)); /* Write data to TX buffer for loopback */ usart_start_tx_dma_transfer(uart); /* Then try to start transfer */ } /** * \brief USART DMA Initialization Function * other init is handled by MX_HAL */ osThreadId_t usart_init(const uart_desc_t* uart, const osThreadAttr_t* attr) { #ifndef USE_HAL_DRIVER LL_USART_InitTypeDef USART_InitStruct = { 0 }; LL_GPIO_InitTypeDef GPIO_InitStruct = { 0 }; /* USART GPIO Configuration */ /* RX pin */ GPIO_InitStruct.Pin = uart->uart_rx_pin; GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE; GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL; GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; GPIO_InitStruct.Alternate = uart->uart_rx_pin_af; LL_GPIO_Init(uart->uart_rx_port, &GPIO_InitStruct); /* Optional TX pin */ if (uart->uart_tx_port != NULL) { GPIO_InitStruct.Pin = uart->uart_tx_pin; GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE; GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL; GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; GPIO_InitStruct.Alternate = uart->uart_tx_pin_af; LL_GPIO_Init(uart->uart_tx_port, &GPIO_InitStruct); } #endif /* USART RX DMA init */ #ifndef USE_HAL_DRIVER LL_DMA_SetPeriphRequest(uart->dma_rx, uart->dma_rx_ch, uart->dma_rx, uart->dma_rx_req); LL_DMA_SetDataTransferDirection(uart->dma_rx, uart->dma_rx_ch, LL_DMA_DIRECTION_PERIPH_TO_MEMORY); LL_DMA_SetChannelPriorityLevel(uart->dma_rx, uart->dma_rx_ch, LL_DMA_PRIORITY_LOW); LL_DMA_SetMode(uart->dma_rx, uart->dma_rx_ch, LL_DMA_MODE_NORMAL); LL_DMA_SetPeriphIncMode(uart->dma_rx, uart->dma_rx_ch, LL_DMA_PERIPH_NOINCREMENT); LL_DMA_SetMemoryIncMode(uart->dma_rx, uart->dma_rx_ch, LL_DMA_MEMORY_INCREMENT); LL_DMA_SetPeriphSize(uart->dma_rx, uart->dma_rx_ch, LL_DMA_PDATAALIGN_BYTE); LL_DMA_SetMemorySize(uart->dma_rx, uart->dma_rx_ch, LL_DMA_MDATAALIGN_BYTE); #endif LL_DMA_SetPeriphAddress(uart->dma_rx, uart->dma_rx_ch, LL_USART_DMA_GetRegAddr(uart->uart, LL_USART_DMA_REG_DATA_RECEIVE)); LL_DMA_SetMemoryAddress(uart->dma_rx, uart->dma_rx_ch, (uint32_t)uart->data->dma_rx_buffer); LL_DMA_SetDataLength(uart->dma_rx, uart->dma_rx_ch, ARRAY_LEN(uart->data->dma_rx_buffer)); /* USART TX DMA init */ #ifndef USE_HAL_DRIVER LL_DMA_SetPeriphRequest(uart->dma_tx, uart->dma_tx_ch, uart->dma_tx, uart->dma_tx_req); LL_DMA_SetDataTransferDirection(uart->dma_tx, uart->dma_tx_ch, LL_DMA_DIRECTION_MEMORY_TO_PERIPH); LL_DMA_SetChannelPriorityLevel(uart->dma_tx, uart->dma_tx_ch, LL_DMA_PRIORITY_LOW); LL_DMA_SetMode(uart->dma_tx, uart->dma_tx_ch, LL_DMA_MODE_NORMAL); LL_DMA_SetPeriphIncMode(uart->dma_tx, uart->dma_tx_ch, LL_DMA_PERIPH_NOINCREMENT); LL_DMA_SetMemoryIncMode(uart->dma_tx, uart->dma_tx_ch, LL_DMA_MEMORY_INCREMENT); LL_DMA_SetPeriphSize(uart->dma_tx, uart->dma_tx_ch, LL_DMA_PDATAALIGN_BYTE); LL_DMA_SetMemorySize(uart->dma_tx, uart->dma_tx_ch, LL_DMA_MDATAALIGN_BYTE); #endif LL_DMA_SetPeriphAddress(uart->dma_tx, uart->dma_tx_ch, LL_USART_DMA_GetRegAddr(uart->uart, LL_USART_DMA_REG_DATA_TRANSMIT)); /* Enable HT & TC interrupts */ LL_DMA_EnableIT_HT(uart->dma_rx, uart->dma_rx_ch); LL_DMA_EnableIT_TC(uart->dma_rx, uart->dma_rx_ch); /* Enable TC interrupts for TX */ LL_DMA_EnableIT_TC(uart->dma_tx, uart->dma_tx_ch); /* DMA interrupt init */ #ifndef USE_HAL_DRIVER NVIC_SetPriority(uart->dma_irq_rx, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), uart->prio, 0)); NVIC_EnableIRQ(uart->dma_irq_rx); NVIC_SetPriority(uart->dma_irq_tx, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), uart->prio, 0)); NVIC_EnableIRQ(uart->dma_irq_tx); #endif /* USART configuration */ #ifndef USE_HAL_DRIVER USART_InitStruct.BaudRate = uart->uart_baudrate; USART_InitStruct.DataWidth = LL_USART_DATAWIDTH_8B; USART_InitStruct.StopBits = LL_USART_STOPBITS_1; USART_InitStruct.Parity = LL_USART_PARITY_NONE; USART_InitStruct.TransferDirection = LL_USART_DIRECTION_TX_RX; USART_InitStruct.HardwareFlowControl = LL_USART_HWCONTROL_NONE; USART_InitStruct.OverSampling = LL_USART_OVERSAMPLING_16; LL_USART_Init(uart->uart, &USART_InitStruct); LL_USART_ConfigAsyncMode(uart->uart); #endif LL_USART_Disable(uart->uart); LL_USART_EnableDMAReq_RX(uart->uart); LL_USART_EnableDMAReq_TX(uart->uart); LL_USART_EnableIT_IDLE(uart->uart); /* USART interrupt */ #ifndef USE_HAL_DRIVER NVIC_SetPriority(uart->uart_irq, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), uart->prio, 1)); NVIC_EnableIRQ(uart->uart_irq); #endif /* Create message queue before enabling UART or DMA */ /* This is to make sure message queue is ready before UART/DMA interrupts are enabled */ uart->data->queue = osMessageQueueNew(10, sizeof(void*), NULL); /*init tx ringbuffer*/ lwrb_init(&uart->data->tx_rb, uart->data->tx_rb_data, sizeof(uart->data->tx_rb_data)); /* Enable USART and DMA */ LL_DMA_EnableStream(uart->dma_rx, uart->dma_rx_ch); LL_USART_Enable(uart->uart); /* Create new thread for USART RX DMA processing */ return osThreadNew(usart_rx_dma_thread, (void*)uart, attr); } /** * \brief General purpose DMA interrupt handler * \note Function must be called from DMA interrupt * * It handles half-transfer and transfer-cmakecomplete interrupts and does the job accordingly * * must be called from the IT function with the correct context pointer * * \param[in] uart: Uart description to handle */ void usart_dma_irq_handler(const uart_desc_t* uart) { void* d = (void*)1; /* Check half-transfer complete interrupt */ if (LL_DMA_IsEnabledIT_HT(uart->dma_rx, uart->dma_rx_ch) && uart->dma_rx_is_ht_fn(uart->dma_rx)) { uart->dma_rx_clear_ht_fn(uart->dma_rx); /* Clear half-transfer complete flag */ osMessageQueuePut(uart->data->queue, &d, 0, 0); /* Write data to queue. Do not use wait function! */ } /* Check transfer-complete interrupt */ if (LL_DMA_IsEnabledIT_TC(uart->dma_rx, uart->dma_rx_ch) && uart->dma_rx_is_tc_fn(uart->dma_rx)) { uart->dma_rx_clear_tc_fn(uart->dma_rx); /* Clear transfer complete flag */ osMessageQueuePut(uart->data->queue, &d, 0, 0); /* Write data to queue. Do not use wait function! */ } } /// must be called from the IT function with the correct context pointer void usart_dma_irq_handler_tx(const uart_desc_t* uart) { /* Check transfer-complete interrupt */ if (LL_DMA_IsEnabledIT_TC(uart->dma_tx, uart->dma_tx_ch) && uart->dma_tx_is_tc_fn(uart->dma_tx)) { uart->dma_tx_clear_tc_fn(uart->dma_tx); /* Clear transfer complete flag */ lwrb_skip(&uart->data->tx_rb, uart->data->tx_dma_current_len); /* Skip buffer, it has been successfully sent out */ uart->data->tx_dma_current_len = 0; /* Reset data length */ uart->data->tx_running = 0; usart_start_tx_dma_transfer(uart); /* Start new transfer */ } } /** * \brief General purpose UART interrupt handler * \note Function must be called from UART interrupt * * It handles IDLE line detection interrupt and does the job accordingly * * must be called from the IT function with the correct context pointer * * \param[in] uart: Uart description to handle */ void usart_irq_handler(const uart_desc_t* uart) { void* d = (void*)1; /* Check for IDLE line interrupt */ if (LL_USART_IsEnabledIT_IDLE(uart->uart) && LL_USART_IsActiveFlag_IDLE(uart->uart)) { LL_USART_ClearFlag_IDLE(uart->uart); /* Clear IDLE line flag */ osMessageQueuePut(uart->data->queue, &d, 0, 0); /* Write data to queue. Do not use wait function! */ } }