394 lines
16 KiB
C
394 lines
16 KiB
C
/**
|
|
* @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 "string.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, strlen(str)); /* 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! */
|
|
}
|
|
}
|