added minmal libaries

This commit is contained in:
2024-02-02 02:44:24 +01:00
parent 1c693986ed
commit f0387d9e80
17 changed files with 1786 additions and 62 deletions

391
lib/uart_driver/AsyncComm.c Normal file
View File

@@ -0,0 +1,391 @@
/**
* @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! */
}
}

108
lib/uart_driver/AsyncComm.h Normal file
View File

@@ -0,0 +1,108 @@
/**
* @file CBSP_AsyncComm.h
* @author owalter
* @brief
* @version 0.1
* @date 28.11.2022
*
* @copyright Copyright © 2022 habemus! electronic + transfer GmbH. All rights reserved.
*/
#ifndef NUN10K22_DISPLAY_CBSP_ASYNCCOMM_H
#define NUN10K22_DISPLAY_CBSP_ASYNCCOMM_H
#include <stdint.h>
#include "cmsis_os.h"
#include "lwrb.h"
#include "main.h"
typedef void (*data_receive_callback)(const void* data, size_t len);
typedef void (*char_receive_callback)(const char* str, size_t len);
typedef void (*bytes_receive_callback)(const uint8_t* str, size_t len);
/**
* \brief Volatile data structure
*
* Variables declared using this structure can not be put to non-volatile memory (such as flash, EEPROM, ..)
*/
typedef struct {
/* OS queue */
osMessageQueueId_t queue; /*!< Message queue */
/* Raw data buffer */
uint8_t dma_rx_buffer[1024]; /*!< DMA buffer for receive data */
size_t old_pos; /*!< Position for data */
/* Tx data buffer */
volatile size_t tx_dma_current_len;
volatile uint8_t tx_running;
lwrb_t tx_rb;
uint8_t tx_rb_data[512];
} uart_desc_volatile_t;
/**
* \brief Non-Volatile data structure
*
* Variables declared using this structure may be put to non-volative memory.
* This is to avoid using RAM for constant data
*/
typedef struct {
/* UART config */
USART_TypeDef* uart; /*!< UART/USART/LPUART instance */
#ifndef USE_HAL_DRIVER
GPIO_TypeDef* uart_tx_port;
GPIO_TypeDef* uart_rx_port;
uint16_t uart_tx_pin;
uint16_t uart_rx_pin;
uint16_t uart_tx_pin_af;
uint16_t uart_rx_pin_af;
/* Interrupts config */
uint8_t prio; /*!< Preemption priority number */
IRQn_Type uart_irq; /*!< UART IRQ instance */
IRQn_Type dma_irq_rx; /*!< DMA IRQ RX instance */
IRQn_Type dma_irq_tx; /*!< DMA IRQ TX instance */
/* DMA config & flags management */
uint32_t dma_rx_req; /*!< RX DMA request */
uint32_t dma_tx_req; /*!< TX DMA request */
#endif
DMA_TypeDef* dma_rx; /*!< RX DMA instance */
uint32_t dma_rx_ch; /*!< RX DMA channel */
void (*dma_rx_clear_tc_fn)(DMA_TypeDef*);
void (*dma_rx_clear_ht_fn)(DMA_TypeDef*);
uint32_t (*dma_rx_is_tc_fn)(DMA_TypeDef*);
uint32_t (*dma_rx_is_ht_fn)(DMA_TypeDef*);
DMA_TypeDef* dma_tx; /*!< TX DMA instance */
uint32_t dma_tx_ch; /*!< TX DMA channel */
void (*dma_tx_clear_tc_fn)(DMA_TypeDef*);
uint32_t (*dma_tx_is_tc_fn)(DMA_TypeDef*);
void (*dma_tx_clear_ht_fn)(DMA_TypeDef*);
void (*dma_tx_clear_gi_fn)(DMA_TypeDef*);
void (*dma_tx_clear_te_fn)(DMA_TypeDef*);
void (*dma_tx_clear_fe_fn)(DMA_TypeDef*);
uart_desc_volatile_t* data; /*!< Pointer to volatile data */
/* receive callback */
data_receive_callback uart_external_receive_callback;
} uart_desc_t;
/* USART related functions */
osThreadId_t usart_init(const uart_desc_t* uart, const osThreadAttr_t* attr);
void usart_dma_irq_handler(const uart_desc_t* uart);
void usart_dma_irq_handler_tx(const uart_desc_t* uart);
void usart_irq_handler(const uart_desc_t* uart);
void usart_send_data(const uart_desc_t* uart, const void* data, size_t len);
/**
* \brief Calculate length of statically allocated array
*/
#define ARRAY_LEN(x) (sizeof(x) / sizeof((x)[0]))
#endif // NUN10K22_DISPLAY_CBSP_ASYNCCOMM_H

