diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c0daed..257ce57 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,29 +15,32 @@ project(embedDIP DESCRIPTION "Portable embedded digital image processing library" ) -set(EMBEDDIP_TARGET_BOARD "" CACHE STRING "Target board (required): STM32F7 or ESP32") -set_property(CACHE EMBEDDIP_TARGET_BOARD PROPERTY STRINGS "STM32F7" "ESP32") +set(EMBEDDIP_TARGET_BOARD "" CACHE STRING "Target board (required): STM32F7, STM32H7S, ESP32, or HOST") +set_property(CACHE EMBEDDIP_TARGET_BOARD PROPERTY STRINGS "STM32F7" "STM32H7S" "ESP32" "HOST") -set(EMBEDDIP_ARCH "" CACHE STRING "Architecture family (required): ARM or XTENSA") -set_property(CACHE EMBEDDIP_ARCH PROPERTY STRINGS "ARM" "XTENSA") +set(EMBEDDIP_ARCH "" CACHE STRING "Architecture family (required): ARM, XTENSA, or HOST") +set_property(CACHE EMBEDDIP_ARCH PROPERTY STRINGS "ARM" "XTENSA" "HOST") -set(EMBEDDIP_CPU "" CACHE STRING "CPU variant (required): CORTEX_M7, LX6, LX7") -set_property(CACHE EMBEDDIP_CPU PROPERTY STRINGS "CORTEX_M7" "LX6" "LX7") +set(EMBEDDIP_CPU "" CACHE STRING "CPU variant (required): CORTEX_M7, LX6, LX7, NATIVE") +set_property(CACHE EMBEDDIP_CPU PROPERTY STRINGS "CORTEX_M7" "LX6" "LX7" "NATIVE") + +set(EMBEDDIP_STM32CUBE_H7RS_ROOT "" CACHE PATH "Path to the STM32CubeH7RS SDK root") option(EMBEDDIP_ENABLE_IMAGE_PROCESSING "Enable image processing modules" ON) option(EMBEDDIP_ENABLE_CAMERA_INPUT "Enable camera input interfaces" ON) option(EMBEDDIP_ENABLE_DISPLAY_OUTPUT "Enable display output interfaces" ON) +option(EMBEDDIP_BUILD_TESTS "Build CTest safety-net tests" OFF) if(EMBEDDIP_TARGET_BOARD STREQUAL "") - message(FATAL_ERROR "EMBEDDIP_TARGET_BOARD is required. Supported values: STM32F7, ESP32") + message(FATAL_ERROR "EMBEDDIP_TARGET_BOARD is required. Supported values: STM32F7, STM32H7S, ESP32, HOST") endif() if(EMBEDDIP_ARCH STREQUAL "") - message(FATAL_ERROR "EMBEDDIP_ARCH is required. Supported values: ARM, XTENSA") + message(FATAL_ERROR "EMBEDDIP_ARCH is required. Supported values: ARM, XTENSA, HOST") endif() if(EMBEDDIP_CPU STREQUAL "") - message(FATAL_ERROR "EMBEDDIP_CPU is required. Supported values: CORTEX_M7, LX6, LX7") + message(FATAL_ERROR "EMBEDDIP_CPU is required. Supported values: CORTEX_M7, LX6, LX7, NATIVE") endif() # Explicit compatibility matrix between board, architecture family and CPU @@ -46,16 +49,24 @@ if(EMBEDDIP_TARGET_BOARD STREQUAL "STM32F7") if(EMBEDDIP_ARCH STREQUAL "ARM" AND EMBEDDIP_CPU STREQUAL "CORTEX_M7") set(_embeddip_pair_valid TRUE) endif() +elseif(EMBEDDIP_TARGET_BOARD STREQUAL "STM32H7S") + if(EMBEDDIP_ARCH STREQUAL "ARM" AND EMBEDDIP_CPU STREQUAL "CORTEX_M7") + set(_embeddip_pair_valid TRUE) + endif() elseif(EMBEDDIP_TARGET_BOARD STREQUAL "ESP32") if(EMBEDDIP_ARCH STREQUAL "XTENSA" AND (EMBEDDIP_CPU STREQUAL "LX6" OR EMBEDDIP_CPU STREQUAL "LX7")) set(_embeddip_pair_valid TRUE) endif() +elseif(EMBEDDIP_TARGET_BOARD STREQUAL "HOST") + if(EMBEDDIP_ARCH STREQUAL "HOST" AND EMBEDDIP_CPU STREQUAL "NATIVE") + set(_embeddip_pair_valid TRUE) + endif() endif() if(NOT _embeddip_pair_valid) message(FATAL_ERROR "Invalid board/arch/cpu combination: ${EMBEDDIP_TARGET_BOARD} + ${EMBEDDIP_ARCH} + ${EMBEDDIP_CPU}. " - "Supported: STM32F7+ARM+CORTEX_M7, ESP32+XTENSA+LX6, ESP32+XTENSA+LX7" + "Supported: STM32F7+ARM+CORTEX_M7, STM32H7S+ARM+CORTEX_M7, ESP32+XTENSA+LX6, ESP32+XTENSA+LX7, HOST+HOST+NATIVE" ) endif() @@ -68,6 +79,7 @@ set(CORE_SOURCES core/error.c core/error.h core/memory_manager.h + core/memory_regions.c core/image.h ) @@ -257,6 +269,11 @@ endif() # === Link Libraries === target_link_libraries(embedDIP PUBLIC m) +if(EMBEDDIP_BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +endif() + # === Install Targets === include(GNUInstallDirs) diff --git a/arch/arm/cmsis_fft.c b/arch/arm/cmsis_fft.c new file mode 100644 index 0000000..fb68374 --- /dev/null +++ b/arch/arm/cmsis_fft.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 EmbedDIP + +#include + +#if defined(EMBED_DIP_ARCH_ARM) + +#include +#include "arm_math.h" + +static arm_cfft_instance_f32 fft_instance; +static int fft_size = -1; + +embeddip_status_t embeddip_fft_backend_init(int n) +{ + if (fft_size == n) { + return EMBEDDIP_OK; + } + + if (arm_cfft_init_f32(&fft_instance, (uint16_t)n) != ARM_MATH_SUCCESS) { + return EMBEDDIP_ERROR_INVALID_SIZE; + } + + fft_size = n; + return EMBEDDIP_OK; +} + +embeddip_status_t embeddip_fft_backend_forward_1d(float *data, int n) +{ + if (!data) { + return EMBEDDIP_ERROR_NULL_PTR; + } + if (n != fft_size) { + return EMBEDDIP_ERROR_INVALID_SIZE; + } + + arm_cfft_f32(&fft_instance, data, 0, 1); + return EMBEDDIP_OK; +} + +embeddip_status_t embeddip_fft_backend_inverse_1d(float *data, int n) +{ + if (!data) { + return EMBEDDIP_ERROR_NULL_PTR; + } + if (n != fft_size) { + return EMBEDDIP_ERROR_INVALID_SIZE; + } + + arm_cfft_f32(&fft_instance, data, 1, 1); + return EMBEDDIP_OK; +} + +#endif diff --git a/arch/arm/dwt_timer.c b/arch/arm/dwt_timer.c new file mode 100644 index 0000000..80ff206 --- /dev/null +++ b/arch/arm/dwt_timer.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 EmbedDIP + +#include +#include + +#if defined(EMBED_DIP_ARCH_ARM) + +/* Include the ST device header, not the bare CMSIS core header: the device + * header defines IRQn_Type, __NVIC_PRIO_BITS, and __DSP_PRESENT before pulling + * in the matching core_cmXX.h. Including core_cmXX.h directly leaves those + * undefined and fails to compile. */ +#if defined(EMBED_DIP_BOARD_STM32F7) +#include "stm32f7xx.h" +#elif defined(EMBED_DIP_BOARD_STM32N6) +#include "stm32n6xx.h" +#elif defined(EMBED_DIP_CPU_CORTEX_M7) +#include "core_cm7.h" +#elif defined(EMBED_DIP_CPU_CORTEX_M55) +#include "core_cm55.h" +#endif + +void tic(void) { + CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; + DWT->CYCCNT = 0u; + DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; +} + +uint32_t toc(void) { + DWT->CTRL &= ~DWT_CTRL_CYCCNTENA_Msk; + return DWT->CYCCNT; +} + +#endif diff --git a/arch/host/arch_profile.cmake b/arch/host/arch_profile.cmake new file mode 100644 index 0000000..c0f9caf --- /dev/null +++ b/arch/host/arch_profile.cmake @@ -0,0 +1,13 @@ +# Architecture profile: native host + +set(EMBEDDIP_ARCH_SOURCES + arch/host/host_timer.c + arch/host/host_fft.c +) + +set(EMBEDDIP_ARCH_DEFINES + EMBED_DIP_ARCH_HOST=1 + EMBED_DIP_CPU_NATIVE=1 +) + +set(EMBEDDIP_ARCH_COMPILE_OPTIONS) diff --git a/arch/host/host_fft.c b/arch/host/host_fft.c new file mode 100644 index 0000000..7a56ef2 --- /dev/null +++ b/arch/host/host_fft.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 EmbedDIP + +#include + +#if defined(EMBED_DIP_ARCH_HOST) + +#include +#include + +#include + +/* + * Portable host FFT backend: naive O(n^2) DFT over interleaved complex floats + * (data[2*i] = real, data[2*i+1] = imag). Matches the CMSIS-DSP contract, + * including the 1/n scaling applied on the inverse transform. This exists so + * the C++ Image wrapper links and runs on host; it is not tuned for speed. + * ponytail: O(n^2) DFT, swap for an FFT if host FFT throughput ever matters. + */ + +static int host_fft_size = -1; + +embeddip_status_t embeddip_fft_backend_init(int n) +{ + if (n <= 0) { + return EMBEDDIP_ERROR_INVALID_SIZE; + } + host_fft_size = n; + return EMBEDDIP_OK; +} + +static embeddip_status_t host_dft(float *data, int n, int inverse) +{ + const double sign = inverse ? 1.0 : -1.0; + const double two_pi = 6.28318530717958647692; + int k; + + if (data == NULL) { + return EMBEDDIP_ERROR_NULL_PTR; + } + if (n != host_fft_size) { + return EMBEDDIP_ERROR_INVALID_SIZE; + } + + { + /* O(n) stack scratch, VLA; host test sizes only. */ + double out_re[n]; + double out_im[n]; + int j; + + for (k = 0; k < n; ++k) { + double sum_re = 0.0; + double sum_im = 0.0; + for (j = 0; j < n; ++j) { + double angle = sign * two_pi * (double)k * (double)j / (double)n; + double c = cos(angle); + double s = sin(angle); + double in_re = (double)data[2 * j]; + double in_im = (double)data[2 * j + 1]; + sum_re += in_re * c - in_im * s; + sum_im += in_re * s + in_im * c; + } + out_re[k] = sum_re; + out_im[k] = sum_im; + } + for (k = 0; k < n; ++k) { + if (inverse) { + out_re[k] /= (double)n; + out_im[k] /= (double)n; + } + data[2 * k] = (float)out_re[k]; + data[2 * k + 1] = (float)out_im[k]; + } + } + return EMBEDDIP_OK; +} + +embeddip_status_t embeddip_fft_backend_forward_1d(float *data, int n) +{ + return host_dft(data, n, 0); +} + +embeddip_status_t embeddip_fft_backend_inverse_1d(float *data, int n) +{ + return host_dft(data, n, 1); +} + +#endif diff --git a/arch/host/host_timer.c b/arch/host/host_timer.c new file mode 100644 index 0000000..5962ae1 --- /dev/null +++ b/arch/host/host_timer.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 EmbedDIP + +#include + +#include +#include +#include + +static struct timespec start_time; +static bool timer_started; + +void tic(void) +{ + (void)clock_gettime(CLOCK_MONOTONIC, &start_time); + timer_started = true; +} + +uint32_t toc(void) +{ + struct timespec end_time; + uint64_t elapsed_ns; + + if (!timer_started || clock_gettime(CLOCK_MONOTONIC, &end_time) != 0) { + return 0u; + } + + elapsed_ns = (uint64_t)(end_time.tv_sec - start_time.tv_sec) * UINT64_C(1000000000); + elapsed_ns += (uint64_t)(end_time.tv_nsec - start_time.tv_nsec); + return (uint32_t)elapsed_ns; +} diff --git a/board/common.c b/board/common.c old mode 100755 new mode 100644 index a91c11f..92e320d --- a/board/common.c +++ b/board/common.c @@ -309,6 +309,97 @@ embeddip_status_t createChalsComplex(Image *inImg, uint8_t numChals) return EMBEDDIP_OK; } +static int image_view_format_depth_is_valid(ImageFormat format, ImageDepth depth) +{ + switch (format) { + case IMAGE_FORMAT_GRAYSCALE: + case IMAGE_FORMAT_MASK: + return depth == IMAGE_DEPTH_U8; + case IMAGE_FORMAT_RGB565: + return depth == IMAGE_DEPTH_U16; + case IMAGE_FORMAT_RGB888: + case IMAGE_FORMAT_YUV: + case IMAGE_FORMAT_HSI: + return depth == IMAGE_DEPTH_U24; + default: + return 0; + } +} + +embeddip_status_t image_view_from_buffer(uint8_t *pixels, + uint32_t width, + uint32_t height, + uint32_t row_stride_bytes, + ImageFormat format, + ImageDepth depth, + embeddip_memory_region_t region, + uint32_t flags, + ImageView *out_view) +{ + const uint8_t bytes_per_pixel = image_pixel_size_bytes(format, depth); + uint32_t minimum_row_stride; + + if (out_view == NULL || pixels == NULL) { + return EMBEDDIP_ERROR_NULL_PTR; + } + if (width == 0u || height == 0u) { + return EMBEDDIP_ERROR_INVALID_SIZE; + } + if (!image_view_format_depth_is_valid(format, depth) || bytes_per_pixel == 0u) { + return EMBEDDIP_ERROR_INVALID_FORMAT; + } + if (width > UINT32_MAX / bytes_per_pixel) { + return EMBEDDIP_ERROR_INVALID_SIZE; + } + + minimum_row_stride = width * bytes_per_pixel; + if (row_stride_bytes < minimum_row_stride) { + return EMBEDDIP_ERROR_INVALID_SIZE; + } + + out_view->pixels = pixels; + out_view->width = width; + out_view->height = height; + out_view->row_stride_bytes = row_stride_bytes; + out_view->format = format; + out_view->depth = depth; + out_view->region = region; + out_view->flags = flags; + return EMBEDDIP_OK; +} + +embeddip_status_t image_view_from_image(const Image *image, ImageView *out_view) +{ + const uint8_t bytes_per_pixel = + image == NULL ? 0u : image_pixel_size_bytes(image->format, image->depth); + + if (image == NULL || out_view == NULL) { + return EMBEDDIP_ERROR_NULL_PTR; + } + if (bytes_per_pixel != 0u && image->width > UINT32_MAX / bytes_per_pixel) { + return EMBEDDIP_ERROR_INVALID_SIZE; + } + + return image_view_from_buffer((uint8_t *)image->pixels, + image->width, + image->height, + image->width * bytes_per_pixel, + image->format, + image->depth, + EMBEDDIP_MEMORY_REGION_DEFAULT, + EMBEDDIP_BUFFER_CPU_READ | EMBEDDIP_BUFFER_CPU_WRITE, + out_view); +} + +uint8_t *image_view_row(const ImageView *view, uint32_t y) +{ + if (view == NULL || y >= view->height) { + return NULL; + } + + return view->pixels + ((size_t)y * view->row_stride_bytes); +} + /* ============================================================================ * Legacy/Deprecated Wrappers (for backward compatibility) * ========================================================================== */ diff --git a/board/host/board_host_memory.c b/board/host/board_host_memory.c new file mode 100644 index 0000000..b342b6f --- /dev/null +++ b/board/host/board_host_memory.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 EmbedDIP + +#if !defined(_POSIX_C_SOURCE) + #define _POSIX_C_SOURCE 200112L +#endif + +#include +#include + +#include + +#define EMBEDDIP_HOST_CACHE_LINE_BYTES 32u + +static uintptr_t last_cache_range_start; +static size_t last_cache_range_size; + +static void memory_test_record_cache_range(const void *address, size_t size) +{ + last_cache_range_start = (uintptr_t)address; + last_cache_range_size = size; +} + +void memory_init(uintptr_t ignored) +{ + (void)ignored; +} + +void *memory_alloc(size_t size) +{ + return size == 0u ? NULL : malloc(size); +} + +void memory_free(void *ptr) +{ + free(ptr); +} + +void *memory_realloc(void *ptr, size_t size) +{ + if (size == 0u) { + free(ptr); + return NULL; + } + + return realloc(ptr, size); +} + +void *embeddip_board_alloc_region(embeddip_memory_region_t region, size_t size, size_t alignment) +{ + void *memory = NULL; + + if (size == 0u || region == EMBEDDIP_MEMORY_REGION_EXTERNAL_FLASH) { + return NULL; + } + if (region != EMBEDDIP_MEMORY_REGION_DEFAULT && region != EMBEDDIP_MEMORY_REGION_FAST_SRAM && + region != EMBEDDIP_MEMORY_REGION_DMA && region != EMBEDDIP_MEMORY_REGION_PSRAM) { + return NULL; + } + if (alignment <= alignof(max_align_t)) { + return malloc(size); + } + if (posix_memalign(&memory, alignment, size) != 0) { + return NULL; + } + + return memory; +} + +embeddip_status_t embeddip_board_cache_clean(const void *address, size_t size) +{ + memory_test_record_cache_range(address, size); + return EMBEDDIP_OK; +} + +embeddip_status_t embeddip_board_cache_invalidate(const void *address, size_t size) +{ + memory_test_record_cache_range(address, size); + return EMBEDDIP_OK; +} + +uintptr_t memory_test_last_cache_range_start(void) +{ + return last_cache_range_start; +} + +size_t memory_test_last_cache_range_size(void) +{ + return last_cache_range_size; +} diff --git a/board/host/board_profile.cmake b/board/host/board_profile.cmake new file mode 100644 index 0000000..cdcb877 --- /dev/null +++ b/board/host/board_profile.cmake @@ -0,0 +1,12 @@ +# Board profile: native host + +set(EMBEDDIP_BOARD_SOURCES + ${BOARD_COMMON_SOURCES} + board/host/board_host_memory.c +) + +set(EMBEDDIP_DEVICE_SOURCES) + +set(EMBEDDIP_BOARD_DEFINES + EMBED_DIP_BOARD_HOST=1 +) diff --git a/board/stm32h7s/board_profile.cmake b/board/stm32h7s/board_profile.cmake new file mode 100644 index 0000000..845d81c --- /dev/null +++ b/board/stm32h7s/board_profile.cmake @@ -0,0 +1,32 @@ +# Board profile: STM32H7S78-DK + +if(NOT IS_DIRECTORY "${EMBEDDIP_STM32CUBE_H7RS_ROOT}") + message(FATAL_ERROR + "EMBEDDIP_STM32CUBE_H7RS_ROOT must name an existing STM32CubeH7RS SDK directory: " + "'${EMBEDDIP_STM32CUBE_H7RS_ROOT}'") +endif() + +set(EMBEDDIP_BOARD_SOURCES + ${BOARD_COMMON_SOURCES} + board/stm32h7s/board_stm32h7s_memory.c +) + +set(EMBEDDIP_DEVICE_SOURCES + ${DEVICE_COMMON_SOURCES} + device/display/stm32h7s_rk050hr18.c + device/serial/stm32h7s_uart.c +) + +set(EMBEDDIP_BOARD_DEFINES + EMBED_DIP_BOARD_STM32H7S=1 + STM32H7S7xx + USE_HAL_DRIVER +) + +set(EMBEDDIP_BOARD_INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/board/stm32h7s + ${EMBEDDIP_STM32CUBE_H7RS_ROOT}/Drivers/CMSIS/Device/ST/STM32H7RSxx/Include + ${EMBEDDIP_STM32CUBE_H7RS_ROOT}/Drivers/CMSIS/Core/Include + ${EMBEDDIP_STM32CUBE_H7RS_ROOT}/Drivers/CMSIS/DSP/Include + ${EMBEDDIP_STM32CUBE_H7RS_ROOT}/Drivers/STM32H7RSxx_HAL_Driver/Inc +) diff --git a/board/stm32h7s/board_stm32h7s_memory.c b/board/stm32h7s/board_stm32h7s_memory.c new file mode 100644 index 0000000..22a6bac --- /dev/null +++ b/board/stm32h7s/board_stm32h7s_memory.c @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 EmbedDIP + +#include "core/memory_manager.h" + +#include +#include +#include + +#include + +#include + +typedef struct { + uintptr_t start; + uintptr_t end; + uintptr_t cursor; + uintptr_t last_payload; /* start of the most recent allocation, 0 if none */ + uintptr_t last_prev_cursor; /* cursor value to restore when that block is freed */ +} h7s_memory_range_t; + +static h7s_memory_range_t fast_sram; +static h7s_memory_range_t dma_memory; +static h7s_memory_range_t psram; +static int memory_initialized; + +static void h7s_reset_range(h7s_memory_range_t *range, uint8_t *start, uint8_t *end) +{ + range->start = (uintptr_t)start; + range->end = (uintptr_t)end; + range->cursor = range->start; + range->last_payload = 0u; + range->last_prev_cursor = range->start; +} + +void memory_init(uintptr_t pool_start_addr) +{ + (void)pool_start_addr; + h7s_reset_range(&fast_sram, __embeddip_fast_sram_start__, __embeddip_fast_sram_end__); + h7s_reset_range(&dma_memory, __embeddip_dma_start__, __embeddip_dma_end__); + h7s_reset_range(&psram, __embeddip_psram_start__, __embeddip_psram_end__); + memory_initialized = 1; +} + +static int h7s_alignment_is_power_of_two(size_t alignment) +{ + return alignment != 0u && (alignment & (alignment - 1u)) == 0u; +} + +static h7s_memory_range_t *h7s_range_for_region(embeddip_memory_region_t region) +{ + switch (region) { + case EMBEDDIP_MEMORY_REGION_DEFAULT: + case EMBEDDIP_MEMORY_REGION_FAST_SRAM: + return &fast_sram; + case EMBEDDIP_MEMORY_REGION_DMA: + return &dma_memory; + case EMBEDDIP_MEMORY_REGION_PSRAM: + return &psram; + case EMBEDDIP_MEMORY_REGION_EXTERNAL_FLASH: + default: + return NULL; + } +} + +static embeddip_status_t h7s_allocate_region(embeddip_memory_region_t region, + size_t size, + size_t alignment, + void **allocation) +{ + h7s_memory_range_t *range; + uintptr_t aligned_cursor; + uintptr_t alignment_mask; + + if (allocation == NULL) { + return EMBEDDIP_ERROR_NULL_PTR; + } + *allocation = NULL; + + if (!h7s_alignment_is_power_of_two(alignment)) { + return EMBEDDIP_ERROR_INVALID_ARG; + } + if (size == 0u) { + return EMBEDDIP_ERROR_INVALID_SIZE; + } + if (region == EMBEDDIP_MEMORY_REGION_EXTERNAL_FLASH) { + return EMBEDDIP_ERROR_NOT_SUPPORTED; + } + + range = h7s_range_for_region(region); + if (range == NULL) { + return EMBEDDIP_ERROR_INVALID_ARG; + } + if (!memory_initialized) { + memory_init(0u); + range = h7s_range_for_region(region); + } + + alignment_mask = (uintptr_t)alignment - 1u; + if (range->end < range->start || range->cursor > UINTPTR_MAX - alignment_mask) { + return EMBEDDIP_ERROR_OUT_OF_MEMORY; + } + aligned_cursor = (range->cursor + alignment_mask) & ~alignment_mask; + if (aligned_cursor > range->end || size > range->end - aligned_cursor) { + return EMBEDDIP_ERROR_OUT_OF_MEMORY; + } + + /* Remember this allocation so a later memory_free() of it can rewind the + * bump cursor (single-level LIFO reclaim). last_prev_cursor restores the + * pre-alignment cursor, so alignment padding is reclaimed too. */ + range->last_prev_cursor = range->cursor; + range->last_payload = aligned_cursor; + range->cursor = aligned_cursor + size; + *allocation = (void *)aligned_cursor; + return EMBEDDIP_OK; +} + +void *embeddip_board_alloc_region(embeddip_memory_region_t region, size_t size, size_t alignment) +{ + void *allocation; + + if (h7s_allocate_region(region, size, alignment, &allocation) != EMBEDDIP_OK) { + return NULL; + } + return allocation; +} + +void *memory_alloc(size_t size) +{ + return embeddip_board_alloc_region(EMBEDDIP_MEMORY_REGION_DEFAULT, size, alignof(max_align_t)); +} + +/* Single-level LIFO reclaim: if ptr is the most recent allocation in its + * region, rewind the bump cursor so the space is reused. Freeing anything + * other than the current top is a safe no-op (the space is reclaimed once the + * blocks above it are freed). This matches how embedDIP allocates and frees + * transient scratch (e.g. imgproc/fft.c's per-call temp buffer, allocated and + * freed with nothing live above it), so a long-running tracker does not leak. + * ponytail: top-of-region reclaim only; a full free-list would be needed for + * arbitrary out-of-order reclamation, which nothing here requires. */ +void memory_free(void *ptr) +{ + h7s_memory_range_t *ranges[3]; + uintptr_t addr = (uintptr_t)ptr; + int i; + + if (ptr == NULL) { + return; + } + ranges[0] = &fast_sram; + ranges[1] = &dma_memory; + ranges[2] = &psram; + for (i = 0; i < 3; ++i) { + h7s_memory_range_t *range = ranges[i]; + if (addr >= range->start && addr < range->end) { + if (addr == range->last_payload) { + range->cursor = range->last_prev_cursor; + range->last_payload = 0u; + } + return; + } + } +} + +void *memory_realloc(void *ptr, size_t new_size) +{ + if (ptr == NULL) { + return memory_alloc(new_size); + } + return NULL; +} + +static embeddip_status_t +h7s_cache_span(const void *address, size_t size, uintptr_t *rounded_start, int32_t *rounded_size) +{ + const uintptr_t line_mask = (uintptr_t)EMBEDDIP_H7S_CACHE_LINE_BYTES - 1u; + uintptr_t start; + uintptr_t end; + uintptr_t rounded_end; + uintptr_t span; + + if (address == NULL) { + return EMBEDDIP_ERROR_NULL_PTR; + } + if (size == 0u) { + return EMBEDDIP_ERROR_INVALID_SIZE; + } + + start = (uintptr_t)address; + if (size > UINTPTR_MAX - start) { + return EMBEDDIP_ERROR_OVERFLOW; + } + end = start + size; + if (end > UINTPTR_MAX - line_mask) { + return EMBEDDIP_ERROR_OVERFLOW; + } + + *rounded_start = start & ~line_mask; + rounded_end = (end + line_mask) & ~line_mask; + span = rounded_end - *rounded_start; + if (span > INT32_MAX) { + return EMBEDDIP_ERROR_OVERFLOW; + } + + *rounded_size = (int32_t)span; + return EMBEDDIP_OK; +} + +embeddip_status_t embeddip_board_cache_clean(const void *address, size_t size) +{ + uintptr_t rounded_start; + int32_t rounded_size; + embeddip_status_t status = h7s_cache_span(address, size, &rounded_start, &rounded_size); + + if (status != EMBEDDIP_OK) { + return status; + } + SCB_CleanDCache_by_Addr((void *)rounded_start, rounded_size); + return EMBEDDIP_OK; +} + +embeddip_status_t embeddip_board_cache_invalidate(const void *address, size_t size) +{ + uintptr_t rounded_start; + int32_t rounded_size; + embeddip_status_t status = h7s_cache_span(address, size, &rounded_start, &rounded_size); + + if (status != EMBEDDIP_OK) { + return status; + } + SCB_InvalidateDCache_by_Addr((void *)rounded_start, rounded_size); + return EMBEDDIP_OK; +} diff --git a/board/stm32h7s/configs.h b/board/stm32h7s/configs.h new file mode 100644 index 0000000..714aae4 --- /dev/null +++ b/board/stm32h7s/configs.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 EmbedDIP + +#ifndef EMBEDDIP_STM32H7S_CONFIGS_H +#define EMBEDDIP_STM32H7S_CONFIGS_H + +#include + +#define EMBEDDIP_H7S_CACHE_LINE_BYTES 32u + +// APS256XX PSRAM, memory-mapped via XSPI2. Used as the LTDC framebuffer base. +#define FRAME_BUFFER 0x90000000u + +#ifdef __cplusplus +extern "C" { +#endif + +extern uint8_t __embeddip_fast_sram_start__[]; +extern uint8_t __embeddip_fast_sram_end__[]; +extern uint8_t __embeddip_dma_start__[]; +extern uint8_t __embeddip_dma_end__[]; +extern uint8_t __embeddip_psram_start__[]; +extern uint8_t __embeddip_psram_end__[]; +extern uint8_t __embeddip_xspi_flash_start__[]; +extern uint8_t __embeddip_xspi_flash_end__[]; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/core/image.h b/core/image.h old mode 100755 new mode 100644 index 141a0f7..4f8d978 --- a/core/image.h +++ b/core/image.h @@ -4,6 +4,8 @@ #ifndef IMAGE_H #define IMAGE_H +#include "core/memory_manager.h" + #include #include @@ -378,6 +380,43 @@ static inline uint8_t image_pixel_size_bytes(ImageFormat fmt, ImageDepth depth) } } +/** + * @brief Non-owning description of a potentially padded pixel buffer. + */ +typedef struct { + uint8_t *pixels; + uint32_t width; + uint32_t height; + uint32_t row_stride_bytes; + ImageFormat format; + ImageDepth depth; + embeddip_memory_region_t region; + uint32_t flags; +} ImageView; + +/** + * @brief Construct a non-owning image view over a pixel buffer. + */ +embeddip_status_t image_view_from_buffer(uint8_t *pixels, + uint32_t width, + uint32_t height, + uint32_t row_stride_bytes, + ImageFormat format, + ImageDepth depth, + embeddip_memory_region_t region, + uint32_t flags, + ImageView *out_view); + +/** + * @brief Construct a tightly packed, CPU-owned view of an existing Image. + */ +embeddip_status_t image_view_from_image(const Image *image, ImageView *out_view); + +/** + * @brief Get the first byte of row @p y in an image view. + */ +uint8_t *image_view_row(const ImageView *view, uint32_t y); + #ifdef __cplusplus } #endif diff --git a/core/memory_manager.h b/core/memory_manager.h old mode 100755 new mode 100644 index d743632..f6347fb --- a/core/memory_manager.h +++ b/core/memory_manager.h @@ -12,6 +12,8 @@ * */ +#include "core/error.h" + #include #include @@ -37,6 +39,30 @@ extern "C" { #define EMBEDDIP_WARN_UNUSED #endif +/** + * @brief Named storage region for buffers shared with hardware accelerators. + */ +typedef enum { + EMBEDDIP_MEMORY_REGION_DEFAULT = 0, + EMBEDDIP_MEMORY_REGION_FAST_SRAM, + EMBEDDIP_MEMORY_REGION_DMA, + EMBEDDIP_MEMORY_REGION_PSRAM, + EMBEDDIP_MEMORY_REGION_EXTERNAL_FLASH +} embeddip_memory_region_t; + +/** + * @brief Access and ownership properties of a buffer. + */ +typedef enum { + EMBEDDIP_BUFFER_CPU_READ = 1u << 0, + EMBEDDIP_BUFFER_CPU_WRITE = 1u << 1, + EMBEDDIP_BUFFER_DMA_READ = 1u << 2, + EMBEDDIP_BUFFER_DMA_WRITE = 1u << 3, + EMBEDDIP_BUFFER_NPU_READ = 1u << 4, + EMBEDDIP_BUFFER_NPU_WRITE = 1u << 5, + EMBEDDIP_BUFFER_READ_ONLY = 1u << 6 +} embeddip_buffer_flags_t; + /** * @brief Initialize the memory manager with default backend settings. * @@ -74,6 +100,28 @@ void memory_free(void *ptr); */ void *memory_realloc(void *ptr, size_t new_size) EMBEDDIP_WARN_UNUSED; +/** + * @brief Allocate writable storage from a named memory region. + * + * @param region Requested memory region. + * @param size Number of bytes to allocate. + * @param alignment Required power-of-two alignment in bytes. + * @return Pointer to allocated storage, or `NULL` when unsupported or unavailable. + */ +void *memory_alloc_region(embeddip_memory_region_t region, + size_t size, + size_t alignment) EMBEDDIP_ALLOC_LIKE; + +/** + * @brief Make CPU writes visible to a cache-coherent device consumer. + */ +embeddip_status_t memory_cache_clean(const void *address, size_t size); + +/** + * @brief Make device writes visible to the CPU. + */ +embeddip_status_t memory_cache_invalidate(const void *address, size_t size); + #ifdef __cplusplus } #endif diff --git a/core/memory_regions.c b/core/memory_regions.c new file mode 100644 index 0000000..6276f54 --- /dev/null +++ b/core/memory_regions.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 EmbedDIP + +#include "core/memory_manager.h" + +#include + +#define EMBEDDIP_CACHE_LINE_BYTES 32u + +void *embeddip_board_alloc_region(embeddip_memory_region_t region, size_t size, size_t alignment); +embeddip_status_t embeddip_board_cache_clean(const void *address, size_t size); +embeddip_status_t embeddip_board_cache_invalidate(const void *address, size_t size); + +static int memory_region_is_writable(embeddip_memory_region_t region) +{ + return region == EMBEDDIP_MEMORY_REGION_DEFAULT || region == EMBEDDIP_MEMORY_REGION_FAST_SRAM || + region == EMBEDDIP_MEMORY_REGION_DMA || region == EMBEDDIP_MEMORY_REGION_PSRAM; +} + +static int memory_alignment_is_power_of_two(size_t alignment) +{ + return alignment != 0u && (alignment & (alignment - 1u)) == 0u; +} + +static embeddip_status_t memory_cache_apply(const void *address, + size_t size, + embeddip_status_t (*operation)(const void *, size_t)) +{ + const uintptr_t line_mask = (uintptr_t)EMBEDDIP_CACHE_LINE_BYTES - 1u; + uintptr_t raw_address; + uintptr_t rounded_address; + uintptr_t end_address; + uintptr_t rounded_end_address; + + if (address == NULL) { + return EMBEDDIP_ERROR_NULL_PTR; + } + if (size == 0u) { + return EMBEDDIP_ERROR_INVALID_SIZE; + } + + raw_address = (uintptr_t)address; + if (size > UINTPTR_MAX - raw_address) { + return EMBEDDIP_ERROR_OVERFLOW; + } + end_address = raw_address + size; + if (end_address > UINTPTR_MAX - line_mask) { + return EMBEDDIP_ERROR_OVERFLOW; + } + + rounded_address = raw_address & ~line_mask; + rounded_end_address = (end_address + line_mask) & ~line_mask; + + return operation((const void *)rounded_address, rounded_end_address - rounded_address); +} + +void *memory_alloc_region(embeddip_memory_region_t region, size_t size, size_t alignment) +{ + if (size == 0u || !memory_alignment_is_power_of_two(alignment) || + !memory_region_is_writable(region)) { + return NULL; + } + + return embeddip_board_alloc_region(region, size, alignment); +} + +embeddip_status_t memory_cache_clean(const void *address, size_t size) +{ + return memory_cache_apply(address, size, embeddip_board_cache_clean); +} + +embeddip_status_t memory_cache_invalidate(const void *address, size_t size) +{ + return memory_cache_apply(address, size, embeddip_board_cache_invalidate); +} diff --git a/device/display/stm32h7s_rk050hr18.c b/device/display/stm32h7s_rk050hr18.c new file mode 100644 index 0000000..fb0e814 --- /dev/null +++ b/device/display/stm32h7s_rk050hr18.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 EmbedDIP + +#include + +#ifdef DEVICE_RK050HR18 + + #include "board/stm32h7s/configs.h" + #include "core/error.h" + #include "device/display/display.h" + + #include "stm32h7rsxx_hal.h" + +// LTDC handle owned/initialized by the application (STM32CubeMX). +extern LTDC_HandleTypeDef hltdc; + + #define LCD_WIDTH 800 + #define LCD_HEIGHT 480 + #define LCD_FRAMEBUFFER ((uint32_t *)(uintptr_t)FRAME_BUFFER) + +static int display_init(void) +{ + HAL_LTDC_SetAddress(&hltdc, (uint32_t)(uintptr_t)LCD_FRAMEBUFFER, LTDC_LAYER_1); + HAL_LTDC_Reload(&hltdc, LTDC_RELOAD_IMMEDIATE); + return EMBEDDIP_OK; +} + +static int display_deinit(void) +{ + HAL_LTDC_DeInit(&hltdc); + return EMBEDDIP_OK; +} + +static int display_reset(void) +{ + return EMBEDDIP_OK; +} + +static int display_clear(displayColor color) +{ + for (uint32_t i = 0; i < (LCD_WIDTH * LCD_HEIGHT); i++) { + LCD_FRAMEBUFFER[i] = color; + } + HAL_LTDC_SetAddress(&hltdc, (uint32_t)(uintptr_t)LCD_FRAMEBUFFER, LTDC_LAYER_1); + return EMBEDDIP_OK; +} + +static int display_show(Image *inImg) +{ + if (!inImg) { + return EMBEDDIP_ERROR_NULL_PTR; + } + + switch (inImg->format) { + case IMAGE_FORMAT_RGB888: + HAL_LTDC_SetPixelFormat(&hltdc, LTDC_PIXEL_FORMAT_RGB888, LTDC_LAYER_1); + break; + case IMAGE_FORMAT_RGB565: + HAL_LTDC_SetPixelFormat(&hltdc, LTDC_PIXEL_FORMAT_RGB565, LTDC_LAYER_1); + break; + case IMAGE_FORMAT_GRAYSCALE: + HAL_LTDC_SetPixelFormat(&hltdc, LTDC_PIXEL_FORMAT_L8, LTDC_LAYER_1); + break; + default: + return EMBEDDIP_ERROR_INVALID_FORMAT; + } + + HAL_LTDC_SetWindowSize(&hltdc, inImg->width, inImg->height, LTDC_LAYER_1); + HAL_LTDC_SetAddress(&hltdc, (uint32_t)(uintptr_t)inImg->pixels, LTDC_LAYER_1); + HAL_LTDC_Reload(&hltdc, LTDC_RELOAD_IMMEDIATE); + return EMBEDDIP_OK; +} + +display_t stm32h7s_rk050hr18 = { + .init = display_init, + .deinit = display_deinit, + .reset = display_reset, + .clear = display_clear, + .show = display_show, +}; + +#endif diff --git a/device/serial/stm32h7s_uart.c b/device/serial/stm32h7s_uart.c new file mode 100644 index 0000000..37b37bf --- /dev/null +++ b/device/serial/stm32h7s_uart.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 EmbedDIP + +#include + +#ifdef DEVICE_STM32H7S_UART + + #include "core/error.h" + #include "device/serial/serial.h" + + #include + #include + + #include "stm32h7rsxx_hal.h" + + // Validation macros to replace assert() + #define CHECK_NULL_INT(ptr) \ + do { \ + if (!(ptr)) \ + return EMBEDDIP_ERROR_NULL_PTR; \ + } while (0) + + #define CHECK_CONDITION_INT(cond) \ + do { \ + if (!(cond)) \ + return EMBEDDIP_ERROR_INVALID_ARG; \ + } while (0) + +// Use the UART handle generated by STM32CubeMX +extern UART_HandleTypeDef huart4; + +static int serial_init(void) +{ + // Optional reinitialization + return EMBEDDIP_OK; +} + +static int serial_flush(void) +{ + __HAL_UART_SEND_REQ(&huart4, UART_RXDATA_FLUSH_REQUEST); + return EMBEDDIP_OK; +} + + #define UART_BLOCK_SIZE_MAX 65535 + #define UART_CMD_CAPTURE "STR" + #define UART_CMD_SEND "STW" + +volatile bool tx_flag, rx_flag = false; + +void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) +{ + (void)huart; + tx_flag = true; +} + +void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) +{ + (void)huart; + rx_flag = true; +} + +static int serial_capture(Image *img) +{ + uint8_t request_start_sequence[3] = "STR"; + CHECK_NULL_INT(img); + CHECK_NULL_INT(img->pixels); + + // Abort any ongoing transfers from previous session + HAL_UART_Abort(&huart4); + + // Clear all UART error flags + __HAL_UART_CLEAR_FLAG(&huart4, + UART_CLEAR_OREF | UART_CLEAR_NEF | UART_CLEAR_FEF | UART_CLEAR_PEF); + + // Flush RX buffer + __HAL_UART_SEND_REQ(&huart4, UART_RXDATA_FLUSH_REQUEST); + + // Reset UART state to ready + huart4.RxState = HAL_UART_STATE_READY; + huart4.gState = HAL_UART_STATE_READY; + + // Calculate block parameters + uint32_t totalBytes = img->size * img->depth; + if (totalBytes == 0U) { + return EMBEDDIP_ERROR_INVALID_SIZE; + } + uint16_t blockSize = + (totalBytes < UART_BLOCK_SIZE_MAX) ? (uint16_t)totalBytes : UART_BLOCK_SIZE_MAX; + uint32_t blockCount = totalBytes / blockSize; + uint16_t lastBlockSize = (uint16_t)(totalBytes % blockSize); + + // Send capture request header + HAL_UART_Transmit(&huart4, request_start_sequence, 3, 5000); + HAL_Delay(1); // Optional small delay + + // Send metadata as fixed-size uint32_t to match Python expectations + uint32_t width_u32 = img->width; + uint32_t height_u32 = img->height; + uint32_t format_u32 = (uint32_t)img->format; + uint32_t depth_u32 = (uint32_t)img->depth; + + HAL_UART_Transmit(&huart4, (uint8_t *)(&width_u32), sizeof(uint32_t), 1000); + HAL_UART_Transmit(&huart4, (uint8_t *)(&height_u32), sizeof(uint32_t), 1000); + HAL_UART_Transmit(&huart4, (uint8_t *)(&format_u32), sizeof(uint32_t), 1000); + HAL_UART_Transmit(&huart4, (uint8_t *)(&depth_u32), sizeof(uint32_t), 1000); + + // Receive image data in blocks with timeout + uint8_t *pixelPtr = img->pixels; + HAL_StatusTypeDef status; + for (uint32_t i = 0; i < blockCount; i++) { + status = + HAL_UART_Receive(&huart4, pixelPtr, blockSize, 10000); // 10 second timeout per block + if (status != HAL_OK) { + // Clear errors and return error code + __HAL_UART_CLEAR_FLAG(&huart4, UART_CLEAR_OREF | UART_CLEAR_NEF | UART_CLEAR_FEF); + return EMBEDDIP_ERROR_IO_ERROR; + } + pixelPtr += blockSize; + } + + // Receive remaining bytes + if (lastBlockSize > 0) { + status = HAL_UART_Receive(&huart4, pixelPtr, lastBlockSize, 10000); + if (status != HAL_OK) { + __HAL_UART_CLEAR_FLAG(&huart4, UART_CLEAR_OREF | UART_CLEAR_NEF | UART_CLEAR_FEF); + return EMBEDDIP_ERROR_IO_ERROR; + } + } + return EMBEDDIP_OK; +} + +static int serial_send(const Image *img) +{ + CHECK_NULL_INT(img); + CHECK_NULL_INT(img->pixels); + uint8_t request_start_sequence[3] = "STW"; + // Calculate block transmission parameters + uint32_t totalBytes = img->size * img->depth; + if (totalBytes == 0U) { + return EMBEDDIP_ERROR_INVALID_SIZE; + } + uint16_t blockSize = + (totalBytes < UART_BLOCK_SIZE_MAX) ? (uint16_t)totalBytes : UART_BLOCK_SIZE_MAX; + uint32_t blockCount = totalBytes / blockSize; + uint16_t lastBlockSize = (uint16_t)(totalBytes % blockSize); + + // Step 1: Send command header + HAL_UART_Transmit(&huart4, request_start_sequence, 3, HAL_MAX_DELAY); + HAL_Delay(1); // Give receiver time to prepare + + // Step 2: Send image metadata as fixed-size uint32_t to match Python expectations + uint32_t width_u32 = img->width; + uint32_t height_u32 = img->height; + // Convert IMAGE_FORMAT_MASK to IMAGE_FORMAT_GRAYSCALE for host compatibility + uint32_t format_u32 = + (img->format == IMAGE_FORMAT_MASK) ? IMAGE_FORMAT_GRAYSCALE : (uint32_t)img->format; + uint32_t depth_u32 = (uint32_t)img->depth; + + HAL_UART_Transmit(&huart4, (uint8_t *)(&width_u32), sizeof(uint32_t), HAL_MAX_DELAY); + HAL_UART_Transmit(&huart4, (uint8_t *)(&height_u32), sizeof(uint32_t), HAL_MAX_DELAY); + HAL_UART_Transmit(&huart4, (uint8_t *)(&format_u32), sizeof(uint32_t), HAL_MAX_DELAY); + HAL_UART_Transmit(&huart4, (uint8_t *)(&depth_u32), sizeof(uint32_t), HAL_MAX_DELAY); + HAL_Delay(2); // Allow receiver to process metadata + + // Step 3: Send image pixel data in blocks + const uint8_t *pixelPtr = img->pixels; + for (uint32_t i = 0; i < blockCount; i++) { + HAL_UART_Transmit(&huart4, pixelPtr, blockSize, HAL_MAX_DELAY); + pixelPtr += blockSize; + HAL_Delay(1); // <-- 1-2ms delay between blocks + } + + // Step 4: Send any remaining bytes + if (lastBlockSize > 0) { + HAL_UART_Transmit(&huart4, pixelPtr, lastBlockSize, HAL_MAX_DELAY); + HAL_Delay(1); // <-- 1-2ms delay between blocks + } + return EMBEDDIP_OK; +} + +static int serial_send_jpeg(const Image *img) +{ + CHECK_NULL_INT(img); + CHECK_NULL_INT(img->pixels); + + const uint8_t header[3] = "STJ"; + HAL_UART_Transmit(&huart4, (uint8_t *)header, sizeof(header), HAL_MAX_DELAY); + + // Send JPEG size (uint32_t) + HAL_UART_Transmit(&huart4, (uint8_t *)&img->size, sizeof(img->size), HAL_MAX_DELAY); + + // Send JPEG data + const uint8_t *ptr = img->pixels; + uint32_t remaining = img->size; + + while (remaining > 0) { + uint16_t chunk = (remaining > UART_BLOCK_SIZE_MAX) ? UART_BLOCK_SIZE_MAX : remaining; + HAL_UART_Transmit(&huart4, ptr, chunk, HAL_MAX_DELAY); + ptr += chunk; + remaining -= chunk; + } + return EMBEDDIP_OK; +} + +static int +serial_send_1d(const void *data, uint8_t elem_size, uint32_t length, Serial1DDataType type) +{ + CHECK_NULL_INT(data); + CHECK_CONDITION_INT(elem_size > 0); + CHECK_CONDITION_INT(length > 0); + + char header[3] = {'S', 'T', '0' + (char)type}; // e.g., ST1 for histogram + HAL_UART_Transmit(&huart4, (uint8_t *)header, sizeof(header), HAL_MAX_DELAY); + + // Send metadata: element count and size + HAL_UART_Transmit(&huart4, (uint8_t *)&length, sizeof(length), HAL_MAX_DELAY); + HAL_UART_Transmit(&huart4, &elem_size, sizeof(elem_size), HAL_MAX_DELAY); + HAL_Delay(1); + + // Send data in chunks + const uint8_t *ptr = (const uint8_t *)data; + uint32_t remaining = elem_size * length; + + while (remaining > 0) { + uint16_t chunk = (remaining > UART_BLOCK_SIZE_MAX) ? UART_BLOCK_SIZE_MAX : remaining; + HAL_UART_Transmit(&huart4, ptr, chunk, HAL_MAX_DELAY); + ptr += chunk; + remaining -= chunk; + HAL_Delay(1); + } + return EMBEDDIP_OK; +} + +// Define the object +serial_t stm32h7s_uart = { + .init = serial_init, + .capture = serial_capture, + .send = serial_send, + .sendJPEG = serial_send_jpeg, + .send1D = serial_send_1d, + .flush = serial_flush, +}; + +#endif diff --git a/embedDIP_configs.h b/embedDIP_configs.h index b44cb92..f5b4817 100755 --- a/embedDIP_configs.h +++ b/embedDIP_configs.h @@ -26,15 +26,19 @@ /* Target selection */ /* -------------------------------------------------------------------------- */ /* Uncomment only if you do not provide these from the build system. */ -/* #define EMBED_DIP_BOARD_STM32F7 1 */ -/* #define EMBED_DIP_BOARD_ESP32 1 */ +/* #define EMBED_DIP_BOARD_STM32F7 1 */ +/* #define EMBED_DIP_BOARD_STM32H7S 1 */ +/* #define EMBED_DIP_BOARD_ESP32 1 */ +/* #define EMBED_DIP_BOARD_HOST 1 */ /* #define EMBED_DIP_ARCH_ARM 1 */ /* #define EMBED_DIP_ARCH_XTENSA 1 */ +/* #define EMBED_DIP_ARCH_HOST 1 */ /* #define EMBED_DIP_CPU_CORTEX_M7 1 */ /* #define EMBED_DIP_CPU_LX6 1 */ /* #define EMBED_DIP_CPU_LX7 1 */ +/* #define EMBED_DIP_CPU_NATIVE 1 */ /* -------------------------------------------------------------------------- */ /* Arduino auto-detection (Library Manager friendly defaults) */ @@ -44,23 +48,25 @@ * infer them from Arduino core/platform macros so sketches can compile * without extra CLI flags. */ -#if !defined(EMBED_DIP_BOARD_STM32F7) && !defined(EMBED_DIP_BOARD_ESP32) +#if !defined(EMBED_DIP_BOARD_STM32F7) && !defined(EMBED_DIP_BOARD_STM32H7S) && !defined(EMBED_DIP_BOARD_ESP32) && !defined(EMBED_DIP_BOARD_HOST) #if defined(ARDUINO_ARCH_ESP32) #define EMBED_DIP_BOARD_ESP32 1 + #elif defined(STM32H7S7xx) + #define EMBED_DIP_BOARD_STM32H7S 1 #elif defined(STM32F7xx) #define EMBED_DIP_BOARD_STM32F7 1 #endif #endif -#if !defined(EMBED_DIP_ARCH_ARM) && !defined(EMBED_DIP_ARCH_XTENSA) +#if !defined(EMBED_DIP_ARCH_ARM) && !defined(EMBED_DIP_ARCH_XTENSA) && !defined(EMBED_DIP_ARCH_HOST) #if defined(EMBED_DIP_BOARD_ESP32) #define EMBED_DIP_ARCH_XTENSA 1 - #elif defined(EMBED_DIP_BOARD_STM32F7) + #elif defined(EMBED_DIP_BOARD_STM32F7) || defined(EMBED_DIP_BOARD_STM32H7S) #define EMBED_DIP_ARCH_ARM 1 #endif #endif -#if !defined(EMBED_DIP_CPU_CORTEX_M7) && !defined(EMBED_DIP_CPU_LX6) && !defined(EMBED_DIP_CPU_LX7) +#if !defined(EMBED_DIP_CPU_CORTEX_M7) && !defined(EMBED_DIP_CPU_LX6) && !defined(EMBED_DIP_CPU_LX7) && !defined(EMBED_DIP_CPU_NATIVE) #if defined(EMBED_DIP_BOARD_ESP32) /* * ESP32/ESP32-S2/ESP32-S3 families are LX6/LX7. Prefer explicit IDF @@ -71,35 +77,35 @@ #else #define EMBED_DIP_CPU_LX6 1 #endif - #elif defined(EMBED_DIP_BOARD_STM32F7) + #elif defined(EMBED_DIP_BOARD_STM32F7) || defined(EMBED_DIP_BOARD_STM32H7S) #define EMBED_DIP_CPU_CORTEX_M7 1 #endif #endif /* Sanity check: exactly one board. */ -#if ((defined(EMBED_DIP_BOARD_STM32F7) ? 1 : 0) + (defined(EMBED_DIP_BOARD_ESP32) ? 1 : 0)) == 0 +#if ((defined(EMBED_DIP_BOARD_STM32F7) ? 1 : 0) + (defined(EMBED_DIP_BOARD_STM32H7S) ? 1 : 0) + (defined(EMBED_DIP_BOARD_ESP32) ? 1 : 0) + (defined(EMBED_DIP_BOARD_HOST) ? 1 : 0)) == 0 #error \ - "No board selected: define exactly one of EMBED_DIP_BOARD_STM32F7 or EMBED_DIP_BOARD_ESP32." -#elif ((defined(EMBED_DIP_BOARD_STM32F7) ? 1 : 0) + (defined(EMBED_DIP_BOARD_ESP32) ? 1 : 0)) > 1 + "No board selected: define exactly one of EMBED_DIP_BOARD_STM32F7, EMBED_DIP_BOARD_STM32H7S, EMBED_DIP_BOARD_ESP32, or EMBED_DIP_BOARD_HOST." +#elif ((defined(EMBED_DIP_BOARD_STM32F7) ? 1 : 0) + (defined(EMBED_DIP_BOARD_STM32H7S) ? 1 : 0) + (defined(EMBED_DIP_BOARD_ESP32) ? 1 : 0) + (defined(EMBED_DIP_BOARD_HOST) ? 1 : 0)) > 1 #error \ - "Multiple boards selected: define only one of EMBED_DIP_BOARD_STM32F7 or EMBED_DIP_BOARD_ESP32." + "Multiple boards selected: define only one EMBED_DIP_BOARD_* macro." #endif /* Sanity check: exactly one architecture family. */ -#if ((defined(EMBED_DIP_ARCH_ARM) ? 1 : 0) + (defined(EMBED_DIP_ARCH_XTENSA) ? 1 : 0)) == 0 +#if ((defined(EMBED_DIP_ARCH_ARM) ? 1 : 0) + (defined(EMBED_DIP_ARCH_XTENSA) ? 1 : 0) + (defined(EMBED_DIP_ARCH_HOST) ? 1 : 0)) == 0 #error \ - "No architecture family selected: define exactly one of EMBED_DIP_ARCH_ARM or EMBED_DIP_ARCH_XTENSA." -#elif ((defined(EMBED_DIP_ARCH_ARM) ? 1 : 0) + (defined(EMBED_DIP_ARCH_XTENSA) ? 1 : 0)) > 1 + "No architecture family selected: define exactly one of EMBED_DIP_ARCH_ARM, EMBED_DIP_ARCH_XTENSA, or EMBED_DIP_ARCH_HOST." +#elif ((defined(EMBED_DIP_ARCH_ARM) ? 1 : 0) + (defined(EMBED_DIP_ARCH_XTENSA) ? 1 : 0) + (defined(EMBED_DIP_ARCH_HOST) ? 1 : 0)) > 1 #error "Multiple architecture families selected: define only one EMBED_DIP_ARCH_* macro." #endif /* Sanity check: exactly one CPU variant. */ #if ((defined(EMBED_DIP_CPU_CORTEX_M7) ? 1 : 0) + (defined(EMBED_DIP_CPU_LX6) ? 1 : 0) + \ - (defined(EMBED_DIP_CPU_LX7) ? 1 : 0)) == 0 + (defined(EMBED_DIP_CPU_LX7) ? 1 : 0) + (defined(EMBED_DIP_CPU_NATIVE) ? 1 : 0)) == 0 #error \ - "No CPU selected: define exactly one of EMBED_DIP_CPU_CORTEX_M7, EMBED_DIP_CPU_LX6, EMBED_DIP_CPU_LX7." + "No CPU selected: define exactly one of EMBED_DIP_CPU_CORTEX_M7, EMBED_DIP_CPU_LX6, EMBED_DIP_CPU_LX7, or EMBED_DIP_CPU_NATIVE." #elif ((defined(EMBED_DIP_CPU_CORTEX_M7) ? 1 : 0) + (defined(EMBED_DIP_CPU_LX6) ? 1 : 0) + \ - (defined(EMBED_DIP_CPU_LX7) ? 1 : 0)) > 1 + (defined(EMBED_DIP_CPU_LX7) ? 1 : 0) + (defined(EMBED_DIP_CPU_NATIVE) ? 1 : 0)) > 1 #error "Multiple CPUs selected: define only one EMBED_DIP_CPU_* macro." #endif @@ -109,12 +115,21 @@ #error \ "Invalid combination: EMBED_DIP_BOARD_STM32F7 requires EMBED_DIP_ARCH_ARM + EMBED_DIP_CPU_CORTEX_M7." #endif +#elif defined(EMBED_DIP_BOARD_STM32H7S) + #if !(defined(EMBED_DIP_ARCH_ARM) && defined(EMBED_DIP_CPU_CORTEX_M7)) + #error \ + "Invalid combination: EMBED_DIP_BOARD_STM32H7S requires EMBED_DIP_ARCH_ARM + EMBED_DIP_CPU_CORTEX_M7." + #endif #elif defined(EMBED_DIP_BOARD_ESP32) #if !(defined(EMBED_DIP_ARCH_XTENSA) && \ (defined(EMBED_DIP_CPU_LX6) || defined(EMBED_DIP_CPU_LX7))) #error \ "Invalid combination: EMBED_DIP_BOARD_ESP32 requires EMBED_DIP_ARCH_XTENSA + (EMBED_DIP_CPU_LX6 or EMBED_DIP_CPU_LX7)." #endif +#elif defined(EMBED_DIP_BOARD_HOST) + #if !(defined(EMBED_DIP_ARCH_HOST) && defined(EMBED_DIP_CPU_NATIVE)) + #error "Invalid combination: EMBED_DIP_BOARD_HOST requires EMBED_DIP_ARCH_HOST + EMBED_DIP_CPU_NATIVE." + #endif #endif /** @@ -149,6 +164,29 @@ #define DEVICE_STM32_UART 1 #endif +/* ============================== STM32H7S ================================== */ +#elif defined(EMBED_DIP_BOARD_STM32H7S) + #ifndef STM32H7S7xx + #define STM32H7S7xx 1 + #endif + + #ifndef ENABLE_IMAGE_PROCESSING + #define ENABLE_IMAGE_PROCESSING 1 + #endif + #ifndef ENABLE_CAMERA_INPUT + #define ENABLE_CAMERA_INPUT 0 + #endif + #ifndef ENABLE_DISPLAY_OUTPUT + #define ENABLE_DISPLAY_OUTPUT 1 + #endif + + #ifndef DEVICE_RK050HR18 + #define DEVICE_RK050HR18 1 + #endif + #ifndef DEVICE_STM32H7S_UART + #define DEVICE_STM32H7S_UART 1 + #endif + /* =============================== ESP32 ==================================== */ #elif defined(EMBED_DIP_BOARD_ESP32) #ifndef ARDUINO_ARCH_ESP32 @@ -171,6 +209,18 @@ #ifndef DEVICE_ESP32_UART #define DEVICE_ESP32_UART 1 #endif + +/* ================================ HOST ==================================== */ +#elif defined(EMBED_DIP_BOARD_HOST) + #ifndef ENABLE_IMAGE_PROCESSING + #define ENABLE_IMAGE_PROCESSING 1 + #endif + #ifndef ENABLE_CAMERA_INPUT + #define ENABLE_CAMERA_INPUT 0 + #endif + #ifndef ENABLE_DISPLAY_OUTPUT + #define ENABLE_DISPLAY_OUTPUT 0 + #endif #endif /** @} */ /* end of embedDIP_cfg_features */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..8d7888e --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,43 @@ +add_executable(embeddip_test_image_lifecycle test_image_lifecycle.c) +target_link_libraries(embeddip_test_image_lifecycle PRIVATE embedDIP) +add_test(NAME embeddip.image_lifecycle COMMAND embeddip_test_image_lifecycle) + +add_executable(embeddip_test_image_view test_image_view.c) +target_link_libraries(embeddip_test_image_view PRIVATE embedDIP) +add_test(NAME embeddip.image_view COMMAND embeddip_test_image_view) + +if(EMBEDDIP_TARGET_BOARD STREQUAL "HOST") + add_executable(embeddip_test_memory_regions test_memory_regions.c) + target_link_libraries(embeddip_test_memory_regions PRIVATE embedDIP) + add_test(NAME embeddip.memory_regions COMMAND embeddip_test_memory_regions) + + add_executable(embeddip_test_stm32h7s_memory test_stm32h7s_memory.c) + target_include_directories(embeddip_test_stm32h7s_memory PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/fakes/stm32h7s + ${CMAKE_SOURCE_DIR}/board/stm32h7s + ${CMAKE_SOURCE_DIR}) + add_test(NAME embeddip.stm32h7s_memory COMMAND embeddip_test_stm32h7s_memory) +endif() + +add_test(NAME embeddip.target_matrix + COMMAND ${CMAKE_COMMAND} + -DEMBEDDIP_SOURCE_DIR=${CMAKE_SOURCE_DIR} + -DEMBEDDIP_BINARY_DIR=${CMAKE_BINARY_DIR}/matrix + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/test_target_matrix.cmake) + +add_test(NAME embeddip.stm32h7s_profile_missing + COMMAND ${CMAKE_COMMAND} + -DEMBEDDIP_SOURCE_DIR=${CMAKE_SOURCE_DIR} + -DEMBEDDIP_BINARY_DIR=${CMAKE_BINARY_DIR}/h7s-missing + -DEMBEDDIP_STM32CUBE_H7RS_ROOT=/path/that/does/not/exist + -DEMBEDDIP_EXPECT_MISSING_SDK=ON + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/test_stm32h7s_profile.cmake) + +if(IS_DIRECTORY "${EMBEDDIP_STM32CUBE_H7RS_ROOT}") + add_test(NAME embeddip.stm32h7s_profile + COMMAND ${CMAKE_COMMAND} + -DEMBEDDIP_SOURCE_DIR=${CMAKE_SOURCE_DIR} + -DEMBEDDIP_BINARY_DIR=${CMAKE_BINARY_DIR}/h7s + "-DEMBEDDIP_STM32CUBE_H7RS_ROOT=${EMBEDDIP_STM32CUBE_H7RS_ROOT}" + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/test_stm32h7s_profile.cmake) +endif() diff --git a/tests/cmake/test_stm32h7s_profile.cmake b/tests/cmake/test_stm32h7s_profile.cmake new file mode 100644 index 0000000..88b8308 --- /dev/null +++ b/tests/cmake/test_stm32h7s_profile.cmake @@ -0,0 +1,20 @@ +if(NOT DEFINED EMBEDDIP_STM32CUBE_H7RS_ROOT) + message(FATAL_ERROR "EMBEDDIP_STM32CUBE_H7RS_ROOT must be provided to this test") +endif() + +execute_process( + COMMAND "${CMAKE_COMMAND}" -S "${EMBEDDIP_SOURCE_DIR}" -B "${EMBEDDIP_BINARY_DIR}" + -DEMBEDDIP_TARGET_BOARD=STM32H7S -DEMBEDDIP_ARCH=ARM -DEMBEDDIP_CPU=CORTEX_M7 + "-DEMBEDDIP_STM32CUBE_H7RS_ROOT=${EMBEDDIP_STM32CUBE_H7RS_ROOT}" + RESULT_VARIABLE result OUTPUT_VARIABLE out ERROR_VARIABLE err) + +if(EMBEDDIP_EXPECT_MISSING_SDK) + if(result EQUAL 0) + message(FATAL_ERROR "H7S profile unexpectedly configured with a missing CubeH7RS SDK") + endif() + if(NOT "${out}${err}" MATCHES "EMBEDDIP_STM32CUBE_H7RS_ROOT") + message(FATAL_ERROR "Missing CubeH7RS SDK diagnostic did not name EMBEDDIP_STM32CUBE_H7RS_ROOT: ${out}${err}") + endif() +elseif(NOT result EQUAL 0) + message(FATAL_ERROR "H7S profile did not configure: ${out}${err}") +endif() diff --git a/tests/cmake/test_target_matrix.cmake b/tests/cmake/test_target_matrix.cmake new file mode 100644 index 0000000..5fd672c --- /dev/null +++ b/tests/cmake/test_target_matrix.cmake @@ -0,0 +1,7 @@ +execute_process( + COMMAND "${CMAKE_COMMAND}" -S "${EMBEDDIP_SOURCE_DIR}" -B "${EMBEDDIP_BINARY_DIR}/bad-n6" + -DEMBEDDIP_TARGET_BOARD=STM32N6 -DEMBEDDIP_ARCH=ARM -DEMBEDDIP_CPU=CORTEX_M7 + RESULT_VARIABLE bad_result OUTPUT_VARIABLE bad_out ERROR_VARIABLE bad_err) +if(bad_result EQUAL 0 OR NOT "${bad_out}${bad_err}" MATCHES "Invalid board/arch/cpu combination") + message(FATAL_ERROR "STM32N6+CORTEX_M7 must be rejected") +endif() diff --git a/tests/fakes/stm32h7s/stm32h7s7xx.h b/tests/fakes/stm32h7s/stm32h7s7xx.h new file mode 100644 index 0000000..0cdf6c3 --- /dev/null +++ b/tests/fakes/stm32h7s/stm32h7s7xx.h @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 EmbedDIP +// Minimal host fake: the real header pulls in Cortex-M7 core intrinsics. +// The test provides its own SCB_*DCache_by_Addr definitions. +#ifndef EMBEDDIP_TEST_FAKE_STM32H7S7XX_H +#define EMBEDDIP_TEST_FAKE_STM32H7S7XX_H +#include +void SCB_CleanDCache_by_Addr(void *address, int32_t size); +void SCB_InvalidateDCache_by_Addr(void *address, int32_t size); +#endif diff --git a/tests/memory_test_hooks.h b/tests/memory_test_hooks.h new file mode 100644 index 0000000..800bb65 --- /dev/null +++ b/tests/memory_test_hooks.h @@ -0,0 +1,14 @@ +#ifndef EMBEDDIP_MEMORY_TEST_HOOKS_H +#define EMBEDDIP_MEMORY_TEST_HOOKS_H + +#include +#include + +#if !defined(EMBED_DIP_BOARD_HOST) + #error "memory test hooks are available only for the host board" +#endif + +uintptr_t memory_test_last_cache_range_start(void); +size_t memory_test_last_cache_range_size(void); + +#endif /* EMBEDDIP_MEMORY_TEST_HOOKS_H */ diff --git a/tests/test_image_lifecycle.c b/tests/test_image_lifecycle.c new file mode 100644 index 0000000..4e33c1e --- /dev/null +++ b/tests/test_image_lifecycle.c @@ -0,0 +1,21 @@ +#include +#include + +#include +#include + +int main(void) +{ + Image *image = 0; + + memory_init(0); + assert(createImageWH(3, 2, IMAGE_FORMAT_RGB888, &image) == EMBEDDIP_OK); + assert(image != 0); + assert(image->width == 3u && image->height == 2u); + assert(image->size == 6u && image->depth == IMAGE_DEPTH_U24); + assert(image->pixels != 0); + ((uint8_t *)image->pixels)[17] = 0xA5u; + assert(((uint8_t *)image->pixels)[17] == 0xA5u); + deleteImage(image); + return 0; +} diff --git a/tests/test_image_view.c b/tests/test_image_view.c new file mode 100644 index 0000000..f46b386 --- /dev/null +++ b/tests/test_image_view.c @@ -0,0 +1,49 @@ +#include +#include + +#include +#include +#include + +int main(void) +{ + uint8_t pixels[16] = {0}; + ImageView view; + Image image = { + .width = 3u, + .height = 2u, + .pixels = pixels, + .format = IMAGE_FORMAT_RGB888, + .depth = IMAGE_DEPTH_U24, + }; + + assert(image_view_from_buffer(pixels, 3u, 2u, 9u, IMAGE_FORMAT_RGB888, IMAGE_DEPTH_U24, + EMBEDDIP_MEMORY_REGION_DMA, EMBEDDIP_BUFFER_DMA_WRITE, + &view) == EMBEDDIP_OK); + assert(image_view_row(&view, 1u) == pixels + 9u); + assert(image_view_from_buffer(pixels, 3u, 2u, 8u, IMAGE_FORMAT_RGB888, IMAGE_DEPTH_U24, + EMBEDDIP_MEMORY_REGION_DMA, EMBEDDIP_BUFFER_DMA_WRITE, + &view) == EMBEDDIP_ERROR_INVALID_SIZE); + + assert(image_view_from_buffer(pixels, 1u, 1u, 3u, IMAGE_FORMAT_RGB888, IMAGE_DEPTH_U24, + EMBEDDIP_MEMORY_REGION_DEFAULT, 0u, + NULL) == EMBEDDIP_ERROR_NULL_PTR); + assert(image_view_from_buffer(NULL, 1u, 1u, 3u, IMAGE_FORMAT_RGB888, IMAGE_DEPTH_U24, + EMBEDDIP_MEMORY_REGION_DEFAULT, 0u, + &view) == EMBEDDIP_ERROR_NULL_PTR); + assert(image_view_from_buffer(pixels, 0u, 1u, 3u, IMAGE_FORMAT_RGB888, IMAGE_DEPTH_U24, + EMBEDDIP_MEMORY_REGION_DEFAULT, 0u, + &view) == EMBEDDIP_ERROR_INVALID_SIZE); + assert(image_view_from_buffer(pixels, 1u, 1u, 3u, IMAGE_FORMAT_RGB888, IMAGE_DEPTH_U8, + EMBEDDIP_MEMORY_REGION_DEFAULT, 0u, + &view) == EMBEDDIP_ERROR_INVALID_FORMAT); + + assert(image_view_from_image(&image, &view) == EMBEDDIP_OK); + assert(view.pixels == pixels && view.row_stride_bytes == 9u); + assert(view.region == EMBEDDIP_MEMORY_REGION_DEFAULT); + assert(view.flags == (EMBEDDIP_BUFFER_CPU_READ | EMBEDDIP_BUFFER_CPU_WRITE)); + assert(image_view_from_image(NULL, &view) == EMBEDDIP_ERROR_NULL_PTR); + assert(image_view_row(&view, 2u) == NULL); + assert(image_view_row(NULL, 0u) == NULL); + return 0; +} diff --git a/tests/test_memory_regions.c b/tests/test_memory_regions.c new file mode 100644 index 0000000..1685d8e --- /dev/null +++ b/tests/test_memory_regions.c @@ -0,0 +1,40 @@ +#include +#include + +#include +#include + +#include "memory_test_hooks.h" + +int main(void) +{ + void *fast = memory_alloc_region(EMBEDDIP_MEMORY_REGION_FAST_SRAM, 64u, 32u); + void *default_region = memory_alloc_region(EMBEDDIP_MEMORY_REGION_DEFAULT, 64u, 8u); + void *dma = memory_alloc_region(EMBEDDIP_MEMORY_REGION_DMA, 64u, 8u); + void *psram = memory_alloc_region(EMBEDDIP_MEMORY_REGION_PSRAM, 64u, 8u); + + assert(fast != NULL && ((uintptr_t)fast % 32u) == 0u); + assert(default_region != NULL); + assert(dma != NULL); + assert(psram != NULL); + assert(memory_alloc_region(EMBEDDIP_MEMORY_REGION_DEFAULT, 0u, 8u) == NULL); + assert(memory_alloc_region(EMBEDDIP_MEMORY_REGION_DEFAULT, 64u, 3u) == NULL); + assert(memory_alloc_region(EMBEDDIP_MEMORY_REGION_EXTERNAL_FLASH, 64u, 8u) == NULL); + + assert(memory_cache_clean((const void *)0x1003u, 61u) == EMBEDDIP_OK); + assert(memory_test_last_cache_range_start() == (uintptr_t)0x1000u); + assert(memory_test_last_cache_range_size() == 64u); + assert(memory_cache_invalidate((const void *)0x1020u, 32u) == EMBEDDIP_OK); + assert(memory_test_last_cache_range_start() == (uintptr_t)0x1020u); + assert(memory_test_last_cache_range_size() == 32u); + assert(memory_cache_clean(NULL, 1u) == EMBEDDIP_ERROR_NULL_PTR); + assert(memory_cache_invalidate((const void *)0x1000u, 0u) == EMBEDDIP_ERROR_INVALID_SIZE); + assert(memory_cache_clean((const void *)(uintptr_t)(UINTPTR_MAX - 1u), 2u) == + EMBEDDIP_ERROR_OVERFLOW); + + memory_free(fast); + memory_free(default_region); + memory_free(dma); + memory_free(psram); + return 0; +} diff --git a/tests/test_stm32h7s_memory.c b/tests/test_stm32h7s_memory.c new file mode 100644 index 0000000..5d4db6f --- /dev/null +++ b/tests/test_stm32h7s_memory.c @@ -0,0 +1,107 @@ +#include +#include +#include +#include + +#include "core/memory_manager.h" + +alignas(32) uint8_t fast_storage[96]; +alignas(32) uint8_t dma_storage[64]; +alignas(32) uint8_t psram_storage[48]; +alignas(32) uint8_t flash_storage[32]; + +__asm__(".globl __embeddip_fast_sram_start__\n" + ".set __embeddip_fast_sram_start__, fast_storage\n" + ".globl __embeddip_fast_sram_end__\n" + ".set __embeddip_fast_sram_end__, fast_storage + 96\n" + ".globl __embeddip_dma_start__\n" + ".set __embeddip_dma_start__, dma_storage\n" + ".globl __embeddip_dma_end__\n" + ".set __embeddip_dma_end__, dma_storage + 64\n" + ".globl __embeddip_psram_start__\n" + ".set __embeddip_psram_start__, psram_storage\n" + ".globl __embeddip_psram_end__\n" + ".set __embeddip_psram_end__, psram_storage + 48\n" + ".globl __embeddip_xspi_flash_start__\n" + ".set __embeddip_xspi_flash_start__, flash_storage\n" + ".globl __embeddip_xspi_flash_end__\n" + ".set __embeddip_xspi_flash_end__, flash_storage + 32\n"); + +static void *last_clean_address; +static int32_t last_clean_size; +static void *last_invalidate_address; +static int32_t last_invalidate_size; + +void SCB_CleanDCache_by_Addr(void *address, int32_t size) +{ + last_clean_address = address; + last_clean_size = size; +} + +void SCB_InvalidateDCache_by_Addr(void *address, int32_t size) +{ + last_invalidate_address = address; + last_invalidate_size = size; +} + +#include "board/stm32h7s/board_stm32h7s_memory.c" + +int main(void) +{ + alignas(32) uint8_t cache_span[96]; + void *allocation = (void *)(uintptr_t)1u; + + memory_init(0u); + + assert(h7s_allocate_region(EMBEDDIP_MEMORY_REGION_DEFAULT, 40u, 32u, &allocation) == EMBEDDIP_OK); + assert(allocation == fast_storage); + assert(h7s_allocate_region(EMBEDDIP_MEMORY_REGION_FAST_SRAM, 64u, 1u, &allocation) == + EMBEDDIP_ERROR_OUT_OF_MEMORY); + assert(allocation == NULL); + + assert(h7s_allocate_region(EMBEDDIP_MEMORY_REGION_DMA, 16u, 3u, &allocation) == + EMBEDDIP_ERROR_INVALID_ARG); + assert(allocation == NULL); + assert(h7s_allocate_region(EMBEDDIP_MEMORY_REGION_DMA, sizeof(dma_storage), 8u, &allocation) == EMBEDDIP_OK); + assert(allocation == dma_storage); + assert(h7s_allocate_region(EMBEDDIP_MEMORY_REGION_PSRAM, sizeof(psram_storage), 16u, &allocation) == EMBEDDIP_OK); + assert(allocation == psram_storage); + assert(h7s_allocate_region(EMBEDDIP_MEMORY_REGION_EXTERNAL_FLASH, 1u, 1u, &allocation) == + EMBEDDIP_ERROR_NOT_SUPPORTED); + assert(allocation == NULL); + + assert(embeddip_board_cache_clean(cache_span + 3u, 33u) == EMBEDDIP_OK); + assert(last_clean_address == cache_span); + assert(last_clean_size == 64); + assert(embeddip_board_cache_invalidate(cache_span + 31u, 2u) == EMBEDDIP_OK); + assert(last_invalidate_address == cache_span); + assert(last_invalidate_size == 64); + + /* Single-level LIFO reclaim: freeing the most recent allocation rewinds the + * bump cursor so the space (and alignment padding) is reused. */ + memory_init(0u); + void *a = NULL, *b = NULL, *c = NULL; + assert(h7s_allocate_region(EMBEDDIP_MEMORY_REGION_DEFAULT, 40u, 8u, &a) == EMBEDDIP_OK); + assert(h7s_allocate_region(EMBEDDIP_MEMORY_REGION_DEFAULT, 40u, 8u, &b) == EMBEDDIP_OK); + assert(a == fast_storage); + assert(b != a); + /* Free the top (b): a subsequent same-size alloc must reuse b's address. */ + memory_free(b); + assert(h7s_allocate_region(EMBEDDIP_MEMORY_REGION_DEFAULT, 40u, 8u, &c) == EMBEDDIP_OK); + assert(c == b); + /* Freeing a non-top block (a, while c is live) is a safe no-op: c stays put + * and the next alloc does not clobber it. */ + memory_free(a); + void *d = NULL; + assert(h7s_allocate_region(EMBEDDIP_MEMORY_REGION_DEFAULT, 8u, 8u, &d) == EMBEDDIP_OK); + assert(d != c); + /* Repeated alloc/free of the top must not advance the cursor (no leak) -- + * this is the fft.c per-call temp pattern that was exhausting the pool. */ + for (int k = 0; k < 1000; ++k) { + void *t = NULL; + assert(h7s_allocate_region(EMBEDDIP_MEMORY_REGION_DEFAULT, 8u, 8u, &t) == EMBEDDIP_OK); + memory_free(t); + } + + return 0; +}