Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 27 additions & 10 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()

Expand All @@ -68,6 +79,7 @@ set(CORE_SOURCES
core/error.c
core/error.h
core/memory_manager.h
core/memory_regions.c
core/image.h
)

Expand Down Expand Up @@ -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)

Expand Down
54 changes: 54 additions & 0 deletions arch/arm/cmsis_fft.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2025 EmbedDIP

#include <embedDIP_configs.h>

#if defined(EMBED_DIP_ARCH_ARM)

#include <arch/fft_backend.h>
#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
34 changes: 34 additions & 0 deletions arch/arm/dwt_timer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2025 EmbedDIP

#include <embedDIP_configs.h>
#include <board/common.h>

#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
13 changes: 13 additions & 0 deletions arch/host/arch_profile.cmake
Original file line number Diff line number Diff line change
@@ -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)
88 changes: 88 additions & 0 deletions arch/host/host_fft.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2025 EmbedDIP

#include <embedDIP_configs.h>

#if defined(EMBED_DIP_ARCH_HOST)

#include <math.h>
#include <stddef.h>

#include <arch/fft_backend.h>

/*
* 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
31 changes: 31 additions & 0 deletions arch/host/host_timer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2025 EmbedDIP

#include <board/common.h>

#include <stdbool.h>
#include <stdint.h>
#include <time.h>

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;
}
Loading
Loading