View File

@@ -0,0 +1,5 @@
//
// Created by oliver on 12/8/23.
//
#include "AsyncFrames.h"

View File

@@ -0,0 +1,17 @@
//
// Created by oliver on 12/8/23.
//
#ifndef MASTERCOMMISION_ASYNCFAMES_H
#define MASTERCOMMISION_ASYNCFAMES_H
#include "stdint.h"
typedef struct AsyncFrameHeader_s {
uint16_t sof;
uint16_t leng;
} AsyncFrameHeader_t;
#endif //MASTERCOMMISION_ASYNCFAMES_H

View File

@@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 3.12)
project(uart_driver C)
add_library(${PROJECT_NAME} "")
target_sources(${PROJECT_NAME}
PRIVATE
${CMAKE_CURRENT_LIST_DIR}/AsyncComm.c
${CMAKE_CURRENT_LIST_DIR}/AsyncFrames.c
INTERFACE
${CMAKE_CURRENT_LIST_DIR}/AsyncComm.h
${CMAKE_CURRENT_LIST_DIR}/AsyncFrames.h
)
target_include_directories(${PROJECT_NAME}
INTERFACE
${CMAKE_CURRENT_LIST_DIR}
)
target_link_libraries(${PROJECT_NAME} PUBLIC lwrb)

546
lib/uart_driver/main.c Normal file
View File

