diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a0ebf94 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/nanopb"] + path = lib/nanopb + url = https://github.com/nanopb/nanopb.git diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..481fe99 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "stm32h7xx_hal_conf.h": "c" + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index cf3a5cf..e5ec203 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,62 +60,28 @@ set(linker_OPTS) # information to the project include("cmake_generated/cmake_generated.cmake") -# Link directories setup -# Must be before executable is added -link_directories(${CMAKE_PROJECT_NAME} ${link_DIRS}) -# Create an executable object type -add_executable(${CMAKE_PROJECT_NAME}) +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib/nanopb/extra) +find_package(Nanopb REQUIRED) +include_directories(${NANOPB_INCLUDE_DIRS}) +nanopb_generate_cpp(PROTO_SRCS PROTO_HDRS RELPATH proto proto/firmware.proto) +add_library(PROTOS ${PROTO_SRCS} ${PROTO_HDRS}) +target_include_directories(PROTOS PUBLIC ${NANOPB_INCLUDE_DIRS}) -# Add sources to executable -target_sources(${CMAKE_PROJECT_NAME} PUBLIC ${sources_SRCS}) -# Add include paths -target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE - ${include_DIRS} - $<$: ${include_c_DIRS}> - $<$: ${include_cxx_DIRS}> - $<$: ${include_asm_DIRS}> -) - -# Add project symbols (macros) -target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE - ${symbols_SYMB} - $<$: ${symbols_c_SYMB}> - $<$: ${symbols_cxx_SYMB}> - $<$: ${symbols_asm_SYMB}> - - # Configuration specific - $<$:DEBUG> - $<$: > -) - -# Add linked libraries -target_link_libraries(${CMAKE_PROJECT_NAME} ${link_LIBS}) - -# Compiler options -target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE +# these options need to be applied for the hole project +# so that all subprojects and libaries are compiled and compatable +add_compile_options( ${cpu_PARAMS} ${compiler_OPTS} -Wall -Wextra -Wpedantic -Wno-unused-parameter - $<$: > - $<$: - - # -Wno-volatile - # -Wold-style-cast - # -Wuseless-cast - # -Wsuggest-override - > - $<$:-x assembler-with-cpp -MMD -MP> - $<$:-Og -g3 -ggdb> - $<$:-Og -g0> + -flto ) -# Linker options -target_link_options(${CMAKE_PROJECT_NAME} PRIVATE +add_link_options( -T${linker_script_SRC} ${cpu_PARAMS} ${linker_OPTS} @@ -128,10 +94,67 @@ target_link_options(${CMAKE_PROJECT_NAME} PRIVATE -lstdc++ -lsupc++ -Wl,--end-group + -flto -Wl,-z,max-page-size=8 # Allow good software remapping across address space (with proper GCC section making) -Wl,--print-memory-usage ) +# Link directories setup +# Must be before executable is added +link_directories(${CMAKE_PROJECT_NAME} ${link_DIRS}) + +# Create an executable object type +add_executable(${CMAKE_PROJECT_NAME}) + +# Add sources to executable +target_sources(${CMAKE_PROJECT_NAME} PUBLIC ${sources_SRCS}) + +# Add include paths +# these options need to be applied for the hole project +include_directories( + ${include_DIRS} + $<$: ${include_c_DIRS}> + $<$: ${include_cxx_DIRS}> + $<$: ${include_asm_DIRS}> +) + +# Add project symbols (macros) +add_compile_definitions( + ${symbols_SYMB} + ${symbols_c_SYMB} + ${symbols_cxx_SYMB} + ${symbols_asm_SYMB} +) + +add_subdirectory("lib") +add_subdirectory("Application") + +# Add linked libraries +# target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC protobuf-nanopb-static) +target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC uart_driver) +target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC lwrb) +target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC PROTOS) +target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC Tasks) + +# Compiler options +target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE + $<$: > + $<$: + + # -Wno-volatile + # -Wold-style-cast + # -Wuseless-cast + # -Wsuggest-override + > + $<$:-x assembler-with-cpp -MMD -MP> + $<$:-Og -g3 -ggdb> + $<$:-O3 -g0> +) + +# Linker options +target_link_options(${CMAKE_PROJECT_NAME} PRIVATE +) + # Execute post-build to print size, generate hex and bin add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_SIZE} $ diff --git a/FATFS/Target/sd_diskio.c b/FATFS/Target/sd_diskio.c index 64bca10..67439ad 100644 --- a/FATFS/Target/sd_diskio.c +++ b/FATFS/Target/sd_diskio.c @@ -641,7 +641,7 @@ void BSP_SD_WriteCpltCallback(void) osMessagePut(SDQueueID, WRITE_CPLT_MSG, 0); #else const uint16_t msg = WRITE_CPLT_MSG; - osMessageQueuePut(SDQueueID, (const void *)&msg, NULL, 0); + osMessageQueuePut(SDQueueID, (const void *)&msg, 0, 0); #endif } @@ -660,7 +660,7 @@ void BSP_SD_ReadCpltCallback(void) osMessagePut(SDQueueID, READ_CPLT_MSG, 0); #else const uint16_t msg = READ_CPLT_MSG; - osMessageQueuePut(SDQueueID, (const void *)&msg, NULL, 0); + osMessageQueuePut(SDQueueID, (const void *)&msg, 0, 0); #endif } diff --git a/USB_DEVICE/App/usbd_cdc_if.c b/USB_DEVICE/App/usbd_cdc_if.c index 1d0c9b8..e637f8f 100644 --- a/USB_DEVICE/App/usbd_cdc_if.c +++ b/USB_DEVICE/App/usbd_cdc_if.c @@ -22,7 +22,7 @@ #include "usbd_cdc_if.h" /* USER CODE BEGIN INCLUDE */ - +#include "UsbDataHandler.h" /* USER CODE END INCLUDE */ /* Private typedef -----------------------------------------------------------*/ @@ -264,6 +264,7 @@ static int8_t CDC_Control_HS(uint8_t cmd, uint8_t* pbuf, uint16_t length) static int8_t CDC_Receive_HS(uint8_t* Buf, uint32_t *Len) { /* USER CODE BEGIN 11 */ + UsbDataHandler_RxCallback(Buf,*Len); USBD_CDC_SetRxBuffer(&hUsbDeviceHS, &Buf[0]); USBD_CDC_ReceivePacket(&hUsbDeviceHS); return (USBD_OK); diff --git a/cmake_generated/cmake_generated.cmake b/cmake_generated/cmake_generated.cmake index 77bcb44..c61de6b 100644 --- a/cmake_generated/cmake_generated.cmake +++ b/cmake_generated/cmake_generated.cmake @@ -23,10 +23,8 @@ set(sources_SRCS ${sources_SRCS} ${CMAKE_CURRENT_SOURCE_DIR}/Core/Src/crc.c ${CMAKE_CURRENT_SOURCE_DIR}/Core/Src/dma.c ${CMAKE_CURRENT_SOURCE_DIR}/Core/Src/fdcan.c - ${CMAKE_CURRENT_SOURCE_DIR}/Core/Src/freertos.c ${CMAKE_CURRENT_SOURCE_DIR}/Core/Src/gpio.c ${CMAKE_CURRENT_SOURCE_DIR}/Core/Src/i2c.c - ${CMAKE_CURRENT_SOURCE_DIR}/Core/Src/main.c ${CMAKE_CURRENT_SOURCE_DIR}/Core/Src/rng.c ${CMAKE_CURRENT_SOURCE_DIR}/Core/Src/rtc.c ${CMAKE_CURRENT_SOURCE_DIR}/Core/Src/sdmmc.c @@ -36,6 +34,8 @@ set(sources_SRCS ${sources_SRCS} ${CMAKE_CURRENT_SOURCE_DIR}/Core/Src/syscalls.c ${CMAKE_CURRENT_SOURCE_DIR}/Core/Src/sysmem.c ${CMAKE_CURRENT_SOURCE_DIR}/Core/Src/system_stm32h7xx.c + ${CMAKE_CURRENT_SOURCE_DIR}/Core/Src/freertos.c + ${CMAKE_CURRENT_SOURCE_DIR}/Core/Src/main.c ${CMAKE_CURRENT_SOURCE_DIR}/Core/Src/usart.c ${CMAKE_CURRENT_SOURCE_DIR}/Core/Startup/startup_stm32h723vgtx.s ${CMAKE_CURRENT_SOURCE_DIR}/Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal.c @@ -140,15 +140,3 @@ set(symbols_cxx_SYMB ${symbols_cxx_SYMB}) set(symbols_asm_SYMB ${symbols_asm_SYMB} "DEBUG" ) - -# Link directories -set(link_DIRS ${link_DIRS}) - -# Link libraries -set(link_LIBS ${link_LIBS}) - -# Compiler options -set(compiler_OPTS ${compiler_OPTS}) - -# Linker options -set(linker_OPTS ${linker_OPTS}) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 0000000..1933ce1 --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(lwrb) +add_subdirectory(uart_driver) +add_subdirectory(nanopb) \ No newline at end of file diff --git a/lib/lwrb/CMakeLists.txt b/lib/lwrb/CMakeLists.txt new file mode 100644 index 0000000..5b93656 --- /dev/null +++ b/lib/lwrb/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.12) + +project(lwrb C) + +add_library(${PROJECT_NAME} "") +target_sources(${PROJECT_NAME} + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/lwrb.c + INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/lwrb.h +) + +target_include_directories(${PROJECT_NAME} + INTERFACE + ${CMAKE_CURRENT_LIST_DIR} +) diff --git a/lib/lwrb/lwrb.c b/lib/lwrb/lwrb.c new file mode 100644 index 0000000..800486b --- /dev/null +++ b/lib/lwrb/lwrb.c @@ -0,0 +1,462 @@ +/** + * \file lwrb.c + * \brief Lightweight ring buffer + */ + +/* + * Copyright (c) 2020 Tilen MAJERLE + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE + * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * This file is part of LwRB - Lightweight ring buffer library. + * + * Author: Tilen MAJERLE + * Version: v2.0.0 + */ +#include "lwrb.h" + +/* Memory set and copy functions */ +#define BUF_MEMSET memset +#define BUF_MEMCPY memcpy + +#if LWRB_USE_MAGIC +#define BUF_IS_VALID(b) ((b) != NULL && (b)->magic1 == 0xDEADBEEF && (b)->magic2 == ~0xDEADBEEF && (b)->buff != NULL && (b)->size > 0) +#else +#define BUF_IS_VALID(b) ((b) != NULL && (b)->buff != NULL && (b)->size > 0) +#endif /* LWRB_USE_MAGIC */ +#define BUF_MIN(x, y) ((x) < (y) ? (x) : (y)) +#define BUF_MAX(x, y) ((x) > (y) ? (x) : (y)) +#define BUF_SEND_EVT(b, type, bp) do { if ((b)->evt_fn != NULL) { (b)->evt_fn((b), (type), (bp)); } } while (0) + +/** + * \brief Initialize buffer handle to default values with size and buffer data array + * \param[in] buff: Buffer handle + * \param[in] buffdata: Pointer to memory to use as buffer data + * \param[in] size: Size of `buffdata` in units of bytes + * Maximum number of bytes buffer can hold is `size - 1` + * \return `1` on success, `0` otherwise + */ +uint8_t +lwrb_init(LWRB_VOLATILE lwrb_t* buff, void* buffdata, size_t size) { + if (buff == NULL || buffdata == NULL || size == 0) { + return 0; + } + + BUF_MEMSET((void*)buff, 0x00, sizeof(*buff)); + + buff->size = size; + buff->buff = buffdata; + +#if LWRB_USE_MAGIC + buff->magic1 = 0xDEADBEEF; + buff->magic2 = ~0xDEADBEEF; +#endif /* LWRB_USE_MAGIC */ + + return 1; +} + +/** + * \brief Check if buff is initialized and ready to use + * \param[in] buff: Buffer handle + * \return `1` if ready, `0` otherwise + */ +uint8_t +lwrb_is_ready(LWRB_VOLATILE lwrb_t* buff) { + return BUF_IS_VALID(buff); +} + +/** + * \brief Free buffer memory + * \note Since implementation does not use dynamic allocation, + * it just sets buffer handle to `NULL` + * \param[in] buff: Buffer handle + */ +void +lwrb_free(LWRB_VOLATILE lwrb_t* buff) { + if (BUF_IS_VALID(buff)) { + buff->buff = NULL; + } +} + +/** + * \brief Set event function callback for different buffer operations + * \param[in] buff: Buffer handle + * \param[in] evt_fn: Callback function + */ +void +lwrb_set_evt_fn(LWRB_VOLATILE lwrb_t* buff, lwrb_evt_fn evt_fn) { + if (BUF_IS_VALID(buff)) { + buff->evt_fn = evt_fn; + } +} + +/** + * \brief Write data to buffer. + * Copies data from `data` array to buffer and marks buffer as full for maximum `btw` number of bytes + * + * \param[in] buff: Buffer handle + * \param[in] data: Pointer to data to write into buffer + * \param[in] btw: Number of bytes to write + * \return Number of bytes written to buffer. + * When returned value is less than `btw`, there was no enough memory available + * to copy full data array + */ +size_t +lwrb_write(LWRB_VOLATILE lwrb_t* buff, const void* data, size_t btw) { + size_t tocopy, free; + const uint8_t* d = data; + + if (!BUF_IS_VALID(buff) || data == NULL || btw == 0) { + return 0; + } + + /* Calculate maximum number of bytes available to write */ + free = lwrb_get_free(buff); + btw = BUF_MIN(free, btw); + if (btw == 0) { + return 0; + } + + /* Step 1: Write data to linear part of buffer */ + tocopy = BUF_MIN(buff->size - buff->w, btw); + BUF_MEMCPY(&buff->buff[buff->w], d, tocopy); + buff->w += tocopy; + btw -= tocopy; + + /* Step 2: Write data to beginning of buffer (overflow part) */ + if (btw > 0) { + BUF_MEMCPY(buff->buff, &d[tocopy], btw); + buff->w = btw; + } + + /* Step 3: Check end of buffer */ + if (buff->w >= buff->size) { + buff->w = 0; + } + BUF_SEND_EVT(buff, LWRB_EVT_WRITE, tocopy + btw); + return tocopy + btw; +} + +/** + * \brief Read data from buffer. + * Copies data from buffer to `data` array and marks buffer as free for maximum `btr` number of bytes + * + * \param[in] buff: Buffer handle + * \param[out] data: Pointer to output memory to copy buffer data to + * \param[in] btr: Number of bytes to read + * \return Number of bytes read and copied to data array + */ +size_t +lwrb_read(LWRB_VOLATILE lwrb_t* buff, void* data, size_t btr) { + size_t tocopy, full; + uint8_t* d = data; + + if (!BUF_IS_VALID(buff) || data == NULL || btr == 0) { + return 0; + } + + /* Calculate maximum number of bytes available to read */ + full = lwrb_get_full(buff); + btr = BUF_MIN(full, btr); + if (btr == 0) { + return 0; + } + + /* Step 1: Read data from linear part of buffer */ + tocopy = BUF_MIN(buff->size - buff->r, btr); + BUF_MEMCPY(d, &buff->buff[buff->r], tocopy); + buff->r += tocopy; + btr -= tocopy; + + /* Step 2: Read data from beginning of buffer (overflow part) */ + if (btr > 0) { + BUF_MEMCPY(&d[tocopy], buff->buff, btr); + buff->r = btr; + } + + /* Step 3: Check end of buffer */ + if (buff->r >= buff->size) { + buff->r = 0; + } + BUF_SEND_EVT(buff, LWRB_EVT_READ, tocopy + btr); + return tocopy + btr; +} + +/** + * \brief Read from buffer without changing read pointer (peek only) + * \param[in] buff: Buffer handle + * \param[in] skip_count: Number of bytes to skip before reading data + * \param[out] data: Pointer to output memory to copy buffer data to + * \param[in] btp: Number of bytes to peek + * \return Number of bytes peeked and written to output array + */ +size_t +lwrb_peek(LWRB_VOLATILE lwrb_t* buff, size_t skip_count, void* data, size_t btp) { + size_t full, tocopy, r; + uint8_t* d = data; + + if (!BUF_IS_VALID(buff) || data == NULL || btp == 0) { + return 0; + } + + r = buff->r; + + /* Calculate maximum number of bytes available to read */ + full = lwrb_get_full(buff); + + /* Skip beginning of buffer */ + if (skip_count >= full) { + return 0; + } + r += skip_count; + full -= skip_count; + if (r >= buff->size) { + r -= buff->size; + } + + /* Check maximum number of bytes available to read after skip */ + btp = BUF_MIN(full, btp); + if (btp == 0) { + return 0; + } + + /* Step 1: Read data from linear part of buffer */ + tocopy = BUF_MIN(buff->size - r, btp); + BUF_MEMCPY(d, &buff->buff[r], tocopy); + btp -= tocopy; + + /* Step 2: Read data from beginning of buffer (overflow part) */ + if (btp > 0) { + BUF_MEMCPY(&d[tocopy], buff->buff, btp); + } + return tocopy + btp; +} + +/** + * \brief Get available size in buffer for write operation + * \param[in] buff: Buffer handle + * \return Number of free bytes in memory + */ +size_t +lwrb_get_free(LWRB_VOLATILE lwrb_t* buff) { + size_t size, w, r; + + if (!BUF_IS_VALID(buff)) { + return 0; + } + + /* Use temporary values in case they are changed during operations */ + w = buff->w; + r = buff->r; + if (w == r) { + size = buff->size; + } else if (r > w) { + size = r - w; + } else { + size = buff->size - (w - r); + } + + /* Buffer free size is always 1 less than actual size */ + return size - 1; +} + +/** + * \brief Get number of bytes currently available in buffer + * \param[in] buff: Buffer handle + * \return Number of bytes ready to be read + */ +size_t +lwrb_get_full(LWRB_VOLATILE lwrb_t* buff) { + size_t w, r, size; + + if (!BUF_IS_VALID(buff)) { + return 0; + } + + /* Use temporary values in case they are changed during operations */ + w = buff->w; + r = buff->r; + if (w == r) { + size = 0; + } else if (w > r) { + size = w - r; + } else { + size = buff->size - (r - w); + } + return size; +} + +/** + * \brief Resets buffer to default values. Buffer size is not modified + * \param[in] buff: Buffer handle + */ +void +lwrb_reset(LWRB_VOLATILE lwrb_t* buff) { + if (BUF_IS_VALID(buff)) { + buff->w = 0; + buff->r = 0; + BUF_SEND_EVT(buff, LWRB_EVT_RESET, 0); + } +} + +/** + * \brief Get linear address for buffer for fast read + * \param[in] buff: Buffer handle + * \return Linear buffer start address + */ +void* +lwrb_get_linear_block_read_address(LWRB_VOLATILE lwrb_t* buff) { + if (!BUF_IS_VALID(buff)) { + return NULL; + } + return &buff->buff[buff->r]; +} + +/** + * \brief Get length of linear block address before it overflows for read operation + * \param[in] buff: Buffer handle + * \return Linear buffer size in units of bytes for read operation + */ +size_t +lwrb_get_linear_block_read_length(LWRB_VOLATILE lwrb_t* buff) { + size_t w, r, len; + + if (!BUF_IS_VALID(buff)) { + return 0; + } + + /* Use temporary values in case they are changed during operations */ + w = buff->w; + r = buff->r; + if (w > r) { + len = w - r; + } else if (r > w) { + len = buff->size - r; + } else { + len = 0; + } + return len; +} + +/** + * \brief Skip (ignore; advance read pointer) buffer data + * Marks data as read in the buffer and increases free memory for up to `len` bytes + * + * \note Useful at the end of streaming transfer such as DMA + * \param[in] buff: Buffer handle + * \param[in] len: Number of bytes to skip and mark as read + * \return Number of bytes skipped + */ +size_t +lwrb_skip(LWRB_VOLATILE lwrb_t* buff, size_t len) { + size_t full; + + if (!BUF_IS_VALID(buff) || len == 0) { + return 0; + } + + full = lwrb_get_full(buff); + len = BUF_MIN(len, full); + buff->r += len; + if (buff->r >= buff->size) { + buff->r -= buff->size; + } + BUF_SEND_EVT(buff, LWRB_EVT_READ, len); + return len; +} + +/** + * \brief Get linear address for buffer for fast read + * \param[in] buff: Buffer handle + * \return Linear buffer start address + */ +void* +lwrb_get_linear_block_write_address(LWRB_VOLATILE lwrb_t* buff) { + if (!BUF_IS_VALID(buff)) { + return NULL; + } + return &buff->buff[buff->w]; +} + +/** + * \brief Get length of linear block address before it overflows for write operation + * \param[in] buff: Buffer handle + * \return Linear buffer size in units of bytes for write operation + */ +size_t +lwrb_get_linear_block_write_length(LWRB_VOLATILE lwrb_t* buff) { + size_t w, r, len; + + if (!BUF_IS_VALID(buff)) { + return 0; + } + + /* Use temporary values in case they are changed during operations */ + w = buff->w; + r = buff->r; + if (w >= r) { + len = buff->size - w; + /* + * When read pointer is 0, + * maximal length is one less as if too many bytes + * are written, buffer would be considered empty again (r == w) + */ + if (r == 0) { + /* + * Cannot overflow: + * - If r is not 0, statement does not get called + * - buff->size cannot be 0 and if r is 0, len is greater 0 + */ + --len; + } + } else { + len = r - w - 1; + } + return len; +} + +/** + * \brief Advance write pointer in the buffer. + * Similar to skip function but modifies write pointer instead of read + * + * \note Useful when hardware is writing to buffer and application needs to increase number + * of bytes written to buffer by hardware + * \param[in] buff: Buffer handle + * \param[in] len: Number of bytes to advance + * \return Number of bytes advanced for write operation + */ +size_t +lwrb_advance(LWRB_VOLATILE lwrb_t* buff, size_t len) { + size_t free; + + if (!BUF_IS_VALID(buff) || len == 0) { + return 0; + } + + free = lwrb_get_free(buff); + len = BUF_MIN(len, free); + buff->w += len; + if (buff->w >= buff->size) { + buff->w -= buff->size; + } + BUF_SEND_EVT(buff, LWRB_EVT_WRITE, len); + return len; +} diff --git a/lib/lwrb/lwrb.h b/lib/lwrb/lwrb.h new file mode 100644 index 0000000..db04782 --- /dev/null +++ b/lib/lwrb/lwrb.h @@ -0,0 +1,135 @@ +/** + * \file lwrb.h + * \brief LwRB - Lightweight ring buffer + */ + +/* + * Copyright (c) 2020 Tilen MAJERLE + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE + * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * This file is part of LwRB - Lightweight ring buffer library. + * + * Author: Tilen MAJERLE + * Version: v2.0.0 + */ +#ifndef LWRB_HDR_H +#define LWRB_HDR_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * \defgroup LWRB Lightweight ring buffer manager + * \brief Lightweight ring buffer manager + * \{ + */ + +/** + * \brief Enable buffer structure pointer parameter as volatile + * To use this feature, uncomment keyword below + */ +#define LWRB_VOLATILE /* volatile */ + +/** + * \brief Adds 2 magic words to make sure if memory is corrupted + * application can detect wrong data pointer and maximum size + */ +#define LWRB_USE_MAGIC 1 + +/** + * \brief Event type for buffer operations + */ +typedef enum { + LWRB_EVT_READ, /*!< Read event */ + LWRB_EVT_WRITE, /*!< Write event */ + LWRB_EVT_RESET, /*!< Reset event */ +} lwrb_evt_type_t; + +/** + * \brief Buffer structure forward declaration + */ +struct lwrb; + +/** + * \brief Event callback function type + * \param[in] buff: Buffer handle for event + * \param[in] evt: Event type + * \param[in] bp: Number of bytes written or read (when used), depends on event type + */ +typedef void (*lwrb_evt_fn)(LWRB_VOLATILE struct lwrb* buff, lwrb_evt_type_t evt, size_t bp); + +/** + * \brief Buffer structure + */ +typedef struct lwrb { +#if LWRB_USE_MAGIC + uint32_t magic1; /*!< Magic 1 word */ +#endif /* LWRB_USE_MAGIC */ + uint8_t* buff; /*!< Pointer to buffer data. + Buffer is considered initialized when `buff != NULL` and `size > 0` */ + size_t size; /*!< Size of buffer data. Size of actual buffer is `1` byte less than value holds */ + size_t r; /*!< Next read pointer. Buffer is considered empty when `r == w` and full when `w == r - 1` */ + size_t w; /*!< Next write pointer. Buffer is considered empty when `r == w` and full when `w == r - 1` */ + lwrb_evt_fn evt_fn; /*!< Pointer to event callback function */ +#if LWRB_USE_MAGIC + uint32_t magic2; /*!< Magic 2 word */ +#endif /* LWRB_USE_MAGIC */ +} lwrb_t; + +uint8_t lwrb_init(LWRB_VOLATILE lwrb_t* buff, void* buffdata, size_t size); +uint8_t lwrb_is_ready(LWRB_VOLATILE lwrb_t* buff); +void lwrb_free(LWRB_VOLATILE lwrb_t* buff); +void lwrb_reset(LWRB_VOLATILE lwrb_t* buff); +void lwrb_set_evt_fn(LWRB_VOLATILE lwrb_t* buff, lwrb_evt_fn fn); + +/* Read/Write functions */ +size_t lwrb_write(LWRB_VOLATILE lwrb_t* buff, const void* data, size_t btw); +size_t lwrb_read(LWRB_VOLATILE lwrb_t* buff, void* data, size_t btr); +size_t lwrb_peek(LWRB_VOLATILE lwrb_t* buff, size_t skip_count, void* data, size_t btp); + +/* Buffer size information */ +size_t lwrb_get_free(LWRB_VOLATILE lwrb_t* buff); +size_t lwrb_get_full(LWRB_VOLATILE lwrb_t* buff); + +/* Read data block management */ +void* lwrb_get_linear_block_read_address(LWRB_VOLATILE lwrb_t* buff); +size_t lwrb_get_linear_block_read_length(LWRB_VOLATILE lwrb_t* buff); +size_t lwrb_skip(LWRB_VOLATILE lwrb_t* buff, size_t len); + +/* Write data block management */ +void* lwrb_get_linear_block_write_address(LWRB_VOLATILE lwrb_t* buff); +size_t lwrb_get_linear_block_write_length(LWRB_VOLATILE lwrb_t* buff); +size_t lwrb_advance(LWRB_VOLATILE lwrb_t* buff, size_t len); + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* LWRB_HDR_H */ diff --git a/lib/nanopb b/lib/nanopb new file mode 160000 index 0000000..1f0c2e1 --- /dev/null +++ b/lib/nanopb @@ -0,0 +1 @@ +Subproject commit 1f0c2e19c661f18dd88428858b8e965a26589e03 diff --git a/lib/uart_driver/AsyncComm.c b/lib/uart_driver/AsyncComm.c new file mode 100644 index 0000000..957a8d9 --- /dev/null +++ b/lib/uart_driver/AsyncComm.c @@ -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! */ + } +} diff --git a/lib/uart_driver/AsyncComm.h b/lib/uart_driver/AsyncComm.h new file mode 100644 index 0000000..b55d278 --- /dev/null +++ b/lib/uart_driver/AsyncComm.h @@ -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 + +#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 diff --git a/lib/uart_driver/AsyncFrames.c b/lib/uart_driver/AsyncFrames.c new file mode 100644 index 0000000..696484f --- /dev/null +++ b/lib/uart_driver/AsyncFrames.c @@ -0,0 +1,5 @@ +// +// Created by oliver on 12/8/23. +// + +#include "AsyncFrames.h" diff --git a/lib/uart_driver/AsyncFrames.h b/lib/uart_driver/AsyncFrames.h new file mode 100644 index 0000000..add52f7 --- /dev/null +++ b/lib/uart_driver/AsyncFrames.h @@ -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 diff --git a/lib/uart_driver/CMakeLists.txt b/lib/uart_driver/CMakeLists.txt new file mode 100644 index 0000000..333c156 --- /dev/null +++ b/lib/uart_driver/CMakeLists.txt @@ -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) diff --git a/lib/uart_driver/main.c b/lib/uart_driver/main.c new file mode 100644 index 0000000..d0f27ba --- /dev/null +++ b/lib/uart_driver/main.c @@ -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 +#include +#include +#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); +}