@@ -0,0 +1,546 @@
/*
* This example shows how application can implement RX and TX DMA for UART.
* It uses simple packet example approach and 3 separate buffers:
*
* - Raw DMA RX buffer where DMA transfers data from UART to memory
* - Ringbuff for RX data which are transfered from raw buffer and ready for application processin
* - Ringbuff for TX data to be sent out by DMA TX
*/
/* Includes */
#include "main.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "lwrb.h"
/* Private function prototypes */
void SystemClock_Config(void);
/* USART related functions */
void usart_init(void);
void usart_rx_check(void);
void usart_process_data(const void* data, size_t len);
void usart_send_string(const char* str);
uint8_t usart_start_tx_dma_transfer(void);
/**
* \brief Calculate length of statically allocated array
*/
#define ARRAY_LEN(x) (sizeof(x) / sizeof((x)[0]))
/**
* \brief USART RX buffer for DMA to transfer every received byte RX
* \note Contains raw data that are about to be processed by different events
*
* Special use case for STM32H7 series.
* Default memory configuration in STM32H7 may put variables to DTCM RAM,
* part of memory that is super fast, however DMA has no access to it.
*
* For this specific example, all variables are by default
* configured in D1 RAM. This is configured in linker script
*/
uint8_t
usart_rx_dma_buffer[64];
/**
* \brief Ring buffer instance for TX data
*/
lwrb_t
usart_rx_rb;
/**
* \brief Ring buffer data array for RX DMA
*/
uint8_t
usart_rx_rb_data[128];
/**
* \brief Ring buffer instance for TX data
*/
lwrb_t
usart_tx_rb;
/**
* \brief Ring buffer data array for TX DMA
*/
uint8_t
usart_tx_rb_data[128];
/**
* \brief Length of currently active TX DMA transfer
*/
volatile size_t
usart_tx_dma_current_len;
/**
* \brief Application entry point
*/
int
main(void) {
uint8_t state, cmd, len;
/* MCU Configuration */
// SCB_DisableDCache();
// SCB_DisableICache();
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
LL_APB4_GRP1_EnableClock(LL_APB4_GRP1_PERIPH_SYSCFG);
/* Configure the system clock */
SystemClock_Config();
/* Initialize ringbuff for TX & RX */
lwrb_init(&usart_tx_rb, usart_tx_rb_data, sizeof(usart_tx_rb_data));
lwrb_init(&usart_rx_rb, usart_rx_rb_data, sizeof(usart_rx_rb_data));
/* Initialize all configured peripherals */
usart_init();
usart_send_string("USART DMA example: DMA HT & TC + USART IDLE LINE interrupts\r\n");
usart_send_string("Start sending data to STM32\r\n");
/* After this point, do not use usart_send_string function anymore */
/* Send packet data over UART from PC (or other STM32 device) */
/* Infinite loop */
state = 0;
while (1) {
uint8_t b;
/* Process RX ringbuffer */
/* Packet format: START_BYTE, CMD, LEN[, DATA[0], DATA[len - 1]], STOP BYTE */
/* DATA bytes are included only if LEN > 0 */
/* An example, send sequence of these bytes: 0x55, 0x01, 0x01, 0xFF, 0xAA */
/* Read byte by byte */
if (lwrb_read(&usart_rx_rb, &b, 1) == 1) {
lwrb_write(&usart_tx_rb, &b, 1); /* Write data to transmit buffer */
usart_start_tx_dma_transfer();
switch (state) {
case 0: { /* Wait for start byte */
if (b == 0x55) {
++state;
}
break;
}
case 1: { /* Check packet command */
cmd = b;
++state;
break;
}
case 2: { /* Packet data length */
len = b;
++state;
if (len == 0) {
++state; /* Ignore data part if len = 0 */
}
break;
}
case 3: { /* Data for command */
--len; /* Decrease for received character */
if (len == 0) {
++state;
}
break;
}
case 4: { /* End of packet */
if (b == 0xAA) {
/* Packet is valid */
/* Send out response with CMD = 0xFF */
b = 0x55; /* Start byte */
lwrb_write(&usart_tx_rb, &b, 1);
cmd = 0xFF; /* Command = 0xFF = OK response */
lwrb_write(&usart_tx_rb, &cmd, 1);
b = 0x00; /* Len = 0 */
lwrb_write(&usart_tx_rb, &b, 1);
b = 0xAA; /* Stop byte */
lwrb_write(&usart_tx_rb, &b, 1);
/* Flush everything */
usart_start_tx_dma_transfer();
}
state = 0;
break;
}
}
}
/* Do other tasks ... */
}
}
/**
* \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(void) {
static size_t old_pos;
size_t pos;
/* Calculate current position in buffer and check for new data available */
pos = ARRAY_LEN(usart_rx_dma_buffer) - LL_DMA_GetDataLength(DMA1, LL_DMA_STREAM_0);
if (pos != old_pos) { /* Check change in received data */
if (pos > 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(&usart_rx_dma_buffer[old_pos], pos - 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(&usart_rx_dma_buffer[old_pos], ARRAY_LEN(usart_rx_dma_buffer) - old_pos);
if (pos > 0) {
usart_process_data(&usart_rx_dma_buffer[0], pos);
}
}
old_pos = pos; /* Save current position as old for next transfers */
}
}
/**
* \brief Check if DMA is active and if not try to send data
*
* This function can be called either by application to start data transfer
* or from DMA TX interrupt after previous transfer just finished
*
* \return `1` if transfer just started, `0` if on-going or no data to transmit
*/
uint8_t
usart_start_tx_dma_transfer(void) {
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 (usart_tx_dma_current_len == 0
&& (usart_tx_dma_current_len = lwrb_get_linear_block_read_length(&usart_tx_rb)) > 0) {
/* Disable channel if enabled */
LL_DMA_DisableStream(DMA1, LL_DMA_STREAM_1);
/* Clear all flags */
LL_DMA_ClearFlag_TC1(DMA1);
LL_DMA_ClearFlag_HT1(DMA1);
LL_DMA_ClearFlag_TE1(DMA1);
LL_DMA_ClearFlag_DME1(DMA1);
LL_DMA_ClearFlag_FE1(DMA1);
/* Prepare DMA data and length */
LL_DMA_SetDataLength(DMA1, LL_DMA_STREAM_1, usart_tx_dma_current_len);
LL_DMA_SetMemoryAddress(DMA1, LL_DMA_STREAM_1, (uint32_t)lwrb_get_linear_block_read_address(&usart_tx_rb));
/* Start transfer */
LL_DMA_EnableStream(DMA1, LL_DMA_STREAM_1);
started = 1;
}
__set_PRIMASK(primask);
return started;
}
/**
* \brief Process received data over UART
* Data are written to RX ringbuffer for application processing at latter stage
* \param[in] data: Data to process
* \param[in] len: Length in units of bytes
*/
void
usart_process_data(const void* data, size_t len) {
lwrb_write(&usart_rx_rb, data, len); /* Write data to receive buffer */
}
/**
* \brief Send string over USART
* \param[in] str: String to send
*/
void
usart_send_string(const char* str) {
lwrb_write(&usart_tx_rb, str, strlen(str)); /* Write data to transmit buffer */
usart_start_tx_dma_transfer();
}
/**
* \brief USART3 Initialization Function
*/
void
usart_init(void) {
LL_USART_InitTypeDef USART_InitStruct = {0};
// CUBE_MX does this already
//LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
/* Peripheral clock enable */
//LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_USART3);
//LL_AHB4_GRP1_EnableClock(LL_AHB4_GRP1_PERIPH_GPIOD);
//LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);
/*
* USART3 GPIO Configuration
*
* PD8 ------> USART3_TX
* PD9 ------> USART3_RX
*/
//GPIO_InitStruct.Pin = LL_GPIO_PIN_8 | LL_GPIO_PIN_9;
//GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
//GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
//GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
//GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
//GPIO_InitStruct.Alternate = LL_GPIO_AF_7;
//LL_GPIO_Init(GPIOD, &GPIO_InitStruct);
/* USART3_RX Init */
// CUBE_MX does this already
//LL_DMA_SetPeriphRequest(DMA1, LL_DMA_STREAM_0, LL_DMAMUX1_REQ_USART3_RX);
//LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_STREAM_0, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
//LL_DMA_SetStreamPriorityLevel(DMA1, LL_DMA_STREAM_0, LL_DMA_PRIORITY_LOW);
//LL_DMA_SetMode(DMA1, LL_DMA_STREAM_0, LL_DMA_MODE_CIRCULAR);
//LL_DMA_SetPeriphIncMode(DMA1, LL_DMA_STREAM_0, LL_DMA_PERIPH_NOINCREMENT);
//LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_STREAM_0, LL_DMA_MEMORY_INCREMENT);
//LL_DMA_SetPeriphSize(DMA1, LL_DMA_STREAM_0, LL_DMA_PDATAALIGN_BYTE);
//LL_DMA_SetMemorySize(DMA1, LL_DMA_STREAM_0, LL_DMA_MDATAALIGN_BYTE);
//LL_DMA_DisableFifoMode(DMA1, LL_DMA_STREAM_0);
LL_DMA_SetPeriphAddress(DMA1, LL_DMA_STREAM_0, LL_USART_DMA_GetRegAddr(USART3, LL_USART_DMA_REG_DATA_RECEIVE));
LL_DMA_SetMemoryAddress(DMA1, LL_DMA_STREAM_0, (uint32_t)usart_rx_dma_buffer);
LL_DMA_SetDataLength(DMA1, LL_DMA_STREAM_0, ARRAY_LEN(usart_rx_dma_buffer));
/* USART3_TX Init */
// CUBE_MX does this already
//LL_DMA_SetPeriphRequest(DMA1, LL_DMA_STREAM_1, LL_DMAMUX1_REQ_USART3_TX);
//LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_STREAM_1, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
//LL_DMA_SetStreamPriorityLevel(DMA1, LL_DMA_STREAM_1, LL_DMA_PRIORITY_LOW);
//LL_DMA_SetMode(DMA1, LL_DMA_STREAM_1, LL_DMA_MODE_NORMAL);
//LL_DMA_SetPeriphIncMode(DMA1, LL_DMA_STREAM_1, LL_DMA_PERIPH_NOINCREMENT);
//LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_STREAM_1, LL_DMA_MEMORY_INCREMENT);
//LL_DMA_SetPeriphSize(DMA1, LL_DMA_STREAM_1, LL_DMA_PDATAALIGN_BYTE);
//LL_DMA_SetMemorySize(DMA1, LL_DMA_STREAM_1, LL_DMA_MDATAALIGN_BYTE);
//LL_DMA_DisableFifoMode(DMA1, LL_DMA_STREAM_1);
LL_DMA_SetPeriphAddress(DMA1, LL_DMA_STREAM_1, LL_USART_DMA_GetRegAddr(USART3, LL_USART_DMA_REG_DATA_TRANSMIT));
/* Enable DMA RX HT & TC interrupts */
LL_DMA_EnableIT_HT(DMA1, LL_DMA_STREAM_0);
LL_DMA_EnableIT_TC(DMA1, LL_DMA_STREAM_0);
/* Enable DMA TX TC interrupts */
LL_DMA_EnableIT_TC(DMA1, LL_DMA_STREAM_1);
/* DMA interrupt init */
// CUBE_MX does this already dma.c
//NVIC_SetPriority(DMA1_Stream0_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 0, 0));
//NVIC_EnableIRQ(DMA1_Stream0_IRQn);
//NVIC_SetPriority(DMA1_Stream1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 0, 0));
//NVIC_EnableIRQ(DMA1_Stream1_IRQn);
/* Configure USART3 */
// CUBE_MX does this already usart.c
//USART_InitStruct.PrescalerValue = LL_USART_PRESCALER_DIV1;
//USART_InitStruct.BaudRate = 115200;
//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(USART3, &USART_InitStruct);
//LL_USART_SetTXFIFOThreshold(USART3, LL_USART_FIFOTHRESHOLD_7_8);
//LL_USART_SetRXFIFOThreshold(USART3, LL_USART_FIFOTHRESHOLD_7_8);
//LL_USART_EnableFIFO(USART3);
//LL_USART_ConfigAsyncMode(USART3);
LL_USART_EnableDMAReq_RX(USART3);
LL_USART_EnableDMAReq_TX(USART3);
LL_USART_EnableIT_IDLE(USART3);
/* USART interrupt, same priority as DMA channel */
// CUBE_MX does this already usart.c
//NVIC_SetPriority(USART3_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 0, 0));
//NVIC_EnableIRQ(USART3_IRQn);
/* Enable USART and DMA RX */
LL_DMA_EnableStream(DMA1, LL_DMA_STREAM_0);
//LL_USART_Enable(USART3);
/* Polling USART3 initialization */
while (!LL_USART_IsActiveFlag_TEACK(USART3) || !LL_USART_IsActiveFlag_REACK(USART3)) {}
}
/* Interrupt handlers here */
/**
* \brief DMA1 stream1 interrupt handler for USART3 RX
*/
void
DMA1_Stream0_IRQHandler(void) {
/* Check half-transfer complete interrupt */
if (LL_DMA_IsEnabledIT_HT(DMA1, LL_DMA_STREAM_0) && LL_DMA_IsActiveFlag_HT0(DMA1)) {
LL_DMA_ClearFlag_HT0(DMA1); /* Clear half-transfer complete flag */
usart_rx_check(); /* Check for data to process */
}
/* Check transfer-complete interrupt */
if (LL_DMA_IsEnabledIT_TC(DMA1, LL_DMA_STREAM_0) && LL_DMA_IsActiveFlag_TC0(DMA1)) {
LL_DMA_ClearFlag_TC0(DMA1); /* Clear transfer complete flag */
usart_rx_check(); /* Check for data to process */
}
/* Implement other events when needed */
}
/**
* \brief DMA1 stream1 interrupt handler for USART3 TX
*/
void
DMA1_Stream1_IRQHandler(void) {
/* Check transfer complete */
if (LL_DMA_IsEnabledIT_TC(DMA1, LL_DMA_STREAM_1) && LL_DMA_IsActiveFlag_TC1(DMA1)) {
LL_DMA_ClearFlag_TC1(DMA1); /* Clear transfer complete flag */
lwrb_skip(&usart_tx_rb, usart_tx_dma_current_len);/* Skip sent data, mark as read */
usart_tx_dma_current_len = 0; /* Clear length variable */
usart_start_tx_dma_transfer(); /* Start sending more data */
}
/* Implement other events when needed */
}
/**
* \brief USART3 global interrupt handler
*/
void
USART3_IRQHandler(void) {
/* Check for IDLE line interrupt */
if (LL_USART_IsEnabledIT_IDLE(USART3) && LL_USART_IsActiveFlag_IDLE(USART3)) {
LL_USART_ClearFlag_IDLE(USART3); /* Clear IDLE line flag */
usart_rx_check(); /* Check for data to process */
}
/* Implement other events when needed */
}
/**
* \brief System Clock Configuration
*/
void
SystemClock_Config(void) {
/* Configure flash latency */
LL_FLASH_SetLatency(LL_FLASH_LATENCY_4);
if (LL_FLASH_GetLatency() != LL_FLASH_LATENCY_4) {
while (1) {}
}
/* Configure power supply and voltage scale */
LL_PWR_ConfigSupply(LL_PWR_LDO_SUPPLY);
LL_PWR_SetRegulVoltageScaling(LL_PWR_REGU_VOLTAGE_SCALE0);
/* Uncomment if used on STM32H745/H755 Nucleo */
/* Dual-Core Nucleo board used external SMPS instead of LDO */
/* Manually enable it */
//PWR->CR3 |= 1 << 2;
/* Configure HSE */
LL_RCC_HSE_EnableBypass();
LL_RCC_HSE_Enable();
while (!LL_RCC_HSE_IsReady()) {}
/* Configure PLL */
LL_RCC_PLL_SetSource(LL_RCC_PLLSOURCE_HSE);
LL_RCC_PLL1P_Enable();
LL_RCC_PLL1Q_Enable();
LL_RCC_PLL1_SetVCOInputRange(LL_RCC_PLLINPUTRANGE_8_16);
LL_RCC_PLL1_SetVCOOutputRange(LL_RCC_PLLVCORANGE_WIDE);
LL_RCC_PLL1_SetM(1);
LL_RCC_PLL1_SetN(120);
LL_RCC_PLL1_SetP(2);
LL_RCC_PLL1_SetQ(20);
LL_RCC_PLL1_SetR(2);
LL_RCC_PLL1_Enable();
while (!LL_RCC_PLL1_IsReady()) {}
/* Intermediate AHB prescaler 2 when target frequency clock is higher than 80 MHz */
LL_RCC_SetAHBPrescaler(LL_RCC_AHB_DIV_2);
LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_PLL1);
LL_RCC_SetSysPrescaler(LL_RCC_SYSCLK_DIV_1);
LL_RCC_SetAHBPrescaler(LL_RCC_AHB_DIV_2);
LL_RCC_SetAPB1Prescaler(LL_RCC_APB1_DIV_2);
LL_RCC_SetAPB2Prescaler(LL_RCC_APB2_DIV_2);
LL_RCC_SetAPB3Prescaler(LL_RCC_APB3_DIV_2);
LL_RCC_SetAPB4Prescaler(LL_RCC_APB4_DIV_2);
/* Configure systick */
LL_Init1msTick(480000000);
LL_SYSTICK_SetClkSource(LL_SYSTICK_CLKSOURCE_HCLK);
LL_SetSystemCoreClock(480000000);
LL_RCC_SetUSARTClockSource(LL_RCC_USART234578_CLKSOURCE_PCLK1);
}