From b691309aa633c06c90ad5753f8fe2cf10d469feb Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Mon, 9 Mar 2026 16:42:03 +0800 Subject: [PATCH 01/36] refactor(core): Abstract parameter manager atomic operations --- src/par.c | 90 ++++++++--------- src/par_atomic.h | 247 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 288 insertions(+), 49 deletions(-) create mode 100644 src/par_atomic.h diff --git a/src/par.c b/src/par.c index e3f00e2..8609565 100644 --- a/src/par.c +++ b/src/par.c @@ -25,9 +25,9 @@ #include #include #include -#include #include "par.h" +#include "par_atomic.h" #include "par_nvm.h" #include "../../par_if.h" @@ -56,13 +56,13 @@ static struct /** * Parameter live values divided by its type in RAM */ -static _Atomic uint8_t * gpu8_par_value = NULL; -static _Atomic int8_t * gpi8_par_value = NULL; -static _Atomic uint16_t * gpu16_par_value = NULL; -static _Atomic int16_t * gpi16_par_value = NULL; -static _Atomic uint32_t * gpu32_par_value = NULL; -static _Atomic int32_t * gpi32_par_value = NULL; -static _Atomic float32_t * gpf32_par_value = NULL; +static par_atomic_u8_t * gpu8_par_value = NULL; +static par_atomic_i8_t * gpi8_par_value = NULL; +static par_atomic_u16_t * gpu16_par_value = NULL; +static par_atomic_i16_t * gpi16_par_value = NULL; +static par_atomic_u32_t * gpu32_par_value = NULL; +static par_atomic_i32_t * gpi32_par_value = NULL; +static par_atomic_f32_t * gpf32_par_value = NULL; /** * Address offset by parameter enumeration @@ -72,29 +72,21 @@ static uint32_t gu32_par_offset[ ePAR_NUM_OF ] = { 0 }; /** * Private getters and setters */ -#define PAR_GET_U8_PRIV(par_num) atomic_load_explicit( &gpu8_par_value[gu32_par_offset[par_num]], memory_order_relaxed ) -#define PAR_GET_I8_PRIV(par_num) atomic_load_explicit( &gpi8_par_value[gu32_par_offset[par_num]], memory_order_relaxed ) -#define PAR_GET_U16_PRIV(par_num) atomic_load_explicit( &gpu16_par_value[gu32_par_offset[par_num]], memory_order_relaxed ) -#define PAR_GET_I16_PRIV(par_num) atomic_load_explicit( &gpi16_par_value[gu32_par_offset[par_num]], memory_order_relaxed ) -#define PAR_GET_U32_PRIV(par_num) atomic_load_explicit( &gpu32_par_value[gu32_par_offset[par_num]], memory_order_relaxed ) -#define PAR_GET_I32_PRIV(par_num) atomic_load_explicit( &gpi32_par_value[gu32_par_offset[par_num]], memory_order_relaxed ) - -// NOTICE: "atomic_load_explicit" does not support float data type, therfore using GCC/CLang build-in primitive "__atomic_load" to overcome this limitation -#define PAR_GET_F32_PRIV(par_num) ({ \ - float32_t __val; \ - __atomic_load( &gpf32_par_value[gu32_par_offset[par_num]], &__val, __ATOMIC_RELAXED); \ - __val; \ -}) - -#define PAR_SET_U8_PRIV(par_num, val) atomic_store_explicit( &gpu8_par_value[gu32_par_offset[par_num]], val, memory_order_relaxed ) -#define PAR_SET_I8_PRIV(par_num, val) atomic_store_explicit( &gpi8_par_value[gu32_par_offset[par_num]], val, memory_order_relaxed ) -#define PAR_SET_U16_PRIV(par_num, val) atomic_store_explicit( &gpu16_par_value[gu32_par_offset[par_num]], val, memory_order_relaxed ) -#define PAR_SET_I16_PRIV(par_num, val) atomic_store_explicit( &gpi16_par_value[gu32_par_offset[par_num]], val, memory_order_relaxed ) -#define PAR_SET_U32_PRIV(par_num, val) atomic_store_explicit( &gpu32_par_value[gu32_par_offset[par_num]], val, memory_order_relaxed ) -#define PAR_SET_I32_PRIV(par_num, val) atomic_store_explicit( &gpi32_par_value[gu32_par_offset[par_num]], val, memory_order_relaxed ) - -// NOTICE: "atomic_store_explicit" does not support float data type, therfore using GCC/CLang build-in primitive "__atomic_store" to overcome this limitation -#define PAR_SET_F32_PRIV(par_num, val) __atomic_store( &gpf32_par_value[gu32_par_offset[par_num]], &val, memory_order_relaxed ) +#define PAR_GET_U8_PRIV(par_num) PAR_ATOMIC_LOAD(u8, &gpu8_par_value[gu32_par_offset[par_num]]) +#define PAR_GET_I8_PRIV(par_num) PAR_ATOMIC_LOAD(i8, &gpi8_par_value[gu32_par_offset[par_num]]) +#define PAR_GET_U16_PRIV(par_num) PAR_ATOMIC_LOAD(u16, &gpu16_par_value[gu32_par_offset[par_num]]) +#define PAR_GET_I16_PRIV(par_num) PAR_ATOMIC_LOAD(i16, &gpi16_par_value[gu32_par_offset[par_num]]) +#define PAR_GET_U32_PRIV(par_num) PAR_ATOMIC_LOAD(u32, &gpu32_par_value[gu32_par_offset[par_num]]) +#define PAR_GET_I32_PRIV(par_num) PAR_ATOMIC_LOAD(i32, &gpi32_par_value[gu32_par_offset[par_num]]) +#define PAR_GET_F32_PRIV(par_num) PAR_ATOMIC_LOAD(f32, &gpf32_par_value[gu32_par_offset[par_num]]) + +#define PAR_SET_U8_PRIV(par_num, val) PAR_ATOMIC_STORE(u8, &gpu8_par_value[gu32_par_offset[par_num]], (val)) +#define PAR_SET_I8_PRIV(par_num, val) PAR_ATOMIC_STORE(i8, &gpi8_par_value[gu32_par_offset[par_num]], (val)) +#define PAR_SET_U16_PRIV(par_num, val) PAR_ATOMIC_STORE(u16, &gpu16_par_value[gu32_par_offset[par_num]], (val)) +#define PAR_SET_I16_PRIV(par_num, val) PAR_ATOMIC_STORE(i16, &gpi16_par_value[gu32_par_offset[par_num]], (val)) +#define PAR_SET_U32_PRIV(par_num, val) PAR_ATOMIC_STORE(u32, &gpu32_par_value[gu32_par_offset[par_num]], (val)) +#define PAR_SET_I32_PRIV(par_num, val) PAR_ATOMIC_STORE(i32, &gpi32_par_value[gu32_par_offset[par_num]], (val)) +#define PAR_SET_F32_PRIV(par_num, val) PAR_ATOMIC_STORE(f32, &gpf32_par_value[gu32_par_offset[par_num]], (val)) #if ( PAR_CFG_DEBUG_EN ) @@ -1177,17 +1169,17 @@ par_status_t par_bitand_set_u8_fast(const par_num_t par_num, const uint8_t val) if ( val > range.max.u8 ) { - atomic_fetch_and_explicit( &gpu8_par_value[gu32_par_offset[par_num]], range.max.u8, memory_order_relaxed ); + PAR_ATOMIC_FETCH_AND(u8, &gpu8_par_value[gu32_par_offset[par_num]], range.max.u8); return ePAR_WAR_LIMITED; } else if ( val < range.min.u8 ) { - atomic_fetch_and_explicit( &gpu8_par_value[gu32_par_offset[par_num]], range.min.u8, memory_order_relaxed ); + PAR_ATOMIC_FETCH_AND(u8, &gpu8_par_value[gu32_par_offset[par_num]], range.min.u8); return ePAR_WAR_LIMITED; } else { - atomic_fetch_and_explicit( &gpu8_par_value[gu32_par_offset[par_num]], val, memory_order_relaxed ); + PAR_ATOMIC_FETCH_AND(u8, &gpu8_par_value[gu32_par_offset[par_num]], val); return ePAR_OK; } } @@ -1210,17 +1202,17 @@ par_status_t par_bitand_set_u16_fast(const par_num_t par_num, const uint16_t val if ( val > range.max.u16 ) { - atomic_fetch_and_explicit( &gpu16_par_value[gu32_par_offset[par_num]], range.max.u16, memory_order_relaxed ); + PAR_ATOMIC_FETCH_AND(u16, &gpu16_par_value[gu32_par_offset[par_num]], range.max.u16); return ePAR_WAR_LIMITED; } else if ( val < range.min.u16 ) { - atomic_fetch_and_explicit( &gpu16_par_value[gu32_par_offset[par_num]], range.min.u16, memory_order_relaxed ); + PAR_ATOMIC_FETCH_AND(u16, &gpu16_par_value[gu32_par_offset[par_num]], range.min.u16); return ePAR_WAR_LIMITED; } else { - atomic_fetch_and_explicit( &gpu16_par_value[gu32_par_offset[par_num]], val, memory_order_relaxed ); + PAR_ATOMIC_FETCH_AND(u16, &gpu16_par_value[gu32_par_offset[par_num]], val); return ePAR_OK; } } @@ -1243,17 +1235,17 @@ par_status_t par_bitand_set_u32_fast(const par_num_t par_num, const uint32_t val if ( val > range.max.u32 ) { - atomic_fetch_and_explicit( &gpu32_par_value[gu32_par_offset[par_num]], range.max.u32, memory_order_relaxed ); + PAR_ATOMIC_FETCH_AND(u32, &gpu32_par_value[gu32_par_offset[par_num]], range.max.u32); return ePAR_WAR_LIMITED; } else if ( val < range.min.u32 ) { - atomic_fetch_and_explicit( &gpu32_par_value[gu32_par_offset[par_num]], range.min.u32, memory_order_relaxed ); + PAR_ATOMIC_FETCH_AND(u32, &gpu32_par_value[gu32_par_offset[par_num]], range.min.u32); return ePAR_WAR_LIMITED; } else { - atomic_fetch_and_explicit( &gpu32_par_value[gu32_par_offset[par_num]], val, memory_order_relaxed ); + PAR_ATOMIC_FETCH_AND(u32, &gpu32_par_value[gu32_par_offset[par_num]], val); return ePAR_OK; } } @@ -1276,17 +1268,17 @@ par_status_t par_bitor_set_u8_fast(const par_num_t par_num, const uint8_t val) if ( val > range.max.u8 ) { - atomic_fetch_or_explicit( &gpu8_par_value[gu32_par_offset[par_num]], range.max.u8, memory_order_relaxed ); + PAR_ATOMIC_FETCH_OR(u8, &gpu8_par_value[gu32_par_offset[par_num]], range.max.u8); return ePAR_WAR_LIMITED; } else if ( val < range.min.u8 ) { - atomic_fetch_or_explicit( &gpu8_par_value[gu32_par_offset[par_num]], range.min.u8, memory_order_relaxed ); + PAR_ATOMIC_FETCH_OR(u8, &gpu8_par_value[gu32_par_offset[par_num]], range.min.u8); return ePAR_WAR_LIMITED; } else { - atomic_fetch_or_explicit( &gpu8_par_value[gu32_par_offset[par_num]], val, memory_order_relaxed ); + PAR_ATOMIC_FETCH_OR(u8, &gpu8_par_value[gu32_par_offset[par_num]], val); return ePAR_OK; } } @@ -1309,17 +1301,17 @@ par_status_t par_bitor_set_u16_fast(const par_num_t par_num, const uint16_t val) if ( val > range.max.u16 ) { - atomic_fetch_or_explicit( &gpu16_par_value[gu32_par_offset[par_num]], range.max.u16, memory_order_relaxed ); + PAR_ATOMIC_FETCH_OR(u16, &gpu16_par_value[gu32_par_offset[par_num]], range.max.u16); return ePAR_WAR_LIMITED; } else if ( val < range.min.u16 ) { - atomic_fetch_or_explicit( &gpu16_par_value[gu32_par_offset[par_num]], range.min.u16, memory_order_relaxed ); + PAR_ATOMIC_FETCH_OR(u16, &gpu16_par_value[gu32_par_offset[par_num]], range.min.u16); return ePAR_WAR_LIMITED; } else { - atomic_fetch_or_explicit( &gpu16_par_value[gu32_par_offset[par_num]], val, memory_order_relaxed ); + PAR_ATOMIC_FETCH_OR(u16, &gpu16_par_value[gu32_par_offset[par_num]], val); return ePAR_OK; } } @@ -1342,17 +1334,17 @@ par_status_t par_bitor_set_u32_fast(const par_num_t par_num, const uint32_t val) if ( val > range.max.u32 ) { - atomic_fetch_or_explicit( &gpu32_par_value[gu32_par_offset[par_num]], range.max.u32, memory_order_relaxed ); + PAR_ATOMIC_FETCH_OR(u32, &gpu32_par_value[gu32_par_offset[par_num]], range.max.u32); return ePAR_WAR_LIMITED; } else if ( val < range.min.u32 ) { - atomic_fetch_or_explicit( &gpu32_par_value[gu32_par_offset[par_num]], range.min.u32, memory_order_relaxed ); + PAR_ATOMIC_FETCH_OR(u32, &gpu32_par_value[gu32_par_offset[par_num]], range.min.u32); return ePAR_WAR_LIMITED; } else { - atomic_fetch_or_explicit( &gpu32_par_value[gu32_par_offset[par_num]], val, memory_order_relaxed ); + PAR_ATOMIC_FETCH_OR(u32, &gpu32_par_value[gu32_par_offset[par_num]], val); return ePAR_OK; } } diff --git a/src/par_atomic.h b/src/par_atomic.h new file mode 100644 index 0000000..6eb0141 --- /dev/null +++ b/src/par_atomic.h @@ -0,0 +1,247 @@ +// Copyright (c) 2026 Ziga Miklosic +// All Rights Reserved +// This software is under MIT licence (https://opensource.org/licenses/MIT) +//////////////////////////////////////////////////////////////////////////////// +/** +*@file par_atomic.h +*@brief Atomic operations API macros and type declarations +*@author wdfk-prog +*@email 1425075683@qq.com +*@date 09.03.2026 +*@version V3.0.1 +*@details This header provides atomic type aliases and helper macros for load, +* store, fetch-and, and fetch-or operations. It supports either the +* C11 atomic backend or a port-specific backend selected by +* PAR_ATOMIC_BACKEND. +*/ + +#ifndef PAR_ATOMIC_H +#define PAR_ATOMIC_H + +#include + +//////////////////////////////////////////////////////////////////////////////// +// Definitions +//////////////////////////////////////////////////////////////////////////////// + +/** + * C11 atomic backend selector + */ +#define PAR_ATOMIC_BACKEND_C11 1 + +/** + * Port-specific atomic backend selector + */ +#define PAR_ATOMIC_BACKEND_PORT 2 + +#ifndef PAR_ATOMIC_BACKEND + /** + * Default atomic backend selection + */ + #define PAR_ATOMIC_BACKEND PAR_ATOMIC_BACKEND_C11 +#endif + +/** + * List of integral types supported by atomic load/store helpers + * + * @param[in] X - Macro invoked as X(tag, type) + */ +#define PAR_ATOMIC_INTEGRAL_TYPE_LIST(X) \ + X(u8, uint8_t) \ + X(i8, int8_t) \ + X(u16, uint16_t) \ + X(i16, int16_t) \ + X(u32, uint32_t) \ + X(i32, int32_t) + +/** + * List of all scalar types supported by atomic helpers + * + * @param[in] X - Macro invoked as X(tag, type) + */ +#define PAR_ATOMIC_TYPE_LIST(X) \ + PAR_ATOMIC_INTEGRAL_TYPE_LIST(X) \ + X(f32, float) + +/** + * List of types supported by atomic fetch-and and fetch-or helpers + * + * @param[in] X - Macro invoked as X(tag, type) + */ +#define PAR_ATOMIC_FETCH_TYPE_LIST(X) \ + X(u8, uint8_t) \ + X(u16, uint16_t) \ + X(u32, uint32_t) + +#if (PAR_ATOMIC_BACKEND == PAR_ATOMIC_BACKEND_C11) + +#include + + /** + * Declare atomic typedef for selected scalar type + * + * @param[in] tag - Type tag suffix + * @param[in] type - Scalar type wrapped by _Atomic + */ + #define PAR_ATOMIC_DECLARE_TYPE(tag, type) \ + typedef _Atomic type par_atomic_##tag##_t; + +PAR_ATOMIC_TYPE_LIST(PAR_ATOMIC_DECLARE_TYPE) + + #undef PAR_ATOMIC_DECLARE_TYPE + + /** + * Define atomic load and store helper functions + * + * @param[in] tag - Type tag suffix + * @param[in] type - Scalar type of generated helpers + */ + #define PAR_ATOMIC_DEFINE_LOAD_STORE(tag, type) \ + static inline type par_atomic_load_##tag(const par_atomic_##tag##_t *ptr) \ + { \ + return atomic_load_explicit(ptr, memory_order_relaxed); \ + } \ + \ + static inline void par_atomic_store_##tag(par_atomic_##tag##_t *ptr, \ + type value) \ + { \ + atomic_store_explicit(ptr, value, memory_order_relaxed); \ + } + +PAR_ATOMIC_INTEGRAL_TYPE_LIST(PAR_ATOMIC_DEFINE_LOAD_STORE) + + #undef PAR_ATOMIC_DEFINE_LOAD_STORE + +//////////////////////////////////////////////////////////////////////////////// +/** +* Load floating-point atomic value +* +* @note "atomic_load_explicit" does not support float data type in this +* implementation, therefore GCC/Clang built-in primitive +* "__atomic_load" is used instead. +* +* @param[in] ptr - Pointer to atomic floating-point object +* @return value - Current floating-point value +*/ +//////////////////////////////////////////////////////////////////////////////// +static inline float par_atomic_load_f32(const par_atomic_f32_t *ptr) +{ + float value; + + __atomic_load(ptr, &value, __ATOMIC_RELAXED); + + return value; +} + +//////////////////////////////////////////////////////////////////////////////// +/** +* Store floating-point atomic value +* +* @note "atomic_store_explicit" does not support float data type in this +* implementation, therefore GCC/Clang built-in primitive +* "__atomic_store" is used instead. +* +* @param[in] ptr - Pointer to atomic floating-point object +* @param[in] value - Value to store +* @return void +*/ +//////////////////////////////////////////////////////////////////////////////// +static inline void par_atomic_store_f32(par_atomic_f32_t *ptr, float value) +{ + __atomic_store(ptr, &value, __ATOMIC_RELAXED); +} + + /** + * Define atomic fetch-and helper function + * + * @param[in] tag - Type tag suffix + * @param[in] type - Scalar type of generated helper + */ + #define PAR_ATOMIC_DEFINE_FETCH_AND(tag, type) \ + static inline type par_atomic_fetch_and_##tag(par_atomic_##tag##_t *ptr, \ + type value) \ + { \ + return atomic_fetch_and_explicit(ptr, value, memory_order_relaxed); \ + } + +PAR_ATOMIC_FETCH_TYPE_LIST(PAR_ATOMIC_DEFINE_FETCH_AND) + + #undef PAR_ATOMIC_DEFINE_FETCH_AND + + /** + * Define atomic fetch-or helper function + * + * @param[in] tag - Type tag suffix + * @param[in] type - Scalar type of generated helper + */ + #define PAR_ATOMIC_DEFINE_FETCH_OR(tag, type) \ + static inline type par_atomic_fetch_or_##tag(par_atomic_##tag##_t *ptr, \ + type value) \ + { \ + return atomic_fetch_or_explicit(ptr, value, memory_order_relaxed); \ + } + +PAR_ATOMIC_FETCH_TYPE_LIST(PAR_ATOMIC_DEFINE_FETCH_OR) + + #undef PAR_ATOMIC_DEFINE_FETCH_OR + +#elif (PAR_ATOMIC_BACKEND == PAR_ATOMIC_BACKEND_PORT) + +#include "../../par_atomic_port.h" + +#else + + #error "Unsupported PAR_ATOMIC_BACKEND" + +#endif + +//////////////////////////////////////////////////////////////////////////////// +/** +* Load atomic value by type tag +* +* @param[in] tag - Type tag suffix +* @param[in] ptr - Pointer to atomic object +* @return value - Current atomic value +*/ +//////////////////////////////////////////////////////////////////////////////// +#define PAR_ATOMIC_LOAD(tag, ptr) par_atomic_load_##tag((ptr)) + +//////////////////////////////////////////////////////////////////////////////// +/** +* Store atomic value by type tag +* +* @param[in] tag - Type tag suffix +* @param[in] ptr - Pointer to atomic object +* @param[in] value - Value to store +* @return void +*/ +//////////////////////////////////////////////////////////////////////////////// +#define PAR_ATOMIC_STORE(tag, ptr, value) par_atomic_store_##tag((ptr), (value)) + +//////////////////////////////////////////////////////////////////////////////// +/** +* Perform atomic fetch-and by type tag +* +* @param[in] tag - Type tag suffix +* @param[in] ptr - Pointer to atomic object +* @param[in] value - Operand for bitwise AND +* @return value - Previous atomic value +*/ +//////////////////////////////////////////////////////////////////////////////// +#define PAR_ATOMIC_FETCH_AND(tag, ptr, value) \ + par_atomic_fetch_and_##tag((ptr), (value)) + +//////////////////////////////////////////////////////////////////////////////// +/** +* Perform atomic fetch-or by type tag +* +* @param[in] tag - Type tag suffix +* @param[in] ptr - Pointer to atomic object +* @param[in] value - Operand for bitwise OR +* @return value - Previous atomic value +*/ +//////////////////////////////////////////////////////////////////////////////// +#define PAR_ATOMIC_FETCH_OR(tag, ptr, value) \ + par_atomic_fetch_or_##tag((ptr), (value)) + +#endif \ No newline at end of file From 63fab6271c179841139f06d4e1830f65154fed4b Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Mon, 9 Mar 2026 17:51:24 +0800 Subject: [PATCH 02/36] feat(parameter): Add parameter value change detection function A parameter value change detection function, par_is_value_changed, protected by the conditional compilation macro PAR_CFG_NVM_EN, has been added. This function is used to determine whether the parameter value has changed. Meanwhile, this function has been integrated into the par_check_table_validy function, and corresponding preprocessing directives for inclusion control have been added. --- src/par.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/par.c b/src/par.c index 8609565..e1e9af0 100644 --- a/src/par.c +++ b/src/par.c @@ -117,7 +117,9 @@ static uint32_t gu32_par_offset[ ePAR_NUM_OF ] = { 0 }; //////////////////////////////////////////////////////////////////////////////// static void par_allocate_ram_space (void); static par_status_t par_check_table_validy (const par_cfg_t * const p_par_cfg); +#if ( 1 == PAR_CFG_NVM_EN ) static bool par_is_value_changed (const par_num_t par_num, const void * p_val); +#endif /* ( 1 == PAR_CFG_NVM_EN ) */ //////////////////////////////////////////////////////////////////////////////// // Functions @@ -266,6 +268,7 @@ static par_status_t par_check_table_validy(const par_cfg_t * const p_par_cfg) return status; } +#if ( 1 == PAR_CFG_NVM_EN ) //////////////////////////////////////////////////////////////////////////////// /** * Is parameter value changed @@ -317,6 +320,7 @@ static bool par_is_value_changed(const par_num_t par_num, const void * p_val) return value_changed; } +#endif /* ( 1 == PAR_CFG_NVM_EN ) */ //////////////////////////////////////////////////////////////////////////////// /** From 316f538ee07c14e0249951593d046452683349b5 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Wed, 11 Mar 2026 11:46:46 +0800 Subject: [PATCH 03/36] perf(core): Optimize parameter ID lookup with hash map --- README.md | 258 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- src/par.c | 160 +++++++++++++++++++++++++++------ 2 files changed, 385 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index a447dfb..7e8725b 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,251 @@ In order to be part of *General Embedded C Libraries Ecosystem* this module must root/middleware/parameters/parameters/"module_space" ``` + +## Parameter identification + +Each parameter defined in the configuration table contains two identifiers: + +- **par_num** – internal parameter index (enumeration) +- **id** – external parameter identifier + +These identifiers serve different purposes and are intentionally separated. + +### Internal identifier (`par_num`) + +`par_num` is the enumeration defined in `par_cfg.h` and represents the **internal index of a parameter**. + +It is primarily used inside firmware code and by the parameter module APIs. + +Example: + +```c +par_set(ePAR_TEST_U8, &value); +```` + +Characteristics: + +* used as an **index into the parameter configuration table** +* provides **fast and type-safe access** inside firmware +* may change if parameters are reordered or new parameters are added + +Because of this, `par_num` should generally be used **only inside firmware code**. + +### External identifier (`id`) + +Each parameter also defines a unique **ID** in the configuration table: + +```c +[ePAR_TEST_U8] = { + .id = 0, + ... +} +``` + +The **ID is intended for external access** to parameters. + +Typical use cases include: + +* CLI commands +* PC configuration tools +* communication protocols (UART / CAN / etc.) +* parameter import/export +* diagnostics or logging + +To support these use cases, the module provides APIs that operate using parameter IDs: + +* `par_set_by_id()` +* `par_get_by_id()` +* `par_save_by_id()` + +Internally, these functions resolve the ID to the corresponding `par_num` before accessing the parameter. + +### Design rationale + +Separating internal and external identifiers provides several advantages: + +* **Efficient internal access** through `par_num` +* **Stable external interface** through `id` +* the ability to **reorder or extend parameters without breaking external tools** + +External systems should always reference parameters by **ID**, while firmware code should typically use **par_num**. + +### ID allocation guidelines + +Parameter IDs must be **unique across the entire parameter table**. + +IDs do not need to be sequential, but it is recommended to group them by subsystem for clarity. + +Example allocation: + +| ID Range | Subsystem | +| -------- | ----------------- | +| 0–99 | Channel 1 | +| 100–199 | Channel 2 | +| 200–299 | Channel 3 | +| 300–399 | Channel 4 | +| 10000+ | System parameters | + +This approach simplifies integration with external tools and communication protocols. + +## ID lookup using hash map + +To improve parameter lookup by **ID**, the module builds a runtime hash map during `par_init()`. + +This hash map is used by APIs such as: + +- `par_get_num_by_id()` +- `par_set_by_id()` +- `par_get_by_id()` +- `par_save_by_id()` + +Instead of scanning the full parameter table for every ID lookup, the module hashes the parameter ID and directly maps it to the corresponding `par_num`. + +### Why a hash map is used + +The parameter module supports two access paths: + +- **`par_num`** for internal firmware access +- **`id`** for external access such as CLI, PC tools, and communication protocols + +Internal access by `par_num` is naturally efficient because it uses the parameter enumeration as a direct table index. + +External access by **ID** is different. Since IDs are user-defined and do not need to be sequential, converting an ID back to `par_num` would otherwise require a linear search through the full parameter table. + +A hash map avoids that cost and provides near constant-time lookup for ID-based APIs. + +### How it works + +During `par_init()`, the module: + +1. walks through the parameter configuration table +2. hashes each parameter ID into a bucket index +3. stores the mapping: + +```text +ID -> par_num +```` + +Later, when an external API uses an ID, the module: + +1. hashes the requested ID +2. checks the corresponding bucket +3. returns the mapped `par_num` +4. performs the actual parameter operation internally + +Conceptually: + +```text +External ID + | + v + hash(id) + | + v + hash bucket + | + v + par_num + | + v + internal parameter API +``` + +### Why this is better than linear search + +Compared to a linear scan of the parameter table, the hash map provides: + +* lower lookup latency +* predictable runtime +* better scalability as the number of parameters grows +* lower overhead for frequently used ID-based APIs + +This is especially useful when parameters are accessed repeatedly from: + +* CLI commands +* host tools +* diagnostic services +* communication stacks + +### Why this is preferred over binary search + +Binary search would require the parameter table to be sorted by ID or an additional sorted lookup table. + +That introduces extra maintenance constraints and reduces flexibility in parameter definition order. + +The hash-based approach keeps the configuration table simple and preserves fast ID lookup without requiring sorted IDs. + +### Collision policy + +This implementation uses a strict one-entry-per-bucket hash map. + +That means: + +* duplicate IDs are rejected +* hash collisions are also rejected during initialization + +If two different IDs map to the same hash bucket, initialization fails and a debug message is printed. + +This design keeps runtime lookup logic simple, fast, and deterministic. + +### What to do if a hash collision is reported + +If initialization prints a message such as: + +```text +ERR, Hash collision: ID X conflicts with ID Y at bucket Z! +ERR, Please regenerate IDs or adjust hash parameters. +``` + +then two different parameter IDs were mapped to the same hash bucket. + +Recommended actions: + +1. change one or more parameter IDs so they no longer collide +2. keep subsystem-based ID allocation, but avoid problematic values + +In practice, the preferred solution is to **regenerate or reassign the conflicting IDs**. + +### Recommended usage + +When defining parameter IDs manually: + +* keep IDs unique across the entire table +* keep IDs stable across firmware versions +* group IDs by subsystem when possible +* avoid changing IDs unless external compatibility is intentionally broken + +### Notes for generated parameter tables + +In future workflows, parameter definitions and IDs can be generated by script tools. + +When IDs are generated automatically, collisions can be checked during generation, which means: + +* duplicate IDs can be prevented before build time +* hash collisions can be avoided before firmware is compiled +* the runtime hash map remains simple and fast +* manual ID maintenance is reduced + +With script-generated parameter tables, hash collision problems should normally not occur. + +### Design tradeoff + +This implementation intentionally favors: + +* **fast lookup** +* **simple runtime logic** +* **deterministic behavior** + +over: + +* runtime collision resolution +* more complex lookup structures + +Compared to alternatives such as linear search, linear probing, or maintaining a sorted structure for binary search, this approach gives better lookup performance for normal operation. + +The main tradeoff is that a rare hash collision must be resolved by reassigning IDs or regenerating the parameter table. For this module, that tradeoff is considered better than paying additional runtime cost or complexity on every ID lookup. + + ## **API** | API Functions | Description | Prototype | | --- | ----------- | ----- | @@ -221,12 +466,13 @@ static const par_cfg_t g_par_table[ePAR_NUM_OF] = | Configuration | Description | | --- | --- | -| **PAR_CFG_NVM_EN** | Enable/Disable usage of NVM for persistant parameters. | -| **PAR_CFG_NVM_REGION** | Select NVM region for Device Parameter storage space. | -| **PAR_CFG_DEBUG_EN** | Enable/Disable debugging mode. | -| **PAR_CFG_ASSERT_EN** | Enable/Disable asserts. Shall be disabled in release build! | -| **PAR_DBG_PRINT** | Definition of debug print. | -| **PAR_ASSERT** | Definition of assert. | +| **PAR_CFG_NVM_EN** | Enable/Disable usage of NVM for persistant parameters. | +| **PAR_CFG_NVM_REGION** | Select NVM region for Device Parameter storage space. | +| **PAR_CFG_DEBUG_EN** | Enable/Disable debugging mode. | +| **PAR_CFG_ASSERT_EN** | Enable/Disable asserts. Shall be disabled in release build! | +| **PAR_DBG_PRINT** | Definition of debug print. | +| **PAR_ASSERT** | Definition of assert. | +| **PAR_ATOMIC_BACKEND** | Select atomic backend implementation. | **5. Call **par_init()** function** diff --git a/src/par.c b/src/par.c index e1e9af0..8964802 100644 --- a/src/par.c +++ b/src/par.c @@ -34,6 +34,43 @@ //////////////////////////////////////////////////////////////////////////////// // Definitions //////////////////////////////////////////////////////////////////////////////// +/* + * http://www.citi.umich.edu/techreports/reports/citi-tr-00-1.pdf + * + * GoldenRatio = ~(Math.pow(2, 32) / ((Math.sqrt(5) - 1) / 2)) + 1 + */ +#define PAR_ID_HASH_GOLDEN_RATIO_32 ( 0x61C88647u ) + +/** + * Minimum number of hash buckets to keep target load factor <= 0.5. + */ +#define PAR_ID_HASH_MIN_BUCKETS ((uint32_t)(2u * (uint32_t)ePAR_NUM_OF)) + +/** + * Hash map geometry derived from ePAR_NUM_OF at compile time. + */ +enum +{ + PAR_ID_HASH_BITS = + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 1 )) ? 1u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 2 )) ? 2u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 3 )) ? 3u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 4 )) ? 4u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 5 )) ? 5u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 6 )) ? 6u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 7 )) ? 7u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 8 )) ? 8u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 9 )) ? 9u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 10 )) ? 10u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 11 )) ? 11u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 12 )) ? 12u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 13 )) ? 13u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 14 )) ? 14u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 15 )) ? 15u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 16 )) ? 16u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 17 )) ? 17u : 18u, + PAR_ID_HASH_SIZE = ( 1u << PAR_ID_HASH_BITS ), +}; //////////////////////////////////////////////////////////////////////////////// // Variables @@ -53,6 +90,26 @@ static struct pf_par_on_change_cb_t on_change; /**< On change callback function (or NULL). */ } g_par_cb_table[ePAR_NUM_OF]; +/** + * ID hash map entry. + */ +typedef struct +{ + uint16_t id; + par_num_t par_num; + uint8_t used; +} par_id_map_entry_t; + +/** + * Runtime ID hash map. + */ +static par_id_map_entry_t g_par_id_map[PAR_ID_HASH_SIZE] = {0}; + +/** + * Initialization guard for ID hash map. + */ +static bool gb_par_id_map_ready = false; + /** * Parameter live values divided by its type in RAM */ @@ -115,8 +172,10 @@ static uint32_t gu32_par_offset[ ePAR_NUM_OF ] = { 0 }; //////////////////////////////////////////////////////////////////////////////// // Function Prototypes //////////////////////////////////////////////////////////////////////////////// -static void par_allocate_ram_space (void); -static par_status_t par_check_table_validy (const par_cfg_t * const p_par_cfg); +static void par_allocate_ram_space (void); +static inline uint32_t par_hash_id (const uint16_t id); +static par_status_t par_build_and_validate_id_map (const par_cfg_t * const p_par_cfg); +static par_status_t par_check_table_validy (const par_cfg_t * const p_par_cfg); #if ( 1 == PAR_CFG_NVM_EN ) static bool par_is_value_changed (const par_num_t par_num, const void * p_val); #endif /* ( 1 == PAR_CFG_NVM_EN ) */ @@ -197,6 +256,62 @@ static void par_allocate_ram_space(void) PAR_DBG_PRINT( "Total RAM consumption for parameters value: %d bytes", total_size ); } +//////////////////////////////////////////////////////////////////////////////// +/** +* Hash parameter ID to bucket index +* +* @param[in] id - Parameter ID +* @return hash index +*/ +//////////////////////////////////////////////////////////////////////////////// +static inline uint32_t par_hash_id(const uint16_t id) +{ + return (((uint32_t) id * PAR_ID_HASH_GOLDEN_RATIO_32 ) >> ( 32u - PAR_ID_HASH_BITS )); +} + +//////////////////////////////////////////////////////////////////////////////// +/** +* Build and validate parameter ID hash map +* +* @param[in] p_par_cfg - Pointer to parameters table +* @return status - Status of operation +*/ +//////////////////////////////////////////////////////////////////////////////// +static par_status_t par_build_and_validate_id_map(const par_cfg_t * const p_par_cfg) +{ + PAR_ASSERT( PAR_ID_HASH_SIZE >= PAR_ID_HASH_MIN_BUCKETS ); + memset( g_par_id_map, 0, sizeof(g_par_id_map) ); + for ( par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) + { + const uint16_t id = p_par_cfg[par_num].id; + const uint32_t bucket_idx = par_hash_id( id ); + par_id_map_entry_t * const bucket = &g_par_id_map[bucket_idx]; + + if ( 0u == bucket->used ) + { + bucket->used = 1u; + bucket->id = id; + bucket->par_num = par_num; + continue; + } + + if ( bucket->id == id ) + { + PAR_DBG_PRINT( "ERR, Duplicate parameter ID %u!", (unsigned) id ); + PAR_ASSERT( 0 ); + return ePAR_ERROR_INIT; + } + + PAR_DBG_PRINT( "ERR, Hash collision: ID %u conflicts with ID %u at bucket %u!", + (unsigned) id, (unsigned) bucket->id, (unsigned) bucket_idx ); + PAR_DBG_PRINT( "ERR, Please regenerate IDs or adjust hash parameters." ); + PAR_ASSERT( 0 ); + return ePAR_ERROR_INIT; + } + + return ePAR_OK; +} + //////////////////////////////////////////////////////////////////////////////// /** * Check that parameter table is correctly defined @@ -209,25 +324,16 @@ static par_status_t par_check_table_validy(const par_cfg_t * const p_par_cfg) { par_status_t status = ePAR_OK; + // Build and validate runtime ID hash map + status = par_build_and_validate_id_map( p_par_cfg ); + if ( ePAR_OK != status ) + { + return status; + } + // For each parameter for ( uint32_t i = 0; i < ePAR_NUM_OF; i++ ) { - // Compare parameters IDs - for ( uint32_t j = 0; j < ePAR_NUM_OF; j++ ) - { - if ( i != j ) - { - // Check for two identical IDs - if ( p_par_cfg[i].id == p_par_cfg[j].id ) - { - status = ePAR_ERROR_INIT; - PAR_DBG_PRINT( "ERR, Two parameters have the same ID %d!", p_par_cfg[i].id ); - PAR_ASSERT( 0 ); - break; - } - } - } - /** * Check for correct MIN, MAX and DEF value definitions * @@ -369,6 +475,7 @@ par_status_t par_init(void) if ( ePAR_OK == status ) { gb_is_init = true; + gb_par_id_map_ready = true; // Set all parameters to default par_set_all_to_default(); @@ -405,6 +512,7 @@ par_status_t par_deinit(void) // Module de-initialized gb_is_init = false; + gb_par_id_map_ready = false; return status; } @@ -1942,17 +2050,15 @@ bool par_is_persistant(const par_num_t par_num) //////////////////////////////////////////////////////////////////////////////// par_status_t par_get_num_by_id(const uint16_t id, par_num_t * const p_par_num) { - if ( NULL != p_par_num ) + if (( NULL != p_par_num ) && ( true == gb_par_id_map_ready )) { - for (par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) - { - const par_cfg_t * const par_cfg = par_get_config(par_num); + const uint32_t bucket_idx = par_hash_id( id ); + const par_id_map_entry_t * const bucket = &g_par_id_map[bucket_idx]; - if (( NULL != par_cfg ) && ( id == par_cfg->id )) - { - *p_par_num = par_num; - return ePAR_OK; - } + if (( 0u != bucket->used ) && ( id == bucket->id )) + { + *p_par_num = bucket->par_num; + return ePAR_OK; } } From f618c26b0d9b33e3218fc29c4c1fa9ea0f936fda Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Thu, 12 Mar 2026 13:30:30 +0800 Subject: [PATCH 04/36] refactor(core): Reorganize parameter manager into core and port layers --- README.md | 272 +++++++++++++++++++++++++-- src/par.c | 6 +- src/par.h | 2 +- src/par_cfg.h | 246 ++++++++++++++++++++++++ template/par_if.ctmp => src/par_if.c | 118 ++++++------ src/par_if.h | 42 +++++ src/par_nvm.c | 4 +- template/par_cfg.ctmp | 6 +- template/par_cfg.htmp | 94 +-------- template/par_cfg_port.htmp | 4 + template/par_def.ctmp | 271 ++++++++++++++++++++++++++ template/par_def.htmp | 256 +++++++++++++++++++++++++ 12 files changed, 1149 insertions(+), 172 deletions(-) create mode 100644 src/par_cfg.h rename template/par_if.ctmp => src/par_if.c (62%) create mode 100644 src/par_if.h create mode 100644 template/par_cfg_port.htmp create mode 100644 template/par_def.ctmp create mode 100644 template/par_def.htmp diff --git a/README.md b/README.md index 7e8725b..36f79fc 100644 --- a/README.md +++ b/README.md @@ -40,14 +40,68 @@ NVM module must take following path: "root/middleware/nvm/nvm/src/nvm.h" ``` -### **2. C11 compiler support** -Parameter module utilize C11 *_Atomic* and *_Generic* features, therefore make sure your compiler supports C11 primitives. +### **2. Platform adaptation layer** + +This package separates the core parameter logic from platform-specific integration. + +Platform-dependent configuration and hooks are provided through the `port/` layer: + +* `port/par_cfg_port.h` – platform configuration bridge +* `port/par_if_port.c` – platform interface backend +* `port/par_atomic_port.h` – platform atomic backend + +This keeps the core module portable while allowing integration with RTOS, mutex, logging, assertion, and atomic services provided by the target platform. + +> Note: `parameters/src/par_cfg.h` includes `par_cfg_port.h` unconditionally. +> You must provide this header in your project include path. +> If no platform override is required, provide an empty stub `par_cfg_port.h` with include guard. + +### **3. Atomic backend configuration** + +The module requires an atomic backend for parameter value access. + +By default, it uses the C11 atomic backend. If your compiler does not provide usable C11 atomics, or if your platform already provides its own atomic API, you can switch the module to a port-specific backend through `par_atomic_port.h`. + +#### When to use `par_atomic_port.h` + +Use `par_atomic_port.h` when: + +- the compiler does not fully support `` +- the target platform already provides atomic primitives +- you want the parameter module to use the RTOS or platform-native atomic implementation + +#### How to enable `par_atomic_port.h` + +Atomic backend selection is controlled by `PAR_ATOMIC_BACKEND` in `parameters/src/par_atomic.h`. + +Available options: + +```c +#define PAR_ATOMIC_BACKEND_C11 1 +#define PAR_ATOMIC_BACKEND_PORT 2 +```` + +To use the port backend, define: + +```c +#define PAR_ATOMIC_BACKEND PAR_ATOMIC_BACKEND_PORT +``` + +After that, `par_atomic.h` will include `par_atomic_port.h` and use the port-provided atomic types and helpers. + +#### Notes + +* `par_atomic_port.h` must provide all atomic types and operations required by `par_atomic.h` +* `float32_t` should be stored and loaded by preserving its raw bit representation, not by numeric cast +* make sure the underlying atomic storage type matches the size of `float` +* keep all platform-specific atomic adaptation inside `par_atomic_port.h` so the core parameter code does not need to change ## **Limitations** - **Heap Usage:** The module uses malloc during par_init() to allocate RAM space for the parameters based on the configuration table. Ensure your heap is sufficiently sized. - **Alignment:** Address offsets are calculated based on 4-byte (32-bit) alignment to satisfy most ARM Cortex-M requirements. - **Flat ID Space:** Parameter IDs must be unique across the entire table to ensure NVM consistency. - **Execution Time:** If many callbacks are chained to a single parameter, the par_set execution time will increase accordingly. + - **Hash-Based ID Lookup:** ID-based lookup is optimized for runtime speed and rejects hash collisions during initialization. See the ID lookup section below. ## **General Embedded C Libraries Ecosystem** In order to be part of *General Embedded C Libraries Ecosystem* this module must be placed in following path: @@ -55,6 +109,153 @@ In order to be part of *General Embedded C Libraries Ecosystem* this module must root/middleware/parameters/parameters/"module_space" ``` +## Package structure + +The package is split into three layers. + +### Core layer + +Portable parameter logic is implemented under: + +```text +parameters/src/ +``` + +This layer contains the core implementation of: + +* parameter storage and typed access +* configuration lookup +* validation and callbacks +* ID lookup +* NVM integration +* atomic abstraction +* configuration abstraction + +### Port layer + +Platform-specific integration is implemented under: + +```text +port/ +``` + +This layer contains: + +* `par_cfg_port.h` – platform configuration bridge +* `par_if_port.c` – platform-specific low-level interface +* `par_atomic_port.h` – platform-specific atomic backend + +This separation keeps the core module portable while isolating platform and RTOS dependencies. + +### Template layer + +Reference templates are provided under: + +```text +parameters/template/ +``` + +This layer currently contains: + +* `par_cfg.htmp` – configuration header template (default macro layout and comments) +* `par_cfg.ctmp` – parameter table source template +* `par_cfg_port.htmp` – port bridge header template + +Template files are used as: + +* baseline references for keeping generated or manually maintained files aligned with upstream style +* starting points when creating new `par_cfg.h` / `par_cfg.c` / `par_cfg_port.h` in other projects + +Template files are not compiled directly by the runtime library. + +--- + +## Configuration model + +The package uses a two-level configuration model. + +### Core configuration + +Core configuration defaults are defined in: + +```text +parameters/src/par_cfg.h +``` + +This file provides default values for options such as: + +* `PAR_CFG_NVM_EN` +* `PAR_CFG_NVM_REGION` +* `PAR_CFG_TABLE_ID_CHECK_EN` +* `PAR_CFG_DEBUG_EN` +* `PAR_CFG_ASSERT_EN` +* `PAR_CFG_MUTEX_EN` +* `PAR_CFG_MUTEX_TIMEOUT_MS` +* `PAR_CFG_IF_PORT_EN` +* `PAR_CFG_PORT_HOOK_EN` + +### Platform bridge + +Platform-specific overrides are provided in: + +```text +port/par_cfg_port.h +``` + +This file maps platform or build-system configuration symbols to `PAR_CFG_*` options. + +`par_cfg_port.h` is mandatory for build because it is directly included by `parameters/src/par_cfg.h`. +If you do not need overrides, keep a minimal empty file: + +```c +#ifndef _PAR_CFG_PORT_H_ +#define _PAR_CFG_PORT_H_ +/* Optional platform overrides */ +#endif +``` + +### Why this split exists + +This design keeps the core implementation independent of any single build system or RTOS, while still allowing package-level integration through a dedicated platform bridge. + +--- + +## Port hooks + +When `PAR_CFG_PORT_HOOK_EN = 1`, the module uses platform-provided hooks for: + +* logging +* assertions +* compile-time assertions + +The following hooks may be provided by the port layer: + +* `PAR_PORT_LOG(...)` +* `PAR_PORT_ASSERT(x)` +* `PAR_PORT_STATIC_ASSERT(name, expn)` + +This allows the module to integrate with the native debug and assert infrastructure of the target platform. + +--- + +## Interface backend + +The low-level interface layer is implemented in `parameters/src/par_if.c`. + +When `PAR_CFG_IF_PORT_EN = 1`, the module uses the platform-specific backend provided by: + +```text +port/par_if_port.c +``` + +This backend is responsible for platform-dependent services such as: + +* initialization +* mutex handling +* optional table hash calculation + +This keeps the public parameter logic independent from the RTOS or platform implementation. + ## Parameter identification @@ -377,10 +578,9 @@ The main tradeoff is that a rare hash collision must be resolved by reassigning ## Usage -**Put all user code between sections: USER CODE BEGIN & USER CODE END!** +### 1. Define parameter enumeration -**1. Copy template files to root directory of module.** -**2. List names of all wanted parameters inside **par_cfg.h** file** +Define the parameter enumeration in `par_def.h`: ```C /** @@ -394,26 +594,28 @@ The main tradeoff is that a rare hash collision must be resolved by reassigning */ typedef enum { - // USER CODE START... - - ePAR_TEST_U8 = 0, - ePAR_TEST_I8, + ePAR_TEST_U8 = 0, + ePAR_TEST_I8, - ePAR_TEST_U16, - ePAR_TEST_I16, + ePAR_TEST_U16, + ePAR_TEST_I16, - ePAR_TEST_U32, - ePAR_TEST_I32, + ePAR_TEST_U32, + ePAR_TEST_I32, - ePAR_TEST_F32, + ePAR_TEST_F32, - // USER CODE END... - - ePAR_NUM_OF + ePAR_NUM_OF } par_num_t; ``` -**3. Change parameter configuration table inside **par_cfg.c** file. It is recommended to use designated initializers.** +### 2. Define the parameter table + +Define the parameter configuration table in `par_def.c`. + +It is recommended to use designated initializers. + +```C ```C /** @@ -462,7 +664,19 @@ static const par_cfg_t g_par_table[ePAR_NUM_OF] = }; ``` -**4. Set-up all configurations options inside **par_cfg.h** file** +### 3. Configure the module + +The main package configuration is defined in: + +```text +parameters/src/par_cfg.h +``` + +Platform-specific overrides and hooks are provided in: + +```text +port/par_cfg_port.h +``` | Configuration | Description | | --- | --- | @@ -473,6 +687,26 @@ static const par_cfg_t g_par_table[ePAR_NUM_OF] = | **PAR_DBG_PRINT** | Definition of debug print. | | **PAR_ASSERT** | Definition of assert. | | **PAR_ATOMIC_BACKEND** | Select atomic backend implementation. | +| **PAR_CFG_TABLE_ID_CHECK_EN** | Enable or disable parameter table unique ID checking for NVM compatibility workflows. | +| **PAR_CFG_MUTEX_EN** | Enable or disable mutex protection. | +| **PAR_CFG_MUTEX_TIMEOUT_MS** | Mutex timeout in milliseconds. | +| **PAR_CFG_IF_PORT_EN** | Enable platform-specific `par_if` backend. | +| **PAR_CFG_PORT_HOOK_EN** | Enable platform log/assert hooks. | + +**4. Build integration** + +The package build script collects source files from: + +* package root +* `parameters/src/` + +and adds the following include paths: + +* package root +* `parameters/src/` +* `port/` + +If you add or replace port-specific files, keep them under `port/` so they remain visible to the build system. **5. Call **par_init()** function** diff --git a/src/par.c b/src/par.c index 8964802..cb5322d 100644 --- a/src/par.c +++ b/src/par.c @@ -29,7 +29,7 @@ #include "par.h" #include "par_atomic.h" #include "par_nvm.h" -#include "../../par_if.h" +#include "par_if.h" //////////////////////////////////////////////////////////////////////////////// // Definitions @@ -72,6 +72,9 @@ enum PAR_ID_HASH_SIZE = ( 1u << PAR_ID_HASH_BITS ), }; +PAR_STATIC_ASSERT(par_id_hash_size_valid, (PAR_ID_HASH_SIZE >= PAR_ID_HASH_MIN_BUCKETS)); +PAR_STATIC_ASSERT(par_id_hash_bits_valid, ((PAR_ID_HASH_BITS > 0u) && (PAR_ID_HASH_BITS < 32u))); + //////////////////////////////////////////////////////////////////////////////// // Variables //////////////////////////////////////////////////////////////////////////////// @@ -279,7 +282,6 @@ static inline uint32_t par_hash_id(const uint16_t id) //////////////////////////////////////////////////////////////////////////////// static par_status_t par_build_and_validate_id_map(const par_cfg_t * const p_par_cfg) { - PAR_ASSERT( PAR_ID_HASH_SIZE >= PAR_ID_HASH_MIN_BUCKETS ); memset( g_par_id_map, 0, sizeof(g_par_id_map) ); for ( par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) { diff --git a/src/par.h b/src/par.h index 861cb4f..657d5ba 100644 --- a/src/par.h +++ b/src/par.h @@ -29,7 +29,7 @@ #include #include -#include "../../par_cfg.h" +#include "par_cfg.h" //////////////////////////////////////////////////////////////////////////////// // Definitions diff --git a/src/par_cfg.h b/src/par_cfg.h new file mode 100644 index 0000000..b849254 --- /dev/null +++ b/src/par_cfg.h @@ -0,0 +1,246 @@ +// Copyright (c) 2026 Ziga Miklosic +// All Rights Reserved +// This software is under MIT licence (https://opensource.org/licenses/MIT) +//////////////////////////////////////////////////////////////////////////////// +/** +*@file par_cfg.h +*@brief Configuration for device parameters +*@author Ziga Miklosic +*@email ziga.miklosic@gmail.com +*@date 29.01.2026 +*@version V3.0.1 +*/ +//////////////////////////////////////////////////////////////////////////////// +/** +*@addtogroup PAR_CFG +* @{ +* +* Configuration for device parameters. +*/ +//////////////////////////////////////////////////////////////////////////////// + +#ifndef _PAR_CFG_H_ +#define _PAR_CFG_H_ + +//////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////// +#include +#include "par_def.h" + +// USER CODE BEGIN... + +/** + * Platform adaptation bridge + * + * @note Port layer may override default PAR_CFG_* settings. + * + * @note This header is included unconditionally. + * Integrator shall provide "par_cfg_port.h" in include path. + * If no platform override is needed, create an empty stub header + * with include guard (for example in port/par_cfg_port.h). + */ +#include "par_cfg_port.h" + +// USER CODE END... + +//////////////////////////////////////////////////////////////////////////////// +// Definitions +//////////////////////////////////////////////////////////////////////////////// + +/** + * Enable/Disable storing persistent parameters to NVM + */ +#ifndef PAR_CFG_NVM_EN + #define PAR_CFG_NVM_EN ( 1 ) +#endif + +/** + * NVM parameter region option + * + * @note User shall select region based on nvm_cfg.h region + * definitions "nvm_region_name_t". + * + * Don't care if "PAR_CFG_NVM_EN" set to 0. + */ +#ifndef PAR_CFG_NVM_REGION + #define PAR_CFG_NVM_REGION ( eNVM_REGION_INT_FLASH_DEV_PAR ) +#endif + +/** + * Enable/Disable parameter table unique ID checking + * + * @note Base on hash unique ID is being calculated with purpose to detect + * device and stored parameter table difference. + * + * Must be disabled once the device is released in order to prevent + * loss of calibrated data stored in NVM. + * + * @pre "PAR_CFG_NVM_EN" must be enabled otherwise it does not make sense + * to calculate ID at all. + */ +#ifndef PAR_CFG_TABLE_ID_CHECK_EN + #define PAR_CFG_TABLE_ID_CHECK_EN ( 0 ) +#endif + +#if ( 1 == PAR_CFG_NVM_EN ) + #ifndef DEBUG + #undef PAR_CFG_TABLE_ID_CHECK_EN + #define PAR_CFG_TABLE_ID_CHECK_EN ( 0 ) + #endif +#endif + +/** + * Enable/Disable debug mode + */ +#ifndef PAR_CFG_DEBUG_EN + #define PAR_CFG_DEBUG_EN ( 1 ) +#endif + +#ifndef DEBUG + #undef PAR_CFG_DEBUG_EN + #define PAR_CFG_DEBUG_EN ( 0 ) +#endif + +/** + * Enable/Disable assertions + */ +#ifndef PAR_CFG_ASSERT_EN + #define PAR_CFG_ASSERT_EN ( 1 ) +#endif + +#ifndef DEBUG + #undef PAR_CFG_ASSERT_EN + #define PAR_CFG_ASSERT_EN ( 0 ) +#endif + +/** + * Platform hook fallbacks + */ +#ifndef PAR_PORT_ASSERT + #define PAR_PORT_ASSERT(x) do { (void)(x); } while (0) +#endif + +#ifndef PAR_PORT_LOG + #define PAR_PORT_LOG(tag, ...) do { (void)(tag); } while (0) +#endif + +#ifndef PAR_PORT_STATIC_ASSERT + #define PAR_PORT_STATIC_ASSERT(name, expn) typedef char _static_assert_##name[(expn) ? 1 : -1] +#endif + +/** + * Package compile-time assert + */ +#define PAR_STATIC_ASSERT(name, expn) PAR_PORT_STATIC_ASSERT(name, expn) + +/** + * Resolve log/assert routing mode before default macro emission + */ +#if !defined(PAR_CFG_PORT_HOOK_EN) + #define PAR_CFG_USE_PORT_HOOKS ( 0 ) +#elif ( 1 == PAR_CFG_PORT_HOOK_EN ) + #define PAR_CFG_USE_PORT_HOOKS ( 1 ) +#else + #define PAR_CFG_USE_PORT_HOOKS ( 0 ) +#endif + +/** + * Debug communication port macros + */ +#if ( 0 == PAR_CFG_USE_PORT_HOOKS ) + #if ( 1 == PAR_CFG_DEBUG_EN ) + #ifndef PAR_CFG_DIRECT_LOG + #define PAR_CFG_DIRECT_LOG(...) ( cli_printf((char *) __VA_ARGS__) ) + #endif + #define PAR_DBG_PRINT(...) PAR_CFG_DIRECT_LOG(__VA_ARGS__) + #else + #define PAR_DBG_PRINT(...) { ; } + #endif +#else + #if ( 1 == PAR_CFG_DEBUG_EN ) + #define PAR_DBG_PRINT(...) PAR_PORT_LOG(__VA_ARGS__) + #else + #define PAR_DBG_PRINT(...) { ; } + #endif +#endif + +/** + * Assertion macros + */ +#if ( 0 == PAR_CFG_USE_PORT_HOOKS ) + #if ( 1 == PAR_CFG_ASSERT_EN ) + #ifndef PAR_CFG_DIRECT_ASSERT + #ifdef PROJ_CFG_ASSERT + #define PAR_CFG_DIRECT_ASSERT(x) PROJ_CFG_ASSERT(x) + #else + #define PAR_CFG_DIRECT_ASSERT(x) do { (void)(x); } while (0) + #endif + #endif + #define PAR_ASSERT(x) PAR_CFG_DIRECT_ASSERT(x) + #else + #define PAR_ASSERT(x) { ; } + #endif +#else + #if ( 1 == PAR_CFG_ASSERT_EN ) + #define PAR_ASSERT(x) PAR_PORT_ASSERT(x) + #else + #define PAR_ASSERT(x) { ; } + #endif +#endif + +#undef PAR_CFG_USE_PORT_HOOKS + +/** + * Invalid configuration catcher + * + * @note Shall be intact by end user! + */ +#if ( 0 == PAR_CFG_NVM_EN ) && ( 1 == PAR_CFG_TABLE_ID_CHECK_EN ) + #error "Parameter settings invalid: Disable table ID checking (PAR_CFG_TABLE_ID_CHECK_EN)!" +#endif + +/** + * Extended package configurations (non-template additions) + */ +#ifndef PAR_CFG_MUTEX_EN + #define PAR_CFG_MUTEX_EN ( 1 ) +#endif + +/** + * Parameter mutex timeout + * + * Unit: ms + */ +#ifndef PAR_CFG_MUTEX_TIMEOUT_MS + #define PAR_CFG_MUTEX_TIMEOUT_MS ( 10 ) +#endif + +/** + * Enable/Disable port-specific par_if backend + */ +#ifndef PAR_CFG_IF_PORT_EN + #define PAR_CFG_IF_PORT_EN ( 0 ) +#endif + +/** + * Enable/Disable port hooks for log/assert + */ +#ifndef PAR_CFG_PORT_HOOK_EN + #define PAR_CFG_PORT_HOOK_EN ( 0 ) +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Functions Prototypes +//////////////////////////////////////////////////////////////////////////////// +const void * par_cfg_get_table (void); +const void * par_cfg_get (const par_num_t par_num); +uint32_t par_cfg_get_table_size (void); + +//////////////////////////////////////////////////////////////////////////////// +/** +* @} +*/ +//////////////////////////////////////////////////////////////////////////////// + +#endif // _PAR_CFG_H_ diff --git a/template/par_if.ctmp b/src/par_if.c similarity index 62% rename from template/par_if.ctmp rename to src/par_if.c index e1132c5..a5305ee 100644 --- a/template/par_if.ctmp +++ b/src/par_if.c @@ -15,10 +15,10 @@ *@addtogroup PAR_IF * @{ * -* Interface layer for device parameters +* Interface layer for device parameters * -* Put code that is platform depended inside code block start with -* "USER_CODE_BEGIN" and with end of "USER_CODE_END". +* Put code that is platform depended inside code block start with +* "USER_CODE_BEGIN" and with end of "USER_CODE_END". */ //////////////////////////////////////////////////////////////////////////////// @@ -27,6 +27,12 @@ //////////////////////////////////////////////////////////////////////////////// #include "par_if.h" +#if ( 1 == PAR_CFG_IF_PORT_EN ) + +#include "par_if_port.c" + +#else + // USER INCLUDES BEGIN... #include "common/utils/src/utils.h" @@ -41,11 +47,11 @@ // USER DEFINITIONS BEGIN... /** - * Parameter mutex timeout + * Parameter mutex timeout * - * Unit: ms + * Unit: ms */ -#define PAR_CFG_MUTEX_TIMEOUT_MS ( 10 ) +#define PAR_CFG_MUTEX_TIMEOUT_MS ( 10 ) // USER DEFINITIONS END... @@ -56,13 +62,13 @@ // USER VARIABLES BEGIN... /** - * Parameters OS mutex + * Parameters OS mutex */ -static osMutexId_t g_par_mutex_id = NULL; +static osMutexId_t g_par_mutex_id = NULL; const osMutexAttr_t g_par_mutex_attr = { - .name = "par", - .attr_bits = ( osMutexPrioInherit ), + .name = "par", + .attr_bits = ( osMutexPrioInherit ), }; // USER VARIABLES END... @@ -73,100 +79,100 @@ const osMutexAttr_t g_par_mutex_attr = //////////////////////////////////////////////////////////////////////////////// /** -* Initialize low level interface +* Initialize low level interface * -* @note User shall provide definition of that function based on used platform! +* @note User shall provide definition of that function based on used platform! * -* @return status - Status of initialization +* @return status - Status of initialization */ //////////////////////////////////////////////////////////////////////////////// par_status_t par_if_init(void) { - par_status_t status = ePAR_OK; + par_status_t status = ePAR_OK; - // USER CODE BEGIN... + // USER CODE BEGIN... - // Create mutex - g_par_mutex_id = osMutexNew( &g_par_mutex_attr ); + // Create mutex + g_par_mutex_id = osMutexNew( &g_par_mutex_attr ); - if ( NULL == g_par_mutex_id ) - { - status = ePAR_ERROR; - } + if ( NULL == g_par_mutex_id ) + { + status = ePAR_ERROR; + } - // USER CODE END... + // USER CODE END... - return status; + return status; } //////////////////////////////////////////////////////////////////////////////// /** -* Acquire mutex for specified parameter +* Acquire mutex for specified parameter * -* @note User shall provide definition of that function based on used platform! +* @note User shall provide definition of that function based on used platform! * * If mutex is not needed leave empty space between user code begin and end. * * @param[in] par_num - Parameter number (enumeration) -* @return status - Status of operation +* @return status - Status of operation */ //////////////////////////////////////////////////////////////////////////////// par_status_t par_if_aquire_mutex(const par_num_t par_num) { - par_status_t status = ePAR_OK; + par_status_t status = ePAR_OK; - // USER CODE BEGIN... + // USER CODE BEGIN... UNUSED(par_num); - if ( osOK == osMutexAcquire( g_par_mutex_id, PAR_CFG_MUTEX_TIMEOUT_MS )) - { - // No action - } - else - { - status = ePAR_ERROR; - } - - // USER CODE END... - - return status; + if ( osOK == osMutexAcquire( g_par_mutex_id, PAR_CFG_MUTEX_TIMEOUT_MS )) + { + // No action + } + else + { + status = ePAR_ERROR; + } + + // USER CODE END... + + return status; } //////////////////////////////////////////////////////////////////////////////// /** -* Release mutex for specified parameter +* Release mutex for specified parameter * -* @note User shall provide definition of that function based on used platform! +* @note User shall provide definition of that function based on used platform! * * If mutex is not needed leave empty space between user code begin and end. * * @param[in] par_num - Parameter number (enumeration) -* @return status - Status of operation +* @return status - Status of operation */ //////////////////////////////////////////////////////////////////////////////// void par_if_release_mutex(const par_num_t par_num) { - // USER CODE BEGIN... + // USER CODE BEGIN... UNUSED(par_num); osMutexRelease( g_par_mutex_id ); - // USER CODE END... + // USER CODE END... } //////////////////////////////////////////////////////////////////////////////// /** -* Calculate hash +* Calculate hash * -* @note User shall provide definition of that function based on used platform! +* @note User shall provide definition of that function based on used platform! * -* If not being used leave empty. +* If not being used leave empty. * -* This function does not have an affect if "PAR_CFG_TABLE_ID_CHECK_EN" -* is set to 0. +* This function does not have an affect if "PAR_CFG_TABLE_ID_CHECK_EN" +* is set to 0. * -* @param[in] p_data - Pointer to data for hash calculation -* @param[in] size - Size of data in bytes -* @return p_hash - Pointer to calculated hash number +* @param[in] p_data - Pointer to data for hash calculation +* @param[in] size - Size of data in bytes +* @return p_hash - Pointer to calculated hash number */ //////////////////////////////////////////////////////////////////////////////// void par_if_calc_hash(const uint8_t * const p_data, const uint32_t size, uint8_t * const p_hash) @@ -175,11 +181,13 @@ void par_if_calc_hash(const uint8_t * const p_data, const uint32_t size, uint8_t UNUSED( p_hash ); UNUSED( size ); - // USER CODE BEGIN... + // USER CODE BEGIN... - // USER CODE END... + // USER CODE END... } +#endif /* ( 1 == PAR_CFG_IF_PORT_EN ) */ + //////////////////////////////////////////////////////////////////////////////// /** * @} diff --git a/src/par_if.h b/src/par_if.h new file mode 100644 index 0000000..838194a --- /dev/null +++ b/src/par_if.h @@ -0,0 +1,42 @@ +// Copyright (c) 2026 Ziga Miklosic +// All Rights Reserved +// This software is under MIT licence (https://opensource.org/licenses/MIT) +//////////////////////////////////////////////////////////////////////////////// +/** +*@file par_if.h +*@brief Interface for device parameters +*@author Ziga Miklosic +*@email ziga.miklosic@gmail.com +*@date 29.01.2026 +*@version V3.0.1 +*/ +//////////////////////////////////////////////////////////////////////////////// +/** +*@addtogroup PAR_IF +* @{ +*/ +//////////////////////////////////////////////////////////////////////////////// + +#ifndef _PAR_IF_H_ +#define _PAR_IF_H_ + +//////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////// +#include +#include "par.h" +#include "par_cfg.h" + +//////////////////////////////////////////////////////////////////////////////// +// Definitions +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Functions Prototypes +//////////////////////////////////////////////////////////////////////////////// +par_status_t par_if_init (void); +par_status_t par_if_aquire_mutex (const par_num_t par_num); +void par_if_release_mutex (const par_num_t par_num); +void par_if_calc_hash (const uint8_t * const p_data, const uint32_t size, uint8_t * const p_hash); + +#endif // _PAR_IF_H_ diff --git a/src/par_nvm.c b/src/par_nvm.c index 19465bc..84f3851 100644 --- a/src/par_nvm.c +++ b/src/par_nvm.c @@ -54,8 +54,8 @@ // Includes //////////////////////////////////////////////////////////////////////////////// #include "par_nvm.h" -#include "../../par_cfg.h" -#include "../../par_if.h" +#include "par_cfg.h" +#include "par_if.h" #if ( 1 == PAR_CFG_NVM_EN ) diff --git a/template/par_cfg.ctmp b/template/par_cfg.ctmp index 06a4e04..368c972 100644 --- a/template/par_cfg.ctmp +++ b/template/par_cfg.ctmp @@ -3,8 +3,8 @@ // This software is under MIT licence (https://opensource.org/licenses/MIT) //////////////////////////////////////////////////////////////////////////////// /** -*@file par_cfg.c -*@brief Configuration for device parameters +*@file par_def.c +*@brief Define for device parameters *@author Ziga Miklosic *@email ziga.miklosic@gmail.com *@date 29.01.2026 @@ -12,7 +12,7 @@ */ //////////////////////////////////////////////////////////////////////////////// /** -*@addtogroup PAR_CFG +*@addtogroup PAR_DEF * @{ * * Configuration for device parameters diff --git a/template/par_cfg.htmp b/template/par_cfg.htmp index 454c8cb..92189ee 100644 --- a/template/par_cfg.htmp +++ b/template/par_cfg.htmp @@ -3,8 +3,8 @@ // This software is under MIT licence (https://opensource.org/licenses/MIT) //////////////////////////////////////////////////////////////////////////////// /** -*@file par_cfg.h -*@brief Configuration for device parameters +*@file par_def.h +*@brief Define for device parameters *@author Ziga Miklosic *@email ziga.miklosic@gmail.com *@date 29.01.2026 @@ -17,8 +17,8 @@ */ //////////////////////////////////////////////////////////////////////////////// -#ifndef _PAR_CFG_H_ -#define _PAR_CFG_H_ +#ifndef _PAR_DEF_H_ +#define _PAR_DEF_H_ //////////////////////////////////////////////////////////////////////////////// // Includes @@ -246,92 +246,6 @@ enum }; typedef uint8_t par_ch_ref_sel_t; -/** - * Enable/Disable storing persistent parameters to NVM - */ -#define PAR_CFG_NVM_EN ( 1 ) - -#if ( 1 == PAR_CFG_NVM_EN ) - /** - * NVM parameter region option - * - * @note User shall select region based on nvm_cfg.h region - * definitions "nvm_region_name_t" - * - * Don't care if "PAR_CFG_NVM_EN" set to 0 - */ - #define PAR_CFG_NVM_REGION ( eNVM_REGION_INT_FLASH_DEV_PAR ) - - /** - * Enable/Disable parameter table unique ID checking - * - * @note Base on hash unique ID is being calculated with - * purpose to detect device and stored parameter table - * difference. - * - * Must be disabled once the device is release in order - * to prevent loss of calibrated data stored in NVM. - * - * @pre "PAR_CFG_NVM_EN" must be enabled otherwise does - * not make sense to calculating ID at all. - */ - #define PAR_CFG_TABLE_ID_CHECK_EN ( 0 ) - - #ifndef DEBUG - #undef PAR_CFG_TABLE_ID_CHECK_EN - #define PAR_CFG_TABLE_ID_CHECK_EN 0 - #endif -#endif - -/** - * Enable/Disable debug mode - */ -#define PAR_CFG_DEBUG_EN ( 1 ) - -#ifndef DEBUG -#undef PAR_CFG_DEBUG_EN -#define PAR_CFG_DEBUG_EN 0 -#endif - -/** - * Enable/Disable assertions - */ -#define PAR_CFG_ASSERT_EN ( 1 ) - -#ifndef DEBUG -#undef PAR_CFG_ASSERT_EN -#define PAR_CFG_ASSERT_EN 0 -#endif - -/** - * Debug communication port macros - */ -#if ( 1 == PAR_CFG_DEBUG_EN ) - #define PAR_DBG_PRINT( ... ) ( cli_printf((char*) __VA_ARGS__ )) -#else - #define PAR_DBG_PRINT( ... ) { ; } -#endif - -/** - * Assertion macros - */ -#if ( 1 == PAR_CFG_ASSERT_EN ) - #define PAR_ASSERT(x) PROJ_CFG_ASSERT(x) -#else - #define PAR_ASSERT(x) { ; } -#endif - -// USER CODE END... - -/** - * Invalid configuration catcher - * - * @note Shall be intact by end user! - */ -#if ( 0 == PAR_CFG_NVM_EN ) && ( 1 == PAR_CFG_TABLE_ID_CHECK_EN ) - #error "Parameter settings invalid: Disable table ID checking (PAR_CFG_TABLE_ID_CHECK_EN)!" -#endif - //////////////////////////////////////////////////////////////////////////////// // Functions Prototypes //////////////////////////////////////////////////////////////////////////////// diff --git a/template/par_cfg_port.htmp b/template/par_cfg_port.htmp new file mode 100644 index 0000000..2031d9d --- /dev/null +++ b/template/par_cfg_port.htmp @@ -0,0 +1,4 @@ +#ifndef _PAR_CFG_PORT_H_ +#define _PAR_CFG_PORT_H_ +/* Optional platform overrides */ +#endif \ No newline at end of file diff --git a/template/par_def.ctmp b/template/par_def.ctmp new file mode 100644 index 0000000..368c972 --- /dev/null +++ b/template/par_def.ctmp @@ -0,0 +1,271 @@ +// Copyright (c) 2026 Ziga Miklosic +// All Rights Reserved +// This software is under MIT licence (https://opensource.org/licenses/MIT) +//////////////////////////////////////////////////////////////////////////////// +/** +*@file par_def.c +*@brief Define for device parameters +*@author Ziga Miklosic +*@email ziga.miklosic@gmail.com +*@date 29.01.2026 +*@version V3.0.1 +*/ +//////////////////////////////////////////////////////////////////////////////// +/** +*@addtogroup PAR_DEF +* @{ +* +* Configuration for device parameters +* +* User shall put code inside inside code block start with +* "USER_CODE_BEGIN" and with end of "USER_CODE_END". +*/ +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////// +#include "par_cfg.h" +#include "parameters/src/par.h" + +//////////////////////////////////////////////////////////////////////////////// +// Definitions +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Variables +//////////////////////////////////////////////////////////////////////////////// + +/** + * Parameters definitions + * + * @brief + * + * Each defined parameter has following properties: + * + * i) Parameter ID: Unique parameter identification number. ID shall not be duplicated. + * ii) Name: Parameter name. Max. length of 32 chars. + * iii) Min: Parameter minimum value. Min value must be less than max value. + * iv) Max: Parameter maximum value. Max value must be more than min value. + * v) Def: Parameter default value. Default value must lie between interval: [min, max] + * vi) Unit: In case parameter shows physical value. Max. length of 32 chars. + * vii) Data type: Parameter data type. Supported types: uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t and float32_t + * viii) Access: Access type visible from external device such as PC. Either ReadWrite or ReadOnly. + * ix) Persistence: Tells if parameter value is being written into NVM. + * + * + * @note User shall fill up wanted parameter definitions! + */ +static const par_cfg_t g_par_table[ePAR_NUM_OF] = +{ + + // USER CODE BEGIN... + + // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + // ID Name Min Max Def Unit Data type PC Access Persistent Description + // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + + + // CHANNEL 1 + // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + + // Channel 1 control + [ePAR_CH1_CTRL] = { .id = 0, .name = "Ch1 Control", .min.u8 = 0U, .max.u8 = 2U, .def.u8 = 2U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 1 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!" }, + [ePAR_CH1_AFE_MEAS_EN] = { .id = 1, .name = "Ch1 AFE Measurement Control", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 1U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 1 control AFE measurement usage for calculations: 0-Disable | 1-Enable" }, + [ePAR_CH1_TEST_MODE_EN] = { .id = 2, .name = "Ch1 Test Mode", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 1 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!" }, + [ePAR_CH1_REF_SEL] = { .id = 3, .name = "Ch1 Reference Selection", .min.u8 = 0U, .max.u8 = 4U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 1 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset" }, + [ePAR_CH1_REF_VAL] = { .id = 4, .name = "Ch1 Reference Value", .min.f32 = -1E6f, .max.f32 = 1E6f, .def.f32 = 0.0f, .unit = NULL, .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 1 reference value based on control variable set" }, + + // TODO: add function generator! + + // Channel 1 status + [ePAR_CH1_STATUS] = { .id = 10, .name = "Ch1 Status", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 3U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected" }, + [ePAR_CH1_FSM_STATE] = { .id = 11, .name = "Ch1 FSM State", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal" }, + + // Channel 1 reference values + [ePAR_CH1_TSIM] = { .id = 20, .name = "Ch1 Ref Temperature", .min.f32 = -20.0f, .max.f32 = 350.0f, .def.f32 = 0.0f, .unit = "degC", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 temperature in degree C" }, + [ePAR_CH1_RSIM] = { .id = 21, .name = "Ch1 Ref Resistance", .min.f32 = 920.0f, .max.f32 = 2300.0f, .def.f32 = 920.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 resistance reference in ohm" }, + [ePAR_CH1_VOUT] = { .id = 22, .name = "Ch1 Ref Vout", .min.f32 = 0.85f, .max.f32 = 1.58f, .def.f32 = 0.85f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 output voltage in V" }, + [ePAR_CH1_ISINK] = { .id = 23, .name = "Ch1 Ref Isink", .min.f32 = 658.5f, .max.f32 = 950.0f, .def.f32 = 658.5f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 sink current reference in uA" }, + [ePAR_CH1_VSET] = { .id = 24, .name = "Ch1 Ref Vset", .min.f32 = 1.7955f, .max.f32 = 2.548f, .def.f32 = 1.7955f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 Vset (reference) voltage in V" }, + [ePAR_CH1_VTH] = { .id = 25, .name = "Ch1 Ref Vth", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined." }, + [ePAR_CH1_VDAC_C] = { .id = 26, .name = "Ch1 Ref Vdac Coarse", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 Vdac coarse voltage in V" }, + [ePAR_CH1_VDAC_F] = { .id = 27, .name = "Ch1 Ref Vdac Fine", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 Vdac fine voltage in V" }, + [ePAR_CH1_VDAC_F_RAW] = { .id = 28, .name = "Ch1 Ref Raw DAC Fine", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 raw fine DAC value" }, + [ePAR_CH1_VDAC_C_RAW] = { .id = 29, .name = "Ch1 Ref Raw DAC Coarse", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 raw coarse DAC value" }, + + // Channel 1 measurements + [ePAR_CH1_CUR] = { .id = 50, .name = "Ch1 Act Isink", .min.f32 = 0.0f, .max.f32 = 5000.0f, .def.f32 = 0.0f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 actual current sink value in uA" }, + [ePAR_CH1_VOL] = { .id = 51, .name = "Ch1 Act Vout", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 actual output voltage value in V" }, + [ePAR_CH1_CUR_RAW] = { .id = 52, .name = "Ch1 Raw Isink", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 raw ADC current value" }, + [ePAR_CH1_VOL_RAW] = { .id = 53, .name = "Ch1 Raw Vout", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 raw ADC voltage value" }, + [ePAR_CH1_AFE_VCC] = { .id = 54, .name = "Ch1 AFE Vcc", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 measured AFE Vcc voltage value in V" }, + [ePAR_CH1_AFE_RES] = { .id = 55, .name = "Ch1 AFE Res", .min.f32 = 0.0f, .max.f32 = 5.0e3f, .def.f32 = 0.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 measured AFE resistance (pull-up + series) value in ohm" }, + + + // CHANNEL 2 + // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + + // Channel 2 control + [ePAR_CH2_CTRL] = { .id = 100, .name = "Ch2 Control", .min.u8 = 0U, .max.u8 = 2U, .def.u8 = 2U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 2 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!" }, + [ePAR_CH2_AFE_MEAS_EN] = { .id = 101, .name = "Ch2 AFE Measurement Control", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 1U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 2 control AFE measurement usage for calculations: 0-Disable | 1-Enable" }, + [ePAR_CH2_TEST_MODE_EN] = { .id = 102, .name = "Ch2 Test Mode", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 2 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!" }, + [ePAR_CH2_REF_SEL] = { .id = 103, .name = "Ch2 Reference Selection", .min.u8 = 0U, .max.u8 = 4U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 2 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset" }, + [ePAR_CH2_REF_VAL] = { .id = 104, .name = "Ch2 Reference Value", .min.f32 = -1E6f, .max.f32 = 1E6f, .def.f32 = 0.0f, .unit = NULL, .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 2 reference value based on control variable set" }, + + // Channel 2 status + [ePAR_CH2_STATUS] = { .id = 110, .name = "Ch2 Status", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 3U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected" }, + [ePAR_CH2_FSM_STATE] = { .id = 111, .name = "Ch2 FSM State", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal" }, + + // Channel 2 reference values + [ePAR_CH2_TSIM] = { .id = 120, .name = "Ch2 Ref Temperature", .min.f32 = -20.0f, .max.f32 = 350.0f, .def.f32 = 0.0f, .unit = "degC", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 temperature in degree C" }, + [ePAR_CH2_RSIM] = { .id = 121, .name = "Ch2 Ref Resistance", .min.f32 = 920.0f, .max.f32 = 2300.0f, .def.f32 = 920.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 resistance reference in ohm" }, + [ePAR_CH2_VOUT] = { .id = 122, .name = "Ch2 Ref Vout", .min.f32 = 0.85f, .max.f32 = 1.58f, .def.f32 = 0.85f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 output voltage in V" }, + [ePAR_CH2_ISINK] = { .id = 123, .name = "Ch2 Ref Isink", .min.f32 = 658.5f, .max.f32 = 950.0f, .def.f32 = 658.5f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 sink current reference in uA" }, + [ePAR_CH2_VSET] = { .id = 124, .name = "Ch2 Ref Vset", .min.f32 = 1.7955f, .max.f32 = 2.548f, .def.f32 = 1.7955f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 Vset (reference) voltage in V" }, + [ePAR_CH2_VTH] = { .id = 125, .name = "Ch2 Ref Vth", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined." }, + [ePAR_CH2_VDAC_C] = { .id = 126, .name = "Ch2 Ref Vdac Coarse", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 Vdac coarse voltage in V" }, + [ePAR_CH2_VDAC_F] = { .id = 127, .name = "Ch2 Ref Vdac Fine", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 Vdac fine voltage in V" }, + [ePAR_CH2_VDAC_F_RAW] = { .id = 128, .name = "Ch2 Ref Raw DAC Fine", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 raw fine DAC value" }, + [ePAR_CH2_VDAC_C_RAW] = { .id = 129, .name = "Ch2 Ref Raw DAC Coarse", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 raw coarse DAC value" }, + + // Channel 2 measurements + [ePAR_CH2_CUR] = { .id = 150, .name = "Ch2 Act Isink", .min.f32 = 0.0f, .max.f32 = 5000.0f, .def.f32 = 0.0f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 actual current sink value in uA" }, + [ePAR_CH2_VOL] = { .id = 151, .name = "Ch2 Act Vout", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 actual output voltage value in V" }, + [ePAR_CH2_CUR_RAW] = { .id = 152, .name = "Ch2 Raw Isink", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 raw ADC current value" }, + [ePAR_CH2_VOL_RAW] = { .id = 153, .name = "Ch2 Raw Vout", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 raw ADC voltage value" }, + [ePAR_CH2_AFE_VCC] = { .id = 154, .name = "Ch2 AFE Vcc", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 measured AFE Vcc voltage value in V" }, + [ePAR_CH2_AFE_RES] = { .id = 155, .name = "Ch2 AFE Res", .min.f32 = 0.0f, .max.f32 = 5.0e3f, .def.f32 = 0.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 measured AFE resistance (pull-up + series) value in ohm" }, + + + // CHANNEL 3 + // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + + // Channel 3 control + [ePAR_CH3_CTRL] = { .id = 200, .name = "Ch3 Control", .min.u8 = 0U, .max.u8 = 2U, .def.u8 = 2U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 3 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!" }, + [ePAR_CH3_AFE_MEAS_EN] = { .id = 201, .name = "Ch3 AFE Measurement Control", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 1U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 3 control AFE measurement usage for calculations: 0-Disable | 1-Enable" }, + [ePAR_CH3_TEST_MODE_EN] = { .id = 202, .name = "Ch3 Test Mode", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 3 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!" }, + [ePAR_CH3_REF_SEL] = { .id = 203, .name = "Ch3 Reference Selection", .min.u8 = 0U, .max.u8 = 4U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 3 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset" }, + [ePAR_CH3_REF_VAL] = { .id = 204, .name = "Ch3 Reference Value", .min.f32 = -1E6f, .max.f32 = 1E6f, .def.f32 = 0.0f, .unit = NULL, .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 3 reference value based on control variable set" }, + + // Channel 3 status + [ePAR_CH3_STATUS] = { .id = 210, .name = "Ch3 Status", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 3U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected" }, + [ePAR_CH3_FSM_STATE] = { .id = 211, .name = "Ch3 FSM State", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal" }, + + // Channel 3 reference values + [ePAR_CH3_TSIM] = { .id = 220, .name = "Ch3 Ref Temperature", .min.f32 = -20.0f, .max.f32 = 350.0f, .def.f32 = 0.0f, .unit = "degC", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 temperature in degree C" }, + [ePAR_CH3_RSIM] = { .id = 221, .name = "Ch3 Ref Resistance", .min.f32 = 920.0f, .max.f32 = 2300.0f, .def.f32 = 920.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 resistance reference in ohm" }, + [ePAR_CH3_VOUT] = { .id = 222, .name = "Ch3 Ref Vout", .min.f32 = 0.85f, .max.f32 = 1.58f, .def.f32 = 0.85f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 output voltage in V" }, + [ePAR_CH3_ISINK] = { .id = 223, .name = "Ch3 Ref Isink", .min.f32 = 658.5f, .max.f32 = 950.0f, .def.f32 = 658.5f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 sink current reference in uA" }, + [ePAR_CH3_VSET] = { .id = 224, .name = "Ch3 Ref Vset", .min.f32 = 1.7955f, .max.f32 = 2.548f, .def.f32 = 1.7955f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 Vset (reference) voltage in V" }, + [ePAR_CH3_VTH] = { .id = 225, .name = "Ch3 Ref Vth", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined." }, + [ePAR_CH3_VDAC_C] = { .id = 226, .name = "Ch3 Ref Vdac Coarse", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 Vdac coarse voltage in V" }, + [ePAR_CH3_VDAC_F] = { .id = 227, .name = "Ch3 Ref Vdac Fine", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 Vdac fine voltage in V" }, + [ePAR_CH3_VDAC_F_RAW] = { .id = 228, .name = "Ch3 Ref Raw DAC Fine", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 raw fine DAC value" }, + [ePAR_CH3_VDAC_C_RAW] = { .id = 229, .name = "Ch3 Ref Raw DAC Coarse", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 raw coarse DAC value" }, + + // Channel 3 measurements + [ePAR_CH3_CUR] = { .id = 250, .name = "Ch3 Act Isink", .min.f32 = 0.0f, .max.f32 = 5000.0f, .def.f32 = 0.0f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 actual current sink value in uA" }, + [ePAR_CH3_VOL] = { .id = 251, .name = "Ch3 Act Vout", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 actual output voltage value in V" }, + [ePAR_CH3_CUR_RAW] = { .id = 252, .name = "Ch3 Raw Isink", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 raw ADC current value" }, + [ePAR_CH3_VOL_RAW] = { .id = 253, .name = "Ch3 Raw Vout", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 raw ADC voltage value" }, + [ePAR_CH3_AFE_VCC] = { .id = 254, .name = "Ch3 AFE Vcc", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 measured AFE Vcc voltage value in V" }, + [ePAR_CH3_AFE_RES] = { .id = 255, .name = "Ch3 AFE Res", .min.f32 = 0.0f, .max.f32 = 5.0e3f, .def.f32 = 0.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 measured AFE resistance (pull-up + series) value in ohm" }, + + + // CHANNEL 4 + // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + + // Channel 4 control + [ePAR_CH4_CTRL] = { .id = 300, .name = "Ch4 Control", .min.u8 = 0U, .max.u8 = 2U, .def.u8 = 2U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 4 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!" }, + [ePAR_CH4_AFE_MEAS_EN] = { .id = 301, .name = "Ch4 AFE Measurement Control", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 1U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 4 control AFE measurement usage for calculations: 0-Disable | 1-Enable" }, + [ePAR_CH4_TEST_MODE_EN] = { .id = 302, .name = "Ch4 Test Mode", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 4 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!" }, + [ePAR_CH4_REF_SEL] = { .id = 303, .name = "Ch4 Reference Selection", .min.u8 = 0U, .max.u8 = 4U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 4 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset" }, + [ePAR_CH4_REF_VAL] = { .id = 304, .name = "Ch4 Reference Value", .min.f32 = -1E6f, .max.f32 = 1E6f, .def.f32 = 0.0f, .unit = NULL, .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 4 reference value based on control variable set" }, + + // Channel 4 status + [ePAR_CH4_STATUS] = { .id = 310, .name = "Ch4 Status", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 3U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected" }, + [ePAR_CH4_FSM_STATE] = { .id = 311, .name = "Ch4 FSM State", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal" }, + + // Channel 4 reference values + [ePAR_CH4_TSIM] = { .id = 320, .name = "Ch4 Ref Temperature", .min.f32 = -20.0f, .max.f32 = 350.0f, .def.f32 = 0.0f, .unit = "degC", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 temperature in degree C" }, + [ePAR_CH4_RSIM] = { .id = 321, .name = "Ch4 Ref Resistance", .min.f32 = 920.0f, .max.f32 = 2300.0f, .def.f32 = 920.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 resistance reference in ohm" }, + [ePAR_CH4_VOUT] = { .id = 322, .name = "Ch4 Ref Vout", .min.f32 = 0.85f, .max.f32 = 1.58f, .def.f32 = 0.85f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 output voltage in V" }, + [ePAR_CH4_ISINK] = { .id = 323, .name = "Ch4 Ref Isink", .min.f32 = 658.5f, .max.f32 = 950.0f, .def.f32 = 658.5f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 sink current reference in uA" }, + [ePAR_CH4_VSET] = { .id = 324, .name = "Ch4 Ref Vset", .min.f32 = 1.7955f, .max.f32 = 2.548f, .def.f32 = 1.7955f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 Vset (reference) voltage in V" }, + [ePAR_CH4_VTH] = { .id = 325, .name = "Ch4 Ref Vth", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined." }, + [ePAR_CH4_VDAC_C] = { .id = 326, .name = "Ch4 Ref Vdac Coarse", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 Vdac coarse voltage in V" }, + [ePAR_CH4_VDAC_F] = { .id = 327, .name = "Ch4 Ref Vdac Fine", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 Vdac fine voltage in V" }, + [ePAR_CH4_VDAC_F_RAW] = { .id = 328, .name = "Ch4 Ref Raw DAC Fine", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 raw fine DAC value" }, + [ePAR_CH4_VDAC_C_RAW] = { .id = 329, .name = "Ch4 Ref Raw DAC Coarse", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 raw coarse DAC value" }, + + // Channel 4 measurements + [ePAR_CH4_CUR] = { .id = 350, .name = "Ch4 Act Isink", .min.f32 = 0.0f, .max.f32 = 5000.0f, .def.f32 = 0.0f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 actual current sink value in uA" }, + [ePAR_CH4_VOL] = { .id = 351, .name = "Ch4 Act Vout", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 actual output voltage value in V" }, + [ePAR_CH4_CUR_RAW] = { .id = 352, .name = "Ch4 Raw Isink", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 raw ADC current value" }, + [ePAR_CH4_VOL_RAW] = { .id = 353, .name = "Ch4 Raw Vout", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 raw ADC voltage value" }, + [ePAR_CH4_AFE_VCC] = { .id = 354, .name = "Ch4 AFE Vcc", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 measured AFE Vcc voltage value in V" }, + [ePAR_CH4_AFE_RES] = { .id = 355, .name = "Ch4 AFE Res", .min.f32 = 0.0f, .max.f32 = 5.0e3f, .def.f32 = 0.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 measured AFE resistance (pull-up + series) value in ohm" }, + + + // System status + [ePAR_SYS_STATUS] = { .id = 10000, .name = "System status", .min.u32 = 0, .max.u32 = UINT32_MAX, .def.u32 = 0, .unit = NULL, .type = ePAR_TYPE_U32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "0-None | For other look at sys_err.h" }, + [ePAR_SYS_CPU_LOAD] = { .id = 10010, .name = "CPU load", .min.f32 = 0, .max.f32 = 100.0f, .def.f32 = 0.0f, .unit = "%", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Current CPU load in %" }, + [ePAR_SYS_CPU_LOAD_MAX] = { .id = 10011, .name = "CPU Max. load", .min.f32 = 0, .max.f32 = 100.0f, .def.f32 = 0.0f, .unit = "%", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Maximum CPU load in %" }, + + // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + + + // USER CODE END... +}; + +/** + * Table size in bytes + */ +static const uint32_t gu32_par_table_size = sizeof( g_par_table ); + +//////////////////////////////////////////////////////////////////////////////// +// Functions +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +/** +* Get full Device Parameter configuration table +* +* @return pointer to configuration table +*/ +//////////////////////////////////////////////////////////////////////////////// +const void * par_cfg_get_table(void) +{ + return &g_par_table; +} + +//////////////////////////////////////////////////////////////////////////////// +/** +* Get single Device Parameter configuration +* +* @return pointer to parameter config +*/ +//////////////////////////////////////////////////////////////////////////////// +const void * par_cfg_get(const par_num_t par_num) +{ + return &g_par_table[par_num]; +} + +//////////////////////////////////////////////////////////////////////////////// +/** +* Get configuration table size in bytes +* +* @return gu32_par_table_size - Size of table in bytes +*/ +//////////////////////////////////////////////////////////////////////////////// +uint32_t par_cfg_get_table_size(void) +{ + return gu32_par_table_size; +} + +//////////////////////////////////////////////////////////////////////////////// +/** +* @} +*/ +//////////////////////////////////////////////////////////////////////////////// diff --git a/template/par_def.htmp b/template/par_def.htmp new file mode 100644 index 0000000..92189ee --- /dev/null +++ b/template/par_def.htmp @@ -0,0 +1,256 @@ +// Copyright (c) 2026 Ziga Miklosic +// All Rights Reserved +// This software is under MIT licence (https://opensource.org/licenses/MIT) +//////////////////////////////////////////////////////////////////////////////// +/** +*@file par_def.h +*@brief Define for device parameters +*@author Ziga Miklosic +*@email ziga.miklosic@gmail.com +*@date 29.01.2026 +*@version V3.0.1 +*/ +//////////////////////////////////////////////////////////////////////////////// +/** +*@addtogroup PAR_CFG +* @{ +*/ +//////////////////////////////////////////////////////////////////////////////// + +#ifndef _PAR_DEF_H_ +#define _PAR_DEF_H_ + +//////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////// +#include + +// USER CODE BEGIN... + +#include "config/proj_cfg.h" + +// Debug communication port +#include "middleware/cli/cli/src/cli.h" + +// NVM +//#include "middleware/nvm/nvm_cfg.h" + +// USER CODE END... + +//////////////////////////////////////////////////////////////////////////////// +// Definitions +//////////////////////////////////////////////////////////////////////////////// + + +/** + * List of device parameters + * + * @note User shall provide parameter name here as it would be using + * later inside code. + * + * @note User shall change code only inside section of "USER_CODE_BEGIN" + * ans "USER_CODE_END". + * + * Must be started with 0! + */ +enum +{ + // USER CODE START... + + // Channel 1 control + ePAR_CH1_CTRL = 0, + ePAR_CH1_AFE_MEAS_EN, + ePAR_CH1_TEST_MODE_EN, + ePAR_CH1_REF_SEL, + ePAR_CH1_REF_VAL, + + // Channel 1 status + ePAR_CH1_STATUS, + ePAR_CH1_FSM_STATE, + + // Channel 1 reference values + ePAR_CH1_TSIM, + ePAR_CH1_RSIM, + ePAR_CH1_VOUT, + ePAR_CH1_ISINK, + ePAR_CH1_VSET, + ePAR_CH1_VTH, + ePAR_CH1_VDAC_C, + ePAR_CH1_VDAC_F, + ePAR_CH1_VDAC_C_RAW, + ePAR_CH1_VDAC_F_RAW, + + // Channel 1 measurements + ePAR_CH1_CUR, + ePAR_CH1_VOL, + ePAR_CH1_CUR_RAW, + ePAR_CH1_VOL_RAW, + ePAR_CH1_AFE_VCC, + ePAR_CH1_AFE_RES, + + // Channel 2 control + ePAR_CH2_CTRL, + ePAR_CH2_AFE_MEAS_EN, + ePAR_CH2_TEST_MODE_EN, + ePAR_CH2_REF_SEL, + ePAR_CH2_REF_VAL, + + // Channel 2 status + ePAR_CH2_STATUS, + ePAR_CH2_FSM_STATE, + + // Channel 2 reference values + ePAR_CH2_TSIM, + ePAR_CH2_RSIM, + ePAR_CH2_VOUT, + ePAR_CH2_ISINK, + ePAR_CH2_VSET, + ePAR_CH2_VTH, + ePAR_CH2_VDAC_C, + ePAR_CH2_VDAC_F, + ePAR_CH2_VDAC_C_RAW, + ePAR_CH2_VDAC_F_RAW, + + // Channel 2 measurements + ePAR_CH2_CUR, + ePAR_CH2_VOL, + ePAR_CH2_CUR_RAW, + ePAR_CH2_VOL_RAW, + ePAR_CH2_AFE_VCC, + ePAR_CH2_AFE_RES, + + // Channel 3 control + ePAR_CH3_CTRL, + ePAR_CH3_AFE_MEAS_EN, + ePAR_CH3_TEST_MODE_EN, + ePAR_CH3_REF_SEL, + ePAR_CH3_REF_VAL, + + // Channel 3 status + ePAR_CH3_STATUS, + ePAR_CH3_FSM_STATE, + + // Channel 3 reference values + ePAR_CH3_TSIM, + ePAR_CH3_RSIM, + ePAR_CH3_VOUT, + ePAR_CH3_ISINK, + ePAR_CH3_VSET, + ePAR_CH3_VTH, + ePAR_CH3_VDAC_C, + ePAR_CH3_VDAC_F, + ePAR_CH3_VDAC_C_RAW, + ePAR_CH3_VDAC_F_RAW, + + // Channel 3 measurements + ePAR_CH3_CUR, + ePAR_CH3_VOL, + ePAR_CH3_CUR_RAW, + ePAR_CH3_VOL_RAW, + ePAR_CH3_AFE_VCC, + ePAR_CH3_AFE_RES, + + // Channel 4 control + ePAR_CH4_CTRL, + ePAR_CH4_AFE_MEAS_EN, + ePAR_CH4_TEST_MODE_EN, + ePAR_CH4_REF_SEL, + ePAR_CH4_REF_VAL, + + // Channel 4 status + ePAR_CH4_STATUS, + ePAR_CH4_FSM_STATE, + + // Channel 4 reference values + ePAR_CH4_TSIM, + ePAR_CH4_RSIM, + ePAR_CH4_VOUT, + ePAR_CH4_ISINK, + ePAR_CH4_VSET, + ePAR_CH4_VTH, + ePAR_CH4_VDAC_C, + ePAR_CH4_VDAC_F, + ePAR_CH4_VDAC_C_RAW, + ePAR_CH4_VDAC_F_RAW, + + // Channel 4 measurements + ePAR_CH4_CUR, + ePAR_CH4_VOL, + ePAR_CH4_CUR_RAW, + ePAR_CH4_VOL_RAW, + ePAR_CH4_AFE_VCC, + ePAR_CH4_AFE_RES, + + // System status + ePAR_SYS_STATUS, + ePAR_SYS_CPU_LOAD, + ePAR_SYS_CPU_LOAD_MAX, + + // USER CODE END... + + ePAR_NUM_OF +}; +typedef uint16_t par_num_t; + +// USER CODE START... + +/** + * Parameter enumeration gap between channels + */ +#define PAR_CH_ENUM_GAP ( ePAR_CH2_CTRL - ePAR_CH1_CTRL ) + +/** + * Get channel parameter enumeration + */ +#define PAR_GET_BY_CH(par,ch) ( par + ( PAR_CH_ENUM_GAP * ch )) + +/** + * Temperature simulation channel control + */ +enum +{ + ePAR_CH_CTRL_NORMAL = 0, /** Date: Fri, 13 Mar 2026 15:56:11 +0800 Subject: [PATCH 05/36] refactor(core): Convert parameter table to X-Macro definition --- README.md | 433 +++++++++++++++++++++++++------------- src/par.c | 24 +-- src/par.h | 2 +- src/par_cfg.h | 6 +- src/par_def.c | 302 ++++++++++++++++++++++++++ src/par_def.h | 49 +++++ template/par_cfg.ctmp | 271 ------------------------ template/par_cfg.htmp | 256 ---------------------- template/par_def.ctmp | 271 ------------------------ template/par_def.htmp | 256 ---------------------- template/par_table.deftmp | 189 +++++++++++++++++ 11 files changed, 839 insertions(+), 1220 deletions(-) create mode 100644 src/par_def.c create mode 100644 src/par_def.h delete mode 100644 template/par_cfg.ctmp delete mode 100644 template/par_cfg.htmp delete mode 100644 template/par_def.ctmp delete mode 100644 template/par_def.htmp create mode 100644 template/par_table.deftmp diff --git a/README.md b/README.md index 36f79fc..c0e207f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[TOC] + # **Device Parameters** The **Device Parameters** module manages all device parameters through a single configuration table, offering a streamlined approach to system configuration and diagnostics. This module often serves as the backbone of an embedded system, controlling the application's behavior and providing insights into device performance, making diagnostics straightforward and efficient. @@ -35,10 +37,7 @@ By leveraging this combination of modules, embedded firmware development becomes ### **1. NVM Module** In case of using NVM module *PAR_CFG_NVM_EN = 1*, then [NVM module](https://github.com/GeneralEmbeddedCLibraries/nvm) must pe part of project. -NVM module must take following path: -``` -"root/middleware/nvm/nvm/src/nvm.h" -``` +NVM module must take following path:`"root/middleware/nvm/nvm/src/nvm.h"` ### **2. Platform adaptation layer** @@ -79,7 +78,7 @@ Available options: ```c #define PAR_ATOMIC_BACKEND_C11 1 #define PAR_ATOMIC_BACKEND_PORT 2 -```` +``` To use the port backend, define: @@ -104,22 +103,15 @@ After that, `par_atomic.h` will include `par_atomic_port.h` and use the port-pro - **Hash-Based ID Lookup:** ID-based lookup is optimized for runtime speed and rejects hash collisions during initialization. See the ID lookup section below. ## **General Embedded C Libraries Ecosystem** -In order to be part of *General Embedded C Libraries Ecosystem* this module must be placed in following path: -``` -root/middleware/parameters/parameters/"module_space" -``` +In order to be part of *General Embedded C Libraries Ecosystem* this module must be placed in following path: `root/middleware/parameters/parameters/"module_space"` -## Package structure +## **Package structure** The package is split into three layers. ### Core layer -Portable parameter logic is implemented under: - -```text -parameters/src/ -``` +Portable parameter logic is implemented under: `parameters/src/` This layer contains the core implementation of: @@ -133,11 +125,7 @@ This layer contains the core implementation of: ### Port layer -Platform-specific integration is implemented under: - -```text -port/ -``` +Platform-specific integration is implemented under:`port/` This layer contains: @@ -149,39 +137,236 @@ This separation keeps the core module portable while isolating platform and RTOS ### Template layer -Reference templates are provided under: - -```text -parameters/template/ -``` +Reference templates are provided under: `parameters/template/` This layer currently contains: -* `par_cfg.htmp` – configuration header template (default macro layout and comments) -* `par_cfg.ctmp` – parameter table source template * `par_cfg_port.htmp` – port bridge header template Template files are used as: * baseline references for keeping generated or manually maintained files aligned with upstream style -* starting points when creating new `par_cfg.h` / `par_cfg.c` / `par_cfg_port.h` in other projects +* starting points when creating new `par_def.h` / `par_def.c` / `par_table.def` / `par_cfg_port.h` in other projects Template files are not compiled directly by the runtime library. +## `par_table.def` and X-Macro workflow + +The parameter table can be maintained through an **X-Macro single-source definition file**: `par_table.def`. + +This file acts as the **single source of truth** for all parameter definitions. + +Typical workflow: + +* define all parameters once in `par_table.def` +* automatically expand: + + * `par_num_t` enumeration + * compile-time validation checks + * `g_par_table[]` configuration table + +This avoids duplicating parameter metadata across multiple files and prevents inconsistencies between enumeration definitions and parameter table entries. + --- -## Configuration model +### Why `par_table.def` is used -The package uses a two-level configuration model. +Without a single-source definition file, the same parameter information would normally be repeated in multiple places: -### Core configuration +* enum definition +* parameter table definition +* optional validation or helper structures + +This approach is error-prone because parameters can easily be added in one location but forgotten in another. + +Using `par_table.def` solves this by keeping **all parameter metadata in one place** and reusing it through macro expansion. + +--- + +### X-Macro expansion model + +Each entry in `par_table.def` is written as a macro invocation describing one parameter. + +Example: + +```c +PAR_ITEM_U8( PAR_CH1_CTRL, 0, "Ch1 Control", 0U, 2U, 2U, NULL, ePAR_ACCESS_RW, false, "Channel 1 control") +``` + +The same file can then be included multiple times with different macro definitions. + +Conceptually: + +* one expansion generates the enum +* one expansion generates compile-time validation checks +* one expansion generates `g_par_table[]` + +--- + +### Example expansions + +#### Enum expansion + +`par_table.def` can be expanded into: + +```c +typedef enum +{ + ePAR_CH1_CTRL = 0, + ... + ePAR_NUM_OF +} par_num_t; +``` + +#### Compile-time validation expansion + +The same item can generate static assertions such as: + +```sh +min <= max +def >= min +def <= max +``` + +#### Table initialization expansion + +The same item can also generate the designated initializer inside the configuration table: + +```c +static const par_cfg_t g_par_table[ePAR_NUM_OF] = +{ + ... +}; +``` + +This X-Macro pattern allows: + +* **single parameter definition** +* **multiple generated outputs** +* **no duplicated metadata** + +--- + +### Why this improves robustness + +The X-Macro + `par_table.def` approach improves the package in several ways: + +* **consistency** – enum, validation, and table initialization are generated from one source +* **maintainability** – parameters are added or modified in only one place +* **early error detection** – configuration errors can be caught during compilation + +For embedded systems with large parameter sets, this significantly reduces configuration drift and integration errors. + +--- + +## **Validation model** + +The parameter module performs validation at two stages: + +1. **Compile-time validation** +2. **Runtime validation** + +This layered approach catches most configuration errors during compilation while still allowing dynamic checks where required. + +--- + +### Compile-time checks + +For **integer parameter types** + +```sh +U8, I8, U16, I16, U32, I32 +``` -Core configuration defaults are defined in: +the module performs compile-time validation using static assertions generated from `par_table.def`. -```text -parameters/src/par_cfg.h +Typical checks include: + +* `min <= max` +* `def >= min` +* `def <= max` + +These checks are expanded through the X-Macro validation layer located in: `par_table_xmacro_check.h` + +Example generated assertion: + +```c +PAR_STATIC_ASSERT(ePAR_CH1_CTRL_def_le_max, ((def_) <= (max_))); ``` +If the condition evaluates to false, compilation fails immediately. + +This guarantees that invalid parameter configurations cannot pass the build stage. + +--- + +### Example compile-time error + +If the default value exceeds the allowed range: + +```c +PAR_ITEM_U8( PAR_CH1_CTRL, 0, "Ch1 Control", 0U, 2U, 2U, NULL, ePAR_ACCESS_RW, false, "Channel 1 control") +``` + +Compilation will fail with an error similar to: + +```sh +error: size of array '_static_assert_ePAR_CH1_CTRL_def_le_max' is negative +``` + +This indicates that the condition `def <= max` failed. + +--- + +### Runtime checks + +Some validation must be performed at runtime. + +Runtime validation includes: + +* floating-point (`F32`) range validation +* parameter `name` must not be `NULL` +* parameter `desc` must not be `NULL` +* `desc` must not contain `,` + +These checks are executed during module initialization in: `par_check_table_validity()` located in `par.c`. + +If a runtime validation fails, module initialization will fail and an error will be reported. + +--- + +### Why `F32` validation is runtime-only + +Floating-point values are not ideal for strict compile-time validation in embedded toolchains because: + +* floating-point constant expressions are handled inconsistently across compilers +* static assertions involving floating-point comparisons are less portable +* rounding behaviour can vary between toolchains + +To maintain portability and predictable builds, `F32` validation is performed **at runtime instead of compile time**. + +--- + +### Validation summary + +| Validation | Stage | Applies to | +| --------------------------- | ------------ | -------------- | +| `min <= max` | compile-time | integer types | +| `def >= min` | compile-time | integer types | +| `def <= max` | compile-time | integer types | +| float range check | runtime | `F32` | +| `name != NULL` | runtime | all parameters | +| `desc != NULL` | runtime | all parameters | +| `desc` must not contain `,` | runtime | all parameters | + +## **Configuration model** + +The package uses a two-level configuration model. + +### Core configuration + +Core configuration defaults are defined in: `parameters/src/par_cfg.h` + This file provides default values for options such as: * `PAR_CFG_NVM_EN` @@ -196,11 +381,7 @@ This file provides default values for options such as: ### Platform bridge -Platform-specific overrides are provided in: - -```text -port/par_cfg_port.h -``` +Platform-specific overrides are provided in: `port/par_cfg_port.h` This file maps platform or build-system configuration symbols to `PAR_CFG_*` options. @@ -220,7 +401,7 @@ This design keeps the core implementation independent of any single build system --- -## Port hooks +## **Port hooks** When `PAR_CFG_PORT_HOOK_EN = 1`, the module uses platform-provided hooks for: @@ -238,15 +419,11 @@ This allows the module to integrate with the native debug and assert infrastruct --- -## Interface backend +## **Interface backend** The low-level interface layer is implemented in `parameters/src/par_if.c`. -When `PAR_CFG_IF_PORT_EN = 1`, the module uses the platform-specific backend provided by: - -```text -port/par_if_port.c -``` +When `PAR_CFG_IF_PORT_EN = 1`, the module uses the platform-specific backend provided by: `port/par_if_port.c` This backend is responsible for platform-dependent services such as: @@ -257,7 +434,7 @@ This backend is responsible for platform-dependent services such as: This keeps the public parameter logic independent from the RTOS or platform implementation. -## Parameter identification +## **Parameter identification** Each parameter defined in the configuration table contains two identifiers: @@ -276,7 +453,7 @@ Example: ```c par_set(ePAR_TEST_U8, &value); -```` +``` Characteristics: @@ -375,11 +552,7 @@ During `par_init()`, the module: 1. walks through the parameter configuration table 2. hashes each parameter ID into a bucket index -3. stores the mapping: - -```text -ID -> par_num -```` +3. stores the mapping: `ID -> par_num` Later, when an external API uses an ID, the module: @@ -390,7 +563,7 @@ Later, when an external API uses an ID, the module: Conceptually: -```text +```sh External ID | v @@ -447,7 +620,7 @@ This design keeps runtime lookup logic simple, fast, and deterministic. If initialization prints a message such as: -```text +```sh ERR, Hash collision: ID X conflicts with ID Y at bucket Z! ERR, Please regenerate IDs or adjust hash parameters. ``` @@ -537,7 +710,7 @@ The main tradeoff is that a rare hash collision must be resolved by reassigning | **par_get** | Get parameter value | par_status_t par_get (const par_num_t par_num, void *const p_val)| | **par_get_id** | Get parameter ID number | par_status_t par_get_id(const par_num_t par_num, uint16_t *const p_id) | | **par_get_u8** | Get u8 parameter value | uint8_t par_get_u8(const par_num_t par_num) | -| **par_get_i8** | Get i8 parameter value | uint8_t par_get_i8(const par_num_t par_num) | +| **par_get_i8** | Get i8 parameter value | int8_t par_get_i8(const par_num_t par_num) | | **par_get_u16** | Get u16 parameter value | uint16_t par_get_u16(const par_num_t par_num) | | **par_get_i16** | Get i16 parameter value | uint16_t par_get_i16(const par_num_t par_num) | | **par_get_u32** | Get u32 parameter value | uint32_t par_get_u32(const par_num_t par_num) | @@ -576,111 +749,79 @@ The main tradeoff is that a rare hash collision must be resolved by reassigning | **par_register_on_change_cb** | Register on value change callback | par_status_t par_register_on_change_cb(const par_on_change_cb_t * const cb) | | **par_register_validation** | Register value validation callback | par_status_t par_register_validation(const par_validation_t * const validation) | -## Usage +## **Usage** -### 1. Define parameter enumeration +### 1. Define parameters in `par_table.def` -Define the parameter enumeration in `par_def.h`: +All parameters should be defined in the single-source definition file:`par_table.def` -```C -/** - * List of device parameters - * - * @note User shall provide parameter name here as it would be using - * later inside code. - * - * @note User shall change code only inside section of "USER_CODE_BEGIN" - * ans "USER_CODE_END". - */ -typedef enum -{ - ePAR_TEST_U8 = 0, - ePAR_TEST_I8, - - ePAR_TEST_U16, - ePAR_TEST_I16, - - ePAR_TEST_U32, - ePAR_TEST_I32, - - ePAR_TEST_F32, - - ePAR_NUM_OF -} par_num_t; -``` - -### 2. Define the parameter table - -Define the parameter configuration table in `par_def.c`. - -It is recommended to use designated initializers. - -```C +Example: -```C -/** - * Parameters definitions +```c +/* + * Parameter single-source list for X-Macro expansion. * - * @brief + * This file is intentionally included multiple times. + * Do not add include guards and do not place executable code here. * - * Each defined parameter has following properties: + * Supported item macros: + * PAR_ITEM_U8 (enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) + * PAR_ITEM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) + * PAR_ITEM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) + * PAR_ITEM_I8 (enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) + * PAR_ITEM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) + * PAR_ITEM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) + * PAR_ITEM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) * - * i) Parameter ID: Unique parameter identification number. ID shall not be duplicated. - * ii) Name: Parameter name. Max. length of 32 chars. - * iii) Min: Parameter minimum value. Min value must be less than max value. - * iv) Max: Parameter maximum value. Max value must be more than min value. - * v) Def: Parameter default value. Default value must lie between interval: [min, max] - * vi) Unit: In case parameter shows physical value. Max. length of 32 chars. - * vii) Data type: Parameter data type. Supported types: uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t and float32_t - * viii) Access: Access type visible from external device such as PC. Either ReadWrite or ReadOnly. - * ix) Persistence: Tells if parameter value is being written into NVM. + * Field description: + * enum_ - Parameter enum index (used as designated initializer index) + * id_ - Unique external parameter ID + * name_ - Parameter display name + * min_ - Minimum allowed value + * max_ - Maximum allowed value + * def_ - Default value, must be within [min_, max_] + * unit_ - Engineering unit string, or NULL if not applicable + * access_ - External access type: ePAR_ACCESS_RO / ePAR_ACCESS_RW + * pers_ - Persistence flag: true if stored to NVM, otherwise false + * desc_ - Human-readable description * - * @note User shall fill up wanted parameter definitions! + * Formatting note: + * Keep items grouped by subsystem/channel and aligned for readability. */ -static const par_cfg_t g_par_table[ePAR_NUM_OF] = -{ - - // USER CODE BEGIN... - - // ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - // ID Name Min Max Def Unit Data type PC Access Persistent Description - // ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - [ePAR_TEST_U8] = { .id = 0, .name = "Test_u8", .min.u8 = 0, .max.u8 = 10, .def.u8 = 8, .unit = "n/a", .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Test parameter U8" }, - [ePAR_TEST_I8] = { .id = 1, .name = "Test_i8", .min.i8 = -10, .max.i8 = 100, .def.i8 = -8, .unit = "n/a", .type = ePAR_TYPE_I8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Test parameter" }, - - [ePAR_TEST_U16] = { .id = 2, .name = "Test_u16", .min.u16 = 0, .max.u16 = 10, .def.u16 = 3, .unit = "n/a", .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Test parameter U16"}, - [ePAR_TEST_I16] = { .id = 3, .name = "Test_i16", .min.i16 = -10, .max.i16 = 100, .def.i16 = -5, .unit = "n/a", .type = ePAR_TYPE_I16, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Test parameter I16"}, - [ePAR_TEST_U32] = { .id = 4, .name = "Test_u32", .min.u32 = 0, .max.u32 = 10, .def.u32 = 10, .unit = "n/a", .type = ePAR_TYPE_U32, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Test parameter U32" }, - [ePAR_TEST_I32] = { .id = 5, .name = "Test_i32", .min.i32 = -10, .max.i32 = 100, .def.i32 = -10, .unit = "n/a", .type = ePAR_TYPE_I32, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Test parameter I32" }, - [ePAR_TEST_F32] = { .id = 6, .name = "Test_f32", .min.f32 = -10, .max.f32 = 100, .def.f32 = -1.123, .unit = "n/a", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Test parameter F32" }, +/* ============================================================================================================================================================= */ +/* enum_ id_ name_ min_ max_ def_ unit_ access_ pers_ desc_ */ +/* ============================================================================================================================================================= */ - // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +/* ============================================================================================================================= */ +/* CHANNEL 1 */ +/* ============================================================================================================================= */ - // USER CODE END... -}; +/* Channel 1 control */ +PAR_ITEM_U8 (ePAR_CH1_CTRL, 0, "Ch1 Control", 0U, 2U, 2U, NULL, ePAR_ACCESS_RW, false, "Channel 1 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!") +PAR_ITEM_U8 (ePAR_CH1_AFE_MEAS_EN, 1, "Ch1 AFE Measurement Control", 0U, 1U, 1U, NULL, ePAR_ACCESS_RW, true, "Channel 1 control AFE measurement usage for calculations: 0-Disable | 1-Enable") +PAR_ITEM_U8 (ePAR_CH1_TEST_MODE_EN, 2, "Ch1 Test Mode", 0U, 1U, 0U, NULL, ePAR_ACCESS_RW, false, "Channel 1 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!") +PAR_ITEM_U8 (ePAR_CH1_REF_SEL, 3, "Ch1 Reference Selection", 0U, 4U, 0U, NULL, ePAR_ACCESS_RW, true, "Channel 1 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset") +PAR_ITEM_F32(ePAR_CH1_REF_VAL, 4, "Ch1 Reference Value", -1E6f, 1E6f, 0.0f, NULL, ePAR_ACCESS_RW, true, "Channel 1 reference value based on control variable set") ``` -### 3. Configure the module +This definition will be expanded through the X-Macro system to generate: -The main package configuration is defined in: +* `par_num_t` enumeration +* compile-time validation checks +* `g_par_table[]` configuration table -```text -parameters/src/par_cfg.h -``` +### 2. Configure the module -Platform-specific overrides and hooks are provided in: +The main package configuration is defined in:`parameters/src/par_cfg.h` -```text -port/par_cfg_port.h -``` +Platform-specific overrides and hooks are provided in:`port/par_cfg_port.h` | Configuration | Description | | --- | --- | -| **PAR_CFG_NVM_EN** | Enable/Disable usage of NVM for persistant parameters. | +| **PAR_CFG_NVM_EN** | Enable/Disable usage of NVM for persistent parameters. | | **PAR_CFG_NVM_REGION** | Select NVM region for Device Parameter storage space. | | **PAR_CFG_DEBUG_EN** | Enable/Disable debugging mode. | | **PAR_CFG_ASSERT_EN** | Enable/Disable asserts. Shall be disabled in release build! | @@ -693,7 +834,7 @@ port/par_cfg_port.h | **PAR_CFG_IF_PORT_EN** | Enable platform-specific `par_if` backend. | | **PAR_CFG_PORT_HOOK_EN** | Enable platform log/assert hooks. | -**4. Build integration** +### **3. Build integration** The package build script collects source files from: @@ -708,7 +849,7 @@ and adds the following include paths: If you add or replace port-specific files, keep them under `port/` so they remain visible to the build system. -**5. Call **par_init()** function** +### **4. Call **par_init()** function** ```C // Init parameters @@ -719,7 +860,7 @@ if ( ePAR_OK != par_init()) ``` **NOTICE: NVM module will be initialized as a part of Device Parameters initialization routine in case of usage (*PAR_CFG_NVM_EN = 1*)!** -**6. Setting/Getting parameter value** +### **5. Setting/Getting parameter value** ```C // Set battery voltage & sytem current @@ -758,7 +899,7 @@ par_set( ePAR_BAT_VOLTAGE, (float32_t*) &(float32_t){ 1.1234f} ); PAR_SET( ePAR_BAT_VOLTAGE, (float32_t) 1.1234f ); ``` -### Normal and fast parameter setting API +### **6.Normal and fast parameter setting API** When choosing an API for setting parameter values, you must decide between the safe (normal) API and the fast API (with suffix *_fast*). The choice depends on whether your priority is data integrity and system observability or raw execution speed. ```C @@ -772,7 +913,7 @@ par_set_f32( ePAR_TARGET_TEMP, 25.5f ); par_set_f32_fast( ePAR_MOTOR_PWM, 0.85f ); ``` -**7. Store to NVM** +### **7. Store to NVM** ```C // Store all paramters to NVM @@ -783,7 +924,7 @@ if ( ePAR_OK != par_save_all()) } ``` -**8. On-change callback usage** +### **8. On-change callback usage** ```C //////////////////////////////////////////////////////////////////////////////// @@ -815,7 +956,7 @@ PAR_DEFINE_ON_CHANGE_CB( test_par_cb, ePAR_CH1_TEST_MODE_EN, par_on_change_cb1); } ``` -**9. Parameter value validation usage** +### **9. Parameter value validation usage** ```C //////////////////////////////////////////////////////////////////////////////// @@ -849,5 +990,3 @@ PAR_DEFINE_VALIDATION( par_validate, ePAR_CH1_REF_VAL, validate_par_value); } } ``` - - diff --git a/src/par.c b/src/par.c index cb5322d..9aa0fbc 100644 --- a/src/par.c +++ b/src/par.c @@ -178,7 +178,7 @@ static uint32_t gu32_par_offset[ ePAR_NUM_OF ] = { 0 }; static void par_allocate_ram_space (void); static inline uint32_t par_hash_id (const uint16_t id); static par_status_t par_build_and_validate_id_map (const par_cfg_t * const p_par_cfg); -static par_status_t par_check_table_validy (const par_cfg_t * const p_par_cfg); +static par_status_t par_check_table_validity (const par_cfg_t * const p_par_cfg); #if ( 1 == PAR_CFG_NVM_EN ) static bool par_is_value_changed (const par_num_t par_num, const void * p_val); #endif /* ( 1 == PAR_CFG_NVM_EN ) */ @@ -322,7 +322,7 @@ static par_status_t par_build_and_validate_id_map(const par_cfg_t * const p_par_ * @return status - Status of operation */ //////////////////////////////////////////////////////////////////////////////// -static par_status_t par_check_table_validy(const par_cfg_t * const p_par_cfg) +static par_status_t par_check_table_validity(const par_cfg_t * const p_par_cfg) { par_status_t status = ePAR_OK; @@ -336,19 +336,13 @@ static par_status_t par_check_table_validy(const par_cfg_t * const p_par_cfg) // For each parameter for ( uint32_t i = 0; i < ePAR_NUM_OF; i++ ) { - /** - * Check for correct MIN, MAX and DEF value definitions + /* + * Keep F32 range/default validation in runtime. * - * 1. Check that MAX is larger than MIN - * 2. Check that DEF is equal or less than MAX - * 3. Check that DEF is equal or more than MIN + * On some embedded/legacy GCC toolchains, float comparisons used in + * typedef-based static asserts may be treated as non-constant + * expressions and trigger file-scope VLA warnings. */ - PAR_ASSERT(( ePAR_TYPE_U8 == p_par_cfg[i].type ) ? ((( p_par_cfg[i].min.u8 < p_par_cfg[i].max.u8 ) && ( p_par_cfg[i].def.u8 <= p_par_cfg[i].max.u8 )) && ( p_par_cfg[i].min.u8 <= p_par_cfg[i].def.u8 )) : ( 1 )); - PAR_ASSERT(( ePAR_TYPE_I8 == p_par_cfg[i].type ) ? ((( p_par_cfg[i].min.i8 < p_par_cfg[i].max.i8 ) && ( p_par_cfg[i].def.i8 <= p_par_cfg[i].max.i8 )) && ( p_par_cfg[i].min.i8 <= p_par_cfg[i].def.i8 )) : ( 1 )); - PAR_ASSERT(( ePAR_TYPE_U16 == p_par_cfg[i].type ) ? ((( p_par_cfg[i].min.u16 < p_par_cfg[i].max.u16 ) && ( p_par_cfg[i].def.u16 <= p_par_cfg[i].max.u16 )) && ( p_par_cfg[i].min.u16 <= p_par_cfg[i].def.u16 )) : ( 1 )); - PAR_ASSERT(( ePAR_TYPE_I16 == p_par_cfg[i].type ) ? ((( p_par_cfg[i].min.i16 < p_par_cfg[i].max.i16 ) && ( p_par_cfg[i].def.i16 <= p_par_cfg[i].max.i16 )) && ( p_par_cfg[i].min.i16 <= p_par_cfg[i].def.i16 )) : ( 1 )); - PAR_ASSERT(( ePAR_TYPE_U32 == p_par_cfg[i].type ) ? ((( p_par_cfg[i].min.u32 < p_par_cfg[i].max.u32 ) && ( p_par_cfg[i].def.u32 <= p_par_cfg[i].max.u32 )) && ( p_par_cfg[i].min.u32 <= p_par_cfg[i].def.u32 )) : ( 1 )); - PAR_ASSERT(( ePAR_TYPE_I32 == p_par_cfg[i].type ) ? ((( p_par_cfg[i].min.i32 < p_par_cfg[i].max.i32 ) && ( p_par_cfg[i].def.i32 <= p_par_cfg[i].max.i32 )) && ( p_par_cfg[i].min.i32 <= p_par_cfg[i].def.i32 )) : ( 1 )); PAR_ASSERT(( ePAR_TYPE_F32 == p_par_cfg[i].type ) ? ((( p_par_cfg[i].min.f32 < p_par_cfg[i].max.f32 ) && ( p_par_cfg[i].def.f32 <= p_par_cfg[i].max.f32 )) && ( p_par_cfg[i].min.f32 <= p_par_cfg[i].def.f32 )) : ( 1 )); // Parameter shall have a description and the name @@ -464,7 +458,7 @@ par_status_t par_init(void) if ( false != par_is_init()) return ePAR_ERROR_INIT; // Check if par table is defined correctly - status |= par_check_table_validy( par_cfg_get_table()); + status |= par_check_table_validity( par_cfg_get_table()); // Allocate space in RAM par_allocate_ram_space(); @@ -1896,7 +1890,7 @@ const par_cfg_t * par_get_config(const par_num_t par_num) PAR_ASSERT( par_num < ePAR_NUM_OF ); if ( par_num >= ePAR_NUM_OF ) return NULL; - return (const par_cfg_t*) par_cfg_get(par_num); + return par_cfg_get(par_num); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/par.h b/src/par.h index 657d5ba..9a0eb94 100644 --- a/src/par.h +++ b/src/par.h @@ -128,7 +128,7 @@ typedef struct * @note Single parameter object has size of 28 bytes on * arm-gcc compiler. */ -typedef struct +typedef struct par_cfg_s { const char * name; /** +* +* Configuration for device parameters +* +* User shall put code inside inside code block start with +* "USER_CODE_BEGIN" and with end of "USER_CODE_END". +*/ +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////// +#include "par_def.h" +#include "par.h" + +//////////////////////////////////////////////////////////////////////////////// +// Definitions +//////////////////////////////////////////////////////////////////////////////// +/** + * Shared compile-time range checks for integer parameter items. + */ +#define PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) \ + PAR_STATIC_ASSERT(enum_##_min_lt_max, ((min_) < (max_))); \ + PAR_STATIC_ASSERT(enum_##_def_ge_min, ((def_) >= (min_))); \ + PAR_STATIC_ASSERT(enum_##_def_le_max, ((def_) <= (max_))) + +/** + * Compile-time checks for each parameter value type. + * + * Signature: + * (enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) + */ +#define PAR_CHECK_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +#define PAR_CHECK_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +#define PAR_CHECK_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +#define PAR_CHECK_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +#define PAR_CHECK_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +#define PAR_CHECK_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +/* + * NOTE: F32 range checks are runtime-only. + * + * Some embedded/legacy GCC toolchains do not reliably treat float comparisons + * in static assertions as integer constant expressions, and may emit + * "variably modified '_static_assert_...' at file scope". + */ +#define PAR_CHECK_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) + +/** + * Dispatch map for compile-time checks. + */ +#define PAR_ITEM_U8 PAR_CHECK_U8 +#define PAR_ITEM_U16 PAR_CHECK_U16 +#define PAR_ITEM_U32 PAR_CHECK_U32 +#define PAR_ITEM_I8 PAR_CHECK_I8 +#define PAR_ITEM_I16 PAR_CHECK_I16 +#define PAR_ITEM_I32 PAR_CHECK_I32 +#define PAR_ITEM_F32 PAR_CHECK_F32 + +#include "../../par_table.def" + +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 + +#undef PAR_CHECK_U8 +#undef PAR_CHECK_U16 +#undef PAR_CHECK_U32 +#undef PAR_CHECK_I8 +#undef PAR_CHECK_I16 +#undef PAR_CHECK_I32 +#undef PAR_CHECK_F32 +#undef PAR_CHECK_INT_COMMON + +//////////////////////////////////////////////////////////////////////////////// +// Variables +//////////////////////////////////////////////////////////////////////////////// + +/** + * Parameters definitions + * + * @brief + * + * Each defined parameter has following properties: + * + * i) Parameter ID: Unique parameter identification number. ID shall not be duplicated. + * ii) Name: Parameter name. Max. length of 32 chars. + * iii) Min: Parameter minimum value. Min value must be less than max value. + * iv) Max: Parameter maximum value. Max value must be more than min value. + * v) Def: Parameter default value. Default value must lie between interval: [min, max] + * vi) Unit: In case parameter shows physical value. Max. length of 32 chars. + * vii) Data type: Parameter data type. Supported types: uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t and float32_t + * viii) Access: Access type visible from external device such as PC. Either ReadWrite or ReadOnly. + * ix) Persistence: Tells if parameter value is being written into NVM. + * + * + * @note User shall fill up wanted parameter definitions! + */ +/** + * X-Macro table initializers for each parameter value type. + * + * Signature: + * (enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) + */ +#define PAR_INIT_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ + [enum_] = { \ + .id = (uint16_t)(id_), \ + .name = (name_), \ + .min.u8 = (uint8_t)(min_), \ + .max.u8 = (uint8_t)(max_), \ + .def.u8 = (uint8_t)(def_), \ + .unit = (unit_), \ + .type = ePAR_TYPE_U8, \ + .access = (access_), \ + .persistant = (pers_), \ + .desc = (desc_) \ + }, + +#define PAR_INIT_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ + [enum_] = { \ + .id = (uint16_t)(id_), \ + .name = (name_), \ + .min.u16 = (uint16_t)(min_), \ + .max.u16 = (uint16_t)(max_), \ + .def.u16 = (uint16_t)(def_), \ + .unit = (unit_), \ + .type = ePAR_TYPE_U16, \ + .access = (access_), \ + .persistant = (pers_), \ + .desc = (desc_) \ + }, + +#define PAR_INIT_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ + [enum_] = { \ + .id = (uint16_t)(id_), \ + .name = (name_), \ + .min.u32 = (uint32_t)(min_), \ + .max.u32 = (uint32_t)(max_), \ + .def.u32 = (uint32_t)(def_), \ + .unit = (unit_), \ + .type = ePAR_TYPE_U32, \ + .access = (access_), \ + .persistant = (pers_), \ + .desc = (desc_) \ + }, + +#define PAR_INIT_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ + [enum_] = { \ + .id = (uint16_t)(id_), \ + .name = (name_), \ + .min.i8 = (int8_t)(min_), \ + .max.i8 = (int8_t)(max_), \ + .def.i8 = (int8_t)(def_), \ + .unit = (unit_), \ + .type = ePAR_TYPE_I8, \ + .access = (access_), \ + .persistant = (pers_), \ + .desc = (desc_) \ + }, + +#define PAR_INIT_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ + [enum_] = { \ + .id = (uint16_t)(id_), \ + .name = (name_), \ + .min.i16 = (int16_t)(min_), \ + .max.i16 = (int16_t)(max_), \ + .def.i16 = (int16_t)(def_), \ + .unit = (unit_), \ + .type = ePAR_TYPE_I16, \ + .access = (access_), \ + .persistant = (pers_), \ + .desc = (desc_) \ + }, + +#define PAR_INIT_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ + [enum_] = { \ + .id = (uint16_t)(id_), \ + .name = (name_), \ + .min.i32 = (int32_t)(min_), \ + .max.i32 = (int32_t)(max_), \ + .def.i32 = (int32_t)(def_), \ + .unit = (unit_), \ + .type = ePAR_TYPE_I32, \ + .access = (access_), \ + .persistant = (pers_), \ + .desc = (desc_) \ + }, + +#define PAR_INIT_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ + [enum_] = { \ + .id = (uint16_t)(id_), \ + .name = (name_), \ + .min.f32 = (float32_t)(min_), \ + .max.f32 = (float32_t)(max_), \ + .def.f32 = (float32_t)(def_), \ + .unit = (unit_), \ + .type = ePAR_TYPE_F32, \ + .access = (access_), \ + .persistant = (pers_), \ + .desc = (desc_) \ + }, + +/** + * Dispatch map for table initialization. + */ +#define PAR_ITEM_U8 PAR_INIT_U8 +#define PAR_ITEM_U16 PAR_INIT_U16 +#define PAR_ITEM_U32 PAR_INIT_U32 +#define PAR_ITEM_I8 PAR_INIT_I8 +#define PAR_ITEM_I16 PAR_INIT_I16 +#define PAR_ITEM_I32 PAR_INIT_I32 +#define PAR_ITEM_F32 PAR_INIT_F32 + +static const par_cfg_t g_par_table[ePAR_NUM_OF] = +{ + #include "../../par_table.def" +}; + +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 + +#undef PAR_INIT_U8 +#undef PAR_INIT_U16 +#undef PAR_INIT_U32 +#undef PAR_INIT_I8 +#undef PAR_INIT_I16 +#undef PAR_INIT_I32 +#undef PAR_INIT_F32 + +/** + * Table size in bytes + */ +static const uint32_t gu32_par_table_size = sizeof( g_par_table ); + +//////////////////////////////////////////////////////////////////////////////// +// Functions +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +/** +* Get full Device Parameter configuration table +* +* @return pointer to configuration table +*/ +//////////////////////////////////////////////////////////////////////////////// +const par_cfg_t * par_cfg_get_table(void) +{ + return g_par_table; +} + +//////////////////////////////////////////////////////////////////////////////// +/** +* Get single Device Parameter configuration +* +* @return pointer to parameter config +*/ +//////////////////////////////////////////////////////////////////////////////// +const par_cfg_t * par_cfg_get(const par_num_t par_num) +{ + return &g_par_table[par_num]; +} + +//////////////////////////////////////////////////////////////////////////////// +/** +* Get configuration table size in bytes +* +* @return gu32_par_table_size - Size of table in bytes +*/ +//////////////////////////////////////////////////////////////////////////////// +uint32_t par_cfg_get_table_size(void) +{ + return gu32_par_table_size; +} + +//////////////////////////////////////////////////////////////////////////////// +/** +* @} +*/ +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/par_def.h b/src/par_def.h new file mode 100644 index 0000000..0d2ba2d --- /dev/null +++ b/src/par_def.h @@ -0,0 +1,49 @@ +#ifndef _PAR_DEF_CORE_H_ +#define _PAR_DEF_CORE_H_ + +#include + +typedef struct par_cfg_s par_cfg_t; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * List of device parameters + * + * @note Must be started with 0! + */ +#define PAR_ITEM_ENUM(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) enum_, +enum +{ + #define PAR_ITEM_U8 PAR_ITEM_ENUM + #define PAR_ITEM_U16 PAR_ITEM_ENUM + #define PAR_ITEM_U32 PAR_ITEM_ENUM + #define PAR_ITEM_I8 PAR_ITEM_ENUM + #define PAR_ITEM_I16 PAR_ITEM_ENUM + #define PAR_ITEM_I32 PAR_ITEM_ENUM + #define PAR_ITEM_F32 PAR_ITEM_ENUM + #include "../../par_table.def" + #undef PAR_ITEM_U8 + #undef PAR_ITEM_U16 + #undef PAR_ITEM_U32 + #undef PAR_ITEM_I8 + #undef PAR_ITEM_I16 + #undef PAR_ITEM_I32 + #undef PAR_ITEM_F32 + + ePAR_NUM_OF +}; +#undef PAR_ITEM_ENUM +typedef uint16_t par_num_t; + +const par_cfg_t * par_cfg_get_table (void); +const par_cfg_t * par_cfg_get (const par_num_t par_num); +uint32_t par_cfg_get_table_size (void); + +#ifdef __cplusplus +} +#endif + +#endif /* _PAR_DEF_CORE_H_ */ diff --git a/template/par_cfg.ctmp b/template/par_cfg.ctmp deleted file mode 100644 index 368c972..0000000 --- a/template/par_cfg.ctmp +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright (c) 2026 Ziga Miklosic -// All Rights Reserved -// This software is under MIT licence (https://opensource.org/licenses/MIT) -//////////////////////////////////////////////////////////////////////////////// -/** -*@file par_def.c -*@brief Define for device parameters -*@author Ziga Miklosic -*@email ziga.miklosic@gmail.com -*@date 29.01.2026 -*@version V3.0.1 -*/ -//////////////////////////////////////////////////////////////////////////////// -/** -*@addtogroup PAR_DEF -* @{ -* -* Configuration for device parameters -* -* User shall put code inside inside code block start with -* "USER_CODE_BEGIN" and with end of "USER_CODE_END". -*/ -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// Includes -//////////////////////////////////////////////////////////////////////////////// -#include "par_cfg.h" -#include "parameters/src/par.h" - -//////////////////////////////////////////////////////////////////////////////// -// Definitions -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// Variables -//////////////////////////////////////////////////////////////////////////////// - -/** - * Parameters definitions - * - * @brief - * - * Each defined parameter has following properties: - * - * i) Parameter ID: Unique parameter identification number. ID shall not be duplicated. - * ii) Name: Parameter name. Max. length of 32 chars. - * iii) Min: Parameter minimum value. Min value must be less than max value. - * iv) Max: Parameter maximum value. Max value must be more than min value. - * v) Def: Parameter default value. Default value must lie between interval: [min, max] - * vi) Unit: In case parameter shows physical value. Max. length of 32 chars. - * vii) Data type: Parameter data type. Supported types: uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t and float32_t - * viii) Access: Access type visible from external device such as PC. Either ReadWrite or ReadOnly. - * ix) Persistence: Tells if parameter value is being written into NVM. - * - * - * @note User shall fill up wanted parameter definitions! - */ -static const par_cfg_t g_par_table[ePAR_NUM_OF] = -{ - - // USER CODE BEGIN... - - // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - // ID Name Min Max Def Unit Data type PC Access Persistent Description - // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - - - // CHANNEL 1 - // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - - // Channel 1 control - [ePAR_CH1_CTRL] = { .id = 0, .name = "Ch1 Control", .min.u8 = 0U, .max.u8 = 2U, .def.u8 = 2U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 1 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!" }, - [ePAR_CH1_AFE_MEAS_EN] = { .id = 1, .name = "Ch1 AFE Measurement Control", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 1U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 1 control AFE measurement usage for calculations: 0-Disable | 1-Enable" }, - [ePAR_CH1_TEST_MODE_EN] = { .id = 2, .name = "Ch1 Test Mode", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 1 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!" }, - [ePAR_CH1_REF_SEL] = { .id = 3, .name = "Ch1 Reference Selection", .min.u8 = 0U, .max.u8 = 4U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 1 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset" }, - [ePAR_CH1_REF_VAL] = { .id = 4, .name = "Ch1 Reference Value", .min.f32 = -1E6f, .max.f32 = 1E6f, .def.f32 = 0.0f, .unit = NULL, .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 1 reference value based on control variable set" }, - - // TODO: add function generator! - - // Channel 1 status - [ePAR_CH1_STATUS] = { .id = 10, .name = "Ch1 Status", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 3U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected" }, - [ePAR_CH1_FSM_STATE] = { .id = 11, .name = "Ch1 FSM State", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal" }, - - // Channel 1 reference values - [ePAR_CH1_TSIM] = { .id = 20, .name = "Ch1 Ref Temperature", .min.f32 = -20.0f, .max.f32 = 350.0f, .def.f32 = 0.0f, .unit = "degC", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 temperature in degree C" }, - [ePAR_CH1_RSIM] = { .id = 21, .name = "Ch1 Ref Resistance", .min.f32 = 920.0f, .max.f32 = 2300.0f, .def.f32 = 920.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 resistance reference in ohm" }, - [ePAR_CH1_VOUT] = { .id = 22, .name = "Ch1 Ref Vout", .min.f32 = 0.85f, .max.f32 = 1.58f, .def.f32 = 0.85f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 output voltage in V" }, - [ePAR_CH1_ISINK] = { .id = 23, .name = "Ch1 Ref Isink", .min.f32 = 658.5f, .max.f32 = 950.0f, .def.f32 = 658.5f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 sink current reference in uA" }, - [ePAR_CH1_VSET] = { .id = 24, .name = "Ch1 Ref Vset", .min.f32 = 1.7955f, .max.f32 = 2.548f, .def.f32 = 1.7955f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 Vset (reference) voltage in V" }, - [ePAR_CH1_VTH] = { .id = 25, .name = "Ch1 Ref Vth", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined." }, - [ePAR_CH1_VDAC_C] = { .id = 26, .name = "Ch1 Ref Vdac Coarse", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 Vdac coarse voltage in V" }, - [ePAR_CH1_VDAC_F] = { .id = 27, .name = "Ch1 Ref Vdac Fine", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 Vdac fine voltage in V" }, - [ePAR_CH1_VDAC_F_RAW] = { .id = 28, .name = "Ch1 Ref Raw DAC Fine", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 raw fine DAC value" }, - [ePAR_CH1_VDAC_C_RAW] = { .id = 29, .name = "Ch1 Ref Raw DAC Coarse", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 raw coarse DAC value" }, - - // Channel 1 measurements - [ePAR_CH1_CUR] = { .id = 50, .name = "Ch1 Act Isink", .min.f32 = 0.0f, .max.f32 = 5000.0f, .def.f32 = 0.0f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 actual current sink value in uA" }, - [ePAR_CH1_VOL] = { .id = 51, .name = "Ch1 Act Vout", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 actual output voltage value in V" }, - [ePAR_CH1_CUR_RAW] = { .id = 52, .name = "Ch1 Raw Isink", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 raw ADC current value" }, - [ePAR_CH1_VOL_RAW] = { .id = 53, .name = "Ch1 Raw Vout", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 raw ADC voltage value" }, - [ePAR_CH1_AFE_VCC] = { .id = 54, .name = "Ch1 AFE Vcc", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 measured AFE Vcc voltage value in V" }, - [ePAR_CH1_AFE_RES] = { .id = 55, .name = "Ch1 AFE Res", .min.f32 = 0.0f, .max.f32 = 5.0e3f, .def.f32 = 0.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 measured AFE resistance (pull-up + series) value in ohm" }, - - - // CHANNEL 2 - // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - - // Channel 2 control - [ePAR_CH2_CTRL] = { .id = 100, .name = "Ch2 Control", .min.u8 = 0U, .max.u8 = 2U, .def.u8 = 2U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 2 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!" }, - [ePAR_CH2_AFE_MEAS_EN] = { .id = 101, .name = "Ch2 AFE Measurement Control", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 1U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 2 control AFE measurement usage for calculations: 0-Disable | 1-Enable" }, - [ePAR_CH2_TEST_MODE_EN] = { .id = 102, .name = "Ch2 Test Mode", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 2 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!" }, - [ePAR_CH2_REF_SEL] = { .id = 103, .name = "Ch2 Reference Selection", .min.u8 = 0U, .max.u8 = 4U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 2 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset" }, - [ePAR_CH2_REF_VAL] = { .id = 104, .name = "Ch2 Reference Value", .min.f32 = -1E6f, .max.f32 = 1E6f, .def.f32 = 0.0f, .unit = NULL, .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 2 reference value based on control variable set" }, - - // Channel 2 status - [ePAR_CH2_STATUS] = { .id = 110, .name = "Ch2 Status", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 3U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected" }, - [ePAR_CH2_FSM_STATE] = { .id = 111, .name = "Ch2 FSM State", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal" }, - - // Channel 2 reference values - [ePAR_CH2_TSIM] = { .id = 120, .name = "Ch2 Ref Temperature", .min.f32 = -20.0f, .max.f32 = 350.0f, .def.f32 = 0.0f, .unit = "degC", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 temperature in degree C" }, - [ePAR_CH2_RSIM] = { .id = 121, .name = "Ch2 Ref Resistance", .min.f32 = 920.0f, .max.f32 = 2300.0f, .def.f32 = 920.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 resistance reference in ohm" }, - [ePAR_CH2_VOUT] = { .id = 122, .name = "Ch2 Ref Vout", .min.f32 = 0.85f, .max.f32 = 1.58f, .def.f32 = 0.85f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 output voltage in V" }, - [ePAR_CH2_ISINK] = { .id = 123, .name = "Ch2 Ref Isink", .min.f32 = 658.5f, .max.f32 = 950.0f, .def.f32 = 658.5f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 sink current reference in uA" }, - [ePAR_CH2_VSET] = { .id = 124, .name = "Ch2 Ref Vset", .min.f32 = 1.7955f, .max.f32 = 2.548f, .def.f32 = 1.7955f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 Vset (reference) voltage in V" }, - [ePAR_CH2_VTH] = { .id = 125, .name = "Ch2 Ref Vth", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined." }, - [ePAR_CH2_VDAC_C] = { .id = 126, .name = "Ch2 Ref Vdac Coarse", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 Vdac coarse voltage in V" }, - [ePAR_CH2_VDAC_F] = { .id = 127, .name = "Ch2 Ref Vdac Fine", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 Vdac fine voltage in V" }, - [ePAR_CH2_VDAC_F_RAW] = { .id = 128, .name = "Ch2 Ref Raw DAC Fine", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 raw fine DAC value" }, - [ePAR_CH2_VDAC_C_RAW] = { .id = 129, .name = "Ch2 Ref Raw DAC Coarse", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 raw coarse DAC value" }, - - // Channel 2 measurements - [ePAR_CH2_CUR] = { .id = 150, .name = "Ch2 Act Isink", .min.f32 = 0.0f, .max.f32 = 5000.0f, .def.f32 = 0.0f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 actual current sink value in uA" }, - [ePAR_CH2_VOL] = { .id = 151, .name = "Ch2 Act Vout", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 actual output voltage value in V" }, - [ePAR_CH2_CUR_RAW] = { .id = 152, .name = "Ch2 Raw Isink", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 raw ADC current value" }, - [ePAR_CH2_VOL_RAW] = { .id = 153, .name = "Ch2 Raw Vout", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 raw ADC voltage value" }, - [ePAR_CH2_AFE_VCC] = { .id = 154, .name = "Ch2 AFE Vcc", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 measured AFE Vcc voltage value in V" }, - [ePAR_CH2_AFE_RES] = { .id = 155, .name = "Ch2 AFE Res", .min.f32 = 0.0f, .max.f32 = 5.0e3f, .def.f32 = 0.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 measured AFE resistance (pull-up + series) value in ohm" }, - - - // CHANNEL 3 - // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - - // Channel 3 control - [ePAR_CH3_CTRL] = { .id = 200, .name = "Ch3 Control", .min.u8 = 0U, .max.u8 = 2U, .def.u8 = 2U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 3 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!" }, - [ePAR_CH3_AFE_MEAS_EN] = { .id = 201, .name = "Ch3 AFE Measurement Control", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 1U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 3 control AFE measurement usage for calculations: 0-Disable | 1-Enable" }, - [ePAR_CH3_TEST_MODE_EN] = { .id = 202, .name = "Ch3 Test Mode", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 3 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!" }, - [ePAR_CH3_REF_SEL] = { .id = 203, .name = "Ch3 Reference Selection", .min.u8 = 0U, .max.u8 = 4U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 3 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset" }, - [ePAR_CH3_REF_VAL] = { .id = 204, .name = "Ch3 Reference Value", .min.f32 = -1E6f, .max.f32 = 1E6f, .def.f32 = 0.0f, .unit = NULL, .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 3 reference value based on control variable set" }, - - // Channel 3 status - [ePAR_CH3_STATUS] = { .id = 210, .name = "Ch3 Status", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 3U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected" }, - [ePAR_CH3_FSM_STATE] = { .id = 211, .name = "Ch3 FSM State", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal" }, - - // Channel 3 reference values - [ePAR_CH3_TSIM] = { .id = 220, .name = "Ch3 Ref Temperature", .min.f32 = -20.0f, .max.f32 = 350.0f, .def.f32 = 0.0f, .unit = "degC", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 temperature in degree C" }, - [ePAR_CH3_RSIM] = { .id = 221, .name = "Ch3 Ref Resistance", .min.f32 = 920.0f, .max.f32 = 2300.0f, .def.f32 = 920.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 resistance reference in ohm" }, - [ePAR_CH3_VOUT] = { .id = 222, .name = "Ch3 Ref Vout", .min.f32 = 0.85f, .max.f32 = 1.58f, .def.f32 = 0.85f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 output voltage in V" }, - [ePAR_CH3_ISINK] = { .id = 223, .name = "Ch3 Ref Isink", .min.f32 = 658.5f, .max.f32 = 950.0f, .def.f32 = 658.5f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 sink current reference in uA" }, - [ePAR_CH3_VSET] = { .id = 224, .name = "Ch3 Ref Vset", .min.f32 = 1.7955f, .max.f32 = 2.548f, .def.f32 = 1.7955f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 Vset (reference) voltage in V" }, - [ePAR_CH3_VTH] = { .id = 225, .name = "Ch3 Ref Vth", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined." }, - [ePAR_CH3_VDAC_C] = { .id = 226, .name = "Ch3 Ref Vdac Coarse", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 Vdac coarse voltage in V" }, - [ePAR_CH3_VDAC_F] = { .id = 227, .name = "Ch3 Ref Vdac Fine", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 Vdac fine voltage in V" }, - [ePAR_CH3_VDAC_F_RAW] = { .id = 228, .name = "Ch3 Ref Raw DAC Fine", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 raw fine DAC value" }, - [ePAR_CH3_VDAC_C_RAW] = { .id = 229, .name = "Ch3 Ref Raw DAC Coarse", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 raw coarse DAC value" }, - - // Channel 3 measurements - [ePAR_CH3_CUR] = { .id = 250, .name = "Ch3 Act Isink", .min.f32 = 0.0f, .max.f32 = 5000.0f, .def.f32 = 0.0f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 actual current sink value in uA" }, - [ePAR_CH3_VOL] = { .id = 251, .name = "Ch3 Act Vout", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 actual output voltage value in V" }, - [ePAR_CH3_CUR_RAW] = { .id = 252, .name = "Ch3 Raw Isink", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 raw ADC current value" }, - [ePAR_CH3_VOL_RAW] = { .id = 253, .name = "Ch3 Raw Vout", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 raw ADC voltage value" }, - [ePAR_CH3_AFE_VCC] = { .id = 254, .name = "Ch3 AFE Vcc", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 measured AFE Vcc voltage value in V" }, - [ePAR_CH3_AFE_RES] = { .id = 255, .name = "Ch3 AFE Res", .min.f32 = 0.0f, .max.f32 = 5.0e3f, .def.f32 = 0.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 measured AFE resistance (pull-up + series) value in ohm" }, - - - // CHANNEL 4 - // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - - // Channel 4 control - [ePAR_CH4_CTRL] = { .id = 300, .name = "Ch4 Control", .min.u8 = 0U, .max.u8 = 2U, .def.u8 = 2U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 4 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!" }, - [ePAR_CH4_AFE_MEAS_EN] = { .id = 301, .name = "Ch4 AFE Measurement Control", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 1U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 4 control AFE measurement usage for calculations: 0-Disable | 1-Enable" }, - [ePAR_CH4_TEST_MODE_EN] = { .id = 302, .name = "Ch4 Test Mode", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 4 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!" }, - [ePAR_CH4_REF_SEL] = { .id = 303, .name = "Ch4 Reference Selection", .min.u8 = 0U, .max.u8 = 4U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 4 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset" }, - [ePAR_CH4_REF_VAL] = { .id = 304, .name = "Ch4 Reference Value", .min.f32 = -1E6f, .max.f32 = 1E6f, .def.f32 = 0.0f, .unit = NULL, .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 4 reference value based on control variable set" }, - - // Channel 4 status - [ePAR_CH4_STATUS] = { .id = 310, .name = "Ch4 Status", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 3U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected" }, - [ePAR_CH4_FSM_STATE] = { .id = 311, .name = "Ch4 FSM State", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal" }, - - // Channel 4 reference values - [ePAR_CH4_TSIM] = { .id = 320, .name = "Ch4 Ref Temperature", .min.f32 = -20.0f, .max.f32 = 350.0f, .def.f32 = 0.0f, .unit = "degC", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 temperature in degree C" }, - [ePAR_CH4_RSIM] = { .id = 321, .name = "Ch4 Ref Resistance", .min.f32 = 920.0f, .max.f32 = 2300.0f, .def.f32 = 920.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 resistance reference in ohm" }, - [ePAR_CH4_VOUT] = { .id = 322, .name = "Ch4 Ref Vout", .min.f32 = 0.85f, .max.f32 = 1.58f, .def.f32 = 0.85f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 output voltage in V" }, - [ePAR_CH4_ISINK] = { .id = 323, .name = "Ch4 Ref Isink", .min.f32 = 658.5f, .max.f32 = 950.0f, .def.f32 = 658.5f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 sink current reference in uA" }, - [ePAR_CH4_VSET] = { .id = 324, .name = "Ch4 Ref Vset", .min.f32 = 1.7955f, .max.f32 = 2.548f, .def.f32 = 1.7955f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 Vset (reference) voltage in V" }, - [ePAR_CH4_VTH] = { .id = 325, .name = "Ch4 Ref Vth", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined." }, - [ePAR_CH4_VDAC_C] = { .id = 326, .name = "Ch4 Ref Vdac Coarse", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 Vdac coarse voltage in V" }, - [ePAR_CH4_VDAC_F] = { .id = 327, .name = "Ch4 Ref Vdac Fine", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 Vdac fine voltage in V" }, - [ePAR_CH4_VDAC_F_RAW] = { .id = 328, .name = "Ch4 Ref Raw DAC Fine", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 raw fine DAC value" }, - [ePAR_CH4_VDAC_C_RAW] = { .id = 329, .name = "Ch4 Ref Raw DAC Coarse", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 raw coarse DAC value" }, - - // Channel 4 measurements - [ePAR_CH4_CUR] = { .id = 350, .name = "Ch4 Act Isink", .min.f32 = 0.0f, .max.f32 = 5000.0f, .def.f32 = 0.0f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 actual current sink value in uA" }, - [ePAR_CH4_VOL] = { .id = 351, .name = "Ch4 Act Vout", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 actual output voltage value in V" }, - [ePAR_CH4_CUR_RAW] = { .id = 352, .name = "Ch4 Raw Isink", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 raw ADC current value" }, - [ePAR_CH4_VOL_RAW] = { .id = 353, .name = "Ch4 Raw Vout", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 raw ADC voltage value" }, - [ePAR_CH4_AFE_VCC] = { .id = 354, .name = "Ch4 AFE Vcc", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 measured AFE Vcc voltage value in V" }, - [ePAR_CH4_AFE_RES] = { .id = 355, .name = "Ch4 AFE Res", .min.f32 = 0.0f, .max.f32 = 5.0e3f, .def.f32 = 0.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 measured AFE resistance (pull-up + series) value in ohm" }, - - - // System status - [ePAR_SYS_STATUS] = { .id = 10000, .name = "System status", .min.u32 = 0, .max.u32 = UINT32_MAX, .def.u32 = 0, .unit = NULL, .type = ePAR_TYPE_U32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "0-None | For other look at sys_err.h" }, - [ePAR_SYS_CPU_LOAD] = { .id = 10010, .name = "CPU load", .min.f32 = 0, .max.f32 = 100.0f, .def.f32 = 0.0f, .unit = "%", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Current CPU load in %" }, - [ePAR_SYS_CPU_LOAD_MAX] = { .id = 10011, .name = "CPU Max. load", .min.f32 = 0, .max.f32 = 100.0f, .def.f32 = 0.0f, .unit = "%", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Maximum CPU load in %" }, - - // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - - - // USER CODE END... -}; - -/** - * Table size in bytes - */ -static const uint32_t gu32_par_table_size = sizeof( g_par_table ); - -//////////////////////////////////////////////////////////////////////////////// -// Functions -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get full Device Parameter configuration table -* -* @return pointer to configuration table -*/ -//////////////////////////////////////////////////////////////////////////////// -const void * par_cfg_get_table(void) -{ - return &g_par_table; -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get single Device Parameter configuration -* -* @return pointer to parameter config -*/ -//////////////////////////////////////////////////////////////////////////////// -const void * par_cfg_get(const par_num_t par_num) -{ - return &g_par_table[par_num]; -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get configuration table size in bytes -* -* @return gu32_par_table_size - Size of table in bytes -*/ -//////////////////////////////////////////////////////////////////////////////// -uint32_t par_cfg_get_table_size(void) -{ - return gu32_par_table_size; -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* @} -*/ -//////////////////////////////////////////////////////////////////////////////// diff --git a/template/par_cfg.htmp b/template/par_cfg.htmp deleted file mode 100644 index 92189ee..0000000 --- a/template/par_cfg.htmp +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright (c) 2026 Ziga Miklosic -// All Rights Reserved -// This software is under MIT licence (https://opensource.org/licenses/MIT) -//////////////////////////////////////////////////////////////////////////////// -/** -*@file par_def.h -*@brief Define for device parameters -*@author Ziga Miklosic -*@email ziga.miklosic@gmail.com -*@date 29.01.2026 -*@version V3.0.1 -*/ -//////////////////////////////////////////////////////////////////////////////// -/** -*@addtogroup PAR_CFG -* @{ -*/ -//////////////////////////////////////////////////////////////////////////////// - -#ifndef _PAR_DEF_H_ -#define _PAR_DEF_H_ - -//////////////////////////////////////////////////////////////////////////////// -// Includes -//////////////////////////////////////////////////////////////////////////////// -#include - -// USER CODE BEGIN... - -#include "config/proj_cfg.h" - -// Debug communication port -#include "middleware/cli/cli/src/cli.h" - -// NVM -//#include "middleware/nvm/nvm_cfg.h" - -// USER CODE END... - -//////////////////////////////////////////////////////////////////////////////// -// Definitions -//////////////////////////////////////////////////////////////////////////////// - - -/** - * List of device parameters - * - * @note User shall provide parameter name here as it would be using - * later inside code. - * - * @note User shall change code only inside section of "USER_CODE_BEGIN" - * ans "USER_CODE_END". - * - * Must be started with 0! - */ -enum -{ - // USER CODE START... - - // Channel 1 control - ePAR_CH1_CTRL = 0, - ePAR_CH1_AFE_MEAS_EN, - ePAR_CH1_TEST_MODE_EN, - ePAR_CH1_REF_SEL, - ePAR_CH1_REF_VAL, - - // Channel 1 status - ePAR_CH1_STATUS, - ePAR_CH1_FSM_STATE, - - // Channel 1 reference values - ePAR_CH1_TSIM, - ePAR_CH1_RSIM, - ePAR_CH1_VOUT, - ePAR_CH1_ISINK, - ePAR_CH1_VSET, - ePAR_CH1_VTH, - ePAR_CH1_VDAC_C, - ePAR_CH1_VDAC_F, - ePAR_CH1_VDAC_C_RAW, - ePAR_CH1_VDAC_F_RAW, - - // Channel 1 measurements - ePAR_CH1_CUR, - ePAR_CH1_VOL, - ePAR_CH1_CUR_RAW, - ePAR_CH1_VOL_RAW, - ePAR_CH1_AFE_VCC, - ePAR_CH1_AFE_RES, - - // Channel 2 control - ePAR_CH2_CTRL, - ePAR_CH2_AFE_MEAS_EN, - ePAR_CH2_TEST_MODE_EN, - ePAR_CH2_REF_SEL, - ePAR_CH2_REF_VAL, - - // Channel 2 status - ePAR_CH2_STATUS, - ePAR_CH2_FSM_STATE, - - // Channel 2 reference values - ePAR_CH2_TSIM, - ePAR_CH2_RSIM, - ePAR_CH2_VOUT, - ePAR_CH2_ISINK, - ePAR_CH2_VSET, - ePAR_CH2_VTH, - ePAR_CH2_VDAC_C, - ePAR_CH2_VDAC_F, - ePAR_CH2_VDAC_C_RAW, - ePAR_CH2_VDAC_F_RAW, - - // Channel 2 measurements - ePAR_CH2_CUR, - ePAR_CH2_VOL, - ePAR_CH2_CUR_RAW, - ePAR_CH2_VOL_RAW, - ePAR_CH2_AFE_VCC, - ePAR_CH2_AFE_RES, - - // Channel 3 control - ePAR_CH3_CTRL, - ePAR_CH3_AFE_MEAS_EN, - ePAR_CH3_TEST_MODE_EN, - ePAR_CH3_REF_SEL, - ePAR_CH3_REF_VAL, - - // Channel 3 status - ePAR_CH3_STATUS, - ePAR_CH3_FSM_STATE, - - // Channel 3 reference values - ePAR_CH3_TSIM, - ePAR_CH3_RSIM, - ePAR_CH3_VOUT, - ePAR_CH3_ISINK, - ePAR_CH3_VSET, - ePAR_CH3_VTH, - ePAR_CH3_VDAC_C, - ePAR_CH3_VDAC_F, - ePAR_CH3_VDAC_C_RAW, - ePAR_CH3_VDAC_F_RAW, - - // Channel 3 measurements - ePAR_CH3_CUR, - ePAR_CH3_VOL, - ePAR_CH3_CUR_RAW, - ePAR_CH3_VOL_RAW, - ePAR_CH3_AFE_VCC, - ePAR_CH3_AFE_RES, - - // Channel 4 control - ePAR_CH4_CTRL, - ePAR_CH4_AFE_MEAS_EN, - ePAR_CH4_TEST_MODE_EN, - ePAR_CH4_REF_SEL, - ePAR_CH4_REF_VAL, - - // Channel 4 status - ePAR_CH4_STATUS, - ePAR_CH4_FSM_STATE, - - // Channel 4 reference values - ePAR_CH4_TSIM, - ePAR_CH4_RSIM, - ePAR_CH4_VOUT, - ePAR_CH4_ISINK, - ePAR_CH4_VSET, - ePAR_CH4_VTH, - ePAR_CH4_VDAC_C, - ePAR_CH4_VDAC_F, - ePAR_CH4_VDAC_C_RAW, - ePAR_CH4_VDAC_F_RAW, - - // Channel 4 measurements - ePAR_CH4_CUR, - ePAR_CH4_VOL, - ePAR_CH4_CUR_RAW, - ePAR_CH4_VOL_RAW, - ePAR_CH4_AFE_VCC, - ePAR_CH4_AFE_RES, - - // System status - ePAR_SYS_STATUS, - ePAR_SYS_CPU_LOAD, - ePAR_SYS_CPU_LOAD_MAX, - - // USER CODE END... - - ePAR_NUM_OF -}; -typedef uint16_t par_num_t; - -// USER CODE START... - -/** - * Parameter enumeration gap between channels - */ -#define PAR_CH_ENUM_GAP ( ePAR_CH2_CTRL - ePAR_CH1_CTRL ) - -/** - * Get channel parameter enumeration - */ -#define PAR_GET_BY_CH(par,ch) ( par + ( PAR_CH_ENUM_GAP * ch )) - -/** - * Temperature simulation channel control - */ -enum -{ - ePAR_CH_CTRL_NORMAL = 0, /** -* -* Configuration for device parameters -* -* User shall put code inside inside code block start with -* "USER_CODE_BEGIN" and with end of "USER_CODE_END". -*/ -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// Includes -//////////////////////////////////////////////////////////////////////////////// -#include "par_cfg.h" -#include "parameters/src/par.h" - -//////////////////////////////////////////////////////////////////////////////// -// Definitions -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// Variables -//////////////////////////////////////////////////////////////////////////////// - -/** - * Parameters definitions - * - * @brief - * - * Each defined parameter has following properties: - * - * i) Parameter ID: Unique parameter identification number. ID shall not be duplicated. - * ii) Name: Parameter name. Max. length of 32 chars. - * iii) Min: Parameter minimum value. Min value must be less than max value. - * iv) Max: Parameter maximum value. Max value must be more than min value. - * v) Def: Parameter default value. Default value must lie between interval: [min, max] - * vi) Unit: In case parameter shows physical value. Max. length of 32 chars. - * vii) Data type: Parameter data type. Supported types: uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t and float32_t - * viii) Access: Access type visible from external device such as PC. Either ReadWrite or ReadOnly. - * ix) Persistence: Tells if parameter value is being written into NVM. - * - * - * @note User shall fill up wanted parameter definitions! - */ -static const par_cfg_t g_par_table[ePAR_NUM_OF] = -{ - - // USER CODE BEGIN... - - // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - // ID Name Min Max Def Unit Data type PC Access Persistent Description - // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - - - // CHANNEL 1 - // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - - // Channel 1 control - [ePAR_CH1_CTRL] = { .id = 0, .name = "Ch1 Control", .min.u8 = 0U, .max.u8 = 2U, .def.u8 = 2U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 1 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!" }, - [ePAR_CH1_AFE_MEAS_EN] = { .id = 1, .name = "Ch1 AFE Measurement Control", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 1U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 1 control AFE measurement usage for calculations: 0-Disable | 1-Enable" }, - [ePAR_CH1_TEST_MODE_EN] = { .id = 2, .name = "Ch1 Test Mode", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 1 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!" }, - [ePAR_CH1_REF_SEL] = { .id = 3, .name = "Ch1 Reference Selection", .min.u8 = 0U, .max.u8 = 4U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 1 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset" }, - [ePAR_CH1_REF_VAL] = { .id = 4, .name = "Ch1 Reference Value", .min.f32 = -1E6f, .max.f32 = 1E6f, .def.f32 = 0.0f, .unit = NULL, .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 1 reference value based on control variable set" }, - - // TODO: add function generator! - - // Channel 1 status - [ePAR_CH1_STATUS] = { .id = 10, .name = "Ch1 Status", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 3U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected" }, - [ePAR_CH1_FSM_STATE] = { .id = 11, .name = "Ch1 FSM State", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal" }, - - // Channel 1 reference values - [ePAR_CH1_TSIM] = { .id = 20, .name = "Ch1 Ref Temperature", .min.f32 = -20.0f, .max.f32 = 350.0f, .def.f32 = 0.0f, .unit = "degC", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 temperature in degree C" }, - [ePAR_CH1_RSIM] = { .id = 21, .name = "Ch1 Ref Resistance", .min.f32 = 920.0f, .max.f32 = 2300.0f, .def.f32 = 920.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 resistance reference in ohm" }, - [ePAR_CH1_VOUT] = { .id = 22, .name = "Ch1 Ref Vout", .min.f32 = 0.85f, .max.f32 = 1.58f, .def.f32 = 0.85f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 output voltage in V" }, - [ePAR_CH1_ISINK] = { .id = 23, .name = "Ch1 Ref Isink", .min.f32 = 658.5f, .max.f32 = 950.0f, .def.f32 = 658.5f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 sink current reference in uA" }, - [ePAR_CH1_VSET] = { .id = 24, .name = "Ch1 Ref Vset", .min.f32 = 1.7955f, .max.f32 = 2.548f, .def.f32 = 1.7955f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 Vset (reference) voltage in V" }, - [ePAR_CH1_VTH] = { .id = 25, .name = "Ch1 Ref Vth", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined." }, - [ePAR_CH1_VDAC_C] = { .id = 26, .name = "Ch1 Ref Vdac Coarse", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 Vdac coarse voltage in V" }, - [ePAR_CH1_VDAC_F] = { .id = 27, .name = "Ch1 Ref Vdac Fine", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 Vdac fine voltage in V" }, - [ePAR_CH1_VDAC_F_RAW] = { .id = 28, .name = "Ch1 Ref Raw DAC Fine", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 raw fine DAC value" }, - [ePAR_CH1_VDAC_C_RAW] = { .id = 29, .name = "Ch1 Ref Raw DAC Coarse", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 raw coarse DAC value" }, - - // Channel 1 measurements - [ePAR_CH1_CUR] = { .id = 50, .name = "Ch1 Act Isink", .min.f32 = 0.0f, .max.f32 = 5000.0f, .def.f32 = 0.0f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 actual current sink value in uA" }, - [ePAR_CH1_VOL] = { .id = 51, .name = "Ch1 Act Vout", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 actual output voltage value in V" }, - [ePAR_CH1_CUR_RAW] = { .id = 52, .name = "Ch1 Raw Isink", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 raw ADC current value" }, - [ePAR_CH1_VOL_RAW] = { .id = 53, .name = "Ch1 Raw Vout", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 raw ADC voltage value" }, - [ePAR_CH1_AFE_VCC] = { .id = 54, .name = "Ch1 AFE Vcc", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 measured AFE Vcc voltage value in V" }, - [ePAR_CH1_AFE_RES] = { .id = 55, .name = "Ch1 AFE Res", .min.f32 = 0.0f, .max.f32 = 5.0e3f, .def.f32 = 0.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 measured AFE resistance (pull-up + series) value in ohm" }, - - - // CHANNEL 2 - // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - - // Channel 2 control - [ePAR_CH2_CTRL] = { .id = 100, .name = "Ch2 Control", .min.u8 = 0U, .max.u8 = 2U, .def.u8 = 2U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 2 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!" }, - [ePAR_CH2_AFE_MEAS_EN] = { .id = 101, .name = "Ch2 AFE Measurement Control", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 1U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 2 control AFE measurement usage for calculations: 0-Disable | 1-Enable" }, - [ePAR_CH2_TEST_MODE_EN] = { .id = 102, .name = "Ch2 Test Mode", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 2 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!" }, - [ePAR_CH2_REF_SEL] = { .id = 103, .name = "Ch2 Reference Selection", .min.u8 = 0U, .max.u8 = 4U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 2 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset" }, - [ePAR_CH2_REF_VAL] = { .id = 104, .name = "Ch2 Reference Value", .min.f32 = -1E6f, .max.f32 = 1E6f, .def.f32 = 0.0f, .unit = NULL, .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 2 reference value based on control variable set" }, - - // Channel 2 status - [ePAR_CH2_STATUS] = { .id = 110, .name = "Ch2 Status", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 3U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected" }, - [ePAR_CH2_FSM_STATE] = { .id = 111, .name = "Ch2 FSM State", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal" }, - - // Channel 2 reference values - [ePAR_CH2_TSIM] = { .id = 120, .name = "Ch2 Ref Temperature", .min.f32 = -20.0f, .max.f32 = 350.0f, .def.f32 = 0.0f, .unit = "degC", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 temperature in degree C" }, - [ePAR_CH2_RSIM] = { .id = 121, .name = "Ch2 Ref Resistance", .min.f32 = 920.0f, .max.f32 = 2300.0f, .def.f32 = 920.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 resistance reference in ohm" }, - [ePAR_CH2_VOUT] = { .id = 122, .name = "Ch2 Ref Vout", .min.f32 = 0.85f, .max.f32 = 1.58f, .def.f32 = 0.85f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 output voltage in V" }, - [ePAR_CH2_ISINK] = { .id = 123, .name = "Ch2 Ref Isink", .min.f32 = 658.5f, .max.f32 = 950.0f, .def.f32 = 658.5f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 sink current reference in uA" }, - [ePAR_CH2_VSET] = { .id = 124, .name = "Ch2 Ref Vset", .min.f32 = 1.7955f, .max.f32 = 2.548f, .def.f32 = 1.7955f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 Vset (reference) voltage in V" }, - [ePAR_CH2_VTH] = { .id = 125, .name = "Ch2 Ref Vth", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined." }, - [ePAR_CH2_VDAC_C] = { .id = 126, .name = "Ch2 Ref Vdac Coarse", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 Vdac coarse voltage in V" }, - [ePAR_CH2_VDAC_F] = { .id = 127, .name = "Ch2 Ref Vdac Fine", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 Vdac fine voltage in V" }, - [ePAR_CH2_VDAC_F_RAW] = { .id = 128, .name = "Ch2 Ref Raw DAC Fine", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 raw fine DAC value" }, - [ePAR_CH2_VDAC_C_RAW] = { .id = 129, .name = "Ch2 Ref Raw DAC Coarse", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 raw coarse DAC value" }, - - // Channel 2 measurements - [ePAR_CH2_CUR] = { .id = 150, .name = "Ch2 Act Isink", .min.f32 = 0.0f, .max.f32 = 5000.0f, .def.f32 = 0.0f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 actual current sink value in uA" }, - [ePAR_CH2_VOL] = { .id = 151, .name = "Ch2 Act Vout", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 actual output voltage value in V" }, - [ePAR_CH2_CUR_RAW] = { .id = 152, .name = "Ch2 Raw Isink", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 raw ADC current value" }, - [ePAR_CH2_VOL_RAW] = { .id = 153, .name = "Ch2 Raw Vout", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 raw ADC voltage value" }, - [ePAR_CH2_AFE_VCC] = { .id = 154, .name = "Ch2 AFE Vcc", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 measured AFE Vcc voltage value in V" }, - [ePAR_CH2_AFE_RES] = { .id = 155, .name = "Ch2 AFE Res", .min.f32 = 0.0f, .max.f32 = 5.0e3f, .def.f32 = 0.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 measured AFE resistance (pull-up + series) value in ohm" }, - - - // CHANNEL 3 - // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - - // Channel 3 control - [ePAR_CH3_CTRL] = { .id = 200, .name = "Ch3 Control", .min.u8 = 0U, .max.u8 = 2U, .def.u8 = 2U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 3 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!" }, - [ePAR_CH3_AFE_MEAS_EN] = { .id = 201, .name = "Ch3 AFE Measurement Control", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 1U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 3 control AFE measurement usage for calculations: 0-Disable | 1-Enable" }, - [ePAR_CH3_TEST_MODE_EN] = { .id = 202, .name = "Ch3 Test Mode", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 3 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!" }, - [ePAR_CH3_REF_SEL] = { .id = 203, .name = "Ch3 Reference Selection", .min.u8 = 0U, .max.u8 = 4U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 3 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset" }, - [ePAR_CH3_REF_VAL] = { .id = 204, .name = "Ch3 Reference Value", .min.f32 = -1E6f, .max.f32 = 1E6f, .def.f32 = 0.0f, .unit = NULL, .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 3 reference value based on control variable set" }, - - // Channel 3 status - [ePAR_CH3_STATUS] = { .id = 210, .name = "Ch3 Status", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 3U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected" }, - [ePAR_CH3_FSM_STATE] = { .id = 211, .name = "Ch3 FSM State", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal" }, - - // Channel 3 reference values - [ePAR_CH3_TSIM] = { .id = 220, .name = "Ch3 Ref Temperature", .min.f32 = -20.0f, .max.f32 = 350.0f, .def.f32 = 0.0f, .unit = "degC", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 temperature in degree C" }, - [ePAR_CH3_RSIM] = { .id = 221, .name = "Ch3 Ref Resistance", .min.f32 = 920.0f, .max.f32 = 2300.0f, .def.f32 = 920.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 resistance reference in ohm" }, - [ePAR_CH3_VOUT] = { .id = 222, .name = "Ch3 Ref Vout", .min.f32 = 0.85f, .max.f32 = 1.58f, .def.f32 = 0.85f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 output voltage in V" }, - [ePAR_CH3_ISINK] = { .id = 223, .name = "Ch3 Ref Isink", .min.f32 = 658.5f, .max.f32 = 950.0f, .def.f32 = 658.5f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 sink current reference in uA" }, - [ePAR_CH3_VSET] = { .id = 224, .name = "Ch3 Ref Vset", .min.f32 = 1.7955f, .max.f32 = 2.548f, .def.f32 = 1.7955f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 Vset (reference) voltage in V" }, - [ePAR_CH3_VTH] = { .id = 225, .name = "Ch3 Ref Vth", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined." }, - [ePAR_CH3_VDAC_C] = { .id = 226, .name = "Ch3 Ref Vdac Coarse", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 Vdac coarse voltage in V" }, - [ePAR_CH3_VDAC_F] = { .id = 227, .name = "Ch3 Ref Vdac Fine", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 Vdac fine voltage in V" }, - [ePAR_CH3_VDAC_F_RAW] = { .id = 228, .name = "Ch3 Ref Raw DAC Fine", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 raw fine DAC value" }, - [ePAR_CH3_VDAC_C_RAW] = { .id = 229, .name = "Ch3 Ref Raw DAC Coarse", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 raw coarse DAC value" }, - - // Channel 3 measurements - [ePAR_CH3_CUR] = { .id = 250, .name = "Ch3 Act Isink", .min.f32 = 0.0f, .max.f32 = 5000.0f, .def.f32 = 0.0f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 actual current sink value in uA" }, - [ePAR_CH3_VOL] = { .id = 251, .name = "Ch3 Act Vout", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 actual output voltage value in V" }, - [ePAR_CH3_CUR_RAW] = { .id = 252, .name = "Ch3 Raw Isink", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 raw ADC current value" }, - [ePAR_CH3_VOL_RAW] = { .id = 253, .name = "Ch3 Raw Vout", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 raw ADC voltage value" }, - [ePAR_CH3_AFE_VCC] = { .id = 254, .name = "Ch3 AFE Vcc", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 measured AFE Vcc voltage value in V" }, - [ePAR_CH3_AFE_RES] = { .id = 255, .name = "Ch3 AFE Res", .min.f32 = 0.0f, .max.f32 = 5.0e3f, .def.f32 = 0.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 measured AFE resistance (pull-up + series) value in ohm" }, - - - // CHANNEL 4 - // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - - // Channel 4 control - [ePAR_CH4_CTRL] = { .id = 300, .name = "Ch4 Control", .min.u8 = 0U, .max.u8 = 2U, .def.u8 = 2U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 4 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!" }, - [ePAR_CH4_AFE_MEAS_EN] = { .id = 301, .name = "Ch4 AFE Measurement Control", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 1U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 4 control AFE measurement usage for calculations: 0-Disable | 1-Enable" }, - [ePAR_CH4_TEST_MODE_EN] = { .id = 302, .name = "Ch4 Test Mode", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 4 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!" }, - [ePAR_CH4_REF_SEL] = { .id = 303, .name = "Ch4 Reference Selection", .min.u8 = 0U, .max.u8 = 4U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 4 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset" }, - [ePAR_CH4_REF_VAL] = { .id = 304, .name = "Ch4 Reference Value", .min.f32 = -1E6f, .max.f32 = 1E6f, .def.f32 = 0.0f, .unit = NULL, .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 4 reference value based on control variable set" }, - - // Channel 4 status - [ePAR_CH4_STATUS] = { .id = 310, .name = "Ch4 Status", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 3U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected" }, - [ePAR_CH4_FSM_STATE] = { .id = 311, .name = "Ch4 FSM State", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal" }, - - // Channel 4 reference values - [ePAR_CH4_TSIM] = { .id = 320, .name = "Ch4 Ref Temperature", .min.f32 = -20.0f, .max.f32 = 350.0f, .def.f32 = 0.0f, .unit = "degC", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 temperature in degree C" }, - [ePAR_CH4_RSIM] = { .id = 321, .name = "Ch4 Ref Resistance", .min.f32 = 920.0f, .max.f32 = 2300.0f, .def.f32 = 920.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 resistance reference in ohm" }, - [ePAR_CH4_VOUT] = { .id = 322, .name = "Ch4 Ref Vout", .min.f32 = 0.85f, .max.f32 = 1.58f, .def.f32 = 0.85f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 output voltage in V" }, - [ePAR_CH4_ISINK] = { .id = 323, .name = "Ch4 Ref Isink", .min.f32 = 658.5f, .max.f32 = 950.0f, .def.f32 = 658.5f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 sink current reference in uA" }, - [ePAR_CH4_VSET] = { .id = 324, .name = "Ch4 Ref Vset", .min.f32 = 1.7955f, .max.f32 = 2.548f, .def.f32 = 1.7955f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 Vset (reference) voltage in V" }, - [ePAR_CH4_VTH] = { .id = 325, .name = "Ch4 Ref Vth", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined." }, - [ePAR_CH4_VDAC_C] = { .id = 326, .name = "Ch4 Ref Vdac Coarse", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 Vdac coarse voltage in V" }, - [ePAR_CH4_VDAC_F] = { .id = 327, .name = "Ch4 Ref Vdac Fine", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 Vdac fine voltage in V" }, - [ePAR_CH4_VDAC_F_RAW] = { .id = 328, .name = "Ch4 Ref Raw DAC Fine", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 raw fine DAC value" }, - [ePAR_CH4_VDAC_C_RAW] = { .id = 329, .name = "Ch4 Ref Raw DAC Coarse", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 raw coarse DAC value" }, - - // Channel 4 measurements - [ePAR_CH4_CUR] = { .id = 350, .name = "Ch4 Act Isink", .min.f32 = 0.0f, .max.f32 = 5000.0f, .def.f32 = 0.0f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 actual current sink value in uA" }, - [ePAR_CH4_VOL] = { .id = 351, .name = "Ch4 Act Vout", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 actual output voltage value in V" }, - [ePAR_CH4_CUR_RAW] = { .id = 352, .name = "Ch4 Raw Isink", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 raw ADC current value" }, - [ePAR_CH4_VOL_RAW] = { .id = 353, .name = "Ch4 Raw Vout", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 raw ADC voltage value" }, - [ePAR_CH4_AFE_VCC] = { .id = 354, .name = "Ch4 AFE Vcc", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 measured AFE Vcc voltage value in V" }, - [ePAR_CH4_AFE_RES] = { .id = 355, .name = "Ch4 AFE Res", .min.f32 = 0.0f, .max.f32 = 5.0e3f, .def.f32 = 0.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 measured AFE resistance (pull-up + series) value in ohm" }, - - - // System status - [ePAR_SYS_STATUS] = { .id = 10000, .name = "System status", .min.u32 = 0, .max.u32 = UINT32_MAX, .def.u32 = 0, .unit = NULL, .type = ePAR_TYPE_U32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "0-None | For other look at sys_err.h" }, - [ePAR_SYS_CPU_LOAD] = { .id = 10010, .name = "CPU load", .min.f32 = 0, .max.f32 = 100.0f, .def.f32 = 0.0f, .unit = "%", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Current CPU load in %" }, - [ePAR_SYS_CPU_LOAD_MAX] = { .id = 10011, .name = "CPU Max. load", .min.f32 = 0, .max.f32 = 100.0f, .def.f32 = 0.0f, .unit = "%", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Maximum CPU load in %" }, - - // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - - - // USER CODE END... -}; - -/** - * Table size in bytes - */ -static const uint32_t gu32_par_table_size = sizeof( g_par_table ); - -//////////////////////////////////////////////////////////////////////////////// -// Functions -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get full Device Parameter configuration table -* -* @return pointer to configuration table -*/ -//////////////////////////////////////////////////////////////////////////////// -const void * par_cfg_get_table(void) -{ - return &g_par_table; -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get single Device Parameter configuration -* -* @return pointer to parameter config -*/ -//////////////////////////////////////////////////////////////////////////////// -const void * par_cfg_get(const par_num_t par_num) -{ - return &g_par_table[par_num]; -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get configuration table size in bytes -* -* @return gu32_par_table_size - Size of table in bytes -*/ -//////////////////////////////////////////////////////////////////////////////// -uint32_t par_cfg_get_table_size(void) -{ - return gu32_par_table_size; -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* @} -*/ -//////////////////////////////////////////////////////////////////////////////// diff --git a/template/par_def.htmp b/template/par_def.htmp deleted file mode 100644 index 92189ee..0000000 --- a/template/par_def.htmp +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright (c) 2026 Ziga Miklosic -// All Rights Reserved -// This software is under MIT licence (https://opensource.org/licenses/MIT) -//////////////////////////////////////////////////////////////////////////////// -/** -*@file par_def.h -*@brief Define for device parameters -*@author Ziga Miklosic -*@email ziga.miklosic@gmail.com -*@date 29.01.2026 -*@version V3.0.1 -*/ -//////////////////////////////////////////////////////////////////////////////// -/** -*@addtogroup PAR_CFG -* @{ -*/ -//////////////////////////////////////////////////////////////////////////////// - -#ifndef _PAR_DEF_H_ -#define _PAR_DEF_H_ - -//////////////////////////////////////////////////////////////////////////////// -// Includes -//////////////////////////////////////////////////////////////////////////////// -#include - -// USER CODE BEGIN... - -#include "config/proj_cfg.h" - -// Debug communication port -#include "middleware/cli/cli/src/cli.h" - -// NVM -//#include "middleware/nvm/nvm_cfg.h" - -// USER CODE END... - -//////////////////////////////////////////////////////////////////////////////// -// Definitions -//////////////////////////////////////////////////////////////////////////////// - - -/** - * List of device parameters - * - * @note User shall provide parameter name here as it would be using - * later inside code. - * - * @note User shall change code only inside section of "USER_CODE_BEGIN" - * ans "USER_CODE_END". - * - * Must be started with 0! - */ -enum -{ - // USER CODE START... - - // Channel 1 control - ePAR_CH1_CTRL = 0, - ePAR_CH1_AFE_MEAS_EN, - ePAR_CH1_TEST_MODE_EN, - ePAR_CH1_REF_SEL, - ePAR_CH1_REF_VAL, - - // Channel 1 status - ePAR_CH1_STATUS, - ePAR_CH1_FSM_STATE, - - // Channel 1 reference values - ePAR_CH1_TSIM, - ePAR_CH1_RSIM, - ePAR_CH1_VOUT, - ePAR_CH1_ISINK, - ePAR_CH1_VSET, - ePAR_CH1_VTH, - ePAR_CH1_VDAC_C, - ePAR_CH1_VDAC_F, - ePAR_CH1_VDAC_C_RAW, - ePAR_CH1_VDAC_F_RAW, - - // Channel 1 measurements - ePAR_CH1_CUR, - ePAR_CH1_VOL, - ePAR_CH1_CUR_RAW, - ePAR_CH1_VOL_RAW, - ePAR_CH1_AFE_VCC, - ePAR_CH1_AFE_RES, - - // Channel 2 control - ePAR_CH2_CTRL, - ePAR_CH2_AFE_MEAS_EN, - ePAR_CH2_TEST_MODE_EN, - ePAR_CH2_REF_SEL, - ePAR_CH2_REF_VAL, - - // Channel 2 status - ePAR_CH2_STATUS, - ePAR_CH2_FSM_STATE, - - // Channel 2 reference values - ePAR_CH2_TSIM, - ePAR_CH2_RSIM, - ePAR_CH2_VOUT, - ePAR_CH2_ISINK, - ePAR_CH2_VSET, - ePAR_CH2_VTH, - ePAR_CH2_VDAC_C, - ePAR_CH2_VDAC_F, - ePAR_CH2_VDAC_C_RAW, - ePAR_CH2_VDAC_F_RAW, - - // Channel 2 measurements - ePAR_CH2_CUR, - ePAR_CH2_VOL, - ePAR_CH2_CUR_RAW, - ePAR_CH2_VOL_RAW, - ePAR_CH2_AFE_VCC, - ePAR_CH2_AFE_RES, - - // Channel 3 control - ePAR_CH3_CTRL, - ePAR_CH3_AFE_MEAS_EN, - ePAR_CH3_TEST_MODE_EN, - ePAR_CH3_REF_SEL, - ePAR_CH3_REF_VAL, - - // Channel 3 status - ePAR_CH3_STATUS, - ePAR_CH3_FSM_STATE, - - // Channel 3 reference values - ePAR_CH3_TSIM, - ePAR_CH3_RSIM, - ePAR_CH3_VOUT, - ePAR_CH3_ISINK, - ePAR_CH3_VSET, - ePAR_CH3_VTH, - ePAR_CH3_VDAC_C, - ePAR_CH3_VDAC_F, - ePAR_CH3_VDAC_C_RAW, - ePAR_CH3_VDAC_F_RAW, - - // Channel 3 measurements - ePAR_CH3_CUR, - ePAR_CH3_VOL, - ePAR_CH3_CUR_RAW, - ePAR_CH3_VOL_RAW, - ePAR_CH3_AFE_VCC, - ePAR_CH3_AFE_RES, - - // Channel 4 control - ePAR_CH4_CTRL, - ePAR_CH4_AFE_MEAS_EN, - ePAR_CH4_TEST_MODE_EN, - ePAR_CH4_REF_SEL, - ePAR_CH4_REF_VAL, - - // Channel 4 status - ePAR_CH4_STATUS, - ePAR_CH4_FSM_STATE, - - // Channel 4 reference values - ePAR_CH4_TSIM, - ePAR_CH4_RSIM, - ePAR_CH4_VOUT, - ePAR_CH4_ISINK, - ePAR_CH4_VSET, - ePAR_CH4_VTH, - ePAR_CH4_VDAC_C, - ePAR_CH4_VDAC_F, - ePAR_CH4_VDAC_C_RAW, - ePAR_CH4_VDAC_F_RAW, - - // Channel 4 measurements - ePAR_CH4_CUR, - ePAR_CH4_VOL, - ePAR_CH4_CUR_RAW, - ePAR_CH4_VOL_RAW, - ePAR_CH4_AFE_VCC, - ePAR_CH4_AFE_RES, - - // System status - ePAR_SYS_STATUS, - ePAR_SYS_CPU_LOAD, - ePAR_SYS_CPU_LOAD_MAX, - - // USER CODE END... - - ePAR_NUM_OF -}; -typedef uint16_t par_num_t; - -// USER CODE START... - -/** - * Parameter enumeration gap between channels - */ -#define PAR_CH_ENUM_GAP ( ePAR_CH2_CTRL - ePAR_CH1_CTRL ) - -/** - * Get channel parameter enumeration - */ -#define PAR_GET_BY_CH(par,ch) ( par + ( PAR_CH_ENUM_GAP * ch )) - -/** - * Temperature simulation channel control - */ -enum -{ - ePAR_CH_CTRL_NORMAL = 0, /** then it will force to Open!") +PAR_ITEM_U8 (ePAR_CH1_AFE_MEAS_EN, 1, "Ch1 AFE Measurement Control", 0U, 1U, 1U, NULL, ePAR_ACCESS_RW, true, "Channel 1 control AFE measurement usage for calculations: 0-Disable | 1-Enable") +PAR_ITEM_U8 (ePAR_CH1_TEST_MODE_EN, 2, "Ch1 Test Mode", 0U, 1U, 0U, NULL, ePAR_ACCESS_RW, false, "Channel 1 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!") +PAR_ITEM_U8 (ePAR_CH1_REF_SEL, 3, "Ch1 Reference Selection", 0U, 4U, 0U, NULL, ePAR_ACCESS_RW, true, "Channel 1 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset") +PAR_ITEM_F32(ePAR_CH1_REF_VAL, 4, "Ch1 Reference Value", -1E6f, 1E6f, 0.0f, NULL, ePAR_ACCESS_RW, true, "Channel 1 reference value based on control variable set") + +/* Channel 1 status */ +PAR_ITEM_U8 (ePAR_CH1_STATUS, 10, "Ch1 Status", 0U, 3U, 3U, NULL, ePAR_ACCESS_RO, false, "Channel 1 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected") +PAR_ITEM_U8 (ePAR_CH1_FSM_STATE, 11, "Ch1 FSM State", 0U, 3U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 1 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal") + +/* Channel 1 reference values */ +PAR_ITEM_F32(ePAR_CH1_TSIM, 20, "Ch1 Ref Temperature", -20.0f, 350.0f, 0.0f, "degC", ePAR_ACCESS_RO, false, "Channel 1 temperature in degree C") +PAR_ITEM_F32(ePAR_CH1_RSIM, 21, "Ch1 Ref Resistance", 920.0f, 2300.0f, 920.0f, "ohm", ePAR_ACCESS_RO, false, "Channel 1 resistance reference in ohm") +PAR_ITEM_F32(ePAR_CH1_VOUT, 22, "Ch1 Ref Vout", 0.85f, 1.58f, 0.85f, "V", ePAR_ACCESS_RO, false, "Channel 1 output voltage in V") +PAR_ITEM_F32(ePAR_CH1_ISINK, 23, "Ch1 Ref Isink", 658.5f, 950.0f, 658.5f, "uA", ePAR_ACCESS_RO, false, "Channel 1 sink current reference in uA") +PAR_ITEM_F32(ePAR_CH1_VSET, 24, "Ch1 Ref Vset", 1.7955f, 2.548f, 1.7955f, "V", ePAR_ACCESS_RO, false, "Channel 1 Vset (reference) voltage in V") +PAR_ITEM_F32(ePAR_CH1_VTH, 25, "Ch1 Ref Vth", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 1 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined.") +PAR_ITEM_F32(ePAR_CH1_VDAC_C, 26, "Ch1 Ref Vdac Coarse", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 1 Vdac coarse voltage in V") +PAR_ITEM_F32(ePAR_CH1_VDAC_F, 27, "Ch1 Ref Vdac Fine", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 1 Vdac fine voltage in V") +PAR_ITEM_U16(ePAR_CH1_VDAC_F_RAW, 28, "Ch1 Ref Raw DAC Fine", 0U, 4095U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 1 raw fine DAC value") +PAR_ITEM_U16(ePAR_CH1_VDAC_C_RAW, 29, "Ch1 Ref Raw DAC Coarse", 0U, 4095U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 1 raw coarse DAC value") + +/* Channel 1 measurements */ +PAR_ITEM_F32(ePAR_CH1_CUR, 50, "Ch1 Act Isink", 0.0f, 5000.0f, 0.0f, "uA", ePAR_ACCESS_RO, false, "Channel 1 actual current sink value in uA") +PAR_ITEM_F32(ePAR_CH1_VOL, 51, "Ch1 Act Vout", 0.0f, 10.0f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 1 actual output voltage value in V") +PAR_ITEM_U16(ePAR_CH1_CUR_RAW, 52, "Ch1 Raw Isink", 0U, 8191U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 1 raw ADC current value") +PAR_ITEM_U16(ePAR_CH1_VOL_RAW, 53, "Ch1 Raw Vout", 0U, 8191U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 1 raw ADC voltage value") +PAR_ITEM_F32(ePAR_CH1_AFE_VCC, 54, "Ch1 AFE Vcc", 0.0f, 10.0f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 1 measured AFE Vcc voltage value in V") +PAR_ITEM_F32(ePAR_CH1_AFE_RES, 55, "Ch1 AFE Res", 0.0f, 5.0e3f, 0.0f, "ohm", ePAR_ACCESS_RO, false, "Channel 1 measured AFE resistance (pull-up + series) value in ohm") + + +/* ============================================================================================================================= */ +/* CHANNEL 2 */ +/* ============================================================================================================================= */ + +/* Channel 2 control */ +PAR_ITEM_U8 (ePAR_CH2_CTRL, 100, "Ch2 Control", 0U, 2U, 2U, NULL, ePAR_ACCESS_RW, false, "Channel 2 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!") +PAR_ITEM_U8 (ePAR_CH2_AFE_MEAS_EN, 101, "Ch2 AFE Measurement Control", 0U, 1U, 1U, NULL, ePAR_ACCESS_RW, true, "Channel 2 control AFE measurement usage for calculations: 0-Disable | 1-Enable") +PAR_ITEM_U8 (ePAR_CH2_TEST_MODE_EN, 102, "Ch2 Test Mode", 0U, 1U, 0U, NULL, ePAR_ACCESS_RW, false, "Channel 2 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!") +PAR_ITEM_U8 (ePAR_CH2_REF_SEL, 103, "Ch2 Reference Selection", 0U, 4U, 0U, NULL, ePAR_ACCESS_RW, true, "Channel 2 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset") +PAR_ITEM_F32(ePAR_CH2_REF_VAL, 104, "Ch2 Reference Value", -1E6f, 1E6f, 0.0f, NULL, ePAR_ACCESS_RW, true, "Channel 2 reference value based on control variable set") + +/* Channel 2 status */ +PAR_ITEM_U8 (ePAR_CH2_STATUS, 110, "Ch2 Status", 0U, 3U, 3U, NULL, ePAR_ACCESS_RO, false, "Channel 2 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected") +PAR_ITEM_U8 (ePAR_CH2_FSM_STATE, 111, "Ch2 FSM State", 0U, 3U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 2 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal") + +/* Channel 2 reference values */ +PAR_ITEM_F32(ePAR_CH2_TSIM, 120, "Ch2 Ref Temperature", -20.0f, 350.0f, 0.0f, "degC", ePAR_ACCESS_RO, false, "Channel 2 temperature in degree C") +PAR_ITEM_F32(ePAR_CH2_RSIM, 121, "Ch2 Ref Resistance", 920.0f, 2300.0f, 920.0f, "ohm", ePAR_ACCESS_RO, false, "Channel 2 resistance reference in ohm") +PAR_ITEM_F32(ePAR_CH2_VOUT, 122, "Ch2 Ref Vout", 0.85f, 1.58f, 0.85f, "V", ePAR_ACCESS_RO, false, "Channel 2 output voltage in V") +PAR_ITEM_F32(ePAR_CH2_ISINK, 123, "Ch2 Ref Isink", 658.5f, 950.0f, 658.5f, "uA", ePAR_ACCESS_RO, false, "Channel 2 sink current reference in uA") +PAR_ITEM_F32(ePAR_CH2_VSET, 124, "Ch2 Ref Vset", 1.7955f, 2.548f, 1.7955f, "V", ePAR_ACCESS_RO, false, "Channel 2 Vset (reference) voltage in V") +PAR_ITEM_F32(ePAR_CH2_VTH, 125, "Ch2 Ref Vth", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 2 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined.") +PAR_ITEM_F32(ePAR_CH2_VDAC_C, 126, "Ch2 Ref Vdac Coarse", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 2 Vdac coarse voltage in V") +PAR_ITEM_F32(ePAR_CH2_VDAC_F, 127, "Ch2 Ref Vdac Fine", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 2 Vdac fine voltage in V") +PAR_ITEM_U16(ePAR_CH2_VDAC_F_RAW, 128, "Ch2 Ref Raw DAC Fine", 0U, 4095U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 2 raw fine DAC value") +PAR_ITEM_U16(ePAR_CH2_VDAC_C_RAW, 129, "Ch2 Ref Raw DAC Coarse", 0U, 4095U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 2 raw coarse DAC value") + +/* Channel 2 measurements */ +PAR_ITEM_F32(ePAR_CH2_CUR, 150, "Ch2 Act Isink", 0.0f, 5000.0f, 0.0f, "uA", ePAR_ACCESS_RO, false, "Channel 2 actual current sink value in uA") +PAR_ITEM_F32(ePAR_CH2_VOL, 151, "Ch2 Act Vout", 0.0f, 10.0f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 2 actual output voltage value in V") +PAR_ITEM_U16(ePAR_CH2_CUR_RAW, 152, "Ch2 Raw Isink", 0U, 8191U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 2 raw ADC current value") +PAR_ITEM_U16(ePAR_CH2_VOL_RAW, 153, "Ch2 Raw Vout", 0U, 8191U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 2 raw ADC voltage value") +PAR_ITEM_F32(ePAR_CH2_AFE_VCC, 154, "Ch2 AFE Vcc", 0.0f, 10.0f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 2 measured AFE Vcc voltage value in V") +PAR_ITEM_F32(ePAR_CH2_AFE_RES, 155, "Ch2 AFE Res", 0.0f, 5.0e3f, 0.0f, "ohm", ePAR_ACCESS_RO, false, "Channel 2 measured AFE resistance (pull-up + series) value in ohm") + + +/* ============================================================================================================================= */ +/* CHANNEL 3 */ +/* ============================================================================================================================= */ + +/* Channel 3 control */ +PAR_ITEM_U8 (ePAR_CH3_CTRL, 200, "Ch3 Control", 0U, 2U, 2U, NULL, ePAR_ACCESS_RW, false, "Channel 3 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!") +PAR_ITEM_U8 (ePAR_CH3_AFE_MEAS_EN, 201, "Ch3 AFE Measurement Control", 0U, 1U, 1U, NULL, ePAR_ACCESS_RW, true, "Channel 3 control AFE measurement usage for calculations: 0-Disable | 1-Enable") +PAR_ITEM_U8 (ePAR_CH3_TEST_MODE_EN, 202, "Ch3 Test Mode", 0U, 1U, 0U, NULL, ePAR_ACCESS_RW, false, "Channel 3 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!") +PAR_ITEM_U8 (ePAR_CH3_REF_SEL, 203, "Ch3 Reference Selection", 0U, 4U, 0U, NULL, ePAR_ACCESS_RW, true, "Channel 3 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset") +PAR_ITEM_F32(ePAR_CH3_REF_VAL, 204, "Ch3 Reference Value", -1E6f, 1E6f, 0.0f, NULL, ePAR_ACCESS_RW, true, "Channel 3 reference value based on control variable set") + +/* Channel 3 status */ +PAR_ITEM_U8 (ePAR_CH3_STATUS, 210, "Ch3 Status", 0U, 3U, 3U, NULL, ePAR_ACCESS_RO, false, "Channel 3 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected") +PAR_ITEM_U8 (ePAR_CH3_FSM_STATE, 211, "Ch3 FSM State", 0U, 3U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 3 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal") + +/* Channel 3 reference values */ +PAR_ITEM_F32(ePAR_CH3_TSIM, 220, "Ch3 Ref Temperature", -20.0f, 350.0f, 0.0f, "degC", ePAR_ACCESS_RO, false, "Channel 3 temperature in degree C") +PAR_ITEM_F32(ePAR_CH3_RSIM, 221, "Ch3 Ref Resistance", 920.0f, 2300.0f, 920.0f, "ohm", ePAR_ACCESS_RO, false, "Channel 3 resistance reference in ohm") +PAR_ITEM_F32(ePAR_CH3_VOUT, 222, "Ch3 Ref Vout", 0.85f, 1.58f, 0.85f, "V", ePAR_ACCESS_RO, false, "Channel 3 output voltage in V") +PAR_ITEM_F32(ePAR_CH3_ISINK, 223, "Ch3 Ref Isink", 658.5f, 950.0f, 658.5f, "uA", ePAR_ACCESS_RO, false, "Channel 3 sink current reference in uA") +PAR_ITEM_F32(ePAR_CH3_VSET, 224, "Ch3 Ref Vset", 1.7955f, 2.548f, 1.7955f, "V", ePAR_ACCESS_RO, false, "Channel 3 Vset (reference) voltage in V") +PAR_ITEM_F32(ePAR_CH3_VTH, 225, "Ch3 Ref Vth", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 3 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined.") +PAR_ITEM_F32(ePAR_CH3_VDAC_C, 226, "Ch3 Ref Vdac Coarse", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 3 Vdac coarse voltage in V") +PAR_ITEM_F32(ePAR_CH3_VDAC_F, 227, "Ch3 Ref Vdac Fine", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 3 Vdac fine voltage in V") +PAR_ITEM_U16(ePAR_CH3_VDAC_F_RAW, 228, "Ch3 Ref Raw DAC Fine", 0U, 4095U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 3 raw fine DAC value") +PAR_ITEM_U16(ePAR_CH3_VDAC_C_RAW, 229, "Ch3 Ref Raw DAC Coarse", 0U, 4095U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 3 raw coarse DAC value") + +/* Channel 3 measurements */ +PAR_ITEM_F32(ePAR_CH3_CUR, 250, "Ch3 Act Isink", 0.0f, 5000.0f, 0.0f, "uA", ePAR_ACCESS_RO, false, "Channel 3 actual current sink value in uA") +PAR_ITEM_F32(ePAR_CH3_VOL, 251, "Ch3 Act Vout", 0.0f, 10.0f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 3 actual output voltage value in V") +PAR_ITEM_U16(ePAR_CH3_CUR_RAW, 252, "Ch3 Raw Isink", 0U, 8191U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 3 raw ADC current value") +PAR_ITEM_U16(ePAR_CH3_VOL_RAW, 253, "Ch3 Raw Vout", 0U, 8191U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 3 raw ADC voltage value") +PAR_ITEM_F32(ePAR_CH3_AFE_VCC, 254, "Ch3 AFE Vcc", 0.0f, 10.0f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 3 measured AFE Vcc voltage value in V") +PAR_ITEM_F32(ePAR_CH3_AFE_RES, 255, "Ch3 AFE Res", 0.0f, 5.0e3f, 0.0f, "ohm", ePAR_ACCESS_RO, false, "Channel 3 measured AFE resistance (pull-up + series) value in ohm") + + +/* ============================================================================================================================= */ +/* CHANNEL 4 */ +/* ============================================================================================================================= */ + +/* Channel 4 control */ +PAR_ITEM_U8 (ePAR_CH4_CTRL, 300, "Ch4 Control", 0U, 2U, 2U, NULL, ePAR_ACCESS_RW, false, "Channel 4 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!") +PAR_ITEM_U8 (ePAR_CH4_AFE_MEAS_EN, 301, "Ch4 AFE Measurement Control", 0U, 1U, 1U, NULL, ePAR_ACCESS_RW, true, "Channel 4 control AFE measurement usage for calculations: 0-Disable | 1-Enable") +PAR_ITEM_U8 (ePAR_CH4_TEST_MODE_EN, 302, "Ch4 Test Mode", 0U, 1U, 0U, NULL, ePAR_ACCESS_RW, false, "Channel 4 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!") +PAR_ITEM_U8 (ePAR_CH4_REF_SEL, 303, "Ch4 Reference Selection", 0U, 4U, 0U, NULL, ePAR_ACCESS_RW, true, "Channel 4 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset") +PAR_ITEM_F32(ePAR_CH4_REF_VAL, 304, "Ch4 Reference Value", -1E6f, 1E6f, 0.0f, NULL, ePAR_ACCESS_RW, true, "Channel 4 reference value based on control variable set") + +/* Channel 4 status */ +PAR_ITEM_U8 (ePAR_CH4_STATUS, 310, "Ch4 Status", 0U, 3U, 3U, NULL, ePAR_ACCESS_RO, false, "Channel 4 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected") +PAR_ITEM_U8 (ePAR_CH4_FSM_STATE, 311, "Ch4 FSM State", 0U, 3U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 4 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal") + +/* Channel 4 reference values */ +PAR_ITEM_F32(ePAR_CH4_TSIM, 320, "Ch4 Ref Temperature", -20.0f, 350.0f, 0.0f, "degC", ePAR_ACCESS_RO, false, "Channel 4 temperature in degree C") +PAR_ITEM_F32(ePAR_CH4_RSIM, 321, "Ch4 Ref Resistance", 920.0f, 2300.0f, 920.0f, "ohm", ePAR_ACCESS_RO, false, "Channel 4 resistance reference in ohm") +PAR_ITEM_F32(ePAR_CH4_VOUT, 322, "Ch4 Ref Vout", 0.85f, 1.58f, 0.85f, "V", ePAR_ACCESS_RO, false, "Channel 4 output voltage in V") +PAR_ITEM_F32(ePAR_CH4_ISINK, 323, "Ch4 Ref Isink", 658.5f, 950.0f, 658.5f, "uA", ePAR_ACCESS_RO, false, "Channel 4 sink current reference in uA") +PAR_ITEM_F32(ePAR_CH4_VSET, 324, "Ch4 Ref Vset", 1.7955f, 2.548f, 1.7955f, "V", ePAR_ACCESS_RO, false, "Channel 4 Vset (reference) voltage in V") +PAR_ITEM_F32(ePAR_CH4_VTH, 325, "Ch4 Ref Vth", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 4 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined.") +PAR_ITEM_F32(ePAR_CH4_VDAC_C, 326, "Ch4 Ref Vdac Coarse", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 4 Vdac coarse voltage in V") +PAR_ITEM_F32(ePAR_CH4_VDAC_F, 327, "Ch4 Ref Vdac Fine", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 4 Vdac fine voltage in V") +PAR_ITEM_U16(ePAR_CH4_VDAC_F_RAW, 328, "Ch4 Ref Raw DAC Fine", 0U, 4095U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 4 raw fine DAC value") +PAR_ITEM_U16(ePAR_CH4_VDAC_C_RAW, 329, "Ch4 Ref Raw DAC Coarse", 0U, 4095U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 4 raw coarse DAC value") + +/* Channel 4 measurements */ +PAR_ITEM_F32(ePAR_CH4_CUR, 350, "Ch4 Act Isink", 0.0f, 5000.0f, 0.0f, "uA", ePAR_ACCESS_RO, false, "Channel 4 actual current sink value in uA") +PAR_ITEM_F32(ePAR_CH4_VOL, 351, "Ch4 Act Vout", 0.0f, 10.0f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 4 actual output voltage value in V") +PAR_ITEM_U16(ePAR_CH4_CUR_RAW, 352, "Ch4 Raw Isink", 0U, 8191U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 4 raw ADC current value") +PAR_ITEM_U16(ePAR_CH4_VOL_RAW, 353, "Ch4 Raw Vout", 0U, 8191U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 4 raw ADC voltage value") +PAR_ITEM_F32(ePAR_CH4_AFE_VCC, 354, "Ch4 AFE Vcc", 0.0f, 10.0f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 4 measured AFE Vcc voltage value in V") +PAR_ITEM_F32(ePAR_CH4_AFE_RES, 355, "Ch4 AFE Res", 0.0f, 5.0e3f, 0.0f, "ohm", ePAR_ACCESS_RO, false, "Channel 4 measured AFE resistance (pull-up + series) value in ohm") + + +/* ============================================================================================================================= */ +/* SYSTEM */ +/* ============================================================================================================================= */ + +/* System status */ +PAR_ITEM_U32(ePAR_SYS_STATUS, 10000, "System status", 0U, UINT32_MAX, 0U, NULL, ePAR_ACCESS_RO, false, "0-None | For other look at sys_err.h") +PAR_ITEM_F32(ePAR_SYS_CPU_LOAD, 10010, "CPU load", 0.0f, 100.0f, 0.0f, "%", ePAR_ACCESS_RO, false, "Current CPU load in %") +PAR_ITEM_F32(ePAR_SYS_CPU_LOAD_MAX, 10011, "CPU Max. load", 0.0f, 100.0f, 0.0f, "%", ePAR_ACCESS_RO, false, "Maximum CPU load in %") \ No newline at end of file From 2cc8bd336f3b51b38e693b2770a90388f836c877 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Mon, 16 Mar 2026 09:39:00 +0800 Subject: [PATCH 06/36] feat(core): Make parameter metadata and ID APIs configurable --- README.md | 38 ++++---- src/par.c | 222 ++++++++++++++++++++++++++++++++++------------ src/par.h | 36 ++++++++ src/par_cfg.h | 69 +++++++++++++++ src/par_def.c | 239 ++++++++++++++++++++++++++++++++------------------ 5 files changed, 451 insertions(+), 153 deletions(-) diff --git a/README.md b/README.md index c0e207f..4f8530e 100644 --- a/README.md +++ b/README.md @@ -815,24 +815,32 @@ This definition will be expanded through the X-Macro system to generate: ### 2. Configure the module -The main package configuration is defined in:`parameters/src/par_cfg.h` +The main package configuration is defined in: `parameters/src/par_cfg.h` -Platform-specific overrides and hooks are provided in:`port/par_cfg_port.h` +Platform-specific overrides and hooks are provided in: `port/par_cfg_port.h` | Configuration | Description | -| --- | --- | -| **PAR_CFG_NVM_EN** | Enable/Disable usage of NVM for persistent parameters. | -| **PAR_CFG_NVM_REGION** | Select NVM region for Device Parameter storage space. | -| **PAR_CFG_DEBUG_EN** | Enable/Disable debugging mode. | -| **PAR_CFG_ASSERT_EN** | Enable/Disable asserts. Shall be disabled in release build! | -| **PAR_DBG_PRINT** | Definition of debug print. | -| **PAR_ASSERT** | Definition of assert. | -| **PAR_ATOMIC_BACKEND** | Select atomic backend implementation. | -| **PAR_CFG_TABLE_ID_CHECK_EN** | Enable or disable parameter table unique ID checking for NVM compatibility workflows. | -| **PAR_CFG_MUTEX_EN** | Enable or disable mutex protection. | -| **PAR_CFG_MUTEX_TIMEOUT_MS** | Mutex timeout in milliseconds. | -| **PAR_CFG_IF_PORT_EN** | Enable platform-specific `par_if` backend. | -| **PAR_CFG_PORT_HOOK_EN** | Enable platform log/assert hooks. | +|---|---| +| **PAR_CFG_NVM_EN** | Enable or disable usage of NVM for persistent parameters. | +| **PAR_CFG_NVM_REGION** | Select NVM region used to store persistent parameter values. | +| **PAR_CFG_DEBUG_EN** | Enable debug features such as diagnostic logging. | +| **PAR_CFG_ASSERT_EN** | Enable runtime assertions. Should be disabled in release builds. | +| **PAR_DBG_PRINT** | Debug print macro implementation. | +| **PAR_ASSERT** | Assertion macro implementation. | +| **PAR_ATOMIC_BACKEND** | Select atomic operation backend implementation (e.g. C11, RTOS, platform-specific). | +| **PAR_CFG_TABLE_ID_CHECK_EN** | Enable compile-time or initialization-time checks for duplicate parameter IDs. | +| **PAR_CFG_MUTEX_EN** | Enable mutex protection for thread-safe parameter access. | +| **PAR_CFG_MUTEX_TIMEOUT_MS** | Mutex acquisition timeout in milliseconds. | +| **PAR_CFG_IF_PORT_EN** | Enable platform-specific backend implemented in `port/par_if_port.c`. | +| **PAR_CFG_PORT_HOOK_EN** | Enable platform log/assert hook redirection. | +| **PAR_CFG_ENABLE_RANGE** | Enable parameter range metadata (`min` / `max`) and range validation. | +| **PAR_CFG_ENABLE_NAME** | Enable parameter name metadata and `par_get_name()` API. | +| **PAR_CFG_ENABLE_UNIT** | Enable parameter unit metadata and `par_get_unit()` API. | +| **PAR_CFG_ENABLE_DESC** | Enable parameter description metadata and `par_get_desc()` API. | +| **PAR_CFG_ENABLE_DESC_COMMA_CHECK** | Enable validation that forbids `,` inside parameter descriptions (used by CSV/CLI tools). | +| **PAR_CFG_ENABLE_ID** | Enable parameter ID metadata and ID-based APIs (`par_get_by_id`, `par_set_by_id`). | +| **PAR_CFG_ENABLE_ACCESS** | Enable access control metadata (`RO/RW`) and `par_get_access()` API. | +| **PAR_CFG_ENABLE_PERSIST** | Enable parameter persistence metadata and persistent parameter support. | ### **3. Build integration** diff --git a/src/par.c b/src/par.c index 9aa0fbc..5fc1c58 100644 --- a/src/par.c +++ b/src/par.c @@ -39,41 +39,43 @@ * * GoldenRatio = ~(Math.pow(2, 32) / ((Math.sqrt(5) - 1) / 2)) + 1 */ -#define PAR_ID_HASH_GOLDEN_RATIO_32 ( 0x61C88647u ) +#if ( 1 == PAR_CFG_ENABLE_ID ) + #define PAR_ID_HASH_GOLDEN_RATIO_32 ( 0x61C88647u ) /** * Minimum number of hash buckets to keep target load factor <= 0.5. */ -#define PAR_ID_HASH_MIN_BUCKETS ((uint32_t)(2u * (uint32_t)ePAR_NUM_OF)) + #define PAR_ID_HASH_MIN_BUCKETS ((uint32_t)(2u * (uint32_t)ePAR_NUM_OF)) /** * Hash map geometry derived from ePAR_NUM_OF at compile time. */ -enum -{ - PAR_ID_HASH_BITS = - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 1 )) ? 1u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 2 )) ? 2u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 3 )) ? 3u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 4 )) ? 4u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 5 )) ? 5u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 6 )) ? 6u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 7 )) ? 7u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 8 )) ? 8u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 9 )) ? 9u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 10 )) ? 10u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 11 )) ? 11u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 12 )) ? 12u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 13 )) ? 13u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 14 )) ? 14u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 15 )) ? 15u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 16 )) ? 16u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 17 )) ? 17u : 18u, - PAR_ID_HASH_SIZE = ( 1u << PAR_ID_HASH_BITS ), -}; - -PAR_STATIC_ASSERT(par_id_hash_size_valid, (PAR_ID_HASH_SIZE >= PAR_ID_HASH_MIN_BUCKETS)); -PAR_STATIC_ASSERT(par_id_hash_bits_valid, ((PAR_ID_HASH_BITS > 0u) && (PAR_ID_HASH_BITS < 32u))); + enum + { + PAR_ID_HASH_BITS = + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 1 )) ? 1u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 2 )) ? 2u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 3 )) ? 3u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 4 )) ? 4u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 5 )) ? 5u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 6 )) ? 6u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 7 )) ? 7u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 8 )) ? 8u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 9 )) ? 9u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 10 )) ? 10u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 11 )) ? 11u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 12 )) ? 12u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 13 )) ? 13u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 14 )) ? 14u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 15 )) ? 15u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 16 )) ? 16u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 17 )) ? 17u : 18u, + PAR_ID_HASH_SIZE = ( 1u << PAR_ID_HASH_BITS ), + }; + + PAR_STATIC_ASSERT(par_id_hash_size_valid, (PAR_ID_HASH_SIZE >= PAR_ID_HASH_MIN_BUCKETS)); + PAR_STATIC_ASSERT(par_id_hash_bits_valid, ((PAR_ID_HASH_BITS > 0u) && (PAR_ID_HASH_BITS < 32u))); +#endif //////////////////////////////////////////////////////////////////////////////// // Variables @@ -96,22 +98,24 @@ static struct /** * ID hash map entry. */ -typedef struct -{ - uint16_t id; - par_num_t par_num; - uint8_t used; -} par_id_map_entry_t; +#if ( 1 == PAR_CFG_ENABLE_ID ) + typedef struct + { + uint16_t id; + par_num_t par_num; + uint8_t used; + } par_id_map_entry_t; -/** - * Runtime ID hash map. - */ -static par_id_map_entry_t g_par_id_map[PAR_ID_HASH_SIZE] = {0}; + /** + * Runtime ID hash map. + */ + static par_id_map_entry_t g_par_id_map[PAR_ID_HASH_SIZE] = {0}; -/** - * Initialization guard for ID hash map. - */ -static bool gb_par_id_map_ready = false; + /** + * Initialization guard for ID hash map. + */ + static bool gb_par_id_map_ready = false; +#endif /** * Parameter live values divided by its type in RAM @@ -176,8 +180,10 @@ static uint32_t gu32_par_offset[ ePAR_NUM_OF ] = { 0 }; // Function Prototypes //////////////////////////////////////////////////////////////////////////////// static void par_allocate_ram_space (void); +#if ( 1 == PAR_CFG_ENABLE_ID ) static inline uint32_t par_hash_id (const uint16_t id); static par_status_t par_build_and_validate_id_map (const par_cfg_t * const p_par_cfg); +#endif static par_status_t par_check_table_validity (const par_cfg_t * const p_par_cfg); #if ( 1 == PAR_CFG_NVM_EN ) static bool par_is_value_changed (const par_num_t par_num, const void * p_val); @@ -267,6 +273,7 @@ static void par_allocate_ram_space(void) * @return hash index */ //////////////////////////////////////////////////////////////////////////////// +#if ( 1 == PAR_CFG_ENABLE_ID ) static inline uint32_t par_hash_id(const uint16_t id) { return (((uint32_t) id * PAR_ID_HASH_GOLDEN_RATIO_32 ) >> ( 32u - PAR_ID_HASH_BITS )); @@ -313,6 +320,7 @@ static par_status_t par_build_and_validate_id_map(const par_cfg_t * const p_par_ return ePAR_OK; } +#endif //////////////////////////////////////////////////////////////////////////////// /** @@ -326,16 +334,19 @@ static par_status_t par_check_table_validity(const par_cfg_t * const p_par_cfg) { par_status_t status = ePAR_OK; +#if ( 1 == PAR_CFG_ENABLE_ID ) // Build and validate runtime ID hash map status = par_build_and_validate_id_map( p_par_cfg ); if ( ePAR_OK != status ) { return status; } +#endif // For each parameter for ( uint32_t i = 0; i < ePAR_NUM_OF; i++ ) { +#if ( 1 == PAR_CFG_ENABLE_RANGE ) /* * Keep F32 range/default validation in runtime. * @@ -344,27 +355,39 @@ static par_status_t par_check_table_validity(const par_cfg_t * const p_par_cfg) * expressions and trigger file-scope VLA warnings. */ PAR_ASSERT(( ePAR_TYPE_F32 == p_par_cfg[i].type ) ? ((( p_par_cfg[i].min.f32 < p_par_cfg[i].max.f32 ) && ( p_par_cfg[i].def.f32 <= p_par_cfg[i].max.f32 )) && ( p_par_cfg[i].min.f32 <= p_par_cfg[i].def.f32 )) : ( 1 )); +#endif - // Parameter shall have a description and the name - if (( NULL == p_par_cfg[i].name ) || ( NULL == p_par_cfg[i].desc )) +#if ( 1 == PAR_CFG_ENABLE_NAME ) + if ( NULL == p_par_cfg[i].name ) { status = ePAR_ERROR_INIT; - PAR_DBG_PRINT( "ERR, Parameter %d definition incomplete!", i ); + PAR_DBG_PRINT( "ERR, Parameter %d name missing!", i ); PAR_ASSERT( 0 ); break; } - else +#endif + +#if ( 1 == PAR_CFG_ENABLE_DESC ) + if ( NULL == p_par_cfg[i].desc ) { - // ',' is prohibited in parameter description - // NOTE: ',' is used as column separator and will break PC tool side parser logic in case of usage in description! - if ( NULL != strchr(p_par_cfg[i].desc, ',')) - { - status = ePAR_ERROR_INIT; - PAR_DBG_PRINT( "ERR, Parameter %d description contains comma!", i ); - PAR_ASSERT( 0 ); - break; - } + status = ePAR_ERROR_INIT; + PAR_DBG_PRINT( "ERR, Parameter %d description missing!", i ); + PAR_ASSERT( 0 ); + break; } +#endif + +#if ( 1 == PAR_CFG_ENABLE_DESC ) && ( 1 == PAR_CFG_ENABLE_DESC_COMMA_CHECK ) + // ',' is prohibited in parameter description + // NOTE: ',' is used as column separator and will break PC tool side parser logic in case of usage in description! + if ( NULL != strchr( p_par_cfg[i].desc, ',' )) + { + status = ePAR_ERROR_INIT; + PAR_DBG_PRINT( "ERR, Parameter %d description contains comma!", i ); + PAR_ASSERT( 0 ); + break; + } +#endif } return status; @@ -471,7 +494,9 @@ par_status_t par_init(void) if ( ePAR_OK == status ) { gb_is_init = true; +#if ( 1 == PAR_CFG_ENABLE_ID ) gb_par_id_map_ready = true; +#endif // Set all parameters to default par_set_all_to_default(); @@ -508,7 +533,9 @@ par_status_t par_deinit(void) // Module de-initialized gb_is_init = false; +#if ( 1 == PAR_CFG_ENABLE_ID ) gb_par_id_map_ready = false; +#endif return status; } @@ -626,6 +653,7 @@ par_status_t par_set(const par_num_t par_num, const void * p_val) * @return status - Status of operation */ //////////////////////////////////////////////////////////////////////////////// +#if ( 1 == PAR_CFG_ENABLE_ID ) par_status_t par_set_by_id(const uint16_t id, const void * p_val) { par_num_t par_num; @@ -639,6 +667,7 @@ par_status_t par_set_by_id(const uint16_t id, const void * p_val) return ePAR_ERROR; } } +#endif //////////////////////////////////////////////////////////////////////////////// /** @@ -1042,6 +1071,7 @@ par_status_t par_set_u8_fast(const par_num_t par_num, const uint8_t val) PAR_ASSERT( true == par_is_init()); PAR_ASSERT( ePAR_TYPE_U8 == par_get_type(par_num)); +#if ( 1 == PAR_CFG_ENABLE_RANGE ) const par_range_t range = par_get_range(par_num); if ( val > range.max.u8 ) @@ -1059,6 +1089,10 @@ par_status_t par_set_u8_fast(const par_num_t par_num, const uint8_t val) PAR_SET_U8_PRIV( par_num, val ); return ePAR_OK; } +#else + PAR_SET_U8_PRIV( par_num, val ); + return ePAR_OK; +#endif } //////////////////////////////////////////////////////////////////////////////// @@ -1075,6 +1109,7 @@ par_status_t par_set_i8_fast(const par_num_t par_num, const int8_t val) PAR_ASSERT( true == par_is_init()); PAR_ASSERT( ePAR_TYPE_I8 == par_get_type(par_num)); +#if ( 1 == PAR_CFG_ENABLE_RANGE ) const par_range_t range = par_get_range(par_num); if ( val > range.max.i8 ) @@ -1092,6 +1127,10 @@ par_status_t par_set_i8_fast(const par_num_t par_num, const int8_t val) PAR_SET_I8_PRIV( par_num, val ); return ePAR_OK; } +#else + PAR_SET_I8_PRIV( par_num, val ); + return ePAR_OK; +#endif } //////////////////////////////////////////////////////////////////////////////// @@ -1108,6 +1147,7 @@ par_status_t par_set_u16_fast(const par_num_t par_num, const uint16_t val) PAR_ASSERT( true == par_is_init()); PAR_ASSERT( ePAR_TYPE_U16 == par_get_type(par_num)); +#if ( 1 == PAR_CFG_ENABLE_RANGE ) const par_range_t range = par_get_range(par_num); if ( val > range.max.u16 ) @@ -1125,6 +1165,10 @@ par_status_t par_set_u16_fast(const par_num_t par_num, const uint16_t val) PAR_SET_U16_PRIV( par_num, val ); return ePAR_OK; } +#else + PAR_SET_U16_PRIV( par_num, val ); + return ePAR_OK; +#endif } //////////////////////////////////////////////////////////////////////////////// @@ -1141,6 +1185,7 @@ par_status_t par_set_i16_fast(const par_num_t par_num, const int16_t val) PAR_ASSERT( true == par_is_init()); PAR_ASSERT( ePAR_TYPE_I16 == par_get_type(par_num)); +#if ( 1 == PAR_CFG_ENABLE_RANGE ) const par_range_t range = par_get_range(par_num); if ( val > range.max.i16 ) @@ -1158,6 +1203,10 @@ par_status_t par_set_i16_fast(const par_num_t par_num, const int16_t val) PAR_SET_I16_PRIV( par_num, val ); return ePAR_OK; } +#else + PAR_SET_I16_PRIV( par_num, val ); + return ePAR_OK; +#endif } //////////////////////////////////////////////////////////////////////////////// @@ -1174,6 +1223,7 @@ par_status_t par_set_u32_fast(const par_num_t par_num, const uint32_t val) PAR_ASSERT( true == par_is_init()); PAR_ASSERT( ePAR_TYPE_U32 == par_get_type(par_num)); +#if ( 1 == PAR_CFG_ENABLE_RANGE ) const par_range_t range = par_get_range(par_num); if ( val > range.max.u32 ) @@ -1191,6 +1241,10 @@ par_status_t par_set_u32_fast(const par_num_t par_num, const uint32_t val) PAR_SET_U32_PRIV( par_num, val ); return ePAR_OK; } +#else + PAR_SET_U32_PRIV( par_num, val ); + return ePAR_OK; +#endif } //////////////////////////////////////////////////////////////////////////////// @@ -1207,6 +1261,7 @@ par_status_t par_set_i32_fast(const par_num_t par_num, const int32_t val) PAR_ASSERT( true == par_is_init()); PAR_ASSERT( ePAR_TYPE_I32 == par_get_type(par_num)); +#if ( 1 == PAR_CFG_ENABLE_RANGE ) const par_range_t range = par_get_range(par_num); if ( val > range.max.i32 ) @@ -1224,6 +1279,10 @@ par_status_t par_set_i32_fast(const par_num_t par_num, const int32_t val) PAR_SET_I32_PRIV( par_num, val ); return ePAR_OK; } +#else + PAR_SET_I32_PRIV( par_num, val ); + return ePAR_OK; +#endif } //////////////////////////////////////////////////////////////////////////////// @@ -1240,6 +1299,7 @@ par_status_t par_set_f32_fast(const par_num_t par_num, const float32_t val) PAR_ASSERT( true == par_is_init()); PAR_ASSERT( ePAR_TYPE_F32 == par_get_type(par_num)); +#if ( 1 == PAR_CFG_ENABLE_RANGE ) const par_range_t range = par_get_range(par_num); if ( val > range.max.f32 ) @@ -1257,6 +1317,10 @@ par_status_t par_set_f32_fast(const par_num_t par_num, const float32_t val) PAR_SET_F32_PRIV( par_num, val ); return ePAR_OK; } +#else + PAR_SET_F32_PRIV( par_num, val ); + return ePAR_OK; +#endif } //////////////////////////////////////////////////////////////////////////////// @@ -1273,6 +1337,7 @@ par_status_t par_bitand_set_u8_fast(const par_num_t par_num, const uint8_t val) PAR_ASSERT( true == par_is_init()); PAR_ASSERT( ePAR_TYPE_U8 == par_get_type(par_num)); +#if ( 1 == PAR_CFG_ENABLE_RANGE ) const par_range_t range = par_get_range(par_num); if ( val > range.max.u8 ) @@ -1290,6 +1355,10 @@ par_status_t par_bitand_set_u8_fast(const par_num_t par_num, const uint8_t val) PAR_ATOMIC_FETCH_AND(u8, &gpu8_par_value[gu32_par_offset[par_num]], val); return ePAR_OK; } +#else + PAR_ATOMIC_FETCH_AND(u8, &gpu8_par_value[gu32_par_offset[par_num]], val); + return ePAR_OK; +#endif } //////////////////////////////////////////////////////////////////////////////// @@ -1306,6 +1375,7 @@ par_status_t par_bitand_set_u16_fast(const par_num_t par_num, const uint16_t val PAR_ASSERT( true == par_is_init()); PAR_ASSERT( ePAR_TYPE_U16 == par_get_type(par_num)); +#if ( 1 == PAR_CFG_ENABLE_RANGE ) const par_range_t range = par_get_range(par_num); if ( val > range.max.u16 ) @@ -1323,6 +1393,10 @@ par_status_t par_bitand_set_u16_fast(const par_num_t par_num, const uint16_t val PAR_ATOMIC_FETCH_AND(u16, &gpu16_par_value[gu32_par_offset[par_num]], val); return ePAR_OK; } +#else + PAR_ATOMIC_FETCH_AND(u16, &gpu16_par_value[gu32_par_offset[par_num]], val); + return ePAR_OK; +#endif } //////////////////////////////////////////////////////////////////////////////// @@ -1339,6 +1413,7 @@ par_status_t par_bitand_set_u32_fast(const par_num_t par_num, const uint32_t val PAR_ASSERT( true == par_is_init()); PAR_ASSERT( ePAR_TYPE_U32 == par_get_type(par_num)); +#if ( 1 == PAR_CFG_ENABLE_RANGE ) const par_range_t range = par_get_range(par_num); if ( val > range.max.u32 ) @@ -1356,6 +1431,10 @@ par_status_t par_bitand_set_u32_fast(const par_num_t par_num, const uint32_t val PAR_ATOMIC_FETCH_AND(u32, &gpu32_par_value[gu32_par_offset[par_num]], val); return ePAR_OK; } +#else + PAR_ATOMIC_FETCH_AND(u32, &gpu32_par_value[gu32_par_offset[par_num]], val); + return ePAR_OK; +#endif } //////////////////////////////////////////////////////////////////////////////// @@ -1372,6 +1451,7 @@ par_status_t par_bitor_set_u8_fast(const par_num_t par_num, const uint8_t val) PAR_ASSERT( true == par_is_init()); PAR_ASSERT( ePAR_TYPE_U8 == par_get_type(par_num)); +#if ( 1 == PAR_CFG_ENABLE_RANGE ) const par_range_t range = par_get_range(par_num); if ( val > range.max.u8 ) @@ -1389,6 +1469,10 @@ par_status_t par_bitor_set_u8_fast(const par_num_t par_num, const uint8_t val) PAR_ATOMIC_FETCH_OR(u8, &gpu8_par_value[gu32_par_offset[par_num]], val); return ePAR_OK; } +#else + PAR_ATOMIC_FETCH_OR(u8, &gpu8_par_value[gu32_par_offset[par_num]], val); + return ePAR_OK; +#endif } //////////////////////////////////////////////////////////////////////////////// @@ -1405,6 +1489,7 @@ par_status_t par_bitor_set_u16_fast(const par_num_t par_num, const uint16_t val) PAR_ASSERT( true == par_is_init()); PAR_ASSERT( ePAR_TYPE_U16 == par_get_type(par_num)); +#if ( 1 == PAR_CFG_ENABLE_RANGE ) const par_range_t range = par_get_range(par_num); if ( val > range.max.u16 ) @@ -1422,6 +1507,10 @@ par_status_t par_bitor_set_u16_fast(const par_num_t par_num, const uint16_t val) PAR_ATOMIC_FETCH_OR(u16, &gpu16_par_value[gu32_par_offset[par_num]], val); return ePAR_OK; } +#else + PAR_ATOMIC_FETCH_OR(u16, &gpu16_par_value[gu32_par_offset[par_num]], val); + return ePAR_OK; +#endif } //////////////////////////////////////////////////////////////////////////////// @@ -1438,6 +1527,7 @@ par_status_t par_bitor_set_u32_fast(const par_num_t par_num, const uint32_t val) PAR_ASSERT( true == par_is_init()); PAR_ASSERT( ePAR_TYPE_U32 == par_get_type(par_num)); +#if ( 1 == PAR_CFG_ENABLE_RANGE ) const par_range_t range = par_get_range(par_num); if ( val > range.max.u32 ) @@ -1455,6 +1545,10 @@ par_status_t par_bitor_set_u32_fast(const par_num_t par_num, const uint32_t val) PAR_ATOMIC_FETCH_OR(u32, &gpu32_par_value[gu32_par_offset[par_num]], val); return ePAR_OK; } +#else + PAR_ATOMIC_FETCH_OR(u32, &gpu32_par_value[gu32_par_offset[par_num]], val); + return ePAR_OK; +#endif } //////////////////////////////////////////////////////////////////////////////// @@ -1614,6 +1708,7 @@ par_status_t par_get(const par_num_t par_num, void * const p_val) * @return status - Status of operation */ //////////////////////////////////////////////////////////////////////////////// +#if ( 1 == PAR_CFG_ENABLE_ID ) par_status_t par_get_by_id(const uint16_t id, void * const p_val) { par_num_t par_num; @@ -1627,6 +1722,7 @@ par_status_t par_get_by_id(const uint16_t id, void * const p_val) return ePAR_ERROR; } } +#endif //////////////////////////////////////////////////////////////////////////////// /** @@ -1901,6 +1997,7 @@ const par_cfg_t * par_get_config(const par_num_t par_num) * @return Parameter name */ //////////////////////////////////////////////////////////////////////////////// +#if ( 1 == PAR_CFG_ENABLE_NAME ) const char * par_get_name(const par_num_t par_num) { const par_cfg_t * const par_cfg = par_get_config(par_num); @@ -1912,6 +2009,7 @@ const char * par_get_name(const par_num_t par_num) return NULL; } +#endif //////////////////////////////////////////////////////////////////////////////// /** @@ -1921,6 +2019,7 @@ const char * par_get_name(const par_num_t par_num) * @return Parameter min/max range */ //////////////////////////////////////////////////////////////////////////////// +#if ( 1 == PAR_CFG_ENABLE_RANGE ) par_range_t par_get_range(const par_num_t par_num) { par_range_t range = {0}; @@ -1934,6 +2033,7 @@ par_range_t par_get_range(const par_num_t par_num) return range; } +#endif //////////////////////////////////////////////////////////////////////////////// /** @@ -1943,6 +2043,7 @@ par_range_t par_get_range(const par_num_t par_num) * @return Parameter unit */ //////////////////////////////////////////////////////////////////////////////// +#if ( 1 == PAR_CFG_ENABLE_UNIT ) const char * par_get_unit(const par_num_t par_num) { const par_cfg_t * const par_cfg = par_get_config(par_num); @@ -1954,6 +2055,7 @@ const char * par_get_unit(const par_num_t par_num) return NULL; } +#endif //////////////////////////////////////////////////////////////////////////////// /** @@ -1963,6 +2065,7 @@ const char * par_get_unit(const par_num_t par_num) * @return Parameter description */ //////////////////////////////////////////////////////////////////////////////// +#if ( 1 == PAR_CFG_ENABLE_DESC ) const char * par_get_desc(const par_num_t par_num) { const par_cfg_t * const par_cfg = par_get_config(par_num); @@ -1974,6 +2077,7 @@ const char * par_get_desc(const par_num_t par_num) return NULL; } +#endif //////////////////////////////////////////////////////////////////////////////// /** @@ -2003,6 +2107,7 @@ par_type_list_t par_get_type(const par_num_t par_num) * @return Parameter access */ //////////////////////////////////////////////////////////////////////////////// +#if ( 1 == PAR_CFG_ENABLE_ACCESS ) par_access_t par_get_access(const par_num_t par_num) { const par_cfg_t * const par_cfg = par_get_config(par_num); @@ -2014,6 +2119,7 @@ par_access_t par_get_access(const par_num_t par_num) return ePAR_ACCESS_RO; } +#endif //////////////////////////////////////////////////////////////////////////////// /** @@ -2023,6 +2129,7 @@ par_access_t par_get_access(const par_num_t par_num) * @return True if parameter persistant */ //////////////////////////////////////////////////////////////////////////////// +#if ( 1 == PAR_CFG_ENABLE_PERSIST ) bool par_is_persistant(const par_num_t par_num) { const par_cfg_t * const par_cfg = par_get_config(par_num); @@ -2034,6 +2141,7 @@ bool par_is_persistant(const par_num_t par_num) return false; } +#endif //////////////////////////////////////////////////////////////////////////////// /** @@ -2044,6 +2152,7 @@ bool par_is_persistant(const par_num_t par_num) * @return status - Status of operation */ //////////////////////////////////////////////////////////////////////////////// +#if ( 1 == PAR_CFG_ENABLE_ID ) par_status_t par_get_num_by_id(const uint16_t id, par_num_t * const p_par_num) { if (( NULL != p_par_num ) && ( true == gb_par_id_map_ready )) @@ -2060,6 +2169,7 @@ par_status_t par_get_num_by_id(const uint16_t id, par_num_t * const p_par_num) return ePAR_ERROR; } +#endif //////////////////////////////////////////////////////////////////////////////// /** @@ -2070,6 +2180,7 @@ par_status_t par_get_num_by_id(const uint16_t id, par_num_t * const p_par_num) * @return status - Status of operation */ //////////////////////////////////////////////////////////////////////////////// +#if ( 1 == PAR_CFG_ENABLE_ID ) par_status_t par_get_id_by_num(const par_num_t par_num, uint16_t * const p_id) { if ( NULL != p_id ) @@ -2085,6 +2196,7 @@ par_status_t par_get_id_by_num(const par_num_t par_num, uint16_t * const p_id) return ePAR_ERROR; } +#endif #if ( 1 == PAR_CFG_NVM_EN ) //////////////////////////////////////////////////////////////////////////////// @@ -2185,6 +2297,7 @@ par_status_t par_get_id_by_num(const par_num_t par_num, uint16_t * const p_id) * @return status - Status of operation */ //////////////////////////////////////////////////////////////////////////////// +#if ( 1 == PAR_CFG_ENABLE_ID ) par_status_t par_save_by_id(const uint16_t par_id) { par_num_t par_num = 0; @@ -2200,6 +2313,7 @@ par_status_t par_get_id_by_num(const par_num_t par_num, uint16_t * const p_id) return ePAR_ERROR; } +#endif //////////////////////////////////////////////////////////////////////////////// /** diff --git a/src/par.h b/src/par.h index 9a0eb94..c502da5 100644 --- a/src/par.h +++ b/src/par.h @@ -116,11 +116,13 @@ typedef union /** * Parameter value range */ +#if ( 1 == PAR_CFG_ENABLE_RANGE ) typedef struct { par_type_t min; /**= (min_))); \ @@ -51,6 +52,14 @@ #define PAR_CHECK_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) #define PAR_CHECK_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) #define PAR_CHECK_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +#else +#define PAR_CHECK_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#define PAR_CHECK_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#define PAR_CHECK_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#define PAR_CHECK_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#define PAR_CHECK_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#define PAR_CHECK_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#endif /* * NOTE: F32 range checks are runtime-only. * @@ -88,7 +97,9 @@ #undef PAR_CHECK_I16 #undef PAR_CHECK_I32 #undef PAR_CHECK_F32 +#if ( 1 == PAR_CFG_ENABLE_RANGE ) #undef PAR_CHECK_INT_COMMON +#endif //////////////////////////////////////////////////////////////////////////////// // Variables @@ -120,102 +131,149 @@ * Signature: * (enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) */ -#define PAR_INIT_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ - [enum_] = { \ - .id = (uint16_t)(id_), \ - .name = (name_), \ - .min.u8 = (uint8_t)(min_), \ - .max.u8 = (uint8_t)(max_), \ - .def.u8 = (uint8_t)(def_), \ - .unit = (unit_), \ - .type = ePAR_TYPE_U8, \ - .access = (access_), \ - .persistant = (pers_), \ - .desc = (desc_) \ +#if ( 1 == PAR_CFG_ENABLE_ID ) + #define PAR_INIT_ID(id_) .id = (uint16_t)(id_), +#else + #define PAR_INIT_ID(id_) +#endif + +#if ( 1 == PAR_CFG_ENABLE_NAME ) + #define PAR_INIT_NAME(name_) .name = (name_), +#else + #define PAR_INIT_NAME(name_) +#endif + +#if ( 1 == PAR_CFG_ENABLE_RANGE ) + #define PAR_INIT_RANGE_U8(min_, max_) .min.u8 = (uint8_t)(min_), .max.u8 = (uint8_t)(max_), + #define PAR_INIT_RANGE_U16(min_, max_) .min.u16 = (uint16_t)(min_), .max.u16 = (uint16_t)(max_), + #define PAR_INIT_RANGE_U32(min_, max_) .min.u32 = (uint32_t)(min_), .max.u32 = (uint32_t)(max_), + #define PAR_INIT_RANGE_I8(min_, max_) .min.i8 = (int8_t)(min_), .max.i8 = (int8_t)(max_), + #define PAR_INIT_RANGE_I16(min_, max_) .min.i16 = (int16_t)(min_), .max.i16 = (int16_t)(max_), + #define PAR_INIT_RANGE_I32(min_, max_) .min.i32 = (int32_t)(min_), .max.i32 = (int32_t)(max_), + #define PAR_INIT_RANGE_F32(min_, max_) .min.f32 = (float32_t)(min_), .max.f32 = (float32_t)(max_), +#else + #define PAR_INIT_RANGE_U8(min_, max_) + #define PAR_INIT_RANGE_U16(min_, max_) + #define PAR_INIT_RANGE_U32(min_, max_) + #define PAR_INIT_RANGE_I8(min_, max_) + #define PAR_INIT_RANGE_I16(min_, max_) + #define PAR_INIT_RANGE_I32(min_, max_) + #define PAR_INIT_RANGE_F32(min_, max_) +#endif + +#if ( 1 == PAR_CFG_ENABLE_UNIT ) + #define PAR_INIT_UNIT(unit_) .unit = (unit_), +#else + #define PAR_INIT_UNIT(unit_) +#endif + +#if ( 1 == PAR_CFG_ENABLE_ACCESS ) + #define PAR_INIT_ACCESS(access_) .access = (access_), +#else + #define PAR_INIT_ACCESS(access_) +#endif + +#if ( 1 == PAR_CFG_ENABLE_PERSIST ) + #define PAR_INIT_PERSIST(pers_) .persistant = (pers_), +#else + #define PAR_INIT_PERSIST(pers_) +#endif + +#if ( 1 == PAR_CFG_ENABLE_DESC ) + #define PAR_INIT_DESC(desc_) .desc = (desc_), +#else + #define PAR_INIT_DESC(desc_) +#endif + +#define PAR_INIT_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_U8(min_, max_) \ + .def.u8 = (uint8_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_U8, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_PERSIST(pers_) \ + PAR_INIT_DESC(desc_) \ }, -#define PAR_INIT_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ - [enum_] = { \ - .id = (uint16_t)(id_), \ - .name = (name_), \ - .min.u16 = (uint16_t)(min_), \ - .max.u16 = (uint16_t)(max_), \ - .def.u16 = (uint16_t)(def_), \ - .unit = (unit_), \ - .type = ePAR_TYPE_U16, \ - .access = (access_), \ - .persistant = (pers_), \ - .desc = (desc_) \ +#define PAR_INIT_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_U16(min_, max_) \ + .def.u16 = (uint16_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_U16, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_PERSIST(pers_) \ + PAR_INIT_DESC(desc_) \ }, -#define PAR_INIT_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ - [enum_] = { \ - .id = (uint16_t)(id_), \ - .name = (name_), \ - .min.u32 = (uint32_t)(min_), \ - .max.u32 = (uint32_t)(max_), \ - .def.u32 = (uint32_t)(def_), \ - .unit = (unit_), \ - .type = ePAR_TYPE_U32, \ - .access = (access_), \ - .persistant = (pers_), \ - .desc = (desc_) \ +#define PAR_INIT_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_U32(min_, max_) \ + .def.u32 = (uint32_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_U32, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_PERSIST(pers_) \ + PAR_INIT_DESC(desc_) \ }, -#define PAR_INIT_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ - [enum_] = { \ - .id = (uint16_t)(id_), \ - .name = (name_), \ - .min.i8 = (int8_t)(min_), \ - .max.i8 = (int8_t)(max_), \ - .def.i8 = (int8_t)(def_), \ - .unit = (unit_), \ - .type = ePAR_TYPE_I8, \ - .access = (access_), \ - .persistant = (pers_), \ - .desc = (desc_) \ +#define PAR_INIT_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_I8(min_, max_) \ + .def.i8 = (int8_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_I8, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_PERSIST(pers_) \ + PAR_INIT_DESC(desc_) \ }, -#define PAR_INIT_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ - [enum_] = { \ - .id = (uint16_t)(id_), \ - .name = (name_), \ - .min.i16 = (int16_t)(min_), \ - .max.i16 = (int16_t)(max_), \ - .def.i16 = (int16_t)(def_), \ - .unit = (unit_), \ - .type = ePAR_TYPE_I16, \ - .access = (access_), \ - .persistant = (pers_), \ - .desc = (desc_) \ +#define PAR_INIT_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_I16(min_, max_) \ + .def.i16 = (int16_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_I16, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_PERSIST(pers_) \ + PAR_INIT_DESC(desc_) \ }, -#define PAR_INIT_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ - [enum_] = { \ - .id = (uint16_t)(id_), \ - .name = (name_), \ - .min.i32 = (int32_t)(min_), \ - .max.i32 = (int32_t)(max_), \ - .def.i32 = (int32_t)(def_), \ - .unit = (unit_), \ - .type = ePAR_TYPE_I32, \ - .access = (access_), \ - .persistant = (pers_), \ - .desc = (desc_) \ +#define PAR_INIT_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_I32(min_, max_) \ + .def.i32 = (int32_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_I32, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_PERSIST(pers_) \ + PAR_INIT_DESC(desc_) \ }, -#define PAR_INIT_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ - [enum_] = { \ - .id = (uint16_t)(id_), \ - .name = (name_), \ - .min.f32 = (float32_t)(min_), \ - .max.f32 = (float32_t)(max_), \ - .def.f32 = (float32_t)(def_), \ - .unit = (unit_), \ - .type = ePAR_TYPE_F32, \ - .access = (access_), \ - .persistant = (pers_), \ - .desc = (desc_) \ +#define PAR_INIT_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_F32(min_, max_) \ + .def.f32 = (float32_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_F32, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_PERSIST(pers_) \ + PAR_INIT_DESC(desc_) \ }, /** @@ -249,6 +307,19 @@ static const par_cfg_t g_par_table[ePAR_NUM_OF] = #undef PAR_INIT_I16 #undef PAR_INIT_I32 #undef PAR_INIT_F32 +#undef PAR_INIT_ID +#undef PAR_INIT_NAME +#undef PAR_INIT_RANGE_U8 +#undef PAR_INIT_RANGE_U16 +#undef PAR_INIT_RANGE_U32 +#undef PAR_INIT_RANGE_I8 +#undef PAR_INIT_RANGE_I16 +#undef PAR_INIT_RANGE_I32 +#undef PAR_INIT_RANGE_F32 +#undef PAR_INIT_UNIT +#undef PAR_INIT_ACCESS +#undef PAR_INIT_PERSIST +#undef PAR_INIT_DESC /** * Table size in bytes From cef94e5d41d1858698aeca44fbf9a83834b594b1 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Mon, 16 Mar 2026 16:06:35 +0800 Subject: [PATCH 07/36] feat(core): Add overridable parameter description validation hook --- README.md | 17 ++++++++++++----- src/par.c | 26 +++++++++++++++++++++----- src/par.h | 4 ++++ src/par_cfg.h | 16 +++++++++++++--- 4 files changed, 50 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 4f8530e..bc2c73b 100644 --- a/README.md +++ b/README.md @@ -300,7 +300,7 @@ This guarantees that invalid parameter configurations cannot pass the build stag --- -### Example compile-time error +#### Example compile-time error If the default value exceeds the allowed range: @@ -327,7 +327,12 @@ Runtime validation includes: * floating-point (`F32`) range validation * parameter `name` must not be `NULL` * parameter `desc` must not be `NULL` -* `desc` must not contain `,` +* parameter description validity check through `par_port_is_desc_valid()` + +By default, the weak implementation of `par_port_is_desc_valid()` rejects descriptions containing `,`. +This default policy helps keep descriptions compatible with CSV- and CLI-oriented tooling. + +Applications may override `par_port_is_desc_valid()` to enforce a stronger or different description policy. These checks are executed during module initialization in: `par_check_table_validity()` located in `par.c`. @@ -335,7 +340,7 @@ If a runtime validation fails, module initialization will fail and an error will --- -### Why `F32` validation is runtime-only +#### Why `F32` validation is runtime-only Floating-point values are not ideal for strict compile-time validation in embedded toolchains because: @@ -357,7 +362,9 @@ To maintain portability and predictable builds, `F32` validation is performed ** | float range check | runtime | `F32` | | `name != NULL` | runtime | all parameters | | `desc != NULL` | runtime | all parameters | -| `desc` must not contain `,` | runtime | all parameters | +| `desc` validity check via `par_port_is_desc_valid()` | runtime | all parameters | + +The default weak implementation rejects `,` in descriptions, but this behavior may be overridden by the application. ## **Configuration model** @@ -837,7 +844,7 @@ Platform-specific overrides and hooks are provided in: `port/par_cfg_port.h` | **PAR_CFG_ENABLE_NAME** | Enable parameter name metadata and `par_get_name()` API. | | **PAR_CFG_ENABLE_UNIT** | Enable parameter unit metadata and `par_get_unit()` API. | | **PAR_CFG_ENABLE_DESC** | Enable parameter description metadata and `par_get_desc()` API. | -| **PAR_CFG_ENABLE_DESC_COMMA_CHECK** | Enable validation that forbids `,` inside parameter descriptions (used by CSV/CLI tools). | +| **PAR_CFG_ENABLE_DESC_CHECK** | Enable description validity checks. The default policy forbids `,` in descriptions, and the application may override the validation hook. | | **PAR_CFG_ENABLE_ID** | Enable parameter ID metadata and ID-based APIs (`par_get_by_id`, `par_set_by_id`). | | **PAR_CFG_ENABLE_ACCESS** | Enable access control metadata (`RO/RW`) and `par_get_access()` API. | | **PAR_CFG_ENABLE_PERSIST** | Enable parameter persistence metadata and persistent parameter support. | diff --git a/src/par.c b/src/par.c index 5fc1c58..5469ef9 100644 --- a/src/par.c +++ b/src/par.c @@ -193,6 +193,24 @@ static bool par_is_value_changed (const par_num_t par_num, co // Functions //////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +#if ( 1 == PAR_CFG_ENABLE_DESC ) && ( 1 == PAR_CFG_ENABLE_DESC_CHECK ) +/** +* Validate parameter description string +* +* @note Default weak implementation only prohibits comma character. +* Application may override this symbol with stronger policy. +* +* @param[in] p_desc - Parameter description +* @return true if description is valid +*/ +//////////////////////////////////////////////////////////////////////////////// +PAR_PORT_WEAK bool par_port_is_desc_valid(const char * const p_desc) +{ + return ((NULL == p_desc) || (NULL == strchr(p_desc, ','))); +} +#endif + //////////////////////////////////////////////////////////////////////////////// /** * Allocate space for live parameter values @@ -377,13 +395,11 @@ static par_status_t par_check_table_validity(const par_cfg_t * const p_par_cfg) } #endif -#if ( 1 == PAR_CFG_ENABLE_DESC ) && ( 1 == PAR_CFG_ENABLE_DESC_COMMA_CHECK ) - // ',' is prohibited in parameter description - // NOTE: ',' is used as column separator and will break PC tool side parser logic in case of usage in description! - if ( NULL != strchr( p_par_cfg[i].desc, ',' )) +#if ( 1 == PAR_CFG_ENABLE_DESC ) && ( 1 == PAR_CFG_ENABLE_DESC_CHECK ) + if ( false == par_port_is_desc_valid( p_par_cfg[i].desc)) { status = ePAR_ERROR_INIT; - PAR_DBG_PRINT( "ERR, Parameter %d description contains comma!", i ); + PAR_DBG_PRINT( "ERR, Parameter %d description is invalid!", i ); PAR_ASSERT( 0 ); break; } diff --git a/src/par.h b/src/par.h index c502da5..649a71e 100644 --- a/src/par.h +++ b/src/par.h @@ -307,6 +307,10 @@ par_status_t par_get_id_by_num (const par_num_t par_num, uint16_t * con void par_register_on_change_cb (const par_num_t par_num, const pf_par_on_change_cb_t cb); void par_register_validation (const par_num_t par_num, const pf_par_validation_t validation); +#if ( 1 == PAR_CFG_ENABLE_DESC ) && ( 1 == PAR_CFG_ENABLE_DESC_CHECK ) +PAR_PORT_WEAK bool par_port_is_desc_valid(const char * const p_desc); +#endif + #if ( PAR_CFG_DEBUG_EN ) const char * par_get_status_str(const par_status_t status); #endif diff --git a/src/par_cfg.h b/src/par_cfg.h index 3d2c6f0..d4c8bda 100644 --- a/src/par_cfg.h +++ b/src/par_cfg.h @@ -26,6 +26,7 @@ // Includes //////////////////////////////////////////////////////////////////////////////// #include +#include #include "par_def.h" // USER CODE BEGIN... @@ -129,6 +130,15 @@ #define PAR_PORT_STATIC_ASSERT(name, expn) typedef char _static_assert_##name[(expn) ? 1 : -1] #endif +/** + * Platform weak symbol macro + * + * @note Integrator may override this macro (for example: RT_WEAK). + */ +#ifndef PAR_PORT_WEAK + #define PAR_PORT_WEAK __attribute__((weak)) +#endif + /** * Package compile-time assert */ @@ -280,12 +290,12 @@ #endif /** - * Enable/Disable description comma check + * Enable/Disable description check * * @note Default follows PAR_CFG_ENABLE_DESC. */ -#ifndef PAR_CFG_ENABLE_DESC_COMMA_CHECK - #define PAR_CFG_ENABLE_DESC_COMMA_CHECK ( PAR_CFG_ENABLE_DESC ) +#ifndef PAR_CFG_ENABLE_DESC_CHECK + #define PAR_CFG_ENABLE_DESC_CHECK ( PAR_CFG_ENABLE_DESC ) #endif /** From c525bf920a00f6a05048132ca57380d8dc3da6e3 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Tue, 17 Mar 2026 15:55:11 +0800 Subject: [PATCH 08/36] refactor(core): Replace dynamic parameter storage with static layout --- README.md | 100 +++++++++++++++- src/par.c | 199 +++++++++++++------------------- src/par_atomic.h | 15 ++- src/par_cfg.h | 50 ++++++++ src/par_def.c | 4 +- src/par_def.h | 112 ++++++++++++++++++ src/par_layout.c | 174 ++++++++++++++++++++++++++++ src/par_layout.h | 67 +++++++++++ template/par_if.htmp | 42 ------- template/par_layout_static.htmp | 33 ++++++ 10 files changed, 632 insertions(+), 164 deletions(-) create mode 100644 src/par_layout.c create mode 100644 src/par_layout.h delete mode 100644 template/par_if.htmp create mode 100644 template/par_layout_static.htmp diff --git a/README.md b/README.md index bc2c73b..e358895 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ For more details about storage into NVM look at the [General Embedded C Library - **Persistence**: Transparent NVM (Non-Volatile Memory) integration with CRC and signature verification. - **Observability**: Built-in support for parameter names, units, and descriptions for easy CLI/GUI integration. - **Event-Driven**: Chained callback system for notifying other modules of parameter changes. +- **No Heap Requirement for Live Storage**: Parameter live values are stored in statically allocated typed groups, which avoids runtime heap allocation and improves predictability for embedded targets. ## Integration with other modules in General Embedded C Libraries ecosystem When combined with the following modules, the **Device Parameters** module significantly enhances the capabilities of embedded applications: @@ -96,11 +97,32 @@ After that, `par_atomic.h` will include `par_atomic_port.h` and use the port-pro * keep all platform-specific atomic adaptation inside `par_atomic_port.h` so the core parameter code does not need to change ## **Limitations** - - **Heap Usage:** The module uses malloc during par_init() to allocate RAM space for the parameters based on the configuration table. Ensure your heap is sufficiently sized. - **Alignment:** Address offsets are calculated based on 4-byte (32-bit) alignment to satisfy most ARM Cortex-M requirements. - **Flat ID Space:** Parameter IDs must be unique across the entire table to ensure NVM consistency. - **Execution Time:** If many callbacks are chained to a single parameter, the par_set execution time will increase accordingly. - - **Hash-Based ID Lookup:** ID-based lookup is optimized for runtime speed and rejects hash collisions during initialization. See the ID lookup section below. + +## **Storage model** + +Parameter live values are stored in statically allocated width-based groups instead of using heap allocation. + +The storage is divided into three groups: + +- 8-bit group: `U8`, `I8` +- 16-bit group: `U16`, `I16` +- 32-bit group: `U32`, `I32`, `F32` + +Each parameter is assigned: + +- a storage group based on its type +- an offset inside that group + +Offsets are provided by the layout layer, which maps parameters to their position in the corresponding storage group. + +This design: + +- removes runtime heap dependency +- keeps memory usage predictable +- allows compact storage by sharing backing memory between compatible types ## **General Embedded C Libraries Ecosystem** In order to be part of *General Embedded C Libraries Ecosystem* this module must be placed in following path: `root/middleware/parameters/parameters/"module_space"` @@ -142,6 +164,7 @@ Reference templates are provided under: `parameters/template/` This layer currently contains: * `par_cfg_port.htmp` – port bridge header template +* `par_layout_static.htmp` – template for externally generated static layout definitions Template files are used as: @@ -402,10 +425,80 @@ If you do not need overrides, keep a minimal empty file: #endif ``` +### Layout-related configuration + +The storage layout can be configured independently from the parameter table logic. + +This allows the module to support: + +* source-driven layout generation +* externally generated layout metadata +* platform-specific alignment handling when needed + ### Why this split exists This design keeps the core implementation independent of any single build system or RTOS, while still allowing package-level integration through a dedicated platform bridge. +## **Storage layout** + +The module uses a dedicated layout layer to map each parameter to an offset inside a typed storage group. + +Live values are stored in three width-based groups: + +* 8-bit group: `U8`, `I8` +* 16-bit group: `U16`, `I16` +* 32-bit group: `U32`, `I32`, `F32` + +Each parameter is assigned: + +* a storage group based on its type +* an offset inside that group + +The active offset map is provided by the layout subsystem through `par_layout`. + +### Layout source options + +The storage layout source is controlled by `PAR_CFG_LAYOUT_SOURCE`. + +Available options: + +```c +#define PAR_CFG_LAYOUT_COMPILE_SCAN ( 0u ) +#define PAR_CFG_LAYOUT_SCRIPT ( 1u ) +``` + +#### `PAR_CFG_LAYOUT_COMPILE_SCAN` + +* storage counts are derived from `par_table.def` +* offsets are generated during initialization by scanning the parameter table + +Use this mode when: + +* you want a self-contained integration +* parameter definitions are maintained directly in source +* no external code generation step is used + +#### `PAR_CFG_LAYOUT_SCRIPT` + +* storage counts and offset table are provided by a generated static layout header +* the layout layer consumes the generated data directly + +Use this mode when: + +* parameter tables are generated by tooling +* you want layout data to be fixed before compilation +* you want the build system to own offset generation + +### Static layout include + +The generated static layout header path is selected through: + +```c +#define PAR_CFG_LAYOUT_STATIC_INCLUDE "par_layout_static.h" +``` + +This can be overridden by the integrator if the generated file lives elsewhere. + --- ## **Port hooks** @@ -848,6 +941,9 @@ Platform-specific overrides and hooks are provided in: `port/par_cfg_port.h` | **PAR_CFG_ENABLE_ID** | Enable parameter ID metadata and ID-based APIs (`par_get_by_id`, `par_set_by_id`). | | **PAR_CFG_ENABLE_ACCESS** | Enable access control metadata (`RO/RW`) and `par_get_access()` API. | | **PAR_CFG_ENABLE_PERSIST** | Enable parameter persistence metadata and persistent parameter support. | +| **PAR_CFG_LAYOUT_SOURCE** | Select the storage layout source: compile-time scan or generated static layout. | +| **PAR_CFG_LAYOUT_STATIC_INCLUDE** | Include path for the generated static layout header when script layout mode is used. | +| **PAR_ALIGNOF** | Alignment abstraction used by compile-time layout compatibility checks. May be overridden by the platform if needed. | ### **3. Build integration** diff --git a/src/par.c b/src/par.c index 5469ef9..e75e689 100644 --- a/src/par.c +++ b/src/par.c @@ -22,12 +22,12 @@ //////////////////////////////////////////////////////////////////////////////// // Includes //////////////////////////////////////////////////////////////////////////////// -#include #include #include #include "par.h" #include "par_atomic.h" +#include "par_layout.h" #include "par_nvm.h" #include "par_if.h" @@ -77,6 +77,15 @@ PAR_STATIC_ASSERT(par_id_hash_bits_valid, ((PAR_ID_HASH_BITS > 0u) && (PAR_ID_HASH_BITS < 32u))); #endif +PAR_STATIC_ASSERT(par_atomic_u8_i8_same_size, sizeof(par_atomic_u8_t) == sizeof(par_atomic_i8_t)); +PAR_STATIC_ASSERT(par_atomic_u8_i8_same_align, PAR_ALIGNOF(par_atomic_u8_t) == PAR_ALIGNOF(par_atomic_i8_t)); +PAR_STATIC_ASSERT(par_atomic_u16_i16_same_size, sizeof(par_atomic_u16_t) == sizeof(par_atomic_i16_t)); +PAR_STATIC_ASSERT(par_atomic_u16_i16_same_align, PAR_ALIGNOF(par_atomic_u16_t) == PAR_ALIGNOF(par_atomic_i16_t)); +PAR_STATIC_ASSERT(par_atomic_u32_i32_same_size, sizeof(par_atomic_u32_t) == sizeof(par_atomic_i32_t)); +PAR_STATIC_ASSERT(par_atomic_u32_i32_same_align, PAR_ALIGNOF(par_atomic_u32_t) == PAR_ALIGNOF(par_atomic_i32_t)); +PAR_STATIC_ASSERT(par_atomic_u32_f32_same_size, sizeof(par_atomic_u32_t) == sizeof(par_atomic_f32_t)); +PAR_STATIC_ASSERT(par_atomic_u32_f32_same_align, PAR_ALIGNOF(par_atomic_u32_t) == PAR_ALIGNOF(par_atomic_f32_t)); + //////////////////////////////////////////////////////////////////////////////// // Variables //////////////////////////////////////////////////////////////////////////////// @@ -117,40 +126,52 @@ static struct static bool gb_par_id_map_ready = false; #endif +/** + * Static typed storage backing parameter live values. + * + * @note Zero-length groups are mapped to size 1 arrays for compiler portability. + */ +static par_atomic_u8_t gs_par_u8_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT8)]; +static par_atomic_u16_t gs_par_u16_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT16)]; +static par_atomic_u32_t gs_par_u32_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT32)]; + /** * Parameter live values divided by its type in RAM */ -static par_atomic_u8_t * gpu8_par_value = NULL; -static par_atomic_i8_t * gpi8_par_value = NULL; -static par_atomic_u16_t * gpu16_par_value = NULL; -static par_atomic_i16_t * gpi16_par_value = NULL; -static par_atomic_u32_t * gpu32_par_value = NULL; -static par_atomic_i32_t * gpi32_par_value = NULL; -static par_atomic_f32_t * gpf32_par_value = NULL; +static par_atomic_u8_t * gpu8_par_value = gs_par_u8_storage; +static par_atomic_i8_t * gpi8_par_value = (par_atomic_i8_t *)gs_par_u8_storage; +static par_atomic_u16_t * gpu16_par_value = gs_par_u16_storage; +static par_atomic_i16_t * gpi16_par_value = (par_atomic_i16_t *)gs_par_u16_storage; +static par_atomic_u32_t * gpu32_par_value = gs_par_u32_storage; +static par_atomic_i32_t * gpi32_par_value = (par_atomic_i32_t *)gs_par_u32_storage; +static par_atomic_f32_t * gpf32_par_value = (par_atomic_f32_t *)gs_par_u32_storage; /** - * Address offset by parameter enumeration + * Offset table compatibility alias. + * + * @note Layout offsets are now owned by par_layout and accessed via getter. + * Keep this local alias so existing indexed access sites remain unchanged. */ -static uint32_t gu32_par_offset[ ePAR_NUM_OF ] = { 0 }; +#define g_par_offset (par_layout_get_offset_table()) /** * Private getters and setters */ -#define PAR_GET_U8_PRIV(par_num) PAR_ATOMIC_LOAD(u8, &gpu8_par_value[gu32_par_offset[par_num]]) -#define PAR_GET_I8_PRIV(par_num) PAR_ATOMIC_LOAD(i8, &gpi8_par_value[gu32_par_offset[par_num]]) -#define PAR_GET_U16_PRIV(par_num) PAR_ATOMIC_LOAD(u16, &gpu16_par_value[gu32_par_offset[par_num]]) -#define PAR_GET_I16_PRIV(par_num) PAR_ATOMIC_LOAD(i16, &gpi16_par_value[gu32_par_offset[par_num]]) -#define PAR_GET_U32_PRIV(par_num) PAR_ATOMIC_LOAD(u32, &gpu32_par_value[gu32_par_offset[par_num]]) -#define PAR_GET_I32_PRIV(par_num) PAR_ATOMIC_LOAD(i32, &gpi32_par_value[gu32_par_offset[par_num]]) -#define PAR_GET_F32_PRIV(par_num) PAR_ATOMIC_LOAD(f32, &gpf32_par_value[gu32_par_offset[par_num]]) - -#define PAR_SET_U8_PRIV(par_num, val) PAR_ATOMIC_STORE(u8, &gpu8_par_value[gu32_par_offset[par_num]], (val)) -#define PAR_SET_I8_PRIV(par_num, val) PAR_ATOMIC_STORE(i8, &gpi8_par_value[gu32_par_offset[par_num]], (val)) -#define PAR_SET_U16_PRIV(par_num, val) PAR_ATOMIC_STORE(u16, &gpu16_par_value[gu32_par_offset[par_num]], (val)) -#define PAR_SET_I16_PRIV(par_num, val) PAR_ATOMIC_STORE(i16, &gpi16_par_value[gu32_par_offset[par_num]], (val)) -#define PAR_SET_U32_PRIV(par_num, val) PAR_ATOMIC_STORE(u32, &gpu32_par_value[gu32_par_offset[par_num]], (val)) -#define PAR_SET_I32_PRIV(par_num, val) PAR_ATOMIC_STORE(i32, &gpi32_par_value[gu32_par_offset[par_num]], (val)) -#define PAR_SET_F32_PRIV(par_num, val) PAR_ATOMIC_STORE(f32, &gpf32_par_value[gu32_par_offset[par_num]], (val)) +#define PAR_GET_U8_PRIV(par_num) PAR_ATOMIC_LOAD(u8, &gpu8_par_value[g_par_offset[par_num]]) +#define PAR_GET_I8_PRIV(par_num) PAR_ATOMIC_LOAD(i8, &gpi8_par_value[g_par_offset[par_num]]) +#define PAR_GET_U16_PRIV(par_num) PAR_ATOMIC_LOAD(u16, &gpu16_par_value[g_par_offset[par_num]]) +#define PAR_GET_I16_PRIV(par_num) PAR_ATOMIC_LOAD(i16, &gpi16_par_value[g_par_offset[par_num]]) +#define PAR_GET_U32_PRIV(par_num) PAR_ATOMIC_LOAD(u32, &gpu32_par_value[g_par_offset[par_num]]) +#define PAR_GET_I32_PRIV(par_num) PAR_ATOMIC_LOAD(i32, &gpi32_par_value[g_par_offset[par_num]]) +#define PAR_GET_F32_PRIV(par_num) PAR_ATOMIC_LOAD(f32, &gpf32_par_value[g_par_offset[par_num]]) + +#define PAR_SET_U8_PRIV(par_num, val) PAR_ATOMIC_STORE(u8, &gpu8_par_value[g_par_offset[par_num]], (val)) +#define PAR_SET_I8_PRIV(par_num, val) PAR_ATOMIC_STORE(i8, &gpi8_par_value[g_par_offset[par_num]], (val)) +#define PAR_SET_U16_PRIV(par_num, val) PAR_ATOMIC_STORE(u16, &gpu16_par_value[g_par_offset[par_num]], (val)) +#define PAR_SET_I16_PRIV(par_num, val) PAR_ATOMIC_STORE(i16, &gpi16_par_value[g_par_offset[par_num]], (val)) +#define PAR_SET_U32_PRIV(par_num, val) PAR_ATOMIC_STORE(u32, &gpu32_par_value[g_par_offset[par_num]], (val)) +#define PAR_SET_I32_PRIV(par_num, val) PAR_ATOMIC_STORE(i32, &gpi32_par_value[g_par_offset[par_num]], (val)) +#define PAR_SET_F32_PRIV(par_num, val) PAR_ATOMIC_STORE(f32, &gpf32_par_value[g_par_offset[par_num]], (val)) #if ( PAR_CFG_DEBUG_EN ) @@ -179,7 +200,6 @@ static uint32_t gu32_par_offset[ ePAR_NUM_OF ] = { 0 }; //////////////////////////////////////////////////////////////////////////////// // Function Prototypes //////////////////////////////////////////////////////////////////////////////// -static void par_allocate_ram_space (void); #if ( 1 == PAR_CFG_ENABLE_ID ) static inline uint32_t par_hash_id (const uint16_t id); static par_status_t par_build_and_validate_id_map (const par_cfg_t * const p_par_cfg); @@ -213,74 +233,19 @@ PAR_PORT_WEAK bool par_port_is_desc_valid(const char * const p_desc) //////////////////////////////////////////////////////////////////////////////// /** -* Allocate space for live parameter values -* -* @return Pointer to allocated RAM space for parameter values + * Bind static space for live parameter values */ //////////////////////////////////////////////////////////////////////////////// -static void par_allocate_ram_space(void) +static void par_bind_storage_layout(void) { - uint32_t total_size = 0; - void * mem = NULL; - - // Group 32-bit types first - Alignment safety - uint32_t group32_size = 0, group32_count = 0; - for ( par_num_t par_it = 0; par_it < ePAR_NUM_OF; par_it++ ) - { - if ( ( ePAR_TYPE_U32 == par_get_type(par_it)) - || ( ePAR_TYPE_I32 == par_get_type(par_it)) - || ( ePAR_TYPE_F32 == par_get_type(par_it))) - { - gu32_par_offset[par_it] = group32_count; - group32_size += 4; - group32_count++; - } - } - - // Group 16-bit types second - uint32_t group16_size = 0, group16_count = 0; - for ( par_num_t par_it = 0; par_it < ePAR_NUM_OF; par_it++ ) - { - if ( ( ePAR_TYPE_U16 == par_get_type(par_it)) - || ( ePAR_TYPE_I16 == par_get_type(par_it))) - { - gu32_par_offset[par_it] = group16_count; - group16_size += 2; - group16_count++; - } - } - - // Group 8-bit types last - uint32_t group8_size = 0, group8_count = 0; - for ( par_num_t par_it = 0; par_it < ePAR_NUM_OF; par_it++ ) - { - if ( ( ePAR_TYPE_U8 == par_get_type(par_it)) - || ( ePAR_TYPE_I8 == par_get_type(par_it))) - { - gu32_par_offset[par_it] = group8_count; - group8_size += 1; - group8_count++; - } - } - - // Calculate full RAM size and allocate memory in single shot - total_size = group32_size + group16_size + group8_size; - mem = malloc(total_size); - - // 32-bit vars share the first part of the memory - gpu32_par_value = mem; - gpf32_par_value = mem; - gpi32_par_value = mem; - - // 16-bit vars share the middle part of the memory - gpu16_par_value = mem + group32_size; - gpi16_par_value = mem + group32_size; - - // 8-bit vars share the last part of the memory - gpu8_par_value = mem + group32_size + group16_size; - gpi8_par_value = mem + group32_size + group16_size; - - PAR_DBG_PRINT( "Total RAM consumption for parameters value: %d bytes", total_size ); + // Initialize and obtain parameter layout (offset map + per-width counts) + par_layout_init(); + + // Calculate full RAM size for static storage groups + PAR_DBG_PRINT("Total RAM consumption for parameters value: %u bytes", (unsigned) + (((uint32_t)par_layout_get_count().count32 * 4u) + + ((uint32_t)par_layout_get_count().count16 * 2u) + + ((uint32_t)par_layout_get_count().count8))); } //////////////////////////////////////////////////////////////////////////////// @@ -499,8 +464,8 @@ par_status_t par_init(void) // Check if par table is defined correctly status |= par_check_table_validity( par_cfg_get_table()); - // Allocate space in RAM - par_allocate_ram_space(); + // Bind storage layout + par_bind_storage_layout(); // Initialize parameter interface status |= par_if_init(); @@ -1358,21 +1323,21 @@ par_status_t par_bitand_set_u8_fast(const par_num_t par_num, const uint8_t val) if ( val > range.max.u8 ) { - PAR_ATOMIC_FETCH_AND(u8, &gpu8_par_value[gu32_par_offset[par_num]], range.max.u8); + PAR_ATOMIC_FETCH_AND(u8, &gpu8_par_value[g_par_offset[par_num]], range.max.u8); return ePAR_WAR_LIMITED; } else if ( val < range.min.u8 ) { - PAR_ATOMIC_FETCH_AND(u8, &gpu8_par_value[gu32_par_offset[par_num]], range.min.u8); + PAR_ATOMIC_FETCH_AND(u8, &gpu8_par_value[g_par_offset[par_num]], range.min.u8); return ePAR_WAR_LIMITED; } else { - PAR_ATOMIC_FETCH_AND(u8, &gpu8_par_value[gu32_par_offset[par_num]], val); + PAR_ATOMIC_FETCH_AND(u8, &gpu8_par_value[g_par_offset[par_num]], val); return ePAR_OK; } #else - PAR_ATOMIC_FETCH_AND(u8, &gpu8_par_value[gu32_par_offset[par_num]], val); + PAR_ATOMIC_FETCH_AND(u8, &gpu8_par_value[g_par_offset[par_num]], val); return ePAR_OK; #endif } @@ -1396,21 +1361,21 @@ par_status_t par_bitand_set_u16_fast(const par_num_t par_num, const uint16_t val if ( val > range.max.u16 ) { - PAR_ATOMIC_FETCH_AND(u16, &gpu16_par_value[gu32_par_offset[par_num]], range.max.u16); + PAR_ATOMIC_FETCH_AND(u16, &gpu16_par_value[g_par_offset[par_num]], range.max.u16); return ePAR_WAR_LIMITED; } else if ( val < range.min.u16 ) { - PAR_ATOMIC_FETCH_AND(u16, &gpu16_par_value[gu32_par_offset[par_num]], range.min.u16); + PAR_ATOMIC_FETCH_AND(u16, &gpu16_par_value[g_par_offset[par_num]], range.min.u16); return ePAR_WAR_LIMITED; } else { - PAR_ATOMIC_FETCH_AND(u16, &gpu16_par_value[gu32_par_offset[par_num]], val); + PAR_ATOMIC_FETCH_AND(u16, &gpu16_par_value[g_par_offset[par_num]], val); return ePAR_OK; } #else - PAR_ATOMIC_FETCH_AND(u16, &gpu16_par_value[gu32_par_offset[par_num]], val); + PAR_ATOMIC_FETCH_AND(u16, &gpu16_par_value[g_par_offset[par_num]], val); return ePAR_OK; #endif } @@ -1434,21 +1399,21 @@ par_status_t par_bitand_set_u32_fast(const par_num_t par_num, const uint32_t val if ( val > range.max.u32 ) { - PAR_ATOMIC_FETCH_AND(u32, &gpu32_par_value[gu32_par_offset[par_num]], range.max.u32); + PAR_ATOMIC_FETCH_AND(u32, &gpu32_par_value[g_par_offset[par_num]], range.max.u32); return ePAR_WAR_LIMITED; } else if ( val < range.min.u32 ) { - PAR_ATOMIC_FETCH_AND(u32, &gpu32_par_value[gu32_par_offset[par_num]], range.min.u32); + PAR_ATOMIC_FETCH_AND(u32, &gpu32_par_value[g_par_offset[par_num]], range.min.u32); return ePAR_WAR_LIMITED; } else { - PAR_ATOMIC_FETCH_AND(u32, &gpu32_par_value[gu32_par_offset[par_num]], val); + PAR_ATOMIC_FETCH_AND(u32, &gpu32_par_value[g_par_offset[par_num]], val); return ePAR_OK; } #else - PAR_ATOMIC_FETCH_AND(u32, &gpu32_par_value[gu32_par_offset[par_num]], val); + PAR_ATOMIC_FETCH_AND(u32, &gpu32_par_value[g_par_offset[par_num]], val); return ePAR_OK; #endif } @@ -1472,21 +1437,21 @@ par_status_t par_bitor_set_u8_fast(const par_num_t par_num, const uint8_t val) if ( val > range.max.u8 ) { - PAR_ATOMIC_FETCH_OR(u8, &gpu8_par_value[gu32_par_offset[par_num]], range.max.u8); + PAR_ATOMIC_FETCH_OR(u8, &gpu8_par_value[g_par_offset[par_num]], range.max.u8); return ePAR_WAR_LIMITED; } else if ( val < range.min.u8 ) { - PAR_ATOMIC_FETCH_OR(u8, &gpu8_par_value[gu32_par_offset[par_num]], range.min.u8); + PAR_ATOMIC_FETCH_OR(u8, &gpu8_par_value[g_par_offset[par_num]], range.min.u8); return ePAR_WAR_LIMITED; } else { - PAR_ATOMIC_FETCH_OR(u8, &gpu8_par_value[gu32_par_offset[par_num]], val); + PAR_ATOMIC_FETCH_OR(u8, &gpu8_par_value[g_par_offset[par_num]], val); return ePAR_OK; } #else - PAR_ATOMIC_FETCH_OR(u8, &gpu8_par_value[gu32_par_offset[par_num]], val); + PAR_ATOMIC_FETCH_OR(u8, &gpu8_par_value[g_par_offset[par_num]], val); return ePAR_OK; #endif } @@ -1510,21 +1475,21 @@ par_status_t par_bitor_set_u16_fast(const par_num_t par_num, const uint16_t val) if ( val > range.max.u16 ) { - PAR_ATOMIC_FETCH_OR(u16, &gpu16_par_value[gu32_par_offset[par_num]], range.max.u16); + PAR_ATOMIC_FETCH_OR(u16, &gpu16_par_value[g_par_offset[par_num]], range.max.u16); return ePAR_WAR_LIMITED; } else if ( val < range.min.u16 ) { - PAR_ATOMIC_FETCH_OR(u16, &gpu16_par_value[gu32_par_offset[par_num]], range.min.u16); + PAR_ATOMIC_FETCH_OR(u16, &gpu16_par_value[g_par_offset[par_num]], range.min.u16); return ePAR_WAR_LIMITED; } else { - PAR_ATOMIC_FETCH_OR(u16, &gpu16_par_value[gu32_par_offset[par_num]], val); + PAR_ATOMIC_FETCH_OR(u16, &gpu16_par_value[g_par_offset[par_num]], val); return ePAR_OK; } #else - PAR_ATOMIC_FETCH_OR(u16, &gpu16_par_value[gu32_par_offset[par_num]], val); + PAR_ATOMIC_FETCH_OR(u16, &gpu16_par_value[g_par_offset[par_num]], val); return ePAR_OK; #endif } @@ -1548,21 +1513,21 @@ par_status_t par_bitor_set_u32_fast(const par_num_t par_num, const uint32_t val) if ( val > range.max.u32 ) { - PAR_ATOMIC_FETCH_OR(u32, &gpu32_par_value[gu32_par_offset[par_num]], range.max.u32); + PAR_ATOMIC_FETCH_OR(u32, &gpu32_par_value[g_par_offset[par_num]], range.max.u32); return ePAR_WAR_LIMITED; } else if ( val < range.min.u32 ) { - PAR_ATOMIC_FETCH_OR(u32, &gpu32_par_value[gu32_par_offset[par_num]], range.min.u32); + PAR_ATOMIC_FETCH_OR(u32, &gpu32_par_value[g_par_offset[par_num]], range.min.u32); return ePAR_WAR_LIMITED; } else { - PAR_ATOMIC_FETCH_OR(u32, &gpu32_par_value[gu32_par_offset[par_num]], val); + PAR_ATOMIC_FETCH_OR(u32, &gpu32_par_value[g_par_offset[par_num]], val); return ePAR_OK; } #else - PAR_ATOMIC_FETCH_OR(u32, &gpu32_par_value[gu32_par_offset[par_num]], val); + PAR_ATOMIC_FETCH_OR(u32, &gpu32_par_value[g_par_offset[par_num]], val); return ePAR_OK; #endif } diff --git a/src/par_atomic.h b/src/par_atomic.h index 6eb0141..9b02adc 100644 --- a/src/par_atomic.h +++ b/src/par_atomic.h @@ -41,6 +41,19 @@ #define PAR_ATOMIC_BACKEND PAR_ATOMIC_BACKEND_C11 #endif +/** + * Atomic shared-storage contract for backend implementers + * + * @note Backends used with static shared storage mode must guarantee identical + * object representation for the following type groups: + * - par_atomic_u8_t and par_atomic_i8_t + * - par_atomic_u16_t and par_atomic_i16_t + * - par_atomic_u32_t, par_atomic_i32_t and par_atomic_f32_t + * + * @note If a port backend cannot satisfy this contract, static shared storage + * mode is not supported for that backend. + */ + /** * List of integral types supported by atomic load/store helpers * @@ -244,4 +257,4 @@ PAR_ATOMIC_FETCH_TYPE_LIST(PAR_ATOMIC_DEFINE_FETCH_OR) #define PAR_ATOMIC_FETCH_OR(tag, ptr, value) \ par_atomic_fetch_or_##tag((ptr), (value)) -#endif \ No newline at end of file +#endif diff --git a/src/par_cfg.h b/src/par_cfg.h index d4c8bda..fe479d0 100644 --- a/src/par_cfg.h +++ b/src/par_cfg.h @@ -27,6 +27,7 @@ //////////////////////////////////////////////////////////////////////////////// #include #include +#include #include "par_def.h" // USER CODE BEGIN... @@ -139,6 +140,23 @@ #define PAR_PORT_WEAK __attribute__((weak)) #endif +/** + * Type alignment abstraction + * + * @note Default uses C11 _Alignof. If unavailable, falls back to + * offsetof-based alignment calculation. + * @note This abstraction is intended for ordinary object types and platform + * atomic-wrapper types, but the platform must guarantee the expression + * is valid for its custom atomic wrapper definitions. + */ +#ifndef PAR_ALIGNOF + #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) + #define PAR_ALIGNOF(type) _Alignof(type) + #else + #define PAR_ALIGNOF(type) offsetof(struct { char _par_align_c; type _par_align_t; }, _par_align_t) + #endif +#endif + /** * Package compile-time assert */ @@ -240,6 +258,32 @@ #define PAR_CFG_PORT_HOOK_EN ( 0 ) #endif +/** + * Parameter storage layout source + */ +#define PAR_CFG_LAYOUT_COMPILE_SCAN ( 0u ) +#define PAR_CFG_LAYOUT_SCRIPT ( 1u ) + +/** + * Select parameter storage layout source + * + * @note + * - COMPILE_SCAN: counts are compile-time constants, offsets are scanned in init + * - SCRIPT : counts/offsets are provided by generated static layout header + */ +#ifndef PAR_CFG_LAYOUT_SOURCE + #define PAR_CFG_LAYOUT_SOURCE PAR_CFG_LAYOUT_COMPILE_SCAN +#endif + +/** + * Static layout include path + * + * @note Can be overridden by integrator to include generated layout header. + */ +#ifndef PAR_CFG_LAYOUT_STATIC_INCLUDE + #define PAR_CFG_LAYOUT_STATIC_INCLUDE "par_layout_static.h" +#endif + /** * Enable/Disable parameter range metadata (min/max) */ @@ -309,6 +353,12 @@ #error "Parameter settings invalid: NVM requires PAR_CFG_ENABLE_PERSIST = 1!" #endif +#if ( PAR_CFG_LAYOUT_SOURCE != PAR_CFG_LAYOUT_COMPILE_SCAN ) && ( PAR_CFG_LAYOUT_SOURCE != PAR_CFG_LAYOUT_SCRIPT ) + #error "Parameter settings invalid: PAR_CFG_LAYOUT_SOURCE must be PAR_CFG_LAYOUT_COMPILE_SCAN or PAR_CFG_LAYOUT_SCRIPT!" +#endif + +#define PAR_UINT16_MAX ( 65535u ) + //////////////////////////////////////////////////////////////////////////////// // Functions Prototypes //////////////////////////////////////////////////////////////////////////////// diff --git a/src/par_def.c b/src/par_def.c index 2ce8fa3..d0cec9d 100644 --- a/src/par_def.c +++ b/src/par_def.c @@ -5,8 +5,8 @@ /** *@file par_cfg.c *@brief Configuration for device parameters -*@author Ziga Miklosic -*@email ziga.miklosic@gmail.com +*@author wdfk-prog +*@email 1425075683@qq.com *@date 29.01.2026 *@version V3.0.1 */ diff --git a/src/par_def.h b/src/par_def.h index 0d2ba2d..3568fb4 100644 --- a/src/par_def.h +++ b/src/par_def.h @@ -1,8 +1,34 @@ +// Copyright (c) 2026 Ziga Miklosic +// All Rights Reserved +// This software is under MIT licence (https://opensource.org/licenses/MIT) +//////////////////////////////////////////////////////////////////////////////// +/** +*@file par_def.h +*@brief Core parameter definition interface +*@author wdfk-prog +*@email 1425075683@qq.com +*@date 29.01.2026 +*@version V3.0.1 +*/ +//////////////////////////////////////////////////////////////////////////////// +/** +*@addtogroup PAR_DEF +* @{ +*/ +//////////////////////////////////////////////////////////////////////////////// + #ifndef _PAR_DEF_CORE_H_ #define _PAR_DEF_CORE_H_ +//////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////// + #include +//////////////////////////////////////////////////////////////////////////////// +// Definitions +//////////////////////////////////////////////////////////////////////////////// typedef struct par_cfg_s par_cfg_t; #ifdef __cplusplus @@ -38,6 +64,86 @@ enum #undef PAR_ITEM_ENUM typedef uint16_t par_num_t; +/** + * Compile-time storage group counts derived from par_table.def. + * + * @note + * These constants are used by layout and static storage allocation. + */ +#define PAR_ITEM_COUNT_ONE(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) + 1u +#define PAR_ITEM_COUNT_ZERO(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) + 0u + +enum +{ + PAR_LAYOUT_COMPILE_COUNT8 = 0u + #define PAR_ITEM_U8 PAR_ITEM_COUNT_ONE + #define PAR_ITEM_U16 PAR_ITEM_COUNT_ZERO + #define PAR_ITEM_U32 PAR_ITEM_COUNT_ZERO + #define PAR_ITEM_I8 PAR_ITEM_COUNT_ONE + #define PAR_ITEM_I16 PAR_ITEM_COUNT_ZERO + #define PAR_ITEM_I32 PAR_ITEM_COUNT_ZERO + #define PAR_ITEM_F32 PAR_ITEM_COUNT_ZERO + #include "../../par_table.def" + #undef PAR_ITEM_U8 + #undef PAR_ITEM_U16 + #undef PAR_ITEM_U32 + #undef PAR_ITEM_I8 + #undef PAR_ITEM_I16 + #undef PAR_ITEM_I32 + #undef PAR_ITEM_F32 +}; + +enum +{ + PAR_LAYOUT_COMPILE_COUNT16 = 0u + #define PAR_ITEM_U8 PAR_ITEM_COUNT_ZERO + #define PAR_ITEM_U16 PAR_ITEM_COUNT_ONE + #define PAR_ITEM_U32 PAR_ITEM_COUNT_ZERO + #define PAR_ITEM_I8 PAR_ITEM_COUNT_ZERO + #define PAR_ITEM_I16 PAR_ITEM_COUNT_ONE + #define PAR_ITEM_I32 PAR_ITEM_COUNT_ZERO + #define PAR_ITEM_F32 PAR_ITEM_COUNT_ZERO + #include "../../par_table.def" + #undef PAR_ITEM_U8 + #undef PAR_ITEM_U16 + #undef PAR_ITEM_U32 + #undef PAR_ITEM_I8 + #undef PAR_ITEM_I16 + #undef PAR_ITEM_I32 + #undef PAR_ITEM_F32 +}; + +enum +{ + PAR_LAYOUT_COMPILE_COUNT32 = 0u + #define PAR_ITEM_U8 PAR_ITEM_COUNT_ZERO + #define PAR_ITEM_U16 PAR_ITEM_COUNT_ZERO + #define PAR_ITEM_U32 PAR_ITEM_COUNT_ONE + #define PAR_ITEM_I8 PAR_ITEM_COUNT_ZERO + #define PAR_ITEM_I16 PAR_ITEM_COUNT_ZERO + #define PAR_ITEM_I32 PAR_ITEM_COUNT_ONE + #define PAR_ITEM_F32 PAR_ITEM_COUNT_ONE + #include "../../par_table.def" + #undef PAR_ITEM_U8 + #undef PAR_ITEM_U16 + #undef PAR_ITEM_U32 + #undef PAR_ITEM_I8 + #undef PAR_ITEM_I16 + #undef PAR_ITEM_I32 + #undef PAR_ITEM_F32 +}; + +enum +{ + PAR_LAYOUT_COMPILE_COUNT_SUM = (PAR_LAYOUT_COMPILE_COUNT8 + PAR_LAYOUT_COMPILE_COUNT16 + PAR_LAYOUT_COMPILE_COUNT32) +}; + +#undef PAR_ITEM_COUNT_ONE +#undef PAR_ITEM_COUNT_ZERO + +//////////////////////////////////////////////////////////////////////////////// +// Functions Prototypes +//////////////////////////////////////////////////////////////////////////////// const par_cfg_t * par_cfg_get_table (void); const par_cfg_t * par_cfg_get (const par_num_t par_num); uint32_t par_cfg_get_table_size (void); @@ -46,4 +152,10 @@ uint32_t par_cfg_get_table_size (void); } #endif +//////////////////////////////////////////////////////////////////////////////// +/** +* @} +*/ +//////////////////////////////////////////////////////////////////////////////// + #endif /* _PAR_DEF_CORE_H_ */ diff --git a/src/par_layout.c b/src/par_layout.c new file mode 100644 index 0000000..7dcd1d5 --- /dev/null +++ b/src/par_layout.c @@ -0,0 +1,174 @@ +// Copyright (c) 2026 Ziga Miklosic +// All Rights Reserved +// This software is under MIT licence (https://opensource.org/licenses/MIT) +//////////////////////////////////////////////////////////////////////////////// +/** +*@file par_layout.c +*@brief Parameter storage layout abstraction implementation +*@author wdfk-prog +*@email 1425075683@qq.com +*@date 16.03.2026 +*@version V3.0.1 +*/ +//////////////////////////////////////////////////////////////////////////////// +/** +*@addtogroup PAR_LAYOUT +* @{ +*/ +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////// +#include "par_layout.h" +#include "par.h" + +//////////////////////////////////////////////////////////////////////////////// +// Definitions +//////////////////////////////////////////////////////////////////////////////// +PAR_STATIC_ASSERT(par_layout_max_par_num_fit_u16, (ePAR_NUM_OF <= PAR_UINT16_MAX)); +PAR_STATIC_ASSERT(par_layout_compile_count_sum_match, ((uint32_t)PAR_LAYOUT_COMPILE_COUNT_SUM == (uint32_t)ePAR_NUM_OF)); + +static const par_layout_count_t gs_layout_count = +{ + .count8 = (uint16_t)PAR_STORAGE_COUNT8, + .count16 = (uint16_t)PAR_STORAGE_COUNT16, + .count32 = (uint16_t)PAR_STORAGE_COUNT32, +}; + +/** + * Runtime-generated offset table. + * + * @note Offsets are generated with a plain runtime scan loop instead of macro + * expansion to keep the mapping logic readable and easy to debug. + * @note When PAR_CFG_LAYOUT_SOURCE is PAR_CFG_LAYOUT_SCRIPT, runtime fill is + * compile-time disabled and PAR_LAYOUT_STATIC_OFFSET_TABLE is consumed + * directly, avoiding extra maintenance complexity. + */ +static uint16_t gs_runtime_offset[ePAR_NUM_OF] = { 0u }; +static const uint16_t *gsp_active_offset = gs_runtime_offset; + +#if ( PAR_CFG_LAYOUT_SOURCE == PAR_CFG_LAYOUT_SCRIPT ) + #ifndef PAR_LAYOUT_STATIC_OFFSET_TABLE + #error "PAR_LAYOUT_STATIC_OFFSET_TABLE must be provided by static layout include!" + #endif +#endif +//////////////////////////////////////////////////////////////////////////////// +// Functions +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/** +* Initialize active parameter layout source +* +* @note COMPILE_SCAN mode fills runtime offset table. +* SCRIPT mode directly consumes static script table. +* +* @return void +*/ +//////////////////////////////////////////////////////////////////////////////// +void par_layout_init(void) +{ +#if ( PAR_CFG_LAYOUT_SOURCE == PAR_CFG_LAYOUT_COMPILE_SCAN ) + par_layout_count_t scan_count = { 0u, 0u, 0u }; + gsp_active_offset = gs_runtime_offset; + + for ( uint32_t par_it = 0u; par_it < (uint32_t) ePAR_NUM_OF; par_it++ ) + { + const par_cfg_t * const p_cfg = par_cfg_get((par_num_t) par_it ); + if ( NULL == p_cfg ) + { + PAR_DBG_PRINT( "ERR, PAR layout config missing at par_num=%u", (unsigned) par_it ); + PAR_ASSERT( 0 ); + return; + } + + switch ( p_cfg->type ) + { + case ePAR_TYPE_U8: + case ePAR_TYPE_I8: + gs_runtime_offset[par_it] = scan_count.count8; + scan_count.count8++; + break; + + case ePAR_TYPE_U16: + case ePAR_TYPE_I16: + gs_runtime_offset[par_it] = scan_count.count16; + scan_count.count16++; + break; + + case ePAR_TYPE_U32: + case ePAR_TYPE_I32: + case ePAR_TYPE_F32: + gs_runtime_offset[par_it] = scan_count.count32; + scan_count.count32++; + break; + + case ePAR_TYPE_NUM_OF: + default: + PAR_DBG_PRINT( "ERR, PAR layout unsupported type at par_num=%u", (unsigned) par_it ); + PAR_ASSERT( 0 ); + return; + } + } + + if ( ( scan_count.count8 != gs_layout_count.count8 ) || ( scan_count.count16 != gs_layout_count.count16 ) || ( scan_count.count32 != gs_layout_count.count32 ) ) + { + PAR_DBG_PRINT( "ERR, PAR layout count mismatch: scan=(%u,%u,%u), cfg=(%u,%u,%u)", + (unsigned) scan_count.count8, + (unsigned) scan_count.count16, + (unsigned) scan_count.count32, + (unsigned) gs_layout_count.count8, + (unsigned) gs_layout_count.count16, + (unsigned) gs_layout_count.count32 ); + PAR_ASSERT( 0 ); + return; + } +#else + /* Script layout mode: consume provided static layout directly with no runtime validation. */ + gsp_active_offset = PAR_LAYOUT_STATIC_OFFSET_TABLE; + return; +#endif +} + +//////////////////////////////////////////////////////////////////////////////// +/** +* Get active offset table pointer +* +* @return p_table - Pointer to active offset table +*/ +//////////////////////////////////////////////////////////////////////////////// +const uint16_t * par_layout_get_offset_table(void) +{ + return gsp_active_offset; +} + +//////////////////////////////////////////////////////////////////////////////// +/** +* Get offset by parameter number +* +* @param[in] par_num - Parameter number (enumeration) +* @return offset - Offset inside type group storage +*/ +//////////////////////////////////////////////////////////////////////////////// +uint16_t par_layout_get_offset(const par_num_t par_num) +{ + return gsp_active_offset[par_num]; +} + +//////////////////////////////////////////////////////////////////////////////// +/** +* Get per-width layout counts +* +* @return count - Number of 8/16/32-bit parameters +*/ +//////////////////////////////////////////////////////////////////////////////// +par_layout_count_t par_layout_get_count(void) +{ + return gs_layout_count; +} + +//////////////////////////////////////////////////////////////////////////////// +/** +* @} +*/ +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/par_layout.h b/src/par_layout.h new file mode 100644 index 0000000..7ae0e05 --- /dev/null +++ b/src/par_layout.h @@ -0,0 +1,67 @@ +// Copyright (c) 2026 Ziga Miklosic +// All Rights Reserved +// This software is under MIT licence (https://opensource.org/licenses/MIT) +//////////////////////////////////////////////////////////////////////////////// +/** +*@file par_layout.h +*@brief Parameter storage layout abstraction +*@author wdfk-prog +*@email 1425075683@qq.com +*@date 16.03.2026 +*@version V3.0.1 +*/ +//////////////////////////////////////////////////////////////////////////////// + +#ifndef _PAR_LAYOUT_H_ +#define _PAR_LAYOUT_H_ + +//////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////// +#include + +#include "par_cfg.h" +#include "par_def.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Definitions +//////////////////////////////////////////////////////////////////////////////// +typedef struct +{ + uint16_t count8; + uint16_t count16; + uint16_t count32; +} par_layout_count_t; + +#if ( PAR_CFG_LAYOUT_SOURCE == PAR_CFG_LAYOUT_COMPILE_SCAN ) + #define PAR_STORAGE_COUNT8 (PAR_LAYOUT_COMPILE_COUNT8) + #define PAR_STORAGE_COUNT16 (PAR_LAYOUT_COMPILE_COUNT16) + #define PAR_STORAGE_COUNT32 (PAR_LAYOUT_COMPILE_COUNT32) +#elif ( PAR_CFG_LAYOUT_SOURCE == PAR_CFG_LAYOUT_SCRIPT ) + #include PAR_CFG_LAYOUT_STATIC_INCLUDE + #define PAR_STORAGE_COUNT8 (PAR_LAYOUT_STATIC_COUNT8) + #define PAR_STORAGE_COUNT16 (PAR_LAYOUT_STATIC_COUNT16) + #define PAR_STORAGE_COUNT32 (PAR_LAYOUT_STATIC_COUNT32) +#else + #error "Unsupported PAR_CFG_LAYOUT_SOURCE value!" +#endif + +#define PAR_STORAGE_NONZERO(count_) (((count_) > 0u) ? (count_) : 1u) + +//////////////////////////////////////////////////////////////////////////////// +// Functions +//////////////////////////////////////////////////////////////////////////////// +const uint16_t * par_layout_get_offset_table(void); +uint16_t par_layout_get_offset(const par_num_t par_num); +par_layout_count_t par_layout_get_count(void); +void par_layout_init(void); + +#ifdef __cplusplus +} +#endif + +#endif // _PAR_LAYOUT_H_ diff --git a/template/par_if.htmp b/template/par_if.htmp deleted file mode 100644 index 635e091..0000000 --- a/template/par_if.htmp +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2026 Ziga Miklosic -// All Rights Reserved -// This software is under MIT licence (https://opensource.org/licenses/MIT) -//////////////////////////////////////////////////////////////////////////////// -/** -*@file par_if.h -*@brief Interface for device parameters -*@author Ziga Miklosic -*@email ziga.miklosic@gmail.com -*@date 29.01.2026 -*@version V3.0.1 -*/ -//////////////////////////////////////////////////////////////////////////////// -/** -*@addtogroup PAR_IF -* @{ -*/ -//////////////////////////////////////////////////////////////////////////////// - -#ifndef _PAR_IF_H_ -#define _PAR_IF_H_ - -//////////////////////////////////////////////////////////////////////////////// -// Includes -//////////////////////////////////////////////////////////////////////////////// -#include -#include "parameters/src/par.h" -#include "par_cfg.h" - -//////////////////////////////////////////////////////////////////////////////// -// Definitions -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// Functions Prototypes -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_if_init (void); -par_status_t par_if_aquire_mutex (const par_num_t par_num); -void par_if_release_mutex (const par_num_t par_num); -void par_if_calc_hash (const uint8_t * const p_data, const uint32_t size, uint8_t * const p_hash); - -#endif // _PAR_IF_H_ diff --git a/template/par_layout_static.htmp b/template/par_layout_static.htmp new file mode 100644 index 0000000..d35efc0 --- /dev/null +++ b/template/par_layout_static.htmp @@ -0,0 +1,33 @@ +#ifndef _PAR_LAYOUT_STATIC_H_ +#define _PAR_LAYOUT_STATIC_H_ + +#include + +#include "../src/par_def.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Static layout contract (template) + * + * @note Script layout mode expects generated values for: + * - PAR_LAYOUT_STATIC_COUNT8/16/32 + * - PAR_LAYOUT_STATIC_OFFSET_TABLE (points to a const offset table) + */ +#define PAR_LAYOUT_STATIC_COUNT8 ( 0u ) +#define PAR_LAYOUT_STATIC_COUNT16 ( 0u ) +#define PAR_LAYOUT_STATIC_COUNT32 ( 0u ) + +/** + * Static offset map generated by external script. + */ +extern const uint16_t g_par_layout_static_offset[ePAR_NUM_OF]; +#define PAR_LAYOUT_STATIC_OFFSET_TABLE ( g_par_layout_static_offset ) + +#ifdef __cplusplus +} +#endif + +#endif // _PAR_LAYOUT_STATIC_H_ From 8433f2635edcf3cc38858596d42595a5ac89940d Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Tue, 17 Mar 2026 16:52:55 +0800 Subject: [PATCH 09/36] docs(core): Reorganize parameter module documentation and layout model --- README.md | 1160 ++--------------- .../DeviceParameter_VerificationReport.xlsx | Bin docs/api-reference.md | 194 +++ docs/architecture.md | 224 ++++ docs/getting-started.md | 274 ++++ src/par_layout.c | 7 - 6 files changed, 783 insertions(+), 1076 deletions(-) rename {doc => docs}/DeviceParameter_VerificationReport.xlsx (100%) create mode 100644 docs/api-reference.md create mode 100644 docs/architecture.md create mode 100644 docs/getting-started.md diff --git a/README.md b/README.md index e358895..0fbdccb 100644 --- a/README.md +++ b/README.md @@ -1,1103 +1,125 @@ -[TOC] +# Device Parameters -# **Device Parameters** +`Device Parameters` is a portable embedded C module for managing runtime parameters, validation, metadata, and optional NVM persistence from a single parameter definition table. -The **Device Parameters** module manages all device parameters through a single configuration table, offering a streamlined approach to system configuration and diagnostics. This module often serves as the backbone of an embedded system, controlling the application's behavior and providing insights into device performance, making diagnostics straightforward and efficient. +It is designed for projects that need a clean way to: -For more details about storage into NVM look at the [General Embedded C Library Manual](https://github.com/GeneralEmbeddedCLibraries/documentation/blob/develop/General_Embedded_C_Library_Manual.pdf) document. +- define parameters once +- read and write them through a consistent API +- expose them to CLI, PC tools, or protocol layers by stable external IDs +- validate values before they are accepted +- persist selected values to NVM +- keep memory usage predictable on embedded targets -## Key Benefits -- **Centralized Configuration**: Simplifies management by defining all system settings in a single table. -- **Data Integrity**: Integrated Min/Max range checking and custom validation callbacks. -- **Thread Safety**: Optional mutex protection for multi-threaded environments (e.g., RTOS). -- **Persistence**: Transparent NVM (Non-Volatile Memory) integration with CRC and signature verification. -- **Observability**: Built-in support for parameter names, units, and descriptions for easy CLI/GUI integration. -- **Event-Driven**: Chained callback system for notifying other modules of parameter changes. -- **No Heap Requirement for Live Storage**: Parameter live values are stored in statically allocated typed groups, which avoids runtime heap allocation and improves predictability for embedded targets. +## What this module provides -## Integration with other modules in General Embedded C Libraries ecosystem -When combined with the following modules, the **Device Parameters** module significantly enhances the capabilities of embedded applications: +- **Single source of truth** through `par_table.def` +- **Typed APIs** for `U8`, `I8`, `U16`, `I16`, `U32`, `I32`, and `F32` +- **Optional metadata** such as name, unit, description, access, ID, and persistence flags +- **Validation pipeline** with compile-time checks for integer ranges and runtime checks for dynamic rules +- **Static live-value storage** grouped by width instead of heap allocation +- **Fast external lookup by ID** through a runtime hash map +- **Optional NVM integration** for persistent parameters +- **Portable core + platform hooks** for RTOS, mutex, logging, assertions, and atomic backends -1. **[CLI (Command Line Interface)](https://github.com/GeneralEmbeddedCLibraries/cli)** - - Enables communication between the embedded application and a PC. - - Allows real-time configuration and diagnostics via the CLI interface. +## Start here -2. **[NVM (Non-Volatile Memory)](https://github.com/GeneralEmbeddedCLibraries/nvm)** - - Ensures that configured settings are stored persistently. +### Quick start -3. **Device Parameters + CLI + NVM** - - Together, these modules allow: - - Communication with a PC using CLI. - - Configuration and diagnostics of the application via Device Parameters. - - Storage of settings to NVM, ensuring data persistence. +1. Add the core sources from `src/` to your build. +2. Provide a project-specific `par_table.def` at the package root. +3. Provide `port/par_cfg_port.h` in your include path. +4. Optionally provide `port/par_if_port.c` and `port/par_atomic_port.h` when your platform needs them. +5. Call `par_init()` before using runtime APIs. +6. Use `PAR_SET`, `PAR_GET`, or the typed `par_set_*` / `par_get_*` APIs in application code. -### Development Benefits -By leveraging this combination of modules, embedded firmware development becomes significantly faster and more efficient, enabling developers to focus on functionality rather than repetitive low-level implementation. - -## **Dependencies** - -### **1. NVM Module** -In case of using NVM module *PAR_CFG_NVM_EN = 1*, then [NVM module](https://github.com/GeneralEmbeddedCLibraries/nvm) must pe part of project. -NVM module must take following path:`"root/middleware/nvm/nvm/src/nvm.h"` - -### **2. Platform adaptation layer** - -This package separates the core parameter logic from platform-specific integration. - -Platform-dependent configuration and hooks are provided through the `port/` layer: - -* `port/par_cfg_port.h` – platform configuration bridge -* `port/par_if_port.c` – platform interface backend -* `port/par_atomic_port.h` – platform atomic backend - -This keeps the core module portable while allowing integration with RTOS, mutex, logging, assertion, and atomic services provided by the target platform. - -> Note: `parameters/src/par_cfg.h` includes `par_cfg_port.h` unconditionally. -> You must provide this header in your project include path. -> If no platform override is required, provide an empty stub `par_cfg_port.h` with include guard. - -### **3. Atomic backend configuration** - -The module requires an atomic backend for parameter value access. - -By default, it uses the C11 atomic backend. If your compiler does not provide usable C11 atomics, or if your platform already provides its own atomic API, you can switch the module to a port-specific backend through `par_atomic_port.h`. - -#### When to use `par_atomic_port.h` - -Use `par_atomic_port.h` when: - -- the compiler does not fully support `` -- the target platform already provides atomic primitives -- you want the parameter module to use the RTOS or platform-native atomic implementation - -#### How to enable `par_atomic_port.h` - -Atomic backend selection is controlled by `PAR_ATOMIC_BACKEND` in `parameters/src/par_atomic.h`. - -Available options: - -```c -#define PAR_ATOMIC_BACKEND_C11 1 -#define PAR_ATOMIC_BACKEND_PORT 2 -``` - -To use the port backend, define: - -```c -#define PAR_ATOMIC_BACKEND PAR_ATOMIC_BACKEND_PORT -``` - -After that, `par_atomic.h` will include `par_atomic_port.h` and use the port-provided atomic types and helpers. - -#### Notes - -* `par_atomic_port.h` must provide all atomic types and operations required by `par_atomic.h` -* `float32_t` should be stored and loaded by preserving its raw bit representation, not by numeric cast -* make sure the underlying atomic storage type matches the size of `float` -* keep all platform-specific atomic adaptation inside `par_atomic_port.h` so the core parameter code does not need to change - -## **Limitations** - - **Alignment:** Address offsets are calculated based on 4-byte (32-bit) alignment to satisfy most ARM Cortex-M requirements. - - **Flat ID Space:** Parameter IDs must be unique across the entire table to ensure NVM consistency. - - **Execution Time:** If many callbacks are chained to a single parameter, the par_set execution time will increase accordingly. - -## **Storage model** - -Parameter live values are stored in statically allocated width-based groups instead of using heap allocation. - -The storage is divided into three groups: - -- 8-bit group: `U8`, `I8` -- 16-bit group: `U16`, `I16` -- 32-bit group: `U32`, `I32`, `F32` - -Each parameter is assigned: - -- a storage group based on its type -- an offset inside that group - -Offsets are provided by the layout layer, which maps parameters to their position in the corresponding storage group. - -This design: - -- removes runtime heap dependency -- keeps memory usage predictable -- allows compact storage by sharing backing memory between compatible types - -## **General Embedded C Libraries Ecosystem** -In order to be part of *General Embedded C Libraries Ecosystem* this module must be placed in following path: `root/middleware/parameters/parameters/"module_space"` - -## **Package structure** - -The package is split into three layers. - -### Core layer - -Portable parameter logic is implemented under: `parameters/src/` - -This layer contains the core implementation of: - -* parameter storage and typed access -* configuration lookup -* validation and callbacks -* ID lookup -* NVM integration -* atomic abstraction -* configuration abstraction - -### Port layer - -Platform-specific integration is implemented under:`port/` - -This layer contains: - -* `par_cfg_port.h` – platform configuration bridge -* `par_if_port.c` – platform-specific low-level interface -* `par_atomic_port.h` – platform-specific atomic backend - -This separation keeps the core module portable while isolating platform and RTOS dependencies. - -### Template layer - -Reference templates are provided under: `parameters/template/` - -This layer currently contains: - -* `par_cfg_port.htmp` – port bridge header template -* `par_layout_static.htmp` – template for externally generated static layout definitions - -Template files are used as: - -* baseline references for keeping generated or manually maintained files aligned with upstream style -* starting points when creating new `par_def.h` / `par_def.c` / `par_table.def` / `par_cfg_port.h` in other projects - -Template files are not compiled directly by the runtime library. - -## `par_table.def` and X-Macro workflow - -The parameter table can be maintained through an **X-Macro single-source definition file**: `par_table.def`. - -This file acts as the **single source of truth** for all parameter definitions. - -Typical workflow: - -* define all parameters once in `par_table.def` -* automatically expand: - - * `par_num_t` enumeration - * compile-time validation checks - * `g_par_table[]` configuration table - -This avoids duplicating parameter metadata across multiple files and prevents inconsistencies between enumeration definitions and parameter table entries. - ---- - -### Why `par_table.def` is used - -Without a single-source definition file, the same parameter information would normally be repeated in multiple places: - -* enum definition -* parameter table definition -* optional validation or helper structures - -This approach is error-prone because parameters can easily be added in one location but forgotten in another. - -Using `par_table.def` solves this by keeping **all parameter metadata in one place** and reusing it through macro expansion. - ---- - -### X-Macro expansion model - -Each entry in `par_table.def` is written as a macro invocation describing one parameter. - -Example: +A minimal example: ```c -PAR_ITEM_U8( PAR_CH1_CTRL, 0, "Ch1 Control", 0U, 2U, 2U, NULL, ePAR_ACCESS_RW, false, "Channel 1 control") -``` - -The same file can then be included multiple times with different macro definitions. - -Conceptually: - -* one expansion generates the enum -* one expansion generates compile-time validation checks -* one expansion generates `g_par_table[]` - ---- - -### Example expansions +#include "par.h" -#### Enum expansion - -`par_table.def` can be expanded into: - -```c -typedef enum +static void app_init(void) { - ePAR_CH1_CTRL = 0, - ... - ePAR_NUM_OF -} par_num_t; -``` - -#### Compile-time validation expansion - -The same item can generate static assertions such as: - -```sh -min <= max -def >= min -def <= max -``` - -#### Table initialization expansion - -The same item can also generate the designated initializer inside the configuration table: - -```c -static const par_cfg_t g_par_table[ePAR_NUM_OF] = -{ - ... -}; -``` - -This X-Macro pattern allows: - -* **single parameter definition** -* **multiple generated outputs** -* **no duplicated metadata** - ---- - -### Why this improves robustness - -The X-Macro + `par_table.def` approach improves the package in several ways: - -* **consistency** – enum, validation, and table initialization are generated from one source -* **maintainability** – parameters are added or modified in only one place -* **early error detection** – configuration errors can be caught during compilation - -For embedded systems with large parameter sets, this significantly reduces configuration drift and integration errors. - ---- - -## **Validation model** - -The parameter module performs validation at two stages: - -1. **Compile-time validation** -2. **Runtime validation** - -This layered approach catches most configuration errors during compilation while still allowing dynamic checks where required. - ---- - -### Compile-time checks - -For **integer parameter types** - -```sh -U8, I8, U16, I16, U32, I32 -``` - -the module performs compile-time validation using static assertions generated from `par_table.def`. - -Typical checks include: - -* `min <= max` -* `def >= min` -* `def <= max` - -These checks are expanded through the X-Macro validation layer located in: `par_table_xmacro_check.h` - -Example generated assertion: - -```c -PAR_STATIC_ASSERT(ePAR_CH1_CTRL_def_le_max, ((def_) <= (max_))); -``` - -If the condition evaluates to false, compilation fails immediately. - -This guarantees that invalid parameter configurations cannot pass the build stage. - ---- - -#### Example compile-time error - -If the default value exceeds the allowed range: - -```c -PAR_ITEM_U8( PAR_CH1_CTRL, 0, "Ch1 Control", 0U, 2U, 2U, NULL, ePAR_ACCESS_RW, false, "Channel 1 control") -``` - -Compilation will fail with an error similar to: - -```sh -error: size of array '_static_assert_ePAR_CH1_CTRL_def_le_max' is negative -``` - -This indicates that the condition `def <= max` failed. - ---- - -### Runtime checks - -Some validation must be performed at runtime. - -Runtime validation includes: - -* floating-point (`F32`) range validation -* parameter `name` must not be `NULL` -* parameter `desc` must not be `NULL` -* parameter description validity check through `par_port_is_desc_valid()` - -By default, the weak implementation of `par_port_is_desc_valid()` rejects descriptions containing `,`. -This default policy helps keep descriptions compatible with CSV- and CLI-oriented tooling. - -Applications may override `par_port_is_desc_valid()` to enforce a stronger or different description policy. - -These checks are executed during module initialization in: `par_check_table_validity()` located in `par.c`. - -If a runtime validation fails, module initialization will fail and an error will be reported. - ---- - -#### Why `F32` validation is runtime-only - -Floating-point values are not ideal for strict compile-time validation in embedded toolchains because: - -* floating-point constant expressions are handled inconsistently across compilers -* static assertions involving floating-point comparisons are less portable -* rounding behaviour can vary between toolchains - -To maintain portability and predictable builds, `F32` validation is performed **at runtime instead of compile time**. - ---- - -### Validation summary - -| Validation | Stage | Applies to | -| --------------------------- | ------------ | -------------- | -| `min <= max` | compile-time | integer types | -| `def >= min` | compile-time | integer types | -| `def <= max` | compile-time | integer types | -| float range check | runtime | `F32` | -| `name != NULL` | runtime | all parameters | -| `desc != NULL` | runtime | all parameters | -| `desc` validity check via `par_port_is_desc_valid()` | runtime | all parameters | - -The default weak implementation rejects `,` in descriptions, but this behavior may be overridden by the application. - -## **Configuration model** - -The package uses a two-level configuration model. - -### Core configuration - -Core configuration defaults are defined in: `parameters/src/par_cfg.h` - -This file provides default values for options such as: - -* `PAR_CFG_NVM_EN` -* `PAR_CFG_NVM_REGION` -* `PAR_CFG_TABLE_ID_CHECK_EN` -* `PAR_CFG_DEBUG_EN` -* `PAR_CFG_ASSERT_EN` -* `PAR_CFG_MUTEX_EN` -* `PAR_CFG_MUTEX_TIMEOUT_MS` -* `PAR_CFG_IF_PORT_EN` -* `PAR_CFG_PORT_HOOK_EN` - -### Platform bridge - -Platform-specific overrides are provided in: `port/par_cfg_port.h` - -This file maps platform or build-system configuration symbols to `PAR_CFG_*` options. - -`par_cfg_port.h` is mandatory for build because it is directly included by `parameters/src/par_cfg.h`. -If you do not need overrides, keep a minimal empty file: - -```c -#ifndef _PAR_CFG_PORT_H_ -#define _PAR_CFG_PORT_H_ -/* Optional platform overrides */ -#endif -``` - -### Layout-related configuration - -The storage layout can be configured independently from the parameter table logic. - -This allows the module to support: - -* source-driven layout generation -* externally generated layout metadata -* platform-specific alignment handling when needed - -### Why this split exists - -This design keeps the core implementation independent of any single build system or RTOS, while still allowing package-level integration through a dedicated platform bridge. - -## **Storage layout** - -The module uses a dedicated layout layer to map each parameter to an offset inside a typed storage group. - -Live values are stored in three width-based groups: - -* 8-bit group: `U8`, `I8` -* 16-bit group: `U16`, `I16` -* 32-bit group: `U32`, `I32`, `F32` - -Each parameter is assigned: - -* a storage group based on its type -* an offset inside that group - -The active offset map is provided by the layout subsystem through `par_layout`. - -### Layout source options - -The storage layout source is controlled by `PAR_CFG_LAYOUT_SOURCE`. - -Available options: - -```c -#define PAR_CFG_LAYOUT_COMPILE_SCAN ( 0u ) -#define PAR_CFG_LAYOUT_SCRIPT ( 1u ) -``` - -#### `PAR_CFG_LAYOUT_COMPILE_SCAN` - -* storage counts are derived from `par_table.def` -* offsets are generated during initialization by scanning the parameter table - -Use this mode when: - -* you want a self-contained integration -* parameter definitions are maintained directly in source -* no external code generation step is used - -#### `PAR_CFG_LAYOUT_SCRIPT` - -* storage counts and offset table are provided by a generated static layout header -* the layout layer consumes the generated data directly - -Use this mode when: - -* parameter tables are generated by tooling -* you want layout data to be fixed before compilation -* you want the build system to own offset generation - -### Static layout include - -The generated static layout header path is selected through: - -```c -#define PAR_CFG_LAYOUT_STATIC_INCLUDE "par_layout_static.h" -``` - -This can be overridden by the integrator if the generated file lives elsewhere. - ---- - -## **Port hooks** - -When `PAR_CFG_PORT_HOOK_EN = 1`, the module uses platform-provided hooks for: - -* logging -* assertions -* compile-time assertions - -The following hooks may be provided by the port layer: - -* `PAR_PORT_LOG(...)` -* `PAR_PORT_ASSERT(x)` -* `PAR_PORT_STATIC_ASSERT(name, expn)` - -This allows the module to integrate with the native debug and assert infrastructure of the target platform. - ---- - -## **Interface backend** - -The low-level interface layer is implemented in `parameters/src/par_if.c`. - -When `PAR_CFG_IF_PORT_EN = 1`, the module uses the platform-specific backend provided by: `port/par_if_port.c` - -This backend is responsible for platform-dependent services such as: - -* initialization -* mutex handling -* optional table hash calculation - -This keeps the public parameter logic independent from the RTOS or platform implementation. - - -## **Parameter identification** - -Each parameter defined in the configuration table contains two identifiers: - -- **par_num** – internal parameter index (enumeration) -- **id** – external parameter identifier - -These identifiers serve different purposes and are intentionally separated. - -### Internal identifier (`par_num`) - -`par_num` is the enumeration defined in `par_cfg.h` and represents the **internal index of a parameter**. - -It is primarily used inside firmware code and by the parameter module APIs. - -Example: - -```c -par_set(ePAR_TEST_U8, &value); -``` - -Characteristics: - -* used as an **index into the parameter configuration table** -* provides **fast and type-safe access** inside firmware -* may change if parameters are reordered or new parameters are added - -Because of this, `par_num` should generally be used **only inside firmware code**. - -### External identifier (`id`) + if (par_init() != ePAR_OK) + { + /* Handle initialization error */ + } -Each parameter also defines a unique **ID** in the configuration table: + PAR_SET(ePAR_CH1_REF_VAL, (float32_t)25.0f); -```c -[ePAR_TEST_U8] = { - .id = 0, - ... + float32_t ref_val = 0.0f; + PAR_GET(ePAR_CH1_REF_VAL, ref_val); } ``` -The **ID is intended for external access** to parameters. - -Typical use cases include: - -* CLI commands -* PC configuration tools -* communication protocols (UART / CAN / etc.) -* parameter import/export -* diagnostics or logging - -To support these use cases, the module provides APIs that operate using parameter IDs: - -* `par_set_by_id()` -* `par_get_by_id()` -* `par_save_by_id()` - -Internally, these functions resolve the ID to the corresponding `par_num` before accessing the parameter. - -### Design rationale - -Separating internal and external identifiers provides several advantages: - -* **Efficient internal access** through `par_num` -* **Stable external interface** through `id` -* the ability to **reorder or extend parameters without breaking external tools** - -External systems should always reference parameters by **ID**, while firmware code should typically use **par_num**. - -### ID allocation guidelines - -Parameter IDs must be **unique across the entire parameter table**. - -IDs do not need to be sequential, but it is recommended to group them by subsystem for clarity. - -Example allocation: - -| ID Range | Subsystem | -| -------- | ----------------- | -| 0–99 | Channel 1 | -| 100–199 | Channel 2 | -| 200–299 | Channel 3 | -| 300–399 | Channel 4 | -| 10000+ | System parameters | - -This approach simplifies integration with external tools and communication protocols. - -## ID lookup using hash map - -To improve parameter lookup by **ID**, the module builds a runtime hash map during `par_init()`. - -This hash map is used by APIs such as: - -- `par_get_num_by_id()` -- `par_set_by_id()` -- `par_get_by_id()` -- `par_save_by_id()` - -Instead of scanning the full parameter table for every ID lookup, the module hashes the parameter ID and directly maps it to the corresponding `par_num`. - -### Why a hash map is used - -The parameter module supports two access paths: +## Documentation map -- **`par_num`** for internal firmware access -- **`id`** for external access such as CLI, PC tools, and communication protocols +- [Getting started](docs/getting-started.md) for integration steps, required files, and first-use examples +- [Architecture](docs/architecture.md) for storage model, validation flow, ID lookup, and layout design +- [API reference](docs/api-reference.md) for the public API grouped by responsibility -Internal access by `par_num` is naturally efficient because it uses the parameter enumeration as a direct table index. +## Package layout -External access by **ID** is different. Since IDs are user-defined and do not need to be sequential, converting an ID back to `par_num` would otherwise require a linear search through the full parameter table. - -A hash map avoids that cost and provides near constant-time lookup for ID-based APIs. - -### How it works - -During `par_init()`, the module: - -1. walks through the parameter configuration table -2. hashes each parameter ID into a bucket index -3. stores the mapping: `ID -> par_num` - -Later, when an external API uses an ID, the module: - -1. hashes the requested ID -2. checks the corresponding bucket -3. returns the mapped `par_num` -4. performs the actual parameter operation internally - -Conceptually: - -```sh -External ID - | - v - hash(id) - | - v - hash bucket - | - v - par_num - | - v - internal parameter API +```text +parameters/ +├── README.md +├── CHANGE_LOG.md +├── doc/ +│ └── DeviceParameter_VerificationReport.xlsx +├── docs/ +│ ├── api-reference.md +│ ├── architecture.md +│ └── getting-started.md +├── src/ +│ ├── par.c +│ ├── par.h +│ ├── par_atomic.h +│ ├── par_cfg.h +│ ├── par_def.c +│ ├── par_def.h +│ ├── par_if.c +│ ├── par_if.h +│ ├── par_layout.c +│ ├── par_layout.h +│ ├── par_nvm.c +│ └── par_nvm.h +└── template/ + ├── par_cfg_port.htmp + ├── par_layout_static.htmp + └── par_table.deftmp ``` -### Why this is better than linear search - -Compared to a linear scan of the parameter table, the hash map provides: - -* lower lookup latency -* predictable runtime -* better scalability as the number of parameters grows -* lower overhead for frequently used ID-based APIs - -This is especially useful when parameters are accessed repeatedly from: +## Required integration files -* CLI commands -* host tools -* diagnostic services -* communication stacks +This repository contains the reusable module core and templates. A real integration still needs project-owned files. -### Why this is preferred over binary search +### Required -Binary search would require the parameter table to be sorted by ID or an additional sorted lookup table. +- `par_table.def` at the package root +- `port/par_cfg_port.h` -That introduces extra maintenance constraints and reduces flexibility in parameter definition order. - -The hash-based approach keeps the configuration table simple and preserves fast ID lookup without requiring sorted IDs. - -### Collision policy - -This implementation uses a strict one-entry-per-bucket hash map. - -That means: - -* duplicate IDs are rejected -* hash collisions are also rejected during initialization - -If two different IDs map to the same hash bucket, initialization fails and a debug message is printed. - -This design keeps runtime lookup logic simple, fast, and deterministic. - -### What to do if a hash collision is reported - -If initialization prints a message such as: - -```sh -ERR, Hash collision: ID X conflicts with ID Y at bucket Z! -ERR, Please regenerate IDs or adjust hash parameters. -``` +### Optional, depending on configuration -then two different parameter IDs were mapped to the same hash bucket. +- `port/par_if_port.c` when `PAR_CFG_IF_PORT_EN = 1` +- `port/par_atomic_port.h` when `PAR_ATOMIC_BACKEND = PAR_ATOMIC_BACKEND_PORT` +- generated static layout header when `PAR_CFG_LAYOUT_SOURCE = PAR_CFG_LAYOUT_SCRIPT` +- the external NVM module when `PAR_CFG_NVM_EN = 1` -Recommended actions: +## When to read which document -1. change one or more parameter IDs so they no longer collide -2. keep subsystem-based ID allocation, but avoid problematic values +- Read [Getting started](docs/getting-started.md) when you are integrating the module into a project. +- Read [Architecture](docs/architecture.md) when you need to understand how `par_table.def`, storage groups, layout, validation, or ID lookup work internally. +- Read [API reference](docs/api-reference.md) when you already understand the model and only need function-level guidance. -In practice, the preferred solution is to **regenerate or reassign the conflicting IDs**. +## Key integration notes -### Recommended usage +- `par_cfg.h` includes `par_cfg_port.h` unconditionally, so your build must provide that header. +- The module separates **internal parameter enumeration** (`par_num_t`) from **external parameter IDs** (`id`). +- Fast setter APIs skip part of the safety and observability path, so they should be reserved for tightly controlled hot paths. +- NVM support is optional, but when enabled it depends on the external NVM module and on ID and persistence metadata being enabled. -When defining parameter IDs manually: +## Related projects -* keep IDs unique across the entire table -* keep IDs stable across firmware versions -* group IDs by subsystem when possible -* avoid changing IDs unless external compatibility is intentionally broken - -### Notes for generated parameter tables - -In future workflows, parameter definitions and IDs can be generated by script tools. - -When IDs are generated automatically, collisions can be checked during generation, which means: - -* duplicate IDs can be prevented before build time -* hash collisions can be avoided before firmware is compiled -* the runtime hash map remains simple and fast -* manual ID maintenance is reduced - -With script-generated parameter tables, hash collision problems should normally not occur. - -### Design tradeoff - -This implementation intentionally favors: - -* **fast lookup** -* **simple runtime logic** -* **deterministic behavior** - -over: - -* runtime collision resolution -* more complex lookup structures - -Compared to alternatives such as linear search, linear probing, or maintaining a sorted structure for binary search, this approach gives better lookup performance for normal operation. - -The main tradeoff is that a rare hash collision must be resolved by reassigning IDs or regenerating the parameter table. For this module, that tradeoff is considered better than paying additional runtime cost or complexity on every ID lookup. - - - ## **API** -| API Functions | Description | Prototype | -| --- | ----------- | ----- | -| **par_init** | Initialization of parameters module | par_status_t par_init(void) |**** -| **par_deinit** | De-initialization of parameters module | par_status_t par_deinit(void) |**** -| **par_is_init** | Get initialization flag | bool par_is_init(void) | - - ## **Setting parameter value API** -| API Functions | Description | Prototype | -| --- | ----------- | ----- | -| **par_set** | Set parameter value | par_status_t par_set(const par_num_t par_num, const void *p_val) | -| **par_set_by_id** | Set parameter value by ID | par_status_t par_set_by_id(const uint16_t id, const void * p_val) | -| **par_set_u8** | Set u8 parameter value | par_status_t par_set_u8(const par_num_t par_num, const uint8_t val) | -| **par_set_i8** | Set i8 parameter value | par_status_t par_set_i8(const par_num_t par_num, const int8_t val) | -| **par_set_u16** | Set u16 parameter value | par_status_t par_set_u16(const par_num_t par_num, const uint16_t val) | -| **par_set_i16** | Set i16 parameter value | par_status_t par_set_i16(const par_num_t par_num, const int16_t val) | -| **par_set_u32** | Set u32 parameter value | par_status_t par_set_u32(const par_num_t par_num, const uint32_t val) | -| **par_set_i32** | Set i32 parameter value | par_status_t par_set_i32(const par_num_t par_num, const int32_t val) | -| **par_set_f32** | Set f32 parameter value | par_status_t par_set_f32(const par_num_t par_num, const float32_t val) | -| **par_set_u8_fast** | Set u8 parameter value fast | par_status_t par_set_u8_fast(const par_num_t par_num, const uint8_t val) | -| **par_set_i8_fast** | Set i8 parameter value fast | par_status_t par_set_i8_fast(const par_num_t par_num, const int8_t val) | -| **par_set_u16_fast** | Set u16 parameter value fast | par_status_t par_set_u16_fast(const par_num_t par_num, const uint16_t val) | -| **par_set_i16_fast** | Set i16 parameter value fast | par_status_t par_set_i16_fast(const par_num_t par_num, const int16_t val) | -| **par_set_u32_fast** | Set u32 parameter value fast | par_status_t par_set_u32_fast(const par_num_t par_num, const uint32_t val) | -| **par_set_i32_fast** | Set i32 parameter value fast | par_status_t par_set_i32_fast(const par_num_t par_num, const int32_t val) | -| **par_set_f32_fast** | Set f32 parameter value fast | par_status_t par_set_f32_fast(const par_num_t par_num, const float32_t val) | -| **par_set_to_default** | Set parameter to default value | par_status_t par_set_to_default (const par_num_t par_num) | -| **par_set_all_to_default** | Set all parameters to default value | par_status_t par_set_all_to_default (void) | -| **PAR_SET** | Set generic parameters value | #define PAR_SET(par_num, value) | - - ## **Getting parameter value API** -| API Functions | Description | Prototype | -| --- | ----------- | ----- | -| **par_get** | Get parameter value | par_status_t par_get (const par_num_t par_num, void *const p_val)| -| **par_get_id** | Get parameter ID number | par_status_t par_get_id(const par_num_t par_num, uint16_t *const p_id) | -| **par_get_u8** | Get u8 parameter value | uint8_t par_get_u8(const par_num_t par_num) | -| **par_get_i8** | Get i8 parameter value | int8_t par_get_i8(const par_num_t par_num) | -| **par_get_u16** | Get u16 parameter value | uint16_t par_get_u16(const par_num_t par_num) | -| **par_get_i16** | Get i16 parameter value | uint16_t par_get_i16(const par_num_t par_num) | -| **par_get_u32** | Get u32 parameter value | uint32_t par_get_u32(const par_num_t par_num) | -| **par_get_i32** | Get i32 parameter value | uint32_t par_get_i32(const par_num_t par_num) | -| **par_get_f32** | Get f32 parameter value | float32_t par_get_f32(const par_num_t par_num) | -| **par_get_default** | Get default parameter value | par_status_t par_get_default(const par_num_t par_num, void * const p_val) | -| **par_is_changed** | Is parameter value changed from default | bool par_is_changed(const par_num_t par_num) | -| **PAR_GET** | Get generic parameters value | #define PAR_GET(par_num, dest) | - - ## **Parameter configurations API** -| API Functions | Description | Prototype | -| --- | ----------- | ----- | -| **par_get_config** | Get parameter configurations | const par_cfg_t * par_get_config(const par_num_t par_num) | -| **par_get_name** | Get parameter name | const char * par_get_name(const par_num_t par_num) | -| **par_get_range** | Get parameter range | par_range_t par_get_range(const par_num_t par_num) | -| **par_get_unit** | Get parameter unit | const char * par_get_unit(const par_num_t par_num) | -| **par_get_desc** | Get parameter describtion | const char * par_get_desc(const par_num_t par_num) | -| **par_get_type** | Get parameter data type | par_type_list_t par_get_type(const par_num_t par_num) | -| **par_get_access** | Get parameter access | par_access_t par_get_access(const par_num_t par_num) | -| **par_is_persistant** | Get parameter persistance | bool par_is_persistant(const par_num_t par_num) | -| **par_get_num_by_id** | Get parameter number (enumeration) by its ID | par_status_t par_get_num_by_id(const uint16_t id, par_num_t *const p_par_num) | -| **par_get_id_by_num** | Get parameter ID by number (enumeration) | par_status_t par_get_id_by_num(const par_num_t par_num, uint16_t * const p_id) | - - ## **Parameter NVM storage API** -| API Functions | Description | Prototype | -| --- | ----------- | ----- | -| **par_set_n_save** | Set and store parameter to NVM | par_status_t par_set_n_save(const par_num_t par_num, const void * p_val) | -| **par_save_all** | Store all parameters to NVM | par_status_t par_save_all(void) | -| **par_save** | Store single parameter | par_status_t par_save(const par_num_t par_num) | -| **par_save_by_id** | Store single parameter by ID | par_status_t par_save_by_id(const uint16_t par_id) | -| **par_save_clean** | Re-Write complete NVM memory | par_status_t par_save_clean(void) | - - ## **Parameter registrations API** -| API Functions | Description | Prototype | -| --- | ----------- | ----- | -| **par_register_on_change_cb** | Register on value change callback | par_status_t par_register_on_change_cb(const par_on_change_cb_t * const cb) | -| **par_register_validation** | Register value validation callback | par_status_t par_register_validation(const par_validation_t * const validation) | - -## **Usage** - -### 1. Define parameters in `par_table.def` - -All parameters should be defined in the single-source definition file:`par_table.def` - -Example: - -```c -/* - * Parameter single-source list for X-Macro expansion. - * - * This file is intentionally included multiple times. - * Do not add include guards and do not place executable code here. - * - * Supported item macros: - * PAR_ITEM_U8 (enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) - * PAR_ITEM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) - * PAR_ITEM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) - * PAR_ITEM_I8 (enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) - * PAR_ITEM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) - * PAR_ITEM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) - * PAR_ITEM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) - * - * Field description: - * enum_ - Parameter enum index (used as designated initializer index) - * id_ - Unique external parameter ID - * name_ - Parameter display name - * min_ - Minimum allowed value - * max_ - Maximum allowed value - * def_ - Default value, must be within [min_, max_] - * unit_ - Engineering unit string, or NULL if not applicable - * access_ - External access type: ePAR_ACCESS_RO / ePAR_ACCESS_RW - * pers_ - Persistence flag: true if stored to NVM, otherwise false - * desc_ - Human-readable description - * - * Formatting note: - * Keep items grouped by subsystem/channel and aligned for readability. - */ - - -/* ============================================================================================================================================================= */ -/* enum_ id_ name_ min_ max_ def_ unit_ access_ pers_ desc_ */ -/* ============================================================================================================================================================= */ - - -/* ============================================================================================================================= */ -/* CHANNEL 1 */ -/* ============================================================================================================================= */ - -/* Channel 1 control */ -PAR_ITEM_U8 (ePAR_CH1_CTRL, 0, "Ch1 Control", 0U, 2U, 2U, NULL, ePAR_ACCESS_RW, false, "Channel 1 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!") -PAR_ITEM_U8 (ePAR_CH1_AFE_MEAS_EN, 1, "Ch1 AFE Measurement Control", 0U, 1U, 1U, NULL, ePAR_ACCESS_RW, true, "Channel 1 control AFE measurement usage for calculations: 0-Disable | 1-Enable") -PAR_ITEM_U8 (ePAR_CH1_TEST_MODE_EN, 2, "Ch1 Test Mode", 0U, 1U, 0U, NULL, ePAR_ACCESS_RW, false, "Channel 1 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!") -PAR_ITEM_U8 (ePAR_CH1_REF_SEL, 3, "Ch1 Reference Selection", 0U, 4U, 0U, NULL, ePAR_ACCESS_RW, true, "Channel 1 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset") -PAR_ITEM_F32(ePAR_CH1_REF_VAL, 4, "Ch1 Reference Value", -1E6f, 1E6f, 0.0f, NULL, ePAR_ACCESS_RW, true, "Channel 1 reference value based on control variable set") -``` - -This definition will be expanded through the X-Macro system to generate: - -* `par_num_t` enumeration -* compile-time validation checks -* `g_par_table[]` configuration table - -### 2. Configure the module - -The main package configuration is defined in: `parameters/src/par_cfg.h` - -Platform-specific overrides and hooks are provided in: `port/par_cfg_port.h` - -| Configuration | Description | -|---|---| -| **PAR_CFG_NVM_EN** | Enable or disable usage of NVM for persistent parameters. | -| **PAR_CFG_NVM_REGION** | Select NVM region used to store persistent parameter values. | -| **PAR_CFG_DEBUG_EN** | Enable debug features such as diagnostic logging. | -| **PAR_CFG_ASSERT_EN** | Enable runtime assertions. Should be disabled in release builds. | -| **PAR_DBG_PRINT** | Debug print macro implementation. | -| **PAR_ASSERT** | Assertion macro implementation. | -| **PAR_ATOMIC_BACKEND** | Select atomic operation backend implementation (e.g. C11, RTOS, platform-specific). | -| **PAR_CFG_TABLE_ID_CHECK_EN** | Enable compile-time or initialization-time checks for duplicate parameter IDs. | -| **PAR_CFG_MUTEX_EN** | Enable mutex protection for thread-safe parameter access. | -| **PAR_CFG_MUTEX_TIMEOUT_MS** | Mutex acquisition timeout in milliseconds. | -| **PAR_CFG_IF_PORT_EN** | Enable platform-specific backend implemented in `port/par_if_port.c`. | -| **PAR_CFG_PORT_HOOK_EN** | Enable platform log/assert hook redirection. | -| **PAR_CFG_ENABLE_RANGE** | Enable parameter range metadata (`min` / `max`) and range validation. | -| **PAR_CFG_ENABLE_NAME** | Enable parameter name metadata and `par_get_name()` API. | -| **PAR_CFG_ENABLE_UNIT** | Enable parameter unit metadata and `par_get_unit()` API. | -| **PAR_CFG_ENABLE_DESC** | Enable parameter description metadata and `par_get_desc()` API. | -| **PAR_CFG_ENABLE_DESC_CHECK** | Enable description validity checks. The default policy forbids `,` in descriptions, and the application may override the validation hook. | -| **PAR_CFG_ENABLE_ID** | Enable parameter ID metadata and ID-based APIs (`par_get_by_id`, `par_set_by_id`). | -| **PAR_CFG_ENABLE_ACCESS** | Enable access control metadata (`RO/RW`) and `par_get_access()` API. | -| **PAR_CFG_ENABLE_PERSIST** | Enable parameter persistence metadata and persistent parameter support. | -| **PAR_CFG_LAYOUT_SOURCE** | Select the storage layout source: compile-time scan or generated static layout. | -| **PAR_CFG_LAYOUT_STATIC_INCLUDE** | Include path for the generated static layout header when script layout mode is used. | -| **PAR_ALIGNOF** | Alignment abstraction used by compile-time layout compatibility checks. May be overridden by the platform if needed. | - -### **3. Build integration** - -The package build script collects source files from: - -* package root -* `parameters/src/` - -and adds the following include paths: - -* package root -* `parameters/src/` -* `port/` - -If you add or replace port-specific files, keep them under `port/` so they remain visible to the build system. - -### **4. Call **par_init()** function** - -```C -// Init parameters -if ( ePAR_OK != par_init()) -{ - PROJECT_CONFIG_ASSERT( 0 ); -} -``` -**NOTICE: NVM module will be initialized as a part of Device Parameters initialization routine in case of usage (*PAR_CFG_NVM_EN = 1*)!** - -### **5. Setting/Getting parameter value** - -```C -// Set battery voltage & sytem current -// NOTICE: When using "par_set" don't forget to always CAST to appropriate data type! -(void) par_set( ePAR_BAT_VOLTAGE, (float32_t*) &g_pwr_data.bat.voltage_filt ); -(void) par_set( ePAR_SYS_CURRENT, (float32_t*) &g_pwr_data.inp.sys_cur ); - -// Or equivalent to "par_set" -PAR_SET( ePAR_BAT_VOLTAGE, g_pwr_data.bat.voltage_filt ); -PAR_SET( ePAR_SYS_CURRENT, g_pwr_data.inp.sys_cur ); - -// Set and save parameter -if ( ePAR_OK != par_set_n_save( ePAR_P1_10, (uint32_t) &p1_10_val )) -{ - // Operation error... - // Further actions here... -} - -// Get battery voltage & sytem current -// NOTICE: When using "par_get" don't forget to always CAST to appropriate data type! -(void) par_get( ePAR_BAT_VOLTAGE, (float32_t*) &g_pwr_data.bat.voltage_filt ); -(void) par_get( ePAR_SYS_CURRENT, (float32_t*) &g_pwr_data.inp.sys_cur ); - -// Or equivalent to "par_get" -PAR_GET( ePAR_BAT_VOLTAGE, g_pwr_data.bat.voltage_filt ); -PAR_GET( ePAR_SYS_CURRENT, g_pwr_data.inp.sys_cur ); -``` - -Setting direct value to parameter: - -```C -par_set( ePAR_BAT_VOLTAGE, (float32_t*) &(float32_t){ 1.1234f} ); - -// Or equivalent using "PAR_SET" -// NOTE: When putting direct numbers using "PAR_SET" always cast to appropriate data type! -PAR_SET( ePAR_BAT_VOLTAGE, (float32_t) 1.1234f ); -``` - -### **6.Normal and fast parameter setting API** -When choosing an API for setting parameter values, you must decide between the safe (normal) API and the fast API (with suffix *_fast*). The choice depends on whether your priority is data integrity and system observability or raw execution speed. - -```C -// --- NORMAL API --- -// Use this for 90% of your code. It prevents errors and triggers callbacks. -par_set_f32( ePAR_TARGET_TEMP, 25.5f ); - -// --- FAST API --- -// Use this in high frequency control loops where every microsecond counts. -// WARNING: No safety checks are performed! -par_set_f32_fast( ePAR_MOTOR_PWM, 0.85f ); -``` - -### **7. Store to NVM** - -```C -// Store all paramters to NVM -if ( ePAR_OK != par_save_all()) -{ - // Storing to NVM error... - // Further actions here... -} -``` - -### **8. On-change callback usage** - -```C -//////////////////////////////////////////////////////////////////////////////// -/** -* Parameter value change callback -* -* @param[out] par_num - Parameter number -* @param[out] new_val - Parameter new value -* @param[out] new_val - Parameter old value -* @return void -*/ -//////////////////////////////////////////////////////////////////////////////// -void par_on_change_cb1(const par_num_t par_num, const par_type_t new_val, const par_type_t old_val) -{ - cli_printf("Parameter %d change from %d to %d", par_num, old_val.u8, new_val.u8 ); -} - -// Define parameter on change callback for "ePAR_CH1_TEST_MODE_EN" parameter -PAR_DEFINE_ON_CHANGE_CB( test_par_cb, ePAR_CH1_TEST_MODE_EN, par_on_change_cb1); - -// Register at init phase -@init -{ - if ( ePAR_OK != par_register_on_change_cb( &test_par_cb )) - { - // Operation error... - // Further actions here... - } -} -``` - -### **9. Parameter value validation usage** - -```C -//////////////////////////////////////////////////////////////////////////////// -/** -* Validate parameter value -* -* @param[in] par_num - Parameter number -* @param[in] val - Parameter new value -* @return true when value valid -*/ -//////////////////////////////////////////////////////////////////////////////// -static bool validate_par_value(const par_num_t par_num, const par_type_t val) -{ - UNUSED(par_num); - - // Prevent parameter to take exact -10 value - if ( val.i8 == -10 ) return false; - else return true; -} - -// Define parameter validation for "ePAR_CH1_REF_VAL" parameter -PAR_DEFINE_VALIDATION( par_validate, ePAR_CH1_REF_VAL, validate_par_value); - -// Register at init phase -@init -{ - if ( ePAR_OK != par_register_validation( &par_validate )) - { - // Operation error... - // Further actions here... - } -} -``` +- [CLI module](https://github.com/GeneralEmbeddedCLibraries/cli) +- [NVM module](https://github.com/GeneralEmbeddedCLibraries/nvm) +- [General Embedded C Library Manual](https://github.com/GeneralEmbeddedCLibraries/documentation/blob/develop/General_Embedded_C_Library_Manual.pdf) diff --git a/doc/DeviceParameter_VerificationReport.xlsx b/docs/DeviceParameter_VerificationReport.xlsx similarity index 100% rename from doc/DeviceParameter_VerificationReport.xlsx rename to docs/DeviceParameter_VerificationReport.xlsx diff --git a/docs/api-reference.md b/docs/api-reference.md new file mode 100644 index 0000000..0ce3232 --- /dev/null +++ b/docs/api-reference.md @@ -0,0 +1,194 @@ +# API reference + +This document groups the public API from `src/par.h` by responsibility. + +## Conventions + +- Most runtime APIs require `par_init()` to be called first. +- Some APIs are compiled only when the matching configuration option is enabled. +- `par_num_t` is the internal parameter index. +- ID-based APIs depend on `PAR_CFG_ENABLE_ID = 1`. +- NVM APIs depend on `PAR_CFG_NVM_EN = 1`. + +## Lifecycle + +| Function | Description | +| --- | --- | +| `par_init()` | Initialize the module, build runtime state, validate the table, and prepare storage access. | +| `par_deinit()` | Deinitialize the module. | +| `par_is_init()` | Return whether the module is initialized. | + +## Mutex helpers + +| Function | Description | +| --- | --- | +| `par_acquire_mutex(par_num)` | Acquire the parameter lock for a specific parameter path. | +| `par_release_mutex(par_num)` | Release the parameter lock. | + +These are relevant only when mutex support is enabled in the integration. + +## Generic setters + +| Function | Description | +| --- | --- | +| `par_set(par_num, p_val)` | Set a parameter from a typed pointer. | +| `par_set_by_id(id, p_val)` | Set a parameter using its external ID. | +| `PAR_SET(par_num, value)` | Use C11 `_Generic` to route a typed value to the matching setter. | + +## Typed setters + +| Function | Description | +| --- | --- | +| `par_set_u8()` | Set a `U8` parameter. | +| `par_set_i8()` | Set an `I8` parameter. | +| `par_set_u16()` | Set a `U16` parameter. | +| `par_set_i16()` | Set an `I16` parameter. | +| `par_set_u32()` | Set a `U32` parameter. | +| `par_set_i32()` | Set an `I32` parameter. | +| `par_set_f32()` | Set an `F32` parameter. | + +## Fast setters + +| Function | Description | +| --- | --- | +| `par_set_u8_fast()` | Fast set for `U8`. | +| `par_set_i8_fast()` | Fast set for `I8`. | +| `par_set_u16_fast()` | Fast set for `U16`. | +| `par_set_i16_fast()` | Fast set for `I16`. | +| `par_set_u32_fast()` | Fast set for `U32`. | +| `par_set_i32_fast()` | Fast set for `I32`. | +| `par_set_f32_fast()` | Fast set for `F32`. | + +Use these only in controlled hot paths. + +## Fast bitwise update helpers + +| Function | Description | +| --- | --- | +| `par_bitand_set_u8_fast()` | Fast bitwise AND update for `U8`. | +| `par_bitand_set_u16_fast()` | Fast bitwise AND update for `U16`. | +| `par_bitand_set_u32_fast()` | Fast bitwise AND update for `U32`. | +| `par_bitor_set_u8_fast()` | Fast bitwise OR update for `U8`. | +| `par_bitor_set_u16_fast()` | Fast bitwise OR update for `U16`. | +| `par_bitor_set_u32_fast()` | Fast bitwise OR update for `U32`. | + +## Reset and change tracking + +| Function | Description | +| --- | --- | +| `par_set_to_default(par_num)` | Reset one parameter to its default value. | +| `par_set_all_to_default()` | Reset all parameters to their default values. | +| `par_has_changed(par_num, p_has_changed)` | Report whether the value differs from its default. | +| `par_is_changed(par_num)` | Return whether the value differs from its default. | + +## Generic getters + +| Function | Description | +| --- | --- | +| `par_get(par_num, p_val)` | Read a parameter into a typed destination pointer. | +| `par_get_by_id(id, p_val)` | Read a parameter using its external ID. | +| `PAR_GET(par_num, dest)` | Use C11 `_Generic` to route to the matching typed getter. | + +## Typed getters + +| Function | Description | +| --- | --- | +| `par_get_u8()` | Read a `U8` parameter. | +| `par_get_i8()` | Read an `I8` parameter. | +| `par_get_u16()` | Read a `U16` parameter. | +| `par_get_i16()` | Read an `I16` parameter. | +| `par_get_u32()` | Read a `U32` parameter. | +| `par_get_i32()` | Read an `I32` parameter. | +| `par_get_f32()` | Read an `F32` parameter. | +| `par_get_default(par_num, p_val)` | Read the configured default value for a parameter. | + +## Metadata access + +These APIs do not follow the same runtime usage pattern as the value access APIs. They expose parameter metadata from the configuration table. + +| Function | Description | +| --- | --- | +| `par_get_config(par_num)` | Return the full configuration object for one parameter. | +| `par_get_name(par_num)` | Return the display name when name metadata is enabled. | +| `par_get_range(par_num)` | Return the configured min/max range when range metadata is enabled. | +| `par_get_unit(par_num)` | Return the engineering unit when unit metadata is enabled. | +| `par_get_desc(par_num)` | Return the description string when description metadata is enabled. | +| `par_get_type(par_num)` | Return the parameter type enum. | +| `par_get_access(par_num)` | Return read-only or read-write access metadata when enabled. | +| `par_is_persistant(par_num)` | Return whether the parameter is marked persistent when enabled. | +| `par_get_num_by_id(id, p_par_num)` | Convert an external ID to `par_num_t`. | +| `par_get_id_by_num(par_num, p_id)` | Convert `par_num_t` to external ID. | + +## NVM APIs + +Available only when `PAR_CFG_NVM_EN = 1`. + +| Function | Description | +| --- | --- | +| `par_set_n_save(par_num, p_val)` | Set one parameter and persist it immediately. | +| `par_save_all()` | Persist all persistent parameters. | +| `par_save(par_num)` | Persist one parameter. | +| `par_save_by_id(par_id)` | Persist one parameter by external ID. | +| `par_save_clean()` | Rewrite the full NVM area managed by the module. | + +## Registration APIs + +These APIs register behavior per parameter. + +| Function | Description | +| --- | --- | +| `par_register_on_change_cb(par_num, cb)` | Register a change callback for one parameter. | +| `par_register_validation(par_num, validation)` | Register a validation callback for one parameter. | + +Example: + +```c +static void on_mode_change( + const par_num_t par_num, + const par_type_t new_val, + const par_type_t old_val) +{ + (void)par_num; + (void)new_val; + (void)old_val; +} + +static bool validate_mode(const par_num_t par_num, const par_type_t val) +{ + (void)par_num; + return (val.u8 <= 3U); +} + +static void app_hooks_init(void) +{ + par_register_on_change_cb(ePAR_MODE, on_mode_change); + par_register_validation(ePAR_MODE, validate_mode); +} +``` + +## Debug helpers + +Available only when debug support is enabled. + +| Function | Description | +| --- | --- | +| `par_get_status_str(status)` | Convert a status code to a debug string. | + +## Status categories + +`par_status_t` combines normal status, errors, and warnings. + +Common values include: + +- `ePAR_OK` +- `ePAR_ERROR` +- `ePAR_ERROR_INIT` +- `ePAR_ERROR_NVM` +- `ePAR_ERROR_CRC` +- `ePAR_ERROR_TYPE` +- `ePAR_ERROR_MUTEX` +- `ePAR_ERROR_VALUE` +- `ePAR_WAR_SET_TO_DEF` +- `ePAR_WAR_NVM_REWRITTEN` +- `ePAR_WAR_NO_PERSISTANT` +- `ePAR_WAR_LIMITED` diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..04be6b5 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,224 @@ +# Architecture + +This document explains how the module is structured internally and how the major pieces work together. + +## High-level model + +The module separates four concerns: + +1. **Parameter definition** through `par_table.def` +2. **Core runtime access** through `par.c` and `par.h` +3. **Layout and storage** through `par_layout.*` +4. **Optional platform and NVM integration** through `par_if.*`, `par_atomic.h`, and `par_nvm.*` + +```mermaid +flowchart TD + A[par_table.def] --> B[par_def.h / par_def.c] + B --> C[Parameter configuration table] + B --> D[par_num_t enum] + B --> E[Compile-time storage counts] + C --> F[par.c runtime API] + E --> G[par_layout.c] + G --> H[Static typed storage] + F --> H + F --> I[ID hash map] + F --> J[Validation and callbacks] + F --> K[Optional NVM layer] + F --> L[Optional port backend] +``` + +## Single-source parameter definition + +`par_table.def` is intentionally included multiple times with different macro definitions. + +That makes one definition file drive multiple generated artifacts: + +- `par_num_t` enumeration in `par_def.h` +- configuration table access through `par_def.c` +- compile-time validation for integer parameter ranges +- compile-time storage group counts for layout + +This design reduces duplication and helps keep enum values, metadata, and storage assumptions aligned. + +## Internal vs external identification + +The module uses two identifiers for each parameter. + +### `par_num_t` + +`par_num_t` is the internal firmware-facing identifier. + +Use it when code inside the firmware directly accesses parameters. + +Characteristics: + +- dense enum-like index +- efficient for table-based access +- not intended to be stable across arbitrary reordering of the parameter list + +### `id` + +`id` is the external identifier stored in parameter metadata. + +Use it when parameters must be addressed by external systems such as: + +- CLI commands +- PC tools +- diagnostic frames +- protocol bridges + +This separation lets firmware use an efficient internal index while external tooling uses a stable numeric contract. + +## Storage model + +The module stores live values in static width-based groups instead of allocating each parameter separately on the heap. + +Storage groups: + +- 8-bit group for `U8`, `I8` +- 16-bit group for `U16`, `I16` +- 32-bit group for `U32`, `I32`, `F32` + +Benefits: + +- predictable RAM usage +- no runtime heap dependency for live storage +- compact grouping by data width + +At runtime, each parameter resolves to: + +- a storage group based on its type +- an offset inside that group + +## Layout subsystem + +The layout subsystem provides the offset map that connects each parameter to its location in the static typed storage. + +### Compile-scan mode + +When: + +```c +#define PAR_CFG_LAYOUT_SOURCE PAR_CFG_LAYOUT_COMPILE_SCAN +``` + +`par_layout_init()` scans the parameter table during initialization and builds offsets dynamically. + +Use this mode when you want a self-contained integration with no external code generation step. + +### Script-layout mode + +When: + +```c +#define PAR_CFG_LAYOUT_SOURCE PAR_CFG_LAYOUT_SCRIPT +``` + +layout data is provided by a generated static header and consumed directly. + +Use this mode when your tooling already owns parameter generation or when you want fixed layout data before compilation. + +## Validation model + +Validation happens in two stages. + +### Compile-time validation + +Compile-time validation is used for integer parameter types: + +- `U8` +- `I8` +- `U16` +- `I16` +- `U32` +- `I32` + +Typical checks include: + +- `min <= max` +- `def >= min` +- `def <= max` + +These checks are generated from `par_table.def`, so invalid integer configurations fail at build time. + +### Runtime validation + +Runtime validation is used for checks that are better handled dynamically, including: + +- floating-point range validation +- `name != NULL` when name metadata is enabled +- `desc != NULL` when description metadata is enabled +- description policy checks through `par_port_is_desc_valid()` when enabled +- per-parameter application validation callbacks + +This split keeps integer configuration errors out of the firmware image while still allowing flexible runtime policies. + +## ID lookup path + +The module supports ID-based APIs such as `par_get_by_id()` and `par_set_by_id()`. + +Because external IDs do not need to be sequential, the runtime builds a hash map during `par_init()`. + +```mermaid +flowchart LR + A[External ID] --> B[Hash function] + B --> C[Bucket lookup] + C --> D[par_num_t] + D --> E[Internal parameter access] +``` + +### Collision policy + +The current implementation uses a strict one-entry-per-bucket map. + +That means: + +- duplicate IDs are rejected +- hash collisions are rejected during initialization + +This keeps runtime lookup simple and deterministic, but it also means a conflicting ID assignment must be fixed at the source. + +## Normal path vs fast path + +The module exposes both normal setters and fast setters. + +### Normal setters + +Normal setters are the default path. They are intended for ordinary application code where correctness and observability matter more than shaving off a few instructions. + +### Fast setters + +Fast setters are specialized APIs for controlled hot paths. They reduce overhead, but they should only be used when the surrounding code already guarantees the assumptions that the full path would normally check. + +## Optional NVM persistence + +When `PAR_CFG_NVM_EN = 1`, the module can persist selected parameters to NVM. + +NVM persistence uses the parameter metadata, persistence flags, CRC handling, and table hash validation to detect incompatible or corrupted stored data. + +For this feature, the external NVM module must be available in the project. + +## Portability model + +The module stays portable by keeping platform-specific logic behind dedicated boundaries. + +### Core portable layer + +Implemented under `src/`: + +- parameter storage +- parameter metadata access +- validation and callbacks +- layout handling +- ID lookup +- optional NVM support + +### Port-specific layer + +Implemented by the integrator as needed: + +- `par_cfg_port.h` +- `par_if_port.c` +- `par_atomic_port.h` + +This separation makes the core reusable while still allowing the target platform to provide mutexes, logging, assertions, and atomic primitives. diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..64cd8d0 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,274 @@ +# Getting started + +This guide shows how to integrate the `Device Parameters` module into a firmware project, which files you must provide, and which configuration choices matter first. + +## Integration checklist + +1. Add `src/*.c` and `src/*.h` to your project. +2. Provide `par_table.def` at the package root. +3. Provide `port/par_cfg_port.h`. +4. Decide whether you want: + - NVM persistence + - a platform-specific interface backend + - a platform-specific atomic backend + - compile-scan or script-provided layout +5. Call `par_init()` before runtime access. +6. Use typed APIs or the generic `PAR_SET` and `PAR_GET` macros. + +## Required files + +### `par_table.def` + +`par_table.def` is the single source of truth for parameter definitions. + +Each row defines one parameter and is reused to build: + +- `par_num_t` +- the parameter configuration table +- compile-time integer validation checks +- compile-time storage counts + +A minimal example: + +```c +PAR_ITEM_U8 ( + ePAR_MODE, + 10, + "Mode", + 0U, + 3U, + 0U, + NULL, + ePAR_ACCESS_RW, + true, + "Application operating mode" +) + +PAR_ITEM_F32( + ePAR_TARGET_TEMP, + 20, + "Target temperature", + -40.0f, + 125.0f, + 25.0f, + "degC", + ePAR_ACCESS_RW, + true, + "Requested control target temperature" +) +``` + +Use `template/par_table.deftmp` as the starting point. + +### `port/par_cfg_port.h` + +`src/par_cfg.h` includes `par_cfg_port.h` unconditionally. + +If you do not need platform overrides yet, start with a minimal stub: + +```c +#ifndef _PAR_CFG_PORT_H_ +#define _PAR_CFG_PORT_H_ +/* Optional platform overrides */ +#endif +``` + +Use `template/par_cfg_port.htmp` as the starting point. + +## Optional integration files + +### `port/par_if_port.c` + +Provide this file only when `PAR_CFG_IF_PORT_EN = 1`. + +Use it to integrate platform-specific services such as: + +- initialization hooks +- mutex handling +- optional table hash support + +### `port/par_atomic_port.h` + +Provide this file only when the default C11 atomic backend is not suitable. + +Use it when: + +- your compiler does not support `` well enough +- your RTOS already provides atomic primitives +- you want all atomic operations mapped to a platform-native implementation + +Enable it with: + +```c +#define PAR_ATOMIC_BACKEND PAR_ATOMIC_BACKEND_PORT +``` + +### Static layout header + +Provide a generated static layout header only when: + +```c +#define PAR_CFG_LAYOUT_SOURCE PAR_CFG_LAYOUT_SCRIPT +``` + +Use `template/par_layout_static.htmp` as the contract for the generated file. + +## Configuration decisions that matter first + +### NVM support + +Enable NVM only when you actually need persistent parameters. + +Relevant options in `par_cfg.h`: + +- `PAR_CFG_NVM_EN` +- `PAR_CFG_NVM_REGION` +- `PAR_CFG_ENABLE_ID` +- `PAR_CFG_ENABLE_PERSIST` + +When NVM is enabled, the external NVM module must be present in the project. + +### Layout source + +Choose one of these two modes: + +```c +#define PAR_CFG_LAYOUT_COMPILE_SCAN (0u) +#define PAR_CFG_LAYOUT_SCRIPT (1u) +``` + +Use **compile scan** when parameter definitions live in source and you want the module to derive offsets at initialization. + +Use **script layout** when your build or tooling already generates fixed layout data before compilation. + +### Atomic backend + +The default backend is C11 atomics. Switch to the port backend only when the default is not a good fit for the target. + +## Initialization + +Call `par_init()` before any runtime parameter access. + +```c +if (par_init() != ePAR_OK) +{ + /* Handle error */ +} +``` + +If `PAR_CFG_NVM_EN = 1`, NVM-related initialization is part of the parameter module startup path. + +## Reading and writing values + +### Use the generic macros in normal application code + +```c +PAR_SET(ePAR_TARGET_TEMP, (float32_t)42.5f); + +float32_t target_temp = 0.0f; +PAR_GET(ePAR_TARGET_TEMP, target_temp); +``` + +### Use typed APIs when explicitness matters + +```c +(void)par_set_u16(ePAR_PWM_LIMIT, 1024U); +uint16_t pwm_limit = par_get_u16(ePAR_PWM_LIMIT); +``` + +### Use pointer-based generic APIs only when needed + +```c +float32_t value = 12.0f; +(void)par_set(ePAR_TARGET_TEMP, &value); + +float32_t readback = 0.0f; +(void)par_get(ePAR_TARGET_TEMP, &readback); +``` + +## Registering callbacks and validation + +The registration APIs work per parameter and take the parameter number directly. + +### On-change callback + +```c +static void on_mode_changed( + const par_num_t par_num, + const par_type_t new_val, + const par_type_t old_val) +{ + (void)par_num; + /* React to the change */ +} + +static void app_register_callbacks(void) +{ + par_register_on_change_cb(ePAR_MODE, on_mode_changed); +} +``` + +### Validation callback + +```c +static bool validate_target_temp(const par_num_t par_num, const par_type_t val) +{ + (void)par_num; + + return (val.f32 >= -20.0f) && (val.f32 <= 100.0f); +} + +static void app_register_validation(void) +{ + par_register_validation(ePAR_TARGET_TEMP, validate_target_temp); +} +``` + +## Normal vs fast setters + +Use the normal setters unless you have a measured reason not to. + +### Normal setters + +Normal setters go through the full parameter path, including checks and side effects such as validation and change tracking. + +```c +(void)par_set_f32(ePAR_TARGET_TEMP, 25.0f); +``` + +### Fast setters + +Fast setters are meant for controlled hot paths where you accept reduced safety or observability in exchange for lower overhead. + +```c +(void)par_set_u16_fast(ePAR_PWM_LIMIT, 1200U); +``` + +Do not use fast setters as the default API for ordinary application code. + +## Persistence to NVM + +When NVM support is enabled, use the NVM APIs for storing current values. + +```c +if (par_save_all() != ePAR_OK) +{ + /* Handle storage error */ +} +``` + +Or update and store one parameter in one step: + +```c +uint32_t baud = 115200U; +(void)par_set_n_save(ePAR_UART_BAUD, &baud); +``` + +## Common mistakes to avoid + +- Forgetting to provide `par_cfg_port.h` +- Treating `par_num_t` as a stable external interface +- Using fast setters before understanding their tradeoffs +- Enabling NVM without the external NVM module in the build +- Writing `par_table.def` entries with duplicate IDs +- Assuming the repository already ships a ready-to-build `par_table.def` for your project diff --git a/src/par_layout.c b/src/par_layout.c index 7dcd1d5..6fc1182 100644 --- a/src/par_layout.c +++ b/src/par_layout.c @@ -75,13 +75,6 @@ void par_layout_init(void) for ( uint32_t par_it = 0u; par_it < (uint32_t) ePAR_NUM_OF; par_it++ ) { const par_cfg_t * const p_cfg = par_cfg_get((par_num_t) par_it ); - if ( NULL == p_cfg ) - { - PAR_DBG_PRINT( "ERR, PAR layout config missing at par_num=%u", (unsigned) par_it ); - PAR_ASSERT( 0 ); - return; - } - switch ( p_cfg->type ) { case ePAR_TYPE_U8: From 194bfe1630b808bf39029e169ed5d7aa77ac3fcf Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Wed, 18 Mar 2026 10:02:53 +0800 Subject: [PATCH 10/36] feat(core): Add compile-time initialization for parameter defaults --- README.md | 1 + docs/api-reference.md | 13 ++++- docs/architecture.md | 23 +++++++++ docs/getting-started.md | 14 ++++- src/par.c | 112 ++++++++++++++++++++++++++++++++++++++-- 5 files changed, 156 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 0fbdccb..ca62b28 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,7 @@ This repository contains the reusable module core and templates. A real integrat - The module separates **internal parameter enumeration** (`par_num_t`) from **external parameter IDs** (`id`). - Fast setter APIs skip part of the safety and observability path, so they should be reserved for tightly controlled hot paths. - NVM support is optional, but when enabled it depends on the external NVM module and on ID and persistence metadata being enabled. +- `par_init()` applies startup default values directly to live storage. Integer default values from `par_table.def` are compiled into the shared width-based storage arrays, while `F32` default values are applied after layout offsets are available. Because this startup initialization does not go through the public setter path, it does not invoke runtime validation or on-change callbacks. ## Related projects diff --git a/docs/api-reference.md b/docs/api-reference.md index 0ce3232..7229f47 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -14,7 +14,7 @@ This document groups the public API from `src/par.h` by responsibility. | Function | Description | | --- | --- | -| `par_init()` | Initialize the module, build runtime state, validate the table, and prepare storage access. | +| `par_init()` | Initialize the module, validate the table, bind layout/runtime state, apply default values to live storage, and optionally load persisted values from NVM. Startup defaults are applied internally and do not use the public setter path. | | `par_deinit()` | Deinitialize the module. | | `par_is_init()` | Return whether the module is initialized. | @@ -81,6 +81,15 @@ Use these only in controlled hot paths. | `par_has_changed(par_num, p_has_changed)` | Report whether the value differs from its default. | | `par_is_changed(par_num)` | Return whether the value differs from its default. | +`par_set_to_default()` and `par_set_all_to_default()` are runtime reset APIs. + +They are different from startup initialization: + +- `par_init()` applies the default values defined in `par_table.def` directly to live storage +- `par_set_to_default()` and `par_set_all_to_default()` still use the normal runtime value path + +That distinction matters if your application depends on validation callbacks, on-change callbacks, or other setter-side effects. + ## Generic getters | Function | Description | @@ -166,6 +175,8 @@ static void app_hooks_init(void) } ``` +These hooks affect runtime writes and explicit reset operations. They are not invoked during the internal startup default initialization performed by `par_init()`. + ## Debug helpers Available only when debug support is enabled. diff --git a/docs/architecture.md b/docs/architecture.md index 04be6b5..016d4a0 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -90,10 +90,33 @@ At runtime, each parameter resolves to: - a storage group based on its type - an offset inside that group +### How default values are applied at startup + +Live storage is initialized in two phases during startup: + +1. Integer default values from `par_table.def` are compiled directly into the shared storage arrays. +2. `F32` default values are written into the shared 32-bit storage after layout offsets are available. + +This means `par_init()` does not need to apply startup defaults through the public setter path for every parameter. + +### Ordering contract for shared storage + +Within each width-based storage group, element order follows `par_table.def` entry order filtered by the types supported by that group. + +That ordering contract matters because: + +- compile-time integer default initializers depend on it +- runtime layout offset generation depends on it +- both sides must stay aligned so each parameter lands in the correct slot + +If the filtered storage order and the runtime layout scan order ever diverge, defaults can be written into the wrong storage positions. + ## Layout subsystem The layout subsystem provides the offset map that connects each parameter to its location in the static typed storage. +The layout step is also what makes it possible to patch `F32` defaults correctly, because floating-point values share the 32-bit storage group and need their final offsets first. + ### Compile-scan mode When: diff --git a/docs/getting-started.md b/docs/getting-started.md index 64cd8d0..c6bdd93 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -156,7 +156,19 @@ if (par_init() != ePAR_OK) } ``` -If `PAR_CFG_NVM_EN = 1`, NVM-related initialization is part of the parameter module startup path. +If `PAR_CFG_NVM_EN = 1`, NVM loading happens after the module applies default values from `par_table.def`, so persisted values can overwrite the startup defaults. + +### How `par_init()` applies default values + +`par_init()` validates the table, binds the storage layout, initializes the interface layer, applies default values to live storage, and then optionally loads persisted values from NVM. + +During startup: + +- integer default values defined in `par_table.def` are already present in the shared storage arrays at definition time +- `F32` default values are written after layout offsets are known +- if NVM support is enabled, persisted values may then overwrite those default values + +Do not rely on startup initialization to trigger application callbacks or runtime validation hooks. ## Reading and writing values diff --git a/src/par.c b/src/par.c index e75e689..7b46761 100644 --- a/src/par.c +++ b/src/par.c @@ -131,9 +131,86 @@ static struct * * @note Zero-length groups are mapped to size 1 arrays for compiler portability. */ -static par_atomic_u8_t gs_par_u8_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT8)]; -static par_atomic_u16_t gs_par_u16_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT16)]; -static par_atomic_u32_t gs_par_u32_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT32)]; +/* + * Compile-time default initialization for shared storage groups. + * + * Storage order inside each width group must follow par_table.def entry order + * filtered by the group-supported types, matching runtime layout scan order. + */ +#define PAR_STORAGE_INIT_NOP(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#define PAR_STORAGE_U8_FROM_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u8_t)((uint8_t)(def_)), +#define PAR_STORAGE_U8_FROM_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u8_t)((uint8_t)(int8_t)(def_)), +#define PAR_STORAGE_U16_FROM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u16_t)((uint16_t)(def_)), +#define PAR_STORAGE_U16_FROM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u16_t)((uint16_t)(int16_t)(def_)), +#define PAR_STORAGE_U32_FROM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u32_t)((uint32_t)(def_)), +#define PAR_STORAGE_U32_FROM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u32_t)((uint32_t)(int32_t)(def_)), +#define PAR_STORAGE_U32_FROM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u32_t)(0u), + +#define PAR_ITEM_U8 PAR_STORAGE_U8_FROM_U8 +#define PAR_ITEM_U16 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_U32 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_I8 PAR_STORAGE_U8_FROM_I8 +#define PAR_ITEM_I16 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_I32 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_F32 PAR_STORAGE_INIT_NOP +static par_atomic_u8_t gs_par_u8_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT8)] = +{ + #include "../../par_table.def" +}; +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 + +#define PAR_ITEM_U8 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_U16 PAR_STORAGE_U16_FROM_U16 +#define PAR_ITEM_U32 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_I8 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_I16 PAR_STORAGE_U16_FROM_I16 +#define PAR_ITEM_I32 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_F32 PAR_STORAGE_INIT_NOP +static par_atomic_u16_t gs_par_u16_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT16)] = +{ + #include "../../par_table.def" +}; +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 + +#define PAR_ITEM_U8 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_U16 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_U32 PAR_STORAGE_U32_FROM_U32 +#define PAR_ITEM_I8 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_I16 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_I32 PAR_STORAGE_U32_FROM_I32 +#define PAR_ITEM_F32 PAR_STORAGE_U32_FROM_F32 +static par_atomic_u32_t gs_par_u32_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT32)] = +{ + #include "../../par_table.def" +}; +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 + +#undef PAR_STORAGE_INIT_NOP +#undef PAR_STORAGE_U8_FROM_U8 +#undef PAR_STORAGE_U8_FROM_I8 +#undef PAR_STORAGE_U16_FROM_U16 +#undef PAR_STORAGE_U16_FROM_I16 +#undef PAR_STORAGE_U32_FROM_U32 +#undef PAR_STORAGE_U32_FROM_I32 +#undef PAR_STORAGE_U32_FROM_F32 /** * Parameter live values divided by its type in RAM @@ -231,6 +308,28 @@ PAR_PORT_WEAK bool par_port_is_desc_valid(const char * const p_desc) } #endif +//////////////////////////////////////////////////////////////////////////////// +/** +* Patch F32 defaults into shared u32 storage as bit-patterns +* +* @note Integer defaults are already initialized at definition time. +* F32 defaults are patched once after layout offsets are available. +* +* @return void +*/ +//////////////////////////////////////////////////////////////////////////////// +static void par_patch_f32_defaults_from_table(void) +{ + for ( par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) + { + const par_cfg_t * const p_cfg = par_cfg_get( par_num ); + if ( ePAR_TYPE_F32 == p_cfg->type ) + { + PAR_SET_F32_PRIV( par_num, p_cfg->def.f32 ); + } + } +} + //////////////////////////////////////////////////////////////////////////////// /** * Bind static space for live parameter values @@ -479,8 +578,11 @@ par_status_t par_init(void) gb_par_id_map_ready = true; #endif - // Set all parameters to default - par_set_all_to_default(); + /* Set all parameters to default + * Integer defaults are already initialized at definition time. + * F32 defaults are patched once after layout offsets are available. + */ + par_patch_f32_defaults_from_table(); #if ( 1 == PAR_CFG_NVM_EN ) // Init and load parameters from NVM From 56294e1308129c15e1ecde8c97741e7299da7ea8 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Thu, 19 Mar 2026 08:30:33 +0800 Subject: [PATCH 11/36] feat(core): Add configurable F32 parameter support --- README.md | 7 ++-- docs/api-reference.md | 24 +++++++++++--- docs/architecture.md | 19 +++++++++-- docs/getting-started.md | 55 +++++++++++++++++++++++++++++-- src/par.c | 40 +++++++++++++++++++++-- src/par.h | 72 ++++++++++++++++++++++++++++------------- src/par_cfg.h | 4 +++ src/par_def.c | 6 +++- src/par_def.h | 16 ++++++--- src/par_layout.c | 2 ++ 10 files changed, 205 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index ca62b28..188e666 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ It is designed for projects that need a clean way to: ## What this module provides - **Single source of truth** through `par_table.def` -- **Typed APIs** for `U8`, `I8`, `U16`, `I16`, `U32`, `I32`, and `F32` +- **Typed APIs** for `U8`, `I8`, `U16`, `I16`, `U32`, `I32`, and, when enabled, `F32` - **Optional metadata** such as name, unit, description, access, ID, and persistence flags - **Validation pipeline** with compile-time checks for integer ranges and runtime checks for dynamic rules - **Static live-value storage** grouped by width instead of heap allocation @@ -35,6 +35,8 @@ It is designed for projects that need a clean way to: A minimal example: +This quick-start example assumes `PAR_CFG_ENABLE_TYPE_F32 = 1`. + ```c #include "par.h" @@ -114,10 +116,11 @@ This repository contains the reusable module core and templates. A real integrat ## Key integration notes - `par_cfg.h` includes `par_cfg_port.h` unconditionally, so your build must provide that header. +- `PAR_CFG_ENABLE_TYPE_F32` controls whether floating-point parameter support, related typed APIs, and `_Generic` float dispatch are compiled in. - The module separates **internal parameter enumeration** (`par_num_t`) from **external parameter IDs** (`id`). - Fast setter APIs skip part of the safety and observability path, so they should be reserved for tightly controlled hot paths. - NVM support is optional, but when enabled it depends on the external NVM module and on ID and persistence metadata being enabled. -- `par_init()` applies startup default values directly to live storage. Integer default values from `par_table.def` are compiled into the shared width-based storage arrays, while `F32` default values are applied after layout offsets are available. Because this startup initialization does not go through the public setter path, it does not invoke runtime validation or on-change callbacks. +- `par_init()` applies startup default values directly to live storage. Integer default values from `par_table.def` are compiled into the shared width-based storage arrays, while `F32` default values are applied after layout offsets are available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. Because this startup initialization does not go through the public setter path, it does not invoke runtime validation or on-change callbacks. ## Related projects diff --git a/docs/api-reference.md b/docs/api-reference.md index 7229f47..c0b853f 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -9,6 +9,19 @@ This document groups the public API from `src/par.h` by responsibility. - `par_num_t` is the internal parameter index. - ID-based APIs depend on `PAR_CFG_ENABLE_ID = 1`. - NVM APIs depend on `PAR_CFG_NVM_EN = 1`. +- `F32` typed APIs and `_Generic` float dispatch depend on `PAR_CFG_ENABLE_TYPE_F32 = 1`. + +## Compile-time availability notes + +The module conditionally compiles parts of the API based on configuration. + +- `PAR_CFG_NVM_EN = 1` enables NVM APIs +- `PAR_CFG_ENABLE_ID = 1` enables ID-dependent behavior +- `PAR_CFG_ENABLE_TYPE_F32 = 1` enables: + - `par_set_f32()` + - `par_get_f32()` + - `par_set_f32_fast()` + - `float32_t` dispatch through `PAR_SET` and `PAR_GET` ## Lifecycle @@ -33,7 +46,8 @@ These are relevant only when mutex support is enabled in the integration. | --- | --- | | `par_set(par_num, p_val)` | Set a parameter from a typed pointer. | | `par_set_by_id(id, p_val)` | Set a parameter using its external ID. | -| `PAR_SET(par_num, value)` | Use C11 `_Generic` to route a typed value to the matching setter. | +| `PAR_SET(par_num, value)` | Use C11 `_Generic` to route a typed value to the matching setter. `float32_t` dispatch is available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. | + ## Typed setters @@ -45,7 +59,7 @@ These are relevant only when mutex support is enabled in the integration. | `par_set_i16()` | Set an `I16` parameter. | | `par_set_u32()` | Set a `U32` parameter. | | `par_set_i32()` | Set an `I32` parameter. | -| `par_set_f32()` | Set an `F32` parameter. | +| `par_set_f32()` | Set an `F32` parameter. Available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. | ## Fast setters @@ -57,7 +71,7 @@ These are relevant only when mutex support is enabled in the integration. | `par_set_i16_fast()` | Fast set for `I16`. | | `par_set_u32_fast()` | Fast set for `U32`. | | `par_set_i32_fast()` | Fast set for `I32`. | -| `par_set_f32_fast()` | Fast set for `F32`. | +| `par_set_f32_fast()` | Fast set for `F32`. Available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. | Use these only in controlled hot paths. @@ -96,7 +110,7 @@ That distinction matters if your application depends on validation callbacks, on | --- | --- | | `par_get(par_num, p_val)` | Read a parameter into a typed destination pointer. | | `par_get_by_id(id, p_val)` | Read a parameter using its external ID. | -| `PAR_GET(par_num, dest)` | Use C11 `_Generic` to route to the matching typed getter. | +| `PAR_GET(par_num, dest)` | Use C11 `_Generic` to route a destination variable to the matching getter. `float32_t` dispatch is available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. | ## Typed getters @@ -108,7 +122,7 @@ That distinction matters if your application depends on validation callbacks, on | `par_get_i16()` | Read an `I16` parameter. | | `par_get_u32()` | Read a `U32` parameter. | | `par_get_i32()` | Read an `I32` parameter. | -| `par_get_f32()` | Read an `F32` parameter. | +| `par_get_f32()` | Read an `F32` parameter. Available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. | | `par_get_default(par_num, p_val)` | Read the configured default value for a parameter. | ## Metadata access diff --git a/docs/architecture.md b/docs/architecture.md index 016d4a0..711f610 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -37,9 +37,19 @@ That makes one definition file drive multiple generated artifacts: - configuration table access through `par_def.c` - compile-time validation for integer parameter ranges - compile-time storage group counts for layout +- fail-fast compile-time rejection for disabled `F32` entries This design reduces duplication and helps keep enum values, metadata, and storage assumptions aligned. +### Why disabled `F32` still appears in enum expansion + +`par_def.h` intentionally keeps enum expansion configuration-independent. + +That keeps `par_num_t` generation stable and avoids include-order coupling with `par_cfg.h`. + +The fail-fast rule is enforced later in `par_def.c`: if `F32` support is disabled and `par_table.def` still contains `PAR_ITEM_F32(...)`, compilation stops with a static assertion. + + ## Internal vs external identification The module uses two identifiers for each parameter. @@ -77,7 +87,7 @@ Storage groups: - 8-bit group for `U8`, `I8` - 16-bit group for `U16`, `I16` -- 32-bit group for `U32`, `I32`, `F32` +- 32-bit group for `U32`, `I32`, and, when enabled, `F32` Benefits: @@ -95,7 +105,7 @@ At runtime, each parameter resolves to: Live storage is initialized in two phases during startup: 1. Integer default values from `par_table.def` are compiled directly into the shared storage arrays. -2. `F32` default values are written into the shared 32-bit storage after layout offsets are available. +2. When `PAR_CFG_ENABLE_TYPE_F32 = 1`, `F32` default values are written into the shared 32-bit storage after layout offsets are available. This means `par_init()` does not need to apply startup defaults through the public setter path for every parameter. @@ -112,6 +122,7 @@ That ordering contract matters because: If the filtered storage order and the runtime layout scan order ever diverge, defaults can be written into the wrong storage positions. ## Layout subsystem +When `PAR_CFG_ENABLE_TYPE_F32 = 1`, the layout step also makes it possible to patch `F32` defaults correctly, because floating-point values share the 32-bit storage group and need their final offsets first. The layout subsystem provides the offset map that connects each parameter to its location in the static typed storage. @@ -164,6 +175,10 @@ Typical checks include: These checks are generated from `par_table.def`, so invalid integer configurations fail at build time. +`F32` range checks are still not evaluated as compile-time constant expressions. + +However, when `PAR_CFG_ENABLE_TYPE_F32 = 0`, any `PAR_ITEM_F32(...)` entry is rejected at compile time through a static assertion. This keeps `par_def.h` configuration-independent while still failing fast in `par_def.c`. + ### Runtime validation Runtime validation is used for checks that are better handled dynamically, including: diff --git a/docs/getting-started.md b/docs/getting-started.md index c6bdd93..65ff3f5 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -12,6 +12,7 @@ This guide shows how to integrate the `Device Parameters` module into a firmware - a platform-specific interface backend - a platform-specific atomic backend - compile-scan or script-provided layout + - whether `F32` parameter support should be compiled in 5. Call `par_init()` before runtime access. 6. Use typed APIs or the generic `PAR_SET` and `PAR_GET` macros. @@ -60,6 +61,8 @@ PAR_ITEM_F32( Use `template/par_table.deftmp` as the starting point. +This example requires `PAR_CFG_ENABLE_TYPE_F32 = 1`. If `F32` support is disabled, remove all `PAR_ITEM_F32(...)` rows from `par_table.def`. + ### `port/par_cfg_port.h` `src/par_cfg.h` includes `par_cfg_port.h` unconditionally. @@ -145,6 +148,22 @@ Use **script layout** when your build or tooling already generates fixed layout The default backend is C11 atomics. Switch to the port backend only when the default is not a good fit for the target. +### F32 type support +Use `PAR_CFG_ENABLE_TYPE_F32` to control whether `F32` parameters are compiled into the module. + +```c +#define PAR_CFG_ENABLE_TYPE_F32 ( 1 ) +``` + +Set it to `0` only when your integration does not need floating-point parameters. + +When `PAR_CFG_ENABLE_TYPE_F32 = 0`: + +* `PAR_ITEM_F32(...)` entries are not allowed in `par_table.def` +* `par_set_f32()`, `par_get_f32()`, and `par_set_f32_fast()` are not available +* `PAR_SET` and `PAR_GET` no longer dispatch `float32_t` +* startup F32 default patching is skipped + ## Initialization Call `par_init()` before any runtime parameter access. @@ -165,15 +184,16 @@ If `PAR_CFG_NVM_EN = 1`, NVM loading happens after the module applies default va During startup: - integer default values defined in `par_table.def` are already present in the shared storage arrays at definition time -- `F32` default values are written after layout offsets are known +- when `PAR_CFG_ENABLE_TYPE_F32 = 1`, `F32` default values are written after layout offsets are known - if NVM support is enabled, persisted values may then overwrite those default values Do not rely on startup initialization to trigger application callbacks or runtime validation hooks. ## Reading and writing values -### Use the generic macros in normal application code +The `F32` examples in this section require `PAR_CFG_ENABLE_TYPE_F32 = 1`. +### Use the generic macros in normal application code ```c PAR_SET(ePAR_TARGET_TEMP, (float32_t)42.5f); @@ -284,3 +304,34 @@ uint32_t baud = 115200U; - Enabling NVM without the external NVM module in the build - Writing `par_table.def` entries with duplicate IDs - Assuming the repository already ships a ready-to-build `par_table.def` for your project +- Disabling `PAR_CFG_ENABLE_TYPE_F32` while keeping `PAR_ITEM_F32(...)` entries in `par_table.def` +- Assuming `PAR_SET` and `PAR_GET` still accept `float32_t` after F32 support is disabled + +### Compile-time error example when F32 support is disabled + +If `PAR_CFG_ENABLE_TYPE_F32 = 0` and `par_table.def` still contains `PAR_ITEM_F32(...)`, the build fails with a static assertion. + +Example: + +```log +def.h:124:51: error: size of array '_static_assert_ePAR_SYS_CPU_LOAD_MAX_f32_type_is_disabled__remove_PAR_ITEM_F32' is negative + 124 | #define _STATIC_ASSERT(name, expn) typedef char _static_assert_##name[(expn)?1:-1] + | ^~~~~~~~~~~~~~~ +port/par_cfg_port.h:130:44: note: in expansion of macro '_STATIC_ASSERT' + 130 | #define PAR_PORT_STATIC_ASSERT(name, expn) _STATIC_ASSERT(name, expn) + | ^~~~~~~~~~~~~~~~ +src/par_cfg.h:160:53: note: in expansion of macro 'PAR_PORT_STATIC_ASSERT' + 160 | #define PAR_STATIC_ASSERT(name, expn) PAR_PORT_STATIC_ASSERT(name, expn); + | ^~~~~~~~~~~~~~~~~~~~~~ +src/par_def.c:73:94: note: in expansion of macro 'PAR_STATIC_ASSERT' + 73 | #define PAR_CHECK_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_STATIC_ASSERT(enum_##_f32_type_is_disabled__remove_PAR_ITEM_F32, 0) + | ^~~~~~~~~~~~~~~~~ +src/par_def.c:85:23: note: in expansion of macro 'PAR_CHECK_F32' + 85 | #define PAR_ITEM_F32 PAR_CHECK_F32 + | ^~~~~~~~~~~~~ +par_table.def:189:1: note: in expansion of macro 'PAR_ITEM_F32' + 189 | PAR_ITEM_F32(ePAR_SYS_CPU_LOAD_MAX, 10011, "CPU Max. load", 0.0f, 100.0f, 0.0f, "%", ePAR_ACCESS_RO, false, "Maximum CPU load in %") + | ^~~~~~~~~~~~ +``` + +Fix the table first: remove the `PAR_ITEM_F32(...)` entry or re-enable `PAR_CFG_ENABLE_TYPE_F32`. diff --git a/src/par.c b/src/par.c index 7b46761..3bc96b3 100644 --- a/src/par.c +++ b/src/par.c @@ -144,7 +144,9 @@ static struct #define PAR_STORAGE_U16_FROM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u16_t)((uint16_t)(int16_t)(def_)), #define PAR_STORAGE_U32_FROM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u32_t)((uint32_t)(def_)), #define PAR_STORAGE_U32_FROM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u32_t)((uint32_t)(int32_t)(def_)), +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) #define PAR_STORAGE_U32_FROM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u32_t)(0u), +#endif #define PAR_ITEM_U8 PAR_STORAGE_U8_FROM_U8 #define PAR_ITEM_U16 PAR_STORAGE_INIT_NOP @@ -190,7 +192,11 @@ static par_atomic_u16_t gs_par_u16_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT #define PAR_ITEM_I8 PAR_STORAGE_INIT_NOP #define PAR_ITEM_I16 PAR_STORAGE_INIT_NOP #define PAR_ITEM_I32 PAR_STORAGE_U32_FROM_I32 -#define PAR_ITEM_F32 PAR_STORAGE_U32_FROM_F32 +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) + #define PAR_ITEM_F32 PAR_STORAGE_U32_FROM_F32 +#else + #define PAR_ITEM_F32 PAR_STORAGE_INIT_NOP +#endif static par_atomic_u32_t gs_par_u32_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT32)] = { #include "../../par_table.def" @@ -210,7 +216,9 @@ static par_atomic_u32_t gs_par_u32_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT #undef PAR_STORAGE_U16_FROM_I16 #undef PAR_STORAGE_U32_FROM_U32 #undef PAR_STORAGE_U32_FROM_I32 +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) #undef PAR_STORAGE_U32_FROM_F32 +#endif /** * Parameter live values divided by its type in RAM @@ -221,7 +229,9 @@ static par_atomic_u16_t * gpu16_par_value = gs_par_u16_storage; static par_atomic_i16_t * gpi16_par_value = (par_atomic_i16_t *)gs_par_u16_storage; static par_atomic_u32_t * gpu32_par_value = gs_par_u32_storage; static par_atomic_i32_t * gpi32_par_value = (par_atomic_i32_t *)gs_par_u32_storage; +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) static par_atomic_f32_t * gpf32_par_value = (par_atomic_f32_t *)gs_par_u32_storage; +#endif /** * Offset table compatibility alias. @@ -240,7 +250,9 @@ static par_atomic_f32_t * gpf32_par_value = (par_atomic_f32_t *)gs_par_u32_st #define PAR_GET_I16_PRIV(par_num) PAR_ATOMIC_LOAD(i16, &gpi16_par_value[g_par_offset[par_num]]) #define PAR_GET_U32_PRIV(par_num) PAR_ATOMIC_LOAD(u32, &gpu32_par_value[g_par_offset[par_num]]) #define PAR_GET_I32_PRIV(par_num) PAR_ATOMIC_LOAD(i32, &gpi32_par_value[g_par_offset[par_num]]) +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) #define PAR_GET_F32_PRIV(par_num) PAR_ATOMIC_LOAD(f32, &gpf32_par_value[g_par_offset[par_num]]) +#endif #define PAR_SET_U8_PRIV(par_num, val) PAR_ATOMIC_STORE(u8, &gpu8_par_value[g_par_offset[par_num]], (val)) #define PAR_SET_I8_PRIV(par_num, val) PAR_ATOMIC_STORE(i8, &gpi8_par_value[g_par_offset[par_num]], (val)) @@ -248,7 +260,9 @@ static par_atomic_f32_t * gpf32_par_value = (par_atomic_f32_t *)gs_par_u32_st #define PAR_SET_I16_PRIV(par_num, val) PAR_ATOMIC_STORE(i16, &gpi16_par_value[g_par_offset[par_num]], (val)) #define PAR_SET_U32_PRIV(par_num, val) PAR_ATOMIC_STORE(u32, &gpu32_par_value[g_par_offset[par_num]], (val)) #define PAR_SET_I32_PRIV(par_num, val) PAR_ATOMIC_STORE(i32, &gpi32_par_value[g_par_offset[par_num]], (val)) +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) #define PAR_SET_F32_PRIV(par_num, val) PAR_ATOMIC_STORE(f32, &gpf32_par_value[g_par_offset[par_num]], (val)) +#endif #if ( PAR_CFG_DEBUG_EN ) @@ -308,6 +322,7 @@ PAR_PORT_WEAK bool par_port_is_desc_valid(const char * const p_desc) } #endif +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) //////////////////////////////////////////////////////////////////////////////// /** * Patch F32 defaults into shared u32 storage as bit-patterns @@ -329,6 +344,7 @@ static void par_patch_f32_defaults_from_table(void) } } } +#endif //////////////////////////////////////////////////////////////////////////////// /** @@ -513,9 +529,11 @@ static bool par_is_value_changed(const par_num_t par_num, const void * p_val) value_changed = (par_get_i32(par_num) != *(int32_t*)p_val); break; +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) case ePAR_TYPE_F32: value_changed = (par_get_f32(par_num) != *(float32_t*)p_val); break; +#endif case ePAR_TYPE_NUM_OF: default: @@ -582,7 +600,9 @@ par_status_t par_init(void) * Integer defaults are already initialized at definition time. * F32 defaults are patched once after layout offsets are available. */ - par_patch_f32_defaults_from_table(); + #if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) + par_patch_f32_defaults_from_table(); + #endif #if ( 1 == PAR_CFG_NVM_EN ) // Init and load parameters from NVM @@ -714,9 +734,11 @@ par_status_t par_set(const par_num_t par_num, const void * p_val) status = par_set_i32( par_num, *(int32_t*) p_val ); break; +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) case ePAR_TYPE_F32: status = par_set_f32( par_num, *(float32_t*) p_val ); break; +#endif case ePAR_TYPE_NUM_OF: default: @@ -1091,6 +1113,7 @@ par_status_t par_set_i32(const par_num_t par_num, const int32_t val) * @return status - Status of operation */ //////////////////////////////////////////////////////////////////////////////// +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) par_status_t par_set_f32(const par_num_t par_num, const float32_t val) { par_status_t status = ePAR_OK; @@ -1136,6 +1159,7 @@ par_status_t par_set_f32(const par_num_t par_num, const float32_t val) return status; } +#endif //////////////////////////////////////////////////////////////////////////////// /** @@ -1377,6 +1401,7 @@ par_status_t par_set_i32_fast(const par_num_t par_num, const int32_t val) * @return status - Status of operation */ //////////////////////////////////////////////////////////////////////////////// +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) par_status_t par_set_f32_fast(const par_num_t par_num, const float32_t val) { PAR_ASSERT( true == par_is_init()); @@ -1405,6 +1430,7 @@ par_status_t par_set_f32_fast(const par_num_t par_num, const float32_t val) return ePAR_OK; #endif } +#endif //////////////////////////////////////////////////////////////////////////////// /** @@ -1709,9 +1735,11 @@ par_status_t par_has_changed(const par_num_t par_num, bool *const p_has_changed) *p_has_changed = (par_get_i32(par_num) != par_cfg->def.i32); break; +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) case ePAR_TYPE_F32: *p_has_changed = (par_get_f32(par_num) != par_cfg->def.f32); break; +#endif case ePAR_TYPE_NUM_OF: default: @@ -1769,9 +1797,11 @@ par_status_t par_get(const par_num_t par_num, void * const p_val) *(int32_t*) p_val = par_get_i32(par_num); break; +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) case ePAR_TYPE_F32: *(float32_t*) p_val = par_get_f32(par_num); break; +#endif case ePAR_TYPE_NUM_OF: default: @@ -1944,6 +1974,7 @@ int32_t par_get_i32(const par_num_t par_num) * @return value - Value of parameter */ //////////////////////////////////////////////////////////////////////////////// +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) float32_t par_get_f32(const par_num_t par_num) { // Check initialization @@ -1956,6 +1987,7 @@ float32_t par_get_f32(const par_num_t par_num) return PAR_GET_F32_PRIV( par_num ); } +#endif //////////////////////////////////////////////////////////////////////////////// /** @@ -1998,9 +2030,11 @@ par_status_t par_get_default(const par_num_t par_num, void * const p_val) *(int32_t*) p_val = (int32_t) par_get_config(par_num)->def.i32; break; +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) case ePAR_TYPE_F32: *(float32_t*) p_val = (float32_t) par_get_config(par_num)->def.f32; break; +#endif case ePAR_TYPE_NUM_OF: default: @@ -2041,8 +2075,10 @@ bool par_is_changed(const par_num_t par_num) case ePAR_TYPE_I32: return (bool) (par_get_i32(par_num) != par_get_config(par_num)->def.i32); +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) case ePAR_TYPE_F32: return (bool) (par_get_f32(par_num) != par_get_config(par_num)->def.f32); +#endif case ePAR_TYPE_NUM_OF: default: diff --git a/src/par.h b/src/par.h index 649a71e..29f0d91 100644 --- a/src/par.h +++ b/src/par.h @@ -189,14 +189,18 @@ par_status_t par_set_u16 (const par_num_t par_num, const uint16_t val par_status_t par_set_i16 (const par_num_t par_num, const int16_t val); par_status_t par_set_u32 (const par_num_t par_num, const uint32_t val); par_status_t par_set_i32 (const par_num_t par_num, const int32_t val); +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) par_status_t par_set_f32 (const par_num_t par_num, const float32_t val); +#endif par_status_t par_set_u8_fast (const par_num_t par_num, const uint8_t val); par_status_t par_set_i8_fast (const par_num_t par_num, const int8_t val); par_status_t par_set_u16_fast (const par_num_t par_num, const uint16_t val); par_status_t par_set_i16_fast (const par_num_t par_num, const int16_t val); par_status_t par_set_u32_fast (const par_num_t par_num, const uint32_t val); par_status_t par_set_i32_fast (const par_num_t par_num, const int32_t val); +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) par_status_t par_set_f32_fast (const par_num_t par_num, const float32_t val); +#endif par_status_t par_bitand_set_u8_fast (const par_num_t par_num, const uint8_t val); par_status_t par_bitand_set_u16_fast(const par_num_t par_num, const uint16_t val); par_status_t par_bitand_set_u32_fast(const par_num_t par_num, const uint32_t val); @@ -217,17 +221,28 @@ par_status_t par_has_changed (const par_num_t par_num, bool *const p_has_ * @param[in] par_num - Parameter number (enumeration) * @param[in] value - The new value of a parameter */ -#define PAR_SET(par_num, value) _Generic((value), \ - uint8_t: par_set_u8, \ - bool: par_set_u8, \ - uint16_t: par_set_u16, \ - uint32_t: par_set_u32, \ - int8_t: par_set_i8, \ - int16_t: par_set_i16, \ - int32_t: par_set_i32, \ - float32_t: par_set_f32, \ - default: par_set_f32 \ -)(par_num, value) +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) + #define PAR_SET(par_num, value) _Generic((value), \ + uint8_t: par_set_u8, \ + bool: par_set_u8, \ + uint16_t: par_set_u16, \ + uint32_t: par_set_u32, \ + int8_t: par_set_i8, \ + int16_t: par_set_i16, \ + int32_t: par_set_i32, \ + float32_t: par_set_f32 \ + )(par_num, value) +#else + #define PAR_SET(par_num, value) _Generic((value), \ + uint8_t: par_set_u8, \ + bool: par_set_u8, \ + uint16_t: par_set_u16, \ + uint32_t: par_set_u32, \ + int8_t: par_set_i8, \ + int16_t: par_set_i16, \ + int32_t: par_set_i32 \ + )(par_num, value) +#endif // Getting parameter value API (module must be first initialized before using those func) par_status_t par_get (const par_num_t par_num, void * const p_val); @@ -240,7 +255,9 @@ uint16_t par_get_u16 (const par_num_t par_num); int16_t par_get_i16 (const par_num_t par_num); uint32_t par_get_u32 (const par_num_t par_num); int32_t par_get_i32 (const par_num_t par_num); +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) float32_t par_get_f32 (const par_num_t par_num); +#endif par_status_t par_get_default (const par_num_t par_num, void * const p_val); bool par_is_changed (const par_num_t par_num); @@ -254,17 +271,28 @@ bool par_is_changed (const par_num_t par_num); * @param[in] dest - The destination variable where the value will be stored. * The type of this variable determines the getter used (e.g., uint8_t, float). */ -#define PAR_GET(par_num, dest) _Generic((dest), \ - uint8_t: dest = par_get_u8(par_num), \ - bool: dest = par_get_u8(par_num), \ - uint16_t: dest = par_get_u16(par_num), \ - uint32_t: dest = par_get_u32(par_num), \ - int8_t: dest = par_get_i8(par_num), \ - int16_t: dest = par_get_i16(par_num), \ - int32_t: dest = par_get_i32(par_num), \ - float32_t: dest = par_get_f32(par_num), \ - default: dest = par_get_f32(par_num) \ -) +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) + #define PAR_GET(par_num, dest) _Generic((dest), \ + uint8_t: dest = par_get_u8(par_num), \ + bool: dest = par_get_u8(par_num), \ + uint16_t: dest = par_get_u16(par_num), \ + uint32_t: dest = par_get_u32(par_num), \ + int8_t: dest = par_get_i8(par_num), \ + int16_t: dest = par_get_i16(par_num), \ + int32_t: dest = par_get_i32(par_num), \ + float32_t: dest = par_get_f32(par_num) \ + ) +#else + #define PAR_GET(par_num, dest) _Generic((dest), \ + uint8_t: dest = par_get_u8(par_num), \ + bool: dest = par_get_u8(par_num), \ + uint16_t: dest = par_get_u16(par_num), \ + uint32_t: dest = par_get_u32(par_num), \ + int8_t: dest = par_get_i8(par_num), \ + int16_t: dest = par_get_i16(par_num), \ + int32_t: dest = par_get_i32(par_num) \ + ) +#endif // Parameter configurations API (usage without module init pre-step) const par_cfg_t * par_get_config (const par_num_t par_num); diff --git a/src/par_cfg.h b/src/par_cfg.h index fe479d0..fbd92b3 100644 --- a/src/par_cfg.h +++ b/src/par_cfg.h @@ -284,6 +284,10 @@ #define PAR_CFG_LAYOUT_STATIC_INCLUDE "par_layout_static.h" #endif +#ifndef PAR_CFG_ENABLE_TYPE_F32 + #define PAR_CFG_ENABLE_TYPE_F32 ( 1 ) +#endif + /** * Enable/Disable parameter range metadata (min/max) */ diff --git a/src/par_def.c b/src/par_def.c index d0cec9d..d4ffa6a 100644 --- a/src/par_def.c +++ b/src/par_def.c @@ -67,7 +67,11 @@ * in static assertions as integer constant expressions, and may emit * "variably modified '_static_assert_...' at file scope". */ -#define PAR_CHECK_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) + #define PAR_CHECK_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#else + #define PAR_CHECK_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_STATIC_ASSERT(enum_##_f32_type_is_disabled__remove_PAR_ITEM_F32, 0) +#endif /** * Dispatch map for compile-time checks. diff --git a/src/par_def.h b/src/par_def.h index 3568fb4..b1f30eb 100644 --- a/src/par_def.h +++ b/src/par_def.h @@ -9,6 +9,10 @@ *@email 1425075683@qq.com *@date 29.01.2026 *@version V3.0.1 +*@note Do not gate F32 in this header. +* par_def.h stays config-independent to avoid par_cfg.h include-order issues +* and to keep enum definitions stable. Disabled F32 table entries are +* rejected in par_def.c at compile time. */ //////////////////////////////////////////////////////////////////////////////// /** @@ -20,6 +24,11 @@ #ifndef _PAR_DEF_CORE_H_ #define _PAR_DEF_CORE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + //////////////////////////////////////////////////////////////////////////////// // Includes //////////////////////////////////////////////////////////////////////////////// @@ -31,14 +40,13 @@ //////////////////////////////////////////////////////////////////////////////// typedef struct par_cfg_s par_cfg_t; -#ifdef __cplusplus -extern "C" { -#endif - /** * List of device parameters * * @note Must be started with 0! + * @note Enum expansion is intentionally configuration-independent: + * PAR_ITEM_F32 always maps to PAR_ITEM_ENUM. + * F32 enable/disable fail-fast is enforced in par_def.c. */ #define PAR_ITEM_ENUM(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) enum_, enum diff --git a/src/par_layout.c b/src/par_layout.c index 6fc1182..2749bc9 100644 --- a/src/par_layout.c +++ b/src/par_layout.c @@ -91,7 +91,9 @@ void par_layout_init(void) case ePAR_TYPE_U32: case ePAR_TYPE_I32: +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) case ePAR_TYPE_F32: +#endif gs_runtime_offset[par_it] = scan_count.count32; scan_count.count32++; break; From 8a730af1b6cf89aa089be1096a47ceb51797c8c3 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Thu, 19 Mar 2026 15:49:23 +0800 Subject: [PATCH 12/36] feat(core): Make validation and change callbacks configurable --- README.md | 5 +- docs/api-reference.md | 27 ++++- docs/architecture.md | 16 ++- docs/getting-started.md | 47 +++++++- src/par.c | 241 ++++++++++++++++++++++++---------------- src/par.h | 4 + src/par_cfg.h | 14 +++ 7 files changed, 244 insertions(+), 110 deletions(-) diff --git a/README.md b/README.md index 188e666..5ae0b27 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ It is designed for projects that need a clean way to: - **Single source of truth** through `par_table.def` - **Typed APIs** for `U8`, `I8`, `U16`, `I16`, `U32`, `I32`, and, when enabled, `F32` - **Optional metadata** such as name, unit, description, access, ID, and persistence flags -- **Validation pipeline** with compile-time checks for integer ranges and runtime checks for dynamic rules +- **Validation pipeline** with compile-time checks for integer ranges and optional runtime hooks for dynamic rules - **Static live-value storage** grouped by width instead of heap allocation - **Fast external lookup by ID** through a runtime hash map - **Optional NVM integration** for persistent parameters @@ -117,8 +117,9 @@ This repository contains the reusable module core and templates. A real integrat - `par_cfg.h` includes `par_cfg_port.h` unconditionally, so your build must provide that header. - `PAR_CFG_ENABLE_TYPE_F32` controls whether floating-point parameter support, related typed APIs, and `_Generic` float dispatch are compiled in. +- `PAR_CFG_ENABLE_RUNTIME_VALIDATION` and `PAR_CFG_ENABLE_CHANGE_CALLBACK` control whether normal setters include runtime validation callbacks and on-change callbacks. - The module separates **internal parameter enumeration** (`par_num_t`) from **external parameter IDs** (`id`). -- Fast setter APIs skip part of the safety and observability path, so they should be reserved for tightly controlled hot paths. +- Fast setter APIs skip part of the safety and observability path, including runtime validation callbacks and on-change callbacks, so they should be reserved for tightly controlled hot paths. - NVM support is optional, but when enabled it depends on the external NVM module and on ID and persistence metadata being enabled. - `par_init()` applies startup default values directly to live storage. Integer default values from `par_table.def` are compiled into the shared width-based storage arrays, while `F32` default values are applied after layout offsets are available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. Because this startup initialization does not go through the public setter path, it does not invoke runtime validation or on-change callbacks. diff --git a/docs/api-reference.md b/docs/api-reference.md index c0b853f..3e2c447 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -10,6 +10,8 @@ This document groups the public API from `src/par.h` by responsibility. - ID-based APIs depend on `PAR_CFG_ENABLE_ID = 1`. - NVM APIs depend on `PAR_CFG_NVM_EN = 1`. - `F32` typed APIs and `_Generic` float dispatch depend on `PAR_CFG_ENABLE_TYPE_F32 = 1`. +- Validation registration APIs depend on `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 1`. +- On-change registration APIs depend on `PAR_CFG_ENABLE_CHANGE_CALLBACK = 1`. ## Compile-time availability notes @@ -22,6 +24,12 @@ The module conditionally compiles parts of the API based on configuration. - `par_get_f32()` - `par_set_f32_fast()` - `float32_t` dispatch through `PAR_SET` and `PAR_GET` +- `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 1` enables: + - `par_register_validation()` + - runtime validation callbacks in normal setter paths +- `PAR_CFG_ENABLE_CHANGE_CALLBACK = 1` enables: + - `par_register_on_change_cb()` + - on-change callbacks in normal setter paths ## Lifecycle @@ -61,6 +69,8 @@ These are relevant only when mutex support is enabled in the integration. | `par_set_i32()` | Set an `I32` parameter. | | `par_set_f32()` | Set an `F32` parameter. Available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. | +Normal typed setters may include runtime validation callbacks and on-change callbacks as part of the normal runtime path. Those hook paths are present only when the matching configuration options are enabled. + ## Fast setters | Function | Description | @@ -102,7 +112,7 @@ They are different from startup initialization: - `par_init()` applies the default values defined in `par_table.def` directly to live storage - `par_set_to_default()` and `par_set_all_to_default()` still use the normal runtime value path -That distinction matters if your application depends on validation callbacks, on-change callbacks, or other setter-side effects. +That distinction matters if your application depends on validation callbacks, on-change callbacks, or other setter-side effects, because those runtime hooks apply only in the normal setter path and only when the matching callback features are enabled. ## Generic getters @@ -158,10 +168,14 @@ Available only when `PAR_CFG_NVM_EN = 1`. These APIs register behavior per parameter. +`par_register_on_change_cb()` is available only when `PAR_CFG_ENABLE_CHANGE_CALLBACK = 1`. + +`par_register_validation()` is available only when `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 1`. + | Function | Description | | --- | --- | -| `par_register_on_change_cb(par_num, cb)` | Register a change callback for one parameter. | -| `par_register_validation(par_num, validation)` | Register a validation callback for one parameter. | +| `par_register_on_change_cb(par_num, cb)` | Register a change callback for one parameter. Available only when `PAR_CFG_ENABLE_CHANGE_CALLBACK = 1`. | +| `par_register_validation(par_num, validation)` | Register a validation callback for one parameter. Available only when `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 1`. | Example: @@ -184,12 +198,17 @@ static bool validate_mode(const par_num_t par_num, const par_type_t val) static void app_hooks_init(void) { +#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) par_register_on_change_cb(ePAR_MODE, on_mode_change); +#endif + +#if ( 1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION ) par_register_validation(ePAR_MODE, validate_mode); +#endif } ``` -These hooks affect runtime writes and explicit reset operations. They are not invoked during the internal startup default initialization performed by `par_init()`. +When enabled, these hooks affect runtime writes and explicit reset operations that use the normal setter path. They are not invoked during the internal startup default initialization performed by `par_init()`. ## Debug helpers diff --git a/docs/architecture.md b/docs/architecture.md index 711f610..dac99a5 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -6,11 +6,13 @@ This document explains how the module is structured internally and how the major The module separates four concerns: -1. **Parameter definition** through `par_table.def` +1. **Generated parameter definition** through `par_table.def`, generated enums, and generated config structs 2. **Core runtime access** through `par.c` and `par.h` 3. **Layout and storage** through `par_layout.*` 4. **Optional platform and NVM integration** through `par_if.*`, `par_atomic.h`, and `par_nvm.*` +Runtime validation hooks and on-change hooks are kept separate from the core `par_cfg_t` metadata table and can be compiled out independently. + ```mermaid flowchart TD A[par_table.def] --> B[par_def.h / par_def.c] @@ -187,9 +189,9 @@ Runtime validation is used for checks that are better handled dynamically, inclu - `name != NULL` when name metadata is enabled - `desc != NULL` when description metadata is enabled - description policy checks through `par_port_is_desc_valid()` when enabled -- per-parameter application validation callbacks +- per-parameter application validation callbacks when `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 1` -This split keeps integer configuration errors out of the firmware image while still allowing flexible runtime policies. +This split keeps integer configuration errors out of the firmware image while still allowing flexible runtime policies. Runtime validation callbacks can be compiled out independently from the rest of the metadata model. ## ID lookup path @@ -218,16 +220,20 @@ This keeps runtime lookup simple and deterministic, but it also means a conflict ## Normal path vs fast path -The module exposes both normal setters and fast setters. +Depending on build-time configuration, the normal path can include runtime validation callbacks and on-change callbacks. ### Normal setters Normal setters are the default path. They are intended for ordinary application code where correctness and observability matter more than shaving off a few instructions. +Depending on build-time configuration, the normal path can include runtime validation callbacks and on-change callbacks. + ### Fast setters Fast setters are specialized APIs for controlled hot paths. They reduce overhead, but they should only be used when the surrounding code already guarantees the assumptions that the full path would normally check. +Fast setters do not execute runtime validation callbacks or on-change callbacks. + ## Optional NVM persistence When `PAR_CFG_NVM_EN = 1`, the module can persist selected parameters to NVM. @@ -246,7 +252,7 @@ Implemented under `src/`: - parameter storage - parameter metadata access -- validation and callbacks +- validation and optional runtime callbacks - layout handling - ID lookup - optional NVM support diff --git a/docs/getting-started.md b/docs/getting-started.md index 65ff3f5..7302544 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -148,6 +148,30 @@ Use **script layout** when your build or tooling already generates fixed layout The default backend is C11 atomics. Switch to the port backend only when the default is not a good fit for the target. +### Runtime hooks + +Use these options to control whether normal setters include runtime validation and on-change notifications: + +```c +#define PAR_CFG_ENABLE_RUNTIME_VALIDATION ( 1 ) +#define PAR_CFG_ENABLE_CHANGE_CALLBACK ( 1 ) +``` + +`PAR_CFG_ENABLE_RUNTIME_VALIDATION` controls whether normal setters call per-parameter validation callbacks registered through `par_register_validation()`. + +`PAR_CFG_ENABLE_CHANGE_CALLBACK` controls whether normal setters raise per-parameter change callbacks registered through `par_register_on_change_cb()`. + +These options are independent: + +- set both to `1` to keep the full normal setter behavior +- set either one to `0` to compile out that part of the runtime hook path +- fast setters still skip these hooks regardless of configuration + +When `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 0`, `par_register_validation()` is not available. + +When `PAR_CFG_ENABLE_CHANGE_CALLBACK = 0`, `par_register_on_change_cb()` is not available. + + ### F32 type support Use `PAR_CFG_ENABLE_TYPE_F32` to control whether `F32` parameters are compiled into the module. @@ -222,9 +246,16 @@ float32_t readback = 0.0f; The registration APIs work per parameter and take the parameter number directly. +`par_register_on_change_cb()` is available only when `PAR_CFG_ENABLE_CHANGE_CALLBACK = 1`. + +`par_register_validation()` is available only when `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 1`. + ### On-change callback +Use this only when `PAR_CFG_ENABLE_CHANGE_CALLBACK = 1`. + ```c +#if (1 == PAR_CFG_ENABLE_CHANGE_CALLBACK) static void on_mode_changed( const par_num_t par_num, const par_type_t new_val, @@ -238,11 +269,15 @@ static void app_register_callbacks(void) { par_register_on_change_cb(ePAR_MODE, on_mode_changed); } +#endif ``` ### Validation callback +Use this only when `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 1`. + ```c +#if (1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION) static bool validate_target_temp(const par_num_t par_num, const par_type_t val) { (void)par_num; @@ -254,6 +289,7 @@ static void app_register_validation(void) { par_register_validation(ePAR_TARGET_TEMP, validate_target_temp); } +#endif ``` ## Normal vs fast setters @@ -262,7 +298,12 @@ Use the normal setters unless you have a measured reason not to. ### Normal setters -Normal setters go through the full parameter path, including checks and side effects such as validation and change tracking. +Normal setters go through the normal runtime path. + +Depending on build-time configuration, that path can include runtime validation callbacks and on-change callbacks: + +- runtime validation callbacks are used only when `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 1` +- on-change callbacks are raised only when `PAR_CFG_ENABLE_CHANGE_CALLBACK = 1` ```c (void)par_set_f32(ePAR_TARGET_TEMP, 25.0f); @@ -270,8 +311,7 @@ Normal setters go through the full parameter path, including checks and side eff ### Fast setters -Fast setters are meant for controlled hot paths where you accept reduced safety or observability in exchange for lower overhead. - +Fast setters are meant for controlled hot paths where you accept reduced safety or observability in exchange for lower overhead. They do not run runtime validation callbacks or on-change callbacks. ```c (void)par_set_u16_fast(ePAR_PWM_LIMIT, 1200U); ``` @@ -306,6 +346,7 @@ uint32_t baud = 115200U; - Assuming the repository already ships a ready-to-build `par_table.def` for your project - Disabling `PAR_CFG_ENABLE_TYPE_F32` while keeping `PAR_ITEM_F32(...)` entries in `par_table.def` - Assuming `PAR_SET` and `PAR_GET` still accept `float32_t` after F32 support is disabled +- Registering validation or change callbacks without enabling the matching configuration macro ### Compile-time error example when F32 support is disabled diff --git a/src/par.c b/src/par.c index 3bc96b3..99f58db 100644 --- a/src/par.c +++ b/src/par.c @@ -97,12 +97,20 @@ static bool gb_is_init = false; /** * Parameter callback table. + * + * @note Keep runtime hooks separate from par_cfg_t metadata table. */ +#if (( 1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION ) || ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK )) static struct { - pf_par_validation_t validation; /**< Validation callback function (or NULL). */ +#if ( 1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION ) + pf_par_validation_t validation; /**< Validation callback function (or NULL). */ +#endif +#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) pf_par_on_change_cb_t on_change; /**< On change callback function (or NULL). */ +#endif } g_par_cb_table[ePAR_NUM_OF]; +#endif /** * ID hash map entry. @@ -295,10 +303,10 @@ static par_atomic_f32_t * gpf32_par_value = (par_atomic_f32_t *)gs_par_u32_st static inline uint32_t par_hash_id (const uint16_t id); static par_status_t par_build_and_validate_id_map (const par_cfg_t * const p_par_cfg); #endif -static par_status_t par_check_table_validity (const par_cfg_t * const p_par_cfg); #if ( 1 == PAR_CFG_NVM_EN ) static bool par_is_value_changed (const par_num_t par_num, const void * p_val); #endif /* ( 1 == PAR_CFG_NVM_EN ) */ +static par_status_t par_check_table_validity (const par_cfg_t * const p_par_cfg); //////////////////////////////////////////////////////////////////////////////// // Functions @@ -489,62 +497,6 @@ static par_status_t par_check_table_validity(const par_cfg_t * const p_par_cfg) return status; } -#if ( 1 == PAR_CFG_NVM_EN ) -//////////////////////////////////////////////////////////////////////////////// -/** -* Is parameter value changed -* -* @param[in] par_num - Parameter number -* @param[in] p_val - Parameter value -* @return True if parameter value is different from current -*/ -//////////////////////////////////////////////////////////////////////////////// -static bool par_is_value_changed(const par_num_t par_num, const void * p_val) -{ - bool value_changed = false; - - switch ( par_get_type(par_num)) - { - case ePAR_TYPE_U8: - value_changed = (par_get_u8(par_num) != *(uint8_t*)p_val); - break; - - case ePAR_TYPE_I8: - value_changed = (par_get_i8(par_num) != *(int8_t*)p_val); - break; - - case ePAR_TYPE_U16: - value_changed = (par_get_u16(par_num) != *(uint16_t*)p_val); - break; - - case ePAR_TYPE_I16: - value_changed = (par_get_i16(par_num) != *(int16_t*)p_val); - break; - - case ePAR_TYPE_U32: - value_changed = (par_get_u32(par_num) != *(uint32_t*)p_val); - break; - - case ePAR_TYPE_I32: - value_changed = (par_get_i32(par_num) != *(int32_t*)p_val); - break; - -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) - case ePAR_TYPE_F32: - value_changed = (par_get_f32(par_num) != *(float32_t*)p_val); - break; -#endif - - case ePAR_TYPE_NUM_OF: - default: - PAR_ASSERT( 0 ); - break; - } - - return value_changed; -} -#endif /* ( 1 == PAR_CFG_NVM_EN ) */ - //////////////////////////////////////////////////////////////////////////////// /** * @} @@ -795,29 +747,35 @@ par_status_t par_set_u8(const par_num_t par_num, const uint8_t val) PAR_ASSERT( ePAR_TYPE_U8 == par_get_type(par_num)); if( ePAR_TYPE_U8 != par_get_type(par_num)) return ePAR_ERROR_TYPE; - pf_par_validation_t validation = g_par_cb_table[par_num].validation; - pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; // Get mutex if ( ePAR_OK == par_acquire_mutex(par_num)) { +#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) const par_type_t old_val = {.u8 = PAR_GET_U8_PRIV( par_num )}; - +#endif + +#if ( 1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION ) // Validated parameter value - if ((validation == NULL) || validation(par_num, (par_type_t){.u8 = val})) + pf_par_validation_t validation = g_par_cb_table[par_num].validation; + if ((validation != NULL) && !validation(par_num, (par_type_t){.u8 = val})) { - status = par_set_u8_fast( par_num, val ); + status = ePAR_ERROR_VALUE; } else +#endif { - status = ePAR_ERROR_VALUE; + status = par_set_u8_fast(par_num, val); } // Raise on change callback +#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) + pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; const par_type_t new_val = {.u8 = PAR_GET_U8_PRIV( par_num )}; if ((on_change != NULL) && (new_val.u8 != old_val.u8)) { on_change(par_num, new_val, old_val); } +#endif par_release_mutex(par_num); } @@ -850,29 +808,35 @@ par_status_t par_set_i8(const par_num_t par_num, const int8_t val) PAR_ASSERT( ePAR_TYPE_I8 == par_get_type(par_num)); if( ePAR_TYPE_I8 != par_get_type(par_num)) return ePAR_ERROR_TYPE; - pf_par_validation_t validation = g_par_cb_table[par_num].validation; - pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; // Get mutex if ( ePAR_OK == par_acquire_mutex(par_num)) { +#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) const par_type_t old_val = {.i8 = PAR_GET_I8_PRIV( par_num )}; +#endif +#if ( 1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION ) // Validated parameter value - if ((validation == NULL) || validation(par_num, (par_type_t){.i8 = val})) + pf_par_validation_t validation = g_par_cb_table[par_num].validation; + if ((validation != NULL) && !validation(par_num, (par_type_t){.i8 = val})) { - status = par_set_i8_fast( par_num, val ); + status = ePAR_ERROR_VALUE; } else +#endif { - status = ePAR_ERROR_VALUE; + status = par_set_i8_fast(par_num, val); } +#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) // Raise on change callback + pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; const par_type_t new_val = {.i8 = PAR_GET_I8_PRIV( par_num )}; if ((on_change != NULL) && (new_val.i8 != old_val.i8)) { on_change(par_num, new_val, old_val); } +#endif par_release_mutex(par_num); } @@ -905,29 +869,35 @@ par_status_t par_set_u16(const par_num_t par_num, const uint16_t val) PAR_ASSERT( ePAR_TYPE_U16 == par_get_type(par_num)); if( ePAR_TYPE_U16 != par_get_type(par_num)) return ePAR_ERROR_TYPE; - pf_par_validation_t validation = g_par_cb_table[par_num].validation; - pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; // Get mutex if ( ePAR_OK == par_acquire_mutex(par_num)) { +#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) const par_type_t old_val = {.u16 = PAR_GET_U16_PRIV( par_num )}; +#endif +#if ( 1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION ) // Validated parameter value - if ((validation == NULL) || validation(par_num, (par_type_t){.u16 = val})) + pf_par_validation_t validation = g_par_cb_table[par_num].validation; + if ((validation != NULL) && !validation(par_num, (par_type_t){.u16 = val})) { - status = par_set_u16_fast( par_num, val ); + status = ePAR_ERROR_VALUE; } else +#endif { - status = ePAR_ERROR_VALUE; + status = par_set_u16_fast(par_num, val); } +#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) // Raise on change callback + pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; const par_type_t new_val = {.u16 = PAR_GET_U16_PRIV( par_num )}; if ((on_change != NULL) && (new_val.u16 != old_val.u16)) { on_change(par_num, new_val, old_val); } +#endif par_release_mutex(par_num); } @@ -960,29 +930,35 @@ par_status_t par_set_i16(const par_num_t par_num, const int16_t val) PAR_ASSERT( ePAR_TYPE_I16 == par_get_type(par_num)); if( ePAR_TYPE_I16 != par_get_type(par_num)) return ePAR_ERROR_TYPE; - pf_par_validation_t validation = g_par_cb_table[par_num].validation; - pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; // Get mutex if ( ePAR_OK == par_acquire_mutex(par_num)) { +#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) const par_type_t old_val = {.i16 = PAR_GET_I16_PRIV( par_num )}; +#endif +#if ( 1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION ) // Validated parameter value - if ((validation == NULL) || validation(par_num, (par_type_t){.i16 = val})) + pf_par_validation_t validation = g_par_cb_table[par_num].validation; + if ((validation != NULL) && !validation(par_num, (par_type_t){.i16 = val})) { - status = par_set_i16_fast( par_num, val ); + status = ePAR_ERROR_VALUE; } else +#endif { - status = ePAR_ERROR_VALUE; + status = par_set_i16_fast(par_num, val); } +#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) // Raise on change callback + pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; const par_type_t new_val = {.i16 = PAR_GET_I16_PRIV( par_num )}; if ((on_change != NULL) && (new_val.i16 != old_val.i16)) { on_change(par_num, new_val, old_val); } +#endif par_release_mutex(par_num); } @@ -1015,29 +991,35 @@ par_status_t par_set_u32(const par_num_t par_num, const uint32_t val) PAR_ASSERT( ePAR_TYPE_U32 == par_get_type(par_num)); if( ePAR_TYPE_U32 != par_get_type(par_num)) return ePAR_ERROR_TYPE; - pf_par_validation_t validation = g_par_cb_table[par_num].validation; - pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; // Get mutex if ( ePAR_OK == par_acquire_mutex(par_num)) { +#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) const par_type_t old_val = {.u32 = PAR_GET_U32_PRIV( par_num )}; +#endif +#if ( 1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION ) // Validated parameter value - if ((validation == NULL) || validation(par_num, (par_type_t){.u32 = val})) + pf_par_validation_t validation = g_par_cb_table[par_num].validation; + if ((validation != NULL) && !validation(par_num, (par_type_t){.u32 = val})) { - status = par_set_u32_fast( par_num, val ); + status = ePAR_ERROR_VALUE; } else +#endif { - status = ePAR_ERROR_VALUE; + status = par_set_u32_fast(par_num, val); } +#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) // Raise on change callback + pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; const par_type_t new_val = {.u32 = PAR_GET_U32_PRIV( par_num )}; if ((on_change != NULL) && (new_val.u32 != old_val.u32)) { on_change(par_num, new_val, old_val); } +#endif par_release_mutex(par_num); } @@ -1070,29 +1052,35 @@ par_status_t par_set_i32(const par_num_t par_num, const int32_t val) PAR_ASSERT( ePAR_TYPE_I32 == par_get_type(par_num)); if( ePAR_TYPE_I32 != par_get_type(par_num)) return ePAR_ERROR_TYPE; - pf_par_validation_t validation = g_par_cb_table[par_num].validation; - pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; // Get mutex if ( ePAR_OK == par_acquire_mutex(par_num)) { +#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) const par_type_t old_val = {.i32 = PAR_GET_I32_PRIV( par_num )}; +#endif +#if ( 1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION ) // Validated parameter value - if ((validation == NULL) || validation(par_num, (par_type_t){.i32 = val})) + pf_par_validation_t validation = g_par_cb_table[par_num].validation; + if ((validation != NULL) && !validation(par_num, (par_type_t){.i32 = val})) { - status = par_set_i32_fast( par_num, val ); + status = ePAR_ERROR_VALUE; } else +#endif { - status = ePAR_ERROR_VALUE; + status = par_set_i32_fast(par_num, val); } +#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) // Raise on change callback + pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; const par_type_t new_val = {.i32 = PAR_GET_I32_PRIV( par_num )}; if ((on_change != NULL) && (new_val.i32 != old_val.i32)) { on_change(par_num, new_val, old_val); } +#endif par_release_mutex(par_num); } @@ -1126,29 +1114,35 @@ par_status_t par_set_f32(const par_num_t par_num, const float32_t val) PAR_ASSERT( ePAR_TYPE_F32 == par_get_type(par_num)); if( ePAR_TYPE_F32 != par_get_type(par_num)) return ePAR_ERROR_TYPE; - pf_par_validation_t validation = g_par_cb_table[par_num].validation; - pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; // Get mutex if ( ePAR_OK == par_acquire_mutex(par_num)) { +#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) const par_type_t old_val = {.f32 = PAR_GET_F32_PRIV( par_num )}; +#endif +#if ( 1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION ) // Validated parameter value - if ((validation == NULL) || validation(par_num, (par_type_t){.f32 = val})) + pf_par_validation_t validation = g_par_cb_table[par_num].validation; + if ((validation != NULL) && !validation(par_num, (par_type_t){.f32 = val})) { - status = par_set_f32_fast( par_num, val ); + status = ePAR_ERROR_VALUE; } else +#endif { - status = ePAR_ERROR_VALUE; + status = par_set_f32_fast(par_num, val); } +#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) // Raise on change callback + pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; const par_type_t new_val = {.f32 = PAR_GET_F32_PRIV( par_num )}; if ((on_change != NULL) && (new_val.f32 != old_val.f32)) { on_change(par_num, new_val, old_val); } +#endif par_release_mutex(par_num); } @@ -2288,7 +2282,6 @@ par_status_t par_get_num_by_id(const uint16_t id, par_num_t * const p_par_num) return ePAR_ERROR; } -#endif //////////////////////////////////////////////////////////////////////////////// /** @@ -2299,7 +2292,6 @@ par_status_t par_get_num_by_id(const uint16_t id, par_num_t * const p_par_num) * @return status - Status of operation */ //////////////////////////////////////////////////////////////////////////////// -#if ( 1 == PAR_CFG_ENABLE_ID ) par_status_t par_get_id_by_num(const par_num_t par_num, uint16_t * const p_id) { if ( NULL != p_id ) @@ -2318,6 +2310,59 @@ par_status_t par_get_id_by_num(const par_num_t par_num, uint16_t * const p_id) #endif #if ( 1 == PAR_CFG_NVM_EN ) + //////////////////////////////////////////////////////////////////////////////// + /** + * Is parameter value changed + * + * @param[in] par_num - Parameter number + * @param[in] p_val - Parameter value + * @return True if parameter value is different from current + */ + //////////////////////////////////////////////////////////////////////////////// + static bool par_is_value_changed(const par_num_t par_num, const void * p_val) + { + bool value_changed = false; + + switch ( par_get_type(par_num)) + { + case ePAR_TYPE_U8: + value_changed = (par_get_u8(par_num) != *(uint8_t*)p_val); + break; + + case ePAR_TYPE_I8: + value_changed = (par_get_i8(par_num) != *(int8_t*)p_val); + break; + + case ePAR_TYPE_U16: + value_changed = (par_get_u16(par_num) != *(uint16_t*)p_val); + break; + + case ePAR_TYPE_I16: + value_changed = (par_get_i16(par_num) != *(int16_t*)p_val); + break; + + case ePAR_TYPE_U32: + value_changed = (par_get_u32(par_num) != *(uint32_t*)p_val); + break; + + case ePAR_TYPE_I32: + value_changed = (par_get_i32(par_num) != *(int32_t*)p_val); + break; + + #if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) + case ePAR_TYPE_F32: + value_changed = (par_get_f32(par_num) != *(float32_t*)p_val); + break; + #endif + + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT( 0 ); + break; + } + + return value_changed; + } //////////////////////////////////////////////////////////////////////////////// /** * Set parameter value and save to NVM if value changed @@ -2467,12 +2512,14 @@ par_status_t par_get_id_by_num(const par_num_t par_num, uint16_t * const p_id) * @return void */ //////////////////////////////////////////////////////////////////////////////// +#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) void par_register_on_change_cb(const par_num_t par_num, const pf_par_on_change_cb_t cb) { PAR_ASSERT( par_num < ePAR_NUM_OF ); g_par_cb_table[par_num].on_change = cb; } +#endif //////////////////////////////////////////////////////////////////////////////// /** @@ -2483,12 +2530,14 @@ void par_register_on_change_cb(const par_num_t par_num, const pf_par_on_change_c * @return void */ //////////////////////////////////////////////////////////////////////////////// +#if ( 1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION ) void par_register_validation(const par_num_t par_num, const pf_par_validation_t validation) { PAR_ASSERT( par_num < ePAR_NUM_OF ); g_par_cb_table[par_num].validation = validation; } +#endif #if ( PAR_CFG_DEBUG_EN ) diff --git a/src/par.h b/src/par.h index 29f0d91..08d7073 100644 --- a/src/par.h +++ b/src/par.h @@ -332,8 +332,12 @@ par_status_t par_get_id_by_num (const par_num_t par_num, uint16_t * con #endif // Registration API +#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) void par_register_on_change_cb (const par_num_t par_num, const pf_par_on_change_cb_t cb); +#endif +#if ( 1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION ) void par_register_validation (const par_num_t par_num, const pf_par_validation_t validation); +#endif #if ( 1 == PAR_CFG_ENABLE_DESC ) && ( 1 == PAR_CFG_ENABLE_DESC_CHECK ) PAR_PORT_WEAK bool par_port_is_desc_valid(const char * const p_desc); diff --git a/src/par_cfg.h b/src/par_cfg.h index fbd92b3..594826b 100644 --- a/src/par_cfg.h +++ b/src/par_cfg.h @@ -288,6 +288,20 @@ #define PAR_CFG_ENABLE_TYPE_F32 ( 1 ) #endif +/** + * Enable/Disable runtime validation callbacks in normal setters + */ +#ifndef PAR_CFG_ENABLE_RUNTIME_VALIDATION + #define PAR_CFG_ENABLE_RUNTIME_VALIDATION ( 1 ) +#endif + +/** + * Enable/Disable on-change callbacks in normal setters + */ +#ifndef PAR_CFG_ENABLE_CHANGE_CALLBACK + #define PAR_CFG_ENABLE_CHANGE_CALLBACK ( 1 ) +#endif + /** * Enable/Disable parameter range metadata (min/max) */ From 41c37716447199c9544a0c51758ee0d812025143 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Fri, 20 Mar 2026 09:23:51 +0800 Subject: [PATCH 13/36] refactor(parameters): Replace min/max with range in par_cfg_t --- docs/architecture.md | 6 +++--- src/par.c | 9 ++++++--- src/par.h | 3 +-- src/par_def.c | 14 +++++++------- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index dac99a5..72177e3 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -171,9 +171,9 @@ Compile-time validation is used for integer parameter types: Typical checks include: -- `min <= max` -- `def >= min` -- `def <= max` +- `range.min <= range.max` +- `def >= range.min` +- `def <= range.max` These checks are generated from `par_table.def`, so invalid integer configurations fail at build time. diff --git a/src/par.c b/src/par.c index 99f58db..deb3e2b 100644 --- a/src/par.c +++ b/src/par.c @@ -460,7 +460,11 @@ static par_status_t par_check_table_validity(const par_cfg_t * const p_par_cfg) * typedef-based static asserts may be treated as non-constant * expressions and trigger file-scope VLA warnings. */ - PAR_ASSERT(( ePAR_TYPE_F32 == p_par_cfg[i].type ) ? ((( p_par_cfg[i].min.f32 < p_par_cfg[i].max.f32 ) && ( p_par_cfg[i].def.f32 <= p_par_cfg[i].max.f32 )) && ( p_par_cfg[i].min.f32 <= p_par_cfg[i].def.f32 )) : ( 1 )); + PAR_ASSERT(( ePAR_TYPE_F32 == p_par_cfg[i].type ) ? + ((( p_par_cfg[i].range.min.f32 < p_par_cfg[i].range.max.f32 ) && + ( p_par_cfg[i].def.f32 <= p_par_cfg[i].range.max.f32 )) && + ( p_par_cfg[i].range.min.f32 <= p_par_cfg[i].def.f32 )) + : ( 1 )); #endif #if ( 1 == PAR_CFG_ENABLE_NAME ) @@ -2140,8 +2144,7 @@ par_range_t par_get_range(const par_num_t par_num) if ( NULL != par_cfg ) { - range.min = par_cfg->min; - range.max = par_cfg->max; + return par_cfg->range; } return range; diff --git a/src/par.h b/src/par.h index 08d7073..ac155f6 100644 --- a/src/par.h +++ b/src/par.h @@ -136,8 +136,7 @@ typedef struct par_cfg_s const char * name; /** Date: Sat, 21 Mar 2026 10:21:26 +0800 Subject: [PATCH 14/36] refactor(API): Replace generic parameter macros with typed wrappers --- README.md | 8 ++--- docs/api-reference.md | 38 +++++++++++++++++----- docs/getting-started.md | 12 +++---- src/par.h | 71 ++++++++++------------------------------- 4 files changed, 56 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index 5ae0b27..6159b40 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ It is designed for projects that need a clean way to: 3. Provide `port/par_cfg_port.h` in your include path. 4. Optionally provide `port/par_if_port.c` and `port/par_atomic_port.h` when your platform needs them. 5. Call `par_init()` before using runtime APIs. -6. Use `PAR_SET`, `PAR_GET`, or the typed `par_set_*` / `par_get_*` APIs in application code. +6. Use the typed macro wrappers such as `PAR_SET_U16` and `PAR_GET_U16`, or the typed `par_set_*` / `par_get_*` APIs in application code. A minimal example: @@ -47,10 +47,10 @@ static void app_init(void) /* Handle initialization error */ } - PAR_SET(ePAR_CH1_REF_VAL, (float32_t)25.0f); + PAR_SET_F32(ePAR_CH1_REF_VAL, (float32_t)25.0f); float32_t ref_val = 0.0f; - PAR_GET(ePAR_CH1_REF_VAL, ref_val); + PAR_GET_F32(ePAR_CH1_REF_VAL, ref_val); } ``` @@ -116,7 +116,7 @@ This repository contains the reusable module core and templates. A real integrat ## Key integration notes - `par_cfg.h` includes `par_cfg_port.h` unconditionally, so your build must provide that header. -- `PAR_CFG_ENABLE_TYPE_F32` controls whether floating-point parameter support, related typed APIs, and `_Generic` float dispatch are compiled in. +- `PAR_CFG_ENABLE_TYPE_F32` controls whether floating-point parameter support, related typed APIs, and the `PAR_SET_F32` / `PAR_GET_F32` macro wrappers are compiled in. - `PAR_CFG_ENABLE_RUNTIME_VALIDATION` and `PAR_CFG_ENABLE_CHANGE_CALLBACK` control whether normal setters include runtime validation callbacks and on-change callbacks. - The module separates **internal parameter enumeration** (`par_num_t`) from **external parameter IDs** (`id`). - Fast setter APIs skip part of the safety and observability path, including runtime validation callbacks and on-change callbacks, so they should be reserved for tightly controlled hot paths. diff --git a/docs/api-reference.md b/docs/api-reference.md index 3e2c447..8508d64 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -9,7 +9,7 @@ This document groups the public API from `src/par.h` by responsibility. - `par_num_t` is the internal parameter index. - ID-based APIs depend on `PAR_CFG_ENABLE_ID = 1`. - NVM APIs depend on `PAR_CFG_NVM_EN = 1`. -- `F32` typed APIs and `_Generic` float dispatch depend on `PAR_CFG_ENABLE_TYPE_F32 = 1`. +- `F32` typed APIs and the `PAR_SET_F32` / `PAR_GET_F32` macro wrappers depend on `PAR_CFG_ENABLE_TYPE_F32 = 1`. - Validation registration APIs depend on `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 1`. - On-change registration APIs depend on `PAR_CFG_ENABLE_CHANGE_CALLBACK = 1`. @@ -23,7 +23,8 @@ The module conditionally compiles parts of the API based on configuration. - `par_set_f32()` - `par_get_f32()` - `par_set_f32_fast()` - - `float32_t` dispatch through `PAR_SET` and `PAR_GET` + - `PAR_SET_F32` + - `PAR_GET_F32` - `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 1` enables: - `par_register_validation()` - runtime validation callbacks in normal setter paths @@ -48,16 +49,26 @@ The module conditionally compiles parts of the API based on configuration. These are relevant only when mutex support is enabled in the integration. -## Generic setters +## Pointer-based setters | Function | Description | | --- | --- | | `par_set(par_num, p_val)` | Set a parameter from a typed pointer. | | `par_set_by_id(id, p_val)` | Set a parameter using its external ID. | -| `PAR_SET(par_num, value)` | Use C11 `_Generic` to route a typed value to the matching setter. `float32_t` dispatch is available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. | +## Typed setter macro wrappers -## Typed setters +| Macro | Description | +| --- | --- | +| `PAR_SET_U8(par_num, value)` | Call `par_set_u8()` through a typed macro wrapper. | +| `PAR_SET_I8(par_num, value)` | Call `par_set_i8()` through a typed macro wrapper. | +| `PAR_SET_U16(par_num, value)` | Call `par_set_u16()` through a typed macro wrapper. | +| `PAR_SET_I16(par_num, value)` | Call `par_set_i16()` through a typed macro wrapper. | +| `PAR_SET_U32(par_num, value)` | Call `par_set_u32()` through a typed macro wrapper. | +| `PAR_SET_I32(par_num, value)` | Call `par_set_i32()` through a typed macro wrapper. | +| `PAR_SET_F32(par_num, value)` | Call `par_set_f32()` through a typed macro wrapper. Available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. | + +## Typed setter functions | Function | Description | | --- | --- | @@ -114,15 +125,26 @@ They are different from startup initialization: That distinction matters if your application depends on validation callbacks, on-change callbacks, or other setter-side effects, because those runtime hooks apply only in the normal setter path and only when the matching callback features are enabled. -## Generic getters +## Pointer-based getters | Function | Description | | --- | --- | | `par_get(par_num, p_val)` | Read a parameter into a typed destination pointer. | | `par_get_by_id(id, p_val)` | Read a parameter using its external ID. | -| `PAR_GET(par_num, dest)` | Use C11 `_Generic` to route a destination variable to the matching getter. `float32_t` dispatch is available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. | -## Typed getters +## Typed getter macro wrappers + +| Macro | Description | +| --- | --- | +| `PAR_GET_U8(par_num, dest)` | Assign the result of `par_get_u8()` through a typed macro wrapper. | +| `PAR_GET_I8(par_num, dest)` | Assign the result of `par_get_i8()` through a typed macro wrapper. | +| `PAR_GET_U16(par_num, dest)` | Assign the result of `par_get_u16()` through a typed macro wrapper. | +| `PAR_GET_I16(par_num, dest)` | Assign the result of `par_get_i16()` through a typed macro wrapper. | +| `PAR_GET_U32(par_num, dest)` | Assign the result of `par_get_u32()` through a typed macro wrapper. | +| `PAR_GET_I32(par_num, dest)` | Assign the result of `par_get_i32()` through a typed macro wrapper. | +| `PAR_GET_F32(par_num, dest)` | Assign the result of `par_get_f32()` through a typed macro wrapper. Available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. | + +## Typed getter functions | Function | Description | | --- | --- | diff --git a/docs/getting-started.md b/docs/getting-started.md index 7302544..bec4b71 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -14,7 +14,7 @@ This guide shows how to integrate the `Device Parameters` module into a firmware - compile-scan or script-provided layout - whether `F32` parameter support should be compiled in 5. Call `par_init()` before runtime access. -6. Use typed APIs or the generic `PAR_SET` and `PAR_GET` macros. +6. Use typed APIs or the typed macro wrappers such as `PAR_SET_U16` and `PAR_GET_U16`. ## Required files @@ -185,7 +185,7 @@ When `PAR_CFG_ENABLE_TYPE_F32 = 0`: * `PAR_ITEM_F32(...)` entries are not allowed in `par_table.def` * `par_set_f32()`, `par_get_f32()`, and `par_set_f32_fast()` are not available -* `PAR_SET` and `PAR_GET` no longer dispatch `float32_t` +* `PAR_SET_F32` and `PAR_GET_F32` are not available * startup F32 default patching is skipped ## Initialization @@ -217,12 +217,12 @@ Do not rely on startup initialization to trigger application callbacks or runtim The `F32` examples in this section require `PAR_CFG_ENABLE_TYPE_F32 = 1`. -### Use the generic macros in normal application code +### Use the typed macro wrappers in normal application code ```c -PAR_SET(ePAR_TARGET_TEMP, (float32_t)42.5f); +PAR_SET_F32(ePAR_TARGET_TEMP, (float32_t)42.5f); float32_t target_temp = 0.0f; -PAR_GET(ePAR_TARGET_TEMP, target_temp); +PAR_GET_F32(ePAR_TARGET_TEMP, target_temp); ``` ### Use typed APIs when explicitness matters @@ -345,7 +345,7 @@ uint32_t baud = 115200U; - Writing `par_table.def` entries with duplicate IDs - Assuming the repository already ships a ready-to-build `par_table.def` for your project - Disabling `PAR_CFG_ENABLE_TYPE_F32` while keeping `PAR_ITEM_F32(...)` entries in `par_table.def` -- Assuming `PAR_SET` and `PAR_GET` still accept `float32_t` after F32 support is disabled +- Assuming `PAR_SET_F32` and `PAR_GET_F32` are still available after F32 support is disabled - Registering validation or change callbacks without enabling the matching configuration macro ### Compile-time error example when F32 support is disabled diff --git a/src/par.h b/src/par.h index ac155f6..205f916 100644 --- a/src/par.h +++ b/src/par.h @@ -212,35 +212,16 @@ par_status_t par_set_all_to_default (void); par_status_t par_has_changed (const par_num_t par_num, bool *const p_has_changed); /** - * @brief Type-generic macro to set a parameter value. - * - * This macro uses C11 _Generic to automatically select the appropriate - * setter function based on the data type of the "value" variable. - * - * @param[in] par_num - Parameter number (enumeration) - * @param[in] value - The new value of a parameter + * @brief Typed macro wrappers for parameter set. */ +#define PAR_SET_U8(par_num, value) par_set_u8((par_num), (value)) +#define PAR_SET_I8(par_num, value) par_set_i8((par_num), (value)) +#define PAR_SET_U16(par_num, value) par_set_u16((par_num), (value)) +#define PAR_SET_I16(par_num, value) par_set_i16((par_num), (value)) +#define PAR_SET_U32(par_num, value) par_set_u32((par_num), (value)) +#define PAR_SET_I32(par_num, value) par_set_i32((par_num), (value)) #if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) - #define PAR_SET(par_num, value) _Generic((value), \ - uint8_t: par_set_u8, \ - bool: par_set_u8, \ - uint16_t: par_set_u16, \ - uint32_t: par_set_u32, \ - int8_t: par_set_i8, \ - int16_t: par_set_i16, \ - int32_t: par_set_i32, \ - float32_t: par_set_f32 \ - )(par_num, value) -#else - #define PAR_SET(par_num, value) _Generic((value), \ - uint8_t: par_set_u8, \ - bool: par_set_u8, \ - uint16_t: par_set_u16, \ - uint32_t: par_set_u32, \ - int8_t: par_set_i8, \ - int16_t: par_set_i16, \ - int32_t: par_set_i32 \ - )(par_num, value) +#define PAR_SET_F32(par_num, value) par_set_f32((par_num), (value)) #endif // Getting parameter value API (module must be first initialized before using those func) @@ -261,36 +242,16 @@ par_status_t par_get_default (const par_num_t par_num, void * const p_val); bool par_is_changed (const par_num_t par_num); /** - * @brief Type-generic macro to retrieve a parameter value. - * - * This macro uses C11 _Generic to automatically select the appropriate - * getter function based on the data type of the "dest" variable. - * - * @param[in] par_num - The unique identifier (ID) of the parameter to retrieve. - * @param[in] dest - The destination variable where the value will be stored. - * The type of this variable determines the getter used (e.g., uint8_t, float). + * @brief Typed macro wrappers for parameter get. */ +#define PAR_GET_U8(par_num, dest) do { (dest) = par_get_u8((par_num)); } while (0) +#define PAR_GET_I8(par_num, dest) do { (dest) = par_get_i8((par_num)); } while (0) +#define PAR_GET_U16(par_num, dest) do { (dest) = par_get_u16((par_num)); } while (0) +#define PAR_GET_I16(par_num, dest) do { (dest) = par_get_i16((par_num)); } while (0) +#define PAR_GET_U32(par_num, dest) do { (dest) = par_get_u32((par_num)); } while (0) +#define PAR_GET_I32(par_num, dest) do { (dest) = par_get_i32((par_num)); } while (0) #if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) - #define PAR_GET(par_num, dest) _Generic((dest), \ - uint8_t: dest = par_get_u8(par_num), \ - bool: dest = par_get_u8(par_num), \ - uint16_t: dest = par_get_u16(par_num), \ - uint32_t: dest = par_get_u32(par_num), \ - int8_t: dest = par_get_i8(par_num), \ - int16_t: dest = par_get_i16(par_num), \ - int32_t: dest = par_get_i32(par_num), \ - float32_t: dest = par_get_f32(par_num) \ - ) -#else - #define PAR_GET(par_num, dest) _Generic((dest), \ - uint8_t: dest = par_get_u8(par_num), \ - bool: dest = par_get_u8(par_num), \ - uint16_t: dest = par_get_u16(par_num), \ - uint32_t: dest = par_get_u32(par_num), \ - int8_t: dest = par_get_i8(par_num), \ - int16_t: dest = par_get_i16(par_num), \ - int32_t: dest = par_get_i32(par_num) \ - ) +#define PAR_GET_F32(par_num, dest) do { (dest) = par_get_f32((par_num)); } while (0) #endif // Parameter configurations API (usage without module init pre-step) From 590f8f1176e9094047adf8d1e14cb99873295fed Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Mon, 23 Mar 2026 08:37:48 +0800 Subject: [PATCH 15/36] refactor(parameters): Extract typed and storage implementation from par.c --- README.md | 5 +- docs/architecture.md | 10 +- docs/getting-started.md | 2 + src/par.c | 1449 ++++--------------------------- src/par_bitwise_impl.inc | 62 ++ src/par_storage_init.inc | 86 ++ src/par_typed_impl.inc | 160 ++++ template/par_layout_static.htmp | 22 +- 8 files changed, 490 insertions(+), 1306 deletions(-) create mode 100644 src/par_bitwise_impl.inc create mode 100644 src/par_storage_init.inc create mode 100644 src/par_typed_impl.inc diff --git a/README.md b/README.md index 6159b40..4b24541 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ parameters/ │ ├── par.c │ ├── par.h │ ├── par_atomic.h +│ ├── par_bitwise_impl.inc │ ├── par_cfg.h │ ├── par_def.c │ ├── par_def.h @@ -84,7 +85,9 @@ parameters/ │ ├── par_layout.c │ ├── par_layout.h │ ├── par_nvm.c -│ └── par_nvm.h +│ ├── par_nvm.h +│ ├── par_storage_init.inc +│ └── par_typed_impl.inc └── template/ ├── par_cfg_port.htmp ├── par_layout_static.htmp diff --git a/docs/architecture.md b/docs/architecture.md index 72177e3..2885432 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -7,8 +7,8 @@ This document explains how the module is structured internally and how the major The module separates four concerns: 1. **Generated parameter definition** through `par_table.def`, generated enums, and generated config structs -2. **Core runtime access** through `par.c` and `par.h` -3. **Layout and storage** through `par_layout.*` +2. **Core runtime access** through `par.c`, `par.h`, and private implementation fragments included only by `par.c` +3. **Layout and storage** through `par_layout.*` and compile-time storage initialization fragments 4. **Optional platform and NVM integration** through `par_if.*`, `par_atomic.h`, and `par_nvm.*` Runtime validation hooks and on-change hooks are kept separate from the core `par_cfg_t` metadata table and can be compiled out independently. @@ -109,6 +109,8 @@ Live storage is initialized in two phases during startup: 1. Integer default values from `par_table.def` are compiled directly into the shared storage arrays. 2. When `PAR_CFG_ENABLE_TYPE_F32 = 1`, `F32` default values are written into the shared 32-bit storage after layout offsets are available. +The compile-time integer storage initializers are emitted through a private include fragment, `par_storage_init.inc`, which is included only by `par.c`. + This means `par_init()` does not need to apply startup defaults through the public setter path for every parameter. ### Ordering contract for shared storage @@ -228,12 +230,16 @@ Normal setters are the default path. They are intended for ordinary application Depending on build-time configuration, the normal path can include runtime validation callbacks and on-change callbacks. +The typed setter/getter implementations are emitted through `par_typed_impl.inc`, a private include fragment included only by `par.c`. + ### Fast setters Fast setters are specialized APIs for controlled hot paths. They reduce overhead, but they should only be used when the surrounding code already guarantees the assumptions that the full path would normally check. Fast setters do not execute runtime validation callbacks or on-change callbacks. +The bitwise fast helpers are emitted through `par_bitwise_impl.inc`, another private include fragment included only by `par.c`. + ## Optional NVM persistence When `PAR_CFG_NVM_EN = 1`, the module can persist selected parameters to NVM. diff --git a/docs/getting-started.md b/docs/getting-started.md index bec4b71..d8dcdee 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -116,6 +116,8 @@ Provide a generated static layout header only when: Use `template/par_layout_static.htmp` as the contract for the generated file. +The template includes the required count macros and offset-table declaration. It can also serve as the starting point for a project-specific file header banner. + ## Configuration decisions that matter first ### NVM support diff --git a/src/par.c b/src/par.c index deb3e2b..7ced3d9 100644 --- a/src/par.c +++ b/src/par.c @@ -34,47 +34,47 @@ //////////////////////////////////////////////////////////////////////////////// // Definitions //////////////////////////////////////////////////////////////////////////////// +#if ( 1 == PAR_CFG_ENABLE_ID ) /* * http://www.citi.umich.edu/techreports/reports/citi-tr-00-1.pdf * * GoldenRatio = ~(Math.pow(2, 32) / ((Math.sqrt(5) - 1) / 2)) + 1 */ -#if ( 1 == PAR_CFG_ENABLE_ID ) - #define PAR_ID_HASH_GOLDEN_RATIO_32 ( 0x61C88647u ) +#define PAR_ID_HASH_GOLDEN_RATIO_32 ( 0x61C88647u ) /** * Minimum number of hash buckets to keep target load factor <= 0.5. */ - #define PAR_ID_HASH_MIN_BUCKETS ((uint32_t)(2u * (uint32_t)ePAR_NUM_OF)) +#define PAR_ID_HASH_MIN_BUCKETS ((uint32_t)(2u * (uint32_t)ePAR_NUM_OF)) /** * Hash map geometry derived from ePAR_NUM_OF at compile time. */ - enum - { - PAR_ID_HASH_BITS = - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 1 )) ? 1u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 2 )) ? 2u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 3 )) ? 3u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 4 )) ? 4u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 5 )) ? 5u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 6 )) ? 6u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 7 )) ? 7u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 8 )) ? 8u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 9 )) ? 9u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 10 )) ? 10u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 11 )) ? 11u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 12 )) ? 12u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 13 )) ? 13u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 14 )) ? 14u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 15 )) ? 15u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 16 )) ? 16u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 17 )) ? 17u : 18u, - PAR_ID_HASH_SIZE = ( 1u << PAR_ID_HASH_BITS ), - }; +enum +{ + PAR_ID_HASH_BITS = + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 1 )) ? 1u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 2 )) ? 2u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 3 )) ? 3u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 4 )) ? 4u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 5 )) ? 5u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 6 )) ? 6u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 7 )) ? 7u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 8 )) ? 8u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 9 )) ? 9u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 10 )) ? 10u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 11 )) ? 11u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 12 )) ? 12u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 13 )) ? 13u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 14 )) ? 14u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 15 )) ? 15u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 16 )) ? 16u : + ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 17 )) ? 17u : 18u, + PAR_ID_HASH_SIZE = ( 1u << PAR_ID_HASH_BITS ), +}; - PAR_STATIC_ASSERT(par_id_hash_size_valid, (PAR_ID_HASH_SIZE >= PAR_ID_HASH_MIN_BUCKETS)); - PAR_STATIC_ASSERT(par_id_hash_bits_valid, ((PAR_ID_HASH_BITS > 0u) && (PAR_ID_HASH_BITS < 32u))); +PAR_STATIC_ASSERT(par_id_hash_size_valid, (PAR_ID_HASH_SIZE >= PAR_ID_HASH_MIN_BUCKETS)); +PAR_STATIC_ASSERT(par_id_hash_bits_valid, ((PAR_ID_HASH_BITS > 0u) && (PAR_ID_HASH_BITS < 32u))); #endif PAR_STATIC_ASSERT(par_atomic_u8_i8_same_size, sizeof(par_atomic_u8_t) == sizeof(par_atomic_i8_t)); @@ -138,95 +138,9 @@ static struct * Static typed storage backing parameter live values. * * @note Zero-length groups are mapped to size 1 arrays for compiler portability. + * Private implementation fragments. Do not include outside par.c. */ -/* - * Compile-time default initialization for shared storage groups. - * - * Storage order inside each width group must follow par_table.def entry order - * filtered by the group-supported types, matching runtime layout scan order. - */ -#define PAR_STORAGE_INIT_NOP(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) -#define PAR_STORAGE_U8_FROM_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u8_t)((uint8_t)(def_)), -#define PAR_STORAGE_U8_FROM_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u8_t)((uint8_t)(int8_t)(def_)), -#define PAR_STORAGE_U16_FROM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u16_t)((uint16_t)(def_)), -#define PAR_STORAGE_U16_FROM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u16_t)((uint16_t)(int16_t)(def_)), -#define PAR_STORAGE_U32_FROM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u32_t)((uint32_t)(def_)), -#define PAR_STORAGE_U32_FROM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u32_t)((uint32_t)(int32_t)(def_)), -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) -#define PAR_STORAGE_U32_FROM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u32_t)(0u), -#endif - -#define PAR_ITEM_U8 PAR_STORAGE_U8_FROM_U8 -#define PAR_ITEM_U16 PAR_STORAGE_INIT_NOP -#define PAR_ITEM_U32 PAR_STORAGE_INIT_NOP -#define PAR_ITEM_I8 PAR_STORAGE_U8_FROM_I8 -#define PAR_ITEM_I16 PAR_STORAGE_INIT_NOP -#define PAR_ITEM_I32 PAR_STORAGE_INIT_NOP -#define PAR_ITEM_F32 PAR_STORAGE_INIT_NOP -static par_atomic_u8_t gs_par_u8_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT8)] = -{ - #include "../../par_table.def" -}; -#undef PAR_ITEM_U8 -#undef PAR_ITEM_U16 -#undef PAR_ITEM_U32 -#undef PAR_ITEM_I8 -#undef PAR_ITEM_I16 -#undef PAR_ITEM_I32 -#undef PAR_ITEM_F32 - -#define PAR_ITEM_U8 PAR_STORAGE_INIT_NOP -#define PAR_ITEM_U16 PAR_STORAGE_U16_FROM_U16 -#define PAR_ITEM_U32 PAR_STORAGE_INIT_NOP -#define PAR_ITEM_I8 PAR_STORAGE_INIT_NOP -#define PAR_ITEM_I16 PAR_STORAGE_U16_FROM_I16 -#define PAR_ITEM_I32 PAR_STORAGE_INIT_NOP -#define PAR_ITEM_F32 PAR_STORAGE_INIT_NOP -static par_atomic_u16_t gs_par_u16_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT16)] = -{ - #include "../../par_table.def" -}; -#undef PAR_ITEM_U8 -#undef PAR_ITEM_U16 -#undef PAR_ITEM_U32 -#undef PAR_ITEM_I8 -#undef PAR_ITEM_I16 -#undef PAR_ITEM_I32 -#undef PAR_ITEM_F32 - -#define PAR_ITEM_U8 PAR_STORAGE_INIT_NOP -#define PAR_ITEM_U16 PAR_STORAGE_INIT_NOP -#define PAR_ITEM_U32 PAR_STORAGE_U32_FROM_U32 -#define PAR_ITEM_I8 PAR_STORAGE_INIT_NOP -#define PAR_ITEM_I16 PAR_STORAGE_INIT_NOP -#define PAR_ITEM_I32 PAR_STORAGE_U32_FROM_I32 -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) - #define PAR_ITEM_F32 PAR_STORAGE_U32_FROM_F32 -#else - #define PAR_ITEM_F32 PAR_STORAGE_INIT_NOP -#endif -static par_atomic_u32_t gs_par_u32_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT32)] = -{ - #include "../../par_table.def" -}; -#undef PAR_ITEM_U8 -#undef PAR_ITEM_U16 -#undef PAR_ITEM_U32 -#undef PAR_ITEM_I8 -#undef PAR_ITEM_I16 -#undef PAR_ITEM_I32 -#undef PAR_ITEM_F32 - -#undef PAR_STORAGE_INIT_NOP -#undef PAR_STORAGE_U8_FROM_U8 -#undef PAR_STORAGE_U8_FROM_I8 -#undef PAR_STORAGE_U16_FROM_U16 -#undef PAR_STORAGE_U16_FROM_I16 -#undef PAR_STORAGE_U32_FROM_U32 -#undef PAR_STORAGE_U32_FROM_I32 -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) -#undef PAR_STORAGE_U32_FROM_F32 -#endif +#include "par_storage_init.inc" /** * Parameter live values divided by its type in RAM @@ -732,1099 +646,188 @@ par_status_t par_set_by_id(const uint16_t id, const void * p_val) //////////////////////////////////////////////////////////////////////////////// /** -* Set unsigned 8-bit parameter -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation +* Typed setter/getter implementation +* @note Private implementation fragments. Do not include outside par.c. + */ //////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_u8(const par_num_t par_num, const uint8_t val) -{ - par_status_t status = ePAR_OK; - - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; +#include "par_typed_impl.inc" - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_U8 == par_get_type(par_num)); - if( ePAR_TYPE_U8 != par_get_type(par_num)) return ePAR_ERROR_TYPE; - - // Get mutex - if ( ePAR_OK == par_acquire_mutex(par_num)) - { -#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) - const par_type_t old_val = {.u8 = PAR_GET_U8_PRIV( par_num )}; -#endif - -#if ( 1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION ) - // Validated parameter value - pf_par_validation_t validation = g_par_cb_table[par_num].validation; - if ((validation != NULL) && !validation(par_num, (par_type_t){.u8 = val})) - { - status = ePAR_ERROR_VALUE; - } - else -#endif - { - status = par_set_u8_fast(par_num, val); - } - - // Raise on change callback -#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) - pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; - const par_type_t new_val = {.u8 = PAR_GET_U8_PRIV( par_num )}; - if ((on_change != NULL) && (new_val.u8 != old_val.u8)) - { - on_change(par_num, new_val, old_val); - } -#endif - - par_release_mutex(par_num); - } - else - { - status = ePAR_ERROR_MUTEX; - } +//////////////////////////////////////////////////////////////////////////////// +/** +* Bitwise fast setter implementation +* @note Private implementation fragments. Do not include outside par.c. +*/ +//////////////////////////////////////////////////////////////////////////////// +#include "par_bitwise_impl.inc" - return status; +//////////////////////////////////////////////////////////////////////////////// +/** +* Set parameter to default value +* +* @pre Parameters must be initialised before usage! +* +* @param[in] par_num - Parameter number (enumeration) +* @return status - Status of operation +*/ +//////////////////////////////////////////////////////////////////////////////// +par_status_t par_set_to_default(const par_num_t par_num) +{ + return par_set(par_num, &(par_get_config(par_num)->def)); } //////////////////////////////////////////////////////////////////////////////// /** -* Set signed 8-bit parameter +* Set all parameters to default value * -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation +* @pre Parameters must be initialised before usage! +* +* @return status - Status of operation */ //////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_i8(const par_num_t par_num, const int8_t val) +par_status_t par_set_all_to_default(void) { - par_status_t status = ePAR_OK; - - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_I8 == par_get_type(par_num)); - if( ePAR_TYPE_I8 != par_get_type(par_num)) return ePAR_ERROR_TYPE; - - // Get mutex - if ( ePAR_OK == par_acquire_mutex(par_num)) - { -#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) - const par_type_t old_val = {.i8 = PAR_GET_I8_PRIV( par_num )}; -#endif - -#if ( 1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION ) - // Validated parameter value - pf_par_validation_t validation = g_par_cb_table[par_num].validation; - if ((validation != NULL) && !validation(par_num, (par_type_t){.i8 = val})) - { - status = ePAR_ERROR_VALUE; - } - else -#endif - { - status = par_set_i8_fast(par_num, val); - } - -#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) - // Raise on change callback - pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; - const par_type_t new_val = {.i8 = PAR_GET_I8_PRIV( par_num )}; - if ((on_change != NULL) && (new_val.i8 != old_val.i8)) - { - on_change(par_num, new_val, old_val); - } -#endif - - par_release_mutex(par_num); - } - else + for ( par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) { - status = ePAR_ERROR_MUTEX; + // Ignore return as it is not possible to return other that OK + (void) par_set_to_default( par_num ); } - return status; + PAR_DBG_PRINT( "PAR: Setting all parameters to default" ); + return ePAR_OK; } //////////////////////////////////////////////////////////////////////////////// /** -* Set unsigned 16-bit parameter +* Check if parameter changed from its default value * -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation +* @param[in] par_num - Parameter number (enumeration) +* @param[out] p_has_changed - Pointer to changed indication +* @return status - Status of operation */ //////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_u16(const par_num_t par_num, const uint16_t val) +par_status_t par_has_changed(const par_num_t par_num, bool *const p_has_changed) { - par_status_t status = ePAR_OK; + const par_cfg_t * const par_cfg = par_get_config(par_num); - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; + switch ( par_cfg->type ) + { + case ePAR_TYPE_U8: + *p_has_changed = (par_get_u8(par_num) != par_cfg->def.u8); + break; - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_U16 == par_get_type(par_num)); - if( ePAR_TYPE_U16 != par_get_type(par_num)) return ePAR_ERROR_TYPE; + case ePAR_TYPE_I8: + *p_has_changed = (par_get_i8(par_num) != par_cfg->def.i8); + break; - // Get mutex - if ( ePAR_OK == par_acquire_mutex(par_num)) - { -#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) - const par_type_t old_val = {.u16 = PAR_GET_U16_PRIV( par_num )}; -#endif + case ePAR_TYPE_U16: + *p_has_changed = (par_get_u16(par_num) != par_cfg->def.u16); + break; -#if ( 1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION ) - // Validated parameter value - pf_par_validation_t validation = g_par_cb_table[par_num].validation; - if ((validation != NULL) && !validation(par_num, (par_type_t){.u16 = val})) - { - status = ePAR_ERROR_VALUE; - } - else -#endif - { - status = par_set_u16_fast(par_num, val); - } + case ePAR_TYPE_I16: + *p_has_changed = (par_get_i16(par_num) != par_cfg->def.i16); + break; -#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) - // Raise on change callback - pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; - const par_type_t new_val = {.u16 = PAR_GET_U16_PRIV( par_num )}; - if ((on_change != NULL) && (new_val.u16 != old_val.u16)) - { - on_change(par_num, new_val, old_val); - } + case ePAR_TYPE_U32: + *p_has_changed = (par_get_u32(par_num) != par_cfg->def.u32); + break; + + case ePAR_TYPE_I32: + *p_has_changed = (par_get_i32(par_num) != par_cfg->def.i32); + break; + +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) + case ePAR_TYPE_F32: + *p_has_changed = (par_get_f32(par_num) != par_cfg->def.f32); + break; #endif - par_release_mutex(par_num); - } - else - { - status = ePAR_ERROR_MUTEX; + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT( 0 ); + return ePAR_ERROR_TYPE; } - return status; + return ePAR_OK; } //////////////////////////////////////////////////////////////////////////////// /** -* Set signed 16-bit parameter +* Get parameter value +* +* @note Mandatory to cast input argument to appropriate type. E.g.: +* +* @code +* float32_t my_val = 0.0f; +* par_get( ePAR_MY_VAR, (float32_t*) &my_val ); +* @endcode +* +* @note Input is parameter number (enumeration) defined in par_cfg.h and not +* parameter ID number! * * @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter +* @param[out] p_val - Parameter value * @return status - Status of operation */ //////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_i16(const par_num_t par_num, const int16_t val) +par_status_t par_get(const par_num_t par_num, void * const p_val) { - par_status_t status = ePAR_OK; + switch ( par_get_type(par_num)) + { + case ePAR_TYPE_U8: + *(uint8_t*) p_val = par_get_u8(par_num); + break; - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; + case ePAR_TYPE_I8: + *(int8_t*) p_val = par_get_i8(par_num); + break; - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_I16 == par_get_type(par_num)); - if( ePAR_TYPE_I16 != par_get_type(par_num)) return ePAR_ERROR_TYPE; + case ePAR_TYPE_U16: + *(uint16_t*) p_val = par_get_u16(par_num); + break; - // Get mutex - if ( ePAR_OK == par_acquire_mutex(par_num)) - { -#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) - const par_type_t old_val = {.i16 = PAR_GET_I16_PRIV( par_num )}; -#endif + case ePAR_TYPE_I16: + *(int16_t*) p_val = par_get_i16(par_num); + break; -#if ( 1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION ) - // Validated parameter value - pf_par_validation_t validation = g_par_cb_table[par_num].validation; - if ((validation != NULL) && !validation(par_num, (par_type_t){.i16 = val})) - { - status = ePAR_ERROR_VALUE; - } - else -#endif - { - status = par_set_i16_fast(par_num, val); - } + case ePAR_TYPE_U32: + *(uint32_t*) p_val = par_get_u32(par_num); + break; -#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) - // Raise on change callback - pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; - const par_type_t new_val = {.i16 = PAR_GET_I16_PRIV( par_num )}; - if ((on_change != NULL) && (new_val.i16 != old_val.i16)) - { - on_change(par_num, new_val, old_val); - } + case ePAR_TYPE_I32: + *(int32_t*) p_val = par_get_i32(par_num); + break; + +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) + case ePAR_TYPE_F32: + *(float32_t*) p_val = par_get_f32(par_num); + break; #endif - par_release_mutex(par_num); - } - else - { - status = ePAR_ERROR_MUTEX; + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT( 0 ); + return ePAR_ERROR_TYPE; } - return status; + return ePAR_OK; } //////////////////////////////////////////////////////////////////////////////// /** -* Set unsigned 32-bit parameter +* Get parameter value by ID * -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter +* @param[in] id - Parameter ID number +* @param[out] p_val - Pointer to value * @return status - Status of operation */ //////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_u32(const par_num_t par_num, const uint32_t val) +#if ( 1 == PAR_CFG_ENABLE_ID ) +par_status_t par_get_by_id(const uint16_t id, void * const p_val) { - par_status_t status = ePAR_OK; - - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_U32 == par_get_type(par_num)); - if( ePAR_TYPE_U32 != par_get_type(par_num)) return ePAR_ERROR_TYPE; + par_num_t par_num; - // Get mutex - if ( ePAR_OK == par_acquire_mutex(par_num)) - { -#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) - const par_type_t old_val = {.u32 = PAR_GET_U32_PRIV( par_num )}; -#endif - -#if ( 1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION ) - // Validated parameter value - pf_par_validation_t validation = g_par_cb_table[par_num].validation; - if ((validation != NULL) && !validation(par_num, (par_type_t){.u32 = val})) - { - status = ePAR_ERROR_VALUE; - } - else -#endif - { - status = par_set_u32_fast(par_num, val); - } - -#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) - // Raise on change callback - pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; - const par_type_t new_val = {.u32 = PAR_GET_U32_PRIV( par_num )}; - if ((on_change != NULL) && (new_val.u32 != old_val.u32)) - { - on_change(par_num, new_val, old_val); - } -#endif - - par_release_mutex(par_num); - } - else - { - status = ePAR_ERROR_MUTEX; - } - - return status; -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Set signed 32-bit parameter -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_i32(const par_num_t par_num, const int32_t val) -{ - par_status_t status = ePAR_OK; - - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_I32 == par_get_type(par_num)); - if( ePAR_TYPE_I32 != par_get_type(par_num)) return ePAR_ERROR_TYPE; - - // Get mutex - if ( ePAR_OK == par_acquire_mutex(par_num)) - { -#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) - const par_type_t old_val = {.i32 = PAR_GET_I32_PRIV( par_num )}; -#endif - -#if ( 1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION ) - // Validated parameter value - pf_par_validation_t validation = g_par_cb_table[par_num].validation; - if ((validation != NULL) && !validation(par_num, (par_type_t){.i32 = val})) - { - status = ePAR_ERROR_VALUE; - } - else -#endif - { - status = par_set_i32_fast(par_num, val); - } - -#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) - // Raise on change callback - pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; - const par_type_t new_val = {.i32 = PAR_GET_I32_PRIV( par_num )}; - if ((on_change != NULL) && (new_val.i32 != old_val.i32)) - { - on_change(par_num, new_val, old_val); - } -#endif - - par_release_mutex(par_num); - } - else - { - status = ePAR_ERROR_MUTEX; - } - - return status; -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Set floating value parameter -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) -par_status_t par_set_f32(const par_num_t par_num, const float32_t val) -{ - par_status_t status = ePAR_OK; - - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_F32 == par_get_type(par_num)); - if( ePAR_TYPE_F32 != par_get_type(par_num)) return ePAR_ERROR_TYPE; - - // Get mutex - if ( ePAR_OK == par_acquire_mutex(par_num)) - { -#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) - const par_type_t old_val = {.f32 = PAR_GET_F32_PRIV( par_num )}; -#endif - -#if ( 1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION ) - // Validated parameter value - pf_par_validation_t validation = g_par_cb_table[par_num].validation; - if ((validation != NULL) && !validation(par_num, (par_type_t){.f32 = val})) - { - status = ePAR_ERROR_VALUE; - } - else -#endif - { - status = par_set_f32_fast(par_num, val); - } - -#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) - // Raise on change callback - pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; - const par_type_t new_val = {.f32 = PAR_GET_F32_PRIV( par_num )}; - if ((on_change != NULL) && (new_val.f32 != old_val.f32)) - { - on_change(par_num, new_val, old_val); - } -#endif - - par_release_mutex(par_num); - } - else - { - status = ePAR_ERROR_MUTEX; - } - - return status; -} -#endif - -//////////////////////////////////////////////////////////////////////////////// -/** -* Set unsigned 8-bit parameter fast -* -* @note Using *(volatile uint8_t*) prevents store tearing as explained -* here: https://lwn.net/Articles/793253/ -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_u8_fast(const par_num_t par_num, const uint8_t val) -{ - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_U8 == par_get_type(par_num)); - -#if ( 1 == PAR_CFG_ENABLE_RANGE ) - const par_range_t range = par_get_range(par_num); - - if ( val > range.max.u8 ) - { - PAR_SET_U8_PRIV( par_num, range.max.u8 ); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.u8 ) - { - PAR_SET_U8_PRIV( par_num, range.min.u8 ); - return ePAR_WAR_LIMITED; - } - else - { - PAR_SET_U8_PRIV( par_num, val ); - return ePAR_OK; - } -#else - PAR_SET_U8_PRIV( par_num, val ); - return ePAR_OK; -#endif -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Set signed 8-bit parameter fast -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_i8_fast(const par_num_t par_num, const int8_t val) -{ - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_I8 == par_get_type(par_num)); - -#if ( 1 == PAR_CFG_ENABLE_RANGE ) - const par_range_t range = par_get_range(par_num); - - if ( val > range.max.i8 ) - { - PAR_SET_I8_PRIV( par_num, range.max.i8 ); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.i8 ) - { - PAR_SET_I8_PRIV( par_num, range.min.i8 ); - return ePAR_WAR_LIMITED; - } - else - { - PAR_SET_I8_PRIV( par_num, val ); - return ePAR_OK; - } -#else - PAR_SET_I8_PRIV( par_num, val ); - return ePAR_OK; -#endif -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Set unsigned 16-bit parameter fast -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_u16_fast(const par_num_t par_num, const uint16_t val) -{ - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_U16 == par_get_type(par_num)); - -#if ( 1 == PAR_CFG_ENABLE_RANGE ) - const par_range_t range = par_get_range(par_num); - - if ( val > range.max.u16 ) - { - PAR_SET_U16_PRIV( par_num, range.max.u16 ); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.u16 ) - { - PAR_SET_U16_PRIV( par_num, range.min.u16 ); - return ePAR_WAR_LIMITED; - } - else - { - PAR_SET_U16_PRIV( par_num, val ); - return ePAR_OK; - } -#else - PAR_SET_U16_PRIV( par_num, val ); - return ePAR_OK; -#endif -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Set signed 16-bit parameter fast -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_i16_fast(const par_num_t par_num, const int16_t val) -{ - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_I16 == par_get_type(par_num)); - -#if ( 1 == PAR_CFG_ENABLE_RANGE ) - const par_range_t range = par_get_range(par_num); - - if ( val > range.max.i16 ) - { - PAR_SET_I16_PRIV( par_num, range.max.i16 ); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.i16 ) - { - PAR_SET_I16_PRIV( par_num, range.min.i16 ); - return ePAR_WAR_LIMITED; - } - else - { - PAR_SET_I16_PRIV( par_num, val ); - return ePAR_OK; - } -#else - PAR_SET_I16_PRIV( par_num, val ); - return ePAR_OK; -#endif -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Set unsigned 16-bit parameter fast -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_u32_fast(const par_num_t par_num, const uint32_t val) -{ - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_U32 == par_get_type(par_num)); - -#if ( 1 == PAR_CFG_ENABLE_RANGE ) - const par_range_t range = par_get_range(par_num); - - if ( val > range.max.u32 ) - { - PAR_SET_U32_PRIV( par_num, range.max.u32 ); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.u32 ) - { - PAR_SET_U32_PRIV( par_num, range.min.u32 ); - return ePAR_WAR_LIMITED; - } - else - { - PAR_SET_U32_PRIV( par_num, val ); - return ePAR_OK; - } -#else - PAR_SET_U32_PRIV( par_num, val ); - return ePAR_OK; -#endif -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Set signed 32-bit parameter fast -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_i32_fast(const par_num_t par_num, const int32_t val) -{ - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_I32 == par_get_type(par_num)); - -#if ( 1 == PAR_CFG_ENABLE_RANGE ) - const par_range_t range = par_get_range(par_num); - - if ( val > range.max.i32 ) - { - PAR_SET_I32_PRIV( par_num, range.max.i32 ); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.i32 ) - { - PAR_SET_I32_PRIV( par_num, range.min.i32 ); - return ePAR_WAR_LIMITED; - } - else - { - PAR_SET_I32_PRIV( par_num, val ); - return ePAR_OK; - } -#else - PAR_SET_I32_PRIV( par_num, val ); - return ePAR_OK; -#endif -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Set floating value parameter fast -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) -par_status_t par_set_f32_fast(const par_num_t par_num, const float32_t val) -{ - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_F32 == par_get_type(par_num)); - -#if ( 1 == PAR_CFG_ENABLE_RANGE ) - const par_range_t range = par_get_range(par_num); - - if ( val > range.max.f32 ) - { - PAR_SET_F32_PRIV( par_num, range.max.f32 ); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.f32 ) - { - PAR_SET_F32_PRIV( par_num, range.min.f32 ); - return ePAR_WAR_LIMITED; - } - else - { - PAR_SET_F32_PRIV( par_num, val ); - return ePAR_OK; - } -#else - PAR_SET_F32_PRIV( par_num, val ); - return ePAR_OK; -#endif -} -#endif - -//////////////////////////////////////////////////////////////////////////////// -/** -* Set unsigned 8-bit parameter ANDing with current set value fast -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_bitand_set_u8_fast(const par_num_t par_num, const uint8_t val) -{ - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_U8 == par_get_type(par_num)); - -#if ( 1 == PAR_CFG_ENABLE_RANGE ) - const par_range_t range = par_get_range(par_num); - - if ( val > range.max.u8 ) - { - PAR_ATOMIC_FETCH_AND(u8, &gpu8_par_value[g_par_offset[par_num]], range.max.u8); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.u8 ) - { - PAR_ATOMIC_FETCH_AND(u8, &gpu8_par_value[g_par_offset[par_num]], range.min.u8); - return ePAR_WAR_LIMITED; - } - else - { - PAR_ATOMIC_FETCH_AND(u8, &gpu8_par_value[g_par_offset[par_num]], val); - return ePAR_OK; - } -#else - PAR_ATOMIC_FETCH_AND(u8, &gpu8_par_value[g_par_offset[par_num]], val); - return ePAR_OK; -#endif -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Set unsigned 16-bit parameter ANDing with current set value fast -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_bitand_set_u16_fast(const par_num_t par_num, const uint16_t val) -{ - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_U16 == par_get_type(par_num)); - -#if ( 1 == PAR_CFG_ENABLE_RANGE ) - const par_range_t range = par_get_range(par_num); - - if ( val > range.max.u16 ) - { - PAR_ATOMIC_FETCH_AND(u16, &gpu16_par_value[g_par_offset[par_num]], range.max.u16); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.u16 ) - { - PAR_ATOMIC_FETCH_AND(u16, &gpu16_par_value[g_par_offset[par_num]], range.min.u16); - return ePAR_WAR_LIMITED; - } - else - { - PAR_ATOMIC_FETCH_AND(u16, &gpu16_par_value[g_par_offset[par_num]], val); - return ePAR_OK; - } -#else - PAR_ATOMIC_FETCH_AND(u16, &gpu16_par_value[g_par_offset[par_num]], val); - return ePAR_OK; -#endif -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Set unsigned 16-bit parameter ANDing with current set value fast -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_bitand_set_u32_fast(const par_num_t par_num, const uint32_t val) -{ - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_U32 == par_get_type(par_num)); - -#if ( 1 == PAR_CFG_ENABLE_RANGE ) - const par_range_t range = par_get_range(par_num); - - if ( val > range.max.u32 ) - { - PAR_ATOMIC_FETCH_AND(u32, &gpu32_par_value[g_par_offset[par_num]], range.max.u32); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.u32 ) - { - PAR_ATOMIC_FETCH_AND(u32, &gpu32_par_value[g_par_offset[par_num]], range.min.u32); - return ePAR_WAR_LIMITED; - } - else - { - PAR_ATOMIC_FETCH_AND(u32, &gpu32_par_value[g_par_offset[par_num]], val); - return ePAR_OK; - } -#else - PAR_ATOMIC_FETCH_AND(u32, &gpu32_par_value[g_par_offset[par_num]], val); - return ePAR_OK; -#endif -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Set unsigned 8-bit parameter ORing with current set value fast -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_bitor_set_u8_fast(const par_num_t par_num, const uint8_t val) -{ - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_U8 == par_get_type(par_num)); - -#if ( 1 == PAR_CFG_ENABLE_RANGE ) - const par_range_t range = par_get_range(par_num); - - if ( val > range.max.u8 ) - { - PAR_ATOMIC_FETCH_OR(u8, &gpu8_par_value[g_par_offset[par_num]], range.max.u8); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.u8 ) - { - PAR_ATOMIC_FETCH_OR(u8, &gpu8_par_value[g_par_offset[par_num]], range.min.u8); - return ePAR_WAR_LIMITED; - } - else - { - PAR_ATOMIC_FETCH_OR(u8, &gpu8_par_value[g_par_offset[par_num]], val); - return ePAR_OK; - } -#else - PAR_ATOMIC_FETCH_OR(u8, &gpu8_par_value[g_par_offset[par_num]], val); - return ePAR_OK; -#endif -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Set unsigned 16-bit parameter ORing with current set value fast -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_bitor_set_u16_fast(const par_num_t par_num, const uint16_t val) -{ - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_U16 == par_get_type(par_num)); - -#if ( 1 == PAR_CFG_ENABLE_RANGE ) - const par_range_t range = par_get_range(par_num); - - if ( val > range.max.u16 ) - { - PAR_ATOMIC_FETCH_OR(u16, &gpu16_par_value[g_par_offset[par_num]], range.max.u16); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.u16 ) - { - PAR_ATOMIC_FETCH_OR(u16, &gpu16_par_value[g_par_offset[par_num]], range.min.u16); - return ePAR_WAR_LIMITED; - } - else - { - PAR_ATOMIC_FETCH_OR(u16, &gpu16_par_value[g_par_offset[par_num]], val); - return ePAR_OK; - } -#else - PAR_ATOMIC_FETCH_OR(u16, &gpu16_par_value[g_par_offset[par_num]], val); - return ePAR_OK; -#endif -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Set unsigned 16-bit parameter ORing with current set value fast -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_bitor_set_u32_fast(const par_num_t par_num, const uint32_t val) -{ - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_U32 == par_get_type(par_num)); - -#if ( 1 == PAR_CFG_ENABLE_RANGE ) - const par_range_t range = par_get_range(par_num); - - if ( val > range.max.u32 ) - { - PAR_ATOMIC_FETCH_OR(u32, &gpu32_par_value[g_par_offset[par_num]], range.max.u32); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.u32 ) - { - PAR_ATOMIC_FETCH_OR(u32, &gpu32_par_value[g_par_offset[par_num]], range.min.u32); - return ePAR_WAR_LIMITED; - } - else - { - PAR_ATOMIC_FETCH_OR(u32, &gpu32_par_value[g_par_offset[par_num]], val); - return ePAR_OK; - } -#else - PAR_ATOMIC_FETCH_OR(u32, &gpu32_par_value[g_par_offset[par_num]], val); - return ePAR_OK; -#endif -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Set parameter to default value -* -* @pre Parameters must be initialised before usage! -* -* @param[in] par_num - Parameter number (enumeration) -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_to_default(const par_num_t par_num) -{ - return par_set(par_num, &(par_get_config(par_num)->def)); -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Set all parameters to default value -* -* @pre Parameters must be initialised before usage! -* -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_all_to_default(void) -{ - for ( par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) - { - // Ignore return as it is not possible to return other that OK - (void) par_set_to_default( par_num ); - } - - PAR_DBG_PRINT( "PAR: Setting all parameters to default" ); - return ePAR_OK; -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Check if parameter changed from its default value -* -* @param[in] par_num - Parameter number (enumeration) -* @param[out] p_has_changed - Pointer to changed indication -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_has_changed(const par_num_t par_num, bool *const p_has_changed) -{ - const par_cfg_t * const par_cfg = par_get_config(par_num); - - switch ( par_cfg->type ) - { - case ePAR_TYPE_U8: - *p_has_changed = (par_get_u8(par_num) != par_cfg->def.u8); - break; - - case ePAR_TYPE_I8: - *p_has_changed = (par_get_i8(par_num) != par_cfg->def.i8); - break; - - case ePAR_TYPE_U16: - *p_has_changed = (par_get_u16(par_num) != par_cfg->def.u16); - break; - - case ePAR_TYPE_I16: - *p_has_changed = (par_get_i16(par_num) != par_cfg->def.i16); - break; - - case ePAR_TYPE_U32: - *p_has_changed = (par_get_u32(par_num) != par_cfg->def.u32); - break; - - case ePAR_TYPE_I32: - *p_has_changed = (par_get_i32(par_num) != par_cfg->def.i32); - break; - -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) - case ePAR_TYPE_F32: - *p_has_changed = (par_get_f32(par_num) != par_cfg->def.f32); - break; -#endif - - case ePAR_TYPE_NUM_OF: - default: - PAR_ASSERT( 0 ); - return ePAR_ERROR_TYPE; - } - - return ePAR_OK; -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get parameter value -* -* @note Mandatory to cast input argument to appropriate type. E.g.: -* -* @code -* float32_t my_val = 0.0f; -* par_get( ePAR_MY_VAR, (float32_t*) &my_val ); -* @endcode -* -* @note Input is parameter number (enumeration) defined in par_cfg.h and not -* parameter ID number! -* -* @param[in] par_num - Parameter number (enumeration) -* @param[out] p_val - Parameter value -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_get(const par_num_t par_num, void * const p_val) -{ - switch ( par_get_type(par_num)) - { - case ePAR_TYPE_U8: - *(uint8_t*) p_val = par_get_u8(par_num); - break; - - case ePAR_TYPE_I8: - *(int8_t*) p_val = par_get_i8(par_num); - break; - - case ePAR_TYPE_U16: - *(uint16_t*) p_val = par_get_u16(par_num); - break; - - case ePAR_TYPE_I16: - *(int16_t*) p_val = par_get_i16(par_num); - break; - - case ePAR_TYPE_U32: - *(uint32_t*) p_val = par_get_u32(par_num); - break; - - case ePAR_TYPE_I32: - *(int32_t*) p_val = par_get_i32(par_num); - break; - -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) - case ePAR_TYPE_F32: - *(float32_t*) p_val = par_get_f32(par_num); - break; -#endif - - case ePAR_TYPE_NUM_OF: - default: - PAR_ASSERT( 0 ); - return ePAR_ERROR_TYPE; - } - - return ePAR_OK; -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get parameter value by ID -* -* @param[in] id - Parameter ID number -* @param[out] p_val - Pointer to value -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -#if ( 1 == PAR_CFG_ENABLE_ID ) -par_status_t par_get_by_id(const uint16_t id, void * const p_val) -{ - par_num_t par_num; - - if ( ePAR_OK == par_get_num_by_id( id, &par_num )) + if ( ePAR_OK == par_get_num_by_id( id, &par_num )) { return par_get( par_num, p_val ); } @@ -1835,158 +838,6 @@ par_status_t par_get_by_id(const uint16_t id, void * const p_val) } #endif -//////////////////////////////////////////////////////////////////////////////// -/** -* Get unsigned 8-bit parameter value -* -* @note Returning as *(volatile uint8_t*) prevent load tearing as explained -* here: https://lwn.net/Articles/793253/ -* -* @param[in] par_num - Parameter number (enumeration) -* @return value - Value of parameter -*/ -//////////////////////////////////////////////////////////////////////////////// -uint8_t par_get_u8(const par_num_t par_num) -{ - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_U8 == par_get_type(par_num)); - if( ePAR_TYPE_U8 != par_get_type(par_num)) return ePAR_ERROR_TYPE; - - return PAR_GET_U8_PRIV( par_num ); -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get signed 8-bit parameter value -* -* @param[in] par_num - Parameter number (enumeration) -* @return value - Value of parameter -*/ -//////////////////////////////////////////////////////////////////////////////// -int8_t par_get_i8(const par_num_t par_num) -{ - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_I8 == par_get_type(par_num)); - if( ePAR_TYPE_I8 != par_get_type(par_num)) return ePAR_ERROR_TYPE; - - return PAR_GET_I8_PRIV( par_num ); -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get unsigned 16-bit parameter value -* -* @param[in] par_num - Parameter number (enumeration) -* @return value - Value of parameter -*/ -//////////////////////////////////////////////////////////////////////////////// -uint16_t par_get_u16(const par_num_t par_num) -{ - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_U16 == par_get_type(par_num)); - if( ePAR_TYPE_U16 != par_get_type(par_num)) return ePAR_ERROR_TYPE; - - return PAR_GET_U16_PRIV( par_num ); -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get signed 16-bit parameter value -* -* @param[in] par_num - Parameter number (enumeration) -* @return value - Value of parameter -*/ -//////////////////////////////////////////////////////////////////////////////// -int16_t par_get_i16(const par_num_t par_num) -{ - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_I16 == par_get_type(par_num)); - if( ePAR_TYPE_I16 != par_get_type(par_num)) return ePAR_ERROR_TYPE; - - return PAR_GET_I16_PRIV( par_num ); -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get unsigned 32-bit parameter value -* -* @param[in] par_num - Parameter number (enumeration) -* @return value - Value of parameter -*/ -//////////////////////////////////////////////////////////////////////////////// -uint32_t par_get_u32(const par_num_t par_num) -{ - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_U32 == par_get_type(par_num)); - if( ePAR_TYPE_U32 != par_get_type(par_num)) return ePAR_ERROR_TYPE; - - return PAR_GET_U32_PRIV( par_num ); -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get signed 32-bit parameter value -* -* @param[in] par_num - Parameter number (enumeration) -* @return value - Value of parameter -*/ -//////////////////////////////////////////////////////////////////////////////// -int32_t par_get_i32(const par_num_t par_num) -{ - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_I32 == par_get_type(par_num)); - if( ePAR_TYPE_I32 != par_get_type(par_num)) return ePAR_ERROR_TYPE; - - return PAR_GET_I32_PRIV( par_num ); -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get floating value parameter value -* -* @param[in] par_num - Parameter number (enumeration) -* @return value - Value of parameter -*/ -//////////////////////////////////////////////////////////////////////////////// -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) -float32_t par_get_f32(const par_num_t par_num) -{ - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_F32 == par_get_type(par_num)); - if( ePAR_TYPE_F32 != par_get_type(par_num)) return ePAR_ERROR_TYPE; - - return PAR_GET_F32_PRIV( par_num ); -} -#endif - //////////////////////////////////////////////////////////////////////////////// /** * Get parameter default value diff --git a/src/par_bitwise_impl.inc b/src/par_bitwise_impl.inc new file mode 100644 index 0000000..d829850 --- /dev/null +++ b/src/par_bitwise_impl.inc @@ -0,0 +1,62 @@ +/* + * Private include fragment. Included only by par.c. + */ + +#define PAR_FOR_EACH_BITWISE_OP(OP, NAME, CTYPE, ETYPE, FIELD, STORAGE) \ + OP(bitand, AND, NAME, CTYPE, ETYPE, FIELD, STORAGE) \ + OP(bitor, OR, NAME, CTYPE, ETYPE, FIELD, STORAGE) + +#if (1 == PAR_CFG_ENABLE_RANGE) +#define PAR_BITWISE_FAST_SET_BODY(ATOMIC_OP, NAME, FIELD, STORAGE, par_num, val) \ + do \ + { \ + const par_range_t range = par_get_range(par_num); \ + if ((val) > range.max.FIELD) \ + { \ + PAR_ATOMIC_FETCH_##ATOMIC_OP(NAME, &(STORAGE)[g_par_offset[par_num]], range.max.FIELD); \ + return ePAR_WAR_LIMITED; \ + } \ + else if ((val) < range.min.FIELD) \ + { \ + PAR_ATOMIC_FETCH_##ATOMIC_OP(NAME, &(STORAGE)[g_par_offset[par_num]], range.min.FIELD); \ + return ePAR_WAR_LIMITED; \ + } \ + else \ + { \ + PAR_ATOMIC_FETCH_##ATOMIC_OP(NAME, &(STORAGE)[g_par_offset[par_num]], (val)); \ + return ePAR_OK; \ + } \ + } while (0) +#else +#define PAR_BITWISE_FAST_SET_BODY(ATOMIC_OP, NAME, FIELD, STORAGE, par_num, val) \ + do \ + { \ + PAR_ATOMIC_FETCH_##ATOMIC_OP(NAME, &(STORAGE)[g_par_offset[par_num]], (val)); \ + return ePAR_OK; \ + } while (0) +#endif + +#define PAR_IMPL_BITWISE_FAST_SETTER(OP_NAME, ATOMIC_OP, NAME, CTYPE, ETYPE, FIELD, STORAGE) \ + par_status_t par_##OP_NAME##_set_##NAME##_fast(const par_num_t par_num, const CTYPE val) \ + { \ + PAR_ASSERT(true == par_is_init()); \ + PAR_ASSERT(ETYPE == par_get_type(par_num)); \ + PAR_BITWISE_FAST_SET_BODY(ATOMIC_OP, NAME, FIELD, STORAGE, par_num, val); \ + } + +#define PAR_BITWISE_ROWS(OP) \ + OP(u8, uint8_t, ePAR_TYPE_U8, u8, gpu8_par_value) \ + OP(u16, uint16_t, ePAR_TYPE_U16, u16, gpu16_par_value) \ + OP(u32, uint32_t, ePAR_TYPE_U32, u32, gpu32_par_value) + +#define PAR_IMPL_BITWISE_WIDTH(NAME, CTYPE, ETYPE, FIELD, STORAGE) \ + PAR_FOR_EACH_BITWISE_OP(PAR_IMPL_BITWISE_FAST_SETTER, NAME, CTYPE, ETYPE, FIELD, STORAGE) + +PAR_BITWISE_ROWS(PAR_IMPL_BITWISE_WIDTH) + +#undef PAR_IMPL_BITWISE_WIDTH +#undef PAR_BITWISE_ROWS + +#undef PAR_IMPL_BITWISE_FAST_SETTER +#undef PAR_BITWISE_FAST_SET_BODY +#undef PAR_FOR_EACH_BITWISE_OP diff --git a/src/par_storage_init.inc b/src/par_storage_init.inc new file mode 100644 index 0000000..20e741e --- /dev/null +++ b/src/par_storage_init.inc @@ -0,0 +1,86 @@ +/* + * Private include fragment. Included only by par.c. + */ + +#define PAR_STORAGE_INIT_NOP(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#define PAR_STORAGE_U8_FROM_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u8_t)((uint8_t)(def_)), +#define PAR_STORAGE_U8_FROM_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u8_t)((uint8_t)(int8_t)(def_)), +#define PAR_STORAGE_U16_FROM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u16_t)((uint16_t)(def_)), +#define PAR_STORAGE_U16_FROM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u16_t)((uint16_t)(int16_t)(def_)), +#define PAR_STORAGE_U32_FROM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u32_t)((uint32_t)(def_)), +#define PAR_STORAGE_U32_FROM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u32_t)((uint32_t)(int32_t)(def_)), +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) +#define PAR_STORAGE_U32_FROM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u32_t)(0u), +#endif + +#define PAR_ITEM_U8 PAR_STORAGE_U8_FROM_U8 +#define PAR_ITEM_U16 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_U32 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_I8 PAR_STORAGE_U8_FROM_I8 +#define PAR_ITEM_I16 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_I32 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_F32 PAR_STORAGE_INIT_NOP +static par_atomic_u8_t gs_par_u8_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT8)] = +{ + #include "../../par_table.def" +}; +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 + +#define PAR_ITEM_U8 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_U16 PAR_STORAGE_U16_FROM_U16 +#define PAR_ITEM_U32 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_I8 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_I16 PAR_STORAGE_U16_FROM_I16 +#define PAR_ITEM_I32 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_F32 PAR_STORAGE_INIT_NOP +static par_atomic_u16_t gs_par_u16_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT16)] = +{ + #include "../../par_table.def" +}; +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 + +#define PAR_ITEM_U8 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_U16 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_U32 PAR_STORAGE_U32_FROM_U32 +#define PAR_ITEM_I8 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_I16 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_I32 PAR_STORAGE_U32_FROM_I32 +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) + #define PAR_ITEM_F32 PAR_STORAGE_U32_FROM_F32 +#else + #define PAR_ITEM_F32 PAR_STORAGE_INIT_NOP +#endif +static par_atomic_u32_t gs_par_u32_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT32)] = +{ + #include "../../par_table.def" +}; +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 + +#undef PAR_STORAGE_INIT_NOP +#undef PAR_STORAGE_U8_FROM_U8 +#undef PAR_STORAGE_U8_FROM_I8 +#undef PAR_STORAGE_U16_FROM_U16 +#undef PAR_STORAGE_U16_FROM_I16 +#undef PAR_STORAGE_U32_FROM_U32 +#undef PAR_STORAGE_U32_FROM_I32 +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) +#undef PAR_STORAGE_U32_FROM_F32 +#endif diff --git a/src/par_typed_impl.inc b/src/par_typed_impl.inc new file mode 100644 index 0000000..e26e417 --- /dev/null +++ b/src/par_typed_impl.inc @@ -0,0 +1,160 @@ +/* Private include fragment. Included only by par.c. */ + +#define PAR_TYPED_ROWS_BASE(OP) \ + OP(u8, U8, uint8_t, ePAR_TYPE_U8, u8) \ + OP(i8, I8, int8_t, ePAR_TYPE_I8, i8) \ + OP(u16, U16, uint16_t, ePAR_TYPE_U16, u16) \ + OP(i16, I16, int16_t, ePAR_TYPE_I16, i16) \ + OP(u32, U32, uint32_t, ePAR_TYPE_U32, u32) \ + OP(i32, I32, int32_t, ePAR_TYPE_I32, i32) + +#if (1 == PAR_CFG_ENABLE_TYPE_F32) +#define PAR_TYPED_ROWS_F32(OP) OP(f32, F32, float32_t, ePAR_TYPE_F32, f32) +#else +#define PAR_TYPED_ROWS_F32(OP) +#endif + +#define PAR_TYPED_ROWS(OP) \ + PAR_TYPED_ROWS_BASE(OP) \ + PAR_TYPED_ROWS_F32(OP) + +#if (1 == PAR_CFG_ENABLE_CHANGE_CALLBACK) +#define PAR_TYPED_SETTER_OLD_VAL_DECL(PRIV, FIELD, par_num) \ + const par_type_t old_val = { .FIELD = PAR_GET_##PRIV##_PRIV(par_num) }; +#define PAR_TYPED_SETTER_ON_CHANGE(PRIV, FIELD, par_num) \ + do \ + { \ + pf_par_on_change_cb_t on_change = g_par_cb_table[(par_num)].on_change; \ + const par_type_t new_val = { .FIELD = PAR_GET_##PRIV##_PRIV((par_num)) }; \ + if ((on_change != NULL) && (new_val.FIELD != old_val.FIELD)) \ + { \ + on_change((par_num), new_val, old_val); \ + } \ + } while (0) +#else +#define PAR_TYPED_SETTER_OLD_VAL_DECL(PRIV, FIELD, par_num) +#define PAR_TYPED_SETTER_ON_CHANGE(PRIV, FIELD, par_num) \ + do \ + { \ + (void)(par_num); \ + } while (0) +#endif + +#if (1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION) +#define PAR_TYPED_SETTER_VALIDATE_OR_SET(NAME, FIELD, par_num, val, status_var) \ + do \ + { \ + pf_par_validation_t validation = g_par_cb_table[(par_num)].validation; \ + if ((validation != NULL) && !validation((par_num), (par_type_t){ .FIELD = (val) })) \ + { \ + (status_var) = ePAR_ERROR_VALUE; \ + } \ + else \ + { \ + (status_var) = par_set_##NAME##_fast((par_num), (val)); \ + } \ + } while (0) +#else +#define PAR_TYPED_SETTER_VALIDATE_OR_SET(NAME, FIELD, par_num, val, status_var) \ + do \ + { \ + (status_var) = par_set_##NAME##_fast((par_num), (val)); \ + } while (0) +#endif + +#if (1 == PAR_CFG_ENABLE_RANGE) +#define PAR_TYPED_FAST_SET_BODY(PRIV, FIELD, par_num, val) \ + do \ + { \ + const par_range_t range = par_get_range(par_num); \ + if ((val) > range.max.FIELD) \ + { \ + PAR_SET_##PRIV##_PRIV((par_num), range.max.FIELD); \ + return ePAR_WAR_LIMITED; \ + } \ + else if ((val) < range.min.FIELD) \ + { \ + PAR_SET_##PRIV##_PRIV((par_num), range.min.FIELD); \ + return ePAR_WAR_LIMITED; \ + } \ + else \ + { \ + PAR_SET_##PRIV##_PRIV((par_num), (val)); \ + return ePAR_OK; \ + } \ + } while (0) +#else +#define PAR_TYPED_FAST_SET_BODY(PRIV, FIELD, par_num, val) \ + do \ + { \ + PAR_SET_##PRIV##_PRIV((par_num), (val)); \ + return ePAR_OK; \ + } while (0) +#endif + +#define PAR_TYPED_IMPL_SETTER(NAME, PRIV, CTYPE, ETYPE, FIELD) \ + par_status_t par_set_##NAME(const par_num_t par_num, const CTYPE val) \ + { \ + par_status_t status = ePAR_OK; \ + \ + PAR_ASSERT(true == par_is_init()); \ + if (true != par_is_init()) \ + return ePAR_ERROR_INIT; \ + \ + PAR_ASSERT(ETYPE == par_get_type(par_num)); \ + if (ETYPE != par_get_type(par_num)) \ + return ePAR_ERROR_TYPE; \ + \ + if (ePAR_OK == par_acquire_mutex(par_num)) \ + { \ + PAR_TYPED_SETTER_OLD_VAL_DECL(PRIV, FIELD, par_num) \ + PAR_TYPED_SETTER_VALIDATE_OR_SET(NAME, FIELD, par_num, val, status); \ + PAR_TYPED_SETTER_ON_CHANGE(PRIV, FIELD, par_num); \ + par_release_mutex(par_num); \ + } \ + else \ + { \ + status = ePAR_ERROR_MUTEX; \ + } \ + \ + return status; \ + } + +PAR_TYPED_ROWS(PAR_TYPED_IMPL_SETTER) +#undef PAR_TYPED_IMPL_SETTER + +#define PAR_TYPED_IMPL_FAST_SETTER(NAME, PRIV, CTYPE, ETYPE, FIELD) \ + par_status_t par_set_##NAME##_fast(const par_num_t par_num, const CTYPE val) \ + { \ + PAR_ASSERT(true == par_is_init()); \ + PAR_ASSERT(ETYPE == par_get_type(par_num)); \ + PAR_TYPED_FAST_SET_BODY(PRIV, FIELD, par_num, val); \ + } + +PAR_TYPED_ROWS(PAR_TYPED_IMPL_FAST_SETTER) +#undef PAR_TYPED_IMPL_FAST_SETTER + +#define PAR_TYPED_IMPL_GETTER(NAME, PRIV, CTYPE, ETYPE, FIELD) \ + CTYPE par_get_##NAME(const par_num_t par_num) \ + { \ + PAR_ASSERT(true == par_is_init()); \ + if (true != par_is_init()) \ + return (CTYPE)ePAR_ERROR_INIT; \ + \ + PAR_ASSERT(ETYPE == par_get_type(par_num)); \ + if (ETYPE != par_get_type(par_num)) \ + return (CTYPE)ePAR_ERROR_TYPE; \ + \ + return PAR_GET_##PRIV##_PRIV(par_num); \ + } + +PAR_TYPED_ROWS(PAR_TYPED_IMPL_GETTER) +#undef PAR_TYPED_IMPL_GETTER + +#undef PAR_TYPED_FAST_SET_BODY +#undef PAR_TYPED_SETTER_VALIDATE_OR_SET +#undef PAR_TYPED_SETTER_ON_CHANGE +#undef PAR_TYPED_SETTER_OLD_VAL_DECL +#undef PAR_TYPED_ROWS +#undef PAR_TYPED_ROWS_F32 +#undef PAR_TYPED_ROWS_BASE diff --git a/template/par_layout_static.htmp b/template/par_layout_static.htmp index d35efc0..3bcc33f 100644 --- a/template/par_layout_static.htmp +++ b/template/par_layout_static.htmp @@ -1,3 +1,17 @@ +/** + * @file par_layout_static.h + * @brief + * @author wdfk-prog () + * @version 1.0 + * @date 2026-03-21 + * + * @copyright Copyright (c) 2026 + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-21 1.0 wdfk-prog first version + */ #ifndef _PAR_LAYOUT_STATIC_H_ #define _PAR_LAYOUT_STATIC_H_ @@ -16,15 +30,15 @@ extern "C" { * - PAR_LAYOUT_STATIC_COUNT8/16/32 * - PAR_LAYOUT_STATIC_OFFSET_TABLE (points to a const offset table) */ -#define PAR_LAYOUT_STATIC_COUNT8 ( 0u ) -#define PAR_LAYOUT_STATIC_COUNT16 ( 0u ) -#define PAR_LAYOUT_STATIC_COUNT32 ( 0u ) +#define PAR_LAYOUT_STATIC_COUNT8 (0u) +#define PAR_LAYOUT_STATIC_COUNT16 (0u) +#define PAR_LAYOUT_STATIC_COUNT32 (0u) /** * Static offset map generated by external script. */ extern const uint16_t g_par_layout_static_offset[ePAR_NUM_OF]; -#define PAR_LAYOUT_STATIC_OFFSET_TABLE ( g_par_layout_static_offset ) +#define PAR_LAYOUT_STATIC_OFFSET_TABLE (g_par_layout_static_offset) #ifdef __cplusplus } From b898842832b460cfa8e2fda739c9eae901cbfe40 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Mon, 23 Mar 2026 14:43:25 +0800 Subject: [PATCH 16/36] feat(core): Add raw reset-all API for parameter manager --- README.md | 1 + docs/api-reference.md | 6 +++- docs/architecture.md | 14 +++++++++ docs/getting-started.md | 21 ++++++++++++- src/par.c | 69 +++++++++++++++++++++++++++++++++++++++-- src/par.h | 20 ++++++++++++ src/par_cfg.h | 18 +++++++++++ 7 files changed, 145 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4b24541..775cffb 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,7 @@ This repository contains the reusable module core and templates. A real integrat - Fast setter APIs skip part of the safety and observability path, including runtime validation callbacks and on-change callbacks, so they should be reserved for tightly controlled hot paths. - NVM support is optional, but when enabled it depends on the external NVM module and on ID and persistence metadata being enabled. - `par_init()` applies startup default values directly to live storage. Integer default values from `par_table.def` are compiled into the shared width-based storage arrays, while `F32` default values are applied after layout offsets are available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. Because this startup initialization does not go through the public setter path, it does not invoke runtime validation or on-change callbacks. +- `PAR_CFG_ENABLE_RESET_ALL_RAW` controls whether `par_reset_all_to_default_raw()` and the default mirror storage are compiled in. Unlike `par_set_all_to_default()`, the raw reset path restores grouped storage directly and bypasses normal setter hooks, including validation, change callbacks, and setter-side range behavior. ## Related projects diff --git a/docs/api-reference.md b/docs/api-reference.md index 8508d64..7b65121 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -31,6 +31,8 @@ The module conditionally compiles parts of the API based on configuration. - `PAR_CFG_ENABLE_CHANGE_CALLBACK = 1` enables: - `par_register_on_change_cb()` - on-change callbacks in normal setter paths +- `PAR_CFG_ENABLE_RESET_ALL_RAW = 1` enables: + - `par_reset_all_to_default_raw()` ## Lifecycle @@ -113,6 +115,7 @@ Use these only in controlled hot paths. | --- | --- | | `par_set_to_default(par_num)` | Reset one parameter to its default value. | | `par_set_all_to_default()` | Reset all parameters to their default values. | +| `par_reset_all_to_default_raw()` | Restore all live values from width-group default mirrors (`U8/U16/U32`) via raw memory copy. Available only when `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`. | | `par_has_changed(par_num, p_has_changed)` | Report whether the value differs from its default. | | `par_is_changed(par_num)` | Return whether the value differs from its default. | @@ -122,8 +125,9 @@ They are different from startup initialization: - `par_init()` applies the default values defined in `par_table.def` directly to live storage - `par_set_to_default()` and `par_set_all_to_default()` still use the normal runtime value path +- `par_reset_all_to_default_raw()` restores storage directly and bypasses normal setter hooks -That distinction matters if your application depends on validation callbacks, on-change callbacks, or other setter-side effects, because those runtime hooks apply only in the normal setter path and only when the matching callback features are enabled. +That distinction matters if your application depends on validation callbacks, on-change callbacks, range behavior, or other setter-side effects, because those runtime hooks apply only in the normal setter path and only when the matching callback features are enabled. ## Pointer-based getters diff --git a/docs/architecture.md b/docs/architecture.md index 2885432..0649c5c 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -111,6 +111,8 @@ Live storage is initialized in two phases during startup: The compile-time integer storage initializers are emitted through a private include fragment, `par_storage_init.inc`, which is included only by `par.c`. +When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`, `par.c` keeps width-group default mirror storage for raw reset (`U8/U16/U32`). + This means `par_init()` does not need to apply startup defaults through the public setter path for every parameter. ### Ordering contract for shared storage @@ -240,6 +242,18 @@ Fast setters do not execute runtime validation callbacks or on-change callbacks. The bitwise fast helpers are emitted through `par_bitwise_impl.inc`, another private include fragment included only by `par.c`. +### Raw reset-all path + +When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`, `par_reset_all_to_default_raw()` is available as a storage-level reset path. + +It restores live values by three width-group `memcpy` operations (`U8/U16/U32`) from default mirrors, so it bypasses: + +- runtime validation callbacks +- on-change callbacks +- normal setter range/flow semantics + +This path is intentionally separate from `par_set_all_to_default()`, which keeps normal runtime setter behavior. + ## Optional NVM persistence When `PAR_CFG_NVM_EN = 1`, the module can persist selected parameters to NVM. diff --git a/docs/getting-started.md b/docs/getting-started.md index d8dcdee..8e60a3e 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -12,7 +12,8 @@ This guide shows how to integrate the `Device Parameters` module into a firmware - a platform-specific interface backend - a platform-specific atomic backend - compile-scan or script-provided layout - - whether `F32` parameter support should be compiled in + - raw reset-all support + - `F32` parameter support 5. Call `par_init()` before runtime access. 6. Use typed APIs or the typed macro wrappers such as `PAR_SET_U16` and `PAR_GET_U16`. @@ -174,6 +175,23 @@ When `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 0`, `par_register_validation()` is not When `PAR_CFG_ENABLE_CHANGE_CALLBACK = 0`, `par_register_on_change_cb()` is not available. +### Raw reset-all API +Use `PAR_CFG_ENABLE_RESET_ALL_RAW` to control whether the raw reset-all API and default mirror storage are compiled in. + +```c +#define PAR_CFG_ENABLE_RESET_ALL_RAW ( 1 ) +``` + +When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`: + +* `par_reset_all_to_default_raw()` is available +* default mirror arrays are kept for `U8/U16/U32` width groups +* `F32` defaults are mirrored into the 32-bit group as bit patterns after layout is known + +`par_reset_all_to_default_raw()` restores live values by width-group memory copy and intentionally bypasses runtime validation callbacks, on-change callbacks, and range logic. + +The default mirrors are built before optional NVM load, so `par_reset_all_to_default_raw()` restores configured defaults, not persisted runtime values loaded from NVM. + ### F32 type support Use `PAR_CFG_ENABLE_TYPE_F32` to control whether `F32` parameters are compiled into the module. @@ -202,6 +220,7 @@ if (par_init() != ePAR_OK) ``` If `PAR_CFG_NVM_EN = 1`, NVM loading happens after the module applies default values from `par_table.def`, so persisted values can overwrite the startup defaults. +When raw reset-all is enabled, its default mirrors are built before that optional NVM load, so raw reset returns live storage to the configured defaults rather than to persisted NVM-loaded values. ### How `par_init()` applies default values diff --git a/src/par.c b/src/par.c index 7ced3d9..6d0016d 100644 --- a/src/par.c +++ b/src/par.c @@ -142,6 +142,18 @@ static struct */ #include "par_storage_init.inc" +#if ( 1 == PAR_CFG_ENABLE_RESET_ALL_RAW ) +/** + * Runtime default mirror storage for raw reset-all API. + * + * @note Mirrors are initialized in par_init() from current live defaults + * after F32 startup patch and before optional NVM load. + */ +static par_atomic_u8_t gs_par_u8_default_mirror[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT8)] = {0}; +static par_atomic_u16_t gs_par_u16_default_mirror[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT16)] = {0}; +static par_atomic_u32_t gs_par_u32_default_mirror[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT32)] = {0}; +#endif + /** * Parameter live values divided by its type in RAM */ @@ -470,9 +482,19 @@ par_status_t par_init(void) * Integer defaults are already initialized at definition time. * F32 defaults are patched once after layout offsets are available. */ - #if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) par_patch_f32_defaults_from_table(); - #endif +#endif + +#if ( 1 == PAR_CFG_ENABLE_RESET_ALL_RAW ) + /* + * Build default mirrors from current live defaults. + * This snapshot is taken before optional NVM load. + */ + memcpy( gs_par_u8_default_mirror, gs_par_u8_storage, sizeof(gs_par_u8_storage) ); + memcpy( gs_par_u16_default_mirror, gs_par_u16_storage, sizeof(gs_par_u16_storage) ); + memcpy( gs_par_u32_default_mirror, gs_par_u32_storage, sizeof(gs_par_u32_storage) ); +#endif #if ( 1 == PAR_CFG_NVM_EN ) // Init and load parameters from NVM @@ -681,6 +703,8 @@ par_status_t par_set_to_default(const par_num_t par_num) * Set all parameters to default value * * @pre Parameters must be initialised before usage! +* @note This function uses normal runtime setter path via par_set_to_default() +* and keeps setter semantics. * * @return status - Status of operation */ @@ -697,6 +721,47 @@ par_status_t par_set_all_to_default(void) return ePAR_OK; } +#if ( 1 == PAR_CFG_ENABLE_RESET_ALL_RAW ) +//////////////////////////////////////////////////////////////////////////////// +/** +* Reset all parameters to default values via raw storage restore +* +* @note Unlike par_set_all_to_default(), this API restores grouped +* storage directly from default mirrors instead of iterating over +* all parameters through the normal setter path. +* +* @note This path is therefore typically faster for bulk reset, because +* it avoids per-parameter runtime validation, on-change callback, +* and setter-side range handling. +* +* @note Restore is performed per storage width group. +* +* @pre Parameters must be initialized before usage. +* +* @return status - Status of operation +*/ +//////////////////////////////////////////////////////////////////////////////// +par_status_t par_reset_all_to_default_raw(void) +{ + PAR_ASSERT( true == par_is_init()); + if ( true != par_is_init()) return ePAR_ERROR_INIT; + + if ( ePAR_OK != par_acquire_mutex((par_num_t)0)) + { + return ePAR_ERROR_MUTEX; + } + + memcpy( gs_par_u8_storage, gs_par_u8_default_mirror, sizeof(gs_par_u8_storage) ); + memcpy( gs_par_u16_storage, gs_par_u16_default_mirror, sizeof(gs_par_u16_storage) ); + memcpy( gs_par_u32_storage, gs_par_u32_default_mirror, sizeof(gs_par_u32_storage) ); + + par_release_mutex((par_num_t)0); + + PAR_DBG_PRINT( "PAR: Raw reset all parameters to default" ); + return ePAR_OK; +} +#endif + //////////////////////////////////////////////////////////////////////////////// /** * Check if parameter changed from its default value diff --git a/src/par.h b/src/par.h index 205f916..b6cd54c 100644 --- a/src/par.h +++ b/src/par.h @@ -207,7 +207,27 @@ par_status_t par_bitor_set_u8_fast (const par_num_t par_num, const uint8_t val) par_status_t par_bitor_set_u16_fast (const par_num_t par_num, const uint16_t val); par_status_t par_bitor_set_u32_fast (const par_num_t par_num, const uint32_t val); par_status_t par_set_to_default (const par_num_t par_num); + +/** + * Reset all parameters through normal runtime setter path. + * + * @note This path keeps setter semantics (validation/callback/range behavior + * depending on build-time configuration). + */ par_status_t par_set_all_to_default (void); +#if ( 1 == PAR_CFG_ENABLE_RESET_ALL_RAW ) +/** + * Reset all parameters through raw storage restore. + * + * @note This path is typically faster than par_set_all_to_default(), because + * it restores grouped storage directly instead of resetting parameters + * one by one through the normal setter path. + * + * @note This path bypasses normal setter hooks (validation, on-change callback, + * and setter-side range behavior). + */ +par_status_t par_reset_all_to_default_raw(void); +#endif par_status_t par_has_changed (const par_num_t par_num, bool *const p_has_changed); diff --git a/src/par_cfg.h b/src/par_cfg.h index 594826b..4de34b7 100644 --- a/src/par_cfg.h +++ b/src/par_cfg.h @@ -302,6 +302,24 @@ #define PAR_CFG_ENABLE_CHANGE_CALLBACK ( 1 ) #endif +/** + * Enable/Disable raw reset-all API and default mirror storage + * + * @note When enabled, module compiles par_reset_all_to_default_raw() and + * keeps per-width default mirror arrays in firmware image. + * + * @note The raw reset path restores parameter storage directly from the + * default mirrors, so it is typically faster than + * par_set_all_to_default(), which resets parameters one by one through + * the normal runtime setter path. + * + * @note The speedup comes from bypassing per-parameter setter-side logic such + * as runtime validation, change callback, and range handling. + */ +#ifndef PAR_CFG_ENABLE_RESET_ALL_RAW + #define PAR_CFG_ENABLE_RESET_ALL_RAW ( 1 ) +#endif + /** * Enable/Disable parameter range metadata (min/max) */ From df1d562ae90d4e152f41bcc376049a6b4fbbad60 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Mon, 23 Mar 2026 17:53:43 +0800 Subject: [PATCH 17/36] refactor(core): Group parameter storage and align raw reset documentation --- README.md | 4 +-- docs/api-reference.md | 2 +- docs/architecture.md | 10 +++---- docs/getting-started.md | 15 ++++++----- src/par.c | 58 ++++++++++++++++++++++++---------------- src/par_cfg.h | 12 ++++----- src/par_storage_init.inc | 36 ++++++++++++++----------- 7 files changed, 78 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 775cffb..dca3e6c 100644 --- a/README.md +++ b/README.md @@ -124,8 +124,8 @@ This repository contains the reusable module core and templates. A real integrat - The module separates **internal parameter enumeration** (`par_num_t`) from **external parameter IDs** (`id`). - Fast setter APIs skip part of the safety and observability path, including runtime validation callbacks and on-change callbacks, so they should be reserved for tightly controlled hot paths. - NVM support is optional, but when enabled it depends on the external NVM module and on ID and persistence metadata being enabled. -- `par_init()` applies startup default values directly to live storage. Integer default values from `par_table.def` are compiled into the shared width-based storage arrays, while `F32` default values are applied after layout offsets are available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. Because this startup initialization does not go through the public setter path, it does not invoke runtime validation or on-change callbacks. -- `PAR_CFG_ENABLE_RESET_ALL_RAW` controls whether `par_reset_all_to_default_raw()` and the default mirror storage are compiled in. Unlike `par_set_all_to_default()`, the raw reset path restores grouped storage directly and bypasses normal setter hooks, including validation, change callbacks, and setter-side range behavior. +- `par_init()` applies startup default values directly to live storage. Integer default values from `par_table.def` are compiled into a grouped width-based storage object, while `F32` default values are applied to the 32-bit storage group after layout offsets are available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. Because this startup initialization does not go through the public setter path, it does not invoke runtime validation or on-change callbacks. +- `PAR_CFG_ENABLE_RESET_ALL_RAW` controls whether raw reset-all support and grouped default mirror snapshot support are enabled. ## Related projects diff --git a/docs/api-reference.md b/docs/api-reference.md index 7b65121..2273d81 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -115,7 +115,7 @@ Use these only in controlled hot paths. | --- | --- | | `par_set_to_default(par_num)` | Reset one parameter to its default value. | | `par_set_all_to_default()` | Reset all parameters to their default values. | -| `par_reset_all_to_default_raw()` | Restore all live values from width-group default mirrors (`U8/U16/U32`) via raw memory copy. Available only when `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`. | +| `par_reset_all_to_default_raw()` | Restore all live values from a grouped default mirror snapshot via raw memory copy. The internal storage model still uses `U8/U16/U32` width groups. Available only when `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`. | | `par_has_changed(par_num, p_has_changed)` | Report whether the value differs from its default. | | `par_is_changed(par_num)` | Return whether the value differs from its default. | diff --git a/docs/architecture.md b/docs/architecture.md index 0649c5c..67bf43d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -106,12 +106,12 @@ At runtime, each parameter resolves to: Live storage is initialized in two phases during startup: -1. Integer default values from `par_table.def` are compiled directly into the shared storage arrays. -2. When `PAR_CFG_ENABLE_TYPE_F32 = 1`, `F32` default values are written into the shared 32-bit storage after layout offsets are available. +1. Integer default values from `par_table.def` are compiled directly into the grouped live storage object in `par.c`. +2. When `PAR_CFG_ENABLE_TYPE_F32 = 1`, `F32` default values are written into the grouped 32-bit storage member after layout offsets are available. -The compile-time integer storage initializers are emitted through a private include fragment, `par_storage_init.inc`, which is included only by `par.c`. +The compile-time integer storage initializers are emitted through a private include fragment, `par_storage_init.inc`, which is included only by `par.c` and initializes the grouped storage object (`U8/U16/U32` members). -When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`, `par.c` keeps width-group default mirror storage for raw reset (`U8/U16/U32`). +When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`, `par.c` keeps a grouped default mirror snapshot for raw reset. The snapshot preserves the same `U8/U16/U32` width-group storage semantics. This means `par_init()` does not need to apply startup defaults through the public setter path for every parameter. @@ -246,7 +246,7 @@ The bitwise fast helpers are emitted through `par_bitwise_impl.inc`, another pri When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`, `par_reset_all_to_default_raw()` is available as a storage-level reset path. -It restores live values by three width-group `memcpy` operations (`U8/U16/U32`) from default mirrors, so it bypasses: +It restores live values by one grouped `memcpy` from the default mirror snapshot, while still preserving the internal `U8/U16/U32` width-group storage model, so it bypasses: - runtime validation callbacks - on-change callbacks diff --git a/docs/getting-started.md b/docs/getting-started.md index 8e60a3e..f26452c 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -185,12 +185,13 @@ Use `PAR_CFG_ENABLE_RESET_ALL_RAW` to control whether the raw reset-all API and When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`: * `par_reset_all_to_default_raw()` is available -* default mirror arrays are kept for `U8/U16/U32` width groups -* `F32` defaults are mirrored into the 32-bit group as bit patterns after layout is known +* a grouped default mirror snapshot is kept in RAM for raw restore +* the grouped snapshot preserves the internal `U8/U16/U32` width-group storage layout +* `F32` defaults are mirrored into the 32-bit storage group as bit patterns after layout is known -`par_reset_all_to_default_raw()` restores live values by width-group memory copy and intentionally bypasses runtime validation callbacks, on-change callbacks, and range logic. +`par_reset_all_to_default_raw()` restores live values by copying the grouped storage snapshot and intentionally bypasses runtime validation callbacks, on-change callbacks, and range logic. -The default mirrors are built before optional NVM load, so `par_reset_all_to_default_raw()` restores configured defaults, not persisted runtime values loaded from NVM. +The grouped default mirror snapshot is built before optional NVM load, so `par_reset_all_to_default_raw()` restores configured defaults, not persisted runtime values loaded from NVM. ### F32 type support Use `PAR_CFG_ENABLE_TYPE_F32` to control whether `F32` parameters are compiled into the module. @@ -220,7 +221,7 @@ if (par_init() != ePAR_OK) ``` If `PAR_CFG_NVM_EN = 1`, NVM loading happens after the module applies default values from `par_table.def`, so persisted values can overwrite the startup defaults. -When raw reset-all is enabled, its default mirrors are built before that optional NVM load, so raw reset returns live storage to the configured defaults rather than to persisted NVM-loaded values. +When raw reset-all is enabled, its grouped default mirror snapshot is built before that optional NVM load, so raw reset returns live storage to the configured defaults rather than to persisted NVM-loaded values. ### How `par_init()` applies default values @@ -228,8 +229,8 @@ When raw reset-all is enabled, its default mirrors are built before that optiona During startup: -- integer default values defined in `par_table.def` are already present in the shared storage arrays at definition time -- when `PAR_CFG_ENABLE_TYPE_F32 = 1`, `F32` default values are written after layout offsets are known +- integer default values defined in `par_table.def` are already present in the grouped live storage object at definition time +- when `PAR_CFG_ENABLE_TYPE_F32 = 1`, `F32` default values are written into the grouped 32-bit storage member after layout offsets are known - if NVM support is enabled, persisted values may then overwrite those default values Do not rely on startup initialization to trigger application callbacks or runtime validation hooks. diff --git a/src/par.c b/src/par.c index 6d0016d..9d62956 100644 --- a/src/par.c +++ b/src/par.c @@ -135,36 +135,51 @@ static struct #endif /** - * Static typed storage backing parameter live values. + * Grouped typed storage backing parameter live values. + * + * @note Storage is organized as U8/U16/U32 typed members inside one private + * grouped storage object. * * @note Zero-length groups are mapped to size 1 arrays for compiler portability. - * Private implementation fragments. Do not include outside par.c. + * + * @note Private implementation fragment below must not be included outside par.c. + */ +typedef struct +{ + par_atomic_u8_t u8[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT8)]; + par_atomic_u16_t u16[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT16)]; + par_atomic_u32_t u32[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT32)]; +} par_storage_groups_t; + +/* + * Private implementation fragment. Do not include outside par.c. + * Defines gs_par_storage with grouped typed initializers. */ #include "par_storage_init.inc" #if ( 1 == PAR_CFG_ENABLE_RESET_ALL_RAW ) /** - * Runtime default mirror storage for raw reset-all API. + * Runtime grouped default mirror storage for raw reset-all API. + * + * @note Mirrors are initialized in par_init() from current live defaults + * after F32 startup patch and before optional NVM load. * - * @note Mirrors are initialized in par_init() from current live defaults - * after F32 startup patch and before optional NVM load. + * @note Mirror layout matches the grouped live storage object. */ -static par_atomic_u8_t gs_par_u8_default_mirror[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT8)] = {0}; -static par_atomic_u16_t gs_par_u16_default_mirror[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT16)] = {0}; -static par_atomic_u32_t gs_par_u32_default_mirror[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT32)] = {0}; +static par_storage_groups_t gs_par_default_mirror = {0}; #endif /** - * Parameter live values divided by its type in RAM + * Typed live-value access pointers into grouped storage. */ -static par_atomic_u8_t * gpu8_par_value = gs_par_u8_storage; -static par_atomic_i8_t * gpi8_par_value = (par_atomic_i8_t *)gs_par_u8_storage; -static par_atomic_u16_t * gpu16_par_value = gs_par_u16_storage; -static par_atomic_i16_t * gpi16_par_value = (par_atomic_i16_t *)gs_par_u16_storage; -static par_atomic_u32_t * gpu32_par_value = gs_par_u32_storage; -static par_atomic_i32_t * gpi32_par_value = (par_atomic_i32_t *)gs_par_u32_storage; +static par_atomic_u8_t * const gpu8_par_value = gs_par_storage.u8; +static par_atomic_i8_t * const gpi8_par_value = (par_atomic_i8_t *)gs_par_storage.u8; +static par_atomic_u16_t * const gpu16_par_value = gs_par_storage.u16; +static par_atomic_i16_t * const gpi16_par_value = (par_atomic_i16_t *)gs_par_storage.u16; +static par_atomic_u32_t * const gpu32_par_value = gs_par_storage.u32; +static par_atomic_i32_t * const gpi32_par_value = (par_atomic_i32_t *)gs_par_storage.u32; #if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) -static par_atomic_f32_t * gpf32_par_value = (par_atomic_f32_t *)gs_par_u32_storage; +static par_atomic_f32_t * const gpf32_par_value = (par_atomic_f32_t *)gs_par_storage.u32; #endif /** @@ -491,9 +506,7 @@ par_status_t par_init(void) * Build default mirrors from current live defaults. * This snapshot is taken before optional NVM load. */ - memcpy( gs_par_u8_default_mirror, gs_par_u8_storage, sizeof(gs_par_u8_storage) ); - memcpy( gs_par_u16_default_mirror, gs_par_u16_storage, sizeof(gs_par_u16_storage) ); - memcpy( gs_par_u32_default_mirror, gs_par_u32_storage, sizeof(gs_par_u32_storage) ); + memcpy( &gs_par_default_mirror, &gs_par_storage, sizeof(gs_par_storage) ); #endif #if ( 1 == PAR_CFG_NVM_EN ) @@ -734,7 +747,8 @@ par_status_t par_set_all_to_default(void) * it avoids per-parameter runtime validation, on-change callback, * and setter-side range handling. * -* @note Restore is performed per storage width group. +* @note Restore is performed as one grouped storage snapshot copy. +* Internal U8/U16/U32 width-group storage semantics are preserved. * * @pre Parameters must be initialized before usage. * @@ -751,9 +765,7 @@ par_status_t par_reset_all_to_default_raw(void) return ePAR_ERROR_MUTEX; } - memcpy( gs_par_u8_storage, gs_par_u8_default_mirror, sizeof(gs_par_u8_storage) ); - memcpy( gs_par_u16_storage, gs_par_u16_default_mirror, sizeof(gs_par_u16_storage) ); - memcpy( gs_par_u32_storage, gs_par_u32_default_mirror, sizeof(gs_par_u32_storage) ); + memcpy( &gs_par_storage, &gs_par_default_mirror, sizeof(gs_par_storage) ); par_release_mutex((par_num_t)0); diff --git a/src/par_cfg.h b/src/par_cfg.h index 4de34b7..b54133e 100644 --- a/src/par_cfg.h +++ b/src/par_cfg.h @@ -306,15 +306,15 @@ * Enable/Disable raw reset-all API and default mirror storage * * @note When enabled, module compiles par_reset_all_to_default_raw() and - * keeps per-width default mirror arrays in firmware image. - * - * @note The raw reset path restores parameter storage directly from the - * default mirrors, so it is typically faster than - * par_set_all_to_default(), which resets parameters one by one through - * the normal runtime setter path. + * keeps a grouped default mirror snapshot for raw restore. * * @note The speedup comes from bypassing per-parameter setter-side logic such * as runtime validation, change callback, and range handling. + * + * @note The raw reset path restores parameter storage directly from that + * grouped default mirror snapshot, so it is typically faster than + * par_set_all_to_default(), which resets parameters one by one through + * the normal runtime setter path. */ #ifndef PAR_CFG_ENABLE_RESET_ALL_RAW #define PAR_CFG_ENABLE_RESET_ALL_RAW ( 1 ) diff --git a/src/par_storage_init.inc b/src/par_storage_init.inc index 20e741e..6c3ba61 100644 --- a/src/par_storage_init.inc +++ b/src/par_storage_init.inc @@ -1,5 +1,8 @@ /* * Private include fragment. Included only by par.c. + * + * Expands compile-time default initializers for the grouped live storage + * object and preserves the internal U8/U16/U32 width-group layout. */ #define PAR_STORAGE_INIT_NOP(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) @@ -13,6 +16,10 @@ #define PAR_STORAGE_U32_FROM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u32_t)(0u), #endif +static par_storage_groups_t gs_par_storage = +{ + .u8 = + { #define PAR_ITEM_U8 PAR_STORAGE_U8_FROM_U8 #define PAR_ITEM_U16 PAR_STORAGE_INIT_NOP #define PAR_ITEM_U32 PAR_STORAGE_INIT_NOP @@ -20,10 +27,7 @@ #define PAR_ITEM_I16 PAR_STORAGE_INIT_NOP #define PAR_ITEM_I32 PAR_STORAGE_INIT_NOP #define PAR_ITEM_F32 PAR_STORAGE_INIT_NOP -static par_atomic_u8_t gs_par_u8_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT8)] = -{ - #include "../../par_table.def" -}; +#include "../../par_table.def" #undef PAR_ITEM_U8 #undef PAR_ITEM_U16 #undef PAR_ITEM_U32 @@ -31,7 +35,10 @@ static par_atomic_u8_t gs_par_u8_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT8) #undef PAR_ITEM_I16 #undef PAR_ITEM_I32 #undef PAR_ITEM_F32 + }, + .u16 = + { #define PAR_ITEM_U8 PAR_STORAGE_INIT_NOP #define PAR_ITEM_U16 PAR_STORAGE_U16_FROM_U16 #define PAR_ITEM_U32 PAR_STORAGE_INIT_NOP @@ -39,10 +46,7 @@ static par_atomic_u8_t gs_par_u8_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT8) #define PAR_ITEM_I16 PAR_STORAGE_U16_FROM_I16 #define PAR_ITEM_I32 PAR_STORAGE_INIT_NOP #define PAR_ITEM_F32 PAR_STORAGE_INIT_NOP -static par_atomic_u16_t gs_par_u16_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT16)] = -{ - #include "../../par_table.def" -}; +#include "../../par_table.def" #undef PAR_ITEM_U8 #undef PAR_ITEM_U16 #undef PAR_ITEM_U32 @@ -50,7 +54,10 @@ static par_atomic_u16_t gs_par_u16_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT #undef PAR_ITEM_I16 #undef PAR_ITEM_I32 #undef PAR_ITEM_F32 + }, + .u32 = + { #define PAR_ITEM_U8 PAR_STORAGE_INIT_NOP #define PAR_ITEM_U16 PAR_STORAGE_INIT_NOP #define PAR_ITEM_U32 PAR_STORAGE_U32_FROM_U32 @@ -58,14 +65,11 @@ static par_atomic_u16_t gs_par_u16_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT #define PAR_ITEM_I16 PAR_STORAGE_INIT_NOP #define PAR_ITEM_I32 PAR_STORAGE_U32_FROM_I32 #if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) - #define PAR_ITEM_F32 PAR_STORAGE_U32_FROM_F32 +#define PAR_ITEM_F32 PAR_STORAGE_U32_FROM_F32 #else - #define PAR_ITEM_F32 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_F32 PAR_STORAGE_INIT_NOP #endif -static par_atomic_u32_t gs_par_u32_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT32)] = -{ - #include "../../par_table.def" -}; +#include "../../par_table.def" #undef PAR_ITEM_U8 #undef PAR_ITEM_U16 #undef PAR_ITEM_U32 @@ -73,6 +77,8 @@ static par_atomic_u32_t gs_par_u32_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT #undef PAR_ITEM_I16 #undef PAR_ITEM_I32 #undef PAR_ITEM_F32 + } +}; #undef PAR_STORAGE_INIT_NOP #undef PAR_STORAGE_U8_FROM_U8 @@ -83,4 +89,4 @@ static par_atomic_u32_t gs_par_u32_storage[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT #undef PAR_STORAGE_U32_FROM_I32 #if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) #undef PAR_STORAGE_U32_FROM_F32 -#endif +#endif \ No newline at end of file From be324eab1e71c80e82621b9680f08cd13e6c7965 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Wed, 25 Mar 2026 09:05:52 +0800 Subject: [PATCH 18/36] refactor(core): Move parameter ID map checks to compile time --- README.md | 3 +- docs/api-reference.md | 27 +++--- docs/architecture.md | 62 ++++++++++++- docs/getting-started.md | 59 +++++++++++- src/par.c | 181 +++++++++++++------------------------ src/par_cfg.h | 83 +++++++++++++++++ src/par_def.c | 76 ++++++++++++++++ src/par_id_map_static.c | 43 +++++++++ src/par_id_map_static.h | 31 +++++++ template/par_cfg_port.htmp | 2 + template/par_table.deftmp | 48 +++++----- 11 files changed, 458 insertions(+), 157 deletions(-) create mode 100644 src/par_id_map_static.c create mode 100644 src/par_id_map_static.h diff --git a/README.md b/README.md index dca3e6c..329a110 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ It is designed for projects that need a clean way to: - **Optional metadata** such as name, unit, description, access, ID, and persistence flags - **Validation pipeline** with compile-time checks for integer ranges and optional runtime hooks for dynamic rules - **Static live-value storage** grouped by width instead of heap allocation -- **Fast external lookup by ID** through a runtime hash map +- **Fast external lookup by ID** through a compile-time generated static hash map - **Optional NVM integration** for persistent parameters - **Portable core + platform hooks** for RTOS, mutex, logging, assertions, and atomic backends @@ -122,6 +122,7 @@ This repository contains the reusable module core and templates. A real integrat - `PAR_CFG_ENABLE_TYPE_F32` controls whether floating-point parameter support, related typed APIs, and the `PAR_SET_F32` / `PAR_GET_F32` macro wrappers are compiled in. - `PAR_CFG_ENABLE_RUNTIME_VALIDATION` and `PAR_CFG_ENABLE_CHANGE_CALLBACK` control whether normal setters include runtime validation callbacks and on-change callbacks. - The module separates **internal parameter enumeration** (`par_num_t`) from **external parameter IDs** (`id`). +- The current ID lookup implementation uses a one-entry-per-bucket hash map generated at compile time from `par_table.def`. External IDs must therefore be not only unique, but also collision-free under the configured hash geometry. Optional runtime diagnostic scans can be enabled with `PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK` and `PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK` when additional startup logs are useful. See `docs/architecture.md` for the collision rule and avoidance guidance. - Fast setter APIs skip part of the safety and observability path, including runtime validation callbacks and on-change callbacks, so they should be reserved for tightly controlled hot paths. - NVM support is optional, but when enabled it depends on the external NVM module and on ID and persistence metadata being enabled. - `par_init()` applies startup default values directly to live storage. Integer default values from `par_table.def` are compiled into a grouped width-based storage object, while `F32` default values are applied to the 32-bit storage group after layout offsets are available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. Because this startup initialization does not go through the public setter path, it does not invoke runtime validation or on-change callbacks. diff --git a/docs/api-reference.md b/docs/api-reference.md index 2273d81..17fcdc1 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -38,8 +38,8 @@ The module conditionally compiles parts of the API based on configuration. | Function | Description | | --- | --- | -| `par_init()` | Initialize the module, validate the table, bind layout/runtime state, apply default values to live storage, and optionally load persisted values from NVM. Startup defaults are applied internally and do not use the public setter path. | -| `par_deinit()` | Deinitialize the module. | +| `par_init()` | Initialize the module, validate the table, bind layout/runtime state, optionally run configured runtime ID diagnostics, apply default values to live storage, and optionally load persisted values from NVM. Startup defaults are applied internally and do not use the public setter path. | +| `par_deinit()` | Best-effort deinitialize the module, including interface-layer resources. It always clears the module init state after attempting child deinit steps. When NVM support is enabled, it only deinitializes the underlying NVM module if this module initialized it. | | `par_is_init()` | Return whether the module is initialized. | ## Mutex helpers @@ -113,21 +113,26 @@ Use these only in controlled hot paths. | Function | Description | | --- | --- | -| `par_set_to_default(par_num)` | Reset one parameter to its default value. | -| `par_set_all_to_default()` | Reset all parameters to their default values. | +| `par_set_to_default(par_num)` | Reset one parameter to its configured default value through the normal runtime setter path. | +| `par_set_all_to_default()` | Reset all parameters to their default values. Depending on configuration, this may use either the normal runtime reset path or a raw grouped-storage restore path. | | `par_reset_all_to_default_raw()` | Restore all live values from a grouped default mirror snapshot via raw memory copy. The internal storage model still uses `U8/U16/U32` width groups. Available only when `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`. | | `par_has_changed(par_num, p_has_changed)` | Report whether the value differs from its default. | | `par_is_changed(par_num)` | Return whether the value differs from its default. | -`par_set_to_default()` and `par_set_all_to_default()` are runtime reset APIs. +`par_set_to_default()` always uses the normal runtime setter path. -They are different from startup initialization: +`par_set_all_to_default()` is configuration-dependent: -- `par_init()` applies the default values defined in `par_table.def` directly to live storage -- `par_set_to_default()` and `par_set_all_to_default()` still use the normal runtime value path -- `par_reset_all_to_default_raw()` restores storage directly and bypasses normal setter hooks +- when `PAR_CFG_ENABLE_RESET_ALL_RAW = 0`, it iterates through parameters and resets them through the normal runtime path +- when `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`, it restores the grouped live storage from the internal default mirror through the raw reset-all path -That distinction matters if your application depends on validation callbacks, on-change callbacks, range behavior, or other setter-side effects, because those runtime hooks apply only in the normal setter path and only when the matching callback features are enabled. +That distinction matters if your application depends on runtime validation callbacks, on-change callbacks, or other setter-side effects. + +These reset APIs are also different from startup initialization: + +- `par_init()` applies startup defaults internally to live storage +- `par_set_to_default()` always uses runtime setter semantics +- `par_set_all_to_default()` may bypass per-parameter runtime setter semantics when raw reset-all support is enabled ## Pointer-based getters @@ -175,7 +180,7 @@ These APIs do not follow the same runtime usage pattern as the value access APIs | `par_get_type(par_num)` | Return the parameter type enum. | | `par_get_access(par_num)` | Return read-only or read-write access metadata when enabled. | | `par_is_persistant(par_num)` | Return whether the parameter is marked persistent when enabled. | -| `par_get_num_by_id(id, p_par_num)` | Convert an external ID to `par_num_t`. | +| `par_get_num_by_id(id, p_par_num)` | Convert an external ID to `par_num_t` through the compile-time generated static ID map. | | `par_get_id_by_num(par_num, p_id)` | Convert `par_num_t` to external ID. | ## NVM APIs diff --git a/docs/architecture.md b/docs/architecture.md index 67bf43d..1168cb0 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -201,7 +201,7 @@ This split keeps integer configuration errors out of the firmware image while st The module supports ID-based APIs such as `par_get_by_id()` and `par_set_by_id()`. -Because external IDs do not need to be sequential, the runtime builds a hash map during `par_init()`. +Because external IDs do not need to be sequential, the build generates a static hash map from `par_table.def`. `par_init()` does not build the ID map at runtime. ```mermaid flowchart LR @@ -217,11 +217,67 @@ The current implementation uses a strict one-entry-per-bucket map. That means: -- duplicate IDs are rejected -- hash collisions are rejected during initialization +- duplicate IDs are rejected by compile-time table checks +- hash collisions are rejected by compile-time table checks +- optional runtime diagnostic scans can be enabled to print clearer startup logs for duplicate-ID and bucket-collision issues This keeps runtime lookup simple and deterministic, but it also means a conflicting ID assignment must be fixed at the source. +### Hash geometry and collision rule + +The current ID lookup implementation uses a strict one-entry-per-bucket hash map. + +Each external parameter ID is mapped to a bucket with the following multiplicative hash used by both static map generation and optional runtime diagnostics: + +```c +bucket = (((uint32_t)id * PAR_ID_HASH_GOLDEN_RATIO_32) >> (32u - PAR_ID_HASH_BITS)); +``` + +Where: + +* `PAR_ID_HASH_GOLDEN_RATIO_32 = 0x61C88647u` +* `PAR_ID_HASH_MIN_BUCKETS = 2 * ePAR_NUM_OF` +* `PAR_ID_HASH_BITS` is the smallest power-of-two bucket geometry that can hold at least `PAR_ID_HASH_MIN_BUCKETS` +* `PAR_ID_HASH_SIZE = 1u << PAR_ID_HASH_BITS` + +This design does **not** support probing or chaining. + +That means the current ID map effectively requires the active parameter table to be collision-free under the selected hash geometry: + +* two rows with the same `id` are invalid +* two different `id` values that land in the same bucket are also invalid + +In other words, the table must behave like a collision-free mapping for the configured bucket count. + +### Compile-time and runtime enforcement + +ID validity is enforced primarily at compile time: + +1. compile-time duplicate-ID and hash-bucket collision checks in `par_def.c` +2. compile-time static ID-map generation in `par_id_map_static.c` +3. optional runtime diagnostic scans in `par.c` + +Compile-time checks fail the build early when the parameter table already proves invalid. + +Optional runtime scans do not build the ID map. They exist only to provide clearer diagnostic logs during startup when additional field debugging is useful. + +### How to avoid hash collisions + +When assigning or changing external IDs in `par_table.def`: + +1. keep every `id` globally unique +2. avoid clustered numeric patterns that repeatedly land in the same hash bucket +3. re-run the build after every ID edit +4. if a collision is reported, change the conflicting external IDs in `par_table.def` +5. do not assume that "different IDs" are automatically safe; different IDs can still hash into the same bucket + +For the current implementation, avoiding collision means avoiding both: + +* duplicate `id` +* duplicate `PAR_HASH_ID_CONST(id)` result + +If frequent ID churn is expected, a probing-based or chained hash map is a more scalable design than relying on a collision-free table. + ## Normal path vs fast path Depending on build-time configuration, the normal path can include runtime validation callbacks and on-change callbacks. diff --git a/docs/getting-started.md b/docs/getting-started.md index f26452c..718e7be 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -89,7 +89,10 @@ Use it to integrate platform-specific services such as: - initialization hooks - mutex handling -- optional table hash support +- optional platform hooks unrelated to the core ID lookup hash map + +The ID lookup hash used by `par_get_by_id()` / `par_set_by_id()` is part of the core module and is generated at compile time, not supplied by `port/par_if_port.c`. +Do not confuse it with optional table-hash support used by NVM compatibility features. ### `port/par_atomic_port.h` @@ -132,7 +135,12 @@ Relevant options in `par_cfg.h`: - `PAR_CFG_ENABLE_ID` - `PAR_CFG_ENABLE_PERSIST` -When NVM is enabled, the external NVM module must be present in the project. +ID-based lookup is generated statically when `PAR_CFG_ENABLE_ID = 1`. Optional startup diagnostics can be enabled with: + +- `PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK` +- `PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK` + +When NVM is enabled, the external NVM module must be present in the project. The parameters module can reuse an already-initialized NVM backend or initialize it on demand and later deinitialize it only when it owns that initialization. Module deinit is best-effort: it attempts NVM and interface cleanup, aggregates status bits, and still clears the top-level module init state. ### Layout source @@ -365,6 +373,8 @@ uint32_t baud = 115200U; - Using fast setters before understanding their tradeoffs - Enabling NVM without the external NVM module in the build - Writing `par_table.def` entries with duplicate IDs +- Assigning different external IDs that still resolve to the same ID hash bucket +- Changing external IDs without rebuilding and checking the compile-time ID-map validation output - Assuming the repository already ships a ready-to-build `par_table.def` for your project - Disabling `PAR_CFG_ENABLE_TYPE_F32` while keeping `PAR_ITEM_F32(...)` entries in `par_table.def` - Assuming `PAR_SET_F32` and `PAR_GET_F32` are still available after F32 support is disabled @@ -398,3 +408,48 @@ par_table.def:189:1: note: in expansion of macro 'PAR_ITEM_F32' ``` Fix the table first: remove the `PAR_ITEM_F32(...)` entry or re-enable `PAR_CFG_ENABLE_TYPE_F32`. + +### Compile-time error example when ID hash buckets collide + +The build also fails when two different external IDs resolve to the same hash bucket, because the static ID map requires a collision-free table under the configured hash geometry. + +This does not mean the IDs are equal. +It means the current one-entry-per-bucket ID map cannot represent both rows at the same time. + +Example: + +```log +par_table.def: In function 'par_compile_check_hash_bucket_collision': +src/par_def.c:156:105: error: duplicate case value + 156 | #define PAR_CHECK_ID_BUCKET_CASE(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) case PAR_HASH_ID_CONST(id_): break; + | ^~~~ +src/par_def.c:162:31: note: in expansion of macro 'PAR_CHECK_ID_BUCKET_CASE' + 162 | #define PAR_ITEM_U16 PAR_CHECK_ID_BUCKET_CASE + | ^~~~~~~~~~~~~~~~~~~~~~~~ +par_table.def:141:1: note: in expansion of macro 'PAR_ITEM_U16' + 141 | PAR_ITEM_U16(ePAR_CH3_VOL_RAW, 253, "Ch3 Raw Vout", ...) + | ^~~~~~~~~~~~ +src/par_def.c:156:105: note: previously used here + 156 | #define PAR_CHECK_ID_BUCKET_CASE(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) case PAR_HASH_ID_CONST(id_): break; + | ^~~~ +src/par_def.c:167:31: note: in expansion of macro 'PAR_CHECK_ID_BUCKET_CASE' + 167 | #define PAR_ITEM_F32 PAR_CHECK_ID_BUCKET_CASE + | ^~~~~~~~~~~~~~~~~~~~~~~~ +par_table.def:54:1: note: in expansion of macro 'PAR_ITEM_F32' + 54 | PAR_ITEM_F32(ePAR_CH1_TSIM, 20, "Ch1 Ref Temperature", ...) + | ^~~~~~~~~~~~ +``` + +Read this error as: + +* the two IDs are different +* but `PAR_HASH_ID_CONST(253)` and `PAR_HASH_ID_CONST(20)` produced the same bucket index +* the current ID map cannot accept both rows + +Fix the table first: + +1. keep IDs unique +2. change one of the conflicting external IDs in `par_table.def` +3. rebuild until the compile-time collision check no longer fails + +If collisions become frequent, reconsider the ID assignment policy or replace the one-entry-per-bucket map with a probing-based implementation. diff --git a/src/par.c b/src/par.c index 9d62956..eb4bd28 100644 --- a/src/par.c +++ b/src/par.c @@ -28,55 +28,13 @@ #include "par.h" #include "par_atomic.h" #include "par_layout.h" +#include "par_id_map_static.h" #include "par_nvm.h" #include "par_if.h" //////////////////////////////////////////////////////////////////////////////// // Definitions //////////////////////////////////////////////////////////////////////////////// -#if ( 1 == PAR_CFG_ENABLE_ID ) -/* - * http://www.citi.umich.edu/techreports/reports/citi-tr-00-1.pdf - * - * GoldenRatio = ~(Math.pow(2, 32) / ((Math.sqrt(5) - 1) / 2)) + 1 - */ -#define PAR_ID_HASH_GOLDEN_RATIO_32 ( 0x61C88647u ) - -/** - * Minimum number of hash buckets to keep target load factor <= 0.5. - */ -#define PAR_ID_HASH_MIN_BUCKETS ((uint32_t)(2u * (uint32_t)ePAR_NUM_OF)) - -/** - * Hash map geometry derived from ePAR_NUM_OF at compile time. - */ -enum -{ - PAR_ID_HASH_BITS = - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 1 )) ? 1u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 2 )) ? 2u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 3 )) ? 3u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 4 )) ? 4u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 5 )) ? 5u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 6 )) ? 6u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 7 )) ? 7u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 8 )) ? 8u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 9 )) ? 9u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 10 )) ? 10u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 11 )) ? 11u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 12 )) ? 12u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 13 )) ? 13u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 14 )) ? 14u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 15 )) ? 15u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 16 )) ? 16u : - ( PAR_ID_HASH_MIN_BUCKETS <= ( 1u << 17 )) ? 17u : 18u, - PAR_ID_HASH_SIZE = ( 1u << PAR_ID_HASH_BITS ), -}; - -PAR_STATIC_ASSERT(par_id_hash_size_valid, (PAR_ID_HASH_SIZE >= PAR_ID_HASH_MIN_BUCKETS)); -PAR_STATIC_ASSERT(par_id_hash_bits_valid, ((PAR_ID_HASH_BITS > 0u) && (PAR_ID_HASH_BITS < 32u))); -#endif - PAR_STATIC_ASSERT(par_atomic_u8_i8_same_size, sizeof(par_atomic_u8_t) == sizeof(par_atomic_i8_t)); PAR_STATIC_ASSERT(par_atomic_u8_i8_same_align, PAR_ALIGNOF(par_atomic_u8_t) == PAR_ALIGNOF(par_atomic_i8_t)); PAR_STATIC_ASSERT(par_atomic_u16_i16_same_size, sizeof(par_atomic_u16_t) == sizeof(par_atomic_i16_t)); @@ -112,28 +70,6 @@ static struct } g_par_cb_table[ePAR_NUM_OF]; #endif -/** - * ID hash map entry. - */ -#if ( 1 == PAR_CFG_ENABLE_ID ) - typedef struct - { - uint16_t id; - par_num_t par_num; - uint8_t used; - } par_id_map_entry_t; - - /** - * Runtime ID hash map. - */ - static par_id_map_entry_t g_par_id_map[PAR_ID_HASH_SIZE] = {0}; - - /** - * Initialization guard for ID hash map. - */ - static bool gb_par_id_map_ready = false; -#endif - /** * Grouped typed storage backing parameter live values. * @@ -242,7 +178,9 @@ static par_atomic_f32_t * const gpf32_par_value = (par_atomic_f32_t *)gs_par_sto //////////////////////////////////////////////////////////////////////////////// #if ( 1 == PAR_CFG_ENABLE_ID ) static inline uint32_t par_hash_id (const uint16_t id); -static par_status_t par_build_and_validate_id_map (const par_cfg_t * const p_par_cfg); +#if (( 1 == PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK ) || ( 1 == PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK )) +static par_status_t par_runtime_validate_id_table (const par_cfg_t * const p_par_cfg); +#endif #endif #if ( 1 == PAR_CFG_NVM_EN ) static bool par_is_value_changed (const par_num_t par_num, const void * p_val); @@ -323,27 +261,34 @@ static void par_bind_storage_layout(void) #if ( 1 == PAR_CFG_ENABLE_ID ) static inline uint32_t par_hash_id(const uint16_t id) { - return (((uint32_t) id * PAR_ID_HASH_GOLDEN_RATIO_32 ) >> ( 32u - PAR_ID_HASH_BITS )); + return PAR_HASH_ID_CONST( id ); } +#if (( 1 == PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK ) || ( 1 == PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK )) //////////////////////////////////////////////////////////////////////////////// /** -* Build and validate parameter ID hash map +* Run optional runtime diagnostics on the compiled parameter ID table +* +* @note Static ID-map generation and compile-time conflict checks are +* always enabled when PAR_CFG_ENABLE_ID = 1. This function exists +* only to provide runtime diagnostics and clearer conflict logs. * * @param[in] p_par_cfg - Pointer to parameters table * @return status - Status of operation */ //////////////////////////////////////////////////////////////////////////////// -static par_status_t par_build_and_validate_id_map(const par_cfg_t * const p_par_cfg) +static par_status_t par_runtime_validate_id_table(const par_cfg_t * const p_par_cfg) { - memset( g_par_id_map, 0, sizeof(g_par_id_map) ); - for ( par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) + par_id_map_entry_t diag_map[PAR_ID_HASH_SIZE]; + memset(diag_map, 0, sizeof(diag_map)); + + for (par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++) { const uint16_t id = p_par_cfg[par_num].id; - const uint32_t bucket_idx = par_hash_id( id ); - par_id_map_entry_t * const bucket = &g_par_id_map[bucket_idx]; + const uint32_t bucket_idx = par_hash_id(id); + par_id_map_entry_t * const bucket = &diag_map[bucket_idx]; - if ( 0u == bucket->used ) + if (0u == bucket->used) { bucket->used = 1u; bucket->id = id; @@ -351,23 +296,30 @@ static par_status_t par_build_and_validate_id_map(const par_cfg_t * const p_par_ continue; } - if ( bucket->id == id ) +#if (1 == PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK) + if (bucket->id == id) { - PAR_DBG_PRINT( "ERR, Duplicate parameter ID %u!", (unsigned) id ); - PAR_ASSERT( 0 ); + PAR_DBG_PRINT("ERR, Duplicate parameter ID %u!", (unsigned)id); + PAR_ASSERT(0); return ePAR_ERROR_INIT; } +#endif - PAR_DBG_PRINT( "ERR, Hash collision: ID %u conflicts with ID %u at bucket %u!", - (unsigned) id, (unsigned) bucket->id, (unsigned) bucket_idx ); - PAR_DBG_PRINT( "ERR, Please regenerate IDs or adjust hash parameters." ); - PAR_ASSERT( 0 ); - return ePAR_ERROR_INIT; +#if (1 == PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK) + if (bucket->id != id) + { + PAR_DBG_PRINT("ERR, Hash collision: ID %u conflicts with ID %u at bucket %u!", + (unsigned)id, (unsigned)bucket->id, (unsigned)bucket_idx); + PAR_ASSERT(0); + return ePAR_ERROR_INIT; + } +#endif } return ePAR_OK; } #endif +#endif //////////////////////////////////////////////////////////////////////////////// /** @@ -381,9 +333,9 @@ static par_status_t par_check_table_validity(const par_cfg_t * const p_par_cfg) { par_status_t status = ePAR_OK; -#if ( 1 == PAR_CFG_ENABLE_ID ) - // Build and validate runtime ID hash map - status = par_build_and_validate_id_map( p_par_cfg ); +#if ( 1 == PAR_CFG_ENABLE_ID ) && (( 1 == PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK ) || ( 1 == PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK )) + // Run optional runtime diagnostics against the compiled static ID map. + status = par_runtime_validate_id_table( p_par_cfg ); if ( ePAR_OK != status ) { return status; @@ -489,9 +441,6 @@ par_status_t par_init(void) if ( ePAR_OK == status ) { gb_is_init = true; -#if ( 1 == PAR_CFG_ENABLE_ID ) - gb_par_id_map_ready = true; -#endif /* Set all parameters to default * Integer defaults are already initialized at definition time. @@ -541,9 +490,6 @@ par_status_t par_deinit(void) // Module de-initialized gb_is_init = false; -#if ( 1 == PAR_CFG_ENABLE_ID ) - gb_par_id_map_ready = false; -#endif return status; } @@ -711,29 +657,6 @@ par_status_t par_set_to_default(const par_num_t par_num) return par_set(par_num, &(par_get_config(par_num)->def)); } -//////////////////////////////////////////////////////////////////////////////// -/** -* Set all parameters to default value -* -* @pre Parameters must be initialised before usage! -* @note This function uses normal runtime setter path via par_set_to_default() -* and keeps setter semantics. -* -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_all_to_default(void) -{ - for ( par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) - { - // Ignore return as it is not possible to return other that OK - (void) par_set_to_default( par_num ); - } - - PAR_DBG_PRINT( "PAR: Setting all parameters to default" ); - return ePAR_OK; -} - #if ( 1 == PAR_CFG_ENABLE_RESET_ALL_RAW ) //////////////////////////////////////////////////////////////////////////////// /** @@ -774,6 +697,32 @@ par_status_t par_reset_all_to_default_raw(void) } #endif +//////////////////////////////////////////////////////////////////////////////// +/** +* Set all parameters to default value +* +* @pre Parameters must be initialised before usage! +* @note This function uses normal runtime setter path via par_set_to_default() +* and keeps setter semantics. +* +* @return status - Status of operation +*/ +//////////////////////////////////////////////////////////////////////////////// +par_status_t par_set_all_to_default(void) +{ +#if ( 1 == PAR_CFG_ENABLE_RESET_ALL_RAW ) + (void) par_reset_all_to_default_raw(); +#else + for ( par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) + { + // Ignore return as it is not possible to return other that OK + (void) par_set_to_default( par_num ); + } + PAR_DBG_PRINT( "PAR: Setting all parameters to default" ); +#endif + return ePAR_OK; +} + //////////////////////////////////////////////////////////////////////////////// /** * Check if parameter changed from its default value @@ -1199,10 +1148,10 @@ bool par_is_persistant(const par_num_t par_num) #if ( 1 == PAR_CFG_ENABLE_ID ) par_status_t par_get_num_by_id(const uint16_t id, par_num_t * const p_par_num) { - if (( NULL != p_par_num ) && ( true == gb_par_id_map_ready )) + if (( NULL != p_par_num ) && ( true == par_is_init() )) { const uint32_t bucket_idx = par_hash_id( id ); - const par_id_map_entry_t * const bucket = &g_par_id_map[bucket_idx]; + const par_id_map_entry_t * const bucket = &g_par_id_map_static[bucket_idx]; if (( 0u != bucket->used ) && ( id == bucket->id )) { diff --git a/src/par_cfg.h b/src/par_cfg.h index b54133e..501ccc7 100644 --- a/src/par_cfg.h +++ b/src/par_cfg.h @@ -355,6 +355,81 @@ #define PAR_CFG_ENABLE_ID ( 1 ) #endif +#if ( 1 == PAR_CFG_ENABLE_ID ) +/** + * Enable/Disable optional runtime duplicate-ID diagnostic scan. + * + * @note Static ID-map generation and compile-time duplicate-ID checking remain + * enabled by default whenever PAR_CFG_ENABLE_ID = 1. + */ +#ifndef PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK + #define PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK ( 0 ) +#endif + +/** + * Enable/Disable optional runtime ID hash-collision diagnostic scan. + * + * @note Static ID-map generation and compile-time hash collision checking + * remain enabled by default whenever PAR_CFG_ENABLE_ID = 1. + */ +#ifndef PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK + #define PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK ( 0 ) +#endif + +/** + * Internal-only ID hash geometry helpers. + * + * @note Shared by the compile-time table checks, compile-time static ID-map + * generation, and optional runtime diagnostic scans. + * + * @note These helpers define the internal geometry of the core ID lookup map. + * Integrators should not treat them as a stable public extension API. + */ +#ifndef PAR_ID_HASH_GOLDEN_RATIO_32 + #define PAR_ID_HASH_GOLDEN_RATIO_32 ( 0x61C88647u ) +#endif + +#ifndef PAR_ID_HASH_MIN_BUCKETS + #define PAR_ID_HASH_MIN_BUCKETS ((uint32_t)(2u * (uint32_t)ePAR_NUM_OF)) +#endif + +#ifndef PAR_ID_HASH_BITS_FROM_MIN_BUCKETS + #define PAR_ID_HASH_BITS_FROM_MIN_BUCKETS(min_buckets_) \ + (((min_buckets_) <= ( 1u << 1 )) ? 1u : \ + ((min_buckets_) <= ( 1u << 2 )) ? 2u : \ + ((min_buckets_) <= ( 1u << 3 )) ? 3u : \ + ((min_buckets_) <= ( 1u << 4 )) ? 4u : \ + ((min_buckets_) <= ( 1u << 5 )) ? 5u : \ + ((min_buckets_) <= ( 1u << 6 )) ? 6u : \ + ((min_buckets_) <= ( 1u << 7 )) ? 7u : \ + ((min_buckets_) <= ( 1u << 8 )) ? 8u : \ + ((min_buckets_) <= ( 1u << 9 )) ? 9u : \ + ((min_buckets_) <= ( 1u << 10 )) ? 10u : \ + ((min_buckets_) <= ( 1u << 11 )) ? 11u : \ + ((min_buckets_) <= ( 1u << 12 )) ? 12u : \ + ((min_buckets_) <= ( 1u << 13 )) ? 13u : \ + ((min_buckets_) <= ( 1u << 14 )) ? 14u : \ + ((min_buckets_) <= ( 1u << 15 )) ? 15u : \ + ((min_buckets_) <= ( 1u << 16 )) ? 16u : \ + ((min_buckets_) <= ( 1u << 17 )) ? 17u : 18u) +#endif + +#ifndef PAR_ID_HASH_BITS + #define PAR_ID_HASH_BITS PAR_ID_HASH_BITS_FROM_MIN_BUCKETS(PAR_ID_HASH_MIN_BUCKETS) +#endif + +#ifndef PAR_ID_HASH_SIZE + #define PAR_ID_HASH_SIZE ( 1u << PAR_ID_HASH_BITS ) +#endif + +#ifndef PAR_HASH_ID_CONST + #define PAR_HASH_ID_CONST(id_) ((((uint32_t)(id_)) * PAR_ID_HASH_GOLDEN_RATIO_32) >> (32u - PAR_ID_HASH_BITS)) +#endif + +PAR_STATIC_ASSERT(par_id_hash_size_valid, (PAR_ID_HASH_SIZE >= PAR_ID_HASH_MIN_BUCKETS)); +PAR_STATIC_ASSERT(par_id_hash_bits_valid, ((PAR_ID_HASH_BITS > 0u) && (PAR_ID_HASH_BITS < 32u))); +#endif + /** * Enable/Disable parameter access metadata */ @@ -389,6 +464,14 @@ #error "Parameter settings invalid: NVM requires PAR_CFG_ENABLE_PERSIST = 1!" #endif +#if ( 0 == PAR_CFG_ENABLE_ID ) && ( 1 == PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK ) + #error "Parameter settings invalid: runtime duplicate-ID diagnostics require PAR_CFG_ENABLE_ID = 1!" +#endif + +#if ( 0 == PAR_CFG_ENABLE_ID ) && ( 1 == PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK ) + #error "Parameter settings invalid: runtime ID hash-collision diagnostics require PAR_CFG_ENABLE_ID = 1!" +#endif + #if ( PAR_CFG_LAYOUT_SOURCE != PAR_CFG_LAYOUT_COMPILE_SCAN ) && ( PAR_CFG_LAYOUT_SOURCE != PAR_CFG_LAYOUT_SCRIPT ) #error "Parameter settings invalid: PAR_CFG_LAYOUT_SOURCE must be PAR_CFG_LAYOUT_COMPILE_SCAN or PAR_CFG_LAYOUT_SCRIPT!" #endif diff --git a/src/par_def.c b/src/par_def.c index 68045db..1e67948 100644 --- a/src/par_def.c +++ b/src/par_def.c @@ -105,6 +105,82 @@ #undef PAR_CHECK_INT_COMMON #endif +#if ( 1 == PAR_CFG_ENABLE_ID ) +/** + * Compile-time check A: duplicated parameter IDs in par_table.def. + * + * @note Duplicate ID values trigger duplicated "case" labels. + */ +#define PAR_CHECK_ID_DUPLICATE_CASE(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) case ((uint32_t)(id_)): break; +static void par_compile_check_duplicate_ids(void) +{ + switch (0u) + { + #define PAR_ITEM_U8 PAR_CHECK_ID_DUPLICATE_CASE + #define PAR_ITEM_U16 PAR_CHECK_ID_DUPLICATE_CASE + #define PAR_ITEM_U32 PAR_CHECK_ID_DUPLICATE_CASE + #define PAR_ITEM_I8 PAR_CHECK_ID_DUPLICATE_CASE + #define PAR_ITEM_I16 PAR_CHECK_ID_DUPLICATE_CASE + #define PAR_ITEM_I32 PAR_CHECK_ID_DUPLICATE_CASE + #define PAR_ITEM_F32 PAR_CHECK_ID_DUPLICATE_CASE + #include "../../par_table.def" + #undef PAR_ITEM_U8 + #undef PAR_ITEM_U16 + #undef PAR_ITEM_U32 + #undef PAR_ITEM_I8 + #undef PAR_ITEM_I16 + #undef PAR_ITEM_I32 + #undef PAR_ITEM_F32 + default: break; + } +} + +/** + * Compile-time check B: external ID hash-bucket collisions in par_table.def. + * + * The runtime ID map is a strict one-entry-per-bucket structure and does not + * implement probing or chaining. + * + * Therefore two different external IDs are still invalid when + * PAR_HASH_ID_CONST(id_a) == PAR_HASH_ID_CONST(id_b). + * + * This check intentionally fails the build early by generating duplicated + * "case" labels for colliding bucket indices. + */ +#define PAR_CHECK_ID_BUCKET_CASE(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) case PAR_HASH_ID_CONST(id_): break; +static void par_compile_check_hash_bucket_collision(void) +{ + switch (0u) + { + #define PAR_ITEM_U8 PAR_CHECK_ID_BUCKET_CASE + #define PAR_ITEM_U16 PAR_CHECK_ID_BUCKET_CASE + #define PAR_ITEM_U32 PAR_CHECK_ID_BUCKET_CASE + #define PAR_ITEM_I8 PAR_CHECK_ID_BUCKET_CASE + #define PAR_ITEM_I16 PAR_CHECK_ID_BUCKET_CASE + #define PAR_ITEM_I32 PAR_CHECK_ID_BUCKET_CASE + #define PAR_ITEM_F32 PAR_CHECK_ID_BUCKET_CASE + #include "../../par_table.def" + #undef PAR_ITEM_U8 + #undef PAR_ITEM_U16 + #undef PAR_ITEM_U32 + #undef PAR_ITEM_I8 + #undef PAR_ITEM_I16 + #undef PAR_ITEM_I32 + #undef PAR_ITEM_F32 + default: break; + } +} + +/* + * Keep compile-check helper functions "used" to avoid unused-function warnings. + */ +PAR_STATIC_ASSERT(par_compile_check_duplicate_ids_ref, (sizeof(&par_compile_check_duplicate_ids) > 0u)); +PAR_STATIC_ASSERT(par_compile_check_hash_bucket_collision_ref, (sizeof(&par_compile_check_hash_bucket_collision) > 0u)); + +#undef PAR_CHECK_ID_DUPLICATE_CASE +#undef PAR_CHECK_ID_BUCKET_CASE +#endif + //////////////////////////////////////////////////////////////////////////////// // Variables //////////////////////////////////////////////////////////////////////////////// diff --git a/src/par_id_map_static.c b/src/par_id_map_static.c new file mode 100644 index 0000000..481edc4 --- /dev/null +++ b/src/par_id_map_static.c @@ -0,0 +1,43 @@ +/** + * @file par_id_map_static.c + * @brief Compile-time generated ID lookup map + * @author wdfk-prog () + * @version 1.0 + * @date 2026-03-24 + * + * @copyright Copyright (c) 2026 + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-24 1.0 wdfk-prog first version + */ +#include "par_id_map_static.h" + +#if ( 1 == PAR_CFG_ENABLE_ID ) + +#define PAR_ID_MAP_ITEM(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ + [PAR_HASH_ID_CONST(id_)] = { .id = (uint16_t)(id_), .par_num = (enum_), .used = 1u }, + +const par_id_map_entry_t g_par_id_map_static[PAR_ID_HASH_SIZE] = +{ + #define PAR_ITEM_U8 PAR_ID_MAP_ITEM + #define PAR_ITEM_U16 PAR_ID_MAP_ITEM + #define PAR_ITEM_U32 PAR_ID_MAP_ITEM + #define PAR_ITEM_I8 PAR_ID_MAP_ITEM + #define PAR_ITEM_I16 PAR_ID_MAP_ITEM + #define PAR_ITEM_I32 PAR_ID_MAP_ITEM + #define PAR_ITEM_F32 PAR_ID_MAP_ITEM + #include "../../par_table.def" + #undef PAR_ITEM_U8 + #undef PAR_ITEM_U16 + #undef PAR_ITEM_U32 + #undef PAR_ITEM_I8 + #undef PAR_ITEM_I16 + #undef PAR_ITEM_I32 + #undef PAR_ITEM_F32 +}; + +#undef PAR_ID_MAP_ITEM + +#endif diff --git a/src/par_id_map_static.h b/src/par_id_map_static.h new file mode 100644 index 0000000..1315ccc --- /dev/null +++ b/src/par_id_map_static.h @@ -0,0 +1,31 @@ +/** + * @file par_id_map_static.h + * @brief + * @author wdfk-prog () + * @version 1.0 + * @date 2026-03-24 + * + * @copyright Copyright (c) 2026 + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-24 1.0 wdfk-prog first version + */ +#ifndef _PAR_ID_MAP_STATIC_H_ +#define _PAR_ID_MAP_STATIC_H_ + +#include "par.h" + +#if ( 1 == PAR_CFG_ENABLE_ID ) +typedef struct +{ + uint16_t id; + par_num_t par_num; + uint8_t used; +} par_id_map_entry_t; + +extern const par_id_map_entry_t g_par_id_map_static[PAR_ID_HASH_SIZE]; +#endif + +#endif /* _PAR_ID_MAP_STATIC_H_ */ diff --git a/template/par_cfg_port.htmp b/template/par_cfg_port.htmp index 2031d9d..a505216 100644 --- a/template/par_cfg_port.htmp +++ b/template/par_cfg_port.htmp @@ -1,4 +1,6 @@ #ifndef _PAR_CFG_PORT_H_ #define _PAR_CFG_PORT_H_ /* Optional platform overrides */ +/* #define PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK ( 0 ) */ +/* #define PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK ( 0 ) */ #endif \ No newline at end of file diff --git a/template/par_table.deftmp b/template/par_table.deftmp index 0ce9e15..12537f4 100644 --- a/template/par_table.deftmp +++ b/template/par_table.deftmp @@ -51,24 +51,24 @@ PAR_ITEM_U8 (ePAR_CH1_STATUS, 10, "Ch1 Status", 0U, PAR_ITEM_U8 (ePAR_CH1_FSM_STATE, 11, "Ch1 FSM State", 0U, 3U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 1 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal") /* Channel 1 reference values */ -PAR_ITEM_F32(ePAR_CH1_TSIM, 20, "Ch1 Ref Temperature", -20.0f, 350.0f, 0.0f, "degC", ePAR_ACCESS_RO, false, "Channel 1 temperature in degree C") -PAR_ITEM_F32(ePAR_CH1_RSIM, 21, "Ch1 Ref Resistance", 920.0f, 2300.0f, 920.0f, "ohm", ePAR_ACCESS_RO, false, "Channel 1 resistance reference in ohm") -PAR_ITEM_F32(ePAR_CH1_VOUT, 22, "Ch1 Ref Vout", 0.85f, 1.58f, 0.85f, "V", ePAR_ACCESS_RO, false, "Channel 1 output voltage in V") -PAR_ITEM_F32(ePAR_CH1_ISINK, 23, "Ch1 Ref Isink", 658.5f, 950.0f, 658.5f, "uA", ePAR_ACCESS_RO, false, "Channel 1 sink current reference in uA") -PAR_ITEM_F32(ePAR_CH1_VSET, 24, "Ch1 Ref Vset", 1.7955f, 2.548f, 1.7955f, "V", ePAR_ACCESS_RO, false, "Channel 1 Vset (reference) voltage in V") -PAR_ITEM_F32(ePAR_CH1_VTH, 25, "Ch1 Ref Vth", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 1 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined.") -PAR_ITEM_F32(ePAR_CH1_VDAC_C, 26, "Ch1 Ref Vdac Coarse", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 1 Vdac coarse voltage in V") -PAR_ITEM_F32(ePAR_CH1_VDAC_F, 27, "Ch1 Ref Vdac Fine", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 1 Vdac fine voltage in V") -PAR_ITEM_U16(ePAR_CH1_VDAC_F_RAW, 28, "Ch1 Ref Raw DAC Fine", 0U, 4095U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 1 raw fine DAC value") -PAR_ITEM_U16(ePAR_CH1_VDAC_C_RAW, 29, "Ch1 Ref Raw DAC Coarse", 0U, 4095U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 1 raw coarse DAC value") +PAR_ITEM_F32(ePAR_CH1_TSIM, 21, "Ch1 Ref Temperature", -20.0f, 350.0f, 0.0f, "degC", ePAR_ACCESS_RO, false, "Channel 1 temperature in degree C") +PAR_ITEM_F32(ePAR_CH1_RSIM, 22, "Ch1 Ref Resistance", 920.0f, 2300.0f, 920.0f, "ohm", ePAR_ACCESS_RO, false, "Channel 1 resistance reference in ohm") +PAR_ITEM_F32(ePAR_CH1_VOUT, 23, "Ch1 Ref Vout", 0.85f, 1.58f, 0.85f, "V", ePAR_ACCESS_RO, false, "Channel 1 output voltage in V") +PAR_ITEM_F32(ePAR_CH1_ISINK, 24, "Ch1 Ref Isink", 658.5f, 950.0f, 658.5f, "uA", ePAR_ACCESS_RO, false, "Channel 1 sink current reference in uA") +PAR_ITEM_F32(ePAR_CH1_VSET, 25, "Ch1 Ref Vset", 1.7955f, 2.548f, 1.7955f, "V", ePAR_ACCESS_RO, false, "Channel 1 Vset (reference) voltage in V") +PAR_ITEM_F32(ePAR_CH1_VTH, 26, "Ch1 Ref Vth", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 1 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined.") +PAR_ITEM_F32(ePAR_CH1_VDAC_C, 27, "Ch1 Ref Vdac Coarse", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 1 Vdac coarse voltage in V") +PAR_ITEM_F32(ePAR_CH1_VDAC_F, 28, "Ch1 Ref Vdac Fine", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 1 Vdac fine voltage in V") +PAR_ITEM_U16(ePAR_CH1_VDAC_F_RAW, 29, "Ch1 Ref Raw DAC Fine", 0U, 4095U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 1 raw fine DAC value") +PAR_ITEM_U16(ePAR_CH1_VDAC_C_RAW, 30, "Ch1 Ref Raw DAC Coarse", 0U, 4095U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 1 raw coarse DAC value") /* Channel 1 measurements */ PAR_ITEM_F32(ePAR_CH1_CUR, 50, "Ch1 Act Isink", 0.0f, 5000.0f, 0.0f, "uA", ePAR_ACCESS_RO, false, "Channel 1 actual current sink value in uA") -PAR_ITEM_F32(ePAR_CH1_VOL, 51, "Ch1 Act Vout", 0.0f, 10.0f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 1 actual output voltage value in V") -PAR_ITEM_U16(ePAR_CH1_CUR_RAW, 52, "Ch1 Raw Isink", 0U, 8191U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 1 raw ADC current value") -PAR_ITEM_U16(ePAR_CH1_VOL_RAW, 53, "Ch1 Raw Vout", 0U, 8191U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 1 raw ADC voltage value") -PAR_ITEM_F32(ePAR_CH1_AFE_VCC, 54, "Ch1 AFE Vcc", 0.0f, 10.0f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 1 measured AFE Vcc voltage value in V") -PAR_ITEM_F32(ePAR_CH1_AFE_RES, 55, "Ch1 AFE Res", 0.0f, 5.0e3f, 0.0f, "ohm", ePAR_ACCESS_RO, false, "Channel 1 measured AFE resistance (pull-up + series) value in ohm") +PAR_ITEM_F32(ePAR_CH1_VOL, 52, "Ch1 Act Vout", 0.0f, 10.0f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 1 actual output voltage value in V") +PAR_ITEM_U16(ePAR_CH1_CUR_RAW, 53, "Ch1 Raw Isink", 0U, 8191U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 1 raw ADC current value") +PAR_ITEM_U16(ePAR_CH1_VOL_RAW, 54, "Ch1 Raw Vout", 0U, 8191U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 1 raw ADC voltage value") +PAR_ITEM_F32(ePAR_CH1_AFE_VCC, 55, "Ch1 AFE Vcc", 0.0f, 10.0f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 1 measured AFE Vcc voltage value in V") +PAR_ITEM_F32(ePAR_CH1_AFE_RES, 56, "Ch1 AFE Res", 0.0f, 5.0e3f, 0.0f, "ohm", ePAR_ACCESS_RO, false, "Channel 1 measured AFE resistance (pull-up + series) value in ohm") /* ============================================================================================================================= */ @@ -83,8 +83,8 @@ PAR_ITEM_U8 (ePAR_CH2_REF_SEL, 103, "Ch2 Reference Selection", 0U, PAR_ITEM_F32(ePAR_CH2_REF_VAL, 104, "Ch2 Reference Value", -1E6f, 1E6f, 0.0f, NULL, ePAR_ACCESS_RW, true, "Channel 2 reference value based on control variable set") /* Channel 2 status */ -PAR_ITEM_U8 (ePAR_CH2_STATUS, 110, "Ch2 Status", 0U, 3U, 3U, NULL, ePAR_ACCESS_RO, false, "Channel 2 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected") -PAR_ITEM_U8 (ePAR_CH2_FSM_STATE, 111, "Ch2 FSM State", 0U, 3U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 2 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal") +PAR_ITEM_U8 (ePAR_CH2_STATUS, 111, "Ch2 Status", 0U, 3U, 3U, NULL, ePAR_ACCESS_RO, false, "Channel 2 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected") +PAR_ITEM_U8 (ePAR_CH2_FSM_STATE, 112, "Ch2 FSM State", 0U, 3U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 2 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal") /* Channel 2 reference values */ PAR_ITEM_F32(ePAR_CH2_TSIM, 120, "Ch2 Ref Temperature", -20.0f, 350.0f, 0.0f, "degC", ePAR_ACCESS_RO, false, "Channel 2 temperature in degree C") @@ -171,12 +171,12 @@ PAR_ITEM_U16(ePAR_CH4_VDAC_F_RAW, 328, "Ch4 Ref Raw DAC Fine", 0U, PAR_ITEM_U16(ePAR_CH4_VDAC_C_RAW, 329, "Ch4 Ref Raw DAC Coarse", 0U, 4095U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 4 raw coarse DAC value") /* Channel 4 measurements */ -PAR_ITEM_F32(ePAR_CH4_CUR, 350, "Ch4 Act Isink", 0.0f, 5000.0f, 0.0f, "uA", ePAR_ACCESS_RO, false, "Channel 4 actual current sink value in uA") -PAR_ITEM_F32(ePAR_CH4_VOL, 351, "Ch4 Act Vout", 0.0f, 10.0f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 4 actual output voltage value in V") -PAR_ITEM_U16(ePAR_CH4_CUR_RAW, 352, "Ch4 Raw Isink", 0U, 8191U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 4 raw ADC current value") -PAR_ITEM_U16(ePAR_CH4_VOL_RAW, 353, "Ch4 Raw Vout", 0U, 8191U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 4 raw ADC voltage value") -PAR_ITEM_F32(ePAR_CH4_AFE_VCC, 354, "Ch4 AFE Vcc", 0.0f, 10.0f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 4 measured AFE Vcc voltage value in V") -PAR_ITEM_F32(ePAR_CH4_AFE_RES, 355, "Ch4 AFE Res", 0.0f, 5.0e3f, 0.0f, "ohm", ePAR_ACCESS_RO, false, "Channel 4 measured AFE resistance (pull-up + series) value in ohm") +PAR_ITEM_F32(ePAR_CH4_CUR, 360, "Ch4 Act Isink", 0.0f, 5000.0f, 0.0f, "uA", ePAR_ACCESS_RO, false, "Channel 4 actual current sink value in uA") +PAR_ITEM_F32(ePAR_CH4_VOL, 361, "Ch4 Act Vout", 0.0f, 10.0f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 4 actual output voltage value in V") +PAR_ITEM_U16(ePAR_CH4_CUR_RAW, 362, "Ch4 Raw Isink", 0U, 8191U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 4 raw ADC current value") +PAR_ITEM_U16(ePAR_CH4_VOL_RAW, 363, "Ch4 Raw Vout", 0U, 8191U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 4 raw ADC voltage value") +PAR_ITEM_F32(ePAR_CH4_AFE_VCC, 364, "Ch4 AFE Vcc", 0.0f, 10.0f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 4 measured AFE Vcc voltage value in V") +PAR_ITEM_F32(ePAR_CH4_AFE_RES, 366, "Ch4 AFE Res", 0.0f, 5.0e3f, 0.0f, "ohm", ePAR_ACCESS_RO, false, "Channel 4 measured AFE resistance (pull-up + series) value in ohm") /* ============================================================================================================================= */ @@ -186,4 +186,4 @@ PAR_ITEM_F32(ePAR_CH4_AFE_RES, 355, "Ch4 AFE Res", 0.0f, /* System status */ PAR_ITEM_U32(ePAR_SYS_STATUS, 10000, "System status", 0U, UINT32_MAX, 0U, NULL, ePAR_ACCESS_RO, false, "0-None | For other look at sys_err.h") PAR_ITEM_F32(ePAR_SYS_CPU_LOAD, 10010, "CPU load", 0.0f, 100.0f, 0.0f, "%", ePAR_ACCESS_RO, false, "Current CPU load in %") -PAR_ITEM_F32(ePAR_SYS_CPU_LOAD_MAX, 10011, "CPU Max. load", 0.0f, 100.0f, 0.0f, "%", ePAR_ACCESS_RO, false, "Maximum CPU load in %") \ No newline at end of file +PAR_ITEM_F32(ePAR_SYS_CPU_LOAD_MAX, 10018, "CPU Max. load", 0.0f, 100.0f, 0.0f, "%", ePAR_ACCESS_RO, false, "Maximum CPU load in %") \ No newline at end of file From 5e3edc105679709576999edc891ead5de4711981 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Wed, 25 Mar 2026 11:37:17 +0800 Subject: [PATCH 19/36] fix(parameters): Correct typed getter/setter error handling --- README.md | 11 +- docs/api-reference.md | 31 ++-- docs/getting-started.md | 14 +- src/par.c | 344 ++++++++++++++++++++++++++-------------- src/par.h | 44 ++--- src/par_typed_impl.inc | 39 +++-- 6 files changed, 292 insertions(+), 191 deletions(-) diff --git a/README.md b/README.md index 329a110..edae8c2 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ It is designed for projects that need a clean way to: 3. Provide `port/par_cfg_port.h` in your include path. 4. Optionally provide `port/par_if_port.c` and `port/par_atomic_port.h` when your platform needs them. 5. Call `par_init()` before using runtime APIs. -6. Use the typed macro wrappers such as `PAR_SET_U16` and `PAR_GET_U16`, or the typed `par_set_*` / `par_get_*` APIs in application code. +6. Use the typed `par_set_*` / `par_get_*` APIs in application code. Getter APIs now use an explicit output pointer and return `par_status_t`. A minimal example: @@ -47,10 +47,13 @@ static void app_init(void) /* Handle initialization error */ } - PAR_SET_F32(ePAR_CH1_REF_VAL, (float32_t)25.0f); + (void)par_set_f32(ePAR_CH1_REF_VAL, (float32_t)25.0f); float32_t ref_val = 0.0f; - PAR_GET_F32(ePAR_CH1_REF_VAL, ref_val); + if (par_get_f32(ePAR_CH1_REF_VAL, &ref_val) != ePAR_OK) + { + /* Handle read error */ + } } ``` @@ -119,7 +122,7 @@ This repository contains the reusable module core and templates. A real integrat ## Key integration notes - `par_cfg.h` includes `par_cfg_port.h` unconditionally, so your build must provide that header. -- `PAR_CFG_ENABLE_TYPE_F32` controls whether floating-point parameter support, related typed APIs, and the `PAR_SET_F32` / `PAR_GET_F32` macro wrappers are compiled in. +- `PAR_CFG_ENABLE_TYPE_F32` controls whether floating-point parameter support and the related typed APIs are compiled in. - `PAR_CFG_ENABLE_RUNTIME_VALIDATION` and `PAR_CFG_ENABLE_CHANGE_CALLBACK` control whether normal setters include runtime validation callbacks and on-change callbacks. - The module separates **internal parameter enumeration** (`par_num_t`) from **external parameter IDs** (`id`). - The current ID lookup implementation uses a one-entry-per-bucket hash map generated at compile time from `par_table.def`. External IDs must therefore be not only unique, but also collision-free under the configured hash geometry. Optional runtime diagnostic scans can be enabled with `PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK` and `PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK` when additional startup logs are useful. See `docs/architecture.md` for the collision rule and avoidance guidance. diff --git a/docs/api-reference.md b/docs/api-reference.md index 17fcdc1..b5835ff 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -9,7 +9,7 @@ This document groups the public API from `src/par.h` by responsibility. - `par_num_t` is the internal parameter index. - ID-based APIs depend on `PAR_CFG_ENABLE_ID = 1`. - NVM APIs depend on `PAR_CFG_NVM_EN = 1`. -- `F32` typed APIs and the `PAR_SET_F32` / `PAR_GET_F32` macro wrappers depend on `PAR_CFG_ENABLE_TYPE_F32 = 1`. +- `F32` typed APIs depend on `PAR_CFG_ENABLE_TYPE_F32 = 1`. - Validation registration APIs depend on `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 1`. - On-change registration APIs depend on `PAR_CFG_ENABLE_CHANGE_CALLBACK = 1`. @@ -24,7 +24,6 @@ The module conditionally compiles parts of the API based on configuration. - `par_get_f32()` - `par_set_f32_fast()` - `PAR_SET_F32` - - `PAR_GET_F32` - `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 1` enables: - `par_register_validation()` - runtime validation callbacks in normal setter paths @@ -141,29 +140,19 @@ These reset APIs are also different from startup initialization: | `par_get(par_num, p_val)` | Read a parameter into a typed destination pointer. | | `par_get_by_id(id, p_val)` | Read a parameter using its external ID. | -## Typed getter macro wrappers - -| Macro | Description | -| --- | --- | -| `PAR_GET_U8(par_num, dest)` | Assign the result of `par_get_u8()` through a typed macro wrapper. | -| `PAR_GET_I8(par_num, dest)` | Assign the result of `par_get_i8()` through a typed macro wrapper. | -| `PAR_GET_U16(par_num, dest)` | Assign the result of `par_get_u16()` through a typed macro wrapper. | -| `PAR_GET_I16(par_num, dest)` | Assign the result of `par_get_i16()` through a typed macro wrapper. | -| `PAR_GET_U32(par_num, dest)` | Assign the result of `par_get_u32()` through a typed macro wrapper. | -| `PAR_GET_I32(par_num, dest)` | Assign the result of `par_get_i32()` through a typed macro wrapper. | -| `PAR_GET_F32(par_num, dest)` | Assign the result of `par_get_f32()` through a typed macro wrapper. Available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. | +Typed getter macros are removed. Call the typed getter functions directly and always check the returned status. ## Typed getter functions | Function | Description | | --- | --- | -| `par_get_u8()` | Read a `U8` parameter. | -| `par_get_i8()` | Read an `I8` parameter. | -| `par_get_u16()` | Read a `U16` parameter. | -| `par_get_i16()` | Read an `I16` parameter. | -| `par_get_u32()` | Read a `U32` parameter. | -| `par_get_i32()` | Read an `I32` parameter. | -| `par_get_f32()` | Read an `F32` parameter. Available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. | +| `par_get_u8(par_num, p_val)` | Read a `U8` parameter into `*p_val`. Returns status. | +| `par_get_i8(par_num, p_val)` | Read an `I8` parameter into `*p_val`. Returns status. | +| `par_get_u16(par_num, p_val)` | Read a `U16` parameter into `*p_val`. Returns status. | +| `par_get_i16(par_num, p_val)` | Read an `I16` parameter into `*p_val`. Returns status. | +| `par_get_u32(par_num, p_val)` | Read a `U32` parameter into `*p_val`. Returns status. | +| `par_get_i32(par_num, p_val)` | Read an `I32` parameter into `*p_val`. Returns status. | +| `par_get_f32(par_num, p_val)` | Read an `F32` parameter into `*p_val`. Available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. Returns status. | | `par_get_default(par_num, p_val)` | Read the configured default value for a parameter. | ## Metadata access @@ -263,6 +252,8 @@ Common values include: - `ePAR_ERROR_TYPE` - `ePAR_ERROR_MUTEX` - `ePAR_ERROR_VALUE` +- `ePAR_ERROR_PARAM` +- `ePAR_ERROR_PAR_NUM` - `ePAR_WAR_SET_TO_DEF` - `ePAR_WAR_NVM_REWRITTEN` - `ePAR_WAR_NO_PERSISTANT` diff --git a/docs/getting-started.md b/docs/getting-started.md index 718e7be..d83fdf5 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -15,7 +15,7 @@ This guide shows how to integrate the `Device Parameters` module into a firmware - raw reset-all support - `F32` parameter support 5. Call `par_init()` before runtime access. -6. Use typed APIs or the typed macro wrappers such as `PAR_SET_U16` and `PAR_GET_U16`. +6. Use the typed APIs. Getter calls take an explicit output pointer and return `par_status_t`. ## Required files @@ -214,7 +214,6 @@ When `PAR_CFG_ENABLE_TYPE_F32 = 0`: * `PAR_ITEM_F32(...)` entries are not allowed in `par_table.def` * `par_set_f32()`, `par_get_f32()`, and `par_set_f32_fast()` are not available -* `PAR_SET_F32` and `PAR_GET_F32` are not available * startup F32 default patching is skipped ## Initialization @@ -247,19 +246,20 @@ Do not rely on startup initialization to trigger application callbacks or runtim The `F32` examples in this section require `PAR_CFG_ENABLE_TYPE_F32 = 1`. -### Use the typed macro wrappers in normal application code +### Use the typed APIs in normal application code ```c -PAR_SET_F32(ePAR_TARGET_TEMP, (float32_t)42.5f); +(void)par_set_f32(ePAR_TARGET_TEMP, (float32_t)42.5f); float32_t target_temp = 0.0f; -PAR_GET_F32(ePAR_TARGET_TEMP, target_temp); +(void)par_get_f32(ePAR_TARGET_TEMP, &target_temp); ``` ### Use typed APIs when explicitness matters ```c (void)par_set_u16(ePAR_PWM_LIMIT, 1024U); -uint16_t pwm_limit = par_get_u16(ePAR_PWM_LIMIT); +uint16_t pwm_limit = 0U; +(void)par_get_u16(ePAR_PWM_LIMIT, &pwm_limit); ``` ### Use pointer-based generic APIs only when needed @@ -377,7 +377,7 @@ uint32_t baud = 115200U; - Changing external IDs without rebuilding and checking the compile-time ID-map validation output - Assuming the repository already ships a ready-to-build `par_table.def` for your project - Disabling `PAR_CFG_ENABLE_TYPE_F32` while keeping `PAR_ITEM_F32(...)` entries in `par_table.def` -- Assuming `PAR_SET_F32` and `PAR_GET_F32` are still available after F32 support is disabled +- Assuming `par_set_f32()` and `par_get_f32()` are still available after F32 support is disabled - Registering validation or change callbacks without enabling the matching configuration macro ### Compile-time error example when F32 support is disabled diff --git a/src/par.c b/src/par.c index eb4bd28..ab93cc2 100644 --- a/src/par.c +++ b/src/par.c @@ -10,7 +10,7 @@ *@author Matej Otic *@email otic.matej@dancing-bits.com *@date 29.01.2026 -*@version V3.0.1 +*@version V3.0.2 */ //////////////////////////////////////////////////////////////////////////////// /** @@ -164,12 +164,16 @@ static par_atomic_f32_t * const gpf32_par_value = (par_atomic_f32_t *)gs_par_sto "ERROR CRC", "ERROR TYPE", "ERROR MUTEX", - "ERROR_VALUE", - + "ERROR VALUE", + "ERROR PARAM", + "ERROR PAR NUM", "WARN SET TO DEF", "WARN NVM REWRITTEN", "NO PERSISTENT", "LIMITED", + "N/A", + "N/A", + "N/A", }; #endif @@ -183,9 +187,9 @@ static par_status_t par_runtime_validate_id_table (const par_cfg_t * const #endif #endif #if ( 1 == PAR_CFG_NVM_EN ) -static bool par_is_value_changed (const par_num_t par_num, const void * p_val); +static par_status_t par_is_value_changed (const par_num_t par_num, const void * p_val, bool * const p_value_changed); #endif /* ( 1 == PAR_CFG_NVM_EN ) */ -static par_status_t par_check_table_validity (const par_cfg_t * const p_par_cfg); +static par_status_t par_check_table_validity (const par_cfg_t * const p_par_cfg); //////////////////////////////////////////////////////////////////////////////// // Functions @@ -559,35 +563,42 @@ par_status_t par_set(const par_num_t par_num, const void * p_val) { par_status_t status = ePAR_OK; + PAR_ASSERT( par_num < ePAR_NUM_OF ); + + if ( NULL == p_val ) + { + return ePAR_ERROR_PARAM; + } + switch ( par_get_type(par_num)) { case ePAR_TYPE_U8: - status = par_set_u8( par_num, *(uint8_t*) p_val ); + status = par_set_u8( par_num, *(const uint8_t*) p_val ); break; case ePAR_TYPE_I8: - status = par_set_i8( par_num, *(int8_t*) p_val ); + status = par_set_i8( par_num, *(const int8_t*) p_val ); break; case ePAR_TYPE_U16: - status = par_set_u16( par_num, *(uint16_t*) p_val ); + status = par_set_u16( par_num, *(const uint16_t*) p_val ); break; case ePAR_TYPE_I16: - status = par_set_i16( par_num, *(int16_t*) p_val ); + status = par_set_i16( par_num, *(const int16_t*) p_val ); break; case ePAR_TYPE_U32: - status = par_set_u32( par_num, *(uint32_t*) p_val ); + status = par_set_u32( par_num, *(const uint32_t*) p_val ); break; case ePAR_TYPE_I32: - status = par_set_i32( par_num, *(int32_t*) p_val ); + status = par_set_i32( par_num, *(const int32_t*) p_val ); break; #if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) case ePAR_TYPE_F32: - status = par_set_f32( par_num, *(float32_t*) p_val ); + status = par_set_f32( par_num, *(const float32_t*) p_val ); break; #endif @@ -612,16 +623,15 @@ par_status_t par_set(const par_num_t par_num, const void * p_val) #if ( 1 == PAR_CFG_ENABLE_ID ) par_status_t par_set_by_id(const uint16_t id, const void * p_val) { - par_num_t par_num; + par_num_t par_num = 0U; + const par_status_t status = par_get_num_by_id( id, &par_num ); - if ( ePAR_OK == par_get_num_by_id( id, &par_num )) - { - return par_set( par_num, p_val ); - } - else + if ( ePAR_OK != status ) { - return ePAR_ERROR; + return status; } + + return par_set( par_num, p_val ); } #endif @@ -734,38 +744,91 @@ par_status_t par_set_all_to_default(void) //////////////////////////////////////////////////////////////////////////////// par_status_t par_has_changed(const par_num_t par_num, bool *const p_has_changed) { - const par_cfg_t * const par_cfg = par_get_config(par_num); + const par_cfg_t * par_cfg = NULL; + + PAR_ASSERT( par_num < ePAR_NUM_OF ); + + if ( NULL == p_has_changed ) + { + return ePAR_ERROR_PARAM; + } + + if ( true != par_is_init() ) + { + return ePAR_ERROR_INIT; + } + + par_cfg = par_get_config(par_num); + if ( NULL == par_cfg ) + { + return ePAR_ERROR; + } switch ( par_cfg->type ) { case ePAR_TYPE_U8: - *p_has_changed = (par_get_u8(par_num) != par_cfg->def.u8); + { + uint8_t cur = 0U; + const par_status_t status = par_get_u8(par_num, &cur); + if ( ePAR_OK != status ) return status; + *p_has_changed = (cur != par_cfg->def.u8); break; + } case ePAR_TYPE_I8: - *p_has_changed = (par_get_i8(par_num) != par_cfg->def.i8); + { + int8_t cur = 0; + const par_status_t status = par_get_i8(par_num, &cur); + if ( ePAR_OK != status ) return status; + *p_has_changed = (cur != par_cfg->def.i8); break; + } case ePAR_TYPE_U16: - *p_has_changed = (par_get_u16(par_num) != par_cfg->def.u16); + { + uint16_t cur = 0U; + const par_status_t status = par_get_u16(par_num, &cur); + if ( ePAR_OK != status ) return status; + *p_has_changed = (cur != par_cfg->def.u16); break; + } case ePAR_TYPE_I16: - *p_has_changed = (par_get_i16(par_num) != par_cfg->def.i16); + { + int16_t cur = 0; + const par_status_t status = par_get_i16(par_num, &cur); + if ( ePAR_OK != status ) return status; + *p_has_changed = (cur != par_cfg->def.i16); break; + } case ePAR_TYPE_U32: - *p_has_changed = (par_get_u32(par_num) != par_cfg->def.u32); + { + uint32_t cur = 0U; + const par_status_t status = par_get_u32(par_num, &cur); + if ( ePAR_OK != status ) return status; + *p_has_changed = (cur != par_cfg->def.u32); break; + } case ePAR_TYPE_I32: - *p_has_changed = (par_get_i32(par_num) != par_cfg->def.i32); + { + int32_t cur = 0; + const par_status_t status = par_get_i32(par_num, &cur); + if ( ePAR_OK != status ) return status; + *p_has_changed = (cur != par_cfg->def.i32); break; + } #if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) case ePAR_TYPE_F32: - *p_has_changed = (par_get_f32(par_num) != par_cfg->def.f32); + { + float32_t cur = 0.0f; + const par_status_t status = par_get_f32(par_num, &cur); + if ( ePAR_OK != status ) return status; + *p_has_changed = (cur != par_cfg->def.f32); break; + } #endif case ePAR_TYPE_NUM_OF: @@ -798,36 +861,36 @@ par_status_t par_has_changed(const par_num_t par_num, bool *const p_has_changed) //////////////////////////////////////////////////////////////////////////////// par_status_t par_get(const par_num_t par_num, void * const p_val) { + PAR_ASSERT( par_num < ePAR_NUM_OF ); + + if ( NULL == p_val ) + { + return ePAR_ERROR_PARAM; + } + switch ( par_get_type(par_num)) { case ePAR_TYPE_U8: - *(uint8_t*) p_val = par_get_u8(par_num); - break; + return par_get_u8(par_num, (uint8_t*) p_val); case ePAR_TYPE_I8: - *(int8_t*) p_val = par_get_i8(par_num); - break; + return par_get_i8(par_num, (int8_t*) p_val); case ePAR_TYPE_U16: - *(uint16_t*) p_val = par_get_u16(par_num); - break; + return par_get_u16(par_num, (uint16_t*) p_val); case ePAR_TYPE_I16: - *(int16_t*) p_val = par_get_i16(par_num); - break; + return par_get_i16(par_num, (int16_t*) p_val); case ePAR_TYPE_U32: - *(uint32_t*) p_val = par_get_u32(par_num); - break; + return par_get_u32(par_num, (uint32_t*) p_val); case ePAR_TYPE_I32: - *(int32_t*) p_val = par_get_i32(par_num); - break; + return par_get_i32(par_num, (int32_t*) p_val); #if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) case ePAR_TYPE_F32: - *(float32_t*) p_val = par_get_f32(par_num); - break; + return par_get_f32(par_num, (float32_t*) p_val); #endif case ePAR_TYPE_NUM_OF: @@ -835,8 +898,6 @@ par_status_t par_get(const par_num_t par_num, void * const p_val) PAR_ASSERT( 0 ); return ePAR_ERROR_TYPE; } - - return ePAR_OK; } //////////////////////////////////////////////////////////////////////////////// @@ -851,16 +912,15 @@ par_status_t par_get(const par_num_t par_num, void * const p_val) #if ( 1 == PAR_CFG_ENABLE_ID ) par_status_t par_get_by_id(const uint16_t id, void * const p_val) { - par_num_t par_num; + par_num_t par_num = 0U; + const par_status_t status = par_get_num_by_id( id, &par_num ); - if ( ePAR_OK == par_get_num_by_id( id, &par_num )) - { - return par_get( par_num, p_val ); - } - else + if ( ePAR_OK != status ) { - return ePAR_ERROR; + return status; } + + return par_get( par_num, p_val ); } #endif @@ -875,39 +935,52 @@ par_status_t par_get_by_id(const uint16_t id, void * const p_val) //////////////////////////////////////////////////////////////////////////////// par_status_t par_get_default(const par_num_t par_num, void * const p_val) { - // Check initialization - PAR_ASSERT( true == par_is_init()); + const par_cfg_t * par_cfg = NULL; + + PAR_ASSERT( par_num < ePAR_NUM_OF ); + + if ( NULL == p_val ) + { + return ePAR_ERROR_PARAM; + } + if ( true != par_is_init()) return ePAR_ERROR_INIT; - switch ( par_get_type( par_num )) + par_cfg = par_get_config(par_num); + if ( NULL == par_cfg ) + { + return ePAR_ERROR; + } + + switch ( par_cfg->type ) { case ePAR_TYPE_U8: - *(uint8_t*) p_val = (uint8_t) par_get_config(par_num)->def.u8; + *(uint8_t*) p_val = (uint8_t) par_cfg->def.u8; break; case ePAR_TYPE_I8: - *(int8_t*) p_val = (int8_t) par_get_config(par_num)->def.i8; + *(int8_t*) p_val = (int8_t) par_cfg->def.i8; break; case ePAR_TYPE_U16: - *(uint16_t*) p_val = (uint16_t) par_get_config(par_num)->def.u16; + *(uint16_t*) p_val = (uint16_t) par_cfg->def.u16; break; case ePAR_TYPE_I16: - *(int16_t*) p_val = (int16_t) par_get_config(par_num)->def.i16; + *(int16_t*) p_val = (int16_t) par_cfg->def.i16; break; case ePAR_TYPE_U32: - *(uint32_t*) p_val = (uint32_t) par_get_config(par_num)->def.u32; + *(uint32_t*) p_val = (uint32_t) par_cfg->def.u32; break; case ePAR_TYPE_I32: - *(int32_t*) p_val = (int32_t) par_get_config(par_num)->def.i32; + *(int32_t*) p_val = (int32_t) par_cfg->def.i32; break; #if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) case ePAR_TYPE_F32: - *(float32_t*) p_val = (float32_t) par_get_config(par_num)->def.f32; + *(float32_t*) p_val = (float32_t) par_cfg->def.f32; break; #endif @@ -920,50 +993,6 @@ par_status_t par_get_default(const par_num_t par_num, void * const p_val) return ePAR_OK; } -//////////////////////////////////////////////////////////////////////////////// -/** -* Check if parameter changed from its default value -* -* @param[in] par_num - Parameter number (enumeration) -* @return true if parameter value has been changed -*/ -//////////////////////////////////////////////////////////////////////////////// -bool par_is_changed(const par_num_t par_num) -{ - switch ( par_get_type(par_num)) - { - case ePAR_TYPE_U8: - return (bool) (par_get_u8(par_num) != par_get_config(par_num)->def.u8); - - case ePAR_TYPE_I8: - return (bool) (par_get_i8(par_num) != par_get_config(par_num)->def.i8); - - case ePAR_TYPE_U16: - return (bool) (par_get_u16(par_num) != par_get_config(par_num)->def.u16); - - case ePAR_TYPE_I16: - return (bool) (par_get_i16(par_num) != par_get_config(par_num)->def.i16); - - case ePAR_TYPE_U32: - return (bool) (par_get_u32(par_num) != par_get_config(par_num)->def.u32); - - case ePAR_TYPE_I32: - return (bool) (par_get_i32(par_num) != par_get_config(par_num)->def.i32); - -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) - case ePAR_TYPE_F32: - return (bool) (par_get_f32(par_num) != par_get_config(par_num)->def.f32); -#endif - - case ePAR_TYPE_NUM_OF: - default: - PAR_ASSERT( 0 ); - return false; - } - - return false; -} - //////////////////////////////////////////////////////////////////////////////// /** * Get parameter configurations @@ -1148,13 +1177,27 @@ bool par_is_persistant(const par_num_t par_num) #if ( 1 == PAR_CFG_ENABLE_ID ) par_status_t par_get_num_by_id(const uint16_t id, par_num_t * const p_par_num) { - if (( NULL != p_par_num ) && ( true == par_is_init() )) + if ( NULL == p_par_num ) + { + return ePAR_ERROR_PARAM; + } + + if ( true != par_is_init() ) + { + return ePAR_ERROR_INIT; + } + { const uint32_t bucket_idx = par_hash_id( id ); const par_id_map_entry_t * const bucket = &g_par_id_map_static[bucket_idx]; if (( 0u != bucket->used ) && ( id == bucket->id )) { + if ( bucket->par_num >= ePAR_NUM_OF ) + { + return ePAR_ERROR_PAR_NUM; + } + *p_par_num = bucket->par_num; return ePAR_OK; } @@ -1174,7 +1217,16 @@ par_status_t par_get_num_by_id(const uint16_t id, par_num_t * const p_par_num) //////////////////////////////////////////////////////////////////////////////// par_status_t par_get_id_by_num(const par_num_t par_num, uint16_t * const p_id) { - if ( NULL != p_id ) + if ( NULL == p_id ) + { + return ePAR_ERROR_PARAM; + } + + if ( par_num >= ePAR_NUM_OF ) + { + return ePAR_ERROR_PAR_NUM; + } + { const par_cfg_t * const par_cfg = par_get_config(par_num); @@ -1199,49 +1251,94 @@ par_status_t par_get_id_by_num(const par_num_t par_num, uint16_t * const p_id) * @return True if parameter value is different from current */ //////////////////////////////////////////////////////////////////////////////// - static bool par_is_value_changed(const par_num_t par_num, const void * p_val) + static par_status_t par_is_value_changed(const par_num_t par_num, const void * p_val, bool * const p_value_changed) { - bool value_changed = false; + PAR_ASSERT( par_num < ePAR_NUM_OF ); + + if (( NULL == p_val ) || ( NULL == p_value_changed )) + { + return ePAR_ERROR_PARAM; + } + + if ( true != par_is_init() ) + { + return ePAR_ERROR_INIT; + } switch ( par_get_type(par_num)) { case ePAR_TYPE_U8: - value_changed = (par_get_u8(par_num) != *(uint8_t*)p_val); + { + uint8_t cur = 0U; + const par_status_t status = par_get_u8(par_num, &cur); + if ( ePAR_OK != status ) return status; + *p_value_changed = (cur != *(const uint8_t*)p_val); break; + } case ePAR_TYPE_I8: - value_changed = (par_get_i8(par_num) != *(int8_t*)p_val); + { + int8_t cur = 0; + const par_status_t status = par_get_i8(par_num, &cur); + if ( ePAR_OK != status ) return status; + *p_value_changed = (cur != *(const int8_t*)p_val); break; + } case ePAR_TYPE_U16: - value_changed = (par_get_u16(par_num) != *(uint16_t*)p_val); + { + uint16_t cur = 0U; + const par_status_t status = par_get_u16(par_num, &cur); + if ( ePAR_OK != status ) return status; + *p_value_changed = (cur != *(const uint16_t*)p_val); break; + } case ePAR_TYPE_I16: - value_changed = (par_get_i16(par_num) != *(int16_t*)p_val); + { + int16_t cur = 0; + const par_status_t status = par_get_i16(par_num, &cur); + if ( ePAR_OK != status ) return status; + *p_value_changed = (cur != *(const int16_t*)p_val); break; + } case ePAR_TYPE_U32: - value_changed = (par_get_u32(par_num) != *(uint32_t*)p_val); + { + uint32_t cur = 0U; + const par_status_t status = par_get_u32(par_num, &cur); + if ( ePAR_OK != status ) return status; + *p_value_changed = (cur != *(const uint32_t*)p_val); break; + } case ePAR_TYPE_I32: - value_changed = (par_get_i32(par_num) != *(int32_t*)p_val); + { + int32_t cur = 0; + const par_status_t status = par_get_i32(par_num, &cur); + if ( ePAR_OK != status ) return status; + *p_value_changed = (cur != *(const int32_t*)p_val); break; + } #if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) case ePAR_TYPE_F32: - value_changed = (par_get_f32(par_num) != *(float32_t*)p_val); + { + float32_t cur = 0.0f; + const par_status_t status = par_get_f32(par_num, &cur); + if ( ePAR_OK != status ) return status; + *p_value_changed = (cur != *(const float32_t*)p_val); break; + } #endif case ePAR_TYPE_NUM_OF: default: PAR_ASSERT( 0 ); - break; + return ePAR_ERROR_TYPE; } - return value_changed; + return ePAR_OK; } //////////////////////////////////////////////////////////////////////////////// /** @@ -1264,11 +1361,18 @@ par_status_t par_get_id_by_num(const par_num_t par_num, uint16_t * const p_id) //////////////////////////////////////////////////////////////////////////////// par_status_t par_set_n_save(const par_num_t par_num, const void * p_val) { - // Check if parameter value is about to change - const bool value_change = par_is_value_changed( par_num, p_val ); + bool value_change = false; + par_status_t status = par_is_value_changed( par_num, p_val, &value_change ); + + if ( ePAR_OK != status ) + { + PAR_DBG_PRINT( "PAR: failed to read current value before set_n_save for par_num=%u with status=%s", (unsigned) par_num, par_get_status_str( status )); + PAR_ASSERT( 0 ); + return status; + } // Set parameter - par_status_t status = par_set(par_num, p_val); + status = par_set(par_num, p_val); // Parameter set OK and value has been changed -> makes sense to store to NVM if (( ePAR_OK == status ) && value_change ) diff --git a/src/par.h b/src/par.h index b6cd54c..af68333 100644 --- a/src/par.h +++ b/src/par.h @@ -40,7 +40,7 @@ */ #define PAR_VER_MAJOR ( 3 ) #define PAR_VER_MINOR ( 0 ) -#define PAR_VER_DEVELOP ( 1 ) +#define PAR_VER_DEVELOP ( 2 ) /** * Parameter status @@ -50,7 +50,7 @@ enum ePAR_OK = 0U, /** set PAR_CFG_NVM_EN to 0 */ - ePAR_WAR_LIMITED = 0x0800U, /** set PAR_CFG_NVM_EN to 0 */ + ePAR_WAR_LIMITED = 0x1000U, /**= ePAR_NUM_OF) \ + return ePAR_ERROR_PAR_NUM; \ + \ PAR_ASSERT(ETYPE == par_get_type(par_num)); \ if (ETYPE != par_get_type(par_num)) \ return ePAR_ERROR_TYPE; \ @@ -127,6 +131,7 @@ PAR_TYPED_ROWS(PAR_TYPED_IMPL_SETTER) par_status_t par_set_##NAME##_fast(const par_num_t par_num, const CTYPE val) \ { \ PAR_ASSERT(true == par_is_init()); \ + PAR_ASSERT(par_num < ePAR_NUM_OF); \ PAR_ASSERT(ETYPE == par_get_type(par_num)); \ PAR_TYPED_FAST_SET_BODY(PRIV, FIELD, par_num, val); \ } @@ -134,18 +139,28 @@ PAR_TYPED_ROWS(PAR_TYPED_IMPL_SETTER) PAR_TYPED_ROWS(PAR_TYPED_IMPL_FAST_SETTER) #undef PAR_TYPED_IMPL_FAST_SETTER -#define PAR_TYPED_IMPL_GETTER(NAME, PRIV, CTYPE, ETYPE, FIELD) \ - CTYPE par_get_##NAME(const par_num_t par_num) \ - { \ - PAR_ASSERT(true == par_is_init()); \ - if (true != par_is_init()) \ - return (CTYPE)ePAR_ERROR_INIT; \ - \ - PAR_ASSERT(ETYPE == par_get_type(par_num)); \ - if (ETYPE != par_get_type(par_num)) \ - return (CTYPE)ePAR_ERROR_TYPE; \ - \ - return PAR_GET_##PRIV##_PRIV(par_num); \ +/* Generate typed getters: par_get_xxx() */ +#define PAR_TYPED_IMPL_GETTER(NAME, PRIV, CTYPE, ETYPE, FIELD) \ + par_status_t par_get_##NAME(const par_num_t par_num, CTYPE * const p_val) \ + { \ + PAR_ASSERT(true == par_is_init()); \ + if (true != par_is_init()) \ + return ePAR_ERROR_INIT; \ + \ + PAR_ASSERT(par_num < ePAR_NUM_OF); \ + if (par_num >= ePAR_NUM_OF) \ + return ePAR_ERROR_PAR_NUM; \ + \ + PAR_ASSERT(ETYPE == par_get_type(par_num)); \ + if (ETYPE != par_get_type(par_num)) \ + return ePAR_ERROR_TYPE; \ + \ + PAR_ASSERT(NULL != p_val); \ + if (NULL == p_val) \ + return ePAR_ERROR_PARAM; \ + \ + *p_val = PAR_GET_##PRIV##_PRIV(par_num); \ + return ePAR_OK; \ } PAR_TYPED_ROWS(PAR_TYPED_IMPL_GETTER) From bdba2ba70e890d941d8cf68356643624bee9ebad Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Wed, 25 Mar 2026 14:29:17 +0800 Subject: [PATCH 20/36] fix(API): Correct runtime validation and F32 change detection - keep public fast setter names unchanged - restore PAR_CFG_ENABLE_RESET_ALL_RAW fast path in par_set_all_to_default() - return correct status when reset-all uses raw path - collapse validation helpers into metadata/runtime variants - remove redundant outer NULL checks and centralize validation - use bitwise compare for F32 change detection - remove legacy persistant compatibility API - update comments and API docs to match current behavior --- README.md | 2 +- docs/api-reference.md | 23 ++-- docs/architecture.md | 2 +- src/par.c | 246 ++++++++++++++++++++++++++++------------- src/par.h | 25 ++++- src/par_def.c | 2 +- src/par_nvm.c | 18 +-- src/par_typed_impl.inc | 126 ++++++++++++--------- 8 files changed, 287 insertions(+), 157 deletions(-) diff --git a/README.md b/README.md index edae8c2..61c34ea 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ This repository contains the reusable module core and templates. A real integrat - `PAR_CFG_ENABLE_RUNTIME_VALIDATION` and `PAR_CFG_ENABLE_CHANGE_CALLBACK` control whether normal setters include runtime validation callbacks and on-change callbacks. - The module separates **internal parameter enumeration** (`par_num_t`) from **external parameter IDs** (`id`). - The current ID lookup implementation uses a one-entry-per-bucket hash map generated at compile time from `par_table.def`. External IDs must therefore be not only unique, but also collision-free under the configured hash geometry. Optional runtime diagnostic scans can be enabled with `PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK` and `PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK` when additional startup logs are useful. See `docs/architecture.md` for the collision rule and avoidance guidance. -- Fast setter APIs skip part of the safety and observability path, including runtime validation callbacks and on-change callbacks, so they should be reserved for tightly controlled hot paths. +- Unchecked setter APIs skip runtime validation callbacks and on-change callbacks, so they should be reserved for tightly controlled hot paths. Legacy `*_fast()` names remain as deprecated aliases. - NVM support is optional, but when enabled it depends on the external NVM module and on ID and persistence metadata being enabled. - `par_init()` applies startup default values directly to live storage. Integer default values from `par_table.def` are compiled into a grouped width-based storage object, while `F32` default values are applied to the 32-bit storage group after layout offsets are available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. Because this startup initialization does not go through the public setter path, it does not invoke runtime validation or on-change callbacks. - `PAR_CFG_ENABLE_RESET_ALL_RAW` controls whether raw reset-all support and grouped default mirror snapshot support are enabled. diff --git a/docs/api-reference.md b/docs/api-reference.md index b5835ff..279925a 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -113,25 +113,22 @@ Use these only in controlled hot paths. | Function | Description | | --- | --- | | `par_set_to_default(par_num)` | Reset one parameter to its configured default value through the normal runtime setter path. | -| `par_set_all_to_default()` | Reset all parameters to their default values. Depending on configuration, this may use either the normal runtime reset path or a raw grouped-storage restore path. | +| `par_set_all_to_default()` | Reset all parameters to their default values. When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`, this public API forwards to the raw grouped-storage reset path for speed. Otherwise it iterates through the normal runtime setter path and aggregates per-parameter status bits. | | `par_reset_all_to_default_raw()` | Restore all live values from a grouped default mirror snapshot via raw memory copy. The internal storage model still uses `U8/U16/U32` width groups. Available only when `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`. | | `par_has_changed(par_num, p_has_changed)` | Report whether the value differs from its default. | -| `par_is_changed(par_num)` | Return whether the value differs from its default. | `par_set_to_default()` always uses the normal runtime setter path. -`par_set_all_to_default()` is configuration-dependent: +`par_set_all_to_default()` is configuration-dependent. When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`, it forwards to `par_reset_all_to_default_raw()` for the fastest bulk restore path. When the raw-reset option is disabled, it iterates through parameters and uses the normal runtime setter path. -- when `PAR_CFG_ENABLE_RESET_ALL_RAW = 0`, it iterates through parameters and resets them through the normal runtime path -- when `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`, it restores the grouped live storage from the internal default mirror through the raw reset-all path +Use `par_reset_all_to_default_raw()` when you want to call the raw grouped-storage restore path explicitly. -That distinction matters if your application depends on runtime validation callbacks, on-change callbacks, or other setter-side effects. - -These reset APIs are also different from startup initialization: +These reset APIs are different from startup initialization: - `par_init()` applies startup defaults internally to live storage -- `par_set_to_default()` always uses runtime setter semantics -- `par_set_all_to_default()` may bypass per-parameter runtime setter semantics when raw reset-all support is enabled +- `par_set_to_default()` uses runtime setter semantics for one parameter +- `par_set_all_to_default()` uses raw restore semantics when raw reset is enabled, otherwise it uses runtime setter semantics +- `par_reset_all_to_default_raw()` always bypasses per-parameter runtime setter semantics ## Pointer-based getters @@ -168,8 +165,8 @@ These APIs do not follow the same runtime usage pattern as the value access APIs | `par_get_desc(par_num)` | Return the description string when description metadata is enabled. | | `par_get_type(par_num)` | Return the parameter type enum. | | `par_get_access(par_num)` | Return read-only or read-write access metadata when enabled. | -| `par_is_persistant(par_num)` | Return whether the parameter is marked persistent when enabled. | -| `par_get_num_by_id(id, p_par_num)` | Convert an external ID to `par_num_t` through the compile-time generated static ID map. | +| `par_is_persistent(par_num)` | Return whether the parameter is marked persistent when enabled. | +| `par_get_num_by_id(id, p_par_num)` | Convert an external ID to `par_num_t` through the compile-time generated static ID map. This metadata API does not require `par_init()`. | | `par_get_id_by_num(par_num, p_id)` | Convert `par_num_t` to external ID. | ## NVM APIs @@ -256,5 +253,5 @@ Common values include: - `ePAR_ERROR_PAR_NUM` - `ePAR_WAR_SET_TO_DEF` - `ePAR_WAR_NVM_REWRITTEN` -- `ePAR_WAR_NO_PERSISTANT` +- `ePAR_WAR_NO_PERSISTENT` - `ePAR_WAR_LIMITED` diff --git a/docs/architecture.md b/docs/architecture.md index 1168cb0..855e39d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -308,7 +308,7 @@ It restores live values by one grouped `memcpy` from the default mirror snapshot - on-change callbacks - normal setter range/flow semantics -This path is intentionally separate from `par_set_all_to_default()`, which keeps normal runtime setter behavior. +When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`, `par_set_all_to_default()` also uses this raw reset path for performance. ## Optional NVM persistence diff --git a/src/par.c b/src/par.c index ab93cc2..9e6f48e 100644 --- a/src/par.c +++ b/src/par.c @@ -189,8 +189,6 @@ static par_status_t par_runtime_validate_id_table (const par_cfg_t * const #if ( 1 == PAR_CFG_NVM_EN ) static par_status_t par_is_value_changed (const par_num_t par_num, const void * p_val, bool * const p_value_changed); #endif /* ( 1 == PAR_CFG_NVM_EN ) */ -static par_status_t par_check_table_validity (const par_cfg_t * const p_par_cfg); - //////////////////////////////////////////////////////////////////////////////// // Functions //////////////////////////////////////////////////////////////////////////////// @@ -213,6 +211,101 @@ PAR_PORT_WEAK bool par_port_is_desc_valid(const char * const p_desc) } #endif +//////////////////////////////////////////////////////////////////////////////// +/** +* Validate metadata access for parameter identified by number +* +* @note Metadata access only checks parameter number and table entry. +* It intentionally does not require the runtime module to be +* initialized, because callers only read compile-time metadata. +* +* @param[in] par_num - Parameter number (enumeration) +* @param[in] p_arg - Optional pointer argument to validate +* @param[in] require_arg - True if p_arg must not be NULL +* @param[out] pp_cfg - Optional output pointer to parameter configuration +* @return status - Status of operation +*/ +//////////////////////////////////////////////////////////////////////////////// +static par_status_t par_validate_metadata(const par_num_t par_num, const void * const p_arg, const bool require_arg, const par_cfg_t ** const pp_cfg) +{ + const par_cfg_t * p_cfg = NULL; + + if (( true == require_arg ) && ( NULL == p_arg )) + { + return ePAR_ERROR_PARAM; + } + + PAR_ASSERT( par_num < ePAR_NUM_OF ); + if ( par_num >= ePAR_NUM_OF ) + { + return ePAR_ERROR_PAR_NUM; + } + + p_cfg = par_get_config( par_num ); + if ( NULL == p_cfg ) + { + return ePAR_ERROR; + } + + if ( NULL != pp_cfg ) + { + *pp_cfg = p_cfg; + } + + return ePAR_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +/** +* Validate runtime access for parameter identified by number +* +* @note Runtime access extends metadata validation with module init +* state validation, because live parameter storage is only valid +* after successful par_init(). +* +* @param[in] par_num - Parameter number (enumeration) +* @param[in] p_arg - Optional pointer argument to validate +* @param[in] require_arg - True if p_arg must not be NULL +* @param[out] pp_cfg - Optional output pointer to parameter configuration +* @return status - Status of operation +*/ +//////////////////////////////////////////////////////////////////////////////// +static par_status_t par_validate_runtime(const par_num_t par_num, const void * const p_arg, const bool require_arg, const par_cfg_t ** const pp_cfg) +{ + if ( true != par_is_init() ) + { + return ePAR_ERROR_INIT; + } + + return par_validate_metadata( par_num, p_arg, require_arg, pp_cfg ); +} + +//////////////////////////////////////////////////////////////////////////////// +/** +* Compare two F32 values by raw bit pattern +* +* @note This helper intentionally uses memcpy() instead of pointer +* casting or union type-punning. memcpy() preserves the exact +* IEEE-754 bit pattern while remaining strict-aliasing safe. +* Bitwise comparison keeps NaN payloads and signed-zero handling +* deterministic for parameter storage use cases. +* +* @param[in] lhs - Left-hand float value +* @param[in] rhs - Right-hand float value +* @return true if raw 32-bit representations are equal +*/ +//////////////////////////////////////////////////////////////////////////////// +static bool par_f32_bits_equal(const float32_t lhs, const float32_t rhs) +{ + uint32_t lhs_bits = 0U; + uint32_t rhs_bits = 0U; + + memcpy( &lhs_bits, &lhs, sizeof(lhs_bits) ); + memcpy( &rhs_bits, &rhs, sizeof(rhs_bits) ); + + return ( lhs_bits == rhs_bits ); +} + #if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) //////////////////////////////////////////////////////////////////////////////// /** @@ -562,15 +655,15 @@ void par_release_mutex(const par_num_t par_num) par_status_t par_set(const par_num_t par_num, const void * p_val) { par_status_t status = ePAR_OK; + const par_cfg_t * par_cfg = NULL; - PAR_ASSERT( par_num < ePAR_NUM_OF ); - - if ( NULL == p_val ) + status = par_validate_runtime(par_num, p_val, true, &par_cfg); + if ( ePAR_OK != status ) { - return ePAR_ERROR_PARAM; + return status; } - switch ( par_get_type(par_num)) + switch ( par_cfg->type ) { case ePAR_TYPE_U8: status = par_set_u8( par_num, *(const uint8_t*) p_val ); @@ -664,7 +757,15 @@ par_status_t par_set_by_id(const uint16_t id, const void * p_val) //////////////////////////////////////////////////////////////////////////////// par_status_t par_set_to_default(const par_num_t par_num) { - return par_set(par_num, &(par_get_config(par_num)->def)); + const par_cfg_t * par_cfg = NULL; + par_status_t status = par_validate_runtime(par_num, NULL, false, &par_cfg); + + if ( ePAR_OK != status ) + { + return status; + } + + return par_set(par_num, &par_cfg->def); } #if ( 1 == PAR_CFG_ENABLE_RESET_ALL_RAW ) @@ -711,26 +812,35 @@ par_status_t par_reset_all_to_default_raw(void) /** * Set all parameters to default value * -* @pre Parameters must be initialised before usage! -* @note This function uses normal runtime setter path via par_set_to_default() -* and keeps setter semantics. +* @pre Parameters must be initialised before usage! +* @note When PAR_CFG_ENABLE_RESET_ALL_RAW = 1, this public API forwards +* to par_reset_all_to_default_raw() for maximum reset speed. +* @note Otherwise it iterates through parameters and resets them via +* par_set_to_default(), preserving normal runtime setter semantics. * -* @return status - Status of operation +* @return status - Status of operation */ //////////////////////////////////////////////////////////////////////////////// par_status_t par_set_all_to_default(void) { #if ( 1 == PAR_CFG_ENABLE_RESET_ALL_RAW ) - (void) par_reset_all_to_default_raw(); + return par_reset_all_to_default_raw(); #else + par_status_t status = ePAR_OK; + + if ( true != par_is_init() ) + { + return ePAR_ERROR_INIT; + } + for ( par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) { - // Ignore return as it is not possible to return other that OK - (void) par_set_to_default( par_num ); + status |= par_set_to_default( par_num ); } + PAR_DBG_PRINT( "PAR: Setting all parameters to default" ); + return status; #endif - return ePAR_OK; } //////////////////////////////////////////////////////////////////////////////// @@ -745,23 +855,12 @@ par_status_t par_set_all_to_default(void) par_status_t par_has_changed(const par_num_t par_num, bool *const p_has_changed) { const par_cfg_t * par_cfg = NULL; + par_status_t status = ePAR_OK; - PAR_ASSERT( par_num < ePAR_NUM_OF ); - - if ( NULL == p_has_changed ) - { - return ePAR_ERROR_PARAM; - } - - if ( true != par_is_init() ) - { - return ePAR_ERROR_INIT; - } - - par_cfg = par_get_config(par_num); - if ( NULL == par_cfg ) + status = par_validate_runtime(par_num, p_has_changed, true, &par_cfg); + if ( ePAR_OK != status ) { - return ePAR_ERROR; + return status; } switch ( par_cfg->type ) @@ -826,7 +925,7 @@ par_status_t par_has_changed(const par_num_t par_num, bool *const p_has_changed) float32_t cur = 0.0f; const par_status_t status = par_get_f32(par_num, &cur); if ( ePAR_OK != status ) return status; - *p_has_changed = (cur != par_cfg->def.f32); + *p_has_changed = !par_f32_bits_equal(cur, par_cfg->def.f32); break; } #endif @@ -861,14 +960,16 @@ par_status_t par_has_changed(const par_num_t par_num, bool *const p_has_changed) //////////////////////////////////////////////////////////////////////////////// par_status_t par_get(const par_num_t par_num, void * const p_val) { - PAR_ASSERT( par_num < ePAR_NUM_OF ); + const par_cfg_t * par_cfg = NULL; + par_status_t status = ePAR_OK; - if ( NULL == p_val ) + status = par_validate_runtime(par_num, p_val, true, &par_cfg); + if ( ePAR_OK != status ) { - return ePAR_ERROR_PARAM; + return status; } - switch ( par_get_type(par_num)) + switch ( par_cfg->type ) { case ePAR_TYPE_U8: return par_get_u8(par_num, (uint8_t*) p_val); @@ -936,20 +1037,12 @@ par_status_t par_get_by_id(const uint16_t id, void * const p_val) par_status_t par_get_default(const par_num_t par_num, void * const p_val) { const par_cfg_t * par_cfg = NULL; + par_status_t status = ePAR_OK; - PAR_ASSERT( par_num < ePAR_NUM_OF ); - - if ( NULL == p_val ) - { - return ePAR_ERROR_PARAM; - } - - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - par_cfg = par_get_config(par_num); - if ( NULL == par_cfg ) + status = par_validate_metadata(par_num, p_val, true, &par_cfg); + if ( ePAR_OK != status ) { - return ePAR_ERROR; + return status; } switch ( par_cfg->type ) @@ -1023,9 +1116,9 @@ const par_cfg_t * par_get_config(const par_num_t par_num) #if ( 1 == PAR_CFG_ENABLE_NAME ) const char * par_get_name(const par_num_t par_num) { - const par_cfg_t * const par_cfg = par_get_config(par_num); + const par_cfg_t * par_cfg = NULL; - if ( NULL != par_cfg ) + if ( ePAR_OK == par_validate_metadata(par_num, NULL, false, &par_cfg)) { return par_cfg->name; } @@ -1046,9 +1139,9 @@ const char * par_get_name(const par_num_t par_num) par_range_t par_get_range(const par_num_t par_num) { par_range_t range = {0}; - const par_cfg_t * const par_cfg = par_get_config(par_num); + const par_cfg_t * par_cfg = NULL; - if ( NULL != par_cfg ) + if ( ePAR_OK == par_validate_metadata(par_num, NULL, false, &par_cfg)) { return par_cfg->range; } @@ -1068,9 +1161,9 @@ par_range_t par_get_range(const par_num_t par_num) #if ( 1 == PAR_CFG_ENABLE_UNIT ) const char * par_get_unit(const par_num_t par_num) { - const par_cfg_t * const par_cfg = par_get_config(par_num); + const par_cfg_t * par_cfg = NULL; - if ( NULL != par_cfg ) + if ( ePAR_OK == par_validate_metadata(par_num, NULL, false, &par_cfg)) { return par_cfg->unit; } @@ -1090,9 +1183,9 @@ const char * par_get_unit(const par_num_t par_num) #if ( 1 == PAR_CFG_ENABLE_DESC ) const char * par_get_desc(const par_num_t par_num) { - const par_cfg_t * const par_cfg = par_get_config(par_num); + const par_cfg_t * par_cfg = NULL; - if ( NULL != par_cfg ) + if ( ePAR_OK == par_validate_metadata(par_num, NULL, false, &par_cfg)) { return par_cfg->desc; } @@ -1111,9 +1204,9 @@ const char * par_get_desc(const par_num_t par_num) //////////////////////////////////////////////////////////////////////////////// par_type_list_t par_get_type(const par_num_t par_num) { - const par_cfg_t * const par_cfg = par_get_config(par_num); + const par_cfg_t * par_cfg = NULL; - if ( NULL != par_cfg ) + if ( ePAR_OK == par_validate_metadata(par_num, NULL, false, &par_cfg)) { return par_cfg->type; } @@ -1132,9 +1225,9 @@ par_type_list_t par_get_type(const par_num_t par_num) #if ( 1 == PAR_CFG_ENABLE_ACCESS ) par_access_t par_get_access(const par_num_t par_num) { - const par_cfg_t * const par_cfg = par_get_config(par_num); + const par_cfg_t * par_cfg = NULL; - if ( NULL != par_cfg ) + if ( ePAR_OK == par_validate_metadata(par_num, NULL, false, &par_cfg)) { return par_cfg->access; } @@ -1145,20 +1238,20 @@ par_access_t par_get_access(const par_num_t par_num) //////////////////////////////////////////////////////////////////////////////// /** -* Is parameter persistant (does it stores to NVM) +* Is parameter persistent (does it store to NVM) * * @param[in] par_num - Parameter number (enumeration) -* @return True if parameter persistant +* @return True if parameter is persistent */ //////////////////////////////////////////////////////////////////////////////// #if ( 1 == PAR_CFG_ENABLE_PERSIST ) -bool par_is_persistant(const par_num_t par_num) +bool par_is_persistent(const par_num_t par_num) { - const par_cfg_t * const par_cfg = par_get_config(par_num); + const par_cfg_t * par_cfg = NULL; - if ( NULL != par_cfg ) + if ( ePAR_OK == par_validate_metadata(par_num, NULL, false, &par_cfg)) { - return par_cfg->persistant; + return par_cfg->persistent; } return false; @@ -1169,6 +1262,10 @@ bool par_is_persistant(const par_num_t par_num) /** * Get parameter number (enumeration) by ID * +* @note This API reads the compile-time static ID map only. +* It does not require par_init(), because it does not access +* runtime parameter storage. +* * @param[in] id - Parameter ID * @param[out] p_par_num - Pointer to parameter enumeration number * @return status - Status of operation @@ -1182,11 +1279,6 @@ par_status_t par_get_num_by_id(const uint16_t id, par_num_t * const p_par_num) return ePAR_ERROR_PARAM; } - if ( true != par_is_init() ) - { - return ePAR_ERROR_INIT; - } - { const uint32_t bucket_idx = par_hash_id( id ); const par_id_map_entry_t * const bucket = &g_par_id_map_static[bucket_idx]; @@ -1253,19 +1345,21 @@ par_status_t par_get_id_by_num(const par_num_t par_num, uint16_t * const p_id) //////////////////////////////////////////////////////////////////////////////// static par_status_t par_is_value_changed(const par_num_t par_num, const void * p_val, bool * const p_value_changed) { - PAR_ASSERT( par_num < ePAR_NUM_OF ); + const par_cfg_t * par_cfg = NULL; + par_status_t status = ePAR_OK; if (( NULL == p_val ) || ( NULL == p_value_changed )) { return ePAR_ERROR_PARAM; } - if ( true != par_is_init() ) + status = par_validate_runtime(par_num, NULL, false, &par_cfg); + if ( ePAR_OK != status ) { - return ePAR_ERROR_INIT; + return status; } - switch ( par_get_type(par_num)) + switch ( par_cfg->type ) { case ePAR_TYPE_U8: { @@ -1327,7 +1421,7 @@ par_status_t par_get_id_by_num(const par_num_t par_num, uint16_t * const p_id) float32_t cur = 0.0f; const par_status_t status = par_get_f32(par_num, &cur); if ( ePAR_OK != status ) return status; - *p_value_changed = (cur != *(const float32_t*)p_val); + *p_value_changed = !par_f32_bits_equal(cur, *(const float32_t*)p_val); break; } #endif diff --git a/src/par.h b/src/par.h index af68333..964c9be 100644 --- a/src/par.h +++ b/src/par.h @@ -65,7 +65,7 @@ enum ePAR_STATUS_WAR_MASK = 0xFE00U, ePAR_WAR_SET_TO_DEF = 0x0200U, /** set PAR_CFG_NVM_EN to 0 */ + ePAR_WAR_NO_PERSISTENT = 0x0800U, /** set PAR_CFG_NVM_EN to 0 */ ePAR_WAR_LIMITED = 0x1000U, /**persistant ) + if ( true == par_get_config(par_num)->persistent ) { // Check if already in LUT if ( false == par_nvm_is_in_nvm_lut( obj_data.id )) @@ -570,11 +570,11 @@ { const par_cfg_t * const par_cfg = par_get_config(par_num); - if ( true == par_cfg->persistant ) + if ( true == par_cfg->persistent ) { if ( false == par_nvm_is_in_nvm_lut( par_cfg->id )) { - // Is persistant and not jet in NVM lut -> Add to LUT + // Is persistent and not jet in NVM lut -> Add to LUT g_par_nvm_data_obj_addr[per_par_nb].id = par_cfg->id; g_par_nvm_data_obj_addr[per_par_nb].addr = obj_addr + ( 8U * ( new_par_cnt + 1U )); g_par_nvm_data_obj_addr[per_par_nb].valid = true; @@ -620,7 +620,7 @@ for ( par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) { - if ( true == par_get_config(par_num)->persistant ) + if ( true == par_get_config(par_num)->persistent ) { num_of_per_par++; } @@ -645,7 +645,7 @@ { const par_cfg_t * const par_cfg = par_get_config( par_num ); - if ( true == par_cfg->persistant ) + if ( true == par_cfg->persistent ) { // First parameter if ( 0 == per_par_nb ) @@ -874,7 +874,7 @@ // No persistent parameters else { - status |= ePAR_WAR_NO_PERSISTANT; + status |= ePAR_WAR_NO_PERSISTENT; PAR_DBG_PRINT( "PAR_NVM: No persistent parameters... Nothing to do..." ); } } @@ -941,7 +941,7 @@ const par_cfg_t * const par_cfg = par_get_config( par_num ); // Is that parameter persistent - if ( true == par_cfg->persistant ) + if ( true == par_cfg->persistent ) { // Get current par value par_get( par_num, (uint32_t*) &obj_data.data ); @@ -1010,8 +1010,8 @@ // Got thru all parameters for ( par_num_t par_num = 0U; par_num < ePAR_NUM_OF; par_num++ ) { - // Store only persistant one - if ( true == par_is_persistant( par_num )) + // Store only persistent one + if ( true == par_is_persistent( par_num )) { // Sync will be done later status |= par_nvm_write( par_num, false ); diff --git a/src/par_typed_impl.inc b/src/par_typed_impl.inc index a01052c..6e69c52 100644 --- a/src/par_typed_impl.inc +++ b/src/par_typed_impl.inc @@ -1,4 +1,23 @@ -/* Private include fragment. Included only by par.c. */ +/* + * Private include fragment. Included only by par.c. + * + * This file generates the typed public APIs declared in par.h. + * + * Generated setter family: + * par_set_u8/i8/u16/i16/u32/i32(/f32) + * + * Generated fast-setter family: + * par_set_u8_fast/i8_fast/u16_fast/i16_fast/u32_fast/i32_fast(/f32_fast) + * + * Generated getter family: + * par_get_u8/i8/u16/i16/u32/i32(/f32) + * + * Expansion mapping: + * PAR_TYPED_ROWS(...) -> selects enabled scalar types + * PAR_TYPED_IMPL_SETTER -> normal setters + * PAR_TYPED_IMPL_FAST_SETTER -> fast setters + * PAR_TYPED_IMPL_GETTER -> getters + */ #define PAR_TYPED_ROWS_BASE(OP) \ OP(u8, U8, uint8_t, ePAR_TYPE_U8, u8) \ @@ -18,18 +37,26 @@ PAR_TYPED_ROWS_BASE(OP) \ PAR_TYPED_ROWS_F32(OP) +#define PAR_TYPED_FIELD_CHANGED_u8(new_val, old_val) ((new_val).u8 != (old_val).u8) +#define PAR_TYPED_FIELD_CHANGED_i8(new_val, old_val) ((new_val).i8 != (old_val).i8) +#define PAR_TYPED_FIELD_CHANGED_u16(new_val, old_val) ((new_val).u16 != (old_val).u16) +#define PAR_TYPED_FIELD_CHANGED_i16(new_val, old_val) ((new_val).i16 != (old_val).i16) +#define PAR_TYPED_FIELD_CHANGED_u32(new_val, old_val) ((new_val).u32 != (old_val).u32) +#define PAR_TYPED_FIELD_CHANGED_i32(new_val, old_val) ((new_val).i32 != (old_val).i32) +#define PAR_TYPED_FIELD_CHANGED_f32(new_val, old_val) (!par_f32_bits_equal((new_val).f32, (old_val).f32)) +#define PAR_TYPED_VALUE_CHANGED(FIELD, new_val, old_val) PAR_TYPED_FIELD_CHANGED_##FIELD((new_val), (old_val)) + #if (1 == PAR_CFG_ENABLE_CHANGE_CALLBACK) -#define PAR_TYPED_SETTER_OLD_VAL_DECL(PRIV, FIELD, par_num) \ - const par_type_t old_val = { .FIELD = PAR_GET_##PRIV##_PRIV(par_num) }; -#define PAR_TYPED_SETTER_ON_CHANGE(PRIV, FIELD, par_num) \ - do \ - { \ - pf_par_on_change_cb_t on_change = g_par_cb_table[(par_num)].on_change; \ - const par_type_t new_val = { .FIELD = PAR_GET_##PRIV##_PRIV((par_num)) }; \ - if ((on_change != NULL) && (new_val.FIELD != old_val.FIELD)) \ - { \ - on_change((par_num), new_val, old_val); \ - } \ +#define PAR_TYPED_SETTER_OLD_VAL_DECL(PRIV, FIELD, par_num) const par_type_t old_val = { .FIELD = PAR_GET_##PRIV##_PRIV(par_num) }; +#define PAR_TYPED_SETTER_ON_CHANGE(PRIV, FIELD, par_num) \ + do \ + { \ + pf_par_on_change_cb_t on_change = g_par_cb_table[(par_num)].on_change; \ + const par_type_t new_val = { .FIELD = PAR_GET_##PRIV##_PRIV((par_num)) }; \ + if ((NULL != on_change) && PAR_TYPED_VALUE_CHANGED(FIELD, new_val, old_val)) \ + { \ + on_change((par_num), new_val, old_val); \ + } \ } while (0) #else #define PAR_TYPED_SETTER_OLD_VAL_DECL(PRIV, FIELD, par_num) @@ -41,18 +68,18 @@ #endif #if (1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION) -#define PAR_TYPED_SETTER_VALIDATE_OR_SET(NAME, FIELD, par_num, val, status_var) \ - do \ - { \ - pf_par_validation_t validation = g_par_cb_table[(par_num)].validation; \ - if ((validation != NULL) && !validation((par_num), (par_type_t){ .FIELD = (val) })) \ - { \ - (status_var) = ePAR_ERROR_VALUE; \ - } \ - else \ - { \ - (status_var) = par_set_##NAME##_fast((par_num), (val)); \ - } \ +#define PAR_TYPED_SETTER_VALIDATE_OR_SET(NAME, FIELD, par_num, val, status_var) \ + do \ + { \ + pf_par_validation_t validation = g_par_cb_table[(par_num)].validation; \ + if ((NULL != validation) && (false == validation((par_num), (par_type_t){ .FIELD = (val) }))) \ + { \ + (status_var) = ePAR_ERROR_VALUE; \ + } \ + else \ + { \ + (status_var) = par_set_##NAME##_fast((par_num), (val)); \ + } \ } while (0) #else #define PAR_TYPED_SETTER_VALIDATE_OR_SET(NAME, FIELD, par_num, val, status_var) \ @@ -96,19 +123,15 @@ par_status_t par_set_##NAME(const par_num_t par_num, const CTYPE val) \ { \ par_status_t status = ePAR_OK; \ - \ PAR_ASSERT(true == par_is_init()); \ if (true != par_is_init()) \ return ePAR_ERROR_INIT; \ - \ PAR_ASSERT(par_num < ePAR_NUM_OF); \ if (par_num >= ePAR_NUM_OF) \ return ePAR_ERROR_PAR_NUM; \ - \ PAR_ASSERT(ETYPE == par_get_type(par_num)); \ if (ETYPE != par_get_type(par_num)) \ return ePAR_ERROR_TYPE; \ - \ if (ePAR_OK == par_acquire_mutex(par_num)) \ { \ PAR_TYPED_SETTER_OLD_VAL_DECL(PRIV, FIELD, par_num) \ @@ -120,7 +143,6 @@ { \ status = ePAR_ERROR_MUTEX; \ } \ - \ return status; \ } @@ -140,27 +162,23 @@ PAR_TYPED_ROWS(PAR_TYPED_IMPL_FAST_SETTER) #undef PAR_TYPED_IMPL_FAST_SETTER /* Generate typed getters: par_get_xxx() */ -#define PAR_TYPED_IMPL_GETTER(NAME, PRIV, CTYPE, ETYPE, FIELD) \ - par_status_t par_get_##NAME(const par_num_t par_num, CTYPE * const p_val) \ - { \ - PAR_ASSERT(true == par_is_init()); \ - if (true != par_is_init()) \ - return ePAR_ERROR_INIT; \ - \ - PAR_ASSERT(par_num < ePAR_NUM_OF); \ - if (par_num >= ePAR_NUM_OF) \ - return ePAR_ERROR_PAR_NUM; \ - \ - PAR_ASSERT(ETYPE == par_get_type(par_num)); \ - if (ETYPE != par_get_type(par_num)) \ - return ePAR_ERROR_TYPE; \ - \ - PAR_ASSERT(NULL != p_val); \ - if (NULL == p_val) \ - return ePAR_ERROR_PARAM; \ - \ - *p_val = PAR_GET_##PRIV##_PRIV(par_num); \ - return ePAR_OK; \ +#define PAR_TYPED_IMPL_GETTER(NAME, PRIV, CTYPE, ETYPE, FIELD) \ + par_status_t par_get_##NAME(const par_num_t par_num, CTYPE * const p_val) \ + { \ + PAR_ASSERT(true == par_is_init()); \ + if (true != par_is_init()) \ + return ePAR_ERROR_INIT; \ + PAR_ASSERT(par_num < ePAR_NUM_OF); \ + if (par_num >= ePAR_NUM_OF) \ + return ePAR_ERROR_PAR_NUM; \ + PAR_ASSERT(ETYPE == par_get_type(par_num)); \ + if (ETYPE != par_get_type(par_num)) \ + return ePAR_ERROR_TYPE; \ + PAR_ASSERT(NULL != p_val); \ + if (NULL == p_val) \ + return ePAR_ERROR_PARAM; \ + *p_val = PAR_GET_##PRIV##_PRIV(par_num); \ + return ePAR_OK; \ } PAR_TYPED_ROWS(PAR_TYPED_IMPL_GETTER) @@ -170,6 +188,14 @@ PAR_TYPED_ROWS(PAR_TYPED_IMPL_GETTER) #undef PAR_TYPED_SETTER_VALIDATE_OR_SET #undef PAR_TYPED_SETTER_ON_CHANGE #undef PAR_TYPED_SETTER_OLD_VAL_DECL +#undef PAR_TYPED_VALUE_CHANGED +#undef PAR_TYPED_FIELD_CHANGED_u8 +#undef PAR_TYPED_FIELD_CHANGED_i8 +#undef PAR_TYPED_FIELD_CHANGED_u16 +#undef PAR_TYPED_FIELD_CHANGED_i16 +#undef PAR_TYPED_FIELD_CHANGED_u32 +#undef PAR_TYPED_FIELD_CHANGED_i32 +#undef PAR_TYPED_FIELD_CHANGED_f32 #undef PAR_TYPED_ROWS #undef PAR_TYPED_ROWS_F32 #undef PAR_TYPED_ROWS_BASE From 9ed8e759c2a70d46c6ca24a12a3180aef4cc59e1 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Wed, 25 Mar 2026 15:14:38 +0800 Subject: [PATCH 21/36] refactor(par_if): replace source inclusion with weak port override - remove #include "par_if_port.c" from the core interface layer - provide weak default implementations in parameters/src/par_if.c - compile port/par_if_port.c as a normal translation unit - guard the RT-Thread port backend with PAR_CFG_IF_PORT_EN --- README.md | 2 +- docs/architecture.md | 2 +- docs/getting-started.md | 3 +- src/par_if.c | 81 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 84 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 61c34ea..8091156 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ This repository contains the reusable module core and templates. A real integrat ### Optional, depending on configuration -- `port/par_if_port.c` when `PAR_CFG_IF_PORT_EN = 1` +- `port/par_if_port.c` when `PAR_CFG_IF_PORT_EN = 1` and the target needs stronger platform hooks than the weak defaults in `par_if.c` - `port/par_atomic_port.h` when `PAR_ATOMIC_BACKEND = PAR_ATOMIC_BACKEND_PORT` - generated static layout header when `PAR_CFG_LAYOUT_SOURCE = PAR_CFG_LAYOUT_SCRIPT` - the external NVM module when `PAR_CFG_NVM_EN = 1` diff --git a/docs/architecture.md b/docs/architecture.md index 855e39d..a341067 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -338,7 +338,7 @@ Implemented under `src/`: Implemented by the integrator as needed: - `par_cfg_port.h` -- `par_if_port.c` +- `par_if_port.c` (optional strong override for the weak defaults in `par_if.c`) - `par_atomic_port.h` This separation makes the core reusable while still allowing the target platform to provide mutexes, logging, assertions, and atomic primitives. diff --git a/docs/getting-started.md b/docs/getting-started.md index d83fdf5..f8f8b63 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -83,7 +83,8 @@ Use `template/par_cfg_port.htmp` as the starting point. ### `port/par_if_port.c` -Provide this file only when `PAR_CFG_IF_PORT_EN = 1`. +Provide this file only when `PAR_CFG_IF_PORT_EN = 1` **and** your target needs to override the core weak defaults. +Compile it as a normal source file (do not `#include` the `.c` file from core code). Use it to integrate platform-specific services such as: diff --git a/src/par_if.c b/src/par_if.c index a5305ee..10b72be 100644 --- a/src/par_if.c +++ b/src/par_if.c @@ -29,7 +29,86 @@ #if ( 1 == PAR_CFG_IF_PORT_EN ) -#include "par_if_port.c" +//////////////////////////////////////////////////////////////////////////////// +/** +* Initialize low level interface +* +* @note Default weak implementation keeps the interface optional. +* Integrator may provide a strong definition in port/par_if_port.c. +* +* @return status - Status of initialization +*/ +//////////////////////////////////////////////////////////////////////////////// +PAR_PORT_WEAK par_status_t par_if_init(void) +{ + return ePAR_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +/** +* De-initialize low level interface +* +* @note Default weak implementation keeps the interface optional. +* Integrator may provide a strong definition in port/par_if_port.c. +* +* @return status - Status of de-initialization +*/ +//////////////////////////////////////////////////////////////////////////////// +PAR_PORT_WEAK par_status_t par_if_deinit(void) +{ + return ePAR_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +/** +* Acquire mutex for specified parameter +* +* @note Default weak implementation does not provide locking. +* Integrator may provide a strong definition in port/par_if_port.c. +* +* @param[in] par_num - Parameter number (enumeration) +* @return status - Status of operation +*/ +//////////////////////////////////////////////////////////////////////////////// +PAR_PORT_WEAK par_status_t par_if_aquire_mutex(const par_num_t par_num) +{ + (void) par_num; + return ePAR_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +/** +* Release mutex for specified parameter +* +* @note Default weak implementation does not provide locking. +* Integrator may provide a strong definition in port/par_if_port.c. +* +* @param[in] par_num - Parameter number (enumeration) +*/ +//////////////////////////////////////////////////////////////////////////////// +PAR_PORT_WEAK void par_if_release_mutex(const par_num_t par_num) +{ + (void) par_num; +} + +//////////////////////////////////////////////////////////////////////////////// +/** +* Calculate hash +* +* @note Default weak implementation leaves hash output untouched. +* Integrator may provide a strong definition in port/par_if_port.c. +* +* @param[in] p_data - Pointer to data for hash calculation +* @param[in] size - Size of data in bytes +* @param[in] p_hash - Pointer to calculated hash number +*/ +//////////////////////////////////////////////////////////////////////////////// +PAR_PORT_WEAK void par_if_calc_hash(const uint8_t * const p_data, const uint32_t size, uint8_t * const p_hash) +{ + (void) p_data; + (void) size; + (void) p_hash; +} #else From 4e7e14191a71a321f065be5b6813082bd07fcad6 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Thu, 26 Mar 2026 10:59:16 +0800 Subject: [PATCH 22/36] refactor(core): Clarify hook contract and make bitwise fast setters flags-only --- README.md | 2 +- docs/api-reference.md | 6 ++++- docs/architecture.md | 2 +- docs/getting-started.md | 15 ++++++++++-- src/par.h | 53 ++++++++++++++++++++++++++++++++++++++-- src/par_bitwise_impl.inc | 32 ++++-------------------- 6 files changed, 76 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 8091156..c73ad4f 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ This repository contains the reusable module core and templates. A real integrat - `PAR_CFG_ENABLE_RUNTIME_VALIDATION` and `PAR_CFG_ENABLE_CHANGE_CALLBACK` control whether normal setters include runtime validation callbacks and on-change callbacks. - The module separates **internal parameter enumeration** (`par_num_t`) from **external parameter IDs** (`id`). - The current ID lookup implementation uses a one-entry-per-bucket hash map generated at compile time from `par_table.def`. External IDs must therefore be not only unique, but also collision-free under the configured hash geometry. Optional runtime diagnostic scans can be enabled with `PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK` and `PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK` when additional startup logs are useful. See `docs/architecture.md` for the collision rule and avoidance guidance. -- Unchecked setter APIs skip runtime validation callbacks and on-change callbacks, so they should be reserved for tightly controlled hot paths. Legacy `*_fast()` names remain as deprecated aliases. +- Unchecked setter APIs skip runtime validation callbacks and on-change callbacks, so they should be reserved for tightly controlled hot paths. Bitwise fast setters are further restricted to `U8` / `U16` / `U32` flags or bitmask parameters. Legacy `*_fast()` names remain as deprecated aliases. - NVM support is optional, but when enabled it depends on the external NVM module and on ID and persistence metadata being enabled. - `par_init()` applies startup default values directly to live storage. Integer default values from `par_table.def` are compiled into a grouped width-based storage object, while `F32` default values are applied to the 32-bit storage group after layout offsets are available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. Because this startup initialization does not go through the public setter path, it does not invoke runtime validation or on-change callbacks. - `PAR_CFG_ENABLE_RESET_ALL_RAW` controls whether raw reset-all support and grouped default mirror snapshot support are enabled. diff --git a/docs/api-reference.md b/docs/api-reference.md index 279925a..8c940c8 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -99,6 +99,8 @@ Use these only in controlled hot paths. ## Fast bitwise update helpers +These helpers are available for unsigned integer widths and are intended only for controlled, high-frequency updates of flags/bitmask parameters. Treat them as unchecked fast paths, not as general-purpose setters for ranged numeric values. + | Function | Description | | --- | --- | | `par_bitand_set_u8_fast()` | Fast bitwise AND update for `U8`. | @@ -225,7 +227,9 @@ static void app_hooks_init(void) } ``` -When enabled, these hooks affect runtime writes and explicit reset operations that use the normal setter path. They are not invoked during the internal startup default initialization performed by `par_init()`. +When enabled, these hooks affect runtime writes and explicit reset operations that use the normal setter path. They are not invoked during the internal startup default initialization performed by `par_init()`, by raw restore/reset paths, by typed fast setters, or by bitwise fast setters. + +Keep both hook types synchronous, short, and non-blocking. Do not perform long-running I/O, waits, sleeps, or other operations that may extend parameter-module lock hold time. Re-entering the parameter module from these hooks is an advanced usage pattern and should be reviewed carefully at application level. ## Debug helpers diff --git a/docs/architecture.md b/docs/architecture.md index a341067..51e3799 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -296,7 +296,7 @@ Fast setters are specialized APIs for controlled hot paths. They reduce overhead Fast setters do not execute runtime validation callbacks or on-change callbacks. -The bitwise fast helpers are emitted through `par_bitwise_impl.inc`, another private include fragment included only by `par.c`. +The bitwise fast helpers are emitted through `par_bitwise_impl.inc`, another private include fragment included only by `par.c`. They are intentionally scoped as flags-only helpers for `U8` / `U16` / `U32` bitmask parameters and do not preserve normal setter range semantics. ### Raw reset-all path diff --git a/docs/getting-started.md b/docs/getting-started.md index f8f8b63..75d081c 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -283,7 +283,7 @@ The registration APIs work per parameter and take the parameter number directly. ### On-change callback -Use this only when `PAR_CFG_ENABLE_CHANGE_CALLBACK = 1`. +Use this only when `PAR_CFG_ENABLE_CHANGE_CALLBACK = 1`. Keep the callback synchronous, short, and non-blocking. Avoid long-running I/O, waits, sleeps, or other operations that may extend parameter-module lock hold time. ```c #if (1 == PAR_CFG_ENABLE_CHANGE_CALLBACK) @@ -305,7 +305,7 @@ static void app_register_callbacks(void) ### Validation callback -Use this only when `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 1`. +Use this only when `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 1`. Keep validation logic synchronous, short, and non-blocking. Avoid long-running I/O, waits, sleeps, or other operations that may extend parameter-module lock hold time. ```c #if (1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION) @@ -349,6 +349,17 @@ Fast setters are meant for controlled hot paths where you accept reduced safety Do not use fast setters as the default API for ordinary application code. +### Bitwise fast setters + +Bitwise fast setters are the flags-only variant of the fast path. Use them only for `U8` / `U16` / `U32` parameters that represent bitmasks or status flags. They intentionally bypass runtime validation callbacks, on-change callbacks, and normal setter range semantics. + +```c +(void)par_bitor_set_u32_fast(ePAR_STATUS_FLAGS, STATUS_FLAG_READY); +(void)par_bitand_set_u32_fast(ePAR_STATUS_FLAGS, (uint32_t)(~STATUS_FLAG_ERROR)); +``` + +Good fits are enable masks, fault flags, and mode bits. Do not use bitwise fast setters as a substitute for ordinary numeric writes such as temperature, current limits, or thresholds. + ## Persistence to NVM When NVM support is enabled, use the NVM APIs for storing current values. diff --git a/src/par.h b/src/par.h index 964c9be..e3ae433 100644 --- a/src/par.h +++ b/src/par.h @@ -160,12 +160,34 @@ typedef struct par_cfg_s } par_cfg_t; /** - * Device Parameters on change callback + * Device Parameters on change callback. + * + * @note Callback is executed from the normal setter path only. Startup default + * initialization, raw restore paths, fast setters, and bitwise fast + * setters do not invoke this hook. + * + * @note Keep callback logic synchronous, short, and non-blocking. Do not + * perform long-running I/O, waits, sleeps, or other operations that may + * extend parameter-module lock hold time. + * + * @note Re-entering the parameter module from this callback is an advanced + * usage pattern and must be reviewed carefully at application level. */ typedef void (*pf_par_on_change_cb_t)(const par_num_t par_num, const par_type_t new_val, const par_type_t old_val); /** - * Device Parameters validation + * Device Parameters validation callback. + * + * @note Validation is executed from the normal setter path only. Startup + * default initialization, raw restore paths, fast setters, and bitwise + * fast setters do not invoke this hook. + * + * @note Keep validation logic synchronous, short, and non-blocking. Do not + * perform long-running I/O, waits, sleeps, or other operations that may + * extend parameter-module lock hold time. + * + * @note Re-entering the parameter module from validation is an advanced usage + * pattern and must be reviewed carefully at application level. */ typedef bool (*pf_par_validation_t)(const par_num_t par_num, const par_type_t val); @@ -212,6 +234,20 @@ par_status_t par_set_i32_fast (const par_num_t par_num, const int32_t val) #if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) par_status_t par_set_f32_fast (const par_num_t par_num, const float32_t val); #endif +/** + * Fast bitwise setters for flags/bitmask parameters. + * + * @note These APIs are intended only for U8/U16/U32 parameters that model + * flags or bitmasks. They are not a general-purpose replacement for the + * normal setter APIs used with ranged numeric values. + * + * @note They follow the same trust model as other fast setters: callers must + * guarantee the module is initialized, par_num is valid, and the typed + * API matches the parameter type. + * + * @note They intentionally bypass runtime validation callbacks, on-change + * callbacks, and normal setter range semantics. + */ par_status_t par_bitand_set_u8_fast (const par_num_t par_num, const uint8_t val); par_status_t par_bitand_set_u16_fast(const par_num_t par_num, const uint16_t val); par_status_t par_bitand_set_u32_fast(const par_num_t par_num, const uint32_t val); @@ -314,9 +350,22 @@ par_status_t par_get_id_by_num (const par_num_t par_num, uint16_t * con // Registration API #if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) +/** + * Register per-parameter on-change callback used by the normal setter path. + * + * @note Registered callback is not used by startup default initialization, + * raw restore paths, fast setters, or bitwise fast setters. + */ void par_register_on_change_cb (const par_num_t par_num, const pf_par_on_change_cb_t cb); #endif #if ( 1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION ) +/** + * Register per-parameter runtime validation callback used by the normal + * setter path. + * + * @note Registered validation is not used by startup default initialization, + * raw restore paths, fast setters, or bitwise fast setters. + */ void par_register_validation (const par_num_t par_num, const pf_par_validation_t validation); #endif diff --git a/src/par_bitwise_impl.inc b/src/par_bitwise_impl.inc index d829850..de8a530 100644 --- a/src/par_bitwise_impl.inc +++ b/src/par_bitwise_impl.inc @@ -2,44 +2,22 @@ * Private include fragment. Included only by par.c. */ -#define PAR_FOR_EACH_BITWISE_OP(OP, NAME, CTYPE, ETYPE, FIELD, STORAGE) \ - OP(bitand, AND, NAME, CTYPE, ETYPE, FIELD, STORAGE) \ - OP(bitor, OR, NAME, CTYPE, ETYPE, FIELD, STORAGE) - -#if (1 == PAR_CFG_ENABLE_RANGE) -#define PAR_BITWISE_FAST_SET_BODY(ATOMIC_OP, NAME, FIELD, STORAGE, par_num, val) \ - do \ - { \ - const par_range_t range = par_get_range(par_num); \ - if ((val) > range.max.FIELD) \ - { \ - PAR_ATOMIC_FETCH_##ATOMIC_OP(NAME, &(STORAGE)[g_par_offset[par_num]], range.max.FIELD); \ - return ePAR_WAR_LIMITED; \ - } \ - else if ((val) < range.min.FIELD) \ - { \ - PAR_ATOMIC_FETCH_##ATOMIC_OP(NAME, &(STORAGE)[g_par_offset[par_num]], range.min.FIELD); \ - return ePAR_WAR_LIMITED; \ - } \ - else \ - { \ - PAR_ATOMIC_FETCH_##ATOMIC_OP(NAME, &(STORAGE)[g_par_offset[par_num]], (val)); \ - return ePAR_OK; \ - } \ - } while (0) -#else + +#define PAR_FOR_EACH_BITWISE_OP(M, ...) \ + M(bitand, AND, __VA_ARGS__) \ + M(bitor, OR, __VA_ARGS__) #define PAR_BITWISE_FAST_SET_BODY(ATOMIC_OP, NAME, FIELD, STORAGE, par_num, val) \ do \ { \ PAR_ATOMIC_FETCH_##ATOMIC_OP(NAME, &(STORAGE)[g_par_offset[par_num]], (val)); \ return ePAR_OK; \ } while (0) -#endif #define PAR_IMPL_BITWISE_FAST_SETTER(OP_NAME, ATOMIC_OP, NAME, CTYPE, ETYPE, FIELD, STORAGE) \ par_status_t par_##OP_NAME##_set_##NAME##_fast(const par_num_t par_num, const CTYPE val) \ { \ PAR_ASSERT(true == par_is_init()); \ + PAR_ASSERT(par_num < ePAR_NUM_OF); \ PAR_ASSERT(ETYPE == par_get_type(par_num)); \ PAR_BITWISE_FAST_SET_BODY(ATOMIC_OP, NAME, FIELD, STORAGE, par_num, val); \ } From 349d9a907275eac9a5e35ea15fec2ac61a20de40 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Fri, 27 Mar 2026 08:13:49 +0800 Subject: [PATCH 23/36] refactor(core): Unify checked setter flow and fast default restore --- docs/api-reference.md | 28 +-- docs/architecture.md | 10 +- src/par.c | 173 +++++++++++------- src/par.h | 34 ++-- src/par_bitwise_impl.inc | 4 +- src/par_nvm.c | 43 ++++- src/par_typed_impl.inc | 379 ++++++++++++++++++++++++++------------- 7 files changed, 450 insertions(+), 221 deletions(-) diff --git a/docs/api-reference.md b/docs/api-reference.md index 8c940c8..bbabb6f 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -33,6 +33,12 @@ The module conditionally compiles parts of the API based on configuration. - `PAR_CFG_ENABLE_RESET_ALL_RAW = 1` enables: - `par_reset_all_to_default_raw()` + +## Status notes + +- `ePAR_ERROR_ACCESS` indicates that a checked public setter rejected a write to an `ePAR_ACCESS_RO` parameter. +- Warning bit values were shifted to make room for the new access-denied error bit. + ## Lifecycle | Function | Description | @@ -54,8 +60,8 @@ These are relevant only when mutex support is enabled in the integration. | Function | Description | | --- | --- | -| `par_set(par_num, p_val)` | Set a parameter from a typed pointer. | -| `par_set_by_id(id, p_val)` | Set a parameter using its external ID. | +| `par_set(par_num, p_val)` | Set a parameter from a typed pointer. This public setter path enforces access policy and returns `ePAR_ERROR_ACCESS` when the target parameter is externally read-only. | +| `par_set_by_id(id, p_val)` | Set a parameter using its external ID. This path resolves the ID to `par_num_t` and then uses the same checked setter flow as `par_set()`. | ## Typed setter macro wrappers @@ -81,7 +87,7 @@ These are relevant only when mutex support is enabled in the integration. | `par_set_i32()` | Set an `I32` parameter. | | `par_set_f32()` | Set an `F32` parameter. Available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. | -Normal typed setters may include runtime validation callbacks and on-change callbacks as part of the normal runtime path. Those hook paths are present only when the matching configuration options are enabled. +Normal typed setters are the canonical checked setter path. They enforce access policy and may include runtime validation callbacks and on-change callbacks when the matching configuration options are enabled. ## Fast setters @@ -95,7 +101,7 @@ Normal typed setters may include runtime validation callbacks and on-change call | `par_set_i32_fast()` | Fast set for `I32`. | | `par_set_f32_fast()` | Fast set for `F32`. Available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. | -Use these only in controlled hot paths. +Use these only in controlled hot paths. Fast setters intentionally bypass access enforcement, runtime validation callbacks, and on-change callbacks. ## Fast bitwise update helpers @@ -114,22 +120,22 @@ These helpers are available for unsigned integer widths and are intended only fo | Function | Description | | --- | --- | -| `par_set_to_default(par_num)` | Reset one parameter to its configured default value through the normal runtime setter path. | -| `par_set_all_to_default()` | Reset all parameters to their default values. When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`, this public API forwards to the raw grouped-storage reset path for speed. Otherwise it iterates through the normal runtime setter path and aggregates per-parameter status bits. | +| `par_set_to_default(par_num)` | Reset one parameter to its configured default value through the internal fast typed setter path. It bypasses access enforcement, validation callbacks, and on-change callbacks. | +| `par_set_all_to_default()` | Reset all parameters to their default values. When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`, this public API forwards to the raw grouped-storage reset path for speed. Otherwise it iterates through `par_set_to_default()` and aggregates per-parameter status bits. | | `par_reset_all_to_default_raw()` | Restore all live values from a grouped default mirror snapshot via raw memory copy. The internal storage model still uses `U8/U16/U32` width groups. Available only when `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`. | | `par_has_changed(par_num, p_has_changed)` | Report whether the value differs from its default. | -`par_set_to_default()` always uses the normal runtime setter path. +`par_set_to_default()` always uses the internal fast typed setter path. -`par_set_all_to_default()` is configuration-dependent. When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`, it forwards to `par_reset_all_to_default_raw()` for the fastest bulk restore path. When the raw-reset option is disabled, it iterates through parameters and uses the normal runtime setter path. +`par_set_all_to_default()` is configuration-dependent. When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`, it forwards to `par_reset_all_to_default_raw()` for the fastest bulk restore path. When the raw-reset option is disabled, it iterates through parameters and uses the same fast default-restore path as `par_set_to_default()`. Use `par_reset_all_to_default_raw()` when you want to call the raw grouped-storage restore path explicitly. These reset APIs are different from startup initialization: - `par_init()` applies startup defaults internally to live storage -- `par_set_to_default()` uses runtime setter semantics for one parameter -- `par_set_all_to_default()` uses raw restore semantics when raw reset is enabled, otherwise it uses runtime setter semantics +- `par_set_to_default()` uses fast default-restore semantics for one parameter +- `par_set_all_to_default()` uses raw restore semantics when raw reset is enabled, otherwise it uses the same fast default-restore semantics as `par_set_to_default()` - `par_reset_all_to_default_raw()` always bypasses per-parameter runtime setter semantics ## Pointer-based getters @@ -166,7 +172,7 @@ These APIs do not follow the same runtime usage pattern as the value access APIs | `par_get_unit(par_num)` | Return the engineering unit when unit metadata is enabled. | | `par_get_desc(par_num)` | Return the description string when description metadata is enabled. | | `par_get_type(par_num)` | Return the parameter type enum. | -| `par_get_access(par_num)` | Return read-only or read-write access metadata when enabled. | +| `par_get_access(par_num)` | Return read-only or read-write access metadata when enabled. Public checked setter APIs consume this metadata to enforce write access. | | `par_is_persistent(par_num)` | Return whether the parameter is marked persistent when enabled. | | `par_get_num_by_id(id, p_par_num)` | Convert an external ID to `par_num_t` through the compile-time generated static ID map. This metadata API does not require `par_init()`. | | `par_get_id_by_num(par_num, p_id)` | Convert `par_num_t` to external ID. | diff --git a/docs/architecture.md b/docs/architecture.md index 51e3799..90aed14 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -199,7 +199,7 @@ This split keeps integer configuration errors out of the firmware image while st ## ID lookup path -The module supports ID-based APIs such as `par_get_by_id()` and `par_set_by_id()`. +The module supports ID-based APIs such as `par_get_by_id()` and `par_set_by_id()`. All public checked write APIs (`par_set()`, `par_set_by_id()`, and typed setters) now converge on the same checked setter core so access enforcement, runtime validation, and on-change semantics remain consistent across entry points. Because external IDs do not need to be sequential, the build generates a static hash map from `par_table.def`. `par_init()` does not build the ID map at runtime. @@ -208,7 +208,7 @@ flowchart LR A[External ID] --> B[Hash function] B --> C[Bucket lookup] C --> D[par_num_t] - D --> E[Internal parameter access] + D --> E[Checked public setter path] ``` ### Collision policy @@ -223,6 +223,12 @@ That means: This keeps runtime lookup simple and deterministic, but it also means a conflicting ID assignment must be fixed at the source. +### Access enforcement boundary + +`par_get_access()` remains metadata, but the metadata is now consumed by the checked setter core. Public write entry points enforce `ePAR_ACCESS_RW` and reject writes to `ePAR_ACCESS_RO` with `ePAR_ERROR_ACCESS`. + +Fast setters and internal restore paths intentionally remain outside that checked boundary. They are used for startup/default/NVM restore flows where the firmware must rehydrate trusted values without invoking public access-control policy. + ### Hash geometry and collision rule The current ID lookup implementation uses a strict one-entry-per-bucket hash map. diff --git a/src/par.c b/src/par.c index 9e6f48e..3229ef8 100644 --- a/src/par.c +++ b/src/par.c @@ -167,13 +167,13 @@ static par_atomic_f32_t * const gpf32_par_value = (par_atomic_f32_t *)gs_par_sto "ERROR VALUE", "ERROR PARAM", "ERROR PAR NUM", + "ERROR ACCESS", "WARN SET TO DEF", "WARN NVM REWRITTEN", "NO PERSISTENT", "LIMITED", "N/A", "N/A", - "N/A", }; #endif @@ -186,8 +186,9 @@ static inline uint32_t par_hash_id (const uint16_t id); static par_status_t par_runtime_validate_id_table (const par_cfg_t * const p_par_cfg); #endif #endif +static par_status_t par_set_checked_core (const par_num_t par_num, const par_type_list_t expected_type, const par_type_t * const p_typed_val, const void * const p_ptr_val); #if ( 1 == PAR_CFG_NVM_EN ) -static par_status_t par_is_value_changed (const par_num_t par_num, const void * p_val, bool * const p_value_changed); +static par_status_t par_is_value_changed (const par_num_t par_num, const void * p_val, bool * const p_value_changed); #endif /* ( 1 == PAR_CFG_NVM_EN ) */ //////////////////////////////////////////////////////////////////////////////// // Functions @@ -213,11 +214,12 @@ PAR_PORT_WEAK bool par_port_is_desc_valid(const char * const p_desc) //////////////////////////////////////////////////////////////////////////////// /** -* Validate metadata access for parameter identified by number +* Resolve metadata entry for parameter identified by number * -* @note Metadata access only checks parameter number and table entry. -* It intentionally does not require the runtime module to be -* initialized, because callers only read compile-time metadata. +* @note Metadata resolution only checks parameter number and table +* entry. It intentionally does not require the runtime module +* to be initialized, because callers only read compile-time +* metadata. * * @param[in] par_num - Parameter number (enumeration) * @param[in] p_arg - Optional pointer argument to validate @@ -226,7 +228,7 @@ PAR_PORT_WEAK bool par_port_is_desc_valid(const char * const p_desc) * @return status - Status of operation */ //////////////////////////////////////////////////////////////////////////////// -static par_status_t par_validate_metadata(const par_num_t par_num, const void * const p_arg, const bool require_arg, const par_cfg_t ** const pp_cfg) +static par_status_t par_resolve_metadata(const par_num_t par_num, const void * const p_arg, const bool require_arg, const par_cfg_t ** const pp_cfg) { const par_cfg_t * p_cfg = NULL; @@ -257,11 +259,11 @@ static par_status_t par_validate_metadata(const par_num_t par_num, const void * //////////////////////////////////////////////////////////////////////////////// /** -* Validate runtime access for parameter identified by number +* Resolve runtime metadata entry for parameter identified by number * -* @note Runtime access extends metadata validation with module init -* state validation, because live parameter storage is only valid -* after successful par_init(). +* @note Runtime resolution extends metadata resolution with module +* init state validation, because live parameter storage is only +* valid after successful par_init(). * * @param[in] par_num - Parameter number (enumeration) * @param[in] p_arg - Optional pointer argument to validate @@ -270,14 +272,43 @@ static par_status_t par_validate_metadata(const par_num_t par_num, const void * * @return status - Status of operation */ //////////////////////////////////////////////////////////////////////////////// -static par_status_t par_validate_runtime(const par_num_t par_num, const void * const p_arg, const bool require_arg, const par_cfg_t ** const pp_cfg) +static par_status_t par_resolve_runtime(const par_num_t par_num, const void * const p_arg, const bool require_arg, const par_cfg_t ** const pp_cfg) { if ( true != par_is_init() ) { return ePAR_ERROR_INIT; } - return par_validate_metadata( par_num, p_arg, require_arg, pp_cfg ); + return par_resolve_metadata( par_num, p_arg, require_arg, pp_cfg ); +} + +//////////////////////////////////////////////////////////////////////////////// +/** +* Validate expected parameter type against resolved metadata +* +* @note Callers shall resolve metadata first and then validate the +* expected parameter type against the resolved configuration. +* +* @param[in] p_cfg - Resolved parameter configuration entry +* @param[in] expected_type - Expected parameter type for operation +* @return status - Status of operation +*/ +//////////////////////////////////////////////////////////////////////////////// +static par_status_t par_validate_expected_type(const par_cfg_t * const p_cfg, + const par_type_list_t expected_type) +{ + if ( NULL == p_cfg ) + { + return ePAR_ERROR; + } + + PAR_ASSERT( expected_type == p_cfg->type ); + if ( expected_type != p_cfg->type ) + { + return ePAR_ERROR_TYPE; + } + + return ePAR_OK; } //////////////////////////////////////////////////////////////////////////////// @@ -654,54 +685,15 @@ void par_release_mutex(const par_num_t par_num) //////////////////////////////////////////////////////////////////////////////// par_status_t par_set(const par_num_t par_num, const void * p_val) { - par_status_t status = ePAR_OK; const par_cfg_t * par_cfg = NULL; + const par_status_t status = par_resolve_runtime( par_num, p_val, true, &par_cfg ); - status = par_validate_runtime(par_num, p_val, true, &par_cfg); if ( ePAR_OK != status ) { return status; } - switch ( par_cfg->type ) - { - case ePAR_TYPE_U8: - status = par_set_u8( par_num, *(const uint8_t*) p_val ); - break; - - case ePAR_TYPE_I8: - status = par_set_i8( par_num, *(const int8_t*) p_val ); - break; - - case ePAR_TYPE_U16: - status = par_set_u16( par_num, *(const uint16_t*) p_val ); - break; - - case ePAR_TYPE_I16: - status = par_set_i16( par_num, *(const int16_t*) p_val ); - break; - - case ePAR_TYPE_U32: - status = par_set_u32( par_num, *(const uint32_t*) p_val ); - break; - - case ePAR_TYPE_I32: - status = par_set_i32( par_num, *(const int32_t*) p_val ); - break; - -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) - case ePAR_TYPE_F32: - status = par_set_f32( par_num, *(const float32_t*) p_val ); - break; -#endif - - case ePAR_TYPE_NUM_OF: - default: - PAR_ASSERT( 0 ); - return ePAR_ERROR_TYPE; - } - - return status; + return par_set_checked_core( par_num, par_cfg->type, NULL, p_val ); } //////////////////////////////////////////////////////////////////////////////// @@ -758,14 +750,59 @@ par_status_t par_set_by_id(const uint16_t id, const void * p_val) par_status_t par_set_to_default(const par_num_t par_num) { const par_cfg_t * par_cfg = NULL; - par_status_t status = par_validate_runtime(par_num, NULL, false, &par_cfg); + par_status_t status = par_resolve_runtime(par_num, NULL, false, &par_cfg); if ( ePAR_OK != status ) { return status; } - return par_set(par_num, &par_cfg->def); + if ( ePAR_OK != par_acquire_mutex( par_num )) + { + return ePAR_ERROR_MUTEX; + } + + switch ( par_cfg->type ) + { + case ePAR_TYPE_U8: + status = par_set_u8_fast( par_num, par_cfg->def.u8 ); + break; + + case ePAR_TYPE_I8: + status = par_set_i8_fast( par_num, par_cfg->def.i8 ); + break; + + case ePAR_TYPE_U16: + status = par_set_u16_fast( par_num, par_cfg->def.u16 ); + break; + + case ePAR_TYPE_I16: + status = par_set_i16_fast( par_num, par_cfg->def.i16 ); + break; + + case ePAR_TYPE_U32: + status = par_set_u32_fast( par_num, par_cfg->def.u32 ); + break; + + case ePAR_TYPE_I32: + status = par_set_i32_fast( par_num, par_cfg->def.i32 ); + break; + +#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) + case ePAR_TYPE_F32: + status = par_set_f32_fast( par_num, par_cfg->def.f32 ); + break; +#endif + + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT( 0 ); + status = ePAR_ERROR_TYPE; + break; + } + + par_release_mutex( par_num ); + return status; } #if ( 1 == PAR_CFG_ENABLE_RESET_ALL_RAW ) @@ -816,7 +853,7 @@ par_status_t par_reset_all_to_default_raw(void) * @note When PAR_CFG_ENABLE_RESET_ALL_RAW = 1, this public API forwards * to par_reset_all_to_default_raw() for maximum reset speed. * @note Otherwise it iterates through parameters and resets them via -* par_set_to_default(), preserving normal runtime setter semantics. +* par_set_to_default(), preserving fast default-restore semantics. * * @return status - Status of operation */ @@ -857,7 +894,7 @@ par_status_t par_has_changed(const par_num_t par_num, bool *const p_has_changed) const par_cfg_t * par_cfg = NULL; par_status_t status = ePAR_OK; - status = par_validate_runtime(par_num, p_has_changed, true, &par_cfg); + status = par_resolve_runtime(par_num, p_has_changed, true, &par_cfg); if ( ePAR_OK != status ) { return status; @@ -963,7 +1000,7 @@ par_status_t par_get(const par_num_t par_num, void * const p_val) const par_cfg_t * par_cfg = NULL; par_status_t status = ePAR_OK; - status = par_validate_runtime(par_num, p_val, true, &par_cfg); + status = par_resolve_runtime(par_num, p_val, true, &par_cfg); if ( ePAR_OK != status ) { return status; @@ -1039,7 +1076,7 @@ par_status_t par_get_default(const par_num_t par_num, void * const p_val) const par_cfg_t * par_cfg = NULL; par_status_t status = ePAR_OK; - status = par_validate_metadata(par_num, p_val, true, &par_cfg); + status = par_resolve_metadata(par_num, p_val, true, &par_cfg); if ( ePAR_OK != status ) { return status; @@ -1118,7 +1155,7 @@ const char * par_get_name(const par_num_t par_num) { const par_cfg_t * par_cfg = NULL; - if ( ePAR_OK == par_validate_metadata(par_num, NULL, false, &par_cfg)) + if ( ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) { return par_cfg->name; } @@ -1141,7 +1178,7 @@ par_range_t par_get_range(const par_num_t par_num) par_range_t range = {0}; const par_cfg_t * par_cfg = NULL; - if ( ePAR_OK == par_validate_metadata(par_num, NULL, false, &par_cfg)) + if ( ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) { return par_cfg->range; } @@ -1163,7 +1200,7 @@ const char * par_get_unit(const par_num_t par_num) { const par_cfg_t * par_cfg = NULL; - if ( ePAR_OK == par_validate_metadata(par_num, NULL, false, &par_cfg)) + if ( ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) { return par_cfg->unit; } @@ -1185,7 +1222,7 @@ const char * par_get_desc(const par_num_t par_num) { const par_cfg_t * par_cfg = NULL; - if ( ePAR_OK == par_validate_metadata(par_num, NULL, false, &par_cfg)) + if ( ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) { return par_cfg->desc; } @@ -1206,7 +1243,7 @@ par_type_list_t par_get_type(const par_num_t par_num) { const par_cfg_t * par_cfg = NULL; - if ( ePAR_OK == par_validate_metadata(par_num, NULL, false, &par_cfg)) + if ( ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) { return par_cfg->type; } @@ -1227,7 +1264,7 @@ par_access_t par_get_access(const par_num_t par_num) { const par_cfg_t * par_cfg = NULL; - if ( ePAR_OK == par_validate_metadata(par_num, NULL, false, &par_cfg)) + if ( ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) { return par_cfg->access; } @@ -1249,7 +1286,7 @@ bool par_is_persistent(const par_num_t par_num) { const par_cfg_t * par_cfg = NULL; - if ( ePAR_OK == par_validate_metadata(par_num, NULL, false, &par_cfg)) + if ( ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) { return par_cfg->persistent; } @@ -1353,7 +1390,7 @@ par_status_t par_get_id_by_num(const par_num_t par_num, uint16_t * const p_id) return ePAR_ERROR_PARAM; } - status = par_validate_runtime(par_num, NULL, false, &par_cfg); + status = par_resolve_runtime(par_num, NULL, false, &par_cfg); if ( ePAR_OK != status ) { return status; diff --git a/src/par.h b/src/par.h index e3ae433..d80b345 100644 --- a/src/par.h +++ b/src/par.h @@ -50,7 +50,7 @@ enum ePAR_OK = 0U, /** set PAR_CFG_NVM_EN to 0 */ - ePAR_WAR_LIMITED = 0x1000U, /** set PAR_CFG_NVM_EN to 0 */ + ePAR_WAR_LIMITED = 0x2000U, /** selects enabled scalar types - * PAR_TYPED_IMPL_SETTER -> normal setters - * PAR_TYPED_IMPL_FAST_SETTER -> fast setters - * PAR_TYPED_IMPL_GETTER -> getters */ -#define PAR_TYPED_ROWS_BASE(OP) \ - OP(u8, U8, uint8_t, ePAR_TYPE_U8, u8) \ - OP(i8, I8, int8_t, ePAR_TYPE_I8, i8) \ - OP(u16, U16, uint16_t, ePAR_TYPE_U16, u16) \ - OP(i16, I16, int16_t, ePAR_TYPE_I16, i16) \ - OP(u32, U32, uint32_t, ePAR_TYPE_U32, u32) \ - OP(i32, I32, int32_t, ePAR_TYPE_I32, i32) +#define PAR_TYPED_ROWS_BASE(X) \ + X(u8, U8, uint8_t, ePAR_TYPE_U8, u8) \ + X(i8, I8, int8_t, ePAR_TYPE_I8, i8) \ + X(u16, U16, uint16_t, ePAR_TYPE_U16, u16) \ + X(i16, I16, int16_t, ePAR_TYPE_I16, i16) \ + X(u32, U32, uint32_t, ePAR_TYPE_U32, u32) \ + X(i32, I32, int32_t, ePAR_TYPE_I32, i32) #if (1 == PAR_CFG_ENABLE_TYPE_F32) -#define PAR_TYPED_ROWS_F32(OP) OP(f32, F32, float32_t, ePAR_TYPE_F32, f32) +#define PAR_TYPED_ROWS_F32(X) \ + X(f32, F32, float32_t, ePAR_TYPE_F32, f32) #else -#define PAR_TYPED_ROWS_F32(OP) +#define PAR_TYPED_ROWS_F32(X) #endif -#define PAR_TYPED_ROWS(OP) \ - PAR_TYPED_ROWS_BASE(OP) \ - PAR_TYPED_ROWS_F32(OP) - -#define PAR_TYPED_FIELD_CHANGED_u8(new_val, old_val) ((new_val).u8 != (old_val).u8) -#define PAR_TYPED_FIELD_CHANGED_i8(new_val, old_val) ((new_val).i8 != (old_val).i8) -#define PAR_TYPED_FIELD_CHANGED_u16(new_val, old_val) ((new_val).u16 != (old_val).u16) -#define PAR_TYPED_FIELD_CHANGED_i16(new_val, old_val) ((new_val).i16 != (old_val).i16) -#define PAR_TYPED_FIELD_CHANGED_u32(new_val, old_val) ((new_val).u32 != (old_val).u32) -#define PAR_TYPED_FIELD_CHANGED_i32(new_val, old_val) ((new_val).i32 != (old_val).i32) -#define PAR_TYPED_FIELD_CHANGED_f32(new_val, old_val) (!par_f32_bits_equal((new_val).f32, (old_val).f32)) -#define PAR_TYPED_VALUE_CHANGED(FIELD, new_val, old_val) PAR_TYPED_FIELD_CHANGED_##FIELD((new_val), (old_val)) - -#if (1 == PAR_CFG_ENABLE_CHANGE_CALLBACK) -#define PAR_TYPED_SETTER_OLD_VAL_DECL(PRIV, FIELD, par_num) const par_type_t old_val = { .FIELD = PAR_GET_##PRIV##_PRIV(par_num) }; -#define PAR_TYPED_SETTER_ON_CHANGE(PRIV, FIELD, par_num) \ - do \ - { \ - pf_par_on_change_cb_t on_change = g_par_cb_table[(par_num)].on_change; \ - const par_type_t new_val = { .FIELD = PAR_GET_##PRIV##_PRIV((par_num)) }; \ - if ((NULL != on_change) && PAR_TYPED_VALUE_CHANGED(FIELD, new_val, old_val)) \ - { \ - on_change((par_num), new_val, old_val); \ - } \ - } while (0) -#else -#define PAR_TYPED_SETTER_OLD_VAL_DECL(PRIV, FIELD, par_num) -#define PAR_TYPED_SETTER_ON_CHANGE(PRIV, FIELD, par_num) \ - do \ - { \ - (void)(par_num); \ - } while (0) -#endif - -#if (1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION) -#define PAR_TYPED_SETTER_VALIDATE_OR_SET(NAME, FIELD, par_num, val, status_var) \ - do \ - { \ - pf_par_validation_t validation = g_par_cb_table[(par_num)].validation; \ - if ((NULL != validation) && (false == validation((par_num), (par_type_t){ .FIELD = (val) }))) \ - { \ - (status_var) = ePAR_ERROR_VALUE; \ - } \ - else \ - { \ - (status_var) = par_set_##NAME##_fast((par_num), (val)); \ - } \ - } while (0) -#else -#define PAR_TYPED_SETTER_VALIDATE_OR_SET(NAME, FIELD, par_num, val, status_var) \ - do \ - { \ - (status_var) = par_set_##NAME##_fast((par_num), (val)); \ - } while (0) -#endif +#define PAR_TYPED_ROWS(X) \ + PAR_TYPED_ROWS_BASE(X) \ + PAR_TYPED_ROWS_F32(X) #if (1 == PAR_CFG_ENABLE_RANGE) #define PAR_TYPED_FAST_SET_BODY(PRIV, FIELD, par_num, val) \ @@ -119,31 +59,224 @@ } while (0) #endif +#define PAR_CHECKED_VALUE_CHANGED_u8(new_val, old_val) ((new_val).u8 != (old_val).u8) +#define PAR_CHECKED_VALUE_CHANGED_i8(new_val, old_val) ((new_val).i8 != (old_val).i8) +#define PAR_CHECKED_VALUE_CHANGED_u16(new_val, old_val) ((new_val).u16 != (old_val).u16) +#define PAR_CHECKED_VALUE_CHANGED_i16(new_val, old_val) ((new_val).i16 != (old_val).i16) +#define PAR_CHECKED_VALUE_CHANGED_u32(new_val, old_val) ((new_val).u32 != (old_val).u32) +#define PAR_CHECKED_VALUE_CHANGED_i32(new_val, old_val) ((new_val).i32 != (old_val).i32) +#define PAR_CHECKED_VALUE_CHANGED_f32(new_val, old_val) (!par_f32_bits_equal((new_val).f32, (old_val).f32)) +#define PAR_CHECKED_VALUE_CHANGED(FIELD, new_val, old_val) PAR_CHECKED_VALUE_CHANGED_##FIELD((new_val), (old_val)) + +#if (1 == PAR_CFG_ENABLE_CHANGE_CALLBACK) +#define PAR_CHECKED_OLD_VAL_DECL(PRIV, FIELD, par_num) \ + const par_type_t old_val = { .FIELD = PAR_GET_##PRIV##_PRIV(par_num) }; +#define PAR_CHECKED_ON_CHANGE(PRIV, FIELD, par_num, status_var) \ + do \ + { \ + pf_par_on_change_cb_t on_change = g_par_cb_table[(par_num)].on_change; \ + if (((ePAR_OK == (status_var)) || (ePAR_WAR_LIMITED == (status_var))) && \ + (NULL != on_change)) \ + { \ + const par_type_t new_val = { .FIELD = PAR_GET_##PRIV##_PRIV(par_num) }; \ + if (PAR_CHECKED_VALUE_CHANGED(FIELD, new_val, old_val)) \ + { \ + on_change((par_num), new_val, old_val); \ + } \ + } \ + } while (0) +#else +#define PAR_CHECKED_OLD_VAL_DECL(PRIV, FIELD, par_num) +#define PAR_CHECKED_ON_CHANGE(PRIV, FIELD, par_num, status_var) \ + do \ + { \ + (void)(par_num); \ + (void)(status_var); \ + } while (0) +#endif + +#if (1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION) +#define PAR_CHECKED_VALIDATE_OR_SET(NAME, FIELD, par_num, val, status_var) \ + do \ + { \ + pf_par_validation_t validation = g_par_cb_table[(par_num)].validation; \ + if ((NULL != validation) && (false == validation((par_num), (val)))) \ + { \ + (status_var) = ePAR_ERROR_VALUE; \ + } \ + else \ + { \ + (status_var) = par_set_##NAME##_fast((par_num), (val).FIELD); \ + } \ + } while (0) +#else +#define PAR_CHECKED_VALIDATE_OR_SET(NAME, FIELD, par_num, val, status_var) \ + do \ + { \ + (status_var) = par_set_##NAME##_fast((par_num), (val).FIELD); \ + } while (0) +#endif + +#define PAR_CHECKED_LOAD_FROM_PTR_CASE(ETYPE, CTYPE, FIELD) \ + case ETYPE: \ + val.FIELD = *(const CTYPE *)p_ptr_val; \ + break; + +#define PAR_CHECKED_SET_CASE(NAME, PRIV, ETYPE, FIELD) \ + case ETYPE: \ + { \ + PAR_CHECKED_OLD_VAL_DECL(PRIV, FIELD, par_num); \ + PAR_CHECKED_VALIDATE_OR_SET(NAME, FIELD, par_num, val, status); \ + PAR_CHECKED_ON_CHANGE(PRIV, FIELD, par_num, status); \ + break; \ + } + +//////////////////////////////////////////////////////////////////////////////// +/** +* Assert fast typed setter preconditions +* +* @note Fast typed APIs are internal trusted paths. They keep assert +* based precondition checks only and intentionally avoid the +* public metadata getter path. +* +* @param[in] par_num - Parameter number (enumeration) +* @param[in] expected_type - Expected parameter type for operation +* @return void +*/ +//////////////////////////////////////////////////////////////////////////////// +static void par_assert_fast_typed_preconditions(const par_num_t par_num, + const par_type_list_t expected_type) +{ + const par_cfg_t * p_cfg = NULL; + + PAR_ASSERT(true == par_is_init()); + PAR_ASSERT(par_num < ePAR_NUM_OF); + + if ((true == par_is_init()) && (par_num < ePAR_NUM_OF)) + { + p_cfg = par_get_config(par_num); + PAR_ASSERT(NULL != p_cfg); + + if (NULL != p_cfg) + { + PAR_ASSERT(expected_type == p_cfg->type); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +/** +* Checked parameter write core for all public setter APIs +* +* @note This is the single enforcement point for all public write +* paths. It performs runtime access checks, type validation, +* access enforcement, optional runtime validation, fast setter +* dispatch, and optional on-change callback dispatch. +* +* @note Exactly one value source shall be provided: +* - p_typed_val: used by typed setters par_set_xxx() +* - p_ptr_val : used by generic par_set() +* +* @note Unchecked internal paths such as par_set_xxx_fast(), NVM +* restore helpers, and other explicit fast paths do not call +* this function. +* +* @param[in] par_num - Parameter number (enumeration) +* @param[in] expected_type - Expected parameter type for write +* @param[in] p_typed_val - Optional typed union value source +* @param[in] p_ptr_val - Optional pointer value source +* +* @return status - Status of operation +*/ +//////////////////////////////////////////////////////////////////////////////// +static par_status_t par_set_checked_core(const par_num_t par_num, + const par_type_list_t expected_type, + const par_type_t * const p_typed_val, + const void * const p_ptr_val) +{ + const par_cfg_t * par_cfg = NULL; + par_type_t val = { 0 }; + par_status_t status = par_resolve_runtime(par_num, + p_ptr_val, + (NULL == p_typed_val), + &par_cfg); + + if (ePAR_OK != status) + { + return status; + } + + status = par_validate_expected_type(par_cfg, expected_type); + if (ePAR_OK != status) + { + return status; + } + +#if (1 == PAR_CFG_ENABLE_ACCESS) + if (ePAR_ACCESS_RW != par_cfg->access) + { + PAR_DBG_PRINT("PAR: write denied for par_num=%u", (unsigned)par_num); + return ePAR_ERROR_ACCESS; + } +#endif + + if (NULL != p_typed_val) + { + val = *p_typed_val; + } + else + { + switch (expected_type) + { + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_U8, uint8_t, u8) + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_I8, int8_t, i8) + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_U16, uint16_t, u16) + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_I16, int16_t, i16) + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_U32, uint32_t, u32) + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_I32, int32_t, i32) +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_F32, float32_t, f32) +#endif + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + return ePAR_ERROR_TYPE; + } + } + + if (ePAR_OK != par_acquire_mutex(par_num)) + { + return ePAR_ERROR_MUTEX; + } + + switch (expected_type) + { + PAR_CHECKED_SET_CASE(u8, U8, ePAR_TYPE_U8, u8) + PAR_CHECKED_SET_CASE(i8, I8, ePAR_TYPE_I8, i8) + PAR_CHECKED_SET_CASE(u16, U16, ePAR_TYPE_U16, u16) + PAR_CHECKED_SET_CASE(i16, I16, ePAR_TYPE_I16, i16) + PAR_CHECKED_SET_CASE(u32, U32, ePAR_TYPE_U32, u32) + PAR_CHECKED_SET_CASE(i32, I32, ePAR_TYPE_I32, i32) +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + PAR_CHECKED_SET_CASE(f32, F32, ePAR_TYPE_F32, f32) +#endif + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + status = ePAR_ERROR_TYPE; + break; + } + + par_release_mutex(par_num); + return status; +} + +/* Generate normal typed setters: par_set_xxx() */ #define PAR_TYPED_IMPL_SETTER(NAME, PRIV, CTYPE, ETYPE, FIELD) \ par_status_t par_set_##NAME(const par_num_t par_num, const CTYPE val) \ { \ - par_status_t status = ePAR_OK; \ - PAR_ASSERT(true == par_is_init()); \ - if (true != par_is_init()) \ - return ePAR_ERROR_INIT; \ - PAR_ASSERT(par_num < ePAR_NUM_OF); \ - if (par_num >= ePAR_NUM_OF) \ - return ePAR_ERROR_PAR_NUM; \ - PAR_ASSERT(ETYPE == par_get_type(par_num)); \ - if (ETYPE != par_get_type(par_num)) \ - return ePAR_ERROR_TYPE; \ - if (ePAR_OK == par_acquire_mutex(par_num)) \ - { \ - PAR_TYPED_SETTER_OLD_VAL_DECL(PRIV, FIELD, par_num) \ - PAR_TYPED_SETTER_VALIDATE_OR_SET(NAME, FIELD, par_num, val, status); \ - PAR_TYPED_SETTER_ON_CHANGE(PRIV, FIELD, par_num); \ - par_release_mutex(par_num); \ - } \ - else \ - { \ - status = ePAR_ERROR_MUTEX; \ - } \ - return status; \ + const par_type_t typed_val = { .FIELD = val }; \ + return par_set_checked_core(par_num, ETYPE, &typed_val, NULL); \ } PAR_TYPED_ROWS(PAR_TYPED_IMPL_SETTER) @@ -152,9 +285,7 @@ PAR_TYPED_ROWS(PAR_TYPED_IMPL_SETTER) #define PAR_TYPED_IMPL_FAST_SETTER(NAME, PRIV, CTYPE, ETYPE, FIELD) \ par_status_t par_set_##NAME##_fast(const par_num_t par_num, const CTYPE val) \ { \ - PAR_ASSERT(true == par_is_init()); \ - PAR_ASSERT(par_num < ePAR_NUM_OF); \ - PAR_ASSERT(ETYPE == par_get_type(par_num)); \ + par_assert_fast_typed_preconditions(par_num, ETYPE); \ PAR_TYPED_FAST_SET_BODY(PRIV, FIELD, par_num, val); \ } @@ -165,18 +296,13 @@ PAR_TYPED_ROWS(PAR_TYPED_IMPL_FAST_SETTER) #define PAR_TYPED_IMPL_GETTER(NAME, PRIV, CTYPE, ETYPE, FIELD) \ par_status_t par_get_##NAME(const par_num_t par_num, CTYPE * const p_val) \ { \ - PAR_ASSERT(true == par_is_init()); \ - if (true != par_is_init()) \ - return ePAR_ERROR_INIT; \ - PAR_ASSERT(par_num < ePAR_NUM_OF); \ - if (par_num >= ePAR_NUM_OF) \ - return ePAR_ERROR_PAR_NUM; \ - PAR_ASSERT(ETYPE == par_get_type(par_num)); \ - if (ETYPE != par_get_type(par_num)) \ - return ePAR_ERROR_TYPE; \ - PAR_ASSERT(NULL != p_val); \ - if (NULL == p_val) \ - return ePAR_ERROR_PARAM; \ + const par_cfg_t * par_cfg = NULL; \ + par_status_t status = par_resolve_runtime(par_num, p_val, true, &par_cfg); \ + if (ePAR_OK != status) \ + return status; \ + status = par_validate_expected_type(par_cfg, ETYPE); \ + if (ePAR_OK != status) \ + return status; \ *p_val = PAR_GET_##PRIV##_PRIV(par_num); \ return ePAR_OK; \ } @@ -184,18 +310,21 @@ PAR_TYPED_ROWS(PAR_TYPED_IMPL_FAST_SETTER) PAR_TYPED_ROWS(PAR_TYPED_IMPL_GETTER) #undef PAR_TYPED_IMPL_GETTER +#undef PAR_CHECKED_SET_CASE +#undef PAR_CHECKED_LOAD_FROM_PTR_CASE +#undef PAR_CHECKED_VALIDATE_OR_SET +#undef PAR_CHECKED_ON_CHANGE +#undef PAR_CHECKED_OLD_VAL_DECL +#undef PAR_CHECKED_VALUE_CHANGED +#undef PAR_CHECKED_VALUE_CHANGED_f32 +#undef PAR_CHECKED_VALUE_CHANGED_i32 +#undef PAR_CHECKED_VALUE_CHANGED_u32 +#undef PAR_CHECKED_VALUE_CHANGED_i16 +#undef PAR_CHECKED_VALUE_CHANGED_u16 +#undef PAR_CHECKED_VALUE_CHANGED_i8 +#undef PAR_CHECKED_VALUE_CHANGED_u8 + #undef PAR_TYPED_FAST_SET_BODY -#undef PAR_TYPED_SETTER_VALIDATE_OR_SET -#undef PAR_TYPED_SETTER_ON_CHANGE -#undef PAR_TYPED_SETTER_OLD_VAL_DECL -#undef PAR_TYPED_VALUE_CHANGED -#undef PAR_TYPED_FIELD_CHANGED_u8 -#undef PAR_TYPED_FIELD_CHANGED_i8 -#undef PAR_TYPED_FIELD_CHANGED_u16 -#undef PAR_TYPED_FIELD_CHANGED_i16 -#undef PAR_TYPED_FIELD_CHANGED_u32 -#undef PAR_TYPED_FIELD_CHANGED_i32 -#undef PAR_TYPED_FIELD_CHANGED_f32 #undef PAR_TYPED_ROWS #undef PAR_TYPED_ROWS_F32 #undef PAR_TYPED_ROWS_BASE From 063a44d312ccd5f86404c7523e0b8a36ad7ddccb Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Fri, 27 Mar 2026 15:22:37 +0800 Subject: [PATCH 24/36] docs(API): Convert comments to Doxygen format and align with clang-format style --- src/.clang-format | 239 +++++ src/par.c | 2065 ++++++++++++++++++-------------------- src/par.h | 833 ++++++++++----- src/par_atomic.h | 302 +++--- src/par_bitwise_impl.inc | 22 +- src/par_cfg.h | 518 +++++----- src/par_def.c | 559 +++++------ src/par_def.h | 217 ++-- src/par_id_map_static.c | 41 +- src/par_id_map_static.h | 14 +- src/par_if.c | 367 ++++--- src/par_if.h | 92 +- src/par_layout.c | 203 ++-- src/par_layout.h | 92 +- src/par_nvm.c | 1717 ++++++++++++++----------------- src/par_nvm.h | 102 +- src/par_storage_init.inc | 84 +- src/par_typed_impl.inc | 273 ++--- 18 files changed, 3952 insertions(+), 3788 deletions(-) create mode 100644 src/.clang-format diff --git a/src/.clang-format b/src/.clang-format new file mode 100644 index 0000000..ebcac08 --- /dev/null +++ b/src/.clang-format @@ -0,0 +1,239 @@ +# Available style options are described in https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# +# An easy way to create the .clang-format file is: +# +# clang-format -style=llvm -dump-config > .clang-format +# +Language: Cpp +BasedOnStyle: LLVM +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: true + PadOperators: true +AlignConsecutiveBitFields: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: true + PadOperators: true +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveShortCaseStatements: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCaseColons: false +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: + Kind: Leave + OverEmptyLines: 1 +AllowAllArgumentsOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: false +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BitFieldColonSpacing: Both +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: Always + AfterEnum: true + AfterExternBlock: false + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + BeforeCatch: true + BeforeElse: true + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAfterAttributes: Never +BreakAfterJavaFieldAnnotations: false +BreakArrays: false +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: AfterColon +BreakInheritanceList: AfterColon +BreakStringLiterals: true +ColumnLimit: 0 +CommentPragmas: "^ IWYU pragma:" +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: Always +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: ".*" + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: "(Test)?$" +IncludeIsMainSourceRegex: "" +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: false +IndentExternBlock: NoIndent +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequiresClause: true +IndentWidth: 4 +IndentWrappedFunctionNames: false +InsertBraces: false +InsertNewlineAtEOF: true +InsertTrailingCommas: None +IntegerLiteralSeparator: + Binary: 0 + BinaryMinDigits: 0 + Decimal: 0 + DecimalMinDigits: 0 + Hex: 0 + HexMinDigits: 0 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +KeepEmptyLinesAtEOF: true +LambdaBodyIndentation: Signature +LineEnding: DeriveLF +MacroBlockBegin: "" +MacroBlockEnd: "" +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: All +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PackConstructorInitializers: BinPack +PenaltyBreakAssignment: 1000 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 1000 +PointerAlignment: Right +PPIndentWidth: 4 +QualifierAlignment: Leave +ReferenceAlignment: Pointer +ReflowComments: false +RemoveBracesLLVM: false +RemoveParentheses: Leave +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: Never +SortJavaStaticImport: Before +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Both +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeJsonColon: false +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInContainerLiterals: true +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParens: Never +SpacesInParensOptions: + InCStyleCasts: false + InConditionalStatements: false + InEmptyParentheses: false + Other: false +SpacesInSquareBrackets: false +Standard: Latest +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 4 +UseTab: Never +VerilogBreakBetweenInstancePorts: true +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE + - CF_SWIFT_NAME + - NS_SWIFT_NAME + - PP_STRINGIZE + - STRINGIZE \ No newline at end of file diff --git a/src/par.c b/src/par.c index 3229ef8..8778196 100644 --- a/src/par.c +++ b/src/par.c @@ -1,27 +1,25 @@ -// Copyright (c) 2026 Ziga Miklosic -// All Rights Reserved -// This software is under MIT licence (https://opensource.org/licenses/MIT) -//////////////////////////////////////////////////////////////////////////////// -/** -*@file par.c -*@brief Device parameters API functions -*@author Ziga Miklosic -*@email ziga.miklosic@gmail.com -*@author Matej Otic -*@email otic.matej@dancing-bits.com -*@date 29.01.2026 -*@version V3.0.2 -*/ -//////////////////////////////////////////////////////////////////////////////// -/** -*@addtogroup PARAMETERS_API -* @{ -*/ -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// Includes -//////////////////////////////////////////////////////////////////////////////// +/** + * @file par.c + * @brief Implement the public device-parameter API. + * @author Ziga Miklosic + * @version V3.0.1 + * @date 2026-01-29 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-01-29 V3.0.1 Ziga Miklosic + */ + +/** + * @addtogroup PARAMETERS_API + * @{ + */ +/** + * @brief Include dependencies. + */ #include #include @@ -31,10 +29,9 @@ #include "par_id_map_static.h" #include "par_nvm.h" #include "par_if.h" - -//////////////////////////////////////////////////////////////////////////////// -// Definitions -//////////////////////////////////////////////////////////////////////////////// +/** + * @brief Compile-time definitions. + */ PAR_STATIC_ASSERT(par_atomic_u8_i8_same_size, sizeof(par_atomic_u8_t) == sizeof(par_atomic_i8_t)); PAR_STATIC_ASSERT(par_atomic_u8_i8_same_align, PAR_ALIGNOF(par_atomic_u8_t) == PAR_ALIGNOF(par_atomic_i8_t)); PAR_STATIC_ASSERT(par_atomic_u16_i16_same_size, sizeof(par_atomic_u16_t) == sizeof(par_atomic_i16_t)); @@ -43,368 +40,330 @@ PAR_STATIC_ASSERT(par_atomic_u32_i32_same_size, sizeof(par_atomic_u32_t) == size PAR_STATIC_ASSERT(par_atomic_u32_i32_same_align, PAR_ALIGNOF(par_atomic_u32_t) == PAR_ALIGNOF(par_atomic_i32_t)); PAR_STATIC_ASSERT(par_atomic_u32_f32_same_size, sizeof(par_atomic_u32_t) == sizeof(par_atomic_f32_t)); PAR_STATIC_ASSERT(par_atomic_u32_f32_same_align, PAR_ALIGNOF(par_atomic_u32_t) == PAR_ALIGNOF(par_atomic_f32_t)); - -//////////////////////////////////////////////////////////////////////////////// -// Variables -//////////////////////////////////////////////////////////////////////////////// - /** - * Initialization guard + * @brief Module-scope variables. + */ +/** + * @brief Initialization guard. */ static bool gb_is_init = false; - /** - * Parameter callback table. - * + * @brief Parameter callback table. * @note Keep runtime hooks separate from par_cfg_t metadata table. */ -#if (( 1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION ) || ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK )) +#if ((1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION) || (1 == PAR_CFG_ENABLE_CHANGE_CALLBACK)) static struct { -#if ( 1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION ) - pf_par_validation_t validation; /**< Validation callback function (or NULL). */ +#if (1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION) + pf_par_validation_t validation; /**< Validation callback function (or NULL). */ #endif -#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) - pf_par_on_change_cb_t on_change; /**< On change callback function (or NULL). */ +#if (1 == PAR_CFG_ENABLE_CHANGE_CALLBACK) + pf_par_on_change_cb_t on_change; /**< On change callback function (or NULL). */ #endif } g_par_cb_table[ePAR_NUM_OF]; #endif /** - * Grouped typed storage backing parameter live values. + * @brief Grouped typed storage backing parameter live values. * - * @note Storage is organized as U8/U16/U32 typed members inside one private - * grouped storage object. + * @note Storage is organized as U8/U16/U32 typed members inside one private. + * grouped storage object. * - * @note Zero-length groups are mapped to size 1 arrays for compiler portability. + * @note Zero-length groups are mapped to size 1 arrays for compiler portability. * - * @note Private implementation fragment below must not be included outside par.c. + * @note Private implementation fragment below must not be included outside par.c. */ typedef struct { - par_atomic_u8_t u8[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT8)]; + par_atomic_u8_t u8[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT8)]; par_atomic_u16_t u16[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT16)]; par_atomic_u32_t u32[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT32)]; } par_storage_groups_t; - -/* - * Private implementation fragment. Do not include outside par.c. - * Defines gs_par_storage with grouped typed initializers. +/** + * @brief Private implementation fragment. Do not include outside par.c. + * @details Defines gs_par_storage with grouped typed initializers. */ #include "par_storage_init.inc" -#if ( 1 == PAR_CFG_ENABLE_RESET_ALL_RAW ) +#if (1 == PAR_CFG_ENABLE_RESET_ALL_RAW) /** - * Runtime grouped default mirror storage for raw reset-all API. + * @brief Runtime grouped default mirror storage for raw reset-all API. * - * @note Mirrors are initialized in par_init() from current live defaults - * after F32 startup patch and before optional NVM load. + * @note Mirrors are initialized in par_init() from current live defaults. + * after F32 startup patch and before optional NVM load. * - * @note Mirror layout matches the grouped live storage object. + * @note Mirror layout matches the grouped live storage object. */ -static par_storage_groups_t gs_par_default_mirror = {0}; +static par_storage_groups_t gs_par_default_mirror = { 0 }; #endif /** - * Typed live-value access pointers into grouped storage. + * @brief Typed live-value access pointers into grouped storage. */ -static par_atomic_u8_t * const gpu8_par_value = gs_par_storage.u8; -static par_atomic_i8_t * const gpi8_par_value = (par_atomic_i8_t *)gs_par_storage.u8; +static par_atomic_u8_t * const gpu8_par_value = gs_par_storage.u8; +static par_atomic_i8_t * const gpi8_par_value = (par_atomic_i8_t *)gs_par_storage.u8; static par_atomic_u16_t * const gpu16_par_value = gs_par_storage.u16; static par_atomic_i16_t * const gpi16_par_value = (par_atomic_i16_t *)gs_par_storage.u16; static par_atomic_u32_t * const gpu32_par_value = gs_par_storage.u32; static par_atomic_i32_t * const gpi32_par_value = (par_atomic_i32_t *)gs_par_storage.u32; -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) +#if (1 == PAR_CFG_ENABLE_TYPE_F32) static par_atomic_f32_t * const gpf32_par_value = (par_atomic_f32_t *)gs_par_storage.u32; #endif /** - * Offset table compatibility alias. + * @brief Offset table compatibility alias. * - * @note Layout offsets are now owned by par_layout and accessed via getter. - * Keep this local alias so existing indexed access sites remain unchanged. + * @note Layout offsets are now owned by par_layout and accessed via getter. + * Keep this local alias so existing indexed access sites remain unchanged. */ -#define g_par_offset (par_layout_get_offset_table()) +#define g_par_offset (par_layout_get_offset_table()) /** - * Private getters and setters + * @brief Private getters and setters. */ -#define PAR_GET_U8_PRIV(par_num) PAR_ATOMIC_LOAD(u8, &gpu8_par_value[g_par_offset[par_num]]) -#define PAR_GET_I8_PRIV(par_num) PAR_ATOMIC_LOAD(i8, &gpi8_par_value[g_par_offset[par_num]]) -#define PAR_GET_U16_PRIV(par_num) PAR_ATOMIC_LOAD(u16, &gpu16_par_value[g_par_offset[par_num]]) -#define PAR_GET_I16_PRIV(par_num) PAR_ATOMIC_LOAD(i16, &gpi16_par_value[g_par_offset[par_num]]) -#define PAR_GET_U32_PRIV(par_num) PAR_ATOMIC_LOAD(u32, &gpu32_par_value[g_par_offset[par_num]]) -#define PAR_GET_I32_PRIV(par_num) PAR_ATOMIC_LOAD(i32, &gpi32_par_value[g_par_offset[par_num]]) -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) -#define PAR_GET_F32_PRIV(par_num) PAR_ATOMIC_LOAD(f32, &gpf32_par_value[g_par_offset[par_num]]) +#define PAR_GET_U8_PRIV(par_num) PAR_ATOMIC_LOAD(u8, &gpu8_par_value[g_par_offset[par_num]]) +#define PAR_GET_I8_PRIV(par_num) PAR_ATOMIC_LOAD(i8, &gpi8_par_value[g_par_offset[par_num]]) +#define PAR_GET_U16_PRIV(par_num) PAR_ATOMIC_LOAD(u16, &gpu16_par_value[g_par_offset[par_num]]) +#define PAR_GET_I16_PRIV(par_num) PAR_ATOMIC_LOAD(i16, &gpi16_par_value[g_par_offset[par_num]]) +#define PAR_GET_U32_PRIV(par_num) PAR_ATOMIC_LOAD(u32, &gpu32_par_value[g_par_offset[par_num]]) +#define PAR_GET_I32_PRIV(par_num) PAR_ATOMIC_LOAD(i32, &gpi32_par_value[g_par_offset[par_num]]) +#if (1 == PAR_CFG_ENABLE_TYPE_F32) +#define PAR_GET_F32_PRIV(par_num) PAR_ATOMIC_LOAD(f32, &gpf32_par_value[g_par_offset[par_num]]) #endif -#define PAR_SET_U8_PRIV(par_num, val) PAR_ATOMIC_STORE(u8, &gpu8_par_value[g_par_offset[par_num]], (val)) -#define PAR_SET_I8_PRIV(par_num, val) PAR_ATOMIC_STORE(i8, &gpi8_par_value[g_par_offset[par_num]], (val)) -#define PAR_SET_U16_PRIV(par_num, val) PAR_ATOMIC_STORE(u16, &gpu16_par_value[g_par_offset[par_num]], (val)) -#define PAR_SET_I16_PRIV(par_num, val) PAR_ATOMIC_STORE(i16, &gpi16_par_value[g_par_offset[par_num]], (val)) -#define PAR_SET_U32_PRIV(par_num, val) PAR_ATOMIC_STORE(u32, &gpu32_par_value[g_par_offset[par_num]], (val)) -#define PAR_SET_I32_PRIV(par_num, val) PAR_ATOMIC_STORE(i32, &gpi32_par_value[g_par_offset[par_num]], (val)) -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) -#define PAR_SET_F32_PRIV(par_num, val) PAR_ATOMIC_STORE(f32, &gpf32_par_value[g_par_offset[par_num]], (val)) +#define PAR_SET_U8_PRIV(par_num, val) PAR_ATOMIC_STORE(u8, &gpu8_par_value[g_par_offset[par_num]], (val)) +#define PAR_SET_I8_PRIV(par_num, val) PAR_ATOMIC_STORE(i8, &gpi8_par_value[g_par_offset[par_num]], (val)) +#define PAR_SET_U16_PRIV(par_num, val) PAR_ATOMIC_STORE(u16, &gpu16_par_value[g_par_offset[par_num]], (val)) +#define PAR_SET_I16_PRIV(par_num, val) PAR_ATOMIC_STORE(i16, &gpi16_par_value[g_par_offset[par_num]], (val)) +#define PAR_SET_U32_PRIV(par_num, val) PAR_ATOMIC_STORE(u32, &gpu32_par_value[g_par_offset[par_num]], (val)) +#define PAR_SET_I32_PRIV(par_num, val) PAR_ATOMIC_STORE(i32, &gpi32_par_value[g_par_offset[par_num]], (val)) +#if (1 == PAR_CFG_ENABLE_TYPE_F32) +#define PAR_SET_F32_PRIV(par_num, val) PAR_ATOMIC_STORE(f32, &gpf32_par_value[g_par_offset[par_num]], (val)) #endif -#if ( PAR_CFG_DEBUG_EN ) +#if (PAR_CFG_DEBUG_EN) - /** - * Status strings - */ - static const char * gs_status[] = - { - "OK", - - "ERROR", - "ERROR INIT", - "ERROR NVM", - "ERROR CRC", - "ERROR TYPE", - "ERROR MUTEX", - "ERROR VALUE", - "ERROR PARAM", - "ERROR PAR NUM", - "ERROR ACCESS", - "WARN SET TO DEF", - "WARN NVM REWRITTEN", - "NO PERSISTENT", - "LIMITED", - "N/A", - "N/A", - }; +/** + * @brief Status strings. + */ +static const char *gs_status[] = { + "OK", + + "ERROR", + "ERROR INIT", + "ERROR NVM", + "ERROR CRC", + "ERROR TYPE", + "ERROR MUTEX", + "ERROR VALUE", + "ERROR PARAM", + "ERROR PAR NUM", + "ERROR ACCESS", + "WARN SET TO DEF", + "WARN NVM REWRITTEN", + "NO PERSISTENT", + "LIMITED", + "N/A", + "N/A", +}; #endif - -//////////////////////////////////////////////////////////////////////////////// -// Function Prototypes -//////////////////////////////////////////////////////////////////////////////// -#if ( 1 == PAR_CFG_ENABLE_ID ) -static inline uint32_t par_hash_id (const uint16_t id); -#if (( 1 == PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK ) || ( 1 == PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK )) -static par_status_t par_runtime_validate_id_table (const par_cfg_t * const p_par_cfg); +/** + * @brief Function declarations. + */ +#if (1 == PAR_CFG_ENABLE_ID) +static inline uint32_t par_hash_id(const uint16_t id); +#if ((1 == PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK) || (1 == PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK)) +static par_status_t par_runtime_validate_id_table(const par_cfg_t * const p_par_cfg); #endif #endif -static par_status_t par_set_checked_core (const par_num_t par_num, const par_type_list_t expected_type, const par_type_t * const p_typed_val, const void * const p_ptr_val); -#if ( 1 == PAR_CFG_NVM_EN ) -static par_status_t par_is_value_changed (const par_num_t par_num, const void * p_val, bool * const p_value_changed); -#endif /* ( 1 == PAR_CFG_NVM_EN ) */ -//////////////////////////////////////////////////////////////////////////////// -// Functions -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -#if ( 1 == PAR_CFG_ENABLE_DESC ) && ( 1 == PAR_CFG_ENABLE_DESC_CHECK ) -/** -* Validate parameter description string -* -* @note Default weak implementation only prohibits comma character. -* Application may override this symbol with stronger policy. -* -* @param[in] p_desc - Parameter description -* @return true if description is valid -*/ -//////////////////////////////////////////////////////////////////////////////// +static par_status_t par_set_checked_core(const par_num_t par_num, const par_type_list_t expected_type, const par_type_t * const p_typed_val, const void * const p_ptr_val); +#if (1 == PAR_CFG_NVM_EN) +static par_status_t par_is_value_changed(const par_num_t par_num, const void *p_val, bool * const p_value_changed); +#endif +/** + * @brief Function declarations and definitions. + */ +#if (1 == PAR_CFG_ENABLE_DESC) && (1 == PAR_CFG_ENABLE_DESC_CHECK) +/** + * @brief Validate parameter description string. + * + * @note Default weak implementation only prohibits comma character. + * Application may override this symbol with stronger policy. + * + * @param p_desc Parameter description. + * @return true if description is valid. + */ PAR_PORT_WEAK bool par_port_is_desc_valid(const char * const p_desc) { return ((NULL == p_desc) || (NULL == strchr(p_desc, ','))); } #endif - -//////////////////////////////////////////////////////////////////////////////// -/** -* Resolve metadata entry for parameter identified by number -* -* @note Metadata resolution only checks parameter number and table -* entry. It intentionally does not require the runtime module -* to be initialized, because callers only read compile-time -* metadata. -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] p_arg - Optional pointer argument to validate -* @param[in] require_arg - True if p_arg must not be NULL -* @param[out] pp_cfg - Optional output pointer to parameter configuration -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// +/** + * @brief Resolve metadata entry for parameter identified by number. + * + * @note Metadata resolution only checks parameter number and table. + * entry. It intentionally does not require the runtime module. + * to be initialized, because callers only read compile-time. + * metadata. + * + * @param par_num Parameter number (enumeration). + * @param p_arg Optional pointer argument to validate. + * @param require_arg True if p_arg must not be NULL. + * @param pp_cfg Optional output pointer to parameter configuration. + * @return Status of operation. + */ static par_status_t par_resolve_metadata(const par_num_t par_num, const void * const p_arg, const bool require_arg, const par_cfg_t ** const pp_cfg) { - const par_cfg_t * p_cfg = NULL; + const par_cfg_t *p_cfg = NULL; - if (( true == require_arg ) && ( NULL == p_arg )) + if ((true == require_arg) && (NULL == p_arg)) { return ePAR_ERROR_PARAM; } - PAR_ASSERT( par_num < ePAR_NUM_OF ); - if ( par_num >= ePAR_NUM_OF ) + PAR_ASSERT(par_num < ePAR_NUM_OF); + if (par_num >= ePAR_NUM_OF) { return ePAR_ERROR_PAR_NUM; } - p_cfg = par_get_config( par_num ); - if ( NULL == p_cfg ) + p_cfg = par_get_config(par_num); + if (NULL == p_cfg) { return ePAR_ERROR; } - if ( NULL != pp_cfg ) + if (NULL != pp_cfg) { *pp_cfg = p_cfg; } return ePAR_OK; } - -//////////////////////////////////////////////////////////////////////////////// -/** -* Resolve runtime metadata entry for parameter identified by number -* -* @note Runtime resolution extends metadata resolution with module -* init state validation, because live parameter storage is only -* valid after successful par_init(). -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] p_arg - Optional pointer argument to validate -* @param[in] require_arg - True if p_arg must not be NULL -* @param[out] pp_cfg - Optional output pointer to parameter configuration -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// +/** + * @brief Resolve runtime metadata entry for parameter identified by number. + * + * @note Runtime resolution extends metadata resolution with module. + * init state validation, because live parameter storage is only. + * valid after successful par_init(). + * + * @param par_num Parameter number (enumeration). + * @param p_arg Optional pointer argument to validate. + * @param require_arg True if p_arg must not be NULL. + * @param pp_cfg Optional output pointer to parameter configuration. + * @return Status of operation. + */ static par_status_t par_resolve_runtime(const par_num_t par_num, const void * const p_arg, const bool require_arg, const par_cfg_t ** const pp_cfg) { - if ( true != par_is_init() ) + if (true != par_is_init()) { return ePAR_ERROR_INIT; } - return par_resolve_metadata( par_num, p_arg, require_arg, pp_cfg ); + return par_resolve_metadata(par_num, p_arg, require_arg, pp_cfg); } - -//////////////////////////////////////////////////////////////////////////////// -/** -* Validate expected parameter type against resolved metadata -* -* @note Callers shall resolve metadata first and then validate the -* expected parameter type against the resolved configuration. -* -* @param[in] p_cfg - Resolved parameter configuration entry -* @param[in] expected_type - Expected parameter type for operation -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// +/** + * @brief Validate expected parameter type against resolved metadata. + * + * @note Callers shall resolve metadata first and then validate the. + * expected parameter type against the resolved configuration. + * + * @param p_cfg Resolved parameter configuration entry. + * @param expected_type Expected parameter type for operation. + * @return Status of operation. + */ static par_status_t par_validate_expected_type(const par_cfg_t * const p_cfg, const par_type_list_t expected_type) { - if ( NULL == p_cfg ) + if (NULL == p_cfg) { return ePAR_ERROR; } - PAR_ASSERT( expected_type == p_cfg->type ); - if ( expected_type != p_cfg->type ) + PAR_ASSERT(expected_type == p_cfg->type); + if (expected_type != p_cfg->type) { return ePAR_ERROR_TYPE; } return ePAR_OK; } - -//////////////////////////////////////////////////////////////////////////////// -/** -* Compare two F32 values by raw bit pattern -* -* @note This helper intentionally uses memcpy() instead of pointer -* casting or union type-punning. memcpy() preserves the exact -* IEEE-754 bit pattern while remaining strict-aliasing safe. -* Bitwise comparison keeps NaN payloads and signed-zero handling -* deterministic for parameter storage use cases. -* -* @param[in] lhs - Left-hand float value -* @param[in] rhs - Right-hand float value -* @return true if raw 32-bit representations are equal -*/ -//////////////////////////////////////////////////////////////////////////////// +/** + * @brief Compare two F32 values by raw bit pattern. + * + * @note This helper intentionally uses memcpy() instead of pointer. + * casting or union type-punning. memcpy() preserves the exact. + * IEEE-754 bit pattern while remaining strict-aliasing safe. + * Bitwise comparison keeps NaN payloads and signed-zero handling. + * deterministic for parameter storage use cases. + * + * @param lhs Left-hand float value. + * @param rhs Right-hand float value. + * @return true if raw 32-bit representations are equal. + */ static bool par_f32_bits_equal(const float32_t lhs, const float32_t rhs) { uint32_t lhs_bits = 0U; uint32_t rhs_bits = 0U; - memcpy( &lhs_bits, &lhs, sizeof(lhs_bits) ); - memcpy( &rhs_bits, &rhs, sizeof(rhs_bits) ); + memcpy(&lhs_bits, &lhs, sizeof(lhs_bits)); + memcpy(&rhs_bits, &rhs, sizeof(rhs_bits)); - return ( lhs_bits == rhs_bits ); + return (lhs_bits == rhs_bits); } -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) -//////////////////////////////////////////////////////////////////////////////// -/** -* Patch F32 defaults into shared u32 storage as bit-patterns -* -* @note Integer defaults are already initialized at definition time. -* F32 defaults are patched once after layout offsets are available. -* -* @return void -*/ -//////////////////////////////////////////////////////////////////////////////// +#if (1 == PAR_CFG_ENABLE_TYPE_F32) +/** + * @brief Patch F32 defaults into shared u32 storage as bit-patterns. + * + * @note Integer defaults are already initialized at definition time. + * F32 defaults are patched once after layout offsets are available. + */ static void par_patch_f32_defaults_from_table(void) { - for ( par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) + for (par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++) { - const par_cfg_t * const p_cfg = par_cfg_get( par_num ); - if ( ePAR_TYPE_F32 == p_cfg->type ) + const par_cfg_t * const p_cfg = par_cfg_get(par_num); + if (ePAR_TYPE_F32 == p_cfg->type) { - PAR_SET_F32_PRIV( par_num, p_cfg->def.f32 ); + PAR_SET_F32_PRIV(par_num, p_cfg->def.f32); } } } #endif - -//////////////////////////////////////////////////////////////////////////////// /** - * Bind static space for live parameter values -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Bind static space for live parameter values. + */ static void par_bind_storage_layout(void) { - // Initialize and obtain parameter layout (offset map + per-width counts) par_layout_init(); - - // Calculate full RAM size for static storage groups - PAR_DBG_PRINT("Total RAM consumption for parameters value: %u bytes", (unsigned) - (((uint32_t)par_layout_get_count().count32 * 4u) + - ((uint32_t)par_layout_get_count().count16 * 2u) + - ((uint32_t)par_layout_get_count().count8))); + PAR_DBG_PRINT("Total RAM consumption for parameters value: %u bytes", (unsigned)(((uint32_t)par_layout_get_count().count32 * 4u) + + ((uint32_t)par_layout_get_count().count16 * 2u) + + ((uint32_t)par_layout_get_count().count8))); } - -//////////////////////////////////////////////////////////////////////////////// /** -* Hash parameter ID to bucket index -* -* @param[in] id - Parameter ID -* @return hash index -*/ -//////////////////////////////////////////////////////////////////////////////// -#if ( 1 == PAR_CFG_ENABLE_ID ) + * @brief Hash parameter ID to bucket index. + * + * @param id Parameter ID. + * @return hash index. + */ +#if (1 == PAR_CFG_ENABLE_ID) static inline uint32_t par_hash_id(const uint16_t id) { - return PAR_HASH_ID_CONST( id ); + return PAR_HASH_ID_CONST(id); } -#if (( 1 == PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK ) || ( 1 == PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK )) -//////////////////////////////////////////////////////////////////////////////// -/** -* Run optional runtime diagnostics on the compiled parameter ID table -* -* @note Static ID-map generation and compile-time conflict checks are -* always enabled when PAR_CFG_ENABLE_ID = 1. This function exists -* only to provide runtime diagnostics and clearer conflict logs. -* -* @param[in] p_par_cfg - Pointer to parameters table -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// +#if ((1 == PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK) || (1 == PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK)) +/** + * @brief Run optional runtime diagnostics on the compiled parameter ID table. + * + * @note Static ID-map generation and compile-time conflict checks are. + * always enabled when PAR_CFG_ENABLE_ID = 1. This function exists. + * only to provide runtime diagnostics and clearer conflict logs. + * + * @param p_par_cfg Pointer to parameters table. + * @return Status of operation. + */ static par_status_t par_runtime_validate_id_table(const par_cfg_t * const p_par_cfg) { par_id_map_entry_t diag_map[PAR_ID_HASH_SIZE]; @@ -437,7 +396,7 @@ static par_status_t par_runtime_validate_id_table(const par_cfg_t * const p_par_ if (bucket->id != id) { PAR_DBG_PRINT("ERR, Hash collision: ID %u conflicts with ID %u at bucket %u!", - (unsigned)id, (unsigned)bucket->id, (unsigned)bucket_idx); + (unsigned)id, (unsigned)bucket->id, (unsigned)bucket_idx); PAR_ASSERT(0); return ePAR_ERROR_INIT; } @@ -448,72 +407,64 @@ static par_status_t par_runtime_validate_id_table(const par_cfg_t * const p_par_ } #endif #endif - -//////////////////////////////////////////////////////////////////////////////// /** -* Check that parameter table is correctly defined -* -* @param[in] p_par_cfg - Pointer to parameters table -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Check that parameter table is correctly defined. + * + * @param p_par_cfg Pointer to parameters table. + * @return Status of operation. + */ static par_status_t par_check_table_validity(const par_cfg_t * const p_par_cfg) { par_status_t status = ePAR_OK; -#if ( 1 == PAR_CFG_ENABLE_ID ) && (( 1 == PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK ) || ( 1 == PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK )) - // Run optional runtime diagnostics against the compiled static ID map. - status = par_runtime_validate_id_table( p_par_cfg ); - if ( ePAR_OK != status ) +#if (1 == PAR_CFG_ENABLE_ID) && ((1 == PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK) || (1 == PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK)) + status = par_runtime_validate_id_table(p_par_cfg); + if (ePAR_OK != status) { return status; } #endif - // For each parameter - for ( uint32_t i = 0; i < ePAR_NUM_OF; i++ ) + for (uint32_t i = 0; i < ePAR_NUM_OF; i++) { -#if ( 1 == PAR_CFG_ENABLE_RANGE ) +#if (1 == PAR_CFG_ENABLE_RANGE) /* - * Keep F32 range/default validation in runtime. - * - * On some embedded/legacy GCC toolchains, float comparisons used in - * typedef-based static asserts may be treated as non-constant - * expressions and trigger file-scope VLA warnings. + * Keep F32 range/default validation at runtime. + * Some embedded GCC variants do not treat these float comparisons as + * constant expressions in file-scope static assertions. */ - PAR_ASSERT(( ePAR_TYPE_F32 == p_par_cfg[i].type ) ? - ((( p_par_cfg[i].range.min.f32 < p_par_cfg[i].range.max.f32 ) && - ( p_par_cfg[i].def.f32 <= p_par_cfg[i].range.max.f32 )) && - ( p_par_cfg[i].range.min.f32 <= p_par_cfg[i].def.f32 )) - : ( 1 )); + PAR_ASSERT((ePAR_TYPE_F32 == p_par_cfg[i].type) ? (((p_par_cfg[i].range.min.f32 < p_par_cfg[i].range.max.f32) && + (p_par_cfg[i].def.f32 <= p_par_cfg[i].range.max.f32)) && + (p_par_cfg[i].range.min.f32 <= p_par_cfg[i].def.f32)) + : (1)); #endif -#if ( 1 == PAR_CFG_ENABLE_NAME ) - if ( NULL == p_par_cfg[i].name ) +#if (1 == PAR_CFG_ENABLE_NAME) + if (NULL == p_par_cfg[i].name) { status = ePAR_ERROR_INIT; - PAR_DBG_PRINT( "ERR, Parameter %d name missing!", i ); - PAR_ASSERT( 0 ); + PAR_DBG_PRINT("ERR, Parameter %d name missing!", i); + PAR_ASSERT(0); break; } #endif -#if ( 1 == PAR_CFG_ENABLE_DESC ) - if ( NULL == p_par_cfg[i].desc ) +#if (1 == PAR_CFG_ENABLE_DESC) + if (NULL == p_par_cfg[i].desc) { status = ePAR_ERROR_INIT; - PAR_DBG_PRINT( "ERR, Parameter %d description missing!", i ); - PAR_ASSERT( 0 ); + PAR_DBG_PRINT("ERR, Parameter %d description missing!", i); + PAR_ASSERT(0); break; } #endif -#if ( 1 == PAR_CFG_ENABLE_DESC ) && ( 1 == PAR_CFG_ENABLE_DESC_CHECK ) - if ( false == par_port_is_desc_valid( p_par_cfg[i].desc)) +#if (1 == PAR_CFG_ENABLE_DESC) && (1 == PAR_CFG_ENABLE_DESC_CHECK) + if (false == par_port_is_desc_valid(p_par_cfg[i].desc)) { status = ePAR_ERROR_INIT; - PAR_DBG_PRINT( "ERR, Parameter %d description is invalid!", i ); - PAR_ASSERT( 0 ); + PAR_DBG_PRINT("ERR, Parameter %d description is invalid!", i); + PAR_ASSERT(0); break; } #endif @@ -521,641 +472,589 @@ static par_status_t par_check_table_validity(const par_cfg_t * const p_par_cfg) return status; } - -//////////////////////////////////////////////////////////////////////////////// /** -* @} -*/ -//////////////////////////////////////////////////////////////////////////////// + * @} + */ -//////////////////////////////////////////////////////////////////////////////// /** -*@addtogroup API_FUNCTIONS -* @{ -* -* Following function are part of Device Parameter module API. -*/ -//////////////////////////////////////////////////////////////////////////////// + * @addtogroup API_FUNCTIONS + * @{ + * + * @brief Following function are part of Device Parameter module API. + */ -//////////////////////////////////////////////////////////////////////////////// /** -* Device Parameters initialization -* -* At init parameter table is being check for correct definition, allocation -* in RAM space for parameters live values and additionaly interface to -* platform is being done. -* -* @return status - Status of initialization -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Device Parameters initialization. + * + * At init parameter table is being check for correct definition, allocation. + * in RAM space for parameters live values and additionaly interface to. + * platform is being done. + * + * @return Status of initialization. + */ par_status_t par_init(void) { par_status_t status = ePAR_OK; - PAR_ASSERT( false == par_is_init()); - if ( false != par_is_init()) return ePAR_ERROR_INIT; - - // Check if par table is defined correctly - status |= par_check_table_validity( par_cfg_get_table()); - - // Bind storage layout + PAR_ASSERT(false == par_is_init()); + if (false != par_is_init()) + return ePAR_ERROR_INIT; + status |= par_check_table_validity(par_cfg_get_table()); par_bind_storage_layout(); - - // Initialize parameter interface status |= par_if_init(); - - // Init succeed PAR_ASSERT(ePAR_OK == status); - if ( ePAR_OK == status ) + if (ePAR_OK == status) { gb_is_init = true; - - /* Set all parameters to default - * Integer defaults are already initialized at definition time. - * F32 defaults are patched once after layout offsets are available. - */ -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) - par_patch_f32_defaults_from_table(); + /* Patch F32 defaults after layout offsets become available. */ +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + par_patch_f32_defaults_from_table(); #endif -#if ( 1 == PAR_CFG_ENABLE_RESET_ALL_RAW ) - /* - * Build default mirrors from current live defaults. - * This snapshot is taken before optional NVM load. - */ - memcpy( &gs_par_default_mirror, &gs_par_storage, sizeof(gs_par_storage) ); +#if (1 == PAR_CFG_ENABLE_RESET_ALL_RAW) + /* Snapshot defaults before optional NVM restore. */ + memcpy(&gs_par_default_mirror, &gs_par_storage, sizeof(gs_par_storage)); #endif - #if ( 1 == PAR_CFG_NVM_EN ) - // Init and load parameters from NVM - status |= par_nvm_init(); - #endif +#if (1 == PAR_CFG_NVM_EN) + /* Restore persisted values after default initialization. */ + status |= par_nvm_init(); +#endif } - PAR_DBG_PRINT( "PAR: Parameters initialized with status: %s", par_get_status_str( status )); + PAR_DBG_PRINT("PAR: Parameters initialized with status: %s", par_get_status_str(status)); return status; } - -//////////////////////////////////////////////////////////////////////////////// /** -* De-initialize Device Parameters -* -* @return status - Status of de-initialization -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief De-initialize Device Parameters. + * + * @return Status of de-initialization. + */ par_status_t par_deinit(void) { par_status_t status = ePAR_OK; - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; + PAR_ASSERT(true == par_is_init()); + if (true != par_is_init()) + return ePAR_ERROR_INIT; - #if ( 1 == PAR_CFG_NVM_EN ) - // Init and load parameters from NVM - status = par_nvm_deinit(); - #endif +#if (1 == PAR_CFG_NVM_EN) + deinit_status = par_nvm_deinit(); + status |= deinit_status; +#endif - // Module de-initialized + deinit_status = par_if_deinit(); + status |= deinit_status; gb_is_init = false; return status; } - -//////////////////////////////////////////////////////////////////////////////// /** -* Get initialization flag -* -* @return Initialization state -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Get initialization flag. + * + * @return Initialization state. + */ bool par_is_init(void) { return gb_is_init; } - -//////////////////////////////////////////////////////////////////////////////// /** -* Try to acquire mutex for specified parameter -* -* @param[in] par_num - Parameter number (enumeration) -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Try to acquire mutex for specified parameter. + * + * @param par_num Parameter number (enumeration). + * @return Status of operation. + */ par_status_t par_acquire_mutex(const par_num_t par_num) { - PAR_ASSERT( par_num < ePAR_NUM_OF ); + PAR_ASSERT(par_num < ePAR_NUM_OF); return par_if_aquire_mutex(par_num); } - -//////////////////////////////////////////////////////////////////////////////// /** -* Try to acquire mutex for specified parameter -* -* @param[in] par_num - Parameter number (enumeration) -* @return void -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Try to acquire mutex for specified parameter. + * + * @param par_num Parameter number (enumeration). + */ void par_release_mutex(const par_num_t par_num) { - PAR_ASSERT( par_num < ePAR_NUM_OF ); + PAR_ASSERT(par_num < ePAR_NUM_OF); par_if_release_mutex(par_num); } - -//////////////////////////////////////////////////////////////////////////////// -/** -* Set parameter value -* -* @note Mandatory to cast input argument to appropriate type. E.g.: -* -* @code -* float32_t my_val = 1.234f; -* par_set( ePAR_MY_VAR, (float32_t*) &my_val ); -* @endcode -* -* @note Input is parameter number (enumeration) defined in par_cfg.h and not -* parameter ID number! -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] p_val - Pointer to value -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set(const par_num_t par_num, const void * p_val) +/** + * @brief Set parameter value. + * + * @note Mandatory to cast input argument to appropriate type. E.g.: + * + * @code + * float32_t my_val = 1.234f;. + * par_set( ePAR_MY_VAR, (float32_t*) &my_val );. + * @endcode + * + * @note Input is parameter number (enumeration) defined in par_cfg.h and not. + * parameter ID number! + * + * @param par_num Parameter number (enumeration). + * @param p_val Pointer to value. + * @return Status of operation. + */ +par_status_t par_set(const par_num_t par_num, const void *p_val) { - const par_cfg_t * par_cfg = NULL; - const par_status_t status = par_resolve_runtime( par_num, p_val, true, &par_cfg ); + const par_cfg_t *par_cfg = NULL; + const par_status_t status = par_resolve_runtime(par_num, p_val, true, &par_cfg); - if ( ePAR_OK != status ) + if (ePAR_OK != status) { return status; } - return par_set_checked_core( par_num, par_cfg->type, NULL, p_val ); + return par_set_checked_core(par_num, par_cfg->type, NULL, p_val); } - -//////////////////////////////////////////////////////////////////////////////// -/** -* Set parameter value by ID -* -* @param[in] id - Parameter ID number -* @param[in] p_val - Pointer to value -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -#if ( 1 == PAR_CFG_ENABLE_ID ) -par_status_t par_set_by_id(const uint16_t id, const void * p_val) +/** + * @brief Set parameter value by ID. + * + * @param id Parameter ID number. + * @param p_val Pointer to value. + * @return Status of operation. + */ +#if (1 == PAR_CFG_ENABLE_ID) +par_status_t par_set_by_id(const uint16_t id, const void *p_val) { par_num_t par_num = 0U; - const par_status_t status = par_get_num_by_id( id, &par_num ); + const par_status_t status = par_get_num_by_id(id, &par_num); - if ( ePAR_OK != status ) + if (ePAR_OK != status) { return status; } - return par_set( par_num, p_val ); + return par_set(par_num, p_val); } #endif - -//////////////////////////////////////////////////////////////////////////////// /** -* Typed setter/getter implementation -* @note Private implementation fragments. Do not include outside par.c. - -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Typed parameter API implementation expansion point. + * + * The following public APIs are not handwritten one by one in this file. + * They are emitted by macro expansion from "par_typed_impl.inc": + * + * - par_set_u8 / i8 / u16 / i16 / u32 / i32 (/ f32). + * - par_set_u8_fast / i8_fast / u16_fast / i16_fast / u32_fast / i32_fast (/ f32_fast). + * - par_get_u8 / i8 / u16 / i16 / u32 / i32 (/ f32). + * + * If IDE navigation cannot jump from par.h declarations to concrete bodies,. + * inspect this include point and then open par_typed_impl.inc. + * + * @note Private implementation fragment. Do not include outside par.c. + */ #include "par_typed_impl.inc" - -//////////////////////////////////////////////////////////////////////////////// /** -* Bitwise fast setter implementation -* @note Private implementation fragments. Do not include outside par.c. -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Bitwise fast setter implementation. + * @note Private implementation fragments. Do not include outside par.c. + */ #include "par_bitwise_impl.inc" - -//////////////////////////////////////////////////////////////////////////////// /** -* Set parameter to default value -* -* @pre Parameters must be initialised before usage! -* -* @param[in] par_num - Parameter number (enumeration) -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Set parameter to default value. + * + * @pre Parameters must be initialised before usage! + * + * @param par_num Parameter number (enumeration). + * @return Status of operation. + */ par_status_t par_set_to_default(const par_num_t par_num) { - const par_cfg_t * par_cfg = NULL; + const par_cfg_t *par_cfg = NULL; par_status_t status = par_resolve_runtime(par_num, NULL, false, &par_cfg); - if ( ePAR_OK != status ) + if (ePAR_OK != status) { return status; } - if ( ePAR_OK != par_acquire_mutex( par_num )) + if (ePAR_OK != par_acquire_mutex(par_num)) { return ePAR_ERROR_MUTEX; } - switch ( par_cfg->type ) + switch (par_cfg->type) { - case ePAR_TYPE_U8: - status = par_set_u8_fast( par_num, par_cfg->def.u8 ); - break; - - case ePAR_TYPE_I8: - status = par_set_i8_fast( par_num, par_cfg->def.i8 ); - break; - - case ePAR_TYPE_U16: - status = par_set_u16_fast( par_num, par_cfg->def.u16 ); - break; - - case ePAR_TYPE_I16: - status = par_set_i16_fast( par_num, par_cfg->def.i16 ); - break; - - case ePAR_TYPE_U32: - status = par_set_u32_fast( par_num, par_cfg->def.u32 ); - break; - - case ePAR_TYPE_I32: - status = par_set_i32_fast( par_num, par_cfg->def.i32 ); - break; - -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) - case ePAR_TYPE_F32: - status = par_set_f32_fast( par_num, par_cfg->def.f32 ); - break; + case ePAR_TYPE_U8: + status = par_set_u8_fast(par_num, par_cfg->def.u8); + break; + + case ePAR_TYPE_I8: + status = par_set_i8_fast(par_num, par_cfg->def.i8); + break; + + case ePAR_TYPE_U16: + status = par_set_u16_fast(par_num, par_cfg->def.u16); + break; + + case ePAR_TYPE_I16: + status = par_set_i16_fast(par_num, par_cfg->def.i16); + break; + + case ePAR_TYPE_U32: + status = par_set_u32_fast(par_num, par_cfg->def.u32); + break; + + case ePAR_TYPE_I32: + status = par_set_i32_fast(par_num, par_cfg->def.i32); + break; + +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + case ePAR_TYPE_F32: + status = par_set_f32_fast(par_num, par_cfg->def.f32); + break; #endif - case ePAR_TYPE_NUM_OF: - default: - PAR_ASSERT( 0 ); - status = ePAR_ERROR_TYPE; - break; + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + status = ePAR_ERROR_TYPE; + break; } - par_release_mutex( par_num ); + par_release_mutex(par_num); return status; } -#if ( 1 == PAR_CFG_ENABLE_RESET_ALL_RAW ) -//////////////////////////////////////////////////////////////////////////////// -/** -* Reset all parameters to default values via raw storage restore -* -* @note Unlike par_set_all_to_default(), this API restores grouped -* storage directly from default mirrors instead of iterating over -* all parameters through the normal setter path. -* -* @note This path is therefore typically faster for bulk reset, because -* it avoids per-parameter runtime validation, on-change callback, -* and setter-side range handling. -* -* @note Restore is performed as one grouped storage snapshot copy. -* Internal U8/U16/U32 width-group storage semantics are preserved. -* -* @pre Parameters must be initialized before usage. -* -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// +#if (1 == PAR_CFG_ENABLE_RESET_ALL_RAW) +/** + * @brief Reset all parameters to default values via raw storage restore. + * + * @note Unlike par_set_all_to_default(), this API restores grouped. + * storage directly from default mirrors instead of iterating over. + * all parameters through the normal setter path. + * + * @note This path is therefore typically faster for bulk reset, because. + * it avoids per-parameter runtime validation, on-change callback,. + * and setter-side range handling. + * + * @note Restore is performed as one grouped storage snapshot copy. + * Internal U8/U16/U32 width-group storage semantics are preserved. + * + * @pre Parameters must be initialized before usage. + * + * @return Status of operation. + */ par_status_t par_reset_all_to_default_raw(void) { - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; + PAR_ASSERT(true == par_is_init()); + if (true != par_is_init()) + return ePAR_ERROR_INIT; - if ( ePAR_OK != par_acquire_mutex((par_num_t)0)) + if (ePAR_OK != par_acquire_mutex((par_num_t)0)) { return ePAR_ERROR_MUTEX; } - memcpy( &gs_par_storage, &gs_par_default_mirror, sizeof(gs_par_storage) ); + memcpy(&gs_par_storage, &gs_par_default_mirror, sizeof(gs_par_storage)); par_release_mutex((par_num_t)0); - PAR_DBG_PRINT( "PAR: Raw reset all parameters to default" ); + PAR_DBG_PRINT("PAR: Raw reset all parameters to default"); return ePAR_OK; } #endif - -//////////////////////////////////////////////////////////////////////////////// -/** -* Set all parameters to default value -* -* @pre Parameters must be initialised before usage! -* @note When PAR_CFG_ENABLE_RESET_ALL_RAW = 1, this public API forwards -* to par_reset_all_to_default_raw() for maximum reset speed. -* @note Otherwise it iterates through parameters and resets them via -* par_set_to_default(), preserving fast default-restore semantics. -* -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// +/** + * @brief Set all parameters to default value. + * + * @pre Parameters must be initialised before usage! + * @note When PAR_CFG_ENABLE_RESET_ALL_RAW = 1, this public API forwards. + * to par_reset_all_to_default_raw() for maximum reset speed. + * @note Otherwise it iterates through parameters and resets them via. + * par_set_to_default(), preserving fast default-restore semantics. + * + * @return Status of operation. + */ par_status_t par_set_all_to_default(void) { -#if ( 1 == PAR_CFG_ENABLE_RESET_ALL_RAW ) +#if (1 == PAR_CFG_ENABLE_RESET_ALL_RAW) return par_reset_all_to_default_raw(); #else par_status_t status = ePAR_OK; - if ( true != par_is_init() ) + if (true != par_is_init()) { return ePAR_ERROR_INIT; } - for ( par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) + for (par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++) { - status |= par_set_to_default( par_num ); + status |= par_set_to_default(par_num); } - PAR_DBG_PRINT( "PAR: Setting all parameters to default" ); + PAR_DBG_PRINT("PAR: Setting all parameters to default"); return status; #endif } - -//////////////////////////////////////////////////////////////////////////////// /** -* Check if parameter changed from its default value -* -* @param[in] par_num - Parameter number (enumeration) -* @param[out] p_has_changed - Pointer to changed indication -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_has_changed(const par_num_t par_num, bool *const p_has_changed) + * @brief Check if parameter changed from its default value. + * + * @param par_num Parameter number (enumeration). + * @param p_has_changed Pointer to changed indication. + * @return Status of operation. + */ +par_status_t par_has_changed(const par_num_t par_num, bool * const p_has_changed) { - const par_cfg_t * par_cfg = NULL; + const par_cfg_t *par_cfg = NULL; par_status_t status = ePAR_OK; status = par_resolve_runtime(par_num, p_has_changed, true, &par_cfg); - if ( ePAR_OK != status ) + if (ePAR_OK != status) { return status; } - switch ( par_cfg->type ) + switch (par_cfg->type) { - case ePAR_TYPE_U8: - { - uint8_t cur = 0U; - const par_status_t status = par_get_u8(par_num, &cur); - if ( ePAR_OK != status ) return status; - *p_has_changed = (cur != par_cfg->def.u8); - break; - } + case ePAR_TYPE_U8: + { + uint8_t cur = 0U; + const par_status_t status = par_get_u8(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_has_changed = (cur != par_cfg->def.u8); + break; + } - case ePAR_TYPE_I8: - { - int8_t cur = 0; - const par_status_t status = par_get_i8(par_num, &cur); - if ( ePAR_OK != status ) return status; - *p_has_changed = (cur != par_cfg->def.i8); - break; - } + case ePAR_TYPE_I8: + { + int8_t cur = 0; + const par_status_t status = par_get_i8(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_has_changed = (cur != par_cfg->def.i8); + break; + } - case ePAR_TYPE_U16: - { - uint16_t cur = 0U; - const par_status_t status = par_get_u16(par_num, &cur); - if ( ePAR_OK != status ) return status; - *p_has_changed = (cur != par_cfg->def.u16); - break; - } + case ePAR_TYPE_U16: + { + uint16_t cur = 0U; + const par_status_t status = par_get_u16(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_has_changed = (cur != par_cfg->def.u16); + break; + } - case ePAR_TYPE_I16: - { - int16_t cur = 0; - const par_status_t status = par_get_i16(par_num, &cur); - if ( ePAR_OK != status ) return status; - *p_has_changed = (cur != par_cfg->def.i16); - break; - } + case ePAR_TYPE_I16: + { + int16_t cur = 0; + const par_status_t status = par_get_i16(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_has_changed = (cur != par_cfg->def.i16); + break; + } - case ePAR_TYPE_U32: - { - uint32_t cur = 0U; - const par_status_t status = par_get_u32(par_num, &cur); - if ( ePAR_OK != status ) return status; - *p_has_changed = (cur != par_cfg->def.u32); - break; - } + case ePAR_TYPE_U32: + { + uint32_t cur = 0U; + const par_status_t status = par_get_u32(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_has_changed = (cur != par_cfg->def.u32); + break; + } - case ePAR_TYPE_I32: - { - int32_t cur = 0; - const par_status_t status = par_get_i32(par_num, &cur); - if ( ePAR_OK != status ) return status; - *p_has_changed = (cur != par_cfg->def.i32); - break; - } + case ePAR_TYPE_I32: + { + int32_t cur = 0; + const par_status_t status = par_get_i32(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_has_changed = (cur != par_cfg->def.i32); + break; + } -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) - case ePAR_TYPE_F32: - { - float32_t cur = 0.0f; - const par_status_t status = par_get_f32(par_num, &cur); - if ( ePAR_OK != status ) return status; - *p_has_changed = !par_f32_bits_equal(cur, par_cfg->def.f32); - break; - } +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + case ePAR_TYPE_F32: + { + float32_t cur = 0.0f; + const par_status_t status = par_get_f32(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_has_changed = !par_f32_bits_equal(cur, par_cfg->def.f32); + break; + } #endif - case ePAR_TYPE_NUM_OF: - default: - PAR_ASSERT( 0 ); - return ePAR_ERROR_TYPE; + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + return ePAR_ERROR_TYPE; } return ePAR_OK; } - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get parameter value -* -* @note Mandatory to cast input argument to appropriate type. E.g.: -* -* @code -* float32_t my_val = 0.0f; -* par_get( ePAR_MY_VAR, (float32_t*) &my_val ); -* @endcode -* -* @note Input is parameter number (enumeration) defined in par_cfg.h and not -* parameter ID number! -* -* @param[in] par_num - Parameter number (enumeration) -* @param[out] p_val - Parameter value -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// +/** + * @brief Get parameter value. + * + * @note Mandatory to cast input argument to appropriate type. E.g.: + * + * @code + * float32_t my_val = 0.0f;. + * par_get( ePAR_MY_VAR, (float32_t*) &my_val );. + * @endcode + * + * @note Input is parameter number (enumeration) defined in par_cfg.h and not. + * parameter ID number! + * + * @param par_num Parameter number (enumeration). + * @param p_val Parameter value. + * @return Status of operation. + */ par_status_t par_get(const par_num_t par_num, void * const p_val) { - const par_cfg_t * par_cfg = NULL; + const par_cfg_t *par_cfg = NULL; par_status_t status = ePAR_OK; status = par_resolve_runtime(par_num, p_val, true, &par_cfg); - if ( ePAR_OK != status ) + if (ePAR_OK != status) { return status; } - switch ( par_cfg->type ) + switch (par_cfg->type) { - case ePAR_TYPE_U8: - return par_get_u8(par_num, (uint8_t*) p_val); + case ePAR_TYPE_U8: + return par_get_u8(par_num, (uint8_t *)p_val); - case ePAR_TYPE_I8: - return par_get_i8(par_num, (int8_t*) p_val); + case ePAR_TYPE_I8: + return par_get_i8(par_num, (int8_t *)p_val); - case ePAR_TYPE_U16: - return par_get_u16(par_num, (uint16_t*) p_val); + case ePAR_TYPE_U16: + return par_get_u16(par_num, (uint16_t *)p_val); - case ePAR_TYPE_I16: - return par_get_i16(par_num, (int16_t*) p_val); + case ePAR_TYPE_I16: + return par_get_i16(par_num, (int16_t *)p_val); - case ePAR_TYPE_U32: - return par_get_u32(par_num, (uint32_t*) p_val); + case ePAR_TYPE_U32: + return par_get_u32(par_num, (uint32_t *)p_val); - case ePAR_TYPE_I32: - return par_get_i32(par_num, (int32_t*) p_val); + case ePAR_TYPE_I32: + return par_get_i32(par_num, (int32_t *)p_val); -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) - case ePAR_TYPE_F32: - return par_get_f32(par_num, (float32_t*) p_val); +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + case ePAR_TYPE_F32: + return par_get_f32(par_num, (float32_t *)p_val); #endif - case ePAR_TYPE_NUM_OF: - default: - PAR_ASSERT( 0 ); - return ePAR_ERROR_TYPE; + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + return ePAR_ERROR_TYPE; } } - -//////////////////////////////////////////////////////////////////////////////// /** -* Get parameter value by ID -* -* @param[in] id - Parameter ID number -* @param[out] p_val - Pointer to value -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -#if ( 1 == PAR_CFG_ENABLE_ID ) + * @brief Get parameter value by ID. + * + * @param id Parameter ID number. + * @param p_val Pointer to value. + * @return Status of operation. + */ +#if (1 == PAR_CFG_ENABLE_ID) par_status_t par_get_by_id(const uint16_t id, void * const p_val) { par_num_t par_num = 0U; - const par_status_t status = par_get_num_by_id( id, &par_num ); + const par_status_t status = par_get_num_by_id(id, &par_num); - if ( ePAR_OK != status ) + if (ePAR_OK != status) { return status; } - return par_get( par_num, p_val ); + return par_get(par_num, p_val); } #endif - -//////////////////////////////////////////////////////////////////////////////// /** -* Get parameter default value -** -* @param[in] par_num - Parameter number (enumeration) -* @param[out] p_val - Parameter default value -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Get parameter default value. + * *. + * @param par_num Parameter number (enumeration). + * @param p_val Parameter default value. + * @return Status of operation. + */ par_status_t par_get_default(const par_num_t par_num, void * const p_val) { - const par_cfg_t * par_cfg = NULL; + const par_cfg_t *par_cfg = NULL; par_status_t status = ePAR_OK; status = par_resolve_metadata(par_num, p_val, true, &par_cfg); - if ( ePAR_OK != status ) + if (ePAR_OK != status) { return status; } - switch ( par_cfg->type ) + switch (par_cfg->type) { - case ePAR_TYPE_U8: - *(uint8_t*) p_val = (uint8_t) par_cfg->def.u8; - break; - - case ePAR_TYPE_I8: - *(int8_t*) p_val = (int8_t) par_cfg->def.i8; - break; - - case ePAR_TYPE_U16: - *(uint16_t*) p_val = (uint16_t) par_cfg->def.u16; - break; - - case ePAR_TYPE_I16: - *(int16_t*) p_val = (int16_t) par_cfg->def.i16; - break; - - case ePAR_TYPE_U32: - *(uint32_t*) p_val = (uint32_t) par_cfg->def.u32; - break; - - case ePAR_TYPE_I32: - *(int32_t*) p_val = (int32_t) par_cfg->def.i32; - break; - -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) - case ePAR_TYPE_F32: - *(float32_t*) p_val = (float32_t) par_cfg->def.f32; - break; + case ePAR_TYPE_U8: + *(uint8_t *)p_val = (uint8_t)par_cfg->def.u8; + break; + + case ePAR_TYPE_I8: + *(int8_t *)p_val = (int8_t)par_cfg->def.i8; + break; + + case ePAR_TYPE_U16: + *(uint16_t *)p_val = (uint16_t)par_cfg->def.u16; + break; + + case ePAR_TYPE_I16: + *(int16_t *)p_val = (int16_t)par_cfg->def.i16; + break; + + case ePAR_TYPE_U32: + *(uint32_t *)p_val = (uint32_t)par_cfg->def.u32; + break; + + case ePAR_TYPE_I32: + *(int32_t *)p_val = (int32_t)par_cfg->def.i32; + break; + +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + case ePAR_TYPE_F32: + *(float32_t *)p_val = (float32_t)par_cfg->def.f32; + break; #endif - case ePAR_TYPE_NUM_OF: - default: - PAR_ASSERT( 0 ); - return ePAR_ERROR_TYPE; + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + return ePAR_ERROR_TYPE; } return ePAR_OK; } - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get parameter configurations -* -* @note In case parameter is not found it return NULL! -* -* @param[in] par_num - Parameter number (enumeration) -* @return Parameter configuration -*/ -//////////////////////////////////////////////////////////////////////////////// -const par_cfg_t * par_get_config(const par_num_t par_num) +/** + * @brief Get parameter configurations. + * + * @note In case parameter is not found it return NULL! + * + * @param par_num Parameter number (enumeration). + * @return Parameter configuration. + */ +const par_cfg_t *par_get_config(const par_num_t par_num) { - // Invalid parameter - PAR_ASSERT( par_num < ePAR_NUM_OF ); - if ( par_num >= ePAR_NUM_OF ) return NULL; + PAR_ASSERT(par_num < ePAR_NUM_OF); + if (par_num >= ePAR_NUM_OF) + return NULL; return par_cfg_get(par_num); } - -//////////////////////////////////////////////////////////////////////////////// /** -* Get parameter name -* -* @param[in] par_num - Parameter number (enumeration) -* @return Parameter name -*/ -//////////////////////////////////////////////////////////////////////////////// -#if ( 1 == PAR_CFG_ENABLE_NAME ) -const char * par_get_name(const par_num_t par_num) + * @brief Get parameter name. + * + * @param par_num Parameter number (enumeration). + * @return Parameter name. + */ +#if (1 == PAR_CFG_ENABLE_NAME) +const char *par_get_name(const par_num_t par_num) { - const par_cfg_t * par_cfg = NULL; + const par_cfg_t *par_cfg = NULL; - if ( ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) + if (ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) { return par_cfg->name; } @@ -1163,22 +1062,19 @@ const char * par_get_name(const par_num_t par_num) return NULL; } #endif - -//////////////////////////////////////////////////////////////////////////////// /** -* Get parameter value range -* -* @param[in] par_num - Parameter number (enumeration) -* @return Parameter min/max range -*/ -//////////////////////////////////////////////////////////////////////////////// -#if ( 1 == PAR_CFG_ENABLE_RANGE ) + * @brief Get parameter value range. + * + * @param par_num Parameter number (enumeration). + * @return Parameter min/max range. + */ +#if (1 == PAR_CFG_ENABLE_RANGE) par_range_t par_get_range(const par_num_t par_num) { - par_range_t range = {0}; - const par_cfg_t * par_cfg = NULL; + par_range_t range = { 0 }; + const par_cfg_t *par_cfg = NULL; - if ( ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) + if (ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) { return par_cfg->range; } @@ -1186,21 +1082,18 @@ par_range_t par_get_range(const par_num_t par_num) return range; } #endif - -//////////////////////////////////////////////////////////////////////////////// /** -* Get parameter unit -* -* @param[in] par_num - Parameter number (enumeration) -* @return Parameter unit -*/ -//////////////////////////////////////////////////////////////////////////////// -#if ( 1 == PAR_CFG_ENABLE_UNIT ) -const char * par_get_unit(const par_num_t par_num) + * @brief Get parameter unit. + * + * @param par_num Parameter number (enumeration). + * @return Parameter unit. + */ +#if (1 == PAR_CFG_ENABLE_UNIT) +const char *par_get_unit(const par_num_t par_num) { - const par_cfg_t * par_cfg = NULL; + const par_cfg_t *par_cfg = NULL; - if ( ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) + if (ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) { return par_cfg->unit; } @@ -1208,21 +1101,18 @@ const char * par_get_unit(const par_num_t par_num) return NULL; } #endif - -//////////////////////////////////////////////////////////////////////////////// /** -* Get parameter description -* -* @param[in] par_num - Parameter number (enumeration) -* @return Parameter description -*/ -//////////////////////////////////////////////////////////////////////////////// -#if ( 1 == PAR_CFG_ENABLE_DESC ) -const char * par_get_desc(const par_num_t par_num) + * @brief Get parameter description. + * + * @param par_num Parameter number (enumeration). + * @return Parameter description. + */ +#if (1 == PAR_CFG_ENABLE_DESC) +const char *par_get_desc(const par_num_t par_num) { - const par_cfg_t * par_cfg = NULL; + const par_cfg_t *par_cfg = NULL; - if ( ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) + if (ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) { return par_cfg->desc; } @@ -1230,41 +1120,35 @@ const char * par_get_desc(const par_num_t par_num) return NULL; } #endif - -//////////////////////////////////////////////////////////////////////////////// /** -* Get parameter type -* -* @param[in] par_num - Parameter number (enumeration) -* @return Parameter data type -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Get parameter type. + * + * @param par_num Parameter number (enumeration). + * @return Parameter data type. + */ par_type_list_t par_get_type(const par_num_t par_num) { - const par_cfg_t * par_cfg = NULL; + const par_cfg_t *par_cfg = NULL; - if ( ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) + if (ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) { return par_cfg->type; } return ePAR_TYPE_NUM_OF; } - -//////////////////////////////////////////////////////////////////////////////// /** -* Get parameter access (RO, RW) -* -* @param[in] par_num - Parameter number (enumeration) -* @return Parameter access -*/ -//////////////////////////////////////////////////////////////////////////////// -#if ( 1 == PAR_CFG_ENABLE_ACCESS ) + * @brief Get parameter access (RO, RW). + * + * @param par_num Parameter number (enumeration). + * @return Parameter access. + */ +#if (1 == PAR_CFG_ENABLE_ACCESS) par_access_t par_get_access(const par_num_t par_num) { - const par_cfg_t * par_cfg = NULL; + const par_cfg_t *par_cfg = NULL; - if ( ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) + if (ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) { return par_cfg->access; } @@ -1272,21 +1156,18 @@ par_access_t par_get_access(const par_num_t par_num) return ePAR_ACCESS_RO; } #endif - -//////////////////////////////////////////////////////////////////////////////// /** -* Is parameter persistent (does it store to NVM) -* -* @param[in] par_num - Parameter number (enumeration) -* @return True if parameter is persistent -*/ -//////////////////////////////////////////////////////////////////////////////// -#if ( 1 == PAR_CFG_ENABLE_PERSIST ) + * @brief Is parameter persistent (does it store to NVM). + * + * @param par_num Parameter number (enumeration). + * @return True if parameter is persistent. + */ +#if (1 == PAR_CFG_ENABLE_PERSIST) bool par_is_persistent(const par_num_t par_num) { - const par_cfg_t * par_cfg = NULL; + const par_cfg_t *par_cfg = NULL; - if ( ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) + if (ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) { return par_cfg->persistent; } @@ -1294,35 +1175,32 @@ bool par_is_persistent(const par_num_t par_num) return false; } #endif - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get parameter number (enumeration) by ID -* -* @note This API reads the compile-time static ID map only. -* It does not require par_init(), because it does not access -* runtime parameter storage. -* -* @param[in] id - Parameter ID -* @param[out] p_par_num - Pointer to parameter enumeration number -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -#if ( 1 == PAR_CFG_ENABLE_ID ) +/** + * @brief Get parameter number (enumeration) by ID. + * + * @note This API reads the compile-time static ID map only. + * It does not require par_init(), because it does not access. + * runtime parameter storage. + * + * @param id Parameter ID. + * @param p_par_num Pointer to parameter enumeration number. + * @return Status of operation. + */ +#if (1 == PAR_CFG_ENABLE_ID) par_status_t par_get_num_by_id(const uint16_t id, par_num_t * const p_par_num) { - if ( NULL == p_par_num ) + if (NULL == p_par_num) { return ePAR_ERROR_PARAM; } { - const uint32_t bucket_idx = par_hash_id( id ); + const uint32_t bucket_idx = par_hash_id(id); const par_id_map_entry_t * const bucket = &g_par_id_map_static[bucket_idx]; - if (( 0u != bucket->used ) && ( id == bucket->id )) + if ((0u != bucket->used) && (id == bucket->id)) { - if ( bucket->par_num >= ePAR_NUM_OF ) + if (bucket->par_num >= ePAR_NUM_OF) { return ePAR_ERROR_PAR_NUM; } @@ -1334,24 +1212,21 @@ par_status_t par_get_num_by_id(const uint16_t id, par_num_t * const p_par_num) return ePAR_ERROR; } - -//////////////////////////////////////////////////////////////////////////////// /** -* Get parameter ID by number (enumeration) -* -* @param[in] par_num - Parameter number -* @param[out] p_id - Pointer to parameter ID -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Get parameter ID by number (enumeration). + * + * @param par_num Parameter number. + * @param p_id Pointer to parameter ID. + * @return Status of operation. + */ par_status_t par_get_id_by_num(const par_num_t par_num, uint16_t * const p_id) { - if ( NULL == p_id ) + if (NULL == p_id) { return ePAR_ERROR_PARAM; } - if ( par_num >= ePAR_NUM_OF ) + if (par_num >= ePAR_NUM_OF) { return ePAR_ERROR_PAR_NUM; } @@ -1359,7 +1234,7 @@ par_status_t par_get_id_by_num(const par_num_t par_num, uint16_t * const p_id) { const par_cfg_t * const par_cfg = par_get_config(par_num); - if ( NULL != par_cfg ) + if (NULL != par_cfg) { *p_id = par_cfg->id; return ePAR_OK; @@ -1370,327 +1245,301 @@ par_status_t par_get_id_by_num(const par_num_t par_num, uint16_t * const p_id) } #endif -#if ( 1 == PAR_CFG_NVM_EN ) - //////////////////////////////////////////////////////////////////////////////// - /** - * Is parameter value changed - * - * @param[in] par_num - Parameter number - * @param[in] p_val - Parameter value - * @return True if parameter value is different from current - */ - //////////////////////////////////////////////////////////////////////////////// - static par_status_t par_is_value_changed(const par_num_t par_num, const void * p_val, bool * const p_value_changed) +#if (1 == PAR_CFG_NVM_EN) +/** + * @brief Is parameter value changed. + * + * @param par_num Parameter number. + * @param p_val Parameter value. + * @return True if parameter value is different from current. + */ +static par_status_t par_is_value_changed(const par_num_t par_num, const void *p_val, bool * const p_value_changed) +{ + const par_cfg_t *par_cfg = NULL; + par_status_t status = ePAR_OK; + + if ((NULL == p_val) || (NULL == p_value_changed)) { - const par_cfg_t * par_cfg = NULL; - par_status_t status = ePAR_OK; + return ePAR_ERROR_PARAM; + } - if (( NULL == p_val ) || ( NULL == p_value_changed )) - { - return ePAR_ERROR_PARAM; - } + status = par_resolve_runtime(par_num, NULL, false, &par_cfg); + if (ePAR_OK != status) + { + return status; + } - status = par_resolve_runtime(par_num, NULL, false, &par_cfg); - if ( ePAR_OK != status ) - { + switch (par_cfg->type) + { + case ePAR_TYPE_U8: + { + uint8_t cur = 0U; + const par_status_t status = par_get_u8(par_num, &cur); + if (ePAR_OK != status) return status; - } - - switch ( par_cfg->type ) - { - case ePAR_TYPE_U8: - { - uint8_t cur = 0U; - const par_status_t status = par_get_u8(par_num, &cur); - if ( ePAR_OK != status ) return status; - *p_value_changed = (cur != *(const uint8_t*)p_val); - break; - } - - case ePAR_TYPE_I8: - { - int8_t cur = 0; - const par_status_t status = par_get_i8(par_num, &cur); - if ( ePAR_OK != status ) return status; - *p_value_changed = (cur != *(const int8_t*)p_val); - break; - } - - case ePAR_TYPE_U16: - { - uint16_t cur = 0U; - const par_status_t status = par_get_u16(par_num, &cur); - if ( ePAR_OK != status ) return status; - *p_value_changed = (cur != *(const uint16_t*)p_val); - break; - } - - case ePAR_TYPE_I16: - { - int16_t cur = 0; - const par_status_t status = par_get_i16(par_num, &cur); - if ( ePAR_OK != status ) return status; - *p_value_changed = (cur != *(const int16_t*)p_val); - break; - } - - case ePAR_TYPE_U32: - { - uint32_t cur = 0U; - const par_status_t status = par_get_u32(par_num, &cur); - if ( ePAR_OK != status ) return status; - *p_value_changed = (cur != *(const uint32_t*)p_val); - break; - } - - case ePAR_TYPE_I32: - { - int32_t cur = 0; - const par_status_t status = par_get_i32(par_num, &cur); - if ( ePAR_OK != status ) return status; - *p_value_changed = (cur != *(const int32_t*)p_val); - break; - } - - #if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) - case ePAR_TYPE_F32: - { - float32_t cur = 0.0f; - const par_status_t status = par_get_f32(par_num, &cur); - if ( ePAR_OK != status ) return status; - *p_value_changed = !par_f32_bits_equal(cur, *(const float32_t*)p_val); - break; - } - #endif - - case ePAR_TYPE_NUM_OF: - default: - PAR_ASSERT( 0 ); - return ePAR_ERROR_TYPE; - } - - return ePAR_OK; + *p_value_changed = (cur != *(const uint8_t *)p_val); + break; } - //////////////////////////////////////////////////////////////////////////////// - /** - * Set parameter value and save to NVM if value changed - * - * @note Mandatory to cast input argument to appropriate type. E.g.: - * - * @code - * float32_t my_val = 1.234f; - * par_set( ePAR_MY_VAR, (float32_t*) &my_val ); - * @endcode - * - * @note Input is parameter number (enumeration) defined in par_cfg.h and not - * parameter ID number! - * - * @param[in] par_num - Parameter number (enumeration) - * @param[in] p_val - Pointer to value - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - par_status_t par_set_n_save(const par_num_t par_num, const void * p_val) + + case ePAR_TYPE_I8: { - bool value_change = false; - par_status_t status = par_is_value_changed( par_num, p_val, &value_change ); + int8_t cur = 0; + const par_status_t status = par_get_i8(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_value_changed = (cur != *(const int8_t *)p_val); + break; + } - if ( ePAR_OK != status ) - { - PAR_DBG_PRINT( "PAR: failed to read current value before set_n_save for par_num=%u with status=%s", (unsigned) par_num, par_get_status_str( status )); - PAR_ASSERT( 0 ); + case ePAR_TYPE_U16: + { + uint16_t cur = 0U; + const par_status_t status = par_get_u16(par_num, &cur); + if (ePAR_OK != status) return status; - } + *p_value_changed = (cur != *(const uint16_t *)p_val); + break; + } - // Set parameter - status = par_set(par_num, p_val); + case ePAR_TYPE_I16: + { + int16_t cur = 0; + const par_status_t status = par_get_i16(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_value_changed = (cur != *(const int16_t *)p_val); + break; + } - // Parameter set OK and value has been changed -> makes sense to store to NVM - if (( ePAR_OK == status ) && value_change ) - { - status |= par_save(par_num); - } + case ePAR_TYPE_U32: + { + uint32_t cur = 0U; + const par_status_t status = par_get_u32(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_value_changed = (cur != *(const uint32_t *)p_val); + break; + } - return status; + case ePAR_TYPE_I32: + { + int32_t cur = 0; + const par_status_t status = par_get_i32(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_value_changed = (cur != *(const int32_t *)p_val); + break; } - //////////////////////////////////////////////////////////////////////////////// - /** - * Store all parameters value to NVM - * - * @pre NVM storage must be initialized first and "PAR_CFG_NVM_EN" - * settings must be enabled. - * - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - par_status_t par_save_all(void) +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + case ePAR_TYPE_F32: { - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; + float32_t cur = 0.0f; + const par_status_t status = par_get_f32(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_value_changed = !par_f32_bits_equal(cur, *(const float32_t *)p_val); + break; + } +#endif - return par_nvm_write_all(); + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + return ePAR_ERROR_TYPE; } - //////////////////////////////////////////////////////////////////////////////// - /** - * Store single parameter value to NVM - * - * @pre NVM storage must be initialized first and "PAR_CFG_NVM_EN" - * settings must be enabled. - * - * @param[in] par_num - Parameter number (enumeration) - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - par_status_t par_save(const par_num_t par_num) - { - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; + return ePAR_OK; +} +/** + * @brief Set parameter value and save to NVM if value changed. + * + * @note Mandatory to cast input argument to appropriate type. E.g.: + * + * @code + * float32_t my_val = 1.234f;. + * par_set( ePAR_MY_VAR, (float32_t*) &my_val );. + * @endcode + * + * @note Input is parameter number (enumeration) defined in par_cfg.h and not. + * parameter ID number! + * + * @param par_num Parameter number (enumeration). + * @param p_val Pointer to value. + * @return Status of operation. + */ +par_status_t par_set_n_save(const par_num_t par_num, const void *p_val) +{ + bool value_change = false; + par_status_t status = par_is_value_changed(par_num, p_val, &value_change); - return par_nvm_write( par_num, true ); + if (ePAR_OK != status) + { + PAR_DBG_PRINT("PAR: failed to read current value before set_n_save for par_num=%u with status=%s", (unsigned)par_num, par_get_status_str(status)); + PAR_ASSERT(0); + return status; } - //////////////////////////////////////////////////////////////////////////////// - /** - * Store single parameter value to NVM by its ID value - * - * @pre NVM storage must be initialized first and "PAR_CFG_NVM_EN" - * settings must be enabled. - * - * @code - * // Use case - * // Store par from ID 10 to 32 - * uint8_t par_id; - * - * for ( par_id = 10; par_id < 32; par_id++ ) - * { - * status |= par_save_by_id( par_id ) - * } - * - * @endcode - * - * @param[in] par_id - Parameter ID number - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// -#if ( 1 == PAR_CFG_ENABLE_ID ) - par_status_t par_save_by_id(const uint16_t par_id) + status = par_set(par_num, p_val); + /* Persist only when the value actually changed. */ + if ((ePAR_OK == status) && value_change) { - par_num_t par_num = 0; + status |= par_save(par_num); + } - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; + return status; +} +/** + * @brief Store all parameters value to NVM. + * + * @pre NVM storage must be initialized first and "PAR_CFG_NVM_EN" + * settings must be enabled. + * + * @return Status of operation. + */ +par_status_t par_save_all(void) +{ + PAR_ASSERT(true == par_is_init()); + if (true != par_is_init()) + return ePAR_ERROR_INIT; - if ( ePAR_OK == par_get_num_by_id( par_id, &par_num )) - { - return par_save( par_num ); - } + return par_nvm_write_all(); +} +/** + * @brief Store single parameter value to NVM. + * + * @pre NVM storage must be initialized first and "PAR_CFG_NVM_EN" + * settings must be enabled. + * + * @param par_num Parameter number (enumeration). + * @return Status of operation. + */ +par_status_t par_save(const par_num_t par_num) +{ + PAR_ASSERT(true == par_is_init()); + if (true != par_is_init()) + return ePAR_ERROR_INIT; - return ePAR_ERROR; - } -#endif + return par_nvm_write(par_num, true); +} +/** + * @brief Store single parameter value to NVM by its ID value. + * + * @pre NVM storage must be initialized first and "PAR_CFG_NVM_EN" + * settings must be enabled. + * + * @code + * // Use case. + * // Store par from ID 10 to 32. + * uint8_t par_id;. + * + * for ( par_id = 10; par_id < 32; par_id++ ). + * {. + * status |= par_save_by_id( par_id ). + * }. + * + * @endcode + * + * @param par_id Parameter ID number. + * @return Status of operation. + */ +#if (1 == PAR_CFG_ENABLE_ID) +par_status_t par_save_by_id(const uint16_t par_id) +{ + par_num_t par_num = 0; + PAR_ASSERT(true == par_is_init()); + if (true != par_is_init()) + return ePAR_ERROR_INIT; - //////////////////////////////////////////////////////////////////////////////// - /** - * Clean all stored parameters inside NVM - * - * @note This function shall be locked as it will erase complete parameter - * region of NVM space. Shall be used only during - * - * @pre NVM storage must be initialized first and "PAR_CFG_NVM_EN" - * settings must be enabled. - * - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - par_status_t par_save_clean(void) + if (ePAR_OK == par_get_num_by_id(par_id, &par_num)) { - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - return par_nvm_reset_all(); + return par_save(par_num); } + return ePAR_ERROR; +} #endif +/** + * @brief Clean all stored parameters inside NVM. + * + * @note This function shall be locked as it will erase complete parameter. + * region of NVM space. Shall be used only during. + * + * @pre NVM storage must be initialized first and "PAR_CFG_NVM_EN" + * settings must be enabled. + * + * @return Status of operation. + */ +par_status_t par_save_clean(void) +{ + PAR_ASSERT(true == par_is_init()); + if (true != par_is_init()) + return ePAR_ERROR_INIT; + + return par_nvm_reset_all(); +} -//////////////////////////////////////////////////////////////////////////////// +#endif /** -* Register parameter on change callback -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] cb - Callback -* @return void -*/ -//////////////////////////////////////////////////////////////////////////////// -#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) + * @brief Register parameter on change callback. + * + * @param par_num Parameter number (enumeration). + * @param cb Callback. + */ +#if (1 == PAR_CFG_ENABLE_CHANGE_CALLBACK) void par_register_on_change_cb(const par_num_t par_num, const pf_par_on_change_cb_t cb) { - PAR_ASSERT( par_num < ePAR_NUM_OF ); + PAR_ASSERT(par_num < ePAR_NUM_OF); g_par_cb_table[par_num].on_change = cb; } #endif - -//////////////////////////////////////////////////////////////////////////////// /** -* Register parameter value validation function -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] validation - Validation -* @return void -*/ -//////////////////////////////////////////////////////////////////////////////// -#if ( 1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION ) + * @brief Register parameter value validation function. + * + * @param par_num Parameter number (enumeration). + * @param validation Validation. + */ +#if (1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION) void par_register_validation(const par_num_t par_num, const pf_par_validation_t validation) { - PAR_ASSERT( par_num < ePAR_NUM_OF ); + PAR_ASSERT(par_num < ePAR_NUM_OF); g_par_cb_table[par_num].validation = validation; } #endif -#if ( PAR_CFG_DEBUG_EN ) - - //////////////////////////////////////////////////////////////////////////////// - /** - * Get status string description - * - * @param[in] status - Parameter status - * @return str - Parameter status description - */ - //////////////////////////////////////////////////////////////////////////////// - const char * par_get_status_str(const par_status_t status) - { - uint8_t i = 0; - const char * str = "N/A"; +#if (PAR_CFG_DEBUG_EN) +/** + * @brief Get status string description. + * + * @param status Parameter status. + * @return Parameter status description. + */ +const char *par_get_status_str(const par_status_t status) +{ + uint8_t i = 0; + const char *str = "N/A"; - if ( ePAR_OK == status ) - { - str = (const char*) gs_status[0]; - } - else + if (ePAR_OK == status) + { + str = (const char *)gs_status[0]; + } + else + { + for (i = 0; i < 16; i++) { - for ( i = 0; i < 16; i++ ) + if (status & (1 << i)) { - if ( status & ( 1< -*/ -//////////////////////////////////////////////////////////////////////////////// + * @} + */ diff --git a/src/par.h b/src/par.h index d80b345..360a74f 100644 --- a/src/par.h +++ b/src/par.h @@ -1,400 +1,681 @@ -// Copyright (c) 2026 Ziga Miklosic -// All Rights Reserved -// This software is under MIT licence (https://opensource.org/licenses/MIT) -//////////////////////////////////////////////////////////////////////////////// -/** -*@file par.h -*@brief Device parameters API functions -*@author Ziga Miklosic -*@email ziga.miklosic@gmail.com -*@author Matej Otic -*@email otic.matej@dancing-bits.com -*@date 29.01.2026 -*@version V3.0.1 -*/ -//////////////////////////////////////////////////////////////////////////////// -/** -*@addtogroup PARAMETERS_API -* @{ -*/ -//////////////////////////////////////////////////////////////////////////////// +/** + * @file par.h + * @brief Declare the public device-parameter API. + * @author Ziga Miklosic + * @version V3.0.1 + * @date 2026-01-29 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-01-29 V3.0.1 Ziga Miklosic + */ +/** + * @addtogroup PARAMETERS_API + * @{ + */ #ifndef _PAR_H_ #define _PAR_H_ - -//////////////////////////////////////////////////////////////////////////////// -// Includes -//////////////////////////////////////////////////////////////////////////////// +/** + * @brief Include dependencies. + */ #include #include #include #include "par_cfg.h" - -//////////////////////////////////////////////////////////////////////////////// -// Definitions -//////////////////////////////////////////////////////////////////////////////// - /** - * Module version + * @brief Compile-time definitions. */ -#define PAR_VER_MAJOR ( 3 ) -#define PAR_VER_MINOR ( 0 ) -#define PAR_VER_DEVELOP ( 2 ) +/** + * @brief Module version. + */ +#define PAR_VER_MAJOR (3) +#define PAR_VER_MINOR (0) +#define PAR_VER_DEVELOP (2) /** - * Parameter status + * @brief Parameter status. */ enum { - ePAR_OK = 0U, /** set PAR_CFG_NVM_EN to 0 */ - ePAR_WAR_LIMITED = 0x2000U, /** set PAR_CFG_NVM_EN to 0. */ + ePAR_WAR_LIMITED = 0x2000U, /**< Parameter value limited within [min,max]. */ }; typedef uint16_t par_status_t; - /** - * Parameters type enumeration + * @brief Parameters type enumeration. */ enum { - ePAR_TYPE_U8 = 0, /** -*/ -//////////////////////////////////////////////////////////////////////////////// + * @} + */ -#endif // _PAR_H_ +#endif /* _PAR_H_ */ diff --git a/src/par_atomic.h b/src/par_atomic.h index 9b02adc..8a29008 100644 --- a/src/par_atomic.h +++ b/src/par_atomic.h @@ -1,63 +1,59 @@ -// Copyright (c) 2026 Ziga Miklosic -// All Rights Reserved -// This software is under MIT licence (https://opensource.org/licenses/MIT) -//////////////////////////////////////////////////////////////////////////////// /** -*@file par_atomic.h -*@brief Atomic operations API macros and type declarations -*@author wdfk-prog -*@email 1425075683@qq.com -*@date 09.03.2026 -*@version V3.0.1 -*@details This header provides atomic type aliases and helper macros for load, -* store, fetch-and, and fetch-or operations. It supports either the -* C11 atomic backend or a port-specific backend selected by -* PAR_ATOMIC_BACKEND. -*/ + * @file par_atomic.h + * @brief Define atomic helper types and macros for parameter storage. + * @author wdfk-prog () + * @version 1.0 + * @date 2026-03-27 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-27 1.0 wdfk-prog first version + */ #ifndef PAR_ATOMIC_H #define PAR_ATOMIC_H #include - -//////////////////////////////////////////////////////////////////////////////// -// Definitions -//////////////////////////////////////////////////////////////////////////////// - /** - * C11 atomic backend selector + * @brief Compile-time definitions. */ -#define PAR_ATOMIC_BACKEND_C11 1 +/** + * @brief C11 atomic backend selector. + */ +#define PAR_ATOMIC_BACKEND_C11 1 /** - * Port-specific atomic backend selector + * @brief Port-specific atomic backend selector. */ -#define PAR_ATOMIC_BACKEND_PORT 2 +#define PAR_ATOMIC_BACKEND_PORT 2 #ifndef PAR_ATOMIC_BACKEND - /** - * Default atomic backend selection - */ - #define PAR_ATOMIC_BACKEND PAR_ATOMIC_BACKEND_C11 +/** + * @brief Default atomic backend selection. + */ +#define PAR_ATOMIC_BACKEND PAR_ATOMIC_BACKEND_C11 #endif /** - * Atomic shared-storage contract for backend implementers + * @brief Atomic shared-storage contract for backend implementers. * - * @note Backends used with static shared storage mode must guarantee identical - * object representation for the following type groups: - * - par_atomic_u8_t and par_atomic_i8_t - * - par_atomic_u16_t and par_atomic_i16_t - * - par_atomic_u32_t, par_atomic_i32_t and par_atomic_f32_t + * @note Backends used with static shared storage mode must guarantee identical. + * object representation for the following type groups: + * - par_atomic_u8_t and par_atomic_i8_t. + * - par_atomic_u16_t and par_atomic_i16_t. + * - par_atomic_u32_t, par_atomic_i32_t and par_atomic_f32_t. * - * @note If a port backend cannot satisfy this contract, static shared storage - * mode is not supported for that backend. + * @note If a port backend cannot satisfy this contract, static shared storage. + * mode is not supported for that backend. */ /** - * List of integral types supported by atomic load/store helpers + * @brief List of integral types supported by atomic load/store helpers. * - * @param[in] X - Macro invoked as X(tag, type) + * @param X Macro invoked as X(tag, type). */ #define PAR_ATOMIC_INTEGRAL_TYPE_LIST(X) \ X(u8, uint8_t) \ @@ -68,18 +64,18 @@ X(i32, int32_t) /** - * List of all scalar types supported by atomic helpers + * @brief List of all scalar types supported by atomic helpers. * - * @param[in] X - Macro invoked as X(tag, type) + * @param X Macro invoked as X(tag, type). */ -#define PAR_ATOMIC_TYPE_LIST(X) \ +#define PAR_ATOMIC_TYPE_LIST(X) \ PAR_ATOMIC_INTEGRAL_TYPE_LIST(X) \ X(f32, float) /** - * List of types supported by atomic fetch-and and fetch-or helpers + * @brief List of types supported by atomic fetch-and and fetch-or helpers. * - * @param[in] X - Macro invoked as X(tag, type) + * @param X Macro invoked as X(tag, type). */ #define PAR_ATOMIC_FETCH_TYPE_LIST(X) \ X(u8, uint8_t) \ @@ -90,53 +86,50 @@ #include - /** - * Declare atomic typedef for selected scalar type - * - * @param[in] tag - Type tag suffix - * @param[in] type - Scalar type wrapped by _Atomic - */ - #define PAR_ATOMIC_DECLARE_TYPE(tag, type) \ - typedef _Atomic type par_atomic_##tag##_t; +/** + * @brief Declare atomic typedef for selected scalar type. + * + * @param tag Type tag suffix. + * @param type Scalar type wrapped by _Atomic. + */ +#define PAR_ATOMIC_DECLARE_TYPE(tag, type) \ + typedef _Atomic type par_atomic_##tag##_t; PAR_ATOMIC_TYPE_LIST(PAR_ATOMIC_DECLARE_TYPE) - #undef PAR_ATOMIC_DECLARE_TYPE +#undef PAR_ATOMIC_DECLARE_TYPE - /** - * Define atomic load and store helper functions - * - * @param[in] tag - Type tag suffix - * @param[in] type - Scalar type of generated helpers - */ - #define PAR_ATOMIC_DEFINE_LOAD_STORE(tag, type) \ - static inline type par_atomic_load_##tag(const par_atomic_##tag##_t *ptr) \ - { \ - return atomic_load_explicit(ptr, memory_order_relaxed); \ - } \ - \ - static inline void par_atomic_store_##tag(par_atomic_##tag##_t *ptr, \ - type value) \ - { \ - atomic_store_explicit(ptr, value, memory_order_relaxed); \ - } +/** + * @brief Define atomic load and store helper functions. + * + * @param tag Type tag suffix. + * @param type Scalar type of generated helpers. + */ +#define PAR_ATOMIC_DEFINE_LOAD_STORE(tag, type) \ + static inline type par_atomic_load_##tag(const par_atomic_##tag##_t *ptr) \ + { \ + return atomic_load_explicit(ptr, memory_order_relaxed); \ + } \ + \ + static inline void par_atomic_store_##tag(par_atomic_##tag##_t *ptr, \ + type value) \ + { \ + atomic_store_explicit(ptr, value, memory_order_relaxed); \ + } PAR_ATOMIC_INTEGRAL_TYPE_LIST(PAR_ATOMIC_DEFINE_LOAD_STORE) - #undef PAR_ATOMIC_DEFINE_LOAD_STORE - -//////////////////////////////////////////////////////////////////////////////// +#undef PAR_ATOMIC_DEFINE_LOAD_STORE /** -* Load floating-point atomic value -* -* @note "atomic_load_explicit" does not support float data type in this -* implementation, therefore GCC/Clang built-in primitive -* "__atomic_load" is used instead. -* -* @param[in] ptr - Pointer to atomic floating-point object -* @return value - Current floating-point value -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Load floating-point atomic value. + * + * @note "atomic_load_explicit" does not support float data type in this. + * implementation, therefore GCC/Clang built-in primitive. + * "__atomic_load" is used instead. + * + * @param ptr Pointer to atomic floating-point object. + * @return Current floating-point value. + */ static inline float par_atomic_load_f32(const par_atomic_f32_t *ptr) { float value; @@ -145,58 +138,54 @@ static inline float par_atomic_load_f32(const par_atomic_f32_t *ptr) return value; } - -//////////////////////////////////////////////////////////////////////////////// /** -* Store floating-point atomic value -* -* @note "atomic_store_explicit" does not support float data type in this -* implementation, therefore GCC/Clang built-in primitive -* "__atomic_store" is used instead. -* -* @param[in] ptr - Pointer to atomic floating-point object -* @param[in] value - Value to store -* @return void -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Store floating-point atomic value. + * + * @note "atomic_store_explicit" does not support float data type in this. + * implementation, therefore GCC/Clang built-in primitive. + * "__atomic_store" is used instead. + * + * @param ptr Pointer to atomic floating-point object. + * @param value Value to store. + */ static inline void par_atomic_store_f32(par_atomic_f32_t *ptr, float value) { __atomic_store(ptr, &value, __ATOMIC_RELAXED); } - /** - * Define atomic fetch-and helper function - * - * @param[in] tag - Type tag suffix - * @param[in] type - Scalar type of generated helper - */ - #define PAR_ATOMIC_DEFINE_FETCH_AND(tag, type) \ - static inline type par_atomic_fetch_and_##tag(par_atomic_##tag##_t *ptr, \ - type value) \ - { \ - return atomic_fetch_and_explicit(ptr, value, memory_order_relaxed); \ - } +/** + * @brief Define atomic fetch-and helper function. + * + * @param tag Type tag suffix. + * @param type Scalar type of generated helper. + */ +#define PAR_ATOMIC_DEFINE_FETCH_AND(tag, type) \ + static inline type par_atomic_fetch_and_##tag(par_atomic_##tag##_t *ptr, \ + type value) \ + { \ + return atomic_fetch_and_explicit(ptr, value, memory_order_relaxed); \ + } PAR_ATOMIC_FETCH_TYPE_LIST(PAR_ATOMIC_DEFINE_FETCH_AND) - #undef PAR_ATOMIC_DEFINE_FETCH_AND +#undef PAR_ATOMIC_DEFINE_FETCH_AND - /** - * Define atomic fetch-or helper function - * - * @param[in] tag - Type tag suffix - * @param[in] type - Scalar type of generated helper - */ - #define PAR_ATOMIC_DEFINE_FETCH_OR(tag, type) \ - static inline type par_atomic_fetch_or_##tag(par_atomic_##tag##_t *ptr, \ - type value) \ - { \ - return atomic_fetch_or_explicit(ptr, value, memory_order_relaxed); \ - } +/** + * @brief Define atomic fetch-or helper function. + * + * @param tag Type tag suffix. + * @param type Scalar type of generated helper. + */ +#define PAR_ATOMIC_DEFINE_FETCH_OR(tag, type) \ + static inline type par_atomic_fetch_or_##tag(par_atomic_##tag##_t *ptr, \ + type value) \ + { \ + return atomic_fetch_or_explicit(ptr, value, memory_order_relaxed); \ + } PAR_ATOMIC_FETCH_TYPE_LIST(PAR_ATOMIC_DEFINE_FETCH_OR) - #undef PAR_ATOMIC_DEFINE_FETCH_OR +#undef PAR_ATOMIC_DEFINE_FETCH_OR #elif (PAR_ATOMIC_BACKEND == PAR_ATOMIC_BACKEND_PORT) @@ -204,56 +193,43 @@ PAR_ATOMIC_FETCH_TYPE_LIST(PAR_ATOMIC_DEFINE_FETCH_OR) #else - #error "Unsupported PAR_ATOMIC_BACKEND" +#error "Unsupported PAR_ATOMIC_BACKEND" #endif - -//////////////////////////////////////////////////////////////////////////////// /** -* Load atomic value by type tag -* -* @param[in] tag - Type tag suffix -* @param[in] ptr - Pointer to atomic object -* @return value - Current atomic value -*/ -//////////////////////////////////////////////////////////////////////////////// -#define PAR_ATOMIC_LOAD(tag, ptr) par_atomic_load_##tag((ptr)) - -//////////////////////////////////////////////////////////////////////////////// + * @brief Load atomic value by type tag. + * + * @param tag Type tag suffix. + * @param ptr Pointer to atomic object. + * @return Current atomic value. + */ +#define PAR_ATOMIC_LOAD(tag, ptr) par_atomic_load_##tag((ptr)) /** -* Store atomic value by type tag -* -* @param[in] tag - Type tag suffix -* @param[in] ptr - Pointer to atomic object -* @param[in] value - Value to store -* @return void -*/ -//////////////////////////////////////////////////////////////////////////////// -#define PAR_ATOMIC_STORE(tag, ptr, value) par_atomic_store_##tag((ptr), (value)) - -//////////////////////////////////////////////////////////////////////////////// + * @brief Store atomic value by type tag. + * + * @param tag Type tag suffix. + * @param ptr Pointer to atomic object. + * @param value Value to store. + */ +#define PAR_ATOMIC_STORE(tag, ptr, value) par_atomic_store_##tag((ptr), (value)) /** -* Perform atomic fetch-and by type tag -* -* @param[in] tag - Type tag suffix -* @param[in] ptr - Pointer to atomic object -* @param[in] value - Operand for bitwise AND -* @return value - Previous atomic value -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Perform atomic fetch-and by type tag. + * + * @param tag Type tag suffix. + * @param ptr Pointer to atomic object. + * @param value Operand for bitwise AND. + * @return Previous atomic value. + */ #define PAR_ATOMIC_FETCH_AND(tag, ptr, value) \ par_atomic_fetch_and_##tag((ptr), (value)) - -//////////////////////////////////////////////////////////////////////////////// /** -* Perform atomic fetch-or by type tag -* -* @param[in] tag - Type tag suffix -* @param[in] ptr - Pointer to atomic object -* @param[in] value - Operand for bitwise OR -* @return value - Previous atomic value -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Perform atomic fetch-or by type tag. + * + * @param tag Type tag suffix. + * @param ptr Pointer to atomic object. + * @param value Operand for bitwise OR. + * @return Previous atomic value. + */ #define PAR_ATOMIC_FETCH_OR(tag, ptr, value) \ par_atomic_fetch_or_##tag((ptr), (value)) diff --git a/src/par_bitwise_impl.inc b/src/par_bitwise_impl.inc index 50ce16b..94d0a1c 100644 --- a/src/par_bitwise_impl.inc +++ b/src/par_bitwise_impl.inc @@ -1,10 +1,24 @@ -/* - * Private include fragment. Included only by par.c. +/** + * @file par_bitwise_impl.inc + * @brief Generate bitwise fast setter implementations. + * @author wdfk-prog () + * @version 1.0 + * @date 2026-03-27 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-27 1.0 wdfk-prog first version */ +/** + * @brief Private include fragment. Included only by par.c. + */ -#define PAR_FOR_EACH_BITWISE_OP(M, ...) \ - M(bitand, AND, __VA_ARGS__) \ +#define PAR_FOR_EACH_BITWISE_OP(M, ...) \ + M(bitand, AND, __VA_ARGS__) \ M(bitor, OR, __VA_ARGS__) #define PAR_BITWISE_FAST_SET_BODY(ATOMIC_OP, NAME, FIELD, STORAGE, par_num, val) \ do \ diff --git a/src/par_cfg.h b/src/par_cfg.h index 501ccc7..2c6ea1e 100644 --- a/src/par_cfg.h +++ b/src/par_cfg.h @@ -1,429 +1,450 @@ -// Copyright (c) 2026 Ziga Miklosic -// All Rights Reserved -// This software is under MIT licence (https://opensource.org/licenses/MIT) -//////////////////////////////////////////////////////////////////////////////// -/** -*@file par_cfg.h -*@brief Configuration for device parameters -*@author Ziga Miklosic -*@email ziga.miklosic@gmail.com -*@date 29.01.2026 -*@version V3.0.1 -*/ -//////////////////////////////////////////////////////////////////////////////// -/** -*@addtogroup PAR_CFG -* @{ -* -* Configuration for device parameters. -*/ -//////////////////////////////////////////////////////////////////////////////// +/** + * @file par_cfg.h + * @brief Provide compile-time configuration for the parameter module. + * @author Ziga Miklosic + * @version 1.0 + * @date 2026-03-27 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-01-29 V3.0.1 Ziga Miklosic + */ + +/** + * @addtogroup PAR_CFG + * @{ + * + * @brief Configuration for device parameters. + */ #ifndef _PAR_CFG_H_ #define _PAR_CFG_H_ - -//////////////////////////////////////////////////////////////////////////////// -// Includes -//////////////////////////////////////////////////////////////////////////////// +/** + * @brief Include dependencies. + */ #include #include #include #include "par_def.h" -// USER CODE BEGIN... +/** + * @brief USER CODE BEGIN... + */ /** - * Platform adaptation bridge + * @brief Platform adaptation bridge. * - * @note Port layer may override default PAR_CFG_* settings. + * @note Port layer may override default PAR_CFG_* settings. * - * @note This header is included unconditionally. - * Integrator shall provide "par_cfg_port.h" in include path. - * If no platform override is needed, create an empty stub header - * with include guard (for example in port/par_cfg_port.h). + * @note This header is included unconditionally. + * Integrator shall provide "par_cfg_port.h" in include path. + * If no platform override is needed, create an empty stub header. + * with include guard (for example in port/par_cfg_port.h). */ #include "par_cfg_port.h" -// USER CODE END... - -//////////////////////////////////////////////////////////////////////////////// -// Definitions -//////////////////////////////////////////////////////////////////////////////// - /** - * Enable/Disable storing persistent parameters to NVM + * @brief USER CODE END... + */ +/** + * @brief Compile-time definitions. + */ +/** + * @brief Enable/Disable storing persistent parameters to NVM. */ #ifndef PAR_CFG_NVM_EN - #define PAR_CFG_NVM_EN ( 1 ) +#define PAR_CFG_NVM_EN (1) #endif /** - * NVM parameter region option + * @brief NVM parameter region option. * - * @note User shall select region based on nvm_cfg.h region - * definitions "nvm_region_name_t". + * @note User shall select region based on nvm_cfg.h region. + * definitions "nvm_region_name_t". * - * Don't care if "PAR_CFG_NVM_EN" set to 0. + * Don't care if "PAR_CFG_NVM_EN" set to 0. */ #ifndef PAR_CFG_NVM_REGION - #define PAR_CFG_NVM_REGION ( eNVM_REGION_INT_FLASH_DEV_PAR ) +#define PAR_CFG_NVM_REGION (eNVM_REGION_INT_FLASH_DEV_PAR) #endif /** - * Enable/Disable parameter table unique ID checking + * @brief Enable/Disable parameter table unique ID checking. * - * @note Base on hash unique ID is being calculated with purpose to detect - * device and stored parameter table difference. + * @note Base on hash unique ID is being calculated with purpose to detect. + * device and stored parameter table difference. * - * Must be disabled once the device is released in order to prevent - * loss of calibrated data stored in NVM. + * Must be disabled once the device is released in order to prevent. + * loss of calibrated data stored in NVM. * * @pre "PAR_CFG_NVM_EN" must be enabled otherwise it does not make sense - * to calculate ID at all. + * to calculate ID at all. */ #ifndef PAR_CFG_TABLE_ID_CHECK_EN - #define PAR_CFG_TABLE_ID_CHECK_EN ( 0 ) +#define PAR_CFG_TABLE_ID_CHECK_EN (0) +#if (1 == PAR_CFG_NVM_EN) +#ifndef DEBUG +#undef PAR_CFG_TABLE_ID_CHECK_EN +#define PAR_CFG_TABLE_ID_CHECK_EN (0) +#endif #endif - -#if ( 1 == PAR_CFG_NVM_EN ) - #ifndef DEBUG - #undef PAR_CFG_TABLE_ID_CHECK_EN - #define PAR_CFG_TABLE_ID_CHECK_EN ( 0 ) - #endif #endif /** - * Enable/Disable debug mode + * @brief Enable/Disable debug mode. */ #ifndef PAR_CFG_DEBUG_EN - #define PAR_CFG_DEBUG_EN ( 1 ) -#endif - +#define PAR_CFG_DEBUG_EN (1) #ifndef DEBUG - #undef PAR_CFG_DEBUG_EN - #define PAR_CFG_DEBUG_EN ( 0 ) +#undef PAR_CFG_DEBUG_EN +#define PAR_CFG_DEBUG_EN (0) +#endif #endif /** - * Enable/Disable assertions + * @brief Enable/Disable assertions. */ #ifndef PAR_CFG_ASSERT_EN - #define PAR_CFG_ASSERT_EN ( 1 ) -#endif - +#define PAR_CFG_ASSERT_EN (1) #ifndef DEBUG - #undef PAR_CFG_ASSERT_EN - #define PAR_CFG_ASSERT_EN ( 0 ) +#undef PAR_CFG_ASSERT_EN +#define PAR_CFG_ASSERT_EN (0) +#endif #endif /** - * Platform hook fallbacks + * @brief Platform hook fallbacks. */ #ifndef PAR_PORT_ASSERT - #define PAR_PORT_ASSERT(x) do { (void)(x); } while (0) +#define PAR_PORT_ASSERT(x) \ + do \ + { \ + (void)(x); \ + } while (0) #endif #ifndef PAR_PORT_LOG - #define PAR_PORT_LOG(tag, ...) do { (void)(tag); } while (0) +#define PAR_PORT_LOG(tag, ...) \ + do \ + { \ + (void)(tag); \ + } while (0) #endif #ifndef PAR_PORT_STATIC_ASSERT - #define PAR_PORT_STATIC_ASSERT(name, expn) typedef char _static_assert_##name[(expn) ? 1 : -1] +#define PAR_PORT_STATIC_ASSERT(name, expn) typedef char _static_assert_##name[(expn) ? 1 : -1] #endif /** - * Platform weak symbol macro + * @brief Platform weak symbol macro. * - * @note Integrator may override this macro (for example: RT_WEAK). + * @note Integrator may override this macro (for example: RT_WEAK). */ #ifndef PAR_PORT_WEAK - #define PAR_PORT_WEAK __attribute__((weak)) +#define PAR_PORT_WEAK __attribute__((weak)) #endif /** - * Type alignment abstraction + * @brief Type alignment abstraction. * - * @note Default uses C11 _Alignof. If unavailable, falls back to - * offsetof-based alignment calculation. - * @note This abstraction is intended for ordinary object types and platform - * atomic-wrapper types, but the platform must guarantee the expression - * is valid for its custom atomic wrapper definitions. + * @note Default uses C11 _Alignof. If unavailable, falls back to. + * offsetof-based alignment calculation. + * @note This abstraction is intended for ordinary object types and platform. + * atomic-wrapper types, but the platform must guarantee the expression. + * is valid for its custom atomic wrapper definitions. */ #ifndef PAR_ALIGNOF - #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) - #define PAR_ALIGNOF(type) _Alignof(type) - #else - #define PAR_ALIGNOF(type) offsetof(struct { char _par_align_c; type _par_align_t; }, _par_align_t) - #endif +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +#define PAR_ALIGNOF(type) _Alignof(type) +#else +#define PAR_ALIGNOF(type) offsetof(struct { char _par_align_c; type _par_align_t; }, _par_align_t) +#endif #endif /** - * Package compile-time assert + * @brief Package compile-time assert. */ -#define PAR_STATIC_ASSERT(name, expn) PAR_PORT_STATIC_ASSERT(name, expn); - +#define PAR_STATIC_ASSERT(name, expn) PAR_PORT_STATIC_ASSERT(name, expn); /** - * Resolve log/assert routing mode before default macro emission + * @brief Resolve log/assert routing mode before default macro emission. */ #if !defined(PAR_CFG_PORT_HOOK_EN) - #define PAR_CFG_USE_PORT_HOOKS ( 0 ) -#elif ( 1 == PAR_CFG_PORT_HOOK_EN ) - #define PAR_CFG_USE_PORT_HOOKS ( 1 ) +#define PAR_CFG_USE_PORT_HOOKS (0) +#elif (1 == PAR_CFG_PORT_HOOK_EN) +#define PAR_CFG_USE_PORT_HOOKS (1) +#else +#define PAR_CFG_USE_PORT_HOOKS (0) +#endif + +/** + * @brief Debug communication port macros. + */ +#if (0 == PAR_CFG_USE_PORT_HOOKS) +#if (1 == PAR_CFG_DEBUG_EN) +#ifndef PAR_CFG_DIRECT_LOG +#define PAR_CFG_DIRECT_LOG(...) (cli_printf((char *)__VA_ARGS__)) +#endif +#define PAR_DBG_PRINT(...) PAR_CFG_DIRECT_LOG(__VA_ARGS__) +#else +#define PAR_DBG_PRINT(...) \ + { \ + ; \ + } +#endif +#else +#if (1 == PAR_CFG_DEBUG_EN) +#define PAR_DBG_PRINT(...) PAR_PORT_LOG(__VA_ARGS__) #else - #define PAR_CFG_USE_PORT_HOOKS ( 0 ) +#define PAR_DBG_PRINT(...) \ + { \ + ; \ + } +#endif #endif /** - * Debug communication port macros + * @brief Assertion macros. */ -#if ( 0 == PAR_CFG_USE_PORT_HOOKS ) - #if ( 1 == PAR_CFG_DEBUG_EN ) - #ifndef PAR_CFG_DIRECT_LOG - #define PAR_CFG_DIRECT_LOG(...) ( cli_printf((char *) __VA_ARGS__) ) - #endif - #define PAR_DBG_PRINT(...) PAR_CFG_DIRECT_LOG(__VA_ARGS__) - #else - #define PAR_DBG_PRINT(...) { ; } - #endif +#if (0 == PAR_CFG_USE_PORT_HOOKS) +#if (1 == PAR_CFG_ASSERT_EN) +#ifndef PAR_CFG_DIRECT_ASSERT +#ifdef PROJ_CFG_ASSERT +#define PAR_CFG_DIRECT_ASSERT(x) PROJ_CFG_ASSERT(x) #else - #if ( 1 == PAR_CFG_DEBUG_EN ) - #define PAR_DBG_PRINT(...) PAR_PORT_LOG(__VA_ARGS__) - #else - #define PAR_DBG_PRINT(...) { ; } - #endif -#endif - -/** - * Assertion macros - */ -#if ( 0 == PAR_CFG_USE_PORT_HOOKS ) - #if ( 1 == PAR_CFG_ASSERT_EN ) - #ifndef PAR_CFG_DIRECT_ASSERT - #ifdef PROJ_CFG_ASSERT - #define PAR_CFG_DIRECT_ASSERT(x) PROJ_CFG_ASSERT(x) - #else - #define PAR_CFG_DIRECT_ASSERT(x) do { (void)(x); } while (0) - #endif - #endif - #define PAR_ASSERT(x) PAR_CFG_DIRECT_ASSERT(x) - #else - #define PAR_ASSERT(x) { ; } - #endif +#define PAR_CFG_DIRECT_ASSERT(x) \ + do \ + { \ + (void)(x); \ + } while (0) +#endif +#endif +#define PAR_ASSERT(x) PAR_CFG_DIRECT_ASSERT(x) #else - #if ( 1 == PAR_CFG_ASSERT_EN ) - #define PAR_ASSERT(x) PAR_PORT_ASSERT(x) - #else - #define PAR_ASSERT(x) { ; } - #endif +#define PAR_ASSERT(x) \ + { \ + ; \ + } +#endif +#else +#if (1 == PAR_CFG_ASSERT_EN) +#define PAR_ASSERT(x) PAR_PORT_ASSERT(x) +#else +#define PAR_ASSERT(x) \ + { \ + ; \ + } +#endif #endif #undef PAR_CFG_USE_PORT_HOOKS /** - * Invalid configuration catcher + * @brief Invalid configuration catcher. * - * @note Shall be intact by end user! + * @note Shall be intact by end user! */ -#if ( 0 == PAR_CFG_NVM_EN ) && ( 1 == PAR_CFG_TABLE_ID_CHECK_EN ) - #error "Parameter settings invalid: Disable table ID checking (PAR_CFG_TABLE_ID_CHECK_EN)!" +#if (0 == PAR_CFG_NVM_EN) && (1 == PAR_CFG_TABLE_ID_CHECK_EN) +#error "Parameter settings invalid: Disable table ID checking (PAR_CFG_TABLE_ID_CHECK_EN)!" #endif /** - * Extended package configurations (non-template additions) + * @brief Extended package configurations (non-template additions). */ #ifndef PAR_CFG_MUTEX_EN - #define PAR_CFG_MUTEX_EN ( 1 ) +#define PAR_CFG_MUTEX_EN (1) #endif /** - * Parameter mutex timeout - * - * Unit: ms + * @brief Parameter mutex timeout. + * @details Unit: ms. */ #ifndef PAR_CFG_MUTEX_TIMEOUT_MS - #define PAR_CFG_MUTEX_TIMEOUT_MS ( 10 ) +#define PAR_CFG_MUTEX_TIMEOUT_MS (10) #endif /** - * Enable/Disable port-specific par_if backend + * @brief Enable/Disable port-specific par_if backend. */ #ifndef PAR_CFG_IF_PORT_EN - #define PAR_CFG_IF_PORT_EN ( 0 ) +#define PAR_CFG_IF_PORT_EN (0) #endif /** - * Enable/Disable port hooks for log/assert + * @brief Enable/Disable port hooks for log/assert. */ #ifndef PAR_CFG_PORT_HOOK_EN - #define PAR_CFG_PORT_HOOK_EN ( 0 ) +#define PAR_CFG_PORT_HOOK_EN (0) #endif /** - * Parameter storage layout source + * @brief Parameter storage layout source. */ -#define PAR_CFG_LAYOUT_COMPILE_SCAN ( 0u ) -#define PAR_CFG_LAYOUT_SCRIPT ( 1u ) +#define PAR_CFG_LAYOUT_COMPILE_SCAN (0u) +#define PAR_CFG_LAYOUT_SCRIPT (1u) /** - * Select parameter storage layout source + * @brief Select parameter storage layout source. * * @note - * - COMPILE_SCAN: counts are compile-time constants, offsets are scanned in init - * - SCRIPT : counts/offsets are provided by generated static layout header + * - COMPILE_SCAN: counts are compile-time constants, offsets are scanned in init. + * - SCRIPT : counts/offsets are provided by generated static layout header. */ #ifndef PAR_CFG_LAYOUT_SOURCE - #define PAR_CFG_LAYOUT_SOURCE PAR_CFG_LAYOUT_COMPILE_SCAN +#define PAR_CFG_LAYOUT_SOURCE PAR_CFG_LAYOUT_COMPILE_SCAN #endif /** - * Static layout include path + * @brief Static layout include path. * * @note Can be overridden by integrator to include generated layout header. */ #ifndef PAR_CFG_LAYOUT_STATIC_INCLUDE - #define PAR_CFG_LAYOUT_STATIC_INCLUDE "par_layout_static.h" +#define PAR_CFG_LAYOUT_STATIC_INCLUDE "par_layout_static.h" #endif #ifndef PAR_CFG_ENABLE_TYPE_F32 - #define PAR_CFG_ENABLE_TYPE_F32 ( 1 ) +#define PAR_CFG_ENABLE_TYPE_F32 (1) #endif /** - * Enable/Disable runtime validation callbacks in normal setters + * @brief Enable/Disable runtime validation callbacks in normal setters. */ #ifndef PAR_CFG_ENABLE_RUNTIME_VALIDATION - #define PAR_CFG_ENABLE_RUNTIME_VALIDATION ( 1 ) +#define PAR_CFG_ENABLE_RUNTIME_VALIDATION (1) #endif /** - * Enable/Disable on-change callbacks in normal setters + * @brief Enable/Disable on-change callbacks in normal setters. */ #ifndef PAR_CFG_ENABLE_CHANGE_CALLBACK - #define PAR_CFG_ENABLE_CHANGE_CALLBACK ( 1 ) +#define PAR_CFG_ENABLE_CHANGE_CALLBACK (1) #endif /** - * Enable/Disable raw reset-all API and default mirror storage + * @brief Enable/Disable raw reset-all API and default mirror storage. * - * @note When enabled, module compiles par_reset_all_to_default_raw() and - * keeps a grouped default mirror snapshot for raw restore. + * @note When enabled, module compiles par_reset_all_to_default_raw() and. + * keeps a grouped default mirror snapshot for raw restore. * - * @note The speedup comes from bypassing per-parameter setter-side logic such - * as runtime validation, change callback, and range handling. + * @note The speedup comes from bypassing per-parameter setter-side logic such. + * as runtime validation, change callback, and range handling. * - * @note The raw reset path restores parameter storage directly from that - * grouped default mirror snapshot, so it is typically faster than - * par_set_all_to_default(), which resets parameters one by one through - * the normal runtime setter path. + * @note The raw reset path restores parameter storage directly from that. + * grouped default mirror snapshot, so it is typically faster than. + * par_set_all_to_default(), which resets parameters one by one through. + * the normal runtime setter path. */ #ifndef PAR_CFG_ENABLE_RESET_ALL_RAW - #define PAR_CFG_ENABLE_RESET_ALL_RAW ( 1 ) +#define PAR_CFG_ENABLE_RESET_ALL_RAW (1) #endif /** - * Enable/Disable parameter range metadata (min/max) + * @brief Enable/Disable parameter range metadata (min/max). */ #ifndef PAR_CFG_ENABLE_RANGE - #define PAR_CFG_ENABLE_RANGE ( 1 ) +#define PAR_CFG_ENABLE_RANGE (1) #endif /** - * Enable/Disable parameter name metadata + * @brief Enable/Disable parameter name metadata. */ #ifndef PAR_CFG_ENABLE_NAME - #define PAR_CFG_ENABLE_NAME ( 1 ) +#define PAR_CFG_ENABLE_NAME (1) #endif /** - * Enable/Disable parameter unit metadata + * @brief Enable/Disable parameter unit metadata. */ #ifndef PAR_CFG_ENABLE_UNIT - #define PAR_CFG_ENABLE_UNIT ( 1 ) +#define PAR_CFG_ENABLE_UNIT (1) #endif /** - * Enable/Disable parameter description metadata + * @brief Enable/Disable parameter description metadata. */ #ifndef PAR_CFG_ENABLE_DESC - #define PAR_CFG_ENABLE_DESC ( 1 ) +#define PAR_CFG_ENABLE_DESC (1) #endif /** - * Enable/Disable parameter ID metadata + * @brief Enable/Disable parameter ID metadata. */ #ifndef PAR_CFG_ENABLE_ID - #define PAR_CFG_ENABLE_ID ( 1 ) +#define PAR_CFG_ENABLE_ID (1) #endif -#if ( 1 == PAR_CFG_ENABLE_ID ) +#if (1 == PAR_CFG_ENABLE_ID) /** - * Enable/Disable optional runtime duplicate-ID diagnostic scan. + * @brief Enable/Disable optional runtime duplicate-ID diagnostic scan. * - * @note Static ID-map generation and compile-time duplicate-ID checking remain - * enabled by default whenever PAR_CFG_ENABLE_ID = 1. + * @note Static ID-map generation and compile-time duplicate-ID checking remain. + * enabled by default whenever PAR_CFG_ENABLE_ID = 1. */ #ifndef PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK - #define PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK ( 0 ) +#define PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK (0) #endif /** - * Enable/Disable optional runtime ID hash-collision diagnostic scan. + * @brief Enable/Disable optional runtime ID hash-collision diagnostic scan. * - * @note Static ID-map generation and compile-time hash collision checking - * remain enabled by default whenever PAR_CFG_ENABLE_ID = 1. + * @note Static ID-map generation and compile-time hash collision checking. + * remain enabled by default whenever PAR_CFG_ENABLE_ID = 1. */ #ifndef PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK - #define PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK ( 0 ) +#define PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK (0) #endif /** - * Internal-only ID hash geometry helpers. + * @brief Internal-only ID hash geometry helpers. * - * @note Shared by the compile-time table checks, compile-time static ID-map - * generation, and optional runtime diagnostic scans. + * @note Shared by the compile-time table checks, compile-time static ID-map. + * generation, and optional runtime diagnostic scans. * - * @note These helpers define the internal geometry of the core ID lookup map. - * Integrators should not treat them as a stable public extension API. + * @note These helpers define the internal geometry of the core ID lookup map. + * Integrators should not treat them as a stable public extension API. */ #ifndef PAR_ID_HASH_GOLDEN_RATIO_32 - #define PAR_ID_HASH_GOLDEN_RATIO_32 ( 0x61C88647u ) +#define PAR_ID_HASH_GOLDEN_RATIO_32 (0x61C88647u) #endif #ifndef PAR_ID_HASH_MIN_BUCKETS - #define PAR_ID_HASH_MIN_BUCKETS ((uint32_t)(2u * (uint32_t)ePAR_NUM_OF)) +#define PAR_ID_HASH_MIN_BUCKETS ((uint32_t)(2u * (uint32_t)ePAR_NUM_OF)) #endif #ifndef PAR_ID_HASH_BITS_FROM_MIN_BUCKETS - #define PAR_ID_HASH_BITS_FROM_MIN_BUCKETS(min_buckets_) \ - (((min_buckets_) <= ( 1u << 1 )) ? 1u : \ - ((min_buckets_) <= ( 1u << 2 )) ? 2u : \ - ((min_buckets_) <= ( 1u << 3 )) ? 3u : \ - ((min_buckets_) <= ( 1u << 4 )) ? 4u : \ - ((min_buckets_) <= ( 1u << 5 )) ? 5u : \ - ((min_buckets_) <= ( 1u << 6 )) ? 6u : \ - ((min_buckets_) <= ( 1u << 7 )) ? 7u : \ - ((min_buckets_) <= ( 1u << 8 )) ? 8u : \ - ((min_buckets_) <= ( 1u << 9 )) ? 9u : \ - ((min_buckets_) <= ( 1u << 10 )) ? 10u : \ - ((min_buckets_) <= ( 1u << 11 )) ? 11u : \ - ((min_buckets_) <= ( 1u << 12 )) ? 12u : \ - ((min_buckets_) <= ( 1u << 13 )) ? 13u : \ - ((min_buckets_) <= ( 1u << 14 )) ? 14u : \ - ((min_buckets_) <= ( 1u << 15 )) ? 15u : \ - ((min_buckets_) <= ( 1u << 16 )) ? 16u : \ - ((min_buckets_) <= ( 1u << 17 )) ? 17u : 18u) +#define PAR_ID_HASH_BITS_FROM_MIN_BUCKETS(min_buckets_) \ + (((min_buckets_) <= (1u << 1)) ? 1u : ((min_buckets_) <= (1u << 2)) ? 2u \ + : ((min_buckets_) <= (1u << 3)) ? 3u \ + : ((min_buckets_) <= (1u << 4)) ? 4u \ + : ((min_buckets_) <= (1u << 5)) ? 5u \ + : ((min_buckets_) <= (1u << 6)) ? 6u \ + : ((min_buckets_) <= (1u << 7)) ? 7u \ + : ((min_buckets_) <= (1u << 8)) ? 8u \ + : ((min_buckets_) <= (1u << 9)) ? 9u \ + : ((min_buckets_) <= (1u << 10)) ? 10u \ + : ((min_buckets_) <= (1u << 11)) ? 11u \ + : ((min_buckets_) <= (1u << 12)) ? 12u \ + : ((min_buckets_) <= (1u << 13)) ? 13u \ + : ((min_buckets_) <= (1u << 14)) ? 14u \ + : ((min_buckets_) <= (1u << 15)) ? 15u \ + : ((min_buckets_) <= (1u << 16)) ? 16u \ + : ((min_buckets_) <= (1u << 17)) ? 17u \ + : 18u) #endif #ifndef PAR_ID_HASH_BITS - #define PAR_ID_HASH_BITS PAR_ID_HASH_BITS_FROM_MIN_BUCKETS(PAR_ID_HASH_MIN_BUCKETS) +#define PAR_ID_HASH_BITS PAR_ID_HASH_BITS_FROM_MIN_BUCKETS(PAR_ID_HASH_MIN_BUCKETS) #endif #ifndef PAR_ID_HASH_SIZE - #define PAR_ID_HASH_SIZE ( 1u << PAR_ID_HASH_BITS ) +#define PAR_ID_HASH_SIZE (1u << PAR_ID_HASH_BITS) #endif #ifndef PAR_HASH_ID_CONST - #define PAR_HASH_ID_CONST(id_) ((((uint32_t)(id_)) * PAR_ID_HASH_GOLDEN_RATIO_32) >> (32u - PAR_ID_HASH_BITS)) +#define PAR_HASH_ID_CONST(id_) ((((uint32_t)(id_)) * PAR_ID_HASH_GOLDEN_RATIO_32) >> (32u - PAR_ID_HASH_BITS)) #endif PAR_STATIC_ASSERT(par_id_hash_size_valid, (PAR_ID_HASH_SIZE >= PAR_ID_HASH_MIN_BUCKETS)); @@ -431,64 +452,61 @@ PAR_STATIC_ASSERT(par_id_hash_bits_valid, ((PAR_ID_HASH_BITS > 0u) && (PAR_ID_HA #endif /** - * Enable/Disable parameter access metadata + * @brief Enable/Disable parameter access metadata. */ #ifndef PAR_CFG_ENABLE_ACCESS - #define PAR_CFG_ENABLE_ACCESS ( 1 ) +#define PAR_CFG_ENABLE_ACCESS (1) #endif /** - * Enable/Disable parameter persistence metadata + * @brief Enable/Disable parameter persistence metadata. */ #ifndef PAR_CFG_ENABLE_PERSIST - #define PAR_CFG_ENABLE_PERSIST ( 1 ) +#define PAR_CFG_ENABLE_PERSIST (1) #endif /** - * Enable/Disable description check + * @brief Enable/Disable description check. * - * @note Default follows PAR_CFG_ENABLE_DESC. + * @note Default follows PAR_CFG_ENABLE_DESC. */ #ifndef PAR_CFG_ENABLE_DESC_CHECK - #define PAR_CFG_ENABLE_DESC_CHECK ( PAR_CFG_ENABLE_DESC ) +#define PAR_CFG_ENABLE_DESC_CHECK (PAR_CFG_ENABLE_DESC) #endif /** - * Configuration dependency checks for optional fields/features + * @brief Configuration dependency checks for optional fields/features. */ -#if ( 1 == PAR_CFG_NVM_EN ) && ( 0 == PAR_CFG_ENABLE_ID ) - #error "Parameter settings invalid: NVM requires PAR_CFG_ENABLE_ID = 1!" +#if (1 == PAR_CFG_NVM_EN) && (0 == PAR_CFG_ENABLE_ID) +#error "Parameter settings invalid: NVM requires PAR_CFG_ENABLE_ID = 1!" #endif -#if ( 1 == PAR_CFG_NVM_EN ) && ( 0 == PAR_CFG_ENABLE_PERSIST ) - #error "Parameter settings invalid: NVM requires PAR_CFG_ENABLE_PERSIST = 1!" +#if (1 == PAR_CFG_NVM_EN) && (0 == PAR_CFG_ENABLE_PERSIST) +#error "Parameter settings invalid: NVM requires PAR_CFG_ENABLE_PERSIST = 1!" #endif -#if ( 0 == PAR_CFG_ENABLE_ID ) && ( 1 == PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK ) - #error "Parameter settings invalid: runtime duplicate-ID diagnostics require PAR_CFG_ENABLE_ID = 1!" +#if (0 == PAR_CFG_ENABLE_ID) && (1 == PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK) +#error "Parameter settings invalid: runtime duplicate-ID diagnostics require PAR_CFG_ENABLE_ID = 1!" #endif -#if ( 0 == PAR_CFG_ENABLE_ID ) && ( 1 == PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK ) - #error "Parameter settings invalid: runtime ID hash-collision diagnostics require PAR_CFG_ENABLE_ID = 1!" +#if (0 == PAR_CFG_ENABLE_ID) && (1 == PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK) +#error "Parameter settings invalid: runtime ID hash-collision diagnostics require PAR_CFG_ENABLE_ID = 1!" #endif -#if ( PAR_CFG_LAYOUT_SOURCE != PAR_CFG_LAYOUT_COMPILE_SCAN ) && ( PAR_CFG_LAYOUT_SOURCE != PAR_CFG_LAYOUT_SCRIPT ) - #error "Parameter settings invalid: PAR_CFG_LAYOUT_SOURCE must be PAR_CFG_LAYOUT_COMPILE_SCAN or PAR_CFG_LAYOUT_SCRIPT!" +#if (PAR_CFG_LAYOUT_SOURCE != PAR_CFG_LAYOUT_COMPILE_SCAN) && (PAR_CFG_LAYOUT_SOURCE != PAR_CFG_LAYOUT_SCRIPT) +#error "Parameter settings invalid: PAR_CFG_LAYOUT_SOURCE must be PAR_CFG_LAYOUT_COMPILE_SCAN or PAR_CFG_LAYOUT_SCRIPT!" #endif -#define PAR_UINT16_MAX ( 65535u ) - -//////////////////////////////////////////////////////////////////////////////// -// Functions Prototypes -//////////////////////////////////////////////////////////////////////////////// -const par_cfg_t * par_cfg_get_table (void); -const par_cfg_t * par_cfg_get (const par_num_t par_num); -uint32_t par_cfg_get_table_size (void); - -//////////////////////////////////////////////////////////////////////////////// +#define PAR_UINT16_MAX (65535u) /** -* @} -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Function declarations. + */ +const par_cfg_t *par_cfg_get_table(void); +const par_cfg_t *par_cfg_get(const par_num_t par_num); +/** + * @brief Return the number of configuration entries. + * @return Configuration table size. + */ +uint32_t par_cfg_get_table_size(void); /**< @} */ -#endif // _PAR_CFG_H_ +#endif /* _PAR_CFG_H_ */ diff --git a/src/par_def.c b/src/par_def.c index b48c0eb..84f25e5 100644 --- a/src/par_def.c +++ b/src/par_def.c @@ -1,57 +1,55 @@ -// Copyright (c) 2026 Ziga Miklosic -// All Rights Reserved -// This software is under MIT licence (https://opensource.org/licenses/MIT) -//////////////////////////////////////////////////////////////////////////////// /** -*@file par_cfg.c -*@brief Configuration for device parameters -*@author wdfk-prog -*@email 1425075683@qq.com -*@date 29.01.2026 -*@version V3.0.1 -*/ -//////////////////////////////////////////////////////////////////////////////// -/** -*@addtogroup PAR_CFG -* @{ -* -* Configuration for device parameters -* -* User shall put code inside inside code block start with -* "USER_CODE_BEGIN" and with end of "USER_CODE_END". -*/ -//////////////////////////////////////////////////////////////////////////////// + * @file par_def.c + * @brief Build parameter-definition tables and derived metadata. + * @author wdfk-prog () + * @version 1.0 + * @date 2026-03-27 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-27 1.0 wdfk-prog first version + */ -//////////////////////////////////////////////////////////////////////////////// -// Includes -//////////////////////////////////////////////////////////////////////////////// +/** + * @addtogroup PAR_CFG + * @{ + * + * @brief Configuration for device parameters. + * + * User shall put code inside inside code block start with. + * "USER_CODE_BEGIN" and with end of "USER_CODE_END". + */ +/** + * @brief Include dependencies. + */ #include "par_def.h" #include "par.h" - -//////////////////////////////////////////////////////////////////////////////// -// Definitions -//////////////////////////////////////////////////////////////////////////////// /** - * Shared compile-time range checks for integer parameter items. + * @brief Compile-time definitions. + */ +/** + * @brief Shared compile-time range checks for integer parameter items. */ -#if ( 1 == PAR_CFG_ENABLE_RANGE ) -#define PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) \ - PAR_STATIC_ASSERT(enum_##_min_lt_max, ((min_) < (max_))); \ - PAR_STATIC_ASSERT(enum_##_def_ge_min, ((def_) >= (min_))); \ +#if (1 == PAR_CFG_ENABLE_RANGE) +#define PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) \ + PAR_STATIC_ASSERT(enum_##_min_lt_max, ((min_) < (max_))); \ + PAR_STATIC_ASSERT(enum_##_def_ge_min, ((def_) >= (min_))); \ PAR_STATIC_ASSERT(enum_##_def_le_max, ((def_) <= (max_))) /** - * Compile-time checks for each parameter value type. - * - * Signature: - * (enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) + * @brief Compile-time checks for each parameter value type. + * @details Signature: + * (enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_). */ -#define PAR_CHECK_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) -#define PAR_CHECK_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) -#define PAR_CHECK_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) -#define PAR_CHECK_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) -#define PAR_CHECK_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) -#define PAR_CHECK_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +#define PAR_CHECK_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +#define PAR_CHECK_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +#define PAR_CHECK_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +#define PAR_CHECK_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +#define PAR_CHECK_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +#define PAR_CHECK_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) #else #define PAR_CHECK_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) #define PAR_CHECK_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) @@ -60,29 +58,28 @@ #define PAR_CHECK_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) #define PAR_CHECK_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) #endif -/* - * NOTE: F32 range checks are runtime-only. - * - * Some embedded/legacy GCC toolchains do not reliably treat float comparisons - * in static assertions as integer constant expressions, and may emit +/** + * @brief NOTE: F32 range checks are runtime-only. + * @details Some embedded/legacy GCC toolchains do not reliably treat float comparisons. + * in static assertions as integer constant expressions, and may emit. * "variably modified '_static_assert_...' at file scope". */ -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) - #define PAR_CHECK_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#if (1 == PAR_CFG_ENABLE_TYPE_F32) +#define PAR_CHECK_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) #else - #define PAR_CHECK_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_STATIC_ASSERT(enum_##_f32_type_is_disabled__remove_PAR_ITEM_F32, 0) +#define PAR_CHECK_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_STATIC_ASSERT(enum_##_f32_type_is_disabled__remove_PAR_ITEM_F32, 0) #endif /** - * Dispatch map for compile-time checks. + * @brief Dispatch map for compile-time checks. */ -#define PAR_ITEM_U8 PAR_CHECK_U8 -#define PAR_ITEM_U16 PAR_CHECK_U16 -#define PAR_ITEM_U32 PAR_CHECK_U32 -#define PAR_ITEM_I8 PAR_CHECK_I8 -#define PAR_ITEM_I16 PAR_CHECK_I16 -#define PAR_ITEM_I32 PAR_CHECK_I32 -#define PAR_ITEM_F32 PAR_CHECK_F32 +#define PAR_ITEM_U8 PAR_CHECK_U8 +#define PAR_ITEM_U16 PAR_CHECK_U16 +#define PAR_ITEM_U32 PAR_CHECK_U32 +#define PAR_ITEM_I8 PAR_CHECK_I8 +#define PAR_ITEM_I16 PAR_CHECK_I16 +#define PAR_ITEM_I32 PAR_CHECK_I32 +#define PAR_ITEM_F32 PAR_CHECK_F32 #include "../../par_table.def" @@ -101,78 +98,81 @@ #undef PAR_CHECK_I16 #undef PAR_CHECK_I32 #undef PAR_CHECK_F32 -#if ( 1 == PAR_CFG_ENABLE_RANGE ) +#if (1 == PAR_CFG_ENABLE_RANGE) #undef PAR_CHECK_INT_COMMON #endif -#if ( 1 == PAR_CFG_ENABLE_ID ) +#if (1 == PAR_CFG_ENABLE_ID) /** - * Compile-time check A: duplicated parameter IDs in par_table.def. + * @brief Compile-time check A: duplicated parameter IDs in par_table.def. * * @note Duplicate ID values trigger duplicated "case" labels. */ -#define PAR_CHECK_ID_DUPLICATE_CASE(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) case ((uint32_t)(id_)): break; +#define PAR_CHECK_ID_DUPLICATE_CASE(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ + case ((uint32_t)(id_)): \ + break; static void par_compile_check_duplicate_ids(void) { switch (0u) { - #define PAR_ITEM_U8 PAR_CHECK_ID_DUPLICATE_CASE - #define PAR_ITEM_U16 PAR_CHECK_ID_DUPLICATE_CASE - #define PAR_ITEM_U32 PAR_CHECK_ID_DUPLICATE_CASE - #define PAR_ITEM_I8 PAR_CHECK_ID_DUPLICATE_CASE - #define PAR_ITEM_I16 PAR_CHECK_ID_DUPLICATE_CASE - #define PAR_ITEM_I32 PAR_CHECK_ID_DUPLICATE_CASE - #define PAR_ITEM_F32 PAR_CHECK_ID_DUPLICATE_CASE - #include "../../par_table.def" - #undef PAR_ITEM_U8 - #undef PAR_ITEM_U16 - #undef PAR_ITEM_U32 - #undef PAR_ITEM_I8 - #undef PAR_ITEM_I16 - #undef PAR_ITEM_I32 - #undef PAR_ITEM_F32 - default: break; +#define PAR_ITEM_U8 PAR_CHECK_ID_DUPLICATE_CASE +#define PAR_ITEM_U16 PAR_CHECK_ID_DUPLICATE_CASE +#define PAR_ITEM_U32 PAR_CHECK_ID_DUPLICATE_CASE +#define PAR_ITEM_I8 PAR_CHECK_ID_DUPLICATE_CASE +#define PAR_ITEM_I16 PAR_CHECK_ID_DUPLICATE_CASE +#define PAR_ITEM_I32 PAR_CHECK_ID_DUPLICATE_CASE +#define PAR_ITEM_F32 PAR_CHECK_ID_DUPLICATE_CASE +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 + default: + break; } } /** - * Compile-time check B: external ID hash-bucket collisions in par_table.def. - * - * The runtime ID map is a strict one-entry-per-bucket structure and does not + * @brief Compile-time check B: external ID hash-bucket collisions in par_table.def. + * @details The runtime ID map is a strict one-entry-per-bucket structure and does not. * implement probing or chaining. - * - * Therefore two different external IDs are still invalid when + * Therefore two different external IDs are still invalid when. * PAR_HASH_ID_CONST(id_a) == PAR_HASH_ID_CONST(id_b). - * - * This check intentionally fails the build early by generating duplicated + * This check intentionally fails the build early by generating duplicated. * "case" labels for colliding bucket indices. */ -#define PAR_CHECK_ID_BUCKET_CASE(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) case PAR_HASH_ID_CONST(id_): break; +#define PAR_CHECK_ID_BUCKET_CASE(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ + case PAR_HASH_ID_CONST(id_): \ + break; static void par_compile_check_hash_bucket_collision(void) { switch (0u) { - #define PAR_ITEM_U8 PAR_CHECK_ID_BUCKET_CASE - #define PAR_ITEM_U16 PAR_CHECK_ID_BUCKET_CASE - #define PAR_ITEM_U32 PAR_CHECK_ID_BUCKET_CASE - #define PAR_ITEM_I8 PAR_CHECK_ID_BUCKET_CASE - #define PAR_ITEM_I16 PAR_CHECK_ID_BUCKET_CASE - #define PAR_ITEM_I32 PAR_CHECK_ID_BUCKET_CASE - #define PAR_ITEM_F32 PAR_CHECK_ID_BUCKET_CASE - #include "../../par_table.def" - #undef PAR_ITEM_U8 - #undef PAR_ITEM_U16 - #undef PAR_ITEM_U32 - #undef PAR_ITEM_I8 - #undef PAR_ITEM_I16 - #undef PAR_ITEM_I32 - #undef PAR_ITEM_F32 - default: break; +#define PAR_ITEM_U8 PAR_CHECK_ID_BUCKET_CASE +#define PAR_ITEM_U16 PAR_CHECK_ID_BUCKET_CASE +#define PAR_ITEM_U32 PAR_CHECK_ID_BUCKET_CASE +#define PAR_ITEM_I8 PAR_CHECK_ID_BUCKET_CASE +#define PAR_ITEM_I16 PAR_CHECK_ID_BUCKET_CASE +#define PAR_ITEM_I32 PAR_CHECK_ID_BUCKET_CASE +#define PAR_ITEM_F32 PAR_CHECK_ID_BUCKET_CASE +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 + default: + break; } } -/* - * Keep compile-check helper functions "used" to avoid unused-function warnings. +/** + * @brief Keep compile-check helper functions "used" to avoid unused-function warnings. */ PAR_STATIC_ASSERT(par_compile_check_duplicate_ids_ref, (sizeof(&par_compile_check_duplicate_ids) > 0u)); PAR_STATIC_ASSERT(par_compile_check_hash_bucket_collision_ref, (sizeof(&par_compile_check_hash_bucket_collision) > 0u)); @@ -180,196 +180,188 @@ PAR_STATIC_ASSERT(par_compile_check_hash_bucket_collision_ref, (sizeof(&par_comp #undef PAR_CHECK_ID_DUPLICATE_CASE #undef PAR_CHECK_ID_BUCKET_CASE #endif - -//////////////////////////////////////////////////////////////////////////////// -// Variables -//////////////////////////////////////////////////////////////////////////////// - /** - * Parameters definitions + * @brief Module-scope variables. + */ +/** + * Parameters definitions. * - * @brief + * @brief * - * Each defined parameter has following properties: + * Each defined parameter has following properties: * - * i) Parameter ID: Unique parameter identification number. ID shall not be duplicated. - * ii) Name: Parameter name. Max. length of 32 chars. - * iii) Min: Parameter minimum value. Min value must be less than max value. - * iv) Max: Parameter maximum value. Max value must be more than min value. - * v) Def: Parameter default value. Default value must lie between interval: [min, max] - * vi) Unit: In case parameter shows physical value. Max. length of 32 chars. - * vii) Data type: Parameter data type. Supported types: uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t and float32_t - * viii) Access: Access type visible from external device such as PC. Either ReadWrite or ReadOnly. - * ix) Persistence: Tells if parameter value is being written into NVM. + * i) Parameter ID: Unique parameter identification number. ID shall not be duplicated. + * ii) Name: Parameter name. Max. length of 32 chars. + * iii) Min: Parameter minimum value. Min value must be less than max value. + * iv) Max: Parameter maximum value. Max value must be more than min value. + * v) Def: Parameter default value. Default value must lie between interval: [min, max]. + * vi) Unit: In case parameter shows physical value. Max. length of 32 chars. + * vii) Data type: Parameter data type. Supported types: uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t and float32_t. + * viii) Access: Access type visible from external device such as PC. Either ReadWrite or ReadOnly. + * ix) Persistence: Tells if parameter value is being written into NVM. * * - * @note User shall fill up wanted parameter definitions! + * @note User shall fill up wanted parameter definitions! */ /** - * X-Macro table initializers for each parameter value type. - * - * Signature: - * (enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) + * @brief X-Macro table initializers for each parameter value type. + * @details Signature: + * (enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_). */ -#if ( 1 == PAR_CFG_ENABLE_ID ) - #define PAR_INIT_ID(id_) .id = (uint16_t)(id_), +#if (1 == PAR_CFG_ENABLE_ID) +#define PAR_INIT_ID(id_) .id = (uint16_t)(id_), #else - #define PAR_INIT_ID(id_) +#define PAR_INIT_ID(id_) #endif -#if ( 1 == PAR_CFG_ENABLE_NAME ) - #define PAR_INIT_NAME(name_) .name = (name_), +#if (1 == PAR_CFG_ENABLE_NAME) +#define PAR_INIT_NAME(name_) .name = (name_), #else - #define PAR_INIT_NAME(name_) +#define PAR_INIT_NAME(name_) #endif -#if ( 1 == PAR_CFG_ENABLE_RANGE ) - #define PAR_INIT_RANGE_U8(min_, max_) .range.min.u8 = (uint8_t)(min_), .range.max.u8 = (uint8_t)(max_), - #define PAR_INIT_RANGE_U16(min_, max_) .range.min.u16 = (uint16_t)(min_), .range.max.u16 = (uint16_t)(max_), - #define PAR_INIT_RANGE_U32(min_, max_) .range.min.u32 = (uint32_t)(min_), .range.max.u32 = (uint32_t)(max_), - #define PAR_INIT_RANGE_I8(min_, max_) .range.min.i8 = (int8_t)(min_), .range.max.i8 = (int8_t)(max_), - #define PAR_INIT_RANGE_I16(min_, max_) .range.min.i16 = (int16_t)(min_), .range.max.i16 = (int16_t)(max_), - #define PAR_INIT_RANGE_I32(min_, max_) .range.min.i32 = (int32_t)(min_), .range.max.i32 = (int32_t)(max_), - #define PAR_INIT_RANGE_F32(min_, max_) .range.min.f32 = (float32_t)(min_), .range.max.f32 = (float32_t)(max_), +#if (1 == PAR_CFG_ENABLE_RANGE) +#define PAR_INIT_RANGE_U8(min_, max_) .range.min.u8 = (uint8_t)(min_), .range.max.u8 = (uint8_t)(max_), +#define PAR_INIT_RANGE_U16(min_, max_) .range.min.u16 = (uint16_t)(min_), .range.max.u16 = (uint16_t)(max_), +#define PAR_INIT_RANGE_U32(min_, max_) .range.min.u32 = (uint32_t)(min_), .range.max.u32 = (uint32_t)(max_), +#define PAR_INIT_RANGE_I8(min_, max_) .range.min.i8 = (int8_t)(min_), .range.max.i8 = (int8_t)(max_), +#define PAR_INIT_RANGE_I16(min_, max_) .range.min.i16 = (int16_t)(min_), .range.max.i16 = (int16_t)(max_), +#define PAR_INIT_RANGE_I32(min_, max_) .range.min.i32 = (int32_t)(min_), .range.max.i32 = (int32_t)(max_), +#define PAR_INIT_RANGE_F32(min_, max_) .range.min.f32 = (float32_t)(min_), .range.max.f32 = (float32_t)(max_), #else - #define PAR_INIT_RANGE_U8(min_, max_) - #define PAR_INIT_RANGE_U16(min_, max_) - #define PAR_INIT_RANGE_U32(min_, max_) - #define PAR_INIT_RANGE_I8(min_, max_) - #define PAR_INIT_RANGE_I16(min_, max_) - #define PAR_INIT_RANGE_I32(min_, max_) - #define PAR_INIT_RANGE_F32(min_, max_) +#define PAR_INIT_RANGE_U8(min_, max_) +#define PAR_INIT_RANGE_U16(min_, max_) +#define PAR_INIT_RANGE_U32(min_, max_) +#define PAR_INIT_RANGE_I8(min_, max_) +#define PAR_INIT_RANGE_I16(min_, max_) +#define PAR_INIT_RANGE_I32(min_, max_) +#define PAR_INIT_RANGE_F32(min_, max_) #endif -#if ( 1 == PAR_CFG_ENABLE_UNIT ) - #define PAR_INIT_UNIT(unit_) .unit = (unit_), +#if (1 == PAR_CFG_ENABLE_UNIT) +#define PAR_INIT_UNIT(unit_) .unit = (unit_), #else - #define PAR_INIT_UNIT(unit_) +#define PAR_INIT_UNIT(unit_) #endif -#if ( 1 == PAR_CFG_ENABLE_ACCESS ) - #define PAR_INIT_ACCESS(access_) .access = (access_), +#if (1 == PAR_CFG_ENABLE_ACCESS) +#define PAR_INIT_ACCESS(access_) .access = (access_), #else - #define PAR_INIT_ACCESS(access_) +#define PAR_INIT_ACCESS(access_) #endif -#if ( 1 == PAR_CFG_ENABLE_PERSIST ) - #define PAR_INIT_PERSIST(pers_) .persistent = (pers_), +#if (1 == PAR_CFG_ENABLE_PERSIST) +#define PAR_INIT_PERSIST(pers_) .persistent = (pers_), #else - #define PAR_INIT_PERSIST(pers_) +#define PAR_INIT_PERSIST(pers_) #endif -#if ( 1 == PAR_CFG_ENABLE_DESC ) - #define PAR_INIT_DESC(desc_) .desc = (desc_), +#if (1 == PAR_CFG_ENABLE_DESC) +#define PAR_INIT_DESC(desc_) .desc = (desc_), #else - #define PAR_INIT_DESC(desc_) +#define PAR_INIT_DESC(desc_) #endif -#define PAR_INIT_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ - [enum_] = { \ - PAR_INIT_ID(id_) \ - PAR_INIT_NAME(name_) \ - PAR_INIT_RANGE_U8(min_, max_) \ - .def.u8 = (uint8_t)(def_), \ - PAR_INIT_UNIT(unit_) \ - .type = ePAR_TYPE_U8, \ - PAR_INIT_ACCESS(access_) \ - PAR_INIT_PERSIST(pers_) \ - PAR_INIT_DESC(desc_) \ - }, - -#define PAR_INIT_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ - [enum_] = { \ - PAR_INIT_ID(id_) \ - PAR_INIT_NAME(name_) \ - PAR_INIT_RANGE_U16(min_, max_) \ - .def.u16 = (uint16_t)(def_), \ - PAR_INIT_UNIT(unit_) \ - .type = ePAR_TYPE_U16, \ - PAR_INIT_ACCESS(access_) \ - PAR_INIT_PERSIST(pers_) \ - PAR_INIT_DESC(desc_) \ +#define PAR_INIT_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_U8(min_, max_) \ + .def.u8 = (uint8_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_U8, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_PERSIST(pers_) \ + PAR_INIT_DESC(desc_) \ }, -#define PAR_INIT_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ - [enum_] = { \ - PAR_INIT_ID(id_) \ - PAR_INIT_NAME(name_) \ - PAR_INIT_RANGE_U32(min_, max_) \ - .def.u32 = (uint32_t)(def_), \ - PAR_INIT_UNIT(unit_) \ - .type = ePAR_TYPE_U32, \ - PAR_INIT_ACCESS(access_) \ - PAR_INIT_PERSIST(pers_) \ - PAR_INIT_DESC(desc_) \ +#define PAR_INIT_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_U16(min_, max_) \ + .def.u16 = (uint16_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_U16, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_PERSIST(pers_) \ + PAR_INIT_DESC(desc_) \ }, -#define PAR_INIT_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ - [enum_] = { \ - PAR_INIT_ID(id_) \ - PAR_INIT_NAME(name_) \ - PAR_INIT_RANGE_I8(min_, max_) \ - .def.i8 = (int8_t)(def_), \ - PAR_INIT_UNIT(unit_) \ - .type = ePAR_TYPE_I8, \ - PAR_INIT_ACCESS(access_) \ - PAR_INIT_PERSIST(pers_) \ - PAR_INIT_DESC(desc_) \ +#define PAR_INIT_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_U32(min_, max_) \ + .def.u32 = (uint32_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_U32, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_PERSIST(pers_) \ + PAR_INIT_DESC(desc_) \ }, -#define PAR_INIT_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ - [enum_] = { \ - PAR_INIT_ID(id_) \ - PAR_INIT_NAME(name_) \ - PAR_INIT_RANGE_I16(min_, max_) \ - .def.i16 = (int16_t)(def_), \ - PAR_INIT_UNIT(unit_) \ - .type = ePAR_TYPE_I16, \ - PAR_INIT_ACCESS(access_) \ - PAR_INIT_PERSIST(pers_) \ - PAR_INIT_DESC(desc_) \ +#define PAR_INIT_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_I8(min_, max_) \ + .def.i8 = (int8_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_I8, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_PERSIST(pers_) \ + PAR_INIT_DESC(desc_) \ }, -#define PAR_INIT_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ - [enum_] = { \ - PAR_INIT_ID(id_) \ - PAR_INIT_NAME(name_) \ - PAR_INIT_RANGE_I32(min_, max_) \ - .def.i32 = (int32_t)(def_), \ - PAR_INIT_UNIT(unit_) \ - .type = ePAR_TYPE_I32, \ - PAR_INIT_ACCESS(access_) \ - PAR_INIT_PERSIST(pers_) \ - PAR_INIT_DESC(desc_) \ +#define PAR_INIT_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_I16(min_, max_) \ + .def.i16 = (int16_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_I16, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_PERSIST(pers_) \ + PAR_INIT_DESC(desc_) \ }, -#define PAR_INIT_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ - [enum_] = { \ - PAR_INIT_ID(id_) \ - PAR_INIT_NAME(name_) \ - PAR_INIT_RANGE_F32(min_, max_) \ - .def.f32 = (float32_t)(def_), \ - PAR_INIT_UNIT(unit_) \ - .type = ePAR_TYPE_F32, \ - PAR_INIT_ACCESS(access_) \ - PAR_INIT_PERSIST(pers_) \ - PAR_INIT_DESC(desc_) \ +#define PAR_INIT_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_I32(min_, max_) \ + .def.i32 = (int32_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_I32, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_PERSIST(pers_) \ + PAR_INIT_DESC(desc_) \ }, -/** - * Dispatch map for table initialization. - */ -#define PAR_ITEM_U8 PAR_INIT_U8 -#define PAR_ITEM_U16 PAR_INIT_U16 -#define PAR_ITEM_U32 PAR_INIT_U32 -#define PAR_ITEM_I8 PAR_INIT_I8 -#define PAR_ITEM_I16 PAR_INIT_I16 -#define PAR_ITEM_I32 PAR_INIT_I32 -#define PAR_ITEM_F32 PAR_INIT_F32 - -static const par_cfg_t g_par_table[ePAR_NUM_OF] = -{ - #include "../../par_table.def" +#define PAR_INIT_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_F32(min_, max_) \ + .def.f32 = (float32_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_F32, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_PERSIST(pers_) \ + PAR_INIT_DESC(desc_) \ + }, /**< Dispatch map for table initialization. */ +#define PAR_ITEM_U8 PAR_INIT_U8 +#define PAR_ITEM_U16 PAR_INIT_U16 +#define PAR_ITEM_U32 PAR_INIT_U32 +#define PAR_ITEM_I8 PAR_INIT_I8 +#define PAR_ITEM_I16 PAR_INIT_I16 +#define PAR_ITEM_I32 PAR_INIT_I32 +#define PAR_ITEM_F32 PAR_INIT_F32 + +static const par_cfg_t g_par_table[ePAR_NUM_OF] = { +#include "../../par_table.def" }; #undef PAR_ITEM_U8 @@ -402,52 +394,39 @@ static const par_cfg_t g_par_table[ePAR_NUM_OF] = #undef PAR_INIT_DESC /** - * Table size in bytes + * @brief Table size in bytes. + */ +static const uint32_t gu32_par_table_size = sizeof(g_par_table); +/** + * @brief Function declarations and definitions. */ -static const uint32_t gu32_par_table_size = sizeof( g_par_table ); - -//////////////////////////////////////////////////////////////////////////////// -// Functions -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// /** -* Get full Device Parameter configuration table -* -* @return pointer to configuration table -*/ -//////////////////////////////////////////////////////////////////////////////// -const par_cfg_t * par_cfg_get_table(void) + * @brief Get full Device Parameter configuration table. + * + * @return pointer to configuration table. + */ +const par_cfg_t *par_cfg_get_table(void) { - return g_par_table; + return g_par_table; } - -//////////////////////////////////////////////////////////////////////////////// /** -* Get single Device Parameter configuration -* -* @return pointer to parameter config -*/ -//////////////////////////////////////////////////////////////////////////////// -const par_cfg_t * par_cfg_get(const par_num_t par_num) + * @brief Get single Device Parameter configuration. + * + * @return pointer to parameter config. + */ +const par_cfg_t *par_cfg_get(const par_num_t par_num) { return &g_par_table[par_num]; } - -//////////////////////////////////////////////////////////////////////////////// /** -* Get configuration table size in bytes -* -* @return gu32_par_table_size - Size of table in bytes -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Get configuration table size in bytes. + * + * @return Size of table in bytes. + */ uint32_t par_cfg_get_table_size(void) { - return gu32_par_table_size; + return gu32_par_table_size; } - -//////////////////////////////////////////////////////////////////////////////// /** -* @} -*/ -//////////////////////////////////////////////////////////////////////////////// + * @} + */ diff --git a/src/par_def.h b/src/par_def.h index b1f30eb..771f176 100644 --- a/src/par_def.h +++ b/src/par_def.h @@ -1,144 +1,129 @@ -// Copyright (c) 2026 Ziga Miklosic -// All Rights Reserved -// This software is under MIT licence (https://opensource.org/licenses/MIT) -//////////////////////////////////////////////////////////////////////////////// /** -*@file par_def.h -*@brief Core parameter definition interface -*@author wdfk-prog -*@email 1425075683@qq.com -*@date 29.01.2026 -*@version V3.0.1 -*@note Do not gate F32 in this header. -* par_def.h stays config-independent to avoid par_cfg.h include-order issues -* and to keep enum definitions stable. Disabled F32 table entries are -* rejected in par_def.c at compile time. -*/ -//////////////////////////////////////////////////////////////////////////////// + * @file par_def.h + * @brief Declare parameter-definition types and compile-time enumerations. + * @author wdfk-prog () + * @version 1.0 + * @date 2026-03-27 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-27 1.0 wdfk-prog first version + */ + /** -*@addtogroup PAR_DEF -* @{ -*/ -//////////////////////////////////////////////////////////////////////////////// + * @addtogroup PAR_DEF + * @{ + */ #ifndef _PAR_DEF_CORE_H_ #define _PAR_DEF_CORE_H_ - #ifdef __cplusplus extern "C" { #endif - -//////////////////////////////////////////////////////////////////////////////// -// Includes -//////////////////////////////////////////////////////////////////////////////// - +/** + * @brief Include dependencies. + */ #include - -//////////////////////////////////////////////////////////////////////////////// -// Definitions -//////////////////////////////////////////////////////////////////////////////// +/** + * @brief Compile-time definitions. + */ typedef struct par_cfg_s par_cfg_t; - /** - * List of device parameters - * - * @note Must be started with 0! - * @note Enum expansion is intentionally configuration-independent: - * PAR_ITEM_F32 always maps to PAR_ITEM_ENUM. - * F32 enable/disable fail-fast is enforced in par_def.c. + * @brief List of device parameters. + * @note Must be started with 0! @note Enum expansion is intentionally configuration-independent: PAR_ITEM_F32 always maps to PAR_ITEM_ENUM. F32 enable/disable fail-fast is enforced in par_def.c. */ #define PAR_ITEM_ENUM(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) enum_, enum { - #define PAR_ITEM_U8 PAR_ITEM_ENUM - #define PAR_ITEM_U16 PAR_ITEM_ENUM - #define PAR_ITEM_U32 PAR_ITEM_ENUM - #define PAR_ITEM_I8 PAR_ITEM_ENUM - #define PAR_ITEM_I16 PAR_ITEM_ENUM - #define PAR_ITEM_I32 PAR_ITEM_ENUM - #define PAR_ITEM_F32 PAR_ITEM_ENUM - #include "../../par_table.def" - #undef PAR_ITEM_U8 - #undef PAR_ITEM_U16 - #undef PAR_ITEM_U32 - #undef PAR_ITEM_I8 - #undef PAR_ITEM_I16 - #undef PAR_ITEM_I32 - #undef PAR_ITEM_F32 +#define PAR_ITEM_U8 PAR_ITEM_ENUM +#define PAR_ITEM_U16 PAR_ITEM_ENUM +#define PAR_ITEM_U32 PAR_ITEM_ENUM +#define PAR_ITEM_I8 PAR_ITEM_ENUM +#define PAR_ITEM_I16 PAR_ITEM_ENUM +#define PAR_ITEM_I32 PAR_ITEM_ENUM +#define PAR_ITEM_F32 PAR_ITEM_ENUM +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 ePAR_NUM_OF }; #undef PAR_ITEM_ENUM typedef uint16_t par_num_t; - /** - * Compile-time storage group counts derived from par_table.def. - * - * @note - * These constants are used by layout and static storage allocation. + * @brief Compile-time storage group counts derived from par_table.def. + * @note These constants are used by layout and static storage allocation. */ -#define PAR_ITEM_COUNT_ONE(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) + 1u -#define PAR_ITEM_COUNT_ZERO(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) + 0u +#define PAR_ITEM_COUNT_ONE(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +1u +#define PAR_ITEM_COUNT_ZERO(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +0u enum { PAR_LAYOUT_COMPILE_COUNT8 = 0u - #define PAR_ITEM_U8 PAR_ITEM_COUNT_ONE - #define PAR_ITEM_U16 PAR_ITEM_COUNT_ZERO - #define PAR_ITEM_U32 PAR_ITEM_COUNT_ZERO - #define PAR_ITEM_I8 PAR_ITEM_COUNT_ONE - #define PAR_ITEM_I16 PAR_ITEM_COUNT_ZERO - #define PAR_ITEM_I32 PAR_ITEM_COUNT_ZERO - #define PAR_ITEM_F32 PAR_ITEM_COUNT_ZERO - #include "../../par_table.def" - #undef PAR_ITEM_U8 - #undef PAR_ITEM_U16 - #undef PAR_ITEM_U32 - #undef PAR_ITEM_I8 - #undef PAR_ITEM_I16 - #undef PAR_ITEM_I32 - #undef PAR_ITEM_F32 +#define PAR_ITEM_U8 PAR_ITEM_COUNT_ONE +#define PAR_ITEM_U16 PAR_ITEM_COUNT_ZERO +#define PAR_ITEM_U32 PAR_ITEM_COUNT_ZERO +#define PAR_ITEM_I8 PAR_ITEM_COUNT_ONE +#define PAR_ITEM_I16 PAR_ITEM_COUNT_ZERO +#define PAR_ITEM_I32 PAR_ITEM_COUNT_ZERO +#define PAR_ITEM_F32 PAR_ITEM_COUNT_ZERO +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 }; enum { PAR_LAYOUT_COMPILE_COUNT16 = 0u - #define PAR_ITEM_U8 PAR_ITEM_COUNT_ZERO - #define PAR_ITEM_U16 PAR_ITEM_COUNT_ONE - #define PAR_ITEM_U32 PAR_ITEM_COUNT_ZERO - #define PAR_ITEM_I8 PAR_ITEM_COUNT_ZERO - #define PAR_ITEM_I16 PAR_ITEM_COUNT_ONE - #define PAR_ITEM_I32 PAR_ITEM_COUNT_ZERO - #define PAR_ITEM_F32 PAR_ITEM_COUNT_ZERO - #include "../../par_table.def" - #undef PAR_ITEM_U8 - #undef PAR_ITEM_U16 - #undef PAR_ITEM_U32 - #undef PAR_ITEM_I8 - #undef PAR_ITEM_I16 - #undef PAR_ITEM_I32 - #undef PAR_ITEM_F32 +#define PAR_ITEM_U8 PAR_ITEM_COUNT_ZERO +#define PAR_ITEM_U16 PAR_ITEM_COUNT_ONE +#define PAR_ITEM_U32 PAR_ITEM_COUNT_ZERO +#define PAR_ITEM_I8 PAR_ITEM_COUNT_ZERO +#define PAR_ITEM_I16 PAR_ITEM_COUNT_ONE +#define PAR_ITEM_I32 PAR_ITEM_COUNT_ZERO +#define PAR_ITEM_F32 PAR_ITEM_COUNT_ZERO +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 }; enum { PAR_LAYOUT_COMPILE_COUNT32 = 0u - #define PAR_ITEM_U8 PAR_ITEM_COUNT_ZERO - #define PAR_ITEM_U16 PAR_ITEM_COUNT_ZERO - #define PAR_ITEM_U32 PAR_ITEM_COUNT_ONE - #define PAR_ITEM_I8 PAR_ITEM_COUNT_ZERO - #define PAR_ITEM_I16 PAR_ITEM_COUNT_ZERO - #define PAR_ITEM_I32 PAR_ITEM_COUNT_ONE - #define PAR_ITEM_F32 PAR_ITEM_COUNT_ONE - #include "../../par_table.def" - #undef PAR_ITEM_U8 - #undef PAR_ITEM_U16 - #undef PAR_ITEM_U32 - #undef PAR_ITEM_I8 - #undef PAR_ITEM_I16 - #undef PAR_ITEM_I32 - #undef PAR_ITEM_F32 +#define PAR_ITEM_U8 PAR_ITEM_COUNT_ZERO +#define PAR_ITEM_U16 PAR_ITEM_COUNT_ZERO +#define PAR_ITEM_U32 PAR_ITEM_COUNT_ONE +#define PAR_ITEM_I8 PAR_ITEM_COUNT_ZERO +#define PAR_ITEM_I16 PAR_ITEM_COUNT_ZERO +#define PAR_ITEM_I32 PAR_ITEM_COUNT_ONE +#define PAR_ITEM_F32 PAR_ITEM_COUNT_ONE +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 }; enum @@ -148,22 +133,22 @@ enum #undef PAR_ITEM_COUNT_ONE #undef PAR_ITEM_COUNT_ZERO - -//////////////////////////////////////////////////////////////////////////////// -// Functions Prototypes -//////////////////////////////////////////////////////////////////////////////// -const par_cfg_t * par_cfg_get_table (void); -const par_cfg_t * par_cfg_get (const par_num_t par_num); -uint32_t par_cfg_get_table_size (void); +/** + * @brief Function declarations. + */ +const par_cfg_t *par_cfg_get_table(void); +const par_cfg_t *par_cfg_get(const par_num_t par_num); +/** + * @brief Return the number of configuration entries. + * @return Configuration table size. + */ +uint32_t par_cfg_get_table_size(void); #ifdef __cplusplus } #endif - -//////////////////////////////////////////////////////////////////////////////// /** -* @} -*/ -//////////////////////////////////////////////////////////////////////////////// + * @} + */ #endif /* _PAR_DEF_CORE_H_ */ diff --git a/src/par_id_map_static.c b/src/par_id_map_static.c index 481edc4..f8f1559 100644 --- a/src/par_id_map_static.c +++ b/src/par_id_map_static.c @@ -4,9 +4,9 @@ * @author wdfk-prog () * @version 1.0 * @date 2026-03-24 - * - * @copyright Copyright (c) 2026 - * + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * * @note : * @par Change Log: * Date Version Author Description @@ -14,28 +14,27 @@ */ #include "par_id_map_static.h" -#if ( 1 == PAR_CFG_ENABLE_ID ) +#if (1 == PAR_CFG_ENABLE_ID) #define PAR_ID_MAP_ITEM(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ [PAR_HASH_ID_CONST(id_)] = { .id = (uint16_t)(id_), .par_num = (enum_), .used = 1u }, -const par_id_map_entry_t g_par_id_map_static[PAR_ID_HASH_SIZE] = -{ - #define PAR_ITEM_U8 PAR_ID_MAP_ITEM - #define PAR_ITEM_U16 PAR_ID_MAP_ITEM - #define PAR_ITEM_U32 PAR_ID_MAP_ITEM - #define PAR_ITEM_I8 PAR_ID_MAP_ITEM - #define PAR_ITEM_I16 PAR_ID_MAP_ITEM - #define PAR_ITEM_I32 PAR_ID_MAP_ITEM - #define PAR_ITEM_F32 PAR_ID_MAP_ITEM - #include "../../par_table.def" - #undef PAR_ITEM_U8 - #undef PAR_ITEM_U16 - #undef PAR_ITEM_U32 - #undef PAR_ITEM_I8 - #undef PAR_ITEM_I16 - #undef PAR_ITEM_I32 - #undef PAR_ITEM_F32 +const par_id_map_entry_t g_par_id_map_static[PAR_ID_HASH_SIZE] = { +#define PAR_ITEM_U8 PAR_ID_MAP_ITEM +#define PAR_ITEM_U16 PAR_ID_MAP_ITEM +#define PAR_ITEM_U32 PAR_ID_MAP_ITEM +#define PAR_ITEM_I8 PAR_ID_MAP_ITEM +#define PAR_ITEM_I16 PAR_ID_MAP_ITEM +#define PAR_ITEM_I32 PAR_ID_MAP_ITEM +#define PAR_ITEM_F32 PAR_ID_MAP_ITEM +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 }; #undef PAR_ID_MAP_ITEM diff --git a/src/par_id_map_static.h b/src/par_id_map_static.h index 1315ccc..bbc6a07 100644 --- a/src/par_id_map_static.h +++ b/src/par_id_map_static.h @@ -1,12 +1,12 @@ /** * @file par_id_map_static.h - * @brief + * @brief * @author wdfk-prog () * @version 1.0 * @date 2026-03-24 - * - * @copyright Copyright (c) 2026 - * + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * * @note : * @par Change Log: * Date Version Author Description @@ -17,12 +17,12 @@ #include "par.h" -#if ( 1 == PAR_CFG_ENABLE_ID ) +#if (1 == PAR_CFG_ENABLE_ID) typedef struct { - uint16_t id; + uint16_t id; par_num_t par_num; - uint8_t used; + uint8_t used; } par_id_map_entry_t; extern const par_id_map_entry_t g_par_id_map_static[PAR_ID_HASH_SIZE]; diff --git a/src/par_if.c b/src/par_if.c index 10b72be..d66adab 100644 --- a/src/par_if.c +++ b/src/par_if.c @@ -1,274 +1,251 @@ -// Copyright (c) 2026 Ziga Miklosic -// All Rights Reserved -// This software is under MIT licence (https://opensource.org/licenses/MIT) -//////////////////////////////////////////////////////////////////////////////// /** -*@file par_if.c -*@brief Interface with device parameters -*@author Ziga Miklosic -*@email ziga.miklosic@gmail.com -*@date 29.01.2026 -*@version V3.0.1 -*/ -//////////////////////////////////////////////////////////////////////////////// -/** -*@addtogroup PAR_IF -* @{ -* -* Interface layer for device parameters -* -* Put code that is platform depended inside code block start with -* "USER_CODE_BEGIN" and with end of "USER_CODE_END". -*/ -//////////////////////////////////////////////////////////////////////////////// + * @file par_if.c + * @brief Implement the parameter interface layer. + * @author Ziga Miklosic + * @version V3.0.1 + * @date 2026-01-29 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-01-29 V3.0.1 Ziga Miklosic + */ -//////////////////////////////////////////////////////////////////////////////// -// Includes -//////////////////////////////////////////////////////////////////////////////// +/** + * @addtogroup PAR_IF + * @{ + * + * @brief Interface layer for device parameters. + * + * Put code that is platform depended inside code block start with. + * "USER_CODE_BEGIN" and with end of "USER_CODE_END". + */ +/** + * @brief Include dependencies. + */ #include "par_if.h" -#if ( 1 == PAR_CFG_IF_PORT_EN ) - -//////////////////////////////////////////////////////////////////////////////// +#if (1 == PAR_CFG_IF_PORT_EN) /** -* Initialize low level interface -* -* @note Default weak implementation keeps the interface optional. -* Integrator may provide a strong definition in port/par_if_port.c. -* -* @return status - Status of initialization -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Initialize low level interface. + * + * @note Default weak implementation keeps the interface optional. + * Integrator may provide a strong definition in port/par_if_port.c. + * + * @return Status of initialization. + */ PAR_PORT_WEAK par_status_t par_if_init(void) { return ePAR_OK; } - -//////////////////////////////////////////////////////////////////////////////// /** -* De-initialize low level interface -* -* @note Default weak implementation keeps the interface optional. -* Integrator may provide a strong definition in port/par_if_port.c. -* -* @return status - Status of de-initialization -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief De-initialize low level interface. + * + * @note Default weak implementation keeps the interface optional. + * Integrator may provide a strong definition in port/par_if_port.c. + * + * @return Status of de-initialization. + */ PAR_PORT_WEAK par_status_t par_if_deinit(void) { return ePAR_OK; } - -//////////////////////////////////////////////////////////////////////////////// /** -* Acquire mutex for specified parameter -* -* @note Default weak implementation does not provide locking. -* Integrator may provide a strong definition in port/par_if_port.c. -* -* @param[in] par_num - Parameter number (enumeration) -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Acquire mutex for specified parameter. + * + * @note Default weak implementation does not provide locking. + * Integrator may provide a strong definition in port/par_if_port.c. + * + * @param par_num Parameter number (enumeration). + * @return Status of operation. + */ PAR_PORT_WEAK par_status_t par_if_aquire_mutex(const par_num_t par_num) { - (void) par_num; + (void)par_num; return ePAR_OK; } - -//////////////////////////////////////////////////////////////////////////////// /** -* Release mutex for specified parameter -* -* @note Default weak implementation does not provide locking. -* Integrator may provide a strong definition in port/par_if_port.c. -* -* @param[in] par_num - Parameter number (enumeration) -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Release mutex for specified parameter. + * + * @note Default weak implementation does not provide locking. + * Integrator may provide a strong definition in port/par_if_port.c. + * + * @param par_num Parameter number (enumeration). + */ PAR_PORT_WEAK void par_if_release_mutex(const par_num_t par_num) { - (void) par_num; + (void)par_num; } - -//////////////////////////////////////////////////////////////////////////////// /** -* Calculate hash -* -* @note Default weak implementation leaves hash output untouched. -* Integrator may provide a strong definition in port/par_if_port.c. -* -* @param[in] p_data - Pointer to data for hash calculation -* @param[in] size - Size of data in bytes -* @param[in] p_hash - Pointer to calculated hash number -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Calculate hash. + * + * @note Default weak implementation leaves hash output untouched. + * Integrator may provide a strong definition in port/par_if_port.c. + * + * @param p_data Pointer to data for hash calculation. + * @param size Size of data in bytes. + * @param p_hash Pointer to calculated hash number. + */ PAR_PORT_WEAK void par_if_calc_hash(const uint8_t * const p_data, const uint32_t size, uint8_t * const p_hash) { - (void) p_data; - (void) size; - (void) p_hash; + (void)p_data; + (void)size; + (void)p_hash; } #else -// USER INCLUDES BEGIN... +/** + * @brief USER INCLUDES BEGIN... + */ #include "common/utils/src/utils.h" #include "cmsis_os2.h" -// USER INCLUDES END... - -//////////////////////////////////////////////////////////////////////////////// -// Definitions -//////////////////////////////////////////////////////////////////////////////// - -// USER DEFINITIONS BEGIN... - /** - * Parameter mutex timeout - * - * Unit: ms + * @brief USER INCLUDES END... + */ +/** + * @brief Compile-time definitions. + */ +/** + * @brief USER DEFINITIONS BEGIN... */ -#define PAR_CFG_MUTEX_TIMEOUT_MS ( 10 ) - -// USER DEFINITIONS END... -//////////////////////////////////////////////////////////////////////////////// -// Variables -//////////////////////////////////////////////////////////////////////////////// +/** + * @brief Parameter mutex timeout. + * @details Unit: ms. + */ +#define PAR_CFG_MUTEX_TIMEOUT_MS (10) -// USER VARIABLES BEGIN... +/** + * @brief USER DEFINITIONS END... + */ +/** + * @brief Module-scope variables. + */ +/** + * @brief USER VARIABLES BEGIN... + */ /** - * Parameters OS mutex + * @brief Parameters OS mutex. */ static osMutexId_t g_par_mutex_id = NULL; -const osMutexAttr_t g_par_mutex_attr = -{ - .name = "par", - .attr_bits = ( osMutexPrioInherit ), +const osMutexAttr_t g_par_mutex_attr = { + .name = "par", + .attr_bits = (osMutexPrioInherit), }; - -// USER VARIABLES END... - -//////////////////////////////////////////////////////////////////////////////// -// Functions -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// /** -* Initialize low level interface -* -* @note User shall provide definition of that function based on used platform! -* -* @return status - Status of initialization -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief USER VARIABLES END... + */ +/** + * @brief Function declarations and definitions. + */ +/** + * @brief Initialize low level interface. + * + * @note User shall provide definition of that function based on used platform! + * + * @return Status of initialization. + */ par_status_t par_if_init(void) { par_status_t status = ePAR_OK; + g_par_mutex_id = osMutexNew(&g_par_mutex_attr); - // USER CODE BEGIN... - - // Create mutex - g_par_mutex_id = osMutexNew( &g_par_mutex_attr ); - - if ( NULL == g_par_mutex_id ) + if (NULL == g_par_mutex_id) { status = ePAR_ERROR; } - // USER CODE END... + return status; +} +/** + * @brief De-initialize low level interface. + * + * @note User shall provide definition of that function based on used platform! + * + * @return Status of de-initialization. + */ +par_status_t par_if_deinit(void) +{ + par_status_t status = ePAR_OK; + osMutexId_t mutex_id = g_par_mutex_id; + if (NULL == mutex_id) + { + status = ePAR_OK; + } + else if (osOK != osMutexDelete(mutex_id)) + { + status = ePAR_ERROR; + } + g_par_mutex_id = NULL; return status; } - -//////////////////////////////////////////////////////////////////////////////// /** -* Acquire mutex for specified parameter -* -* @note User shall provide definition of that function based on used platform! -* -* If mutex is not needed leave empty space between user code begin and end. -* -* @param[in] par_num - Parameter number (enumeration) -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Acquire mutex for specified parameter. + * + * @note User shall provide definition of that function based on used platform! + * + * If mutex is not needed leave empty space between user code begin and end. + * + * @param par_num Parameter number (enumeration). + * @return Status of operation. + */ par_status_t par_if_aquire_mutex(const par_num_t par_num) { par_status_t status = ePAR_OK; - // USER CODE BEGIN... UNUSED(par_num); - if ( osOK == osMutexAcquire( g_par_mutex_id, PAR_CFG_MUTEX_TIMEOUT_MS )) - { - // No action - } - else + if (osOK != osMutexAcquire(g_par_mutex_id, PAR_CFG_MUTEX_TIMEOUT_MS)) { status = ePAR_ERROR; } - // USER CODE END... - return status; } - -//////////////////////////////////////////////////////////////////////////////// /** -* Release mutex for specified parameter -* -* @note User shall provide definition of that function based on used platform! -* -* If mutex is not needed leave empty space between user code begin and end. -* -* @param[in] par_num - Parameter number (enumeration) -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Release mutex for specified parameter. + * + * @note User shall provide definition of that function based on used platform! + * + * If mutex is not needed leave empty space between user code begin and end. + * + * @param par_num Parameter number (enumeration). + * @return Status of operation. + */ void par_if_release_mutex(const par_num_t par_num) { - // USER CODE BEGIN... UNUSED(par_num); - osMutexRelease( g_par_mutex_id ); - - // USER CODE END... + osMutexRelease(g_par_mutex_id); } - -//////////////////////////////////////////////////////////////////////////////// /** -* Calculate hash -* -* @note User shall provide definition of that function based on used platform! -* -* If not being used leave empty. -* -* This function does not have an affect if "PAR_CFG_TABLE_ID_CHECK_EN" -* is set to 0. -* -* @param[in] p_data - Pointer to data for hash calculation -* @param[in] size - Size of data in bytes -* @return p_hash - Pointer to calculated hash number -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Calculate hash. + * + * @note User shall provide definition of that function based on used platform! + * + * If not being used leave empty. + * + * This function does not have an affect if "PAR_CFG_TABLE_ID_CHECK_EN". + * is set to 0. + * + * @param p_data Pointer to data for hash calculation. + * @param size Size of data in bytes. + * @return Pointer to calculated hash number. + */ void par_if_calc_hash(const uint8_t * const p_data, const uint32_t size, uint8_t * const p_hash) { - UNUSED( p_data ); - UNUSED( p_hash ); - UNUSED( size ); - - // USER CODE BEGIN... - - // USER CODE END... + UNUSED(p_data); + UNUSED(p_hash); + UNUSED(size); } -#endif /* ( 1 == PAR_CFG_IF_PORT_EN ) */ - -//////////////////////////////////////////////////////////////////////////////// +#endif /** -* @} -*/ -//////////////////////////////////////////////////////////////////////////////// + * @} + */ diff --git a/src/par_if.h b/src/par_if.h index 838194a..5442714 100644 --- a/src/par_if.h +++ b/src/par_if.h @@ -1,42 +1,64 @@ -// Copyright (c) 2026 Ziga Miklosic -// All Rights Reserved -// This software is under MIT licence (https://opensource.org/licenses/MIT) -//////////////////////////////////////////////////////////////////////////////// -/** -*@file par_if.h -*@brief Interface for device parameters -*@author Ziga Miklosic -*@email ziga.miklosic@gmail.com -*@date 29.01.2026 -*@version V3.0.1 -*/ -//////////////////////////////////////////////////////////////////////////////// -/** -*@addtogroup PAR_IF -* @{ -*/ -//////////////////////////////////////////////////////////////////////////////// +/** + * @file par_if.h + * @brief Declare the parameter interface layer. + * @author Ziga Miklosic + * @version V3.0.1 + * @date 2026-01-29 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-01-29 V3.0.1 Ziga Miklosic + */ + +/** + * @addtogroup PAR_IF + * @{ + */ #ifndef _PAR_IF_H_ #define _PAR_IF_H_ - -//////////////////////////////////////////////////////////////////////////////// -// Includes -//////////////////////////////////////////////////////////////////////////////// +/** + * @brief Include dependencies. + */ #include #include "par.h" #include "par_cfg.h" +/** + * @brief Compile-time definitions. + */ +/** + * @brief Function declarations. + */ +/** + * @brief Initialize the interface layer. + * @return Operation status. + */ +par_status_t par_if_init(void); +/** + * @brief Deinitialize the interface layer. + * @return Operation status. + */ +par_status_t par_if_deinit(void); +/** + * @brief Acquire the parameter mutex for one parameter path. + * @param par_num Parameter number. + * @return Operation status. + */ +par_status_t par_if_aquire_mutex(const par_num_t par_num); +/** + * @brief Release the parameter mutex for one parameter path. + * @param par_num Parameter number. + */ +void par_if_release_mutex(const par_num_t par_num); +/** + * @brief Calculate the table hash for parameter metadata. + * @param p_data Pointer to the input bytes. + * @param size Number of bytes in p_data. + * @param p_hash Pointer to the output hash buffer. + */ +void par_if_calc_hash(const uint8_t * const p_data, const uint32_t size, uint8_t * const p_hash); -//////////////////////////////////////////////////////////////////////////////// -// Definitions -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// Functions Prototypes -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_if_init (void); -par_status_t par_if_aquire_mutex (const par_num_t par_num); -void par_if_release_mutex (const par_num_t par_num); -void par_if_calc_hash (const uint8_t * const p_data, const uint32_t size, uint8_t * const p_hash); - -#endif // _PAR_IF_H_ +#endif /* _PAR_IF_H_ */ diff --git a/src/par_layout.c b/src/par_layout.c index 2749bc9..00e9de8 100644 --- a/src/par_layout.c +++ b/src/par_layout.c @@ -1,121 +1,110 @@ -// Copyright (c) 2026 Ziga Miklosic -// All Rights Reserved -// This software is under MIT licence (https://opensource.org/licenses/MIT) -//////////////////////////////////////////////////////////////////////////////// /** -*@file par_layout.c -*@brief Parameter storage layout abstraction implementation -*@author wdfk-prog -*@email 1425075683@qq.com -*@date 16.03.2026 -*@version V3.0.1 -*/ -//////////////////////////////////////////////////////////////////////////////// -/** -*@addtogroup PAR_LAYOUT -* @{ -*/ -//////////////////////////////////////////////////////////////////////////////// + * @file par_layout.c + * @brief Implement parameter storage layout helpers. + * @author wdfk-prog () + * @version 1.0 + * @date 2026-03-27 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-27 1.0 wdfk-prog first version + */ -//////////////////////////////////////////////////////////////////////////////// -// Includes -//////////////////////////////////////////////////////////////////////////////// +/** + * @addtogroup PAR_LAYOUT + * @{ + */ +/** + * @brief Include dependencies. + */ #include "par_layout.h" #include "par.h" - -//////////////////////////////////////////////////////////////////////////////// -// Definitions -//////////////////////////////////////////////////////////////////////////////// +/** + * @brief Compile-time definitions. + */ PAR_STATIC_ASSERT(par_layout_max_par_num_fit_u16, (ePAR_NUM_OF <= PAR_UINT16_MAX)); PAR_STATIC_ASSERT(par_layout_compile_count_sum_match, ((uint32_t)PAR_LAYOUT_COMPILE_COUNT_SUM == (uint32_t)ePAR_NUM_OF)); -static const par_layout_count_t gs_layout_count = -{ +static const par_layout_count_t gs_layout_count = { .count8 = (uint16_t)PAR_STORAGE_COUNT8, .count16 = (uint16_t)PAR_STORAGE_COUNT16, .count32 = (uint16_t)PAR_STORAGE_COUNT32, }; - /** - * Runtime-generated offset table. - * - * @note Offsets are generated with a plain runtime scan loop instead of macro - * expansion to keep the mapping logic readable and easy to debug. - * @note When PAR_CFG_LAYOUT_SOURCE is PAR_CFG_LAYOUT_SCRIPT, runtime fill is - * compile-time disabled and PAR_LAYOUT_STATIC_OFFSET_TABLE is consumed - * directly, avoiding extra maintenance complexity. + * @brief Runtime-generated offset table. + * @note Offsets are generated with a plain runtime scan loop instead of macro. expansion to keep the mapping logic readable and easy to debug. + * @note When PAR_CFG_LAYOUT_SOURCE is PAR_CFG_LAYOUT_SCRIPT, runtime fill is. compile-time disabled and PAR_LAYOUT_STATIC_OFFSET_TABLE is consumed. directly, avoiding extra maintenance complexity. */ static uint16_t gs_runtime_offset[ePAR_NUM_OF] = { 0u }; static const uint16_t *gsp_active_offset = gs_runtime_offset; -#if ( PAR_CFG_LAYOUT_SOURCE == PAR_CFG_LAYOUT_SCRIPT ) - #ifndef PAR_LAYOUT_STATIC_OFFSET_TABLE - #error "PAR_LAYOUT_STATIC_OFFSET_TABLE must be provided by static layout include!" - #endif +#if (PAR_CFG_LAYOUT_SOURCE == PAR_CFG_LAYOUT_SCRIPT) +#ifndef PAR_LAYOUT_STATIC_OFFSET_TABLE +#error "PAR_LAYOUT_STATIC_OFFSET_TABLE must be provided by static layout include!" +#endif #endif -//////////////////////////////////////////////////////////////////////////////// -// Functions -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// /** -* Initialize active parameter layout source -* -* @note COMPILE_SCAN mode fills runtime offset table. -* SCRIPT mode directly consumes static script table. -* -* @return void -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Function declarations and definitions. + */ +/** + * @brief Initialize active parameter layout source. + * + * @note COMPILE_SCAN mode fills runtime offset table. + * SCRIPT mode directly consumes static script table. + */ void par_layout_init(void) { -#if ( PAR_CFG_LAYOUT_SOURCE == PAR_CFG_LAYOUT_COMPILE_SCAN ) +#if (PAR_CFG_LAYOUT_SOURCE == PAR_CFG_LAYOUT_COMPILE_SCAN) par_layout_count_t scan_count = { 0u, 0u, 0u }; gsp_active_offset = gs_runtime_offset; - for ( uint32_t par_it = 0u; par_it < (uint32_t) ePAR_NUM_OF; par_it++ ) + for (uint32_t par_it = 0u; par_it < (uint32_t)ePAR_NUM_OF; par_it++) { - const par_cfg_t * const p_cfg = par_cfg_get((par_num_t) par_it ); - switch ( p_cfg->type ) + const par_cfg_t * const p_cfg = par_cfg_get((par_num_t)par_it); + switch (p_cfg->type) { - case ePAR_TYPE_U8: - case ePAR_TYPE_I8: - gs_runtime_offset[par_it] = scan_count.count8; - scan_count.count8++; - break; + case ePAR_TYPE_U8: + case ePAR_TYPE_I8: + gs_runtime_offset[par_it] = scan_count.count8; + scan_count.count8++; + break; - case ePAR_TYPE_U16: - case ePAR_TYPE_I16: - gs_runtime_offset[par_it] = scan_count.count16; - scan_count.count16++; - break; + case ePAR_TYPE_U16: + case ePAR_TYPE_I16: + gs_runtime_offset[par_it] = scan_count.count16; + scan_count.count16++; + break; - case ePAR_TYPE_U32: - case ePAR_TYPE_I32: -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) - case ePAR_TYPE_F32: + case ePAR_TYPE_U32: + case ePAR_TYPE_I32: +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + case ePAR_TYPE_F32: #endif - gs_runtime_offset[par_it] = scan_count.count32; - scan_count.count32++; - break; + gs_runtime_offset[par_it] = scan_count.count32; + scan_count.count32++; + break; - case ePAR_TYPE_NUM_OF: - default: - PAR_DBG_PRINT( "ERR, PAR layout unsupported type at par_num=%u", (unsigned) par_it ); - PAR_ASSERT( 0 ); - return; + case ePAR_TYPE_NUM_OF: + default: + PAR_DBG_PRINT("ERR, PAR layout unsupported type at par_num=%u", (unsigned)par_it); + PAR_ASSERT(0); + return; } } - if ( ( scan_count.count8 != gs_layout_count.count8 ) || ( scan_count.count16 != gs_layout_count.count16 ) || ( scan_count.count32 != gs_layout_count.count32 ) ) + if ((scan_count.count8 != gs_layout_count.count8) || (scan_count.count16 != gs_layout_count.count16) || (scan_count.count32 != gs_layout_count.count32)) { - PAR_DBG_PRINT( "ERR, PAR layout count mismatch: scan=(%u,%u,%u), cfg=(%u,%u,%u)", - (unsigned) scan_count.count8, - (unsigned) scan_count.count16, - (unsigned) scan_count.count32, - (unsigned) gs_layout_count.count8, - (unsigned) gs_layout_count.count16, - (unsigned) gs_layout_count.count32 ); - PAR_ASSERT( 0 ); + PAR_DBG_PRINT("ERR, PAR layout count mismatch: scan=(%u,%u,%u), cfg=(%u,%u,%u)", + (unsigned)scan_count.count8, + (unsigned)scan_count.count16, + (unsigned)scan_count.count32, + (unsigned)gs_layout_count.count8, + (unsigned)gs_layout_count.count16, + (unsigned)gs_layout_count.count32); + PAR_ASSERT(0); return; } #else @@ -124,46 +113,34 @@ void par_layout_init(void) return; #endif } - -//////////////////////////////////////////////////////////////////////////////// /** -* Get active offset table pointer -* -* @return p_table - Pointer to active offset table -*/ -//////////////////////////////////////////////////////////////////////////////// -const uint16_t * par_layout_get_offset_table(void) + * @brief Get active offset table pointer. + * + * @return Pointer to active offset table. + */ +const uint16_t *par_layout_get_offset_table(void) { return gsp_active_offset; } - -//////////////////////////////////////////////////////////////////////////////// /** -* Get offset by parameter number -* -* @param[in] par_num - Parameter number (enumeration) -* @return offset - Offset inside type group storage -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Get offset by parameter number. + * + * @param par_num Parameter number (enumeration). + * @return Offset inside type group storage. + */ uint16_t par_layout_get_offset(const par_num_t par_num) { return gsp_active_offset[par_num]; } - -//////////////////////////////////////////////////////////////////////////////// /** -* Get per-width layout counts -* -* @return count - Number of 8/16/32-bit parameters -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Get per-width layout counts. + * + * @return Number of 8/16/32-bit parameters. + */ par_layout_count_t par_layout_get_count(void) { return gs_layout_count; } - -//////////////////////////////////////////////////////////////////////////////// /** -* @} -*/ -//////////////////////////////////////////////////////////////////////////////// + * @} + */ diff --git a/src/par_layout.h b/src/par_layout.h index 7ae0e05..2d1d153 100644 --- a/src/par_layout.h +++ b/src/par_layout.h @@ -1,23 +1,23 @@ -// Copyright (c) 2026 Ziga Miklosic -// All Rights Reserved -// This software is under MIT licence (https://opensource.org/licenses/MIT) -//////////////////////////////////////////////////////////////////////////////// /** -*@file par_layout.h -*@brief Parameter storage layout abstraction -*@author wdfk-prog -*@email 1425075683@qq.com -*@date 16.03.2026 -*@version V3.0.1 -*/ -//////////////////////////////////////////////////////////////////////////////// + * @file par_layout.h + * @brief Declare parameter storage layout helpers. + * @author wdfk-prog () + * @version 1.0 + * @date 2026-03-27 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-27 1.0 wdfk-prog first version + */ #ifndef _PAR_LAYOUT_H_ #define _PAR_LAYOUT_H_ - -//////////////////////////////////////////////////////////////////////////////// -// Includes -//////////////////////////////////////////////////////////////////////////////// +/** + * @brief Include dependencies. + */ #include #include "par_cfg.h" @@ -26,10 +26,9 @@ #ifdef __cplusplus extern "C" { #endif - -//////////////////////////////////////////////////////////////////////////////// -// Definitions -//////////////////////////////////////////////////////////////////////////////// +/** + * @brief Compile-time definitions. + */ typedef struct { uint16_t count8; @@ -37,31 +36,46 @@ typedef struct uint16_t count32; } par_layout_count_t; -#if ( PAR_CFG_LAYOUT_SOURCE == PAR_CFG_LAYOUT_COMPILE_SCAN ) - #define PAR_STORAGE_COUNT8 (PAR_LAYOUT_COMPILE_COUNT8) - #define PAR_STORAGE_COUNT16 (PAR_LAYOUT_COMPILE_COUNT16) - #define PAR_STORAGE_COUNT32 (PAR_LAYOUT_COMPILE_COUNT32) -#elif ( PAR_CFG_LAYOUT_SOURCE == PAR_CFG_LAYOUT_SCRIPT ) - #include PAR_CFG_LAYOUT_STATIC_INCLUDE - #define PAR_STORAGE_COUNT8 (PAR_LAYOUT_STATIC_COUNT8) - #define PAR_STORAGE_COUNT16 (PAR_LAYOUT_STATIC_COUNT16) - #define PAR_STORAGE_COUNT32 (PAR_LAYOUT_STATIC_COUNT32) +#if (PAR_CFG_LAYOUT_SOURCE == PAR_CFG_LAYOUT_COMPILE_SCAN) +#define PAR_STORAGE_COUNT8 (PAR_LAYOUT_COMPILE_COUNT8) +#define PAR_STORAGE_COUNT16 (PAR_LAYOUT_COMPILE_COUNT16) +#define PAR_STORAGE_COUNT32 (PAR_LAYOUT_COMPILE_COUNT32) +#elif (PAR_CFG_LAYOUT_SOURCE == PAR_CFG_LAYOUT_SCRIPT) +#include PAR_CFG_LAYOUT_STATIC_INCLUDE +#define PAR_STORAGE_COUNT8 (PAR_LAYOUT_STATIC_COUNT8) +#define PAR_STORAGE_COUNT16 (PAR_LAYOUT_STATIC_COUNT16) +#define PAR_STORAGE_COUNT32 (PAR_LAYOUT_STATIC_COUNT32) #else - #error "Unsupported PAR_CFG_LAYOUT_SOURCE value!" +#error "Unsupported PAR_CFG_LAYOUT_SOURCE value!" #endif -#define PAR_STORAGE_NONZERO(count_) (((count_) > 0u) ? (count_) : 1u) - -//////////////////////////////////////////////////////////////////////////////// -// Functions -//////////////////////////////////////////////////////////////////////////////// -const uint16_t * par_layout_get_offset_table(void); -uint16_t par_layout_get_offset(const par_num_t par_num); +#define PAR_STORAGE_NONZERO(count_) (((count_) > 0u) ? (count_) : 1u) +/** + * @brief Function declarations and definitions. + */ +/** + * @brief Return the parameter offset table. + * @return Pointer to the offset table. + */ +const uint16_t *par_layout_get_offset_table(void); +/** + * @brief Return the storage offset for one parameter. + * @param par_num Parameter number. + * @return Storage offset for par_num. + */ +uint16_t par_layout_get_offset(const par_num_t par_num); +/** + * @brief Return the grouped storage counts. + * @return Grouped storage counts for 8-bit, 16-bit, and 32-bit data. + */ par_layout_count_t par_layout_get_count(void); -void par_layout_init(void); +/** + * @brief Initialize the storage layout metadata. + */ +void par_layout_init(void); #ifdef __cplusplus } #endif -#endif // _PAR_LAYOUT_H_ +#endif /* _PAR_LAYOUT_H_ */ diff --git a/src/par_nvm.c b/src/par_nvm.c index 3e35591..e7b55af 100644 --- a/src/par_nvm.c +++ b/src/par_nvm.c @@ -1,1018 +1,829 @@ -// Copyright (c) 2026 Ziga Miklosic -// All Rights Reserved -// This software is under MIT licence (https://opensource.org/licenses/MIT) -//////////////////////////////////////////////////////////////////////////////// /** -*@file par_nvm.c -*@brief Parameter storage to non-volatile memory -*@author Ziga Miklosic -*@email ziga.miklosic@gmail.com -*@date 29.01.2026 -*@version V3.0.1 -*/ -//////////////////////////////////////////////////////////////////////////////// + * @file par_nvm.c + * @brief Implement non-volatile storage support for parameters. + * @author Ziga Miklosic + * @version V3.0.1 + * @date 2026-01-29 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-01-29 V3.0.1 Ziga Miklosic + */ + +/** + * @addtogroup PAR_NVM + * @{ + * + * Parameter storage to non-volatile memory handling. + * + * + * @pre NVM module shall have memory region called "Parameters". + * + * NVM module may already be initialized by the application, or it. + * may be initialized on demand by this module when needed. + * + * @brief This module is responsible for parameter NVM object creation and. + * storage manipulation. NVM parameter object consist of it's value. + * and a CRC value for validation purposes. + * + * Parameter storage is reserved in "Parameters" region of NVM. Look. + * at the nvm_cfg.h/c module for NVM region descriptions. + * + * Parameters stored into NVM in little endianness format. + * + * For details how parameters are handled in NVM go look at the. + * documentation. + * + * @note RULES OF "PAR_CFG_TABLE_ID_CHECK_EN" SETTINGS: + * + * It is normal that parameter table will change during development. + * and therefore code will detect table change between "RAM" and "NVM". + * (detection of par table change enable/disable with. + * "PAR_CFG_TABLE_ID_CHECK_EN" setting). + * + * But as SW is released and potential at customer, developer must not. + * change the pre-existing parameter table as it will loose all values. + * stored inside NVM. Therefore it is recommended to disable table ID. + * detection after first release of SW. Adding new parameters to pre-existing. + * table has no harm at all nor does it have any side effects. + */ /** -*@addtogroup PAR_NVM -* @{ -* -* Parameter storage to non-volatile memory handling. -* -* -* @pre NVM module shall have memory region called "Parameters". -* -* NVM module must be initialized before calling any of following -* functions. -* -* @brief This module is responsible for parameter NVM object creation and -* storage manipulation. NVM parameter object consist of it's value -* and a CRC value for validation purposes. -* -* Parameter storage is reserved in "Parameters" region of NVM. Look -* at the nvm_cfg.h/c module for NVM region descriptions. -* -* Parameters stored into NVM in little endianness format. -* -* For details how parameters are handled in NVM go look at the -* documentation. -* -* @note RULES OF "PAR_CFG_TABLE_ID_CHECK_EN" SETTINGS: -* -* It is normal that parameter table will change during development -* and therefore code will detect table change between "RAM" and "NVM" -* (detection of par table change enable/disable with -* "PAR_CFG_TABLE_ID_CHECK_EN" setting). -* -* But as SW is released and potential at customer, developer must not -* change the pre-existing parameter table as it will loose all values -* stored inside NVM. Therefore it is recommended to disable table ID -* detection after first release of SW. Adding new parameters to pre-existing -* table has no harm at all nor does it have any side effects. -*/ -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// Includes -//////////////////////////////////////////////////////////////////////////////// + * @brief Include dependencies. + */ #include "par_nvm.h" #include "par_cfg.h" #include "par_if.h" #if ( 1 == PAR_CFG_NVM_EN ) - #include - #include - - #include "middleware/nvm/nvm/src/nvm.h" - - /** - * Check NVM module compatibility - */ - _Static_assert( 2 == NVM_VER_MAJOR ); - _Static_assert( 1 <= NVM_VER_MINOR ); - - //////////////////////////////////////////////////////////////////////////////// - // Definitions - //////////////////////////////////////////////////////////////////////////////// - - /** - * Parameter signature and size in bytes - */ - #define PAR_NVM_SIGN ( 0xFF00AA55 ) - #define PAR_NVM_SIGN_SIZE ( 4U ) - - /** - * Parameter header number of object size - * - * Unit: byte - */ - #define PAR_NVM_NB_OF_OBJ_SIZE ( 2U ) - - /** - * Parameter CRC size - * - * Unit: byte - */ - #define PAR_NVM_CRC_SIZE ( 2U ) - - /** - * Parameter configuration hash size - * - * Unit: byte - */ - #define PAR_NVM_HASH_SIZE ( 32U ) - - /** - * Parameter NVM header content address start - * - * @note This is offset to reserved NVM region. For absolute address - * add that value to NVM start region. - */ - #define PAR_NVM_HEAD_ADDR ( 0x00 ) - #define PAR_NVM_HEAD_SIGN_ADDR ( PAR_NVM_HEAD_ADDR ) - #define PAR_NVM_HEAD_NB_OF_OBJ_ADDR ( PAR_NVM_HEAD_SIGN_ADDR + PAR_NVM_SIGN_SIZE ) - #define PAR_NVM_HEAD_CRC_ADDR ( PAR_NVM_HEAD_NB_OF_OBJ_ADDR + PAR_NVM_NB_OF_OBJ_SIZE ) - #define PAR_NVM_HEAD_HASH_ADDR ( PAR_NVM_HEAD_CRC_ADDR + PAR_NVM_CRC_SIZE ) - - /** - * Parameters first data object start address - * - * Unit: byte - */ - #define PAR_NVM_FIRST_DATA_OBJ_ADDR ( PAR_NVM_HEAD_HASH_ADDR + PAR_NVM_HASH_SIZE ) - - /** - * Parameter NVM header object - */ - typedef struct - { - uint32_t sign; /** +#include - switch ( par_get_type( par_num ) ) - { - case ePAR_TYPE_U8: - return par_set_u8_fast( par_num, *(const uint8_t*) p_val ); +#include "middleware/nvm/nvm/src/nvm.h" - case ePAR_TYPE_I8: - return par_set_i8_fast( par_num, *(const int8_t*) p_val ); +/** + * @brief Check NVM module compatibility. + */ +_Static_assert(2 == NVM_VER_MAJOR); +_Static_assert(1 <= NVM_VER_MINOR); +/** + * @brief Compile-time definitions. + */ +/** + * @brief Parameter signature and size in bytes. + */ +#define PAR_NVM_SIGN (0xFF00AA55) +#define PAR_NVM_SIGN_SIZE (4U) - case ePAR_TYPE_U16: - return par_set_u16_fast( par_num, *(const uint16_t*) p_val ); +/** + * @brief Parameter header number of object size. + * @details Unit: byte. + */ +#define PAR_NVM_NB_OF_OBJ_SIZE (2U) - case ePAR_TYPE_I16: - return par_set_i16_fast( par_num, *(const int16_t*) p_val ); +/** + * @brief Parameter CRC size. + * @details Unit: byte. + */ +#define PAR_NVM_CRC_SIZE (2U) - case ePAR_TYPE_U32: - return par_set_u32_fast( par_num, *(const uint32_t*) p_val ); +/** + * @brief Parameter configuration hash size. + * @details Unit: byte. + */ +#define PAR_NVM_HASH_SIZE (32U) + +/** + * @brief Parameter NVM header content address start. + * + * @note This is offset to reserved NVM region. For absolute address. + * add that value to NVM start region. + */ +#define PAR_NVM_HEAD_ADDR (0x00) +#define PAR_NVM_HEAD_SIGN_ADDR (PAR_NVM_HEAD_ADDR) +#define PAR_NVM_HEAD_NB_OF_OBJ_ADDR (PAR_NVM_HEAD_SIGN_ADDR + PAR_NVM_SIGN_SIZE) +#define PAR_NVM_HEAD_CRC_ADDR (PAR_NVM_HEAD_NB_OF_OBJ_ADDR + PAR_NVM_NB_OF_OBJ_SIZE) +#define PAR_NVM_HEAD_HASH_ADDR (PAR_NVM_HEAD_CRC_ADDR + PAR_NVM_CRC_SIZE) - case ePAR_TYPE_I32: - return par_set_i32_fast( par_num, *(const int32_t*) p_val ); +/** + * @brief Parameters first data object start address. + * @details Unit: byte. + */ +#define PAR_NVM_FIRST_DATA_OBJ_ADDR (PAR_NVM_HEAD_HASH_ADDR + PAR_NVM_HASH_SIZE) -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) - case ePAR_TYPE_F32: - return par_set_f32_fast( par_num, *(const float32_t*) p_val ); +/** + * @brief Parameter NVM header object. + */ +typedef struct +{ + uint32_t sign; /**< Signature. */ + uint16_t obj_nb; /**< Stored data object number. */ + uint16_t crc; /**< Header CRC. */ +} par_nvm_head_obj_t; +/** + * @brief Parameter NVM data object. + */ +typedef struct +{ + uint16_t id; /**< Parameter ID. */ + uint8_t size; /**< Size of parameter data block. */ + uint8_t crc; /**< CRC of parameter value. */ + par_type_t data; /**< 4-byte storage for parameter value. */ +} par_nvm_data_obj_t; +/** + * @brief Parameter NVM LUT talbe. + */ +typedef struct +{ + uint32_t addr; /**< Start address of parameter. */ + uint16_t id; /**< ID of stored parameter. */ + bool valid; /**< Valid entry. */ +} par_nvm_lut_t; +/** + * @brief Module-scope variables. + */ +/** + * @brief Initialization guard. + */ +static bool gb_is_init = false; +/** + * @brief Ownership guard for the underlying NVM module. + */ +static bool gb_is_nvm_owner = false; +/** + * @brief Parameter NVM lut. + */ +static par_nvm_lut_t g_par_nvm_data_obj_addr[ePAR_NUM_OF] = { 0 }; +/** + * @brief Function declarations. + */ +static par_status_t par_nvm_load_all(const uint16_t num_of_par); +static par_status_t par_nvm_restore_fast(const par_num_t par_num, const void *p_val); + +static par_status_t par_nvm_corrupt_signature(void); +static par_status_t par_nvm_read_header(par_nvm_head_obj_t * const p_head_obj); +static par_status_t par_nvm_write_header(const uint16_t num_of_par); +static par_status_t par_nvm_validate_header(uint16_t * const p_num_of_par); + +static uint16_t par_nvm_calc_crc(const uint8_t * const p_data, const uint8_t size); +static uint8_t par_nvm_calc_obj_crc(const par_nvm_data_obj_t * const p_obj); +static uint16_t par_nvm_get_per_par(void); + +static void par_nvm_build_new_nvm_lut(void); +static uint32_t par_nvm_get_nvm_lut_addr(const uint16_t id); +static bool par_nvm_is_in_nvm_lut(const uint16_t id); + +#if (1 == PAR_CFG_TABLE_ID_CHECK_EN) +static par_status_t par_nvm_erase_signature(void); +static par_status_t par_nvm_check_table_id(const uint8_t * const p_table_id); +static par_status_t par_nvm_write_table_id(const uint8_t * const p_table_id); #endif - case ePAR_TYPE_NUM_OF: - default: - return ePAR_ERROR_TYPE; - } +static par_status_t par_nvm_init_nvm(void); +static par_status_t par_nvm_sync(void); +/** + * @brief Function declarations and definitions. + */ +static par_status_t par_nvm_restore_fast(const par_num_t par_num, const void *p_val) +{ + if (NULL == p_val) + { + return ePAR_ERROR_PARAM; } - //////////////////////////////////////////////////////////////////////////////// - /** - * Corrupt parameter signature to NVM - * - * @brief Return ePAR_OK if signature corrupted OK. In case of NVM error it returns - * ePAR_ERROR_NVM. - * - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - static par_status_t par_nvm_corrupt_signature(void) + switch (par_get_type(par_num)) { - par_status_t status = ePAR_OK; - - if ( eNVM_OK != nvm_erase( PAR_CFG_NVM_REGION, PAR_NVM_HEAD_SIGN_ADDR, PAR_NVM_SIGN_SIZE )) - { - status = ePAR_ERROR_NVM; - PAR_DBG_PRINT( "PAR_NVM: NVM error during signature corruption!" ); - } - - return status; - } + case ePAR_TYPE_U8: + return par_set_u8_fast(par_num, *(const uint8_t *)p_val); - #if ( 1 == PAR_CFG_TABLE_ID_CHECK_EN ) + case ePAR_TYPE_I8: + return par_set_i8_fast(par_num, *(const int8_t *)p_val); - //////////////////////////////////////////////////////////////////////////////// - /** - * Erase parameter signature from NVM - * - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - static par_status_t par_nvm_erase_signature(void) - { - par_status_t status = ePAR_OK; + case ePAR_TYPE_U16: + return par_set_u16_fast(par_num, *(const uint16_t *)p_val); - status = nvm_erase( PAR_CFG_NVM_REGION, PAR_NVM_SIGNATURE_ADDR_OFFSET, 4U ); + case ePAR_TYPE_I16: + return par_set_i16_fast(par_num, *(const int16_t *)p_val); - return status; - } + case ePAR_TYPE_U32: + return par_set_u32_fast(par_num, *(const uint32_t *)p_val); - //////////////////////////////////////////////////////////////////////////////// - /** - * Check unique parameter table ID - * - * @brief This function check for parameter configuration table change while - * some parameters are already stored in NVM. First it read stored - * table ID from NVM and then compare it with current table ID (live - * from RAM). In case of mismatched it return error. - * - * Table ID is being calculated based on hash algorithm (SHA-256). - * - * @param[in] p_table_id - Pointer to reference table ID (in "RAM") - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - static par_status_t par_nvm_check_table_id(const uint8_t * const p_table_id) - { - par_status_t status = ePAR_OK; - uint8_t nvm_table_id[32] = { 0 }; + case ePAR_TYPE_I32: + return par_set_i32_fast(par_num, *(const int32_t *)p_val); - if ( eNVM_OK != nvm_read( eNVM_REGION_EEPROM_RUN_PAR, PAR_NVM_TABLE_ID_ADDR_OFFSET, 32U, (uint8_t*) &nvm_table_id )) - { - status = ePAR_ERROR_NVM; - } - else - { - // Table ID is the same in "RAM" and in NVM - if ( 0 == memcmp( &nvm_table_id, p_table_id, 32U )) - { - status = ePAR_OK; - } +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + case ePAR_TYPE_F32: + return par_set_f32_fast(par_num, *(const float32_t *)p_val); +#endif - // Different table ID found - else - { - status = ePAR_ERROR; - } - } + case ePAR_TYPE_NUM_OF: + default: + return ePAR_ERROR_TYPE; + } +} +/** + * Corrupt parameter signature to NVM. + * + * @brief Return ePAR_OK if signature corrupted OK. In case of NVM error it returns. + * ePAR_ERROR_NVM. + * + * @return Status of operation. + */ +static par_status_t par_nvm_corrupt_signature(void) +{ + par_status_t status = ePAR_OK; + + if (eNVM_OK != nvm_erase(PAR_CFG_NVM_REGION, PAR_NVM_HEAD_SIGN_ADDR, PAR_NVM_SIGN_SIZE)) + { + status = ePAR_ERROR_NVM; + PAR_DBG_PRINT("PAR_NVM: NVM error during signature corruption!"); + } - return status; - } + return status; +} - //////////////////////////////////////////////////////////////////////////////// - /** - * Write unique parameter table ID to NVM - * - * @param[in] p_table_id - Pointer to reference table ID (in "RAM") - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - static par_status_t par_nvm_write_table_id(const uint8_t * const p_table_id) +#if (1 == PAR_CFG_TABLE_ID_CHECK_EN) +/** + * @brief Erase parameter signature from NVM. + * + * @return Status of operation. + */ +static par_status_t par_nvm_erase_signature(void) +{ + par_status_t status = ePAR_OK; + + status = nvm_erase(PAR_CFG_NVM_REGION, PAR_NVM_SIGNATURE_ADDR_OFFSET, 4U); + + return status; +} +/** + * Check unique parameter table ID. + * + * @brief This function check for parameter configuration table change while. + * some parameters are already stored in NVM. First it read stored. + * table ID from NVM and then compare it with current table ID (live. + * from RAM). In case of mismatched it return error. + * + * Table ID is being calculated based on hash algorithm (SHA-256). + * + * @param p_table_id Pointer to reference table ID (in "RAM"). + * @return Status of operation. + */ +static par_status_t par_nvm_check_table_id(const uint8_t * const p_table_id) +{ + par_status_t status = ePAR_OK; + uint8_t nvm_table_id[32] = { 0 }; + + if (eNVM_OK != nvm_read(eNVM_REGION_EEPROM_RUN_PAR, PAR_NVM_TABLE_ID_ADDR_OFFSET, 32U, (uint8_t *)&nvm_table_id)) + { + status = ePAR_ERROR_NVM; + } + else + { + if (0 == memcmp(&nvm_table_id, p_table_id, 32U)) { - par_status_t status = ePAR_OK; - - if ( eNVM_OK != nvm_write( eNVM_REGION_EEPROM_RUN_PAR, PAR_NVM_TABLE_ID_ADDR_OFFSET, 32U, p_table_id )) - { - status = ePAR_ERROR_NVM; - } - - return status; + status = ePAR_OK; } - #endif // 1 == PAR_CFG_TABLE_ID_CHECK_EN - - //////////////////////////////////////////////////////////////////////////////// - /** - * Read parameter NVM header - * - * @param[in] p_head_obj - Pointer to parameter NVM header - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - static par_status_t par_nvm_read_header(par_nvm_head_obj_t * const p_head_obj) - { - par_status_t status = ePAR_OK; - - PAR_ASSERT( NULL != p_head_obj ); - - if ( eNVM_OK != nvm_read( PAR_CFG_NVM_REGION, PAR_NVM_HEAD_ADDR, sizeof( par_nvm_head_obj_t ), (uint8_t*) p_head_obj )) + else { - status = ePAR_ERROR_NVM; - PAR_DBG_PRINT( "PAR_NVM: NVM error during header read!" ); + status = ePAR_ERROR; } - - return status; } - //////////////////////////////////////////////////////////////////////////////// - /** - * Write parameter NVM header - * - * @param[in] num_of_par - Number of persistent parameters that are stored in NVM - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - static par_status_t par_nvm_write_header(const uint16_t num_of_par) + return status; +} +/** + * @brief Write unique parameter table ID to NVM. + * + * @param p_table_id Pointer to reference table ID (in "RAM"). + * @return Status of operation. + */ +static par_status_t par_nvm_write_table_id(const uint8_t * const p_table_id) +{ + par_status_t status = ePAR_OK; + + if (eNVM_OK != nvm_write(eNVM_REGION_EEPROM_RUN_PAR, PAR_NVM_TABLE_ID_ADDR_OFFSET, 32U, p_table_id)) { - par_status_t status = ePAR_OK; - par_nvm_head_obj_t head_obj = {0}; - - // Add number of objects - head_obj.obj_nb = num_of_par; - - // Calculate CRC - head_obj.crc = par_nvm_calc_crc((uint8_t*) &head_obj.obj_nb, PAR_NVM_NB_OF_OBJ_SIZE ); - - // Set signature - head_obj.sign = PAR_NVM_SIGN; - - // Write num of object and CRC - if ( eNVM_OK != nvm_write( PAR_CFG_NVM_REGION, PAR_NVM_HEAD_ADDR, sizeof( par_nvm_head_obj_t ), (const uint8_t*) &head_obj )) - { - status = ePAR_ERROR_NVM; - PAR_DBG_PRINT( "PAR_NVM: NVM error during header write!" ); - } + status = ePAR_ERROR_NVM; + } - PAR_DBG_PRINT( "PAR_NVM: Write NVM header with %d nb. of object", num_of_par ); + return status; +} - return status; +#endif /* 1 == PAR_CFG_TABLE_ID_CHECK_EN */ +/** + * @brief Read parameter NVM header. + * + * @param p_head_obj Pointer to parameter NVM header. + * @return Status of operation. + */ +static par_status_t par_nvm_read_header(par_nvm_head_obj_t * const p_head_obj) +{ + par_status_t status = ePAR_OK; + + PAR_ASSERT(NULL != p_head_obj); + + if (eNVM_OK != nvm_read(PAR_CFG_NVM_REGION, PAR_NVM_HEAD_ADDR, sizeof(par_nvm_head_obj_t), (uint8_t *)p_head_obj)) + { + status = ePAR_ERROR_NVM; + PAR_DBG_PRINT("PAR_NVM: NVM error during header read!"); } - //////////////////////////////////////////////////////////////////////////////// - /** - * Validate parameter NVM header - * - * @param[out] p_num_of_par - Pointer to number of persistent parameters that are stored in NVM - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - static par_status_t par_nvm_validate_header(uint16_t * const p_num_of_par) + return status; +} +/** + * @brief Write parameter NVM header. + * + * @param num_of_par Number of persistent parameters that are stored in NVM. + * @return Status of operation. + */ +static par_status_t par_nvm_write_header(const uint16_t num_of_par) +{ + par_status_t status = ePAR_OK; + par_nvm_head_obj_t head_obj = { 0 }; + head_obj.obj_nb = num_of_par; + head_obj.crc = par_nvm_calc_crc((uint8_t *)&head_obj.obj_nb, PAR_NVM_NB_OF_OBJ_SIZE); + head_obj.sign = PAR_NVM_SIGN; + if (eNVM_OK != nvm_write(PAR_CFG_NVM_REGION, PAR_NVM_HEAD_ADDR, sizeof(par_nvm_head_obj_t), (const uint8_t *)&head_obj)) { - par_status_t status = ePAR_OK; - par_nvm_head_obj_t obj_head = { 0 }; - uint16_t crc_calc = 0; + status = ePAR_ERROR_NVM; + PAR_DBG_PRINT("PAR_NVM: NVM error during header write!"); + } - // Read header - status = par_nvm_read_header( &obj_head ); + PAR_DBG_PRINT("PAR_NVM: Write NVM header with %d nb. of object", num_of_par); - // NVM error - if ( ePAR_ERROR_NVM == status ) - { - // No actions... - } - else + return status; +} +/** + * @brief Validate parameter NVM header. + * + * @param p_num_of_par Pointer to number of persistent parameters that are stored in NVM. + * @return Status of operation. + */ +static par_status_t par_nvm_validate_header(uint16_t * const p_num_of_par) +{ + par_status_t status = ePAR_OK; + par_nvm_head_obj_t obj_head = { 0 }; + uint16_t crc_calc = 0; + status = par_nvm_read_header(&obj_head); + if (ePAR_ERROR_NVM != status) + { + if (PAR_NVM_SIGN == obj_head.sign) { - // Check for signature - if ( PAR_NVM_SIGN == obj_head.sign ) + crc_calc = par_nvm_calc_crc((uint8_t *)&obj_head.obj_nb, PAR_NVM_NB_OF_OBJ_SIZE); + if (crc_calc == obj_head.crc) { - // Calculate CRC - crc_calc = par_nvm_calc_crc((uint8_t*) &obj_head.obj_nb, PAR_NVM_NB_OF_OBJ_SIZE ); - - // Validate CRC - if ( crc_calc == obj_head.crc ) - { - *p_num_of_par = obj_head.obj_nb; - PAR_DBG_PRINT( "PAR_NVM: HVM header OK! Nb. of stored obj: %d", obj_head.obj_nb ); - } - - // CRC corrupt - else - { - status = ePAR_ERROR_CRC; - PAR_DBG_PRINT( "PAR_NVM: Header CRC corrupted!" ); - } + *p_num_of_par = obj_head.obj_nb; + PAR_DBG_PRINT("PAR_NVM: HVM header OK! Nb. of stored obj: %d", obj_head.obj_nb); } + else { - status = ePAR_ERROR; - PAR_DBG_PRINT( "PAR_NVM: Signature corrupted!" ); + status = ePAR_ERROR_CRC; + PAR_DBG_PRINT("PAR_NVM: Header CRC corrupted!"); } } - - return status; + else + { + status = ePAR_ERROR; + PAR_DBG_PRINT("PAR_NVM: Signature corrupted!"); + } } - //////////////////////////////////////////////////////////////////////////////// - /** - * Calculate CRC-16 - * - * @param[in] p_data - Pointer to data - * @param[in] size - Size of data to calc crc - * @return crc16 - Calculated CRC - */ - //////////////////////////////////////////////////////////////////////////////// - static uint16_t par_nvm_calc_crc(const uint8_t * const p_data, const uint8_t size) + return status; +} +/** + * @brief Calculate CRC-16. + * + * @param p_data Pointer to data. + * @param size Size of data to calc crc. + * @return Calculated CRC. + */ +static uint16_t par_nvm_calc_crc(const uint8_t * const p_data, const uint8_t size) +{ + const uint16_t poly = 0x1021U; // CRC-16-CCITT + const uint16_t seed = 0x1234U; // Custom seed + uint16_t crc16 = seed; + PAR_ASSERT(NULL != p_data); + PAR_ASSERT(size > 0); + + for (uint8_t i = 0; i < size; i++) { - const uint16_t poly = 0x1021U; // CRC-16-CCITT - const uint16_t seed = 0x1234U; // Custom seed - uint16_t crc16 = seed; + crc16 = (crc16 ^ (p_data[i] << 8U)); - // Check input - PAR_ASSERT( NULL != p_data ); - PAR_ASSERT( size > 0 ); - - for (uint8_t i = 0; i < size; i++) + for (uint8_t j = 0U; j < 8U; j++) { - crc16 = ( crc16 ^ ( p_data[i] << 8U )); - - for (uint8_t j = 0U; j < 8U; j++) + if (crc16 & 0x8000) { - if (crc16 & 0x8000) - { - crc16 = (( crc16 << 1U ) ^ poly ); - } - else - { - crc16 = ( crc16 << 1U ); - } + crc16 = ((crc16 << 1U) ^ poly); + } + else + { + crc16 = (crc16 << 1U); } } - - return crc16; - } - - //////////////////////////////////////////////////////////////////////////////// - /** - * Calculate parameter data object CRC - * - * @param[in] p_obj - Pointer to data object - * @return crc16 - Calculated CRC - */ - //////////////////////////////////////////////////////////////////////////////// - static uint8_t par_nvm_calc_obj_crc(const par_nvm_data_obj_t * const p_obj) - { - uint16_t crc = 0; - uint8_t rtn_crc = 0; - - crc = par_nvm_calc_crc((const uint8_t*) &p_obj->id, 2U ); - crc ^= par_nvm_calc_crc((const uint8_t*) &p_obj->size, 1U ); - crc ^= par_nvm_calc_crc((const uint8_t*) &p_obj->data.u8, 4U ); - rtn_crc = ( crc & 0xFFU ); - - return rtn_crc; } - //////////////////////////////////////////////////////////////////////////////// - /** - * Load all parameters value from NVM - * - * @param[in] num_of_par - Number of stored parameters inside NVM - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - static par_status_t par_nvm_load_all(const uint16_t num_of_par) + return crc16; +} +/** + * @brief Calculate parameter data object CRC. + * + * @param p_obj Pointer to data object. + * @return Calculated CRC. + */ +static uint8_t par_nvm_calc_obj_crc(const par_nvm_data_obj_t * const p_obj) +{ + uint16_t crc = 0; + uint8_t rtn_crc = 0; + + crc = par_nvm_calc_crc((const uint8_t *)&p_obj->id, 2U); + crc ^= par_nvm_calc_crc((const uint8_t *)&p_obj->size, 1U); + crc ^= par_nvm_calc_crc((const uint8_t *)&p_obj->data.u8, 4U); + rtn_crc = (crc & 0xFFU); + + return rtn_crc; +} +/** + * @brief Load all parameters value from NVM. + * + * @param num_of_par Number of stored parameters inside NVM. + * @return Status of operation. + */ +static par_status_t par_nvm_load_all(const uint16_t num_of_par) +{ + par_status_t status = ePAR_OK; + par_num_t par_num = 0; + uint16_t i = 0; + uint32_t obj_addr = 0; + par_nvm_data_obj_t obj_data = { 0 }; + nvm_status_t nvm_status = eNVM_OK; + uint8_t crc_calc = 0; + uint16_t per_par_nb = 0; + uint16_t new_par_cnt = 0; + for (i = 0; i < num_of_par; i++) { - par_status_t status = ePAR_OK; - par_num_t par_num = 0; - uint16_t i = 0; - uint32_t obj_addr = 0; - par_nvm_data_obj_t obj_data = {0}; - nvm_status_t nvm_status = eNVM_OK; - uint8_t crc_calc = 0; - uint16_t per_par_nb = 0; - uint16_t new_par_cnt = 0; - - // Loop thru stored NVM object - for ( i = 0; i < num_of_par; i++ ) + /* Each NVM object currently occupies 8 bytes. */ + obj_addr = ((8 * i) + PAR_NVM_FIRST_DATA_OBJ_ADDR); + nvm_status = nvm_read(PAR_CFG_NVM_REGION, obj_addr, sizeof(par_nvm_data_obj_t), (uint8_t *)&obj_data); + if (eNVM_OK == nvm_status) { - // Calculate address - // NOTE: For know fixed 8 bytes! - obj_addr = (( 8 * i ) + PAR_NVM_FIRST_DATA_OBJ_ADDR ); - - // Load parameter NVM object - nvm_status = nvm_read( PAR_CFG_NVM_REGION, obj_addr, sizeof( par_nvm_data_obj_t ), (uint8_t*) &obj_data ); - - // NVM read OK - if ( eNVM_OK == nvm_status ) + crc_calc = par_nvm_calc_obj_crc(&obj_data); + if (crc_calc == obj_data.crc) { - // Calculate CRC - crc_calc = par_nvm_calc_obj_crc( &obj_data ); - - // CRC OK - if ( crc_calc == obj_data.crc ) + if (ePAR_OK == par_get_num_by_id(obj_data.id, &par_num)) { - // Is that parameter in current table - if ( ePAR_OK == par_get_num_by_id( obj_data.id, &par_num )) + /* Restore only parameters that still exist and remain persistent. */ + if (true == par_get_config(par_num)->persistent) { - /** - * Parameter found in device and stored in NVM - * - * Check if that parameter is still persistent! - */ - if ( true == par_get_config(par_num)->persistent ) + if (false == par_nvm_is_in_nvm_lut(obj_data.id)) { - // Check if already in LUT - if ( false == par_nvm_is_in_nvm_lut( obj_data.id )) - { - // Add to NVM lut - g_par_nvm_data_obj_addr[per_par_nb].id = obj_data.id; - g_par_nvm_data_obj_addr[per_par_nb].addr = obj_addr; - g_par_nvm_data_obj_addr[per_par_nb].valid = true; - - // Restore parameter via internal fast path semantics - (void) par_nvm_restore_fast( par_num, &obj_data.data ); - - // Increment current persistent parameter counter - per_par_nb++; - } + g_par_nvm_data_obj_addr[per_par_nb].id = obj_data.id; + g_par_nvm_data_obj_addr[per_par_nb].addr = obj_addr; + g_par_nvm_data_obj_addr[per_par_nb].valid = true; + /* Restore through the internal fast path. */ + (void)par_nvm_restore_fast(par_num, &obj_data.data); + per_par_nb++; } } - - // Parameter not in current table - else - { - // No action... - } - } - - // CRC corrupted - else - { - status = ePAR_ERROR_CRC; - break; } } + else { - status = ePAR_ERROR_NVM; + status = ePAR_ERROR_CRC; break; } } + else + { + status = ePAR_ERROR_NVM; + break; + } + } - PAR_DBG_PRINT( "PAR_NVM: Loading all persistent parameters with status: %s", par_get_status_str(status)); - PAR_DBG_PRINT( "PAR_NVM: Nb. of stored pars in NVM: %d", num_of_par ); - PAR_DBG_PRINT( "PAR_NVM: Nb. of live persistent: \t%d", par_nvm_get_per_par()); - - // Find new persistent parameter - if ( ePAR_OK == status ) + PAR_DBG_PRINT("PAR_NVM: Loading all persistent parameters with status: %s", par_get_status_str(status)); + PAR_DBG_PRINT("PAR_NVM: Nb. of stored pars in NVM: %d", num_of_par); + PAR_DBG_PRINT("PAR_NVM: Nb. of live persistent: \t%d", par_nvm_get_per_par()); + if (ePAR_OK == status) + { + for (i = 0; i < ePAR_NUM_OF; i++) { - for ( i = 0; i < ePAR_NUM_OF; i++ ) - { - const par_cfg_t * const par_cfg = par_get_config(par_num); + const par_cfg_t * const par_cfg = par_get_config(par_num); - if ( true == par_cfg->persistent ) + if (true == par_cfg->persistent) + { + if (false == par_nvm_is_in_nvm_lut(par_cfg->id)) { - if ( false == par_nvm_is_in_nvm_lut( par_cfg->id )) - { - // Is persistent and not jet in NVM lut -> Add to LUT - g_par_nvm_data_obj_addr[per_par_nb].id = par_cfg->id; - g_par_nvm_data_obj_addr[per_par_nb].addr = obj_addr + ( 8U * ( new_par_cnt + 1U )); - g_par_nvm_data_obj_addr[per_par_nb].valid = true; - - // Write new par to NVM - par_save( i ); - - per_par_nb++; - new_par_cnt++; - } + /* Extend the LUT for newly added persistent parameters. */ + g_par_nvm_data_obj_addr[per_par_nb].id = par_cfg->id; + g_par_nvm_data_obj_addr[per_par_nb].addr = obj_addr + (8U * (new_par_cnt + 1U)); + g_par_nvm_data_obj_addr[per_par_nb].valid = true; + par_save(i); + + per_par_nb++; + new_par_cnt++; } } + } - // If there is a new persistent parameter change HVM header - if ( new_par_cnt > 0 ) - { - // Add additional new persistent parameters number to existing one! - // NOTE: In general obj number will only rise! - status |= par_nvm_write_header( num_of_par + new_par_cnt ); - - // Sync NVM - status |= par_nvm_sync(); + if (new_par_cnt > 0) + { + /* The object count only grows when new persistent parameters appear. */ + status |= par_nvm_write_header(num_of_par + new_par_cnt); + status |= par_nvm_sync(); - #if ( PAR_CFG_DEBUG_EN ) - PAR_DBG_PRINT( "PAR_NVM: Added %d new parameters to NVM LUT table!", new_par_cnt ); - #endif - } +#if (PAR_CFG_DEBUG_EN) + PAR_DBG_PRINT("PAR_NVM: Added %d new parameters to NVM LUT table!", new_par_cnt); +#endif } - - return status; } - //////////////////////////////////////////////////////////////////////////////// - /** - * Get total number of persistent parameters - * - * @return num_of_per_par - Number of persistent parameters - */ - //////////////////////////////////////////////////////////////////////////////// - static uint16_t par_nvm_get_per_par(void) + return status; +} +/** + * @brief Get total number of persistent parameters. + * + * @return Number of persistent parameters. + */ +static uint16_t par_nvm_get_per_par(void) +{ + uint16_t num_of_per_par = 0U; + + for (par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++) { - uint16_t num_of_per_par = 0U; - - for ( par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) + if (true == par_get_config(par_num)->persistent) { - if ( true == par_get_config(par_num)->persistent ) - { - num_of_per_par++; - } + num_of_per_par++; } - - return num_of_per_par; } - //////////////////////////////////////////////////////////////////////////////// - /** - * Build new parameter NVM LUT table - * - * @return void - */ - //////////////////////////////////////////////////////////////////////////////// - static void par_nvm_build_new_nvm_lut(void) + return num_of_per_par; +} +/** + * @brief Build new parameter NVM LUT table. + */ +static void par_nvm_build_new_nvm_lut(void) +{ + uint16_t per_par_nb = 0U; + for (par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++) { - uint16_t per_par_nb = 0U; + const par_cfg_t * const par_cfg = par_get_config(par_num); - // Loop thru all parameters - for ( par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) + if (true == par_cfg->persistent) { - const par_cfg_t * const par_cfg = par_get_config( par_num ); - - if ( true == par_cfg->persistent ) + if (0 == per_par_nb) { - // First parameter - if ( 0 == per_par_nb ) - { - g_par_nvm_data_obj_addr[per_par_nb].addr = PAR_NVM_FIRST_DATA_OBJ_ADDR; - } - - // Build consecutive address space - // NOTE: For know each NVM data object is fixed in size (8 bytes) - else - { - // Calculate and add address of next parameter - g_par_nvm_data_obj_addr[per_par_nb].addr = ( g_par_nvm_data_obj_addr[per_par_nb-1].addr + 8U ); - } - - // Store parameter ID - g_par_nvm_data_obj_addr[per_par_nb].id = par_cfg->id; - - // Next persistent parameter - per_par_nb++; + g_par_nvm_data_obj_addr[per_par_nb].addr = PAR_NVM_FIRST_DATA_OBJ_ADDR; } - } - - // Show NVM LUT table - par_nvm_print_nvm_lut(); - } - //////////////////////////////////////////////////////////////////////////////// - /** - * Get parameter NVM object start address based on its ID - * - * @note In case ID is not found there is a problem with building the - * NVM lut!!! - * - * @param[in] id - Parameter ID - * @return obj_addr - NVM address of object with ID - */ - //////////////////////////////////////////////////////////////////////////////// - static uint32_t par_nvm_get_nvm_lut_addr(const uint16_t id) - { - for ( par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) - { - if ( id == g_par_nvm_data_obj_addr[par_num].id ) + /* NVM data objects currently use a fixed 8-byte stride. */ + else { - return g_par_nvm_data_obj_addr[par_num].addr; + g_par_nvm_data_obj_addr[per_par_nb].addr = (g_par_nvm_data_obj_addr[per_par_nb - 1].addr + 8U); } - } - return 0; + g_par_nvm_data_obj_addr[per_par_nb].id = par_cfg->id; + per_par_nb++; + } } - //////////////////////////////////////////////////////////////////////////////// - /** - * Check if parameter is in NVM LUT - * - * @param[in] id - Parameter ID - * @return is_in_lut - Flag that indicated if object is in NVM lut - */ - //////////////////////////////////////////////////////////////////////////////// - static bool par_nvm_is_in_nvm_lut(const uint16_t id ) + par_nvm_print_nvm_lut(); +} +/** + * @brief Get parameter NVM object start address based on its ID. + * + * @note In case ID is not found there is a problem with building the. + * NVM lut!!! + * + * @param id Parameter ID. + * @return NVM address of object with ID. + */ +static uint32_t par_nvm_get_nvm_lut_addr(const uint16_t id) +{ + for (par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++) { - for ( par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) + if (id == g_par_nvm_data_obj_addr[par_num].id) { - if ( ( id == g_par_nvm_data_obj_addr[par_num].id ) - && ( true == g_par_nvm_data_obj_addr[par_num].valid )) - { - return true; - } + return g_par_nvm_data_obj_addr[par_num].addr; } - - return false; } - //////////////////////////////////////////////////////////////////////////////// - /** - * Initialize NVM module - * - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - static par_status_t par_nvm_init_nvm(void) + return 0; +} +/** + * @brief Check if parameter is in NVM LUT. + * + * @param id Parameter ID. + * @return Flag that indicated if object is in NVM lut. + */ +static bool par_nvm_is_in_nvm_lut(const uint16_t id) +{ + for (par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++) { - par_status_t status = ePAR_OK; - bool is_nvm_init = false; - - // First check if NVM is already init - (void) nvm_is_init( &is_nvm_init ); - - // NVM is not jet init - if ( false == is_nvm_init ) + if ((id == g_par_nvm_data_obj_addr[par_num].id) && (true == g_par_nvm_data_obj_addr[par_num].valid)) { - // Init NVM - if ( eNVM_OK != nvm_init()) - { - status = ePAR_ERROR_INIT; - PAR_DBG_PRINT( "PAR_NVM: NVM module init error!" ); - } + return true; } - - return status; } - //////////////////////////////////////////////////////////////////////////////// - /** - * Sync NVM module - * - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - static par_status_t par_nvm_sync(void) + return false; +} +/** + * @brief Initialize NVM module. + * + * @return Status of operation. + */ +static par_status_t par_nvm_init_nvm(void) +{ + par_status_t status = ePAR_OK; + bool is_nvm_init = false; + + gb_is_nvm_owner = false; + (void)nvm_is_init(&is_nvm_init); + if (false == is_nvm_init) { - par_status_t status = ePAR_OK; - - if ( eNVM_OK != nvm_sync( PAR_CFG_NVM_REGION )) + if (eNVM_OK != nvm_init()) { - status = ePAR_ERROR_NVM; + status = ePAR_ERROR_INIT; + PAR_DBG_PRINT("PAR_NVM: NVM module init error!"); + } + else + { + gb_is_nvm_owner = true; } - - return status; } - //////////////////////////////////////////////////////////////////////////////// - /** - * @} - */ - //////////////////////////////////////////////////////////////////////////////// - - //////////////////////////////////////////////////////////////////////////////// - /** - *@addtogroup API_PAR_NVM_FUNCTIONS - * @{ - * - * Following functions are part of Device Parameter NVM manipulation API. - */ - //////////////////////////////////////////////////////////////////////////////// - - //////////////////////////////////////////////////////////////////////////////// - /** - * Initialize parameter NVM handling - * - * @brief Based on settings in "par_cfg.h" initialisation phase is done. - * Settings such as "PAR_CFG_TABLE_ID_CHECK_EN" will affect checking - * for table ID. - * - * @return status - Status of initialisation - */ - //////////////////////////////////////////////////////////////////////////////// - par_status_t par_nvm_init(void) + return status; +} +/** + * @brief Sync NVM module. + * + * @return Status of operation. + */ +static par_status_t par_nvm_sync(void) +{ + par_status_t status = ePAR_OK; + + if (eNVM_OK != nvm_sync(PAR_CFG_NVM_REGION)) { - par_status_t status = ePAR_OK; - uint16_t obj_nb = 0U; - uint16_t per_par_nb = 0U; - - // Init NVM module - status = par_nvm_init_nvm(); + status = ePAR_ERROR_NVM; + } - // NVM driver init OK - if ( ePAR_OK == status ) - { - // Par NVM module init - gb_is_init = true; + return status; +} +/** + * @} + */ - // Get number of persistent parameters - per_par_nb = par_nvm_get_per_par(); +/** + * @addtogroup API_PAR_NVM_FUNCTIONS + * @{ + * + * @brief Following functions are part of Device Parameter NVM manipulation API. + */ - // At least one persistent parameter - if ( per_par_nb > 0 ) +/** + * @brief Initialize parameter NVM handling. + * @details Initialization behavior depends on the settings in par_cfg.h. + * Table-ID checking is enabled only when PAR_CFG_TABLE_ID_CHECK_EN is set. + * @return Status of initialization. + */ +par_status_t par_nvm_init(void) +{ + par_status_t status = ePAR_OK; + uint16_t obj_nb = 0U; + uint16_t per_par_nb = 0U; + status = par_nvm_init_nvm(); + if (ePAR_OK == status) + { + gb_is_init = true; + per_par_nb = par_nvm_get_per_par(); + if (per_par_nb > 0) + { + status = par_nvm_validate_header(&obj_nb); + if (ePAR_OK == status) { - // Validate header - status = par_nvm_validate_header( &obj_nb ); - - // NVM header OK - if ( ePAR_OK == status ) - { - // Check hash - #if ( PAR_CFG_TABLE_ID_CHECK_EN ) - // TODO: ... - #endif - - // Load all parameters - status = par_nvm_load_all( obj_nb ); - - // Load CRC error - if ( ePAR_ERROR_CRC == status ) - { - status = par_nvm_reset_all(); - status |= ( ePAR_WAR_SET_TO_DEF | ePAR_WAR_NVM_REWRITTEN ); - } - - // NVM error - else if ( ePAR_ERROR_NVM == status ) - { - /** - * @note Set all parameters to default as it might happend - * that some of the parameters will be loaded from - * NVM and some will have default values. - * - * System might behave unexpectedly if having some - * default and some modified parameter values! - */ - par_set_all_to_default(); - status |= ePAR_WAR_SET_TO_DEF; - } - else - { - // No actions... - } - } +#if (PAR_CFG_TABLE_ID_CHECK_EN) + /* TODO: add table-ID consistency check. */ +#endif - // Signature NOT OK - // OR Header CRC corrupted - else if ( ( ePAR_ERROR == status ) - || ( ePAR_ERROR_CRC == status )) + status = par_nvm_load_all(obj_nb); + if (ePAR_ERROR_CRC == status) { status = par_nvm_reset_all(); - status |= ( ePAR_WAR_SET_TO_DEF | ePAR_WAR_NVM_REWRITTEN ); + status |= (ePAR_WAR_SET_TO_DEF | ePAR_WAR_NVM_REWRITTEN); } - // NVM Error - else + else if (ePAR_ERROR_NVM == status) { - // No actions... + /* + * Fall back to defaults if NVM contents are only partially + * usable. A mixed state of restored and default values is + * unsafe. + */ + par_set_all_to_default(); + status |= ePAR_WAR_SET_TO_DEF; } } - // No persistent parameters - else + else if ((ePAR_ERROR == status) || (ePAR_ERROR_CRC == status)) { - status |= ePAR_WAR_NO_PERSISTENT; - PAR_DBG_PRINT( "PAR_NVM: No persistent parameters... Nothing to do..." ); + status = par_nvm_reset_all(); + status |= (ePAR_WAR_SET_TO_DEF | ePAR_WAR_NVM_REWRITTEN); } + } - return status; + else + { + status |= ePAR_WAR_NO_PERSISTENT; + PAR_DBG_PRINT("PAR_NVM: No persistent parameters... Nothing to do..."); + } } - //////////////////////////////////////////////////////////////////////////////// - /** - * De-Initialize parameter NVM handling - * - * @return status - Status of de-init - */ - //////////////////////////////////////////////////////////////////////////////// - par_status_t par_nvm_deinit(void) + return status; +} +/** + * @brief De-Initialize parameter NVM handling. + * + * @return Status of de-init. + */ +par_status_t par_nvm_deinit(void) +{ + par_status_t status = ePAR_OK; + + if (true == gb_is_init) { - par_status_t status = ePAR_OK; - - if ( true == gb_is_init ) + if (true == gb_is_nvm_owner) { - if ( eNVM_OK != nvm_deinit()) + if (eNVM_OK != nvm_deinit()) { status = ePAR_ERROR; } } - else + + if (ePAR_OK == status) { - status = ePAR_ERROR; + gb_is_init = false; + gb_is_nvm_owner = false; } - - return status; } - - //////////////////////////////////////////////////////////////////////////////// - /** - * Store parameter value to NVM - * - * @note Sync has only effect when using EEPROM emulated NVM feature! When - * using Flash end memory device. - * - * @note In case of using Flash end memory for storing parameters take special - * care when enabling sync (nvm_sync=true). At each sync data from RAM - * is copied to FLASH. - * - * @param[in] par_num - Parameter enumeration number - * @param[in] nvm_sync - Perform NVM sync after parameter write - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - par_status_t par_nvm_write(const par_num_t par_num, const bool nvm_sync) + else { - par_status_t status = ePAR_OK; - par_nvm_data_obj_t obj_data = { 0 }; - uint32_t par_addr = 0UL; - - PAR_ASSERT( true == gb_is_init ); - PAR_ASSERT( par_num < ePAR_NUM_OF ); + status = ePAR_ERROR; + } - if ( true == gb_is_init ) + return status; +} +/** + * @brief Store parameter value to NVM. + * + * @note Sync has only effect when using EEPROM emulated NVM feature! When. + * using Flash end memory device. + * + * @note In case of using Flash end memory for storing parameters take special. + * care when enabling sync (nvm_sync=true). At each sync data from RAM. + * is copied to FLASH. + * + * @param par_num Parameter enumeration number. + * @param nvm_sync Perform NVM sync after parameter write. + * @return Status of operation. + */ +par_status_t par_nvm_write(const par_num_t par_num, const bool nvm_sync) +{ + par_status_t status = ePAR_OK; + par_nvm_data_obj_t obj_data = { 0 }; + uint32_t par_addr = 0UL; + + PAR_ASSERT(true == gb_is_init); + PAR_ASSERT(par_num < ePAR_NUM_OF); + + if (true == gb_is_init) + { + if (par_num < ePAR_NUM_OF) { - if ( par_num < ePAR_NUM_OF ) + const par_cfg_t * const par_cfg = par_get_config(par_num); + if (true == par_cfg->persistent) { - // Get configuration - const par_cfg_t * const par_cfg = par_get_config( par_num ); - - // Is that parameter persistent - if ( true == par_cfg->persistent ) + par_get(par_num, (uint32_t *)&obj_data.data); + obj_data.id = par_cfg->id; + /* Current implementation stores fixed-size 8-byte objects. */ + obj_data.size = 4U; + obj_data.crc = par_nvm_calc_obj_crc(&obj_data); + par_addr = par_nvm_get_nvm_lut_addr(obj_data.id); + if (eNVM_OK != nvm_write(PAR_CFG_NVM_REGION, par_addr, sizeof(par_nvm_data_obj_t), (const uint8_t *)&obj_data)) { - // Get current par value - par_get( par_num, (uint32_t*) &obj_data.data ); - - // Get parameter ID - obj_data.id = par_cfg->id; - - // Get parameter type size - // NOTE: For know fixed! - obj_data.size = 4U; - - // Calculate CRC - obj_data.crc = par_nvm_calc_obj_crc( &obj_data ); - - // Get address from NVM lut - par_addr = par_nvm_get_nvm_lut_addr( obj_data.id ); - - // Write to NVM - if ( eNVM_OK != nvm_write( PAR_CFG_NVM_REGION, par_addr, sizeof( par_nvm_data_obj_t ), (const uint8_t*) &obj_data )) - { - status |= ePAR_ERROR_NVM; - } - - // Sync NVM - if ( true == nvm_sync ) - { - status |= par_nvm_sync(); - } + status |= ePAR_ERROR_NVM; } - else + + if (true == nvm_sync) { - status = ePAR_ERROR; + status |= par_nvm_sync(); } } else @@ -1022,125 +833,103 @@ } else { - status = ePAR_ERROR_INIT; + status = ePAR_ERROR; } - - return status; } - - //////////////////////////////////////////////////////////////////////////////// - /** - * Store all parameter value to NVM - * - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - par_status_t par_nvm_write_all(void) + else { - par_status_t status = ePAR_OK; + status = ePAR_ERROR_INIT; + } - PAR_ASSERT( true == gb_is_init ); + return status; +} +/** + * @brief Store all parameter value to NVM. + * + * @return Status of operation. + */ +par_status_t par_nvm_write_all(void) +{ + par_status_t status = ePAR_OK; - if ( true == gb_is_init ) - { - // Corrupt header (enter critical) - status |= par_nvm_corrupt_signature(); + PAR_ASSERT(true == gb_is_init); - // Got thru all parameters - for ( par_num_t par_num = 0U; par_num < ePAR_NUM_OF; par_num++ ) + if (true == gb_is_init) + { + /* Mark the header invalid while rewriting the NVM image. */ + status |= par_nvm_corrupt_signature(); + for (par_num_t par_num = 0U; par_num < ePAR_NUM_OF; par_num++) + { + if (true == par_is_persistent(par_num)) { - // Store only persistent one - if ( true == par_is_persistent( par_num )) - { - // Sync will be done later - status |= par_nvm_write( par_num, false ); - } + status |= par_nvm_write(par_num, false); } - - // Re-write header (exit critical) - status |= par_nvm_write_header( par_nvm_get_per_par()); - - // Sync NVM - status |= par_nvm_sync(); - - PAR_DBG_PRINT( "PAR_NVM: Storing all to NVM status: %s", par_get_status_str(status)); - } - else - { - status = ePAR_ERROR_INIT; } - return status; - } + /* Restore a valid header after the bulk rewrite completes. */ + status |= par_nvm_write_header(par_nvm_get_per_par()); + status |= par_nvm_sync(); - //////////////////////////////////////////////////////////////////////////////// - /** - * Reset total section of parameters NVM - * - * @brief This function completely re-write whole NVM section. - * - * It first corrupt signature in order to raise "working in progress" - * flag. - * - * @return num_of_per_par - Number of persistent parameters - */ - //////////////////////////////////////////////////////////////////////////////// - par_status_t par_nvm_reset_all(void) + PAR_DBG_PRINT("PAR_NVM: Storing all to NVM status: %s", par_get_status_str(status)); + } + else { - par_status_t status = ePAR_OK; - - PAR_ASSERT( true == gb_is_init ); - - if ( true == gb_is_init ) - { - // Build new NVM lut - par_nvm_build_new_nvm_lut(); - - // Write all data object - status |= par_nvm_write_all(); - } - else - { - status = ePAR_ERROR_INIT; - } - - return status; + status = ePAR_ERROR_INIT; } - //////////////////////////////////////////////////////////////////////////////// - /** - * Print parameter NVM table - * - * @note Only for debugging purposes - * - * @return void - */ - //////////////////////////////////////////////////////////////////////////////// - par_status_t par_nvm_print_nvm_lut(void) + return status; +} +/** + * @brief Rewrite the whole parameter NVM section. + * @details The signature is corrupted first to mark the image as being + * rewritten, then the LUT and all persistent objects are rebuilt. + * @return Status of operation. + */ +par_status_t par_nvm_reset_all(void) +{ + par_status_t status = ePAR_OK; + + PAR_ASSERT(true == gb_is_init); + + if (true == gb_is_init) { - par_status_t status = ePAR_OK; - - #if ( PAR_CFG_DEBUG_EN ) - PAR_DBG_PRINT( "PAR_NVM: Parameter NVM look-up table:" ); - PAR_DBG_PRINT( " %s\t%s\t%s\t\t%s", "#", "ID", "Addr", "Valid" ); - PAR_DBG_PRINT( "===============================" ); - - for ( par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) - { - PAR_DBG_PRINT( " %d\t%d\t0x%04X\t%d", par_num, g_par_nvm_data_obj_addr[par_num].id, - g_par_nvm_data_obj_addr[par_num].addr, - g_par_nvm_data_obj_addr[par_num].valid ); - PAR_DBG_PRINT( "-----------------------------" ); - } - #endif + par_nvm_build_new_nvm_lut(); + status |= par_nvm_write_all(); + } + else + { + status = ePAR_ERROR_INIT; + } - return status; + return status; +} +/** + * @brief Print parameter NVM table. + * + * @note Only for debugging purposes. + */ +par_status_t par_nvm_print_nvm_lut(void) +{ + par_status_t status = ePAR_OK; + +#if (PAR_CFG_DEBUG_EN) + PAR_DBG_PRINT("PAR_NVM: Parameter NVM look-up table:"); + PAR_DBG_PRINT(" %s\t%s\t%s\t\t%s", "#", "ID", "Addr", "Valid"); + PAR_DBG_PRINT("==============================="); + + for (par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++) + { + PAR_DBG_PRINT(" %d\t%d\t0x%04X\t%d", par_num, g_par_nvm_data_obj_addr[par_num].id, + g_par_nvm_data_obj_addr[par_num].addr, + g_par_nvm_data_obj_addr[par_num].valid); + PAR_DBG_PRINT("-----------------------------"); } +#endif - //////////////////////////////////////////////////////////////////////////////// - /** - * @} - */ - //////////////////////////////////////////////////////////////////////////////// + return status; +} +/** + * @} + */ -#endif // 1 == PAR_CFG_NVM_EN +#endif /* 1 == PAR_CFG_NVM_EN */ diff --git a/src/par_nvm.h b/src/par_nvm.h index 3282912..7b61477 100644 --- a/src/par_nvm.h +++ b/src/par_nvm.h @@ -1,52 +1,74 @@ -// Copyright (c) 2026 Ziga Miklosic -// All Rights Reserved -// This software is under MIT licence (https://opensource.org/licenses/MIT) -//////////////////////////////////////////////////////////////////////////////// -/** -*@file par_nvm.h -*@brief Parameter storage to non-volatile memory -*@author Ziga Miklosic -*@email ziga.miklosic@gmail.com -*@date 29.01.2026 -*@version V3.0.1 -*/ -//////////////////////////////////////////////////////////////////////////////// -/** -*@addtogroup PAR_NVM -* @{ -*/ -//////////////////////////////////////////////////////////////////////////////// +/** + * @file par_nvm.h + * @brief Declare non-volatile storage support for parameters. + * @author Ziga Miklosic + * @version V3.0.1 + * @date 2026-01-29 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-01-29 V3.0.1 Ziga Miklosic + */ + +/** + * @addtogroup PAR_NVM + * @{ + */ #ifndef _PAR_NVM_H_ #define _PAR_NVM_H_ - -//////////////////////////////////////////////////////////////////////////////// -// Includes -//////////////////////////////////////////////////////////////////////////////// +/** + * @brief Include dependencies. + */ #include #include #include #include "par.h" -#if ( 1 == PAR_CFG_NVM_EN ) - - //////////////////////////////////////////////////////////////////////////////// - // Functions Prototypes - //////////////////////////////////////////////////////////////////////////////// - par_status_t par_nvm_init (void); - par_status_t par_nvm_deinit (void); - par_status_t par_nvm_write (const par_num_t par_num, const bool nvm_sync); - par_status_t par_nvm_write_all (void); - par_status_t par_nvm_reset_all (void); - par_status_t par_nvm_print_nvm_lut (void); - -#endif // 1 == PAR_CFG_NVM_EN +#if (1 == PAR_CFG_NVM_EN) +/** + * @brief Function declarations. + */ +/** + * @brief Initialize parameter non-volatile storage support. + * @return Operation status. + */ +par_status_t par_nvm_init(void); +/** + * @brief Deinitialize parameter non-volatile storage support. + * @return Operation status. + */ +par_status_t par_nvm_deinit(void); +/** + * @brief Persist one parameter to non-volatile storage. + * @param par_num Parameter number. + * @param nvm_sync Synchronize the underlying NVM device after the write when true. + * @return Operation status. + */ +par_status_t par_nvm_write(const par_num_t par_num, const bool nvm_sync); +/** + * @brief Persist all persistent parameters to non-volatile storage. + * @return Operation status. + */ +par_status_t par_nvm_write_all(void); +/** + * @brief Erase all parameter data managed by the NVM backend. + * @return Operation status. + */ +par_status_t par_nvm_reset_all(void); +/** + * @brief Print the internal NVM lookup table for diagnostics. + * @return Operation status. + */ +par_status_t par_nvm_print_nvm_lut(void); -//////////////////////////////////////////////////////////////////////////////// +#endif /* 1 == PAR_CFG_NVM_EN */ /** -* @} -*/ -//////////////////////////////////////////////////////////////////////////////// + * @} + */ -#endif // _PAR_NVM_H_ +#endif /* _PAR_NVM_H_ */ diff --git a/src/par_storage_init.inc b/src/par_storage_init.inc index 6c3ba61..b3e411d 100644 --- a/src/par_storage_init.inc +++ b/src/par_storage_init.inc @@ -1,7 +1,21 @@ -/* - * Private include fragment. Included only by par.c. +/** + * @file par_storage_init.inc + * @brief Generate parameter storage initialization code. + * @author wdfk-prog () + * @version 1.0 + * @date 2026-03-27 * - * Expands compile-time default initializers for the grouped live storage + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-27 1.0 wdfk-prog first version + */ + +/** + * @brief Private include fragment. Included only by par.c. + * @details Expands compile-time default initializers for the grouped live storage. * object and preserves the internal U8/U16/U32 width-group layout. */ @@ -12,21 +26,19 @@ #define PAR_STORAGE_U16_FROM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u16_t)((uint16_t)(int16_t)(def_)), #define PAR_STORAGE_U32_FROM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u32_t)((uint32_t)(def_)), #define PAR_STORAGE_U32_FROM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u32_t)((uint32_t)(int32_t)(def_)), -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) +#if (1 == PAR_CFG_ENABLE_TYPE_F32) #define PAR_STORAGE_U32_FROM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u32_t)(0u), #endif -static par_storage_groups_t gs_par_storage = -{ - .u8 = - { -#define PAR_ITEM_U8 PAR_STORAGE_U8_FROM_U8 -#define PAR_ITEM_U16 PAR_STORAGE_INIT_NOP -#define PAR_ITEM_U32 PAR_STORAGE_INIT_NOP -#define PAR_ITEM_I8 PAR_STORAGE_U8_FROM_I8 -#define PAR_ITEM_I16 PAR_STORAGE_INIT_NOP -#define PAR_ITEM_I32 PAR_STORAGE_INIT_NOP -#define PAR_ITEM_F32 PAR_STORAGE_INIT_NOP +static par_storage_groups_t gs_par_storage = { + .u8 = { +#define PAR_ITEM_U8 PAR_STORAGE_U8_FROM_U8 +#define PAR_ITEM_U16 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_U32 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_I8 PAR_STORAGE_U8_FROM_I8 +#define PAR_ITEM_I16 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_I32 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_F32 PAR_STORAGE_INIT_NOP #include "../../par_table.def" #undef PAR_ITEM_U8 #undef PAR_ITEM_U16 @@ -37,15 +49,14 @@ static par_storage_groups_t gs_par_storage = #undef PAR_ITEM_F32 }, - .u16 = - { -#define PAR_ITEM_U8 PAR_STORAGE_INIT_NOP -#define PAR_ITEM_U16 PAR_STORAGE_U16_FROM_U16 -#define PAR_ITEM_U32 PAR_STORAGE_INIT_NOP -#define PAR_ITEM_I8 PAR_STORAGE_INIT_NOP -#define PAR_ITEM_I16 PAR_STORAGE_U16_FROM_I16 -#define PAR_ITEM_I32 PAR_STORAGE_INIT_NOP -#define PAR_ITEM_F32 PAR_STORAGE_INIT_NOP + .u16 = { +#define PAR_ITEM_U8 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_U16 PAR_STORAGE_U16_FROM_U16 +#define PAR_ITEM_U32 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_I8 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_I16 PAR_STORAGE_U16_FROM_I16 +#define PAR_ITEM_I32 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_F32 PAR_STORAGE_INIT_NOP #include "../../par_table.def" #undef PAR_ITEM_U8 #undef PAR_ITEM_U16 @@ -56,18 +67,17 @@ static par_storage_groups_t gs_par_storage = #undef PAR_ITEM_F32 }, - .u32 = - { -#define PAR_ITEM_U8 PAR_STORAGE_INIT_NOP -#define PAR_ITEM_U16 PAR_STORAGE_INIT_NOP -#define PAR_ITEM_U32 PAR_STORAGE_U32_FROM_U32 -#define PAR_ITEM_I8 PAR_STORAGE_INIT_NOP -#define PAR_ITEM_I16 PAR_STORAGE_INIT_NOP -#define PAR_ITEM_I32 PAR_STORAGE_U32_FROM_I32 -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) -#define PAR_ITEM_F32 PAR_STORAGE_U32_FROM_F32 + .u32 = { +#define PAR_ITEM_U8 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_U16 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_U32 PAR_STORAGE_U32_FROM_U32 +#define PAR_ITEM_I8 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_I16 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_I32 PAR_STORAGE_U32_FROM_I32 +#if (1 == PAR_CFG_ENABLE_TYPE_F32) +#define PAR_ITEM_F32 PAR_STORAGE_U32_FROM_F32 #else -#define PAR_ITEM_F32 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_F32 PAR_STORAGE_INIT_NOP #endif #include "../../par_table.def" #undef PAR_ITEM_U8 @@ -87,6 +97,6 @@ static par_storage_groups_t gs_par_storage = #undef PAR_STORAGE_U16_FROM_I16 #undef PAR_STORAGE_U32_FROM_U32 #undef PAR_STORAGE_U32_FROM_I32 -#if ( 1 == PAR_CFG_ENABLE_TYPE_F32 ) +#if (1 == PAR_CFG_ENABLE_TYPE_F32) #undef PAR_STORAGE_U32_FROM_F32 -#endif \ No newline at end of file +#endif diff --git a/src/par_typed_impl.inc b/src/par_typed_impl.inc index c04a4ad..b4f78ed 100644 --- a/src/par_typed_impl.inc +++ b/src/par_typed_impl.inc @@ -1,22 +1,35 @@ -/* - * Macro-generated typed parameter APIs and checked write core. +/** + * @file par_typed_impl.inc + * @brief Generate typed getter and setter implementations. + * @author wdfk-prog () + * @version 1.0 + * @date 2026-03-27 * - * This fragment is included only by par.c and expands the typed setter/getter - * families from a single type table. + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-27 1.0 wdfk-prog first version + */ + +/** + * @brief Macro-generated typed parameter APIs and checked write core. + * @details This fragment is included only by par.c and expands the typed setter/getter. + * families from a single type table. * Generated API groups: - * par_set_u8/i8/u16/i16/u32/i32(/f32) - * par_set_u8_fast/i8_fast/u16_fast/i16_fast/u32_fast/i32_fast(/f32_fast) - * par_get_u8/i8/u16/i16/u32/i32(/f32) + * par_set_u8/i8/u16/i16/u32/i32(/f32). + * par_set_u8_fast/i8_fast/u16_fast/i16_fast/u32_fast/i32_fast(/f32_fast). + * par_get_u8/i8/u16/i16/u32/i32(/f32). */ -#define PAR_TYPED_ROWS_BASE(X) \ - X(u8, U8, uint8_t, ePAR_TYPE_U8, u8) \ - X(i8, I8, int8_t, ePAR_TYPE_I8, i8) \ - X(u16, U16, uint16_t, ePAR_TYPE_U16, u16) \ - X(i16, I16, int16_t, ePAR_TYPE_I16, i16) \ - X(u32, U32, uint32_t, ePAR_TYPE_U32, u32) \ - X(i32, I32, int32_t, ePAR_TYPE_I32, i32) +#define PAR_TYPED_ROWS_BASE(X) \ + X(u8, U8, uint8_t, ePAR_TYPE_U8, u8) \ + X(i8, I8, int8_t, ePAR_TYPE_I8, i8) \ + X(u16, U16, uint16_t, ePAR_TYPE_U16, u16) \ + X(i16, I16, int16_t, ePAR_TYPE_I16, i16) \ + X(u32, U32, uint32_t, ePAR_TYPE_U32, u32) \ + X(i32, I32, int32_t, ePAR_TYPE_I32, i32) #if (1 == PAR_CFG_ENABLE_TYPE_F32) #define PAR_TYPED_ROWS_F32(X) \ @@ -59,67 +72,67 @@ } while (0) #endif -#define PAR_CHECKED_VALUE_CHANGED_u8(new_val, old_val) ((new_val).u8 != (old_val).u8) -#define PAR_CHECKED_VALUE_CHANGED_i8(new_val, old_val) ((new_val).i8 != (old_val).i8) -#define PAR_CHECKED_VALUE_CHANGED_u16(new_val, old_val) ((new_val).u16 != (old_val).u16) -#define PAR_CHECKED_VALUE_CHANGED_i16(new_val, old_val) ((new_val).i16 != (old_val).i16) -#define PAR_CHECKED_VALUE_CHANGED_u32(new_val, old_val) ((new_val).u32 != (old_val).u32) -#define PAR_CHECKED_VALUE_CHANGED_i32(new_val, old_val) ((new_val).i32 != (old_val).i32) -#define PAR_CHECKED_VALUE_CHANGED_f32(new_val, old_val) (!par_f32_bits_equal((new_val).f32, (old_val).f32)) +#define PAR_CHECKED_VALUE_CHANGED_u8(new_val, old_val) ((new_val).u8 != (old_val).u8) +#define PAR_CHECKED_VALUE_CHANGED_i8(new_val, old_val) ((new_val).i8 != (old_val).i8) +#define PAR_CHECKED_VALUE_CHANGED_u16(new_val, old_val) ((new_val).u16 != (old_val).u16) +#define PAR_CHECKED_VALUE_CHANGED_i16(new_val, old_val) ((new_val).i16 != (old_val).i16) +#define PAR_CHECKED_VALUE_CHANGED_u32(new_val, old_val) ((new_val).u32 != (old_val).u32) +#define PAR_CHECKED_VALUE_CHANGED_i32(new_val, old_val) ((new_val).i32 != (old_val).i32) +#define PAR_CHECKED_VALUE_CHANGED_f32(new_val, old_val) (!par_f32_bits_equal((new_val).f32, (old_val).f32)) #define PAR_CHECKED_VALUE_CHANGED(FIELD, new_val, old_val) PAR_CHECKED_VALUE_CHANGED_##FIELD((new_val), (old_val)) #if (1 == PAR_CFG_ENABLE_CHANGE_CALLBACK) #define PAR_CHECKED_OLD_VAL_DECL(PRIV, FIELD, par_num) \ const par_type_t old_val = { .FIELD = PAR_GET_##PRIV##_PRIV(par_num) }; -#define PAR_CHECKED_ON_CHANGE(PRIV, FIELD, par_num, status_var) \ - do \ - { \ - pf_par_on_change_cb_t on_change = g_par_cb_table[(par_num)].on_change; \ - if (((ePAR_OK == (status_var)) || (ePAR_WAR_LIMITED == (status_var))) && \ - (NULL != on_change)) \ - { \ - const par_type_t new_val = { .FIELD = PAR_GET_##PRIV##_PRIV(par_num) }; \ - if (PAR_CHECKED_VALUE_CHANGED(FIELD, new_val, old_val)) \ - { \ - on_change((par_num), new_val, old_val); \ - } \ - } \ +#define PAR_CHECKED_ON_CHANGE(PRIV, FIELD, par_num, status_var) \ + do \ + { \ + pf_par_on_change_cb_t on_change = g_par_cb_table[(par_num)].on_change; \ + if (((ePAR_OK == (status_var)) || (ePAR_WAR_LIMITED == (status_var))) && \ + (NULL != on_change)) \ + { \ + const par_type_t new_val = { .FIELD = PAR_GET_##PRIV##_PRIV(par_num) }; \ + if (PAR_CHECKED_VALUE_CHANGED(FIELD, new_val, old_val)) \ + { \ + on_change((par_num), new_val, old_val); \ + } \ + } \ } while (0) #else #define PAR_CHECKED_OLD_VAL_DECL(PRIV, FIELD, par_num) #define PAR_CHECKED_ON_CHANGE(PRIV, FIELD, par_num, status_var) \ - do \ - { \ - (void)(par_num); \ - (void)(status_var); \ + do \ + { \ + (void)(par_num); \ + (void)(status_var); \ } while (0) #endif #if (1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION) -#define PAR_CHECKED_VALIDATE_OR_SET(NAME, FIELD, par_num, val, status_var) \ - do \ - { \ - pf_par_validation_t validation = g_par_cb_table[(par_num)].validation; \ - if ((NULL != validation) && (false == validation((par_num), (val)))) \ - { \ - (status_var) = ePAR_ERROR_VALUE; \ - } \ - else \ - { \ - (status_var) = par_set_##NAME##_fast((par_num), (val).FIELD); \ - } \ +#define PAR_CHECKED_VALIDATE_OR_SET(NAME, FIELD, par_num, val, status_var) \ + do \ + { \ + pf_par_validation_t validation = g_par_cb_table[(par_num)].validation; \ + if ((NULL != validation) && (false == validation((par_num), (val)))) \ + { \ + (status_var) = ePAR_ERROR_VALUE; \ + } \ + else \ + { \ + (status_var) = par_set_##NAME##_fast((par_num), (val).FIELD); \ + } \ } while (0) #else #define PAR_CHECKED_VALIDATE_OR_SET(NAME, FIELD, par_num, val, status_var) \ - do \ - { \ - (status_var) = par_set_##NAME##_fast((par_num), (val).FIELD); \ + do \ + { \ + (status_var) = par_set_##NAME##_fast((par_num), (val).FIELD); \ } while (0) #endif #define PAR_CHECKED_LOAD_FROM_PTR_CASE(ETYPE, CTYPE, FIELD) \ - case ETYPE: \ - val.FIELD = *(const CTYPE *)p_ptr_val; \ + case ETYPE: \ + val.FIELD = *(const CTYPE *)p_ptr_val; \ break; #define PAR_CHECKED_SET_CASE(NAME, PRIV, ETYPE, FIELD) \ @@ -130,24 +143,20 @@ PAR_CHECKED_ON_CHANGE(PRIV, FIELD, par_num, status); \ break; \ } - -//////////////////////////////////////////////////////////////////////////////// /** -* Assert fast typed setter preconditions -* -* @note Fast typed APIs are internal trusted paths. They keep assert -* based precondition checks only and intentionally avoid the -* public metadata getter path. -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] expected_type - Expected parameter type for operation -* @return void -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Assert fast typed setter preconditions. + * + * @note Fast typed APIs are internal trusted paths. They keep assert. + * based precondition checks only and intentionally avoid the. + * public metadata getter path. + * + * @param par_num Parameter number (enumeration). + * @param expected_type Expected parameter type for operation. + */ static void par_assert_fast_typed_preconditions(const par_num_t par_num, const par_type_list_t expected_type) { - const par_cfg_t * p_cfg = NULL; + const par_cfg_t *p_cfg = NULL; PAR_ASSERT(true == par_is_init()); PAR_ASSERT(par_num < ePAR_NUM_OF); @@ -163,38 +172,35 @@ static void par_assert_fast_typed_preconditions(const par_num_t par_num, } } } - -//////////////////////////////////////////////////////////////////////////////// /** -* Checked parameter write core for all public setter APIs -* -* @note This is the single enforcement point for all public write -* paths. It performs runtime access checks, type validation, -* access enforcement, optional runtime validation, fast setter -* dispatch, and optional on-change callback dispatch. -* -* @note Exactly one value source shall be provided: -* - p_typed_val: used by typed setters par_set_xxx() -* - p_ptr_val : used by generic par_set() -* -* @note Unchecked internal paths such as par_set_xxx_fast(), NVM -* restore helpers, and other explicit fast paths do not call -* this function. -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] expected_type - Expected parameter type for write -* @param[in] p_typed_val - Optional typed union value source -* @param[in] p_ptr_val - Optional pointer value source -* -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Checked parameter write core for all public setter APIs. + * + * @note This is the single enforcement point for all public write. + * paths. It performs runtime access checks, type validation,. + * access enforcement, optional runtime validation, fast setter. + * dispatch, and optional on-change callback dispatch. + * + * @note Exactly one value source shall be provided: + * - p_typed_val: used by typed setters par_set_xxx(). + * - p_ptr_val : used by generic par_set(). + * + * @note Unchecked internal paths such as par_set_xxx_fast(), NVM. + * restore helpers, and other explicit fast paths do not call. + * this function. + * + * @param par_num Parameter number (enumeration). + * @param expected_type Expected parameter type for write. + * @param p_typed_val Optional typed union value source. + * @param p_ptr_val Optional pointer value source. + * + * @return Status of operation. + */ static par_status_t par_set_checked_core(const par_num_t par_num, const par_type_list_t expected_type, const par_type_t * const p_typed_val, const void * const p_ptr_val) { - const par_cfg_t * par_cfg = NULL; + const par_cfg_t *par_cfg = NULL; par_type_t val = { 0 }; par_status_t status = par_resolve_runtime(par_num, p_ptr_val, @@ -228,19 +234,19 @@ static par_status_t par_set_checked_core(const par_num_t par_num, { switch (expected_type) { - PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_U8, uint8_t, u8) - PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_I8, int8_t, i8) - PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_U16, uint16_t, u16) - PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_I16, int16_t, i16) - PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_U32, uint32_t, u32) - PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_I32, int32_t, i32) + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_U8, uint8_t, u8) + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_I8, int8_t, i8) + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_U16, uint16_t, u16) + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_I16, int16_t, i16) + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_U32, uint32_t, u32) + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_I32, int32_t, i32) #if (1 == PAR_CFG_ENABLE_TYPE_F32) PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_F32, float32_t, f32) #endif - case ePAR_TYPE_NUM_OF: - default: - PAR_ASSERT(0); - return ePAR_ERROR_TYPE; + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + return ePAR_ERROR_TYPE; } } @@ -251,8 +257,8 @@ static par_status_t par_set_checked_core(const par_num_t par_num, switch (expected_type) { - PAR_CHECKED_SET_CASE(u8, U8, ePAR_TYPE_U8, u8) - PAR_CHECKED_SET_CASE(i8, I8, ePAR_TYPE_I8, i8) + PAR_CHECKED_SET_CASE(u8, U8, ePAR_TYPE_U8, u8) + PAR_CHECKED_SET_CASE(i8, I8, ePAR_TYPE_I8, i8) PAR_CHECKED_SET_CASE(u16, U16, ePAR_TYPE_U16, u16) PAR_CHECKED_SET_CASE(i16, I16, ePAR_TYPE_I16, i16) PAR_CHECKED_SET_CASE(u32, U32, ePAR_TYPE_U32, u32) @@ -260,28 +266,33 @@ static par_status_t par_set_checked_core(const par_num_t par_num, #if (1 == PAR_CFG_ENABLE_TYPE_F32) PAR_CHECKED_SET_CASE(f32, F32, ePAR_TYPE_F32, f32) #endif - case ePAR_TYPE_NUM_OF: - default: - PAR_ASSERT(0); - status = ePAR_ERROR_TYPE; - break; + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + status = ePAR_ERROR_TYPE; + break; } par_release_mutex(par_num); return status; } -/* Generate normal typed setters: par_set_xxx() */ -#define PAR_TYPED_IMPL_SETTER(NAME, PRIV, CTYPE, ETYPE, FIELD) \ - par_status_t par_set_##NAME(const par_num_t par_num, const CTYPE val) \ - { \ - const par_type_t typed_val = { .FIELD = val }; \ - return par_set_checked_core(par_num, ETYPE, &typed_val, NULL); \ +/** + * @brief Generate normal typed setters: par_set_xxx(). + */ +#define PAR_TYPED_IMPL_SETTER(NAME, PRIV, CTYPE, ETYPE, FIELD) \ + par_status_t par_set_##NAME(const par_num_t par_num, const CTYPE val) \ + { \ + const par_type_t typed_val = { .FIELD = val }; \ + return par_set_checked_core(par_num, ETYPE, &typed_val, NULL); \ } PAR_TYPED_ROWS(PAR_TYPED_IMPL_SETTER) #undef PAR_TYPED_IMPL_SETTER +/** + * @brief Generate fast typed setters: par_set_xxx_fast(). + */ #define PAR_TYPED_IMPL_FAST_SETTER(NAME, PRIV, CTYPE, ETYPE, FIELD) \ par_status_t par_set_##NAME##_fast(const par_num_t par_num, const CTYPE val) \ { \ @@ -292,19 +303,21 @@ PAR_TYPED_ROWS(PAR_TYPED_IMPL_SETTER) PAR_TYPED_ROWS(PAR_TYPED_IMPL_FAST_SETTER) #undef PAR_TYPED_IMPL_FAST_SETTER -/* Generate typed getters: par_get_xxx() */ -#define PAR_TYPED_IMPL_GETTER(NAME, PRIV, CTYPE, ETYPE, FIELD) \ - par_status_t par_get_##NAME(const par_num_t par_num, CTYPE * const p_val) \ - { \ - const par_cfg_t * par_cfg = NULL; \ +/** + * @brief Generate typed getters: par_get_xxx(). + */ +#define PAR_TYPED_IMPL_GETTER(NAME, PRIV, CTYPE, ETYPE, FIELD) \ + par_status_t par_get_##NAME(const par_num_t par_num, CTYPE * const p_val) \ + { \ + const par_cfg_t *par_cfg = NULL; \ par_status_t status = par_resolve_runtime(par_num, p_val, true, &par_cfg); \ - if (ePAR_OK != status) \ - return status; \ - status = par_validate_expected_type(par_cfg, ETYPE); \ - if (ePAR_OK != status) \ - return status; \ - *p_val = PAR_GET_##PRIV##_PRIV(par_num); \ - return ePAR_OK; \ + if (ePAR_OK != status) \ + return status; \ + status = par_validate_expected_type(par_cfg, ETYPE); \ + if (ePAR_OK != status) \ + return status; \ + *p_val = PAR_GET_##PRIV##_PRIV(par_num); \ + return ePAR_OK; \ } PAR_TYPED_ROWS(PAR_TYPED_IMPL_GETTER) From 424e0248856e429a8844a668490fbb9d6a0400a6 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Fri, 27 Mar 2026 15:22:00 +0800 Subject: [PATCH 25/36] refactor(core): Decouple par_nvm from concrete storage backend --- README.md | 7 +- docs/api-reference.md | 4 +- docs/architecture.md | 6 +- docs/getting-started.md | 15 ++- src/backend/par_store_backend.h | 63 ++++++++++ src/backend/par_store_backend_gel_nvm.c | 91 +++++++++++++++ src/par_nvm.c | 149 +++++++++++++++++++----- 7 files changed, 297 insertions(+), 38 deletions(-) create mode 100644 src/backend/par_store_backend.h create mode 100644 src/backend/par_store_backend_gel_nvm.c diff --git a/README.md b/README.md index c73ad4f..4bfd005 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,9 @@ parameters/ │ ├── architecture.md │ └── getting-started.md ├── src/ +│ ├── backend/ +│ │ ├── par_store_backend.h +│ │ └── par_store_backend_gel_nvm.c │ ├── par.c │ ├── par.h │ ├── par_atomic.h @@ -111,7 +114,7 @@ This repository contains the reusable module core and templates. A real integrat - `port/par_if_port.c` when `PAR_CFG_IF_PORT_EN = 1` and the target needs stronger platform hooks than the weak defaults in `par_if.c` - `port/par_atomic_port.h` when `PAR_ATOMIC_BACKEND = PAR_ATOMIC_BACKEND_PORT` - generated static layout header when `PAR_CFG_LAYOUT_SOURCE = PAR_CFG_LAYOUT_SCRIPT` -- the external NVM module when `PAR_CFG_NVM_EN = 1` +- a concrete storage backend implementation when `PAR_CFG_NVM_EN = 1` ## When to read which document @@ -127,7 +130,7 @@ This repository contains the reusable module core and templates. A real integrat - The module separates **internal parameter enumeration** (`par_num_t`) from **external parameter IDs** (`id`). - The current ID lookup implementation uses a one-entry-per-bucket hash map generated at compile time from `par_table.def`. External IDs must therefore be not only unique, but also collision-free under the configured hash geometry. Optional runtime diagnostic scans can be enabled with `PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK` and `PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK` when additional startup logs are useful. See `docs/architecture.md` for the collision rule and avoidance guidance. - Unchecked setter APIs skip runtime validation callbacks and on-change callbacks, so they should be reserved for tightly controlled hot paths. Bitwise fast setters are further restricted to `U8` / `U16` / `U32` flags or bitmask parameters. Legacy `*_fast()` names remain as deprecated aliases. -- NVM support is optional, but when enabled it depends on the external NVM module and on ID and persistence metadata being enabled. +- NVM support is optional. When enabled, `par_nvm.c` depends on a mounted storage backend interface and on ID and persistence metadata being enabled. The package can build the `GeneralEmbeddedCLibraries/nvm` adapter from `src/backend/`, or the application can provide its own `par_store_backend_get_api()` implementation. - `par_init()` applies startup default values directly to live storage. Integer default values from `par_table.def` are compiled into a grouped width-based storage object, while `F32` default values are applied to the 32-bit storage group after layout offsets are available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. Because this startup initialization does not go through the public setter path, it does not invoke runtime validation or on-change callbacks. - `PAR_CFG_ENABLE_RESET_ALL_RAW` controls whether raw reset-all support and grouped default mirror snapshot support are enabled. diff --git a/docs/api-reference.md b/docs/api-reference.md index bbabb6f..0b82c1d 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -44,7 +44,7 @@ The module conditionally compiles parts of the API based on configuration. | Function | Description | | --- | --- | | `par_init()` | Initialize the module, validate the table, bind layout/runtime state, optionally run configured runtime ID diagnostics, apply default values to live storage, and optionally load persisted values from NVM. Startup defaults are applied internally and do not use the public setter path. | -| `par_deinit()` | Best-effort deinitialize the module, including interface-layer resources. It always clears the module init state after attempting child deinit steps. When NVM support is enabled, it only deinitializes the underlying NVM module if this module initialized it. | +| `par_deinit()` | Deinitialize the module, including interface-layer resources. When NVM support is enabled, it only deinitializes the underlying NVM module if this module initialized it. The top-level init state is cleared only when child deinit steps succeed; if NVM backend deinit fails, the module remains initialized. | | `par_is_init()` | Return whether the module is initialized. | ## Mutex helpers @@ -179,7 +179,7 @@ These APIs do not follow the same runtime usage pattern as the value access APIs ## NVM APIs -Available only when `PAR_CFG_NVM_EN = 1`. +Available only when `PAR_CFG_NVM_EN = 1` and a concrete parameter-storage backend is linked. | Function | Description | | --- | --- | diff --git a/docs/architecture.md b/docs/architecture.md index 90aed14..a9a7996 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -322,7 +322,7 @@ When `PAR_CFG_NVM_EN = 1`, the module can persist selected parameters to NVM. NVM persistence uses the parameter metadata, persistence flags, CRC handling, and table hash validation to detect incompatible or corrupted stored data. -For this feature, the external NVM module must be available in the project. +For this feature, `par_nvm.c` mounts a parameter-storage backend interface during initialization. The packaged `GeneralEmbeddedCLibraries/nvm` adapter is one option, but the core no longer depends directly on a specific NVM repository layout. ## Portability model @@ -337,7 +337,7 @@ Implemented under `src/`: - validation and optional runtime callbacks - layout handling - ID lookup -- optional NVM support +- optional NVM support and packaged backend adapters under `src/backend/` ### Port-specific layer @@ -347,4 +347,4 @@ Implemented by the integrator as needed: - `par_if_port.c` (optional strong override for the weak defaults in `par_if.c`) - `par_atomic_port.h` -This separation makes the core reusable while still allowing the target platform to provide mutexes, logging, assertions, and atomic primitives. +This separation makes the core reusable while still allowing the target platform to provide mutexes, logging, assertions, and atomic primitives. Packaged storage backend adapters stay with the core source tree because they are reusable module integrations rather than board-specific port code. diff --git a/docs/getting-started.md b/docs/getting-started.md index 75d081c..4e1940d 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -8,7 +8,7 @@ This guide shows how to integrate the `Device Parameters` module into a firmware 2. Provide `par_table.def` at the package root. 3. Provide `port/par_cfg_port.h`. 4. Decide whether you want: - - NVM persistence + - NVM persistence and which storage backend will implement it - a platform-specific interface backend - a platform-specific atomic backend - compile-scan or script-provided layout @@ -141,7 +141,14 @@ ID-based lookup is generated statically when `PAR_CFG_ENABLE_ID = 1`. Optional s - `PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK` - `PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK` -When NVM is enabled, the external NVM module must be present in the project. The parameters module can reuse an already-initialized NVM backend or initialize it on demand and later deinitialize it only when it owns that initialization. Module deinit is best-effort: it attempts NVM and interface cleanup, aggregates status bits, and still clears the top-level module init state. +When NVM is enabled, the parameters module requires a concrete storage backend implementation. `par_nvm.c` resolves and validates the backend API once during initialization, then uses the mounted callbacks directly for later reads, writes, erases, and sync operations. The package can build the `GeneralEmbeddedCLibraries/nvm` adapter from `parameters/src/backend/`, or the application can provide `par_store_backend_get_api()` itself. The module can reuse an already-initialized backend or initialize it on demand and later deinitialize it only when it owns that initialization. Module deinit is conservative: it attempts backend and interface cleanup, and it clears the top-level module init state only after the owned child deinit steps succeed. + +Backend choices: + +- enable the packaged `GeneralEmbeddedCLibraries/nvm` adapter +- provide exactly one application-owned `par_store_backend_get_api()` implementation + +If `PAR_CFG_NVM_EN = 1` and no backend implementation is linked, the build fails at link time by design. ### Layout source @@ -362,7 +369,7 @@ Good fits are enable masks, fault flags, and mode bits. Do not use bitwise fast ## Persistence to NVM -When NVM support is enabled, use the NVM APIs for storing current values. +When NVM support is enabled and a storage backend is linked, use the NVM APIs for storing current values. ```c if (par_save_all() != ePAR_OK) @@ -383,7 +390,7 @@ uint32_t baud = 115200U; - Forgetting to provide `par_cfg_port.h` - Treating `par_num_t` as a stable external interface - Using fast setters before understanding their tradeoffs -- Enabling NVM without the external NVM module in the build +- Enabling NVM without linking any concrete parameter-storage backend - Writing `par_table.def` entries with duplicate IDs - Assigning different external IDs that still resolve to the same ID hash bucket - Changing external IDs without rebuilding and checking the compile-time ID-map validation output diff --git a/src/backend/par_store_backend.h b/src/backend/par_store_backend.h new file mode 100644 index 0000000..9e33395 --- /dev/null +++ b/src/backend/par_store_backend.h @@ -0,0 +1,63 @@ +/** + * @file par_store_backend.h + * @brief Declare the abstract parameter-storage backend interface. + * @author OpenAI + * @version 1.0 + * @date 2026-03-27 + * + * @copyright Copyright (c) 2026. + * + * @details par_nvm.c uses this interface instead of depending directly on a + * concrete NVM repository layout. Integrators may provide any storage backend + * that supports the required byte-addressable operations. + */ + +#ifndef _PAR_STORE_BACKEND_H_ +#define _PAR_STORE_BACKEND_H_ + +#include +#include + +#include "par.h" + +#if (1 == PAR_CFG_NVM_EN) +/** + * @brief Abstract parameter-storage backend contract. + * + * @details All offsets are relative to the storage region reserved for the + * parameter image. The backend owns any region, partition, or device-specific + * context needed to execute the operation. + */ +typedef struct +{ + /** @brief Initialize the storage backend. */ + par_status_t (*init)(void); + /** @brief Deinitialize the storage backend. */ + par_status_t (*deinit)(void); + /** @brief Query whether the storage backend is initialized. */ + par_status_t (*is_init)(bool * const p_is_init); + /** @brief Read raw bytes from the storage backend. */ + par_status_t (*read)(const uint32_t addr, const uint32_t size, uint8_t * const p_buf); + /** @brief Write raw bytes to the storage backend. */ + par_status_t (*write)(const uint32_t addr, const uint32_t size, const uint8_t * const p_buf); + /** @brief Erase raw bytes in the storage backend. */ + par_status_t (*erase)(const uint32_t addr, const uint32_t size); + /** @brief Flush pending backend data to the final storage medium. */ + par_status_t (*sync)(void); + /** @brief Optional backend name for diagnostics. */ + const char *name; +} par_store_backend_api_t; + +/** + * @brief Resolve the active parameter-storage backend API. + * + * @details Link exactly one concrete implementation when `PAR_CFG_NVM_EN = 1`. + * The package can provide the GeneralEmbeddedCLibraries/nvm adapter, or the + * application can provide its own implementation. + * + * @return Pointer to backend API, or NULL if no backend is available. + */ +const par_store_backend_api_t * par_store_backend_get_api(void); +#endif /* 1 == PAR_CFG_NVM_EN */ + +#endif /* _PAR_STORE_BACKEND_H_ */ diff --git a/src/backend/par_store_backend_gel_nvm.c b/src/backend/par_store_backend_gel_nvm.c new file mode 100644 index 0000000..940cccd --- /dev/null +++ b/src/backend/par_store_backend_gel_nvm.c @@ -0,0 +1,91 @@ +/** + * @file par_store_backend_gel_nvm.c + * @brief Adapt GeneralEmbeddedCLibraries/nvm to the packaged parameter-storage backend interface. + * @author OpenAI + * @version 1.0 + * @date 2026-03-27 + * + * @details This adapter is optional. Enable it only when the project includes + * the GeneralEmbeddedCLibraries/nvm module and a valid PAR_CFG_NVM_REGION is + * configured for parameter storage. + */ + +#include "par_cfg.h" +#include "par_store_backend.h" + +#if (1 == PAR_CFG_NVM_EN) && (1 == PAR_CFG_NVM_BACKEND_GEL_EN) +#include "middleware/nvm/nvm/src/nvm.h" + +/** + * @brief Verify the expected upstream NVM major/minor API. + */ +_Static_assert(2 == NVM_VER_MAJOR); +_Static_assert(1 <= NVM_VER_MINOR); + +static par_status_t par_store_gel_init(void) +{ + return (eNVM_OK == nvm_init()) ? ePAR_OK : ePAR_ERROR_INIT; +} + +static par_status_t par_store_gel_deinit(void) +{ + return (eNVM_OK == nvm_deinit()) ? ePAR_OK : ePAR_ERROR; +} + +static par_status_t par_store_gel_is_init(bool * const p_is_init) +{ + if (NULL == p_is_init) + { + return ePAR_ERROR_PARAM; + } + + return (eNVM_OK == nvm_is_init(p_is_init)) ? ePAR_OK : ePAR_ERROR_INIT; +} + +static par_status_t par_store_gel_read(const uint32_t addr, const uint32_t size, uint8_t * const p_buf) +{ + if (NULL == p_buf) + { + return ePAR_ERROR_PARAM; + } + + return (eNVM_OK == nvm_read(PAR_CFG_NVM_REGION, addr, size, p_buf)) ? ePAR_OK : ePAR_ERROR_NVM; +} + +static par_status_t par_store_gel_write(const uint32_t addr, const uint32_t size, const uint8_t * const p_buf) +{ + if (NULL == p_buf) + { + return ePAR_ERROR_PARAM; + } + + return (eNVM_OK == nvm_write(PAR_CFG_NVM_REGION, addr, size, p_buf)) ? ePAR_OK : ePAR_ERROR_NVM; +} + +static par_status_t par_store_gel_erase(const uint32_t addr, const uint32_t size) +{ + return (eNVM_OK == nvm_erase(PAR_CFG_NVM_REGION, addr, size)) ? ePAR_OK : ePAR_ERROR_NVM; +} + +static par_status_t par_store_gel_sync(void) +{ + return (eNVM_OK == nvm_sync(PAR_CFG_NVM_REGION)) ? ePAR_OK : ePAR_ERROR_NVM; +} + +static const par_store_backend_api_t g_par_store_backend_gel = +{ + .init = par_store_gel_init, + .deinit = par_store_gel_deinit, + .is_init = par_store_gel_is_init, + .read = par_store_gel_read, + .write = par_store_gel_write, + .erase = par_store_gel_erase, + .sync = par_store_gel_sync, + .name = "gel_nvm", +}; + +const par_store_backend_api_t * par_store_backend_get_api(void) +{ + return &g_par_store_backend_gel; +} +#endif /* (1 == PAR_CFG_NVM_EN) && (1 == PAR_CFG_NVM_BACKEND_GEL_EN) */ diff --git a/src/par_nvm.c b/src/par_nvm.c index e7b55af..e444e5a 100644 --- a/src/par_nvm.c +++ b/src/par_nvm.c @@ -62,13 +62,7 @@ #include #include -#include "middleware/nvm/nvm/src/nvm.h" - -/** - * @brief Check NVM module compatibility. - */ -_Static_assert(2 == NVM_VER_MAJOR); -_Static_assert(1 <= NVM_VER_MINOR); +#include "backend/par_store_backend.h" /** * @brief Compile-time definitions. */ @@ -150,9 +144,17 @@ typedef struct */ static bool gb_is_init = false; /** - * @brief Ownership guard for the underlying NVM module. + * @brief Ownership guard for the mounted storage backend. */ static bool gb_is_nvm_owner = false; +/** + * @brief Selected parameter storage backend API. + * + * @details The backend is resolved once during initialization. The core NVM + * logic then uses only this abstract interface and no longer depends on a + * specific repository layout. + */ +static const par_store_backend_api_t *gp_store = NULL; /** * @brief Parameter NVM lut. */ @@ -184,6 +186,82 @@ static par_status_t par_nvm_write_table_id(const uint8_t * const p_table_id); static par_status_t par_nvm_init_nvm(void); static par_status_t par_nvm_sync(void); + +static par_status_t par_nvm_store_read(const uint32_t addr, const uint32_t size, uint8_t * const p_buf); +static par_status_t par_nvm_store_write(const uint32_t addr, const uint32_t size, const uint8_t * const p_buf); +static par_status_t par_nvm_store_erase(const uint32_t addr, const uint32_t size); +static par_status_t par_nvm_store_deinit(void); +static par_status_t par_nvm_store_is_init(bool * const p_is_init); + +/** + * @brief Read raw bytes from the selected parameter storage backend. + * + * @param addr Backend-relative start address. + * @param size Number of bytes to read. + * @param p_buf Destination buffer. + * @return Operation status. + */ +static par_status_t par_nvm_store_read(const uint32_t addr, const uint32_t size, uint8_t * const p_buf) +{ + if (NULL == p_buf) + { + return ePAR_ERROR_PARAM; + } + + return gp_store->read(addr, size, p_buf); +} +/** + * @brief Write raw bytes to the selected parameter storage backend. + * + * @param addr Backend-relative start address. + * @param size Number of bytes to write. + * @param p_buf Source buffer. + * @return Operation status. + */ +static par_status_t par_nvm_store_write(const uint32_t addr, const uint32_t size, const uint8_t * const p_buf) +{ + if (NULL == p_buf) + { + return ePAR_ERROR_PARAM; + } + + return gp_store->write(addr, size, p_buf); +} +/** + * @brief Erase a raw storage range through the selected backend. + * + * @param addr Backend-relative start address. + * @param size Number of bytes to erase. + * @return Operation status. + */ +static par_status_t par_nvm_store_erase(const uint32_t addr, const uint32_t size) +{ + return gp_store->erase(addr, size); +} +/** + * @brief Deinitialize the selected backend. + * + * @return Operation status. + */ +static par_status_t par_nvm_store_deinit(void) +{ + return gp_store->deinit(); +} +/** + * @brief Query backend initialization state. + * + * @param p_is_init Destination initialization flag. + * @return Operation status. + */ +static par_status_t par_nvm_store_is_init(bool * const p_is_init) +{ + if (NULL == p_is_init) + { + return ePAR_ERROR_PARAM; + } + + return gp_store->is_init(p_is_init); +} /** * @brief Function declarations and definitions. */ @@ -236,7 +314,7 @@ static par_status_t par_nvm_corrupt_signature(void) { par_status_t status = ePAR_OK; - if (eNVM_OK != nvm_erase(PAR_CFG_NVM_REGION, PAR_NVM_HEAD_SIGN_ADDR, PAR_NVM_SIGN_SIZE)) + if (ePAR_OK != par_nvm_store_erase(PAR_NVM_HEAD_SIGN_ADDR, PAR_NVM_SIGN_SIZE)) { status = ePAR_ERROR_NVM; PAR_DBG_PRINT("PAR_NVM: NVM error during signature corruption!"); @@ -255,7 +333,7 @@ static par_status_t par_nvm_erase_signature(void) { par_status_t status = ePAR_OK; - status = nvm_erase(PAR_CFG_NVM_REGION, PAR_NVM_SIGNATURE_ADDR_OFFSET, 4U); + status = par_nvm_store_erase(PAR_NVM_HEAD_SIGN_ADDR, PAR_NVM_SIGN_SIZE); return status; } @@ -277,7 +355,7 @@ static par_status_t par_nvm_check_table_id(const uint8_t * const p_table_id) par_status_t status = ePAR_OK; uint8_t nvm_table_id[32] = { 0 }; - if (eNVM_OK != nvm_read(eNVM_REGION_EEPROM_RUN_PAR, PAR_NVM_TABLE_ID_ADDR_OFFSET, 32U, (uint8_t *)&nvm_table_id)) + if (ePAR_OK != par_nvm_store_read(PAR_NVM_HEAD_HASH_ADDR, PAR_NVM_HASH_SIZE, (uint8_t *)&nvm_table_id)) { status = ePAR_ERROR_NVM; } @@ -306,7 +384,7 @@ static par_status_t par_nvm_write_table_id(const uint8_t * const p_table_id) { par_status_t status = ePAR_OK; - if (eNVM_OK != nvm_write(eNVM_REGION_EEPROM_RUN_PAR, PAR_NVM_TABLE_ID_ADDR_OFFSET, 32U, p_table_id)) + if (ePAR_OK != par_nvm_store_write(PAR_NVM_HEAD_HASH_ADDR, PAR_NVM_HASH_SIZE, p_table_id)) { status = ePAR_ERROR_NVM; } @@ -327,7 +405,7 @@ static par_status_t par_nvm_read_header(par_nvm_head_obj_t * const p_head_obj) PAR_ASSERT(NULL != p_head_obj); - if (eNVM_OK != nvm_read(PAR_CFG_NVM_REGION, PAR_NVM_HEAD_ADDR, sizeof(par_nvm_head_obj_t), (uint8_t *)p_head_obj)) + if (ePAR_OK != par_nvm_store_read(PAR_NVM_HEAD_ADDR, sizeof(par_nvm_head_obj_t), (uint8_t *)p_head_obj)) { status = ePAR_ERROR_NVM; PAR_DBG_PRINT("PAR_NVM: NVM error during header read!"); @@ -348,7 +426,7 @@ static par_status_t par_nvm_write_header(const uint16_t num_of_par) head_obj.obj_nb = num_of_par; head_obj.crc = par_nvm_calc_crc((uint8_t *)&head_obj.obj_nb, PAR_NVM_NB_OF_OBJ_SIZE); head_obj.sign = PAR_NVM_SIGN; - if (eNVM_OK != nvm_write(PAR_CFG_NVM_REGION, PAR_NVM_HEAD_ADDR, sizeof(par_nvm_head_obj_t), (const uint8_t *)&head_obj)) + if (ePAR_OK != par_nvm_store_write(PAR_NVM_HEAD_ADDR, sizeof(par_nvm_head_obj_t), (const uint8_t *)&head_obj)) { status = ePAR_ERROR_NVM; PAR_DBG_PRINT("PAR_NVM: NVM error during header write!"); @@ -461,7 +539,7 @@ static par_status_t par_nvm_load_all(const uint16_t num_of_par) uint16_t i = 0; uint32_t obj_addr = 0; par_nvm_data_obj_t obj_data = { 0 }; - nvm_status_t nvm_status = eNVM_OK; + par_status_t store_status = ePAR_OK; uint8_t crc_calc = 0; uint16_t per_par_nb = 0; uint16_t new_par_cnt = 0; @@ -469,8 +547,8 @@ static par_status_t par_nvm_load_all(const uint16_t num_of_par) { /* Each NVM object currently occupies 8 bytes. */ obj_addr = ((8 * i) + PAR_NVM_FIRST_DATA_OBJ_ADDR); - nvm_status = nvm_read(PAR_CFG_NVM_REGION, obj_addr, sizeof(par_nvm_data_obj_t), (uint8_t *)&obj_data); - if (eNVM_OK == nvm_status) + store_status = par_nvm_store_read(obj_addr, sizeof(par_nvm_data_obj_t), (uint8_t *)&obj_data); + if (ePAR_OK == store_status) { crc_calc = par_nvm_calc_obj_crc(&obj_data); if (crc_calc == obj_data.crc) @@ -513,7 +591,7 @@ static par_status_t par_nvm_load_all(const uint16_t num_of_par) { for (i = 0; i < ePAR_NUM_OF; i++) { - const par_cfg_t * const par_cfg = par_get_config(par_num); + const par_cfg_t * const par_cfg = par_get_config(i); if (true == par_cfg->persistent) { @@ -588,6 +666,7 @@ static void par_nvm_build_new_nvm_lut(void) } g_par_nvm_data_obj_addr[per_par_nb].id = par_cfg->id; + g_par_nvm_data_obj_addr[per_par_nb].valid = true; per_par_nb++; } } @@ -634,7 +713,7 @@ static bool par_nvm_is_in_nvm_lut(const uint16_t id) return false; } /** - * @brief Initialize NVM module. + * @brief Resolve, validate, and initialize the mounted storage backend. * * @return Status of operation. */ @@ -643,14 +722,29 @@ static par_status_t par_nvm_init_nvm(void) par_status_t status = ePAR_OK; bool is_nvm_init = false; + gp_store = par_store_backend_get_api(); gb_is_nvm_owner = false; - (void)nvm_is_init(&is_nvm_init); - if (false == is_nvm_init) + + /* Validate the mounted backend once before any operation callback is used. */ + if ((NULL == gp_store) || (NULL == gp_store->init) || (NULL == gp_store->deinit) || + (NULL == gp_store->is_init) || (NULL == gp_store->read) || (NULL == gp_store->write) || + (NULL == gp_store->erase) || (NULL == gp_store->sync)) + { + PAR_DBG_PRINT("PAR_NVM: No valid parameter storage backend is wired!"); + status = ePAR_ERROR_INIT; + } + + if (ePAR_OK == status) + { + status = par_nvm_store_is_init(&is_nvm_init); + } + + if ((ePAR_OK == status) && (false == is_nvm_init)) { - if (eNVM_OK != nvm_init()) + if (ePAR_OK != gp_store->init()) { status = ePAR_ERROR_INIT; - PAR_DBG_PRINT("PAR_NVM: NVM module init error!"); + PAR_DBG_PRINT("PAR_NVM: Parameter storage backend init error!"); } else { @@ -661,7 +755,7 @@ static par_status_t par_nvm_init_nvm(void) return status; } /** - * @brief Sync NVM module. + * @brief Synchronize the mounted storage backend. * * @return Status of operation. */ @@ -669,7 +763,7 @@ static par_status_t par_nvm_sync(void) { par_status_t status = ePAR_OK; - if (eNVM_OK != nvm_sync(PAR_CFG_NVM_REGION)) + if (ePAR_OK != gp_store->sync()) { status = ePAR_ERROR_NVM; } @@ -761,7 +855,7 @@ par_status_t par_nvm_deinit(void) { if (true == gb_is_nvm_owner) { - if (eNVM_OK != nvm_deinit()) + if (ePAR_OK != par_nvm_store_deinit()) { status = ePAR_ERROR; } @@ -771,6 +865,7 @@ par_status_t par_nvm_deinit(void) { gb_is_init = false; gb_is_nvm_owner = false; + gp_store = NULL; } } else @@ -816,7 +911,7 @@ par_status_t par_nvm_write(const par_num_t par_num, const bool nvm_sync) obj_data.size = 4U; obj_data.crc = par_nvm_calc_obj_crc(&obj_data); par_addr = par_nvm_get_nvm_lut_addr(obj_data.id); - if (eNVM_OK != nvm_write(PAR_CFG_NVM_REGION, par_addr, sizeof(par_nvm_data_obj_t), (const uint8_t *)&obj_data)) + if (ePAR_OK != par_nvm_store_write(par_addr, sizeof(par_nvm_data_obj_t), (const uint8_t *)&obj_data)) { status |= ePAR_ERROR_NVM; } From 9bb3812545a17a99d10f361fb0a9d49b0d2db0d6 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Sun, 29 Mar 2026 10:45:06 +0800 Subject: [PATCH 26/36] refactor(core): Reorganize parameter manager source tree and sync docs --- src/.clang-format => .clang-format | 0 README.md | 49 +++++++++++-------- docs/architecture.md | 29 ++++++----- docs/getting-started.md | 18 ++++--- src/{ => def}/par_def.c | 2 +- src/{ => def}/par_def.h | 0 src/{ => def}/par_id_map_static.c | 2 +- src/{ => def}/par_id_map_static.h | 2 +- src/{ => detail}/par_bitwise_impl.inc | 0 src/{ => detail}/par_storage_init.inc | 0 src/{ => detail}/par_typed_impl.inc | 0 src/{ => layout}/par_layout.c | 2 +- src/{ => layout}/par_layout.h | 2 +- src/par.c | 29 +++++------ src/par_cfg.h | 14 +++--- src/{ => persist}/backend/par_store_backend.h | 14 +++--- .../backend/par_store_backend_gel_nvm.c | 14 ++++-- src/{ => persist}/par_nvm.c | 6 +-- src/{ => persist}/par_nvm.h | 0 src/{ => port}/par_atomic.h | 0 src/{ => port}/par_if.c | 2 +- src/{ => port}/par_if.h | 1 - template/par_layout_static.htmp | 4 +- 23 files changed, 103 insertions(+), 87 deletions(-) rename src/.clang-format => .clang-format (100%) rename src/{ => def}/par_def.c (99%) rename src/{ => def}/par_def.h (100%) rename src/{ => def}/par_id_map_static.c (96%) rename src/{ => def}/par_id_map_static.h (90%) rename src/{ => detail}/par_bitwise_impl.inc (100%) rename src/{ => detail}/par_storage_init.inc (100%) rename src/{ => detail}/par_typed_impl.inc (100%) rename src/{ => layout}/par_layout.c (99%) rename src/{ => layout}/par_layout.h (98%) rename src/{ => persist}/backend/par_store_backend.h (91%) rename src/{ => persist}/backend/par_store_backend_gel_nvm.c (90%) rename src/{ => persist}/par_nvm.c (99%) rename src/{ => persist}/par_nvm.h (100%) rename src/{ => port}/par_atomic.h (100%) rename src/{ => port}/par_if.c (99%) rename src/{ => port}/par_if.h (98%) diff --git a/src/.clang-format b/.clang-format similarity index 100% rename from src/.clang-format rename to .clang-format diff --git a/README.md b/README.md index 4bfd005..6a0bf45 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ It is designed for projects that need a clean way to: ### Quick start -1. Add the core sources from `src/` to your build. +1. Add `src/par.c` and the sources from `src/def`, `src/layout`, `src/persist`, and `src/port` to your build. Add `src/persist/backend` only when you use the packaged backend adapter. 2. Provide a project-specific `par_table.def` at the package root. 3. Provide `port/par_cfg_port.h` in your include path. 4. Optionally provide `port/par_if_port.c` and `port/par_atomic_port.h` when your platform needs them. @@ -69,31 +69,37 @@ static void app_init(void) parameters/ ├── README.md ├── CHANGE_LOG.md -├── doc/ -│ └── DeviceParameter_VerificationReport.xlsx ├── docs/ +│ ├── DeviceParameter_VerificationReport.xlsx │ ├── api-reference.md │ ├── architecture.md │ └── getting-started.md ├── src/ -│ ├── backend/ -│ │ ├── par_store_backend.h -│ │ └── par_store_backend_gel_nvm.c │ ├── par.c │ ├── par.h -│ ├── par_atomic.h -│ ├── par_bitwise_impl.inc │ ├── par_cfg.h -│ ├── par_def.c -│ ├── par_def.h -│ ├── par_if.c -│ ├── par_if.h -│ ├── par_layout.c -│ ├── par_layout.h -│ ├── par_nvm.c -│ ├── par_nvm.h -│ ├── par_storage_init.inc -│ └── par_typed_impl.inc +│ ├── def/ +│ │ ├── par_def.c +│ │ ├── par_def.h +│ │ ├── par_id_map_static.c +│ │ └── par_id_map_static.h +│ ├── detail/ +│ │ ├── par_bitwise_impl.inc +│ │ ├── par_storage_init.inc +│ │ └── par_typed_impl.inc +│ ├── layout/ +│ │ ├── par_layout.c +│ │ └── par_layout.h +│ ├── persist/ +│ │ ├── backend/ +│ │ │ ├── par_store_backend.h +│ │ │ └── par_store_backend_gel_nvm.c +│ │ ├── par_nvm.c +│ │ └── par_nvm.h +│ └── port/ +│ ├── par_atomic.h +│ ├── par_if.c +│ └── par_if.h └── template/ ├── par_cfg_port.htmp ├── par_layout_static.htmp @@ -111,7 +117,7 @@ This repository contains the reusable module core and templates. A real integrat ### Optional, depending on configuration -- `port/par_if_port.c` when `PAR_CFG_IF_PORT_EN = 1` and the target needs stronger platform hooks than the weak defaults in `par_if.c` +- `port/par_if_port.c` when `PAR_CFG_IF_PORT_EN = 1` and the target needs stronger platform hooks than the weak defaults in `src/port/par_if.c` - `port/par_atomic_port.h` when `PAR_ATOMIC_BACKEND = PAR_ATOMIC_BACKEND_PORT` - generated static layout header when `PAR_CFG_LAYOUT_SOURCE = PAR_CFG_LAYOUT_SCRIPT` - a concrete storage backend implementation when `PAR_CFG_NVM_EN = 1` @@ -124,13 +130,14 @@ This repository contains the reusable module core and templates. A real integrat ## Key integration notes -- `par_cfg.h` includes `par_cfg_port.h` unconditionally, so your build must provide that header. +- `src/par.h` is the main public entry header. Keep `parameters/src` on the compiler include path so application code can use `#include "par.h"`. +- `par_cfg.h` includes `par_cfg_port.h` unconditionally, so your build must provide that header and make its directory visible to the compiler. - `PAR_CFG_ENABLE_TYPE_F32` controls whether floating-point parameter support and the related typed APIs are compiled in. - `PAR_CFG_ENABLE_RUNTIME_VALIDATION` and `PAR_CFG_ENABLE_CHANGE_CALLBACK` control whether normal setters include runtime validation callbacks and on-change callbacks. - The module separates **internal parameter enumeration** (`par_num_t`) from **external parameter IDs** (`id`). - The current ID lookup implementation uses a one-entry-per-bucket hash map generated at compile time from `par_table.def`. External IDs must therefore be not only unique, but also collision-free under the configured hash geometry. Optional runtime diagnostic scans can be enabled with `PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK` and `PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK` when additional startup logs are useful. See `docs/architecture.md` for the collision rule and avoidance guidance. - Unchecked setter APIs skip runtime validation callbacks and on-change callbacks, so they should be reserved for tightly controlled hot paths. Bitwise fast setters are further restricted to `U8` / `U16` / `U32` flags or bitmask parameters. Legacy `*_fast()` names remain as deprecated aliases. -- NVM support is optional. When enabled, `par_nvm.c` depends on a mounted storage backend interface and on ID and persistence metadata being enabled. The package can build the `GeneralEmbeddedCLibraries/nvm` adapter from `src/backend/`, or the application can provide its own `par_store_backend_get_api()` implementation. +- NVM support is optional. When enabled, `src/persist/par_nvm.c` depends on a mounted storage backend interface and on ID and persistence metadata being enabled. The package can build the `GeneralEmbeddedCLibraries/nvm` adapter from `src/persist/backend/`, or the application can provide its own `par_store_backend_get_api()` implementation. - `par_init()` applies startup default values directly to live storage. Integer default values from `par_table.def` are compiled into a grouped width-based storage object, while `F32` default values are applied to the 32-bit storage group after layout offsets are available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. Because this startup initialization does not go through the public setter path, it does not invoke runtime validation or on-change callbacks. - `PAR_CFG_ENABLE_RESET_ALL_RAW` controls whether raw reset-all support and grouped default mirror snapshot support are enabled. diff --git a/docs/architecture.md b/docs/architecture.md index a9a7996..5b071ca 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -7,7 +7,7 @@ This document explains how the module is structured internally and how the major The module separates four concerns: 1. **Generated parameter definition** through `par_table.def`, generated enums, and generated config structs -2. **Core runtime access** through `par.c`, `par.h`, and private implementation fragments included only by `par.c` +2. **Core runtime access** through `src/par.c`, `src/par.h`, and private implementation fragments included only by `src/par.c` 3. **Layout and storage** through `par_layout.*` and compile-time storage initialization fragments 4. **Optional platform and NVM integration** through `par_if.*`, `par_atomic.h`, and `par_nvm.*` @@ -109,7 +109,7 @@ Live storage is initialized in two phases during startup: 1. Integer default values from `par_table.def` are compiled directly into the grouped live storage object in `par.c`. 2. When `PAR_CFG_ENABLE_TYPE_F32 = 1`, `F32` default values are written into the grouped 32-bit storage member after layout offsets are available. -The compile-time integer storage initializers are emitted through a private include fragment, `par_storage_init.inc`, which is included only by `par.c` and initializes the grouped storage object (`U8/U16/U32` members). +The compile-time integer storage initializers are emitted through a private include fragment, `src/detail/par_storage_init.inc`, which is included only by `par.c` and initializes the grouped storage object (`U8/U16/U32` members). When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`, `par.c` keeps a grouped default mirror snapshot for raw reset. The snapshot preserves the same `U8/U16/U32` width-group storage semantics. @@ -294,7 +294,7 @@ Normal setters are the default path. They are intended for ordinary application Depending on build-time configuration, the normal path can include runtime validation callbacks and on-change callbacks. -The typed setter/getter implementations are emitted through `par_typed_impl.inc`, a private include fragment included only by `par.c`. +The typed setter/getter implementations are emitted through `src/detail/par_typed_impl.inc`, a private include fragment included only by `par.c`. ### Fast setters @@ -302,7 +302,7 @@ Fast setters are specialized APIs for controlled hot paths. They reduce overhead Fast setters do not execute runtime validation callbacks or on-change callbacks. -The bitwise fast helpers are emitted through `par_bitwise_impl.inc`, another private include fragment included only by `par.c`. They are intentionally scoped as flags-only helpers for `U8` / `U16` / `U32` bitmask parameters and do not preserve normal setter range semantics. +The bitwise fast helpers are emitted through `src/detail/par_bitwise_impl.inc`, another private include fragment included only by `par.c`. They are intentionally scoped as flags-only helpers for `U8` / `U16` / `U32` bitmask parameters and do not preserve normal setter range semantics. ### Raw reset-all path @@ -328,23 +328,26 @@ For this feature, `par_nvm.c` mounts a parameter-storage backend interface durin The module stays portable by keeping platform-specific logic behind dedicated boundaries. +Header placement follows the same rule: the main public entry header stays at `src/par.h`, while helper headers remain next to the implementation areas that own them. This keeps the package self-contained without introducing a separate `include/` tree before the API surface is stable. + ### Core portable layer -Implemented under `src/`: +Implemented under layered `src/` subdirectories: -- parameter storage -- parameter metadata access -- validation and optional runtime callbacks -- layout handling -- ID lookup -- optional NVM support and packaged backend adapters under `src/backend/` +- `src/par.c`, `src/par.h`, and `src/par_cfg.h` for public runtime entry points and top-level orchestration +- `src/def/` for parameter definitions and generated static ID mapping +- `src/layout/` for storage layout calculation and accessors +- `src/persist/` for NVM persistence logic +- `src/persist/backend/` for reusable packaged backend adapters +- `src/port/` for platform-facing hooks used by the portable core +- `src/detail/` for private implementation fragments included only by `src/par.c` ### Port-specific layer Implemented by the integrator as needed: - `par_cfg_port.h` -- `par_if_port.c` (optional strong override for the weak defaults in `par_if.c`) +- `par_if_port.c` (optional strong override for the weak defaults in `src/port/par_if.c`) - `par_atomic_port.h` -This separation makes the core reusable while still allowing the target platform to provide mutexes, logging, assertions, and atomic primitives. Packaged storage backend adapters stay with the core source tree because they are reusable module integrations rather than board-specific port code. +This separation makes the core reusable while still allowing the target platform to provide mutexes, logging, assertions, and atomic primitives. Packaged storage backend adapters stay inside the `src/persist/backend/` subtree because they are reusable module integrations rather than board-specific port code. diff --git a/docs/getting-started.md b/docs/getting-started.md index 4e1940d..24a96fd 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -4,7 +4,7 @@ This guide shows how to integrate the `Device Parameters` module into a firmware ## Integration checklist -1. Add `src/*.c` and `src/*.h` to your project. +1. Add `src/par.c` and the needed sources from `src/def`, `src/layout`, `src/persist`, and `src/port` to your project. Add `src/persist/backend` only when you use the packaged backend adapter. 2. Provide `par_table.def` at the package root. 3. Provide `port/par_cfg_port.h`. 4. Decide whether you want: @@ -79,6 +79,8 @@ If you do not need platform overrides yet, start with a minimal stub: Use `template/par_cfg_port.htmp` as the starting point. +Keep `parameters/src` on the compiler include path so application code can include `par.h`. Also add the directory that contains your integration-owned `par_cfg_port.h` (and optionally `par_atomic_port.h`) to the compiler include path. + ## Optional integration files ### `port/par_if_port.c` @@ -141,7 +143,7 @@ ID-based lookup is generated statically when `PAR_CFG_ENABLE_ID = 1`. Optional s - `PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK` - `PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK` -When NVM is enabled, the parameters module requires a concrete storage backend implementation. `par_nvm.c` resolves and validates the backend API once during initialization, then uses the mounted callbacks directly for later reads, writes, erases, and sync operations. The package can build the `GeneralEmbeddedCLibraries/nvm` adapter from `parameters/src/backend/`, or the application can provide `par_store_backend_get_api()` itself. The module can reuse an already-initialized backend or initialize it on demand and later deinitialize it only when it owns that initialization. Module deinit is conservative: it attempts backend and interface cleanup, and it clears the top-level module init state only after the owned child deinit steps succeed. +When NVM is enabled, the parameters module requires a concrete storage backend implementation. `src/persist/par_nvm.c` resolves and validates the backend API once during initialization, then uses the mounted callbacks directly for later reads, writes, erases, and sync operations. The package can build the `GeneralEmbeddedCLibraries/nvm` adapter from `parameters/src/persist/backend/`, or the application can provide `par_store_backend_get_api()` itself. The module can reuse an already-initialized backend or initialize it on demand and later deinitialize it only when it owns that initialization. Module deinit is conservative: it attempts backend and interface cleanup, and it clears the top-level module init state only after the owned child deinit steps succeed. Backend choices: @@ -415,10 +417,10 @@ port/par_cfg_port.h:130:44: note: in expansion of macro '_STATIC_ASSERT' src/par_cfg.h:160:53: note: in expansion of macro 'PAR_PORT_STATIC_ASSERT' 160 | #define PAR_STATIC_ASSERT(name, expn) PAR_PORT_STATIC_ASSERT(name, expn); | ^~~~~~~~~~~~~~~~~~~~~~ -src/par_def.c:73:94: note: in expansion of macro 'PAR_STATIC_ASSERT' +src/def/par_def.c:73:94: note: in expansion of macro 'PAR_STATIC_ASSERT' 73 | #define PAR_CHECK_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_STATIC_ASSERT(enum_##_f32_type_is_disabled__remove_PAR_ITEM_F32, 0) | ^~~~~~~~~~~~~~~~~ -src/par_def.c:85:23: note: in expansion of macro 'PAR_CHECK_F32' +src/def/par_def.c:85:23: note: in expansion of macro 'PAR_CHECK_F32' 85 | #define PAR_ITEM_F32 PAR_CHECK_F32 | ^~~~~~~~~~~~~ par_table.def:189:1: note: in expansion of macro 'PAR_ITEM_F32' @@ -439,19 +441,19 @@ Example: ```log par_table.def: In function 'par_compile_check_hash_bucket_collision': -src/par_def.c:156:105: error: duplicate case value +src/def/par_def.c:156:105: error: duplicate case value 156 | #define PAR_CHECK_ID_BUCKET_CASE(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) case PAR_HASH_ID_CONST(id_): break; | ^~~~ -src/par_def.c:162:31: note: in expansion of macro 'PAR_CHECK_ID_BUCKET_CASE' +src/def/par_def.c:162:31: note: in expansion of macro 'PAR_CHECK_ID_BUCKET_CASE' 162 | #define PAR_ITEM_U16 PAR_CHECK_ID_BUCKET_CASE | ^~~~~~~~~~~~~~~~~~~~~~~~ par_table.def:141:1: note: in expansion of macro 'PAR_ITEM_U16' 141 | PAR_ITEM_U16(ePAR_CH3_VOL_RAW, 253, "Ch3 Raw Vout", ...) | ^~~~~~~~~~~~ -src/par_def.c:156:105: note: previously used here +src/def/par_def.c:156:105: note: previously used here 156 | #define PAR_CHECK_ID_BUCKET_CASE(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) case PAR_HASH_ID_CONST(id_): break; | ^~~~ -src/par_def.c:167:31: note: in expansion of macro 'PAR_CHECK_ID_BUCKET_CASE' +src/def/par_def.c:167:31: note: in expansion of macro 'PAR_CHECK_ID_BUCKET_CASE' 167 | #define PAR_ITEM_F32 PAR_CHECK_ID_BUCKET_CASE | ^~~~~~~~~~~~~~~~~~~~~~~~ par_table.def:54:1: note: in expansion of macro 'PAR_ITEM_F32' diff --git a/src/par_def.c b/src/def/par_def.c similarity index 99% rename from src/par_def.c rename to src/def/par_def.c index 84f25e5..4f8844f 100644 --- a/src/par_def.c +++ b/src/def/par_def.c @@ -25,7 +25,7 @@ /** * @brief Include dependencies. */ -#include "par_def.h" +#include "def/par_def.h" #include "par.h" /** * @brief Compile-time definitions. diff --git a/src/par_def.h b/src/def/par_def.h similarity index 100% rename from src/par_def.h rename to src/def/par_def.h diff --git a/src/par_id_map_static.c b/src/def/par_id_map_static.c similarity index 96% rename from src/par_id_map_static.c rename to src/def/par_id_map_static.c index f8f1559..3a549b2 100644 --- a/src/par_id_map_static.c +++ b/src/def/par_id_map_static.c @@ -12,7 +12,7 @@ * Date Version Author Description * 2026-03-24 1.0 wdfk-prog first version */ -#include "par_id_map_static.h" +#include "def/par_id_map_static.h" #if (1 == PAR_CFG_ENABLE_ID) diff --git a/src/par_id_map_static.h b/src/def/par_id_map_static.h similarity index 90% rename from src/par_id_map_static.h rename to src/def/par_id_map_static.h index bbc6a07..b088eed 100644 --- a/src/par_id_map_static.h +++ b/src/def/par_id_map_static.h @@ -1,6 +1,6 @@ /** * @file par_id_map_static.h - * @brief + * @brief Declare the compile-time generated static ID lookup map. * @author wdfk-prog () * @version 1.0 * @date 2026-03-24 diff --git a/src/par_bitwise_impl.inc b/src/detail/par_bitwise_impl.inc similarity index 100% rename from src/par_bitwise_impl.inc rename to src/detail/par_bitwise_impl.inc diff --git a/src/par_storage_init.inc b/src/detail/par_storage_init.inc similarity index 100% rename from src/par_storage_init.inc rename to src/detail/par_storage_init.inc diff --git a/src/par_typed_impl.inc b/src/detail/par_typed_impl.inc similarity index 100% rename from src/par_typed_impl.inc rename to src/detail/par_typed_impl.inc diff --git a/src/par_layout.c b/src/layout/par_layout.c similarity index 99% rename from src/par_layout.c rename to src/layout/par_layout.c index 00e9de8..837b933 100644 --- a/src/par_layout.c +++ b/src/layout/par_layout.c @@ -20,7 +20,7 @@ /** * @brief Include dependencies. */ -#include "par_layout.h" +#include "layout/par_layout.h" #include "par.h" /** * @brief Compile-time definitions. diff --git a/src/par_layout.h b/src/layout/par_layout.h similarity index 98% rename from src/par_layout.h rename to src/layout/par_layout.h index 2d1d153..5895412 100644 --- a/src/par_layout.h +++ b/src/layout/par_layout.h @@ -21,7 +21,7 @@ #include #include "par_cfg.h" -#include "par_def.h" +#include "def/par_def.h" #ifdef __cplusplus extern "C" { diff --git a/src/par.c b/src/par.c index 8778196..bb1700f 100644 --- a/src/par.c +++ b/src/par.c @@ -24,11 +24,11 @@ #include #include "par.h" -#include "par_atomic.h" -#include "par_layout.h" -#include "par_id_map_static.h" -#include "par_nvm.h" -#include "par_if.h" +#include "port/par_atomic.h" +#include "layout/par_layout.h" +#include "def/par_id_map_static.h" +#include "persist/par_nvm.h" +#include "port/par_if.h" /** * @brief Compile-time definitions. */ @@ -83,7 +83,7 @@ typedef struct * @brief Private implementation fragment. Do not include outside par.c. * @details Defines gs_par_storage with grouped typed initializers. */ -#include "par_storage_init.inc" +#include "detail/par_storage_init.inc" #if (1 == PAR_CFG_ENABLE_RESET_ALL_RAW) /** @@ -534,6 +534,7 @@ par_status_t par_init(void) par_status_t par_deinit(void) { par_status_t status = ePAR_OK; + par_status_t deinit_status = ePAR_OK; PAR_ASSERT(true == par_is_init()); if (true != par_is_init()) @@ -592,8 +593,8 @@ void par_release_mutex(const par_num_t par_num) * par_set( ePAR_MY_VAR, (float32_t*) &my_val );. * @endcode * - * @note Input is parameter number (enumeration) defined in par_cfg.h and not. - * parameter ID number! + * @note Input is the internal parameter number (`par_num_t`) from `par_def.h`, + * not the external parameter ID. * * @param par_num Parameter number (enumeration). * @param p_val Pointer to value. @@ -647,12 +648,12 @@ par_status_t par_set_by_id(const uint16_t id, const void *p_val) * * @note Private implementation fragment. Do not include outside par.c. */ -#include "par_typed_impl.inc" +#include "detail/par_typed_impl.inc" /** * @brief Bitwise fast setter implementation. * @note Private implementation fragments. Do not include outside par.c. */ -#include "par_bitwise_impl.inc" +#include "detail/par_bitwise_impl.inc" /** * @brief Set parameter to default value. * @@ -899,8 +900,8 @@ par_status_t par_has_changed(const par_num_t par_num, bool * const p_has_changed * par_get( ePAR_MY_VAR, (float32_t*) &my_val );. * @endcode * - * @note Input is parameter number (enumeration) defined in par_cfg.h and not. - * parameter ID number! + * @note Input is the internal parameter number (`par_num_t`) from `par_def.h`, + * not the external parameter ID. * * @param par_num Parameter number (enumeration). * @param p_val Parameter value. @@ -1361,8 +1362,8 @@ static par_status_t par_is_value_changed(const par_num_t par_num, const void *p_ * par_set( ePAR_MY_VAR, (float32_t*) &my_val );. * @endcode * - * @note Input is parameter number (enumeration) defined in par_cfg.h and not. - * parameter ID number! + * @note Input is the internal parameter number (`par_num_t`) from `par_def.h`, + * not the external parameter ID. * * @param par_num Parameter number (enumeration). * @param p_val Pointer to value. diff --git a/src/par_cfg.h b/src/par_cfg.h index 2c6ea1e..498af61 100644 --- a/src/par_cfg.h +++ b/src/par_cfg.h @@ -28,7 +28,7 @@ #include #include #include -#include "par_def.h" +#include "def/par_def.h" /** * @brief USER CODE BEGIN... @@ -60,15 +60,13 @@ #endif /** - * @brief NVM parameter region option. + * @brief Enable/Disable the legacy GeneralEmbeddedCLibraries/nvm backend. * - * @note User shall select region based on nvm_cfg.h region. - * definitions "nvm_region_name_t". - * - * Don't care if "PAR_CFG_NVM_EN" set to 0. + * @note Keep disabled when the application provides an out-of-package storage + * backend, such as the RT-Thread AT24CXX adapter. */ -#ifndef PAR_CFG_NVM_REGION -#define PAR_CFG_NVM_REGION (eNVM_REGION_INT_FLASH_DEV_PAR) +#ifndef PAR_CFG_NVM_BACKEND_GEL_EN +#define PAR_CFG_NVM_BACKEND_GEL_EN (0) #endif /** diff --git a/src/backend/par_store_backend.h b/src/persist/backend/par_store_backend.h similarity index 91% rename from src/backend/par_store_backend.h rename to src/persist/backend/par_store_backend.h index 9e33395..103daa2 100644 --- a/src/backend/par_store_backend.h +++ b/src/persist/backend/par_store_backend.h @@ -1,17 +1,19 @@ /** * @file par_store_backend.h * @brief Declare the abstract parameter-storage backend interface. - * @author OpenAI + * @author wdfk-prog () * @version 1.0 - * @date 2026-03-27 - * - * @copyright Copyright (c) 2026. - * + * @date 2026-03-29 + * + * @copyright Copyright (c) 2026 + * * @details par_nvm.c uses this interface instead of depending directly on a * concrete NVM repository layout. Integrators may provide any storage backend * that supports the required byte-addressable operations. + * @par Change Log: + * Date Version Author Description + * 2026-03-29 1.0 wdfk-prog first version */ - #ifndef _PAR_STORE_BACKEND_H_ #define _PAR_STORE_BACKEND_H_ diff --git a/src/backend/par_store_backend_gel_nvm.c b/src/persist/backend/par_store_backend_gel_nvm.c similarity index 90% rename from src/backend/par_store_backend_gel_nvm.c rename to src/persist/backend/par_store_backend_gel_nvm.c index 940cccd..e275f52 100644 --- a/src/backend/par_store_backend_gel_nvm.c +++ b/src/persist/backend/par_store_backend_gel_nvm.c @@ -1,17 +1,21 @@ /** * @file par_store_backend_gel_nvm.c * @brief Adapt GeneralEmbeddedCLibraries/nvm to the packaged parameter-storage backend interface. - * @author OpenAI + * @author wdfk-prog () * @version 1.0 - * @date 2026-03-27 - * + * @date 2026-03-29 + * + * @copyright Copyright (c) 2026 + * * @details This adapter is optional. Enable it only when the project includes * the GeneralEmbeddedCLibraries/nvm module and a valid PAR_CFG_NVM_REGION is * configured for parameter storage. + * @par Change Log: + * Date Version Author Description + * 2026-03-29 1.0 wdfk-prog first version */ - #include "par_cfg.h" -#include "par_store_backend.h" +#include "persist/backend/par_store_backend.h" #if (1 == PAR_CFG_NVM_EN) && (1 == PAR_CFG_NVM_BACKEND_GEL_EN) #include "middleware/nvm/nvm/src/nvm.h" diff --git a/src/par_nvm.c b/src/persist/par_nvm.c similarity index 99% rename from src/par_nvm.c rename to src/persist/par_nvm.c index e444e5a..09028d1 100644 --- a/src/par_nvm.c +++ b/src/persist/par_nvm.c @@ -53,16 +53,16 @@ /** * @brief Include dependencies. */ -#include "par_nvm.h" +#include "persist/par_nvm.h" #include "par_cfg.h" -#include "par_if.h" +#include "port/par_if.h" #if ( 1 == PAR_CFG_NVM_EN ) #include #include -#include "backend/par_store_backend.h" +#include "persist/backend/par_store_backend.h" /** * @brief Compile-time definitions. */ diff --git a/src/par_nvm.h b/src/persist/par_nvm.h similarity index 100% rename from src/par_nvm.h rename to src/persist/par_nvm.h diff --git a/src/par_atomic.h b/src/port/par_atomic.h similarity index 100% rename from src/par_atomic.h rename to src/port/par_atomic.h diff --git a/src/par_if.c b/src/port/par_if.c similarity index 99% rename from src/par_if.c rename to src/port/par_if.c index d66adab..4397126 100644 --- a/src/par_if.c +++ b/src/port/par_if.c @@ -25,7 +25,7 @@ /** * @brief Include dependencies. */ -#include "par_if.h" +#include "port/par_if.h" #if (1 == PAR_CFG_IF_PORT_EN) /** diff --git a/src/par_if.h b/src/port/par_if.h similarity index 98% rename from src/par_if.h rename to src/port/par_if.h index 5442714..6c01032 100644 --- a/src/par_if.h +++ b/src/port/par_if.h @@ -25,7 +25,6 @@ */ #include #include "par.h" -#include "par_cfg.h" /** * @brief Compile-time definitions. */ diff --git a/template/par_layout_static.htmp b/template/par_layout_static.htmp index 3bcc33f..3fee5e9 100644 --- a/template/par_layout_static.htmp +++ b/template/par_layout_static.htmp @@ -1,6 +1,6 @@ /** * @file par_layout_static.h - * @brief + * @brief Declare the generated static layout contract. * @author wdfk-prog () * @version 1.0 * @date 2026-03-21 @@ -17,7 +17,7 @@ #include -#include "../src/par_def.h" +#include "def/par_def.h" #ifdef __cplusplus extern "C" { From cc14aa640160fcfa3967204ae2d902ae9be239f9 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Mon, 30 Mar 2026 16:51:04 +0800 Subject: [PATCH 27/36] feat(API): Add generic fast setter and simplify NVM table-ID handling --- README.md | 2 + docs/api-reference.md | 3 + docs/architecture.md | 6 +- docs/getting-started.md | 6 + src/def/par_def.c | 5 + src/def/par_def.h | 28 ++ src/detail/par_typed_impl.inc | 52 +++- src/par.c | 85 +++--- src/par.h | 49 ++- src/par_cfg.h | 11 + src/persist/fnv.h | 20 ++ src/persist/hash_32a.c | 163 ++++++++++ src/persist/par_nvm.c | 541 ++++++++++++++++----------------- src/persist/par_nvm_table_id.c | 153 ++++++++++ src/persist/par_nvm_table_id.h | 56 ++++ src/port/par_if.c | 36 --- src/port/par_if.h | 7 - 17 files changed, 832 insertions(+), 391 deletions(-) create mode 100644 src/persist/fnv.h create mode 100644 src/persist/hash_32a.c create mode 100644 src/persist/par_nvm_table_id.c create mode 100644 src/persist/par_nvm_table_id.h diff --git a/README.md b/README.md index 6a0bf45..3586bf2 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,8 @@ This repository contains the reusable module core and templates. A real integrat - The current ID lookup implementation uses a one-entry-per-bucket hash map generated at compile time from `par_table.def`. External IDs must therefore be not only unique, but also collision-free under the configured hash geometry. Optional runtime diagnostic scans can be enabled with `PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK` and `PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK` when additional startup logs are useful. See `docs/architecture.md` for the collision rule and avoidance guidance. - Unchecked setter APIs skip runtime validation callbacks and on-change callbacks, so they should be reserved for tightly controlled hot paths. Bitwise fast setters are further restricted to `U8` / `U16` / `U32` flags or bitmask parameters. Legacy `*_fast()` names remain as deprecated aliases. - NVM support is optional. When enabled, `src/persist/par_nvm.c` depends on a mounted storage backend interface and on ID and persistence metadata being enabled. The package can build the `GeneralEmbeddedCLibraries/nvm` adapter from `src/persist/backend/`, or the application can provide its own `par_store_backend_get_api()` implementation. +- When `PAR_CFG_TABLE_ID_CHECK_EN = 1`, startup uses a fixed 32-bit FNV-1a table-ID to validate persisted-image compatibility. The table-ID covers `PAR_CFG_TABLE_ID_SCHEMA_VER`, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID. It intentionally does not cover defaults, ranges, names, units, descriptions, or access flags. +- `PAR_CFG_TABLE_ID_SCHEMA_VER` defaults in `src/par_cfg.h` and may be overridden in `port/par_cfg_port.h`; the integrator should bump it when intentionally changing the serialized table-ID schema. - `par_init()` applies startup default values directly to live storage. Integer default values from `par_table.def` are compiled into a grouped width-based storage object, while `F32` default values are applied to the 32-bit storage group after layout offsets are available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. Because this startup initialization does not go through the public setter path, it does not invoke runtime validation or on-change callbacks. - `PAR_CFG_ENABLE_RESET_ALL_RAW` controls whether raw reset-all support and grouped default mirror snapshot support are enabled. diff --git a/docs/api-reference.md b/docs/api-reference.md index 0b82c1d..1001f97 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -61,6 +61,7 @@ These are relevant only when mutex support is enabled in the integration. | Function | Description | | --- | --- | | `par_set(par_num, p_val)` | Set a parameter from a typed pointer. This public setter path enforces access policy and returns `ePAR_ERROR_ACCESS` when the target parameter is externally read-only. | +| `par_set_fast(par_num, p_val)` | Set a parameter from a typed pointer through the unchecked fast path. This API resolves the runtime type and then dispatches to the matching `par_set_xxx_fast()` implementation. | | `par_set_by_id(id, p_val)` | Set a parameter using its external ID. This path resolves the ID to `par_num_t` and then uses the same checked setter flow as `par_set()`. | ## Typed setter macro wrappers @@ -181,6 +182,8 @@ These APIs do not follow the same runtime usage pattern as the value access APIs Available only when `PAR_CFG_NVM_EN = 1` and a concrete parameter-storage backend is linked. +When `PAR_CFG_TABLE_ID_CHECK_EN = 1`, `par_nvm_init()` compares a stored 4-byte FNV-1a table-ID against the live table. The table-ID covers `PAR_CFG_TABLE_ID_SCHEMA_VER`, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID for parameters that are actually persisted. Startup first validates header CRC/signature, then validates the table-ID, then loads payload objects only if both checks pass. The collected error bits then drive a centralized recovery flow: NVM access errors restore live RAM values to defaults, while table-ID/header/CRC mismatches restore defaults and rebuild the managed NVM image. + | Function | Description | | --- | --- | | `par_set_n_save(par_num, p_val)` | Set one parameter and persist it immediately. | diff --git a/docs/architecture.md b/docs/architecture.md index 5b071ca..dab8ba9 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -320,7 +320,11 @@ When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`, `par_set_all_to_default()` also uses th When `PAR_CFG_NVM_EN = 1`, the module can persist selected parameters to NVM. -NVM persistence uses the parameter metadata, persistence flags, CRC handling, and table hash validation to detect incompatible or corrupted stored data. +NVM persistence uses per-object CRC plus an optional startup table-ID check to detect incompatible or corrupted stored data. + +When `PAR_CFG_TABLE_ID_CHECK_EN = 1`, the module uses a fixed 32-bit FNV-1a digest and stores 4 bytes in the NVM header. The digest covers only metadata that changes binary compatibility of persisted records: `PAR_CFG_TABLE_ID_SCHEMA_VER`, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID. Default values, ranges, names, units, descriptions, and access flags are intentionally excluded because they do not change the serialized NVM object layout used by `par_nvm.c`. + +If the stored table-ID mismatches the live table-ID, the module treats the persisted image as incompatible and rebuilds the managed NVM area from current default values and the current schema. This can happen after an intentional schema/version bump, after parameter persistence layout changes, or after stored-image corruption. The recovery action is centralized in `par_nvm_init()`: it accumulates status bits from header validation, table-ID validation, and payload loading, then decides whether to restore defaults only or restore defaults plus rewrite the managed NVM image. For this feature, `par_nvm.c` mounts a parameter-storage backend interface during initialization. The packaged `GeneralEmbeddedCLibraries/nvm` adapter is one option, but the core no longer depends directly on a specific NVM repository layout. diff --git a/docs/getting-started.md b/docs/getting-started.md index 24a96fd..d56ccf8 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -356,6 +356,12 @@ Fast setters are meant for controlled hot paths where you accept reduced safety (void)par_set_u16_fast(ePAR_PWM_LIMIT, 1200U); ``` +When you only have a typed value pointer and still want the unchecked path, use the generic fast dispatcher: +```c +float32_t value = 12.0f; +(void)par_set_fast(ePAR_TARGET_TEMP, &value); +``` + Do not use fast setters as the default API for ordinary application code. ### Bitwise fast setters diff --git a/src/def/par_def.c b/src/def/par_def.c index 4f8844f..0ed8c97 100644 --- a/src/def/par_def.c +++ b/src/def/par_def.c @@ -397,6 +397,11 @@ static const par_cfg_t g_par_table[ePAR_NUM_OF] = { * @brief Table size in bytes. */ static const uint32_t gu32_par_table_size = sizeof(g_par_table); + +/** + * @brief Compile-time derived number of parameters flagged persistent. + */ +const uint16_t g_par_persistent_count = (uint16_t)PAR_PERSISTENT_COMPILE_COUNT; /** * @brief Function declarations and definitions. */ diff --git a/src/def/par_def.h b/src/def/par_def.h index 771f176..f644eaf 100644 --- a/src/def/par_def.h +++ b/src/def/par_def.h @@ -28,8 +28,13 @@ extern "C" { * @brief Include dependencies. */ #include +#include /** * @brief Compile-time definitions. + * + * @note is required here because par_table.def uses true/false in + * the persistence column, and this header expands that column in + * PAR_PERSISTENT_COMPILE_COUNT. */ typedef struct par_cfg_s par_cfg_t; /** @@ -131,6 +136,28 @@ enum PAR_LAYOUT_COMPILE_COUNT_SUM = (PAR_LAYOUT_COMPILE_COUNT8 + PAR_LAYOUT_COMPILE_COUNT16 + PAR_LAYOUT_COMPILE_COUNT32) }; +#define PAR_ITEM_PERSIST_COUNT(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) + ((pers_) ? 1u : 0u) +enum +{ + PAR_PERSISTENT_COMPILE_COUNT = 0u +#define PAR_ITEM_U8 PAR_ITEM_PERSIST_COUNT +#define PAR_ITEM_U16 PAR_ITEM_PERSIST_COUNT +#define PAR_ITEM_U32 PAR_ITEM_PERSIST_COUNT +#define PAR_ITEM_I8 PAR_ITEM_PERSIST_COUNT +#define PAR_ITEM_I16 PAR_ITEM_PERSIST_COUNT +#define PAR_ITEM_I32 PAR_ITEM_PERSIST_COUNT +#define PAR_ITEM_F32 PAR_ITEM_PERSIST_COUNT +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +}; +#undef PAR_ITEM_PERSIST_COUNT + #undef PAR_ITEM_COUNT_ONE #undef PAR_ITEM_COUNT_ZERO /** @@ -144,6 +171,7 @@ const par_cfg_t *par_cfg_get(const par_num_t par_num); */ uint32_t par_cfg_get_table_size(void); + #ifdef __cplusplus } #endif diff --git a/src/detail/par_typed_impl.inc b/src/detail/par_typed_impl.inc index b4f78ed..06d1b57 100644 --- a/src/detail/par_typed_impl.inc +++ b/src/detail/par_typed_impl.inc @@ -172,6 +172,32 @@ static void par_assert_fast_typed_preconditions(const par_num_t par_num, } } } +/** + * @brief Validate expected parameter type against resolved metadata. + * + * @note Callers shall resolve metadata first and then validate the. + * expected parameter type against the resolved configuration. + * + * @param p_cfg Resolved parameter configuration entry. + * @param expected_type Expected parameter type for operation. + * @return Status of operation. + */ +static par_status_t par_validate_expected_type(const par_cfg_t * const p_cfg, + const par_type_list_t expected_type) +{ + if (NULL == p_cfg) + { + return ePAR_ERROR; + } + + PAR_ASSERT(expected_type == p_cfg->type); + if (expected_type != p_cfg->type) + { + return ePAR_ERROR_TYPE; + } + + return ePAR_OK; +} /** * @brief Checked parameter write core for all public setter APIs. * @@ -195,17 +221,11 @@ static void par_assert_fast_typed_preconditions(const par_num_t par_num, * * @return Status of operation. */ -static par_status_t par_set_checked_core(const par_num_t par_num, - const par_type_list_t expected_type, - const par_type_t * const p_typed_val, - const void * const p_ptr_val) +static par_status_t par_set_checked_core(const par_num_t par_num, const par_type_list_t expected_type, const par_type_t * const p_typed_val, const void * const p_ptr_val) { const par_cfg_t *par_cfg = NULL; par_type_t val = { 0 }; - par_status_t status = par_resolve_runtime(par_num, - p_ptr_val, - (NULL == p_typed_val), - &par_cfg); + par_status_t status = par_resolve_runtime(par_num, p_ptr_val, (NULL == p_typed_val), &par_cfg); if (ePAR_OK != status) { @@ -234,12 +254,12 @@ static par_status_t par_set_checked_core(const par_num_t par_num, { switch (expected_type) { - PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_U8, uint8_t, u8) - PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_I8, int8_t, i8) - PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_U16, uint16_t, u16) - PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_I16, int16_t, i16) - PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_U32, uint32_t, u32) - PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_I32, int32_t, i32) + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_U8, uint8_t, u8) + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_I8, int8_t, i8) + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_U16, uint16_t, u16) + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_I16, int16_t, i16) + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_U32, uint32_t, u32) + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_I32, int32_t, i32) #if (1 == PAR_CFG_ENABLE_TYPE_F32) PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_F32, float32_t, f32) #endif @@ -257,8 +277,8 @@ static par_status_t par_set_checked_core(const par_num_t par_num, switch (expected_type) { - PAR_CHECKED_SET_CASE(u8, U8, ePAR_TYPE_U8, u8) - PAR_CHECKED_SET_CASE(i8, I8, ePAR_TYPE_I8, i8) + PAR_CHECKED_SET_CASE(u8, U8, ePAR_TYPE_U8, u8) + PAR_CHECKED_SET_CASE(i8, I8, ePAR_TYPE_I8, i8) PAR_CHECKED_SET_CASE(u16, U16, ePAR_TYPE_U16, u16) PAR_CHECKED_SET_CASE(i16, I16, ePAR_TYPE_I16, i16) PAR_CHECKED_SET_CASE(u32, U32, ePAR_TYPE_U32, u32) diff --git a/src/par.c b/src/par.c index bb1700f..c7ea134 100644 --- a/src/par.c +++ b/src/par.c @@ -159,27 +159,19 @@ static const char *gs_status[] = { "ERROR PARAM", "ERROR PAR NUM", "ERROR ACCESS", + "ERROR TABLE ID", "WARN SET TO DEF", "WARN NVM REWRITTEN", "NO PERSISTENT", "LIMITED", "N/A", - "N/A", }; #endif /** * @brief Function declarations. */ -#if (1 == PAR_CFG_ENABLE_ID) -static inline uint32_t par_hash_id(const uint16_t id); -#if ((1 == PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK) || (1 == PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK)) -static par_status_t par_runtime_validate_id_table(const par_cfg_t * const p_par_cfg); -#endif -#endif static par_status_t par_set_checked_core(const par_num_t par_num, const par_type_list_t expected_type, const par_type_t * const p_typed_val, const void * const p_ptr_val); -#if (1 == PAR_CFG_NVM_EN) -static par_status_t par_is_value_changed(const par_num_t par_num, const void *p_val, bool * const p_value_changed); -#endif + /** * @brief Function declarations and definitions. */ @@ -262,32 +254,7 @@ static par_status_t par_resolve_runtime(const par_num_t par_num, const void * co return par_resolve_metadata(par_num, p_arg, require_arg, pp_cfg); } -/** - * @brief Validate expected parameter type against resolved metadata. - * - * @note Callers shall resolve metadata first and then validate the. - * expected parameter type against the resolved configuration. - * - * @param p_cfg Resolved parameter configuration entry. - * @param expected_type Expected parameter type for operation. - * @return Status of operation. - */ -static par_status_t par_validate_expected_type(const par_cfg_t * const p_cfg, - const par_type_list_t expected_type) -{ - if (NULL == p_cfg) - { - return ePAR_ERROR; - } - - PAR_ASSERT(expected_type == p_cfg->type); - if (expected_type != p_cfg->type) - { - return ePAR_ERROR_TYPE; - } - return ePAR_OK; -} /** * @brief Compare two F32 values by raw bit pattern. * @@ -612,6 +579,54 @@ par_status_t par_set(const par_num_t par_num, const void *p_val) return par_set_checked_core(par_num, par_cfg->type, NULL, p_val); } +/** + * @brief Set parameter value through the generic unchecked fast path. + * + * @param par_num Parameter number (enumeration). + * @param p_val Pointer to value. + * @return Status of operation. + */ +par_status_t par_set_fast(const par_num_t par_num, const void *p_val) +{ + const par_cfg_t *par_cfg = NULL; + const par_status_t status = par_resolve_runtime(par_num, p_val, true, &par_cfg); + + if (ePAR_OK != status) + { + return status; + } + + switch (par_cfg->type) + { + case ePAR_TYPE_U8: + return par_set_u8_fast(par_num, *(const uint8_t *)p_val); + + case ePAR_TYPE_I8: + return par_set_i8_fast(par_num, *(const int8_t *)p_val); + + case ePAR_TYPE_U16: + return par_set_u16_fast(par_num, *(const uint16_t *)p_val); + + case ePAR_TYPE_I16: + return par_set_i16_fast(par_num, *(const int16_t *)p_val); + + case ePAR_TYPE_U32: + return par_set_u32_fast(par_num, *(const uint32_t *)p_val); + + case ePAR_TYPE_I32: + return par_set_i32_fast(par_num, *(const int32_t *)p_val); + +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + case ePAR_TYPE_F32: + return par_set_f32_fast(par_num, *(const float32_t *)p_val); +#endif + + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + return ePAR_ERROR_TYPE; + } +} /** * @brief Set parameter value by ID. * diff --git a/src/par.h b/src/par.h index 360a74f..dddd807 100644 --- a/src/par.h +++ b/src/par.h @@ -45,24 +45,25 @@ enum ePAR_OK = 0U, /**< Normal operation. */ /* Error status bits. */ - ePAR_STATUS_ERROR_MASK = 0x03FFU, - ePAR_ERROR = 0x0001U, /**< General parameter error. */ - ePAR_ERROR_INIT = 0x0002U, /**< Parameter initialization error or usage before initialization. */ - ePAR_ERROR_NVM = 0x0004U, /**< Parameter storage to NVM error. */ - ePAR_ERROR_CRC = 0x0008U, /**< Parameter CRC corrupted. */ - ePAR_ERROR_TYPE = 0x0010U, /**< Using invalid API function for given parameter data type. */ - ePAR_ERROR_MUTEX = 0x0020U, /**< Acquiring mutex failed. */ - ePAR_ERROR_VALUE = 0x0040U, /**< Invalid parameter value (validation failed). */ - ePAR_ERROR_PARAM = 0x0080U, /**< Invalid function argument. */ - ePAR_ERROR_PAR_NUM = 0x0100U, /**< Invalid parameter number. */ - ePAR_ERROR_ACCESS = 0x0200U, /**< Write access denied by parameter access policy. */ + ePAR_STATUS_ERROR_MASK = 0x07FFU, + ePAR_ERROR = 0x0001U, /**< General parameter error. */ + ePAR_ERROR_INIT = 0x0002U, /**< Parameter initialization error or usage before initialization. */ + ePAR_ERROR_NVM = 0x0004U, /**< Parameter storage to NVM error. */ + ePAR_ERROR_CRC = 0x0008U, /**< Parameter CRC corrupted. */ + ePAR_ERROR_TYPE = 0x0010U, /**< Using invalid API function for given parameter data type. */ + ePAR_ERROR_MUTEX = 0x0020U, /**< Acquiring mutex failed. */ + ePAR_ERROR_VALUE = 0x0040U, /**< Invalid parameter value (validation failed). */ + ePAR_ERROR_PARAM = 0x0080U, /**< Invalid function argument. */ + ePAR_ERROR_PAR_NUM = 0x0100U, /**< Invalid parameter number. */ + ePAR_ERROR_ACCESS = 0x0200U, /**< Write access denied by parameter access policy. */ + ePAR_ERROR_TABLE_ID = 0x0400U, /**< Stored parameter-table ID does not match the live table. */ /* Warning status bits. */ - ePAR_STATUS_WAR_MASK = 0xFC00U, - ePAR_WAR_SET_TO_DEF = 0x0400U, /**< Parameters set to default. */ - ePAR_WAR_NVM_REWRITTEN = 0x0800U, /**< NVM parameters area completely re-written. */ - ePAR_WAR_NO_PERSISTENT = 0x1000U, /**< No persistent parameters -> set PAR_CFG_NVM_EN to 0. */ - ePAR_WAR_LIMITED = 0x2000U, /**< Parameter value limited within [min,max]. */ + ePAR_STATUS_WAR_MASK = 0xF800U, + ePAR_WAR_SET_TO_DEF = 0x0800U, /**< Parameters set to default. */ + ePAR_WAR_NVM_REWRITTEN = 0x1000U, /**< NVM parameters area completely re-written. */ + ePAR_WAR_NO_PERSISTENT = 0x2000U, /**< No persistent parameters -> set PAR_CFG_NVM_EN to 0. */ + ePAR_WAR_LIMITED = 0x4000U, /**< Parameter value limited within [min,max]. */ }; typedef uint16_t par_status_t; /** @@ -215,6 +216,22 @@ void par_release_mutex(const par_num_t par_num); * @return Operation status. */ par_status_t par_set(const par_num_t par_num, const void *p_val); +/** + * @brief Set one parameter from a typed input pointer through the unchecked fast path. + * + * @note This generic fast setter validates initialization, parameter number, + * and pointer arguments, then dispatches to the matching typed + * `par_set_xxx_fast()` implementation. + * + * @note It intentionally bypasses access enforcement, runtime validation + * callbacks, and on-change callbacks. Range limiting still follows + * the typed fast setter behavior. + * + * @param par_num Parameter number. + * @param p_val Pointer to the input value. + * @return Operation status. + */ +par_status_t par_set_fast(const par_num_t par_num, const void *p_val); #if (1 == PAR_CFG_ENABLE_ID) /** * @brief Set one parameter by external parameter ID. diff --git a/src/par_cfg.h b/src/par_cfg.h index 498af61..bc42095 100644 --- a/src/par_cfg.h +++ b/src/par_cfg.h @@ -91,6 +91,17 @@ #endif #endif +/** + * @brief Parameter-table ID schema version. + * + * @note Increase this value when the serialized table-ID composition changes. + * The integrator owns this version number and may override it in + * port/par_cfg_port.h before this header provides the default. + */ +#ifndef PAR_CFG_TABLE_ID_SCHEMA_VER +#define PAR_CFG_TABLE_ID_SCHEMA_VER (1U) +#endif + /** * @brief Enable/Disable debug mode. */ diff --git a/src/persist/fnv.h b/src/persist/fnv.h new file mode 100644 index 0000000..f295d7a --- /dev/null +++ b/src/persist/fnv.h @@ -0,0 +1,20 @@ +/** + * @file fnv.h + * @brief Minimal FNV declarations required by the bundled hash_32a.c implementation. + * + * @note This header exists to support the public-domain lcn2/fnv hash_32a.c source. + */ +#ifndef _PAR_PERSIST_FNV_H_ +#define _PAR_PERSIST_FNV_H_ + +#include +#include + +typedef uint32_t Fnv32_t; + +#define FNV1_32A_INIT ((Fnv32_t)0x811C9DC5U) + +Fnv32_t fnv_32a_buf(void *buf, size_t len, Fnv32_t hval); +Fnv32_t fnv_32a_str(char *str, Fnv32_t hval); + +#endif /* _PAR_PERSIST_FNV_H_ */ diff --git a/src/persist/hash_32a.c b/src/persist/hash_32a.c new file mode 100644 index 0000000..c118eb2 --- /dev/null +++ b/src/persist/hash_32a.c @@ -0,0 +1,163 @@ +/* + * hash_32 - 32 bit Fowler/Noll/Vo FNV-1a hash code + * + *** + * + * For the most up to date copy of this code, see: + * + * https://github.com/lcn2/fnv + * + * For more information on the FNV hash, see: + * + * http://www.isthe.com/chongo/tech/comp/fnv/index.html + * + *** + * + * Fowler/Noll/Vo hash + * + * The basis of this hash algorithm was taken from an idea sent + * as reviewer comments to the IEEE POSIX P1003.2 committee by: + * + * Phong Vo (http://www.research.att.com/info/kpv/) + * Glenn Fowler (http://www.research.att.com/~gsf/) + * + * In a subsequent ballot round: + * + * Landon Curt Noll (http://www.isthe.com/chongo/) + * + * improved on their algorithm. Some people tried this hash + * and found that it worked rather well. In an EMail message + * to Landon, they named it the ``Fowler/Noll/Vo'' or FNV hash. + * + * FNV hashes are designed to be fast while maintaining a low + * collision rate. The FNV speed allows one to quickly hash lots + * of data while maintaining a reasonable collision rate. + * + *** + * + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * 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 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. + * + * For more information, please refer to + * + *** + * + * Author: + * + * chongo (Landon Curt Noll) /\oo/\ + * + * http://www.isthe.com/chongo/index.html + * https://github.com/lcn2 + * + * Share and enjoy! :-) + */ + + +#include +#include "fnv.h" + + +/* + * 32 bit magic FNV-1a prime + */ +#define FNV_32_PRIME ((Fnv32_t)0x01000193) + + +/* + * fnv_32a_buf - perform a 32 bit Fowler/Noll/Vo FNV-1a hash on a buffer + * + * input: + * buf - start of buffer to hash + * len - length of buffer in octets + * hval - previous hash value or 0 if first call + * + * returns: + * 32 bit hash as a static hash type + * + * NOTE: To use the recommended 32 bit FNV-1a hash, use FNV1_32A_INIT as the + * hval arg on the first call to either fnv_32a_buf() or fnv_32a_str(). + */ +Fnv32_t +fnv_32a_buf(void *buf, size_t len, Fnv32_t hval) +{ + unsigned char *bp = (unsigned char *)buf; /* start of buffer */ + unsigned char *be = bp + len; /* beyond end of buffer */ + + /* + * FNV-1a hash each octet in the buffer + */ + while (bp < be) { + + /* xor the bottom with the current octet */ + hval ^= (Fnv32_t)*bp++; + + /* multiply by the 32 bit FNV magic prime mod 2^32 */ +#if defined(NO_FNV_GCC_OPTIMIZATION) + hval *= FNV_32_PRIME; +#else + hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24); +#endif + } + + /* return our new hash value */ + return hval; +} + + +/* + * fnv_32a_str - perform a 32 bit Fowler/Noll/Vo FNV-1a hash on a string + * + * input: + * str - string to hash + * hval - previous hash value or 0 if first call + * + * returns: + * 32 bit hash as a static hash type + * + * NOTE: To use the recommended 32 bit FNV-1a hash, use FNV1_32A_INIT as the + * hval arg on the first call to either fnv_32a_buf() or fnv_32a_str(). + */ +Fnv32_t +fnv_32a_str(char *str, Fnv32_t hval) +{ + unsigned char *s = (unsigned char *)str; /* unsigned string */ + + /* + * FNV-1a hash each octet in the buffer + */ + while (*s) { + + /* xor the bottom with the current octet */ + hval ^= (Fnv32_t)*s++; + + /* multiply by the 32 bit FNV magic prime mod 2^32 */ +#if defined(NO_FNV_GCC_OPTIMIZATION) + hval *= FNV_32_PRIME; +#else + hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24); +#endif + } + + /* return our new hash value */ + return hval; +} \ No newline at end of file diff --git a/src/persist/par_nvm.c b/src/persist/par_nvm.c index 09028d1..8f0d97b 100644 --- a/src/persist/par_nvm.c +++ b/src/persist/par_nvm.c @@ -11,6 +11,7 @@ * @par Change Log: * Date Version Author Description * 2026-01-29 V3.0.1 Ziga Miklosic + * 2026-03-30 1.2 wdfk-prog simplify NVM flow and table-ID handling */ /** @@ -39,16 +40,15 @@ * * @note RULES OF "PAR_CFG_TABLE_ID_CHECK_EN" SETTINGS: * - * It is normal that parameter table will change during development. - * and therefore code will detect table change between "RAM" and "NVM". - * (detection of par table change enable/disable with. - * "PAR_CFG_TABLE_ID_CHECK_EN" setting). + * During development it is normal that the parameter table and the persisted + * compatibility model evolve. Startup therefore validates the live table-ID + * against the value stored in NVM when table-ID checking is enabled. * - * But as SW is released and potential at customer, developer must not. - * change the pre-existing parameter table as it will loose all values. - * stored inside NVM. Therefore it is recommended to disable table ID. - * detection after first release of SW. Adding new parameters to pre-existing. - * table has no harm at all nor does it have any side effects. + * A mismatch means the managed NVM image is no longer compatible with the live + * firmware image. Typical causes are an intentional schema-version bump, a + * change in persisted-parameter order/type/ID, or stored-image corruption. + * The recovery path is centralized in par_nvm_init(): restore defaults first, + * then rebuild the managed NVM image only for errors that require a rewrite. */ /** * @brief Include dependencies. @@ -57,38 +57,41 @@ #include "par_cfg.h" #include "port/par_if.h" -#if ( 1 == PAR_CFG_NVM_EN ) +#if (1 == PAR_CFG_NVM_EN) #include +#include #include #include "persist/backend/par_store_backend.h" +#include "persist/par_nvm_table_id.h" /** - * @brief Compile-time definitions. - */ -/** - * @brief Parameter signature and size in bytes. + * @brief Parameter NVM header object. */ -#define PAR_NVM_SIGN (0xFF00AA55) -#define PAR_NVM_SIGN_SIZE (4U) - +typedef struct +{ + uint32_t sign; /**< Signature. */ + uint16_t obj_nb; /**< Stored data object number. */ + uint16_t crc; /**< Header CRC. */ +} par_nvm_head_obj_t; /** - * @brief Parameter header number of object size. - * @details Unit: byte. + * @brief Serialized parameter NVM header layout. + * + * @details This type models the exact on-storage header prefix: the fixed + * header followed by the persisted parameter-table hash. */ -#define PAR_NVM_NB_OF_OBJ_SIZE (2U) - +typedef struct +{ + par_nvm_head_obj_t head; /**< Fixed header prefix. */ + uint8_t hash[PAR_NVM_TABLE_ID_SIZE]; /**< Parameter-table ID digest. */ +} par_nvm_head_layout_t; /** - * @brief Parameter CRC size. - * @details Unit: byte. + * @brief Compile-time definitions. */ -#define PAR_NVM_CRC_SIZE (2U) - /** - * @brief Parameter configuration hash size. - * @details Unit: byte. + * @brief Parameter signature value. */ -#define PAR_NVM_HASH_SIZE (32U) +#define PAR_NVM_SIGN (0xFF00AA55) /** * @brief Parameter NVM header content address start. @@ -96,27 +99,16 @@ * @note This is offset to reserved NVM region. For absolute address. * add that value to NVM start region. */ -#define PAR_NVM_HEAD_ADDR (0x00) -#define PAR_NVM_HEAD_SIGN_ADDR (PAR_NVM_HEAD_ADDR) -#define PAR_NVM_HEAD_NB_OF_OBJ_ADDR (PAR_NVM_HEAD_SIGN_ADDR + PAR_NVM_SIGN_SIZE) -#define PAR_NVM_HEAD_CRC_ADDR (PAR_NVM_HEAD_NB_OF_OBJ_ADDR + PAR_NVM_NB_OF_OBJ_SIZE) -#define PAR_NVM_HEAD_HASH_ADDR (PAR_NVM_HEAD_CRC_ADDR + PAR_NVM_CRC_SIZE) +#define PAR_NVM_HEAD_ADDR (0x00U) +#define PAR_NVM_HEAD_SIGN_ADDR (PAR_NVM_HEAD_ADDR + (uint32_t)offsetof(par_nvm_head_layout_t, head.sign)) +#define PAR_NVM_HEAD_HASH_ADDR (PAR_NVM_HEAD_ADDR + (uint32_t)offsetof(par_nvm_head_layout_t, hash)) /** * @brief Parameters first data object start address. * @details Unit: byte. */ -#define PAR_NVM_FIRST_DATA_OBJ_ADDR (PAR_NVM_HEAD_HASH_ADDR + PAR_NVM_HASH_SIZE) +#define PAR_NVM_FIRST_DATA_OBJ_ADDR (PAR_NVM_HEAD_ADDR + (uint32_t)sizeof(par_nvm_head_layout_t)) -/** - * @brief Parameter NVM header object. - */ -typedef struct -{ - uint32_t sign; /**< Signature. */ - uint16_t obj_nb; /**< Stored data object number. */ - uint16_t crc; /**< Header CRC. */ -} par_nvm_head_obj_t; /** * @brief Parameter NVM data object. */ @@ -165,103 +157,24 @@ static par_nvm_lut_t g_par_nvm_data_obj_addr[ePAR_NUM_OF] = { 0 }; static par_status_t par_nvm_load_all(const uint16_t num_of_par); static par_status_t par_nvm_restore_fast(const par_num_t par_num, const void *p_val); -static par_status_t par_nvm_corrupt_signature(void); static par_status_t par_nvm_read_header(par_nvm_head_obj_t * const p_head_obj); static par_status_t par_nvm_write_header(const uint16_t num_of_par); static par_status_t par_nvm_validate_header(uint16_t * const p_num_of_par); static uint16_t par_nvm_calc_crc(const uint8_t * const p_data, const uint8_t size); static uint8_t par_nvm_calc_obj_crc(const par_nvm_data_obj_t * const p_obj); -static uint16_t par_nvm_get_per_par(void); static void par_nvm_build_new_nvm_lut(void); static uint32_t par_nvm_get_nvm_lut_addr(const uint16_t id); static bool par_nvm_is_in_nvm_lut(const uint16_t id); #if (1 == PAR_CFG_TABLE_ID_CHECK_EN) -static par_status_t par_nvm_erase_signature(void); -static par_status_t par_nvm_check_table_id(const uint8_t * const p_table_id); -static par_status_t par_nvm_write_table_id(const uint8_t * const p_table_id); +static par_status_t par_nvm_check_table_id(const uint32_t table_id); +static par_status_t par_nvm_write_table_id(const uint32_t table_id); #endif static par_status_t par_nvm_init_nvm(void); -static par_status_t par_nvm_sync(void); - -static par_status_t par_nvm_store_read(const uint32_t addr, const uint32_t size, uint8_t * const p_buf); -static par_status_t par_nvm_store_write(const uint32_t addr, const uint32_t size, const uint8_t * const p_buf); -static par_status_t par_nvm_store_erase(const uint32_t addr, const uint32_t size); -static par_status_t par_nvm_store_deinit(void); -static par_status_t par_nvm_store_is_init(bool * const p_is_init); - -/** - * @brief Read raw bytes from the selected parameter storage backend. - * - * @param addr Backend-relative start address. - * @param size Number of bytes to read. - * @param p_buf Destination buffer. - * @return Operation status. - */ -static par_status_t par_nvm_store_read(const uint32_t addr, const uint32_t size, uint8_t * const p_buf) -{ - if (NULL == p_buf) - { - return ePAR_ERROR_PARAM; - } - - return gp_store->read(addr, size, p_buf); -} -/** - * @brief Write raw bytes to the selected parameter storage backend. - * - * @param addr Backend-relative start address. - * @param size Number of bytes to write. - * @param p_buf Source buffer. - * @return Operation status. - */ -static par_status_t par_nvm_store_write(const uint32_t addr, const uint32_t size, const uint8_t * const p_buf) -{ - if (NULL == p_buf) - { - return ePAR_ERROR_PARAM; - } - - return gp_store->write(addr, size, p_buf); -} -/** - * @brief Erase a raw storage range through the selected backend. - * - * @param addr Backend-relative start address. - * @param size Number of bytes to erase. - * @return Operation status. - */ -static par_status_t par_nvm_store_erase(const uint32_t addr, const uint32_t size) -{ - return gp_store->erase(addr, size); -} -/** - * @brief Deinitialize the selected backend. - * - * @return Operation status. - */ -static par_status_t par_nvm_store_deinit(void) -{ - return gp_store->deinit(); -} -/** - * @brief Query backend initialization state. - * - * @param p_is_init Destination initialization flag. - * @return Operation status. - */ -static par_status_t par_nvm_store_is_init(bool * const p_is_init) -{ - if (NULL == p_is_init) - { - return ePAR_ERROR_PARAM; - } - return gp_store->is_init(p_is_init); -} /** * @brief Function declarations and definitions. */ @@ -302,91 +215,57 @@ static par_status_t par_nvm_restore_fast(const par_num_t par_num, const void *p_ return ePAR_ERROR_TYPE; } } -/** - * Corrupt parameter signature to NVM. - * - * @brief Return ePAR_OK if signature corrupted OK. In case of NVM error it returns. - * ePAR_ERROR_NVM. - * - * @return Status of operation. - */ -static par_status_t par_nvm_corrupt_signature(void) -{ - par_status_t status = ePAR_OK; - - if (ePAR_OK != par_nvm_store_erase(PAR_NVM_HEAD_SIGN_ADDR, PAR_NVM_SIGN_SIZE)) - { - status = ePAR_ERROR_NVM; - PAR_DBG_PRINT("PAR_NVM: NVM error during signature corruption!"); - } - - return status; -} - #if (1 == PAR_CFG_TABLE_ID_CHECK_EN) /** - * @brief Erase parameter signature from NVM. - * - * @return Status of operation. - */ -static par_status_t par_nvm_erase_signature(void) -{ - par_status_t status = ePAR_OK; - - status = par_nvm_store_erase(PAR_NVM_HEAD_SIGN_ADDR, PAR_NVM_SIGN_SIZE); - - return status; -} -/** - * Check unique parameter table ID. - * - * @brief This function check for parameter configuration table change while. - * some parameters are already stored in NVM. First it read stored. - * table ID from NVM and then compare it with current table ID (live. - * from RAM). In case of mismatched it return error. + * @brief Check unique parameter table ID. * - * Table ID is being calculated based on hash algorithm (SHA-256). + * @details This function compares the stored 32-bit little-endian table-ID + * value with the live value calculated from the current parameter table. * - * @param p_table_id Pointer to reference table ID (in "RAM"). + * @param table_id Host-endian live table-ID value. * @return Status of operation. */ -static par_status_t par_nvm_check_table_id(const uint8_t * const p_table_id) +static par_status_t par_nvm_check_table_id(const uint32_t table_id) { par_status_t status = ePAR_OK; - uint8_t nvm_table_id[32] = { 0 }; + uint32_t stored_table_id = 0U; + const par_status_t store_status = gp_store->read(PAR_NVM_HEAD_HASH_ADDR, PAR_NVM_TABLE_ID_SIZE, (uint8_t *)&stored_table_id); - if (ePAR_OK != par_nvm_store_read(PAR_NVM_HEAD_HASH_ADDR, PAR_NVM_HASH_SIZE, (uint8_t *)&nvm_table_id)) + if (ePAR_OK == store_status) { - status = ePAR_ERROR_NVM; - } - else - { - if (0 == memcmp(&nvm_table_id, p_table_id, 32U)) - { - status = ePAR_OK; - } + const uint32_t expected_table_id = par_nvm_table_id_to_storage(table_id); - else + if (stored_table_id != expected_table_id) { - status = ePAR_ERROR; + status = ePAR_ERROR_TABLE_ID; } } + else + { + status = ePAR_ERROR_NVM; + PAR_DBG_PRINT("PAR_NVM: table-ID read failed, %u", (unsigned)store_status); + } return status; } /** * @brief Write unique parameter table ID to NVM. * - * @param p_table_id Pointer to reference table ID (in "RAM"). + * @param table_id Host-endian live table-ID value. * @return Status of operation. */ -static par_status_t par_nvm_write_table_id(const uint8_t * const p_table_id) +static par_status_t par_nvm_write_table_id(const uint32_t table_id) { par_status_t status = ePAR_OK; + const uint32_t stored_table_id = par_nvm_table_id_to_storage(table_id); + const par_status_t store_status = gp_store->write(PAR_NVM_HEAD_HASH_ADDR, + PAR_NVM_TABLE_ID_SIZE, + (const uint8_t *)&stored_table_id); - if (ePAR_OK != par_nvm_store_write(PAR_NVM_HEAD_HASH_ADDR, PAR_NVM_HASH_SIZE, p_table_id)) + if (ePAR_OK != store_status) { status = ePAR_ERROR_NVM; + PAR_DBG_PRINT("PAR_NVM: table-ID write failed, %u", (unsigned)store_status); } return status; @@ -405,10 +284,15 @@ static par_status_t par_nvm_read_header(par_nvm_head_obj_t * const p_head_obj) PAR_ASSERT(NULL != p_head_obj); - if (ePAR_OK != par_nvm_store_read(PAR_NVM_HEAD_ADDR, sizeof(par_nvm_head_obj_t), (uint8_t *)p_head_obj)) { - status = ePAR_ERROR_NVM; - PAR_DBG_PRINT("PAR_NVM: NVM error during header read!"); + const par_status_t store_status = gp_store->read(PAR_NVM_HEAD_ADDR, + (uint32_t)sizeof(par_nvm_head_obj_t), + (uint8_t *)p_head_obj); + if (ePAR_OK != store_status) + { + status = ePAR_ERROR_NVM; + PAR_DBG_PRINT("PAR_NVM: header read failed, %u", (unsigned)store_status); + } } return status; @@ -423,13 +307,30 @@ static par_status_t par_nvm_write_header(const uint16_t num_of_par) { par_status_t status = ePAR_OK; par_nvm_head_obj_t head_obj = { 0 }; + +#if (1 == PAR_CFG_TABLE_ID_CHECK_EN) + const uint32_t table_id = par_nvm_table_id_calc(); + + status = par_nvm_write_table_id(table_id); + if (ePAR_OK != status) + { + PAR_DBG_PRINT("PAR_NVM: table-ID write failed, %u", (unsigned)status); + return status; + } +#endif + head_obj.obj_nb = num_of_par; - head_obj.crc = par_nvm_calc_crc((uint8_t *)&head_obj.obj_nb, PAR_NVM_NB_OF_OBJ_SIZE); + head_obj.crc = par_nvm_calc_crc((uint8_t *)&head_obj.obj_nb, sizeof(head_obj.obj_nb)); head_obj.sign = PAR_NVM_SIGN; - if (ePAR_OK != par_nvm_store_write(PAR_NVM_HEAD_ADDR, sizeof(par_nvm_head_obj_t), (const uint8_t *)&head_obj)) + + const par_status_t store_status = gp_store->write(PAR_NVM_HEAD_ADDR, + (uint32_t)sizeof(par_nvm_head_obj_t), + (const uint8_t *)&head_obj); + if (ePAR_OK != store_status) { status = ePAR_ERROR_NVM; - PAR_DBG_PRINT("PAR_NVM: NVM error during header write!"); + PAR_DBG_PRINT("PAR_NVM: header write failed, %u", (unsigned)store_status); + return status; } PAR_DBG_PRINT("PAR_NVM: Write NVM header with %d nb. of object", num_of_par); @@ -452,7 +353,7 @@ static par_status_t par_nvm_validate_header(uint16_t * const p_num_of_par) { if (PAR_NVM_SIGN == obj_head.sign) { - crc_calc = par_nvm_calc_crc((uint8_t *)&obj_head.obj_nb, PAR_NVM_NB_OF_OBJ_SIZE); + crc_calc = par_nvm_calc_crc((uint8_t *)&obj_head.obj_nb, sizeof(obj_head.obj_nb)); if (crc_calc == obj_head.crc) { *p_num_of_par = obj_head.obj_nb; @@ -547,7 +448,7 @@ static par_status_t par_nvm_load_all(const uint16_t num_of_par) { /* Each NVM object currently occupies 8 bytes. */ obj_addr = ((8 * i) + PAR_NVM_FIRST_DATA_OBJ_ADDR); - store_status = par_nvm_store_read(obj_addr, sizeof(par_nvm_data_obj_t), (uint8_t *)&obj_data); + store_status = gp_store->read(obj_addr, (uint32_t)sizeof(par_nvm_data_obj_t), (uint8_t *)&obj_data); if (ePAR_OK == store_status) { crc_calc = par_nvm_calc_obj_crc(&obj_data); @@ -580,13 +481,14 @@ static par_status_t par_nvm_load_all(const uint16_t num_of_par) else { status = ePAR_ERROR_NVM; + PAR_DBG_PRINT("PAR_NVM: object read failed, %u", (unsigned)store_status); break; } } PAR_DBG_PRINT("PAR_NVM: Loading all persistent parameters with status: %s", par_get_status_str(status)); PAR_DBG_PRINT("PAR_NVM: Nb. of stored pars in NVM: %d", num_of_par); - PAR_DBG_PRINT("PAR_NVM: Nb. of live persistent: \t%d", par_nvm_get_per_par()); + PAR_DBG_PRINT("PAR_NVM: Nb. of live persistent: \t%d", PAR_PERSISTENT_COMPILE_COUNT); if (ePAR_OK == status) { for (i = 0; i < ePAR_NUM_OF; i++) @@ -613,7 +515,15 @@ static par_status_t par_nvm_load_all(const uint16_t num_of_par) { /* The object count only grows when new persistent parameters appear. */ status |= par_nvm_write_header(num_of_par + new_par_cnt); - status |= par_nvm_sync(); + if (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK)) + { + const par_status_t sync_status = gp_store->sync(); + if (ePAR_OK != sync_status) + { + status |= ePAR_ERROR_NVM; + PAR_DBG_PRINT("PAR_NVM: sync failed, %u", (unsigned)sync_status); + } + } #if (PAR_CFG_DEBUG_EN) PAR_DBG_PRINT("PAR_NVM: Added %d new parameters to NVM LUT table!", new_par_cnt); @@ -623,27 +533,9 @@ static par_status_t par_nvm_load_all(const uint16_t num_of_par) return status; } -/** - * @brief Get total number of persistent parameters. - * - * @return Number of persistent parameters. - */ -static uint16_t par_nvm_get_per_par(void) -{ - uint16_t num_of_per_par = 0U; - - for (par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++) - { - if (true == par_get_config(par_num)->persistent) - { - num_of_per_par++; - } - } - - return num_of_per_par; -} /** * @brief Build new parameter NVM LUT table. + */ static void par_nvm_build_new_nvm_lut(void) { @@ -736,15 +628,16 @@ static par_status_t par_nvm_init_nvm(void) if (ePAR_OK == status) { - status = par_nvm_store_is_init(&is_nvm_init); + (void)gp_store->is_init(&is_nvm_init); } if ((ePAR_OK == status) && (false == is_nvm_init)) { - if (ePAR_OK != gp_store->init()) + const par_status_t store_status = gp_store->init(); + if (ePAR_OK != store_status) { status = ePAR_ERROR_INIT; - PAR_DBG_PRINT("PAR_NVM: Parameter storage backend init error!"); + PAR_DBG_PRINT("PAR_NVM: backend init failed, %u", (unsigned)store_status); } else { @@ -754,22 +647,6 @@ static par_status_t par_nvm_init_nvm(void) return status; } -/** - * @brief Synchronize the mounted storage backend. - * - * @return Status of operation. - */ -static par_status_t par_nvm_sync(void) -{ - par_status_t status = ePAR_OK; - - if (ePAR_OK != gp_store->sync()) - { - status = ePAR_ERROR_NVM; - } - - return status; -} /** * @} */ @@ -785,59 +662,122 @@ static par_status_t par_nvm_sync(void) * @brief Initialize parameter NVM handling. * @details Initialization behavior depends on the settings in par_cfg.h. * Table-ID checking is enabled only when PAR_CFG_TABLE_ID_CHECK_EN is set. + * + * The recovery flow is centralized and cumulative: + * - header validation runs first; + * - table-ID validation runs only when the header is valid; + * - payload loading runs only when both checks pass; + * - then the collected error bits decide whether startup should restore live + * RAM values to defaults only, or restore defaults and also rebuild the + * managed NVM image. + * + * This keeps the recovery action aligned with the detected failure class + * instead of hiding it inside a long if-else chain. + * * @return Status of initialization. */ par_status_t par_nvm_init(void) { par_status_t status = ePAR_OK; + par_status_t detect_status = ePAR_OK; uint16_t obj_nb = 0U; uint16_t per_par_nb = 0U; + bool need_set_default = false; + bool need_rewrite_nvm = false; + status = par_nvm_init_nvm(); - if (ePAR_OK == status) + if (ePAR_OK != status) { - gb_is_init = true; - per_par_nb = par_nvm_get_per_par(); - if (per_par_nb > 0) - { - status = par_nvm_validate_header(&obj_nb); - if (ePAR_OK == status) - { -#if (PAR_CFG_TABLE_ID_CHECK_EN) - /* TODO: add table-ID consistency check. */ + return status; + } + + gb_is_init = true; + per_par_nb = PAR_PERSISTENT_COMPILE_COUNT; + + if (per_par_nb == 0U) + { + return (par_status_t)(status | ePAR_WAR_NO_PERSISTENT); + } + + /* Step 1: validate header */ + detect_status = par_nvm_validate_header(&obj_nb); + +#if (1 == PAR_CFG_TABLE_ID_CHECK_EN) + /* Step 2: validate table-ID only when header is valid */ + if (ePAR_OK == (detect_status & ePAR_STATUS_ERROR_MASK)) + { + const uint32_t live_table_id = par_nvm_table_id_calc(); + detect_status |= par_nvm_check_table_id(live_table_id); + } #endif - status = par_nvm_load_all(obj_nb); - if (ePAR_ERROR_CRC == status) - { - status = par_nvm_reset_all(); - status |= (ePAR_WAR_SET_TO_DEF | ePAR_WAR_NVM_REWRITTEN); - } + /* Step 3: load payload only when previous checks are valid */ + if (ePAR_OK == (detect_status & ePAR_STATUS_ERROR_MASK)) + { + detect_status |= par_nvm_load_all(obj_nb); + } - else if (ePAR_ERROR_NVM == status) - { - /* - * Fall back to defaults if NVM contents are only partially - * usable. A mixed state of restored and default values is - * unsafe. - */ - par_set_all_to_default(); - status |= ePAR_WAR_SET_TO_DEF; - } - } + /* Step 4: classify recovery action from detected issues */ + if (0U != (detect_status & ePAR_ERROR_TABLE_ID)) + { + PAR_DBG_PRINT("PAR_NVM: Table-ID mismatch detected; restoring defaults and rebuilding managed NVM image."); + need_set_default = true; + need_rewrite_nvm = true; + } - else if ((ePAR_ERROR == status) || (ePAR_ERROR_CRC == status)) - { - status = par_nvm_reset_all(); - status |= (ePAR_WAR_SET_TO_DEF | ePAR_WAR_NVM_REWRITTEN); - } + if (0U != (detect_status & ePAR_ERROR_CRC)) + { + PAR_DBG_PRINT("PAR_NVM: CRC corruption detected; rebuilding NVM from defaults."); + need_set_default = true; + need_rewrite_nvm = true; + } - } + /* + * ePAR_ERROR here represents generic header/signature validation failure + * from par_nvm_validate_header(). + */ + if (0U != (detect_status & ePAR_ERROR)) + { + PAR_DBG_PRINT("PAR_NVM: Header/signature mismatch detected; rebuilding NVM from defaults."); + need_set_default = true; + need_rewrite_nvm = true; + } - else - { - status |= ePAR_WAR_NO_PERSISTENT; - PAR_DBG_PRINT("PAR_NVM: No persistent parameters... Nothing to do..."); - } + /* + * NVM access failures are not considered fully recoverable. + * Restore live values to defaults, but keep ePAR_ERROR_NVM in final status. + */ + if (0U != (detect_status & ePAR_ERROR_NVM)) + { + PAR_DBG_PRINT("PAR_NVM: NVM access error detected; restoring live values to defaults without forced rewrite."); + need_set_default = true; + } + + /* Step 5: perform recovery */ + if (true == need_set_default) + { + status |= par_set_all_to_default(); + status |= ePAR_WAR_SET_TO_DEF; + } + + if (true == need_rewrite_nvm) + { + status |= par_nvm_reset_all(); + status |= ePAR_WAR_NVM_REWRITTEN; + } + + /* + * Step 6: finalize returned status. + * + * Recoverable problems (header/signature mismatch, CRC mismatch, + * table-ID mismatch) should not remain as error bits once recovery + * succeeded. Keep only warnings for those cases. + * + * Non-recoverable backend/storage access failures remain reported. + */ + if (0U != (detect_status & ePAR_ERROR_NVM)) + { + status |= ePAR_ERROR_NVM; } return status; @@ -855,9 +795,11 @@ par_status_t par_nvm_deinit(void) { if (true == gb_is_nvm_owner) { - if (ePAR_OK != par_nvm_store_deinit()) + const par_status_t store_status = gp_store->deinit(); + if (ePAR_OK != store_status) { status = ePAR_ERROR; + PAR_DBG_PRINT("PAR_NVM: backend deinit failed, %u", (unsigned)store_status); } } @@ -905,20 +847,37 @@ par_status_t par_nvm_write(const par_num_t par_num, const bool nvm_sync) const par_cfg_t * const par_cfg = par_get_config(par_num); if (true == par_cfg->persistent) { + par_status_t store_status = ePAR_OK; + par_get(par_num, (uint32_t *)&obj_data.data); obj_data.id = par_cfg->id; /* Current implementation stores fixed-size 8-byte objects. */ obj_data.size = 4U; obj_data.crc = par_nvm_calc_obj_crc(&obj_data); par_addr = par_nvm_get_nvm_lut_addr(obj_data.id); - if (ePAR_OK != par_nvm_store_write(par_addr, sizeof(par_nvm_data_obj_t), (const uint8_t *)&obj_data)) + store_status = gp_store->write(par_addr, + (uint32_t)sizeof(par_nvm_data_obj_t), + (const uint8_t *)&obj_data); + if (ePAR_OK != store_status) { status |= ePAR_ERROR_NVM; + PAR_DBG_PRINT("PAR_NVM: parameter write failed, par_num=%u id=%u addr=0x%08lX err=%u", + (unsigned)par_num, + (unsigned)obj_data.id, + (unsigned long)par_addr, + (unsigned)store_status); } - if (true == nvm_sync) + if ((true == nvm_sync) && (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK))) { - status |= par_nvm_sync(); + const par_status_t sync_status = gp_store->sync(); + if (ePAR_OK != sync_status) + { + status |= ePAR_ERROR_NVM; + PAR_DBG_PRINT("PAR_NVM: sync failed after parameter write, par_num=%u err=%u", + (unsigned)par_num, + (unsigned)sync_status); + } } } else @@ -951,19 +910,41 @@ par_status_t par_nvm_write_all(void) if (true == gb_is_init) { - /* Mark the header invalid while rewriting the NVM image. */ - status |= par_nvm_corrupt_signature(); - for (par_num_t par_num = 0U; par_num < ePAR_NUM_OF; par_num++) + /* Mark the header invalid before bulk rewrite and commit that state. */ { - if (true == par_is_persistent(par_num)) + const par_status_t store_status = gp_store->erase(PAR_NVM_HEAD_SIGN_ADDR, (uint32_t)sizeof(uint32_t)); + if (ePAR_OK != store_status) { - status |= par_nvm_write(par_num, false); + status |= ePAR_ERROR_NVM; + PAR_DBG_PRINT("PAR_NVM: signature erase failed, %u", (unsigned)store_status); } } - /* Restore a valid header after the bulk rewrite completes. */ - status |= par_nvm_write_header(par_nvm_get_per_par()); - status |= par_nvm_sync(); + if (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK)) + { + for (par_num_t par_num = 0U; par_num < ePAR_NUM_OF; par_num++) + { + if (true == par_is_persistent(par_num)) + { + status |= par_nvm_write(par_num, false); + if (ePAR_OK != (status & ePAR_STATUS_ERROR_MASK)) + { + PAR_DBG_PRINT("PAR_NVM: bulk write aborted, par_num=%u id=%u addr=0x%08lX err=%u", + (unsigned)par_num, + (unsigned)par_get_config(par_num)->id, + (unsigned long)par_nvm_get_nvm_lut_addr(par_get_config(par_num)->id), + (unsigned)status); + break; + } + } + } + } + + /* Restore a valid header only after the full rewrite completes successfully. */ + if (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK)) + { + status |= par_nvm_write_header(PAR_PERSISTENT_COMPILE_COUNT); + } PAR_DBG_PRINT("PAR_NVM: Storing all to NVM status: %s", par_get_status_str(status)); } diff --git a/src/persist/par_nvm_table_id.c b/src/persist/par_nvm_table_id.c new file mode 100644 index 0000000..f0cee3c --- /dev/null +++ b/src/persist/par_nvm_table_id.c @@ -0,0 +1,153 @@ +/** + * @file par_nvm_table_id.c + * @brief Implement the parameter-table ID hash adapter. + * @author wdfk-prog () + * @version 1.0 + * @date 2026-03-30 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-30 1.0 wdfk-prog first version + */ + +#include + +#include "par.h" +#include "persist/fnv.h" +#include "persist/par_nvm_table_id.h" + +/** + * @brief Serialized table-ID record size for one persisted parameter. + */ +enum +{ + PAR_NVM_TABLE_ID_REC_SIZE = sizeof(((par_cfg_t *)0)->type) +#if (1 == PAR_CFG_ENABLE_ID) + + sizeof(((par_cfg_t *)0)->id) +#endif +}; + +/** + * @brief Convert 16-bit value from host endianness to little-endian storage order. + */ +static uint16_t par_nvm_table_id_to_le16(const uint16_t value) +{ +#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#if defined(__GNUC__) || defined(__clang__) + return __builtin_bswap16(value); +#else + return (uint16_t)(((value & 0x00FFU) << 8U) | ((value & 0xFF00U) >> 8U)); +#endif +#else + return value; +#endif +} + +/** + * @brief Convert 32-bit value from host endianness to little-endian storage order. + */ +static uint32_t par_nvm_table_id_to_le32(const uint32_t value) +{ +#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#if defined(__GNUC__) || defined(__clang__) + return __builtin_bswap32(value); +#else + return (((value & 0x000000FFUL) << 24U) | + ((value & 0x0000FF00UL) << 8U) | + ((value & 0x00FF0000UL) >> 8U) | + ((value & 0xFF000000UL) >> 24U)); +#endif +#else + return value; +#endif +} + +/** + * @brief Update FNV-1a context with one little-endian serialized scalar. + * + * @param p_hval Pointer to rolling FNV-1a state. + * @param p_serialized_size Pointer to serialized byte counter. + * @param p_value Pointer to source scalar. + * @param value_size Scalar width in bytes. Supported values: 1, 2, 4. + */ +static void par_nvm_table_id_hash_update_le(Fnv32_t * const p_hval, + uint32_t * const p_serialized_size, + const void * const p_value, + const uint32_t value_size) +{ + uint8_t serialized[sizeof(uint32_t)] = { 0U }; + + PAR_ASSERT(NULL != p_hval); + PAR_ASSERT(NULL != p_serialized_size); + PAR_ASSERT(NULL != p_value); + PAR_ASSERT((1U == value_size) || (2U == value_size) || (4U == value_size)); + + if (1U == value_size) + { + serialized[0] = *(const uint8_t *)p_value; + } + else if (2U == value_size) + { + const uint16_t value_le = par_nvm_table_id_to_le16(*(const uint16_t *)p_value); + memcpy(serialized, &value_le, sizeof(value_le)); + } + else + { + const uint32_t value_le = par_nvm_table_id_to_le32(*(const uint32_t *)p_value); + memcpy(serialized, &value_le, sizeof(value_le)); + } + + *p_hval = fnv_32a_buf(serialized, (size_t)value_size, *p_hval); + *p_serialized_size += value_size; +} + +/** + * @brief Convert host-endian digest to stored little-endian representation. + */ +uint32_t par_nvm_table_id_to_storage(const uint32_t table_id) +{ + return par_nvm_table_id_to_le32(table_id); +} + +/** + * @brief Calculate live parameter-table ID. + * + * @details The digest covers only metadata that affects the NVM storage + * compatibility of persisted parameters: schema version, persisted-parameter + * count, persisted-parameter order, type, and optional ID field. + */ +uint32_t par_nvm_table_id_calc(void) +{ + Fnv32_t hval = FNV1_32A_INIT; + uint32_t serialized_size = 0U; + const uint32_t schema_version = (uint32_t)PAR_CFG_TABLE_ID_SCHEMA_VER; + const uint16_t persistent_count = (uint16_t)PAR_PERSISTENT_COMPILE_COUNT; + const uint32_t expected_size = (uint32_t)sizeof(schema_version) + (uint32_t)sizeof(persistent_count) + ((uint32_t)persistent_count * (uint32_t)PAR_NVM_TABLE_ID_REC_SIZE); + + par_nvm_table_id_hash_update_le(&hval, &serialized_size, &schema_version, (uint32_t)sizeof(schema_version)); + par_nvm_table_id_hash_update_le(&hval, &serialized_size, &persistent_count, (uint32_t)sizeof(persistent_count)); + + for (par_num_t par_num = 0U; par_num < ePAR_NUM_OF; par_num++) + { + const par_cfg_t * const p_cfg = par_get_config(par_num); + const uint8_t type = (uint8_t)p_cfg->type; + +#if (1 == PAR_CFG_ENABLE_PERSIST) + if (false == p_cfg->persistent) + { + continue; + } +#endif + + par_nvm_table_id_hash_update_le(&hval, &serialized_size, &type, (uint32_t)sizeof(type)); +#if (1 == PAR_CFG_ENABLE_ID) + par_nvm_table_id_hash_update_le(&hval, &serialized_size, &p_cfg->id, (uint32_t)sizeof(p_cfg->id)); +#endif + } + + PAR_ASSERT(serialized_size == expected_size); + return (uint32_t)hval; +} diff --git a/src/persist/par_nvm_table_id.h b/src/persist/par_nvm_table_id.h new file mode 100644 index 0000000..509ec75 --- /dev/null +++ b/src/persist/par_nvm_table_id.h @@ -0,0 +1,56 @@ +/** + * @file par_nvm_table_id.h + * @brief Declare the parameter-table ID hash adapter. + * @author wdfk-prog () + * @version 1.0 + * @date 2026-03-30 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-30 1.0 wdfk-prog first version + */ +#ifndef _PAR_NVM_TABLE_ID_H_ +#define _PAR_NVM_TABLE_ID_H_ + +#include + +/** + * @brief Persisted table-ID width in bytes. + * + * @note The package fixes the table-ID algorithm to 32-bit FNV-1a whenever + * PAR_CFG_TABLE_ID_CHECK_EN is enabled, so the stored digest width is exactly + * one 32-bit word. + */ +#define PAR_NVM_TABLE_ID_SIZE ((uint32_t)sizeof(uint32_t)) + +/** + * @brief Calculate the live parameter-table ID. + * + * @details The digest covers only metadata that changes the binary + * compatibility of the persisted NVM image: + * - PAR_CFG_TABLE_ID_SCHEMA_VER + * - persistent-parameter count + * - persistent-parameter order + * - parameter type + * - parameter ID + * + * Default values, ranges, names, units, descriptions, and access flags are + * intentionally excluded because they do not change the serialized NVM object + * layout used by par_nvm.c. + * + * @return Host-endian 32-bit FNV-1a digest. + */ +uint32_t par_nvm_table_id_calc(void); + +/** + * @brief Convert a host-endian table-ID value into stored little-endian form. + * + * @param table_id Host-endian table-ID digest. + * @return 32-bit value encoded in the on-storage little-endian byte order. + */ +uint32_t par_nvm_table_id_to_storage(const uint32_t table_id); + +#endif /* _PAR_NVM_TABLE_ID_H_ */ diff --git a/src/port/par_if.c b/src/port/par_if.c index 4397126..03fb958 100644 --- a/src/port/par_if.c +++ b/src/port/par_if.c @@ -78,22 +78,6 @@ PAR_PORT_WEAK void par_if_release_mutex(const par_num_t par_num) { (void)par_num; } -/** - * @brief Calculate hash. - * - * @note Default weak implementation leaves hash output untouched. - * Integrator may provide a strong definition in port/par_if_port.c. - * - * @param p_data Pointer to data for hash calculation. - * @param size Size of data in bytes. - * @param p_hash Pointer to calculated hash number. - */ -PAR_PORT_WEAK void par_if_calc_hash(const uint8_t * const p_data, const uint32_t size, uint8_t * const p_hash) -{ - (void)p_data; - (void)size; - (void)p_hash; -} #else @@ -224,26 +208,6 @@ void par_if_release_mutex(const par_num_t par_num) UNUSED(par_num); osMutexRelease(g_par_mutex_id); } -/** - * @brief Calculate hash. - * - * @note User shall provide definition of that function based on used platform! - * - * If not being used leave empty. - * - * This function does not have an affect if "PAR_CFG_TABLE_ID_CHECK_EN". - * is set to 0. - * - * @param p_data Pointer to data for hash calculation. - * @param size Size of data in bytes. - * @return Pointer to calculated hash number. - */ -void par_if_calc_hash(const uint8_t * const p_data, const uint32_t size, uint8_t * const p_hash) -{ - UNUSED(p_data); - UNUSED(p_hash); - UNUSED(size); -} #endif /** diff --git a/src/port/par_if.h b/src/port/par_if.h index 6c01032..45b9632 100644 --- a/src/port/par_if.h +++ b/src/port/par_if.h @@ -52,12 +52,5 @@ par_status_t par_if_aquire_mutex(const par_num_t par_num); * @param par_num Parameter number. */ void par_if_release_mutex(const par_num_t par_num); -/** - * @brief Calculate the table hash for parameter metadata. - * @param p_data Pointer to the input bytes. - * @param size Number of bytes in p_data. - * @param p_hash Pointer to the output hash buffer. - */ -void par_if_calc_hash(const uint8_t * const p_data, const uint32_t size, uint8_t * const p_hash); #endif /* _PAR_IF_H_ */ From abbe2105bafff6dfa7172b4beb5fe41a56b90595 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Wed, 1 Apr 2026 14:56:41 +0800 Subject: [PATCH 28/36] fix(core)!: Rework parameter NVM header and CRC serialization --- README.md | 6 +- docs/api-reference.md | 4 +- docs/architecture.md | 8 +- docs/getting-started.md | 2 + src/persist/par_nvm.c | 380 +++++++++++++-------------------- src/persist/par_nvm_table_id.c | 91 ++------ src/persist/par_nvm_table_id.h | 20 +- src/port/par_if.c | 80 +++++++ src/port/par_if.h | 35 +++ 9 files changed, 292 insertions(+), 334 deletions(-) diff --git a/README.md b/README.md index 3586bf2..2fdc69a 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,11 @@ This repository contains the reusable module core and templates. A real integrat - The current ID lookup implementation uses a one-entry-per-bucket hash map generated at compile time from `par_table.def`. External IDs must therefore be not only unique, but also collision-free under the configured hash geometry. Optional runtime diagnostic scans can be enabled with `PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK` and `PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK` when additional startup logs are useful. See `docs/architecture.md` for the collision rule and avoidance guidance. - Unchecked setter APIs skip runtime validation callbacks and on-change callbacks, so they should be reserved for tightly controlled hot paths. Bitwise fast setters are further restricted to `U8` / `U16` / `U32` flags or bitmask parameters. Legacy `*_fast()` names remain as deprecated aliases. - NVM support is optional. When enabled, `src/persist/par_nvm.c` depends on a mounted storage backend interface and on ID and persistence metadata being enabled. The package can build the `GeneralEmbeddedCLibraries/nvm` adapter from `src/persist/backend/`, or the application can provide its own `par_store_backend_get_api()` implementation. -- When `PAR_CFG_TABLE_ID_CHECK_EN = 1`, startup uses a fixed 32-bit FNV-1a table-ID to validate persisted-image compatibility. The table-ID covers `PAR_CFG_TABLE_ID_SCHEMA_VER`, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID. It intentionally does not cover defaults, ranges, names, units, descriptions, or access flags. +- Live RAM layout and persisted NVM layout are intentionally different. RAM storage is grouped by value width, while the persistence area is a linear list of fixed 8-byte objects: `id(2) + size(1) + crc8(1) + data(4)`. +- The persisted `id` is the primary self-description field, so startup and rewrite logic do not rely only on hard-coded slot meaning. The current `size` field is still always written as `4`, so it is mainly a descriptor/integrity helper rather than a space-saving variable-width encoding. +- The serialized NVM header is written explicitly as a fixed 12-byte storage image (`sign(4) + obj_nb(2) + table_id(4) + crc16(2)`), so on-storage layout does not depend on compiler struct padding. Header CRC-16 covers the serialized `obj_nb + table_id` bytes, while each data record carries its own CRC-8 over `id + size + data`. +- CRC calculation is routed through port hooks with bundled software defaults. In this single-target profile the persisted image and the table-ID digest both use the native byte order of the running platform, so no additional byte-order conversion hook is required by the persistence path. +- When `PAR_CFG_TABLE_ID_CHECK_EN = 1`, startup compares the stored table-ID against the live table-ID to validate persisted-image compatibility. The table-ID covers `PAR_CFG_TABLE_ID_SCHEMA_VER`, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID, hashed as platform-native scalar bytes under the single-target profile. It intentionally does not cover defaults, ranges, names, units, descriptions, or access flags. - `PAR_CFG_TABLE_ID_SCHEMA_VER` defaults in `src/par_cfg.h` and may be overridden in `port/par_cfg_port.h`; the integrator should bump it when intentionally changing the serialized table-ID schema. - `par_init()` applies startup default values directly to live storage. Integer default values from `par_table.def` are compiled into a grouped width-based storage object, while `F32` default values are applied to the 32-bit storage group after layout offsets are available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. Because this startup initialization does not go through the public setter path, it does not invoke runtime validation or on-change callbacks. - `PAR_CFG_ENABLE_RESET_ALL_RAW` controls whether raw reset-all support and grouped default mirror snapshot support are enabled. diff --git a/docs/api-reference.md b/docs/api-reference.md index 1001f97..0970474 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -182,7 +182,9 @@ These APIs do not follow the same runtime usage pattern as the value access APIs Available only when `PAR_CFG_NVM_EN = 1` and a concrete parameter-storage backend is linked. -When `PAR_CFG_TABLE_ID_CHECK_EN = 1`, `par_nvm_init()` compares a stored 4-byte FNV-1a table-ID against the live table. The table-ID covers `PAR_CFG_TABLE_ID_SCHEMA_VER`, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID for parameters that are actually persisted. Startup first validates header CRC/signature, then validates the table-ID, then loads payload objects only if both checks pass. The collected error bits then drive a centralized recovery flow: NVM access errors restore live RAM values to defaults, while table-ID/header/CRC mismatches restore defaults and rebuild the managed NVM image. +The managed NVM payload area is a linear list of fixed 8-byte objects, not a width-group partition like the live RAM layout. Each object stores `id(2) + size(1) + crc8(1) + data(4)`. The `id` is the main self-description field used to match persisted records back to live parameters. The current implementation still writes `size = 4` for all persisted parameters, so that field is primarily descriptive and used in the integrity model rather than for NVM compaction. + +The serialized NVM header is emitted explicitly as a fixed 12-byte image (`sign + obj_nb + table_id + crc16`) rather than by writing raw struct memory. Header CRC-16 covers the serialized native-order `obj_nb + table_id` bytes, so corrupted header bytes fail header validation before any compatibility decision is made. Each small data record uses CRC-8 over its serialized native-order `id + size + data` bytes. When `PAR_CFG_TABLE_ID_CHECK_EN = 1`, `par_nvm_init()` compares the stored table-ID directly against the live table digest. The table-ID covers `PAR_CFG_TABLE_ID_SCHEMA_VER`, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID for parameters that are actually persisted, hashed as native platform scalar bytes. Startup first validates header CRC/signature, then validates the table-ID, then loads payload objects only if both checks pass. The collected error bits then drive a centralized recovery flow: NVM access errors restore live RAM values to defaults, while table-ID/header/CRC mismatches restore defaults and rebuild the managed NVM image. CRC calculation is exposed through port hooks with bundled software defaults. | Function | Description | | --- | --- | diff --git a/docs/architecture.md b/docs/architecture.md index dab8ba9..73f60fa 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -320,9 +320,11 @@ When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`, `par_set_all_to_default()` also uses th When `PAR_CFG_NVM_EN = 1`, the module can persist selected parameters to NVM. -NVM persistence uses per-object CRC plus an optional startup table-ID check to detect incompatible or corrupted stored data. +NVM persistence uses a fixed-size record format that is intentionally different from the live RAM layout. Live RAM storage is grouped by value width (`U8` / `U16` / `U32` / optional `F32`) for efficient access and raw reset, while the managed NVM area is a dense linear list of fixed 8-byte objects. Each persisted object stores: external parameter ID (2 bytes), payload-size descriptor (1 byte), per-record CRC-8 (1 byte), and one fixed 4-byte payload slot. -When `PAR_CFG_TABLE_ID_CHECK_EN = 1`, the module uses a fixed 32-bit FNV-1a digest and stores 4 bytes in the NVM header. The digest covers only metadata that changes binary compatibility of persisted records: `PAR_CFG_TABLE_ID_SCHEMA_VER`, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID. Default values, ranges, names, units, descriptions, and access flags are intentionally excluded because they do not change the serialized NVM object layout used by `par_nvm.c`. +The external ID is the main self-description field of the persisted image. Loader and rewrite paths use it to match records to live parameters, so the persistence model is less dependent on one parameter meaning being bound forever to one absolute slot position. The `size` field is currently written as `4` for all persisted parameters. In other words, the current implementation does not save NVM space for `U8`/`U16`; `size` is kept mainly as a descriptor and integrity-assistance field for the fixed-slot format. + +The serialized NVM header is a fixed 12-byte storage image (`sign + obj_nb + table_id + crc16`) emitted explicitly instead of by writing raw struct memory. The header CRC covers the serialized native-order `obj_nb + table_id` bytes, so header corruption is detected separately from a valid header that simply belongs to another parameter-table schema. Each data object uses CRC-8 over its serialized native-order `id + size + data` bytes. This profile intentionally ties the persisted image to the current target architecture and does not normalize byte order for cross-platform migration. CRC calculation is routed through port hooks with bundled software defaults. The table-ID digest follows the same rule and hashes each scalar exactly as represented in native platform memory. The digest covers only metadata that changes binary compatibility of persisted records: `PAR_CFG_TABLE_ID_SCHEMA_VER`, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID. Default values, ranges, names, units, descriptions, and access flags are intentionally excluded because they do not change the serialized NVM object layout used by `par_nvm.c`. If the stored table-ID mismatches the live table-ID, the module treats the persisted image as incompatible and rebuilds the managed NVM area from current default values and the current schema. This can happen after an intentional schema/version bump, after parameter persistence layout changes, or after stored-image corruption. The recovery action is centralized in `par_nvm_init()`: it accumulates status bits from header validation, table-ID validation, and payload loading, then decides whether to restore defaults only or restore defaults plus rewrite the managed NVM image. @@ -351,7 +353,7 @@ Implemented under layered `src/` subdirectories: Implemented by the integrator as needed: - `par_cfg_port.h` -- `par_if_port.c` (optional strong override for the weak defaults in `src/port/par_if.c`) +- `par_if_port.c` (optional strong override for the weak defaults in `src/port/par_if.c`, including mutex and CRC helpers) - `par_atomic_port.h` This separation makes the core reusable while still allowing the target platform to provide mutexes, logging, assertions, and atomic primitives. Packaged storage backend adapters stay inside the `src/persist/backend/` subtree because they are reusable module integrations rather than board-specific port code. diff --git a/docs/getting-started.md b/docs/getting-started.md index d56ccf8..6eb7519 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -145,6 +145,8 @@ ID-based lookup is generated statically when `PAR_CFG_ENABLE_ID = 1`. Optional s When NVM is enabled, the parameters module requires a concrete storage backend implementation. `src/persist/par_nvm.c` resolves and validates the backend API once during initialization, then uses the mounted callbacks directly for later reads, writes, erases, and sync operations. The package can build the `GeneralEmbeddedCLibraries/nvm` adapter from `parameters/src/persist/backend/`, or the application can provide `par_store_backend_get_api()` itself. The module can reuse an already-initialized backend or initialize it on demand and later deinitialize it only when it owns that initialization. Module deinit is conservative: it attempts backend and interface cleanup, and it clears the top-level module init state only after the owned child deinit steps succeed. +The persisted NVM image uses fixed 8-byte objects (`id + size + crc8 + 4-byte data slot`) instead of the grouped-width RAM layout used at runtime. The persisted `id` keeps records self-describing across load/rewrite paths. The current implementation still writes `size = 4` for every persisted parameter, so narrow values do not save NVM bytes yet. The header is serialized explicitly as a fixed 12-byte image (`sign + obj_nb + table_id + crc16`). Under the current single-target assumption, fields are stored, validated, and hashed in the native byte order of the running platform instead of being normalized to a cross-platform storage endianness. Header CRC-16 therefore covers the serialized native-order `obj_nb + table_id` bytes, each data object uses CRC-8 over native-order `id + size + data`, and the table-ID digest is computed from native-order scalar images as well. The bundled defaults are provided through `par_if_crc16_accumulate()` and `par_if_crc8_accumulate()`. + Backend choices: - enable the packaged `GeneralEmbeddedCLibraries/nvm` adapter diff --git a/src/persist/par_nvm.c b/src/persist/par_nvm.c index 8f0d97b..a2fb3eb 100644 --- a/src/persist/par_nvm.c +++ b/src/persist/par_nvm.c @@ -33,7 +33,7 @@ * Parameter storage is reserved in "Parameters" region of NVM. Look. * at the nvm_cfg.h/c module for NVM region descriptions. * - * Parameters stored into NVM in little endianness format. + * Parameters stored into NVM in the native byte order of the target platform. * * For details how parameters are handled in NVM go look at the. * documentation. @@ -41,8 +41,10 @@ * @note RULES OF "PAR_CFG_TABLE_ID_CHECK_EN" SETTINGS: * * During development it is normal that the parameter table and the persisted - * compatibility model evolve. Startup therefore validates the live table-ID - * against the value stored in NVM when table-ID checking is enabled. + * compatibility model evolve. The serialized NVM header therefore stores the + * current table-ID digest together with the persistent-object count, and the + * header CRC protects both fields. Startup validates the live table-ID against + * the stored digest when table-ID checking is enabled. * * A mismatch means the managed NVM image is no longer compatible with the live * firmware image. Typical causes are an intentional schema-version bump, a @@ -70,21 +72,11 @@ */ typedef struct { - uint32_t sign; /**< Signature. */ - uint16_t obj_nb; /**< Stored data object number. */ - uint16_t crc; /**< Header CRC. */ + uint32_t sign; /**< Signature in host order. */ + uint16_t obj_nb; /**< Stored data object number in host order. */ + uint32_t table_id; /**< Stored parameter-table ID in platform-native order. */ + uint16_t crc; /**< Header CRC-16 over serialized obj_nb and table_id. */ } par_nvm_head_obj_t; -/** - * @brief Serialized parameter NVM header layout. - * - * @details This type models the exact on-storage header prefix: the fixed - * header followed by the persisted parameter-table hash. - */ -typedef struct -{ - par_nvm_head_obj_t head; /**< Fixed header prefix. */ - uint8_t hash[PAR_NVM_TABLE_ID_SIZE]; /**< Parameter-table ID digest. */ -} par_nvm_head_layout_t; /** * @brief Compile-time definitions. */ @@ -99,26 +91,56 @@ typedef struct * @note This is offset to reserved NVM region. For absolute address. * add that value to NVM start region. */ -#define PAR_NVM_HEAD_ADDR (0x00U) -#define PAR_NVM_HEAD_SIGN_ADDR (PAR_NVM_HEAD_ADDR + (uint32_t)offsetof(par_nvm_head_layout_t, head.sign)) -#define PAR_NVM_HEAD_HASH_ADDR (PAR_NVM_HEAD_ADDR + (uint32_t)offsetof(par_nvm_head_layout_t, hash)) - -/** - * @brief Parameters first data object start address. - * @details Unit: byte. - */ -#define PAR_NVM_FIRST_DATA_OBJ_ADDR (PAR_NVM_HEAD_ADDR + (uint32_t)sizeof(par_nvm_head_layout_t)) - -/** - * @brief Parameter NVM data object. +#define PAR_NVM_HEAD_ADDR (0x00U) +#define PAR_NVM_HEAD_SIGN_ADDR (PAR_NVM_HEAD_ADDR) +#define PAR_NVM_HEAD_SIGN_SIZE ((uint32_t)sizeof(uint32_t)) +#define PAR_NVM_HEAD_OBJ_NB_OFFSET (PAR_NVM_HEAD_SIGN_SIZE) +#define PAR_NVM_HEAD_OBJ_NB_SIZE ((uint32_t)sizeof(uint16_t)) +#define PAR_NVM_HEAD_TABLE_ID_OFFSET (PAR_NVM_HEAD_OBJ_NB_OFFSET + PAR_NVM_HEAD_OBJ_NB_SIZE) +#define PAR_NVM_HEAD_TABLE_ID_SIZE ((uint32_t)sizeof(uint32_t)) +#define PAR_NVM_HEAD_CRC_OFFSET (PAR_NVM_HEAD_TABLE_ID_OFFSET + PAR_NVM_HEAD_TABLE_ID_SIZE) +#define PAR_NVM_HEAD_CRC_SIZE ((uint32_t)sizeof(uint16_t)) +#define PAR_NVM_HEAD_SIZE (PAR_NVM_HEAD_CRC_OFFSET + PAR_NVM_HEAD_CRC_SIZE) +#define PAR_NVM_FIRST_DATA_OBJ_ADDR (PAR_NVM_HEAD_ADDR + PAR_NVM_HEAD_SIZE) + +/** + * @brief Persisted parameter object layout (current implementation). + * + * @details Each persistent parameter is serialized as one fixed-size 8-byte + * object in the managed NVM area: + * + * Byte offset: 0 1 2 3 4 5 6 7 + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * Field: | id | size | crc8 | data slot | + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * Width: |<---- 2 bytes ---->| 1 B | 1 B |<------ 4 bytes ------->| + * + * Meaning: + * - id : external parameter ID of this persisted entry. Its main value is + * that persisted records stay self-describing instead of depending + * only on fixed slot positions. + * - size : payload-width descriptor. The current implementation always writes + * the full 4-byte payload-slot width, so this field does not save + * NVM space yet; it is kept as a descriptor and integrity helper. + * - crc : per-record CRC-8 calculated over the serialized id/size/data bytes. + * - data : 32-bit payload slot used to store the parameter value. Even U8/U16 + * parameters still occupy the same 4-byte payload slot in NVM. + * + * @note Live RAM layout and persisted NVM layout are intentionally different. + * RAM storage is grouped by value width, while the NVM persistence area is a + * linear list of fixed 8-byte objects. */ typedef struct { uint16_t id; /**< Parameter ID. */ - uint8_t size; /**< Size of parameter data block. */ - uint8_t crc; /**< CRC of parameter value. */ - par_type_t data; /**< 4-byte storage for parameter value. */ + uint8_t size; /**< Payload-width descriptor. */ + uint8_t crc; /**< CRC-8 over id, size, and 4-byte payload slot. */ + par_type_t data; /**< Fixed 4-byte payload slot for the parameter value. */ } par_nvm_data_obj_t; + +#define PAR_NVM_DATA_OBJ_STRIDE ((uint32_t)sizeof(par_nvm_data_obj_t)) +#define PAR_NVM_DATA_SLOT_SIZE ((uint8_t)sizeof(((par_nvm_data_obj_t *)0)->data)) + /** * @brief Parameter NVM LUT talbe. */ @@ -154,124 +176,9 @@ static par_nvm_lut_t g_par_nvm_data_obj_addr[ePAR_NUM_OF] = { 0 }; /** * @brief Function declarations. */ -static par_status_t par_nvm_load_all(const uint16_t num_of_par); -static par_status_t par_nvm_restore_fast(const par_num_t par_num, const void *p_val); - -static par_status_t par_nvm_read_header(par_nvm_head_obj_t * const p_head_obj); -static par_status_t par_nvm_write_header(const uint16_t num_of_par); -static par_status_t par_nvm_validate_header(uint16_t * const p_num_of_par); - -static uint16_t par_nvm_calc_crc(const uint8_t * const p_data, const uint8_t size); +static uint16_t par_nvm_calc_head_crc(const par_nvm_head_obj_t * const p_head_obj); static uint8_t par_nvm_calc_obj_crc(const par_nvm_data_obj_t * const p_obj); - -static void par_nvm_build_new_nvm_lut(void); -static uint32_t par_nvm_get_nvm_lut_addr(const uint16_t id); static bool par_nvm_is_in_nvm_lut(const uint16_t id); - -#if (1 == PAR_CFG_TABLE_ID_CHECK_EN) -static par_status_t par_nvm_check_table_id(const uint32_t table_id); -static par_status_t par_nvm_write_table_id(const uint32_t table_id); -#endif - -static par_status_t par_nvm_init_nvm(void); - -/** - * @brief Function declarations and definitions. - */ -static par_status_t par_nvm_restore_fast(const par_num_t par_num, const void *p_val) -{ - if (NULL == p_val) - { - return ePAR_ERROR_PARAM; - } - - switch (par_get_type(par_num)) - { - case ePAR_TYPE_U8: - return par_set_u8_fast(par_num, *(const uint8_t *)p_val); - - case ePAR_TYPE_I8: - return par_set_i8_fast(par_num, *(const int8_t *)p_val); - - case ePAR_TYPE_U16: - return par_set_u16_fast(par_num, *(const uint16_t *)p_val); - - case ePAR_TYPE_I16: - return par_set_i16_fast(par_num, *(const int16_t *)p_val); - - case ePAR_TYPE_U32: - return par_set_u32_fast(par_num, *(const uint32_t *)p_val); - - case ePAR_TYPE_I32: - return par_set_i32_fast(par_num, *(const int32_t *)p_val); - -#if (1 == PAR_CFG_ENABLE_TYPE_F32) - case ePAR_TYPE_F32: - return par_set_f32_fast(par_num, *(const float32_t *)p_val); -#endif - - case ePAR_TYPE_NUM_OF: - default: - return ePAR_ERROR_TYPE; - } -} -#if (1 == PAR_CFG_TABLE_ID_CHECK_EN) -/** - * @brief Check unique parameter table ID. - * - * @details This function compares the stored 32-bit little-endian table-ID - * value with the live value calculated from the current parameter table. - * - * @param table_id Host-endian live table-ID value. - * @return Status of operation. - */ -static par_status_t par_nvm_check_table_id(const uint32_t table_id) -{ - par_status_t status = ePAR_OK; - uint32_t stored_table_id = 0U; - const par_status_t store_status = gp_store->read(PAR_NVM_HEAD_HASH_ADDR, PAR_NVM_TABLE_ID_SIZE, (uint8_t *)&stored_table_id); - - if (ePAR_OK == store_status) - { - const uint32_t expected_table_id = par_nvm_table_id_to_storage(table_id); - - if (stored_table_id != expected_table_id) - { - status = ePAR_ERROR_TABLE_ID; - } - } - else - { - status = ePAR_ERROR_NVM; - PAR_DBG_PRINT("PAR_NVM: table-ID read failed, %u", (unsigned)store_status); - } - - return status; -} -/** - * @brief Write unique parameter table ID to NVM. - * - * @param table_id Host-endian live table-ID value. - * @return Status of operation. - */ -static par_status_t par_nvm_write_table_id(const uint32_t table_id) -{ - par_status_t status = ePAR_OK; - const uint32_t stored_table_id = par_nvm_table_id_to_storage(table_id); - const par_status_t store_status = gp_store->write(PAR_NVM_HEAD_HASH_ADDR, - PAR_NVM_TABLE_ID_SIZE, - (const uint8_t *)&stored_table_id); - - if (ePAR_OK != store_status) - { - status = ePAR_ERROR_NVM; - PAR_DBG_PRINT("PAR_NVM: table-ID write failed, %u", (unsigned)store_status); - } - - return status; -} - -#endif /* 1 == PAR_CFG_TABLE_ID_CHECK_EN */ /** * @brief Read parameter NVM header. * @@ -281,18 +188,22 @@ static par_status_t par_nvm_write_table_id(const uint32_t table_id) static par_status_t par_nvm_read_header(par_nvm_head_obj_t * const p_head_obj) { par_status_t status = ePAR_OK; + uint8_t head_buf[PAR_NVM_HEAD_SIZE] = { 0U }; PAR_ASSERT(NULL != p_head_obj); + const par_status_t store_status = gp_store->read(PAR_NVM_HEAD_ADDR, PAR_NVM_HEAD_SIZE, head_buf); + if (ePAR_OK != store_status) { - const par_status_t store_status = gp_store->read(PAR_NVM_HEAD_ADDR, - (uint32_t)sizeof(par_nvm_head_obj_t), - (uint8_t *)p_head_obj); - if (ePAR_OK != store_status) - { - status = ePAR_ERROR_NVM; - PAR_DBG_PRINT("PAR_NVM: header read failed, %u", (unsigned)store_status); - } + status = ePAR_ERROR_NVM; + PAR_DBG_PRINT("PAR_NVM: header read failed, %u", (unsigned)store_status); + } + else + { + memcpy(&p_head_obj->sign, &head_buf[PAR_NVM_HEAD_SIGN_ADDR], sizeof(p_head_obj->sign)); + memcpy(&p_head_obj->obj_nb, &head_buf[PAR_NVM_HEAD_OBJ_NB_OFFSET], sizeof(p_head_obj->obj_nb)); + memcpy(&p_head_obj->table_id, &head_buf[PAR_NVM_HEAD_TABLE_ID_OFFSET], sizeof(p_head_obj->table_id)); + memcpy(&p_head_obj->crc, &head_buf[PAR_NVM_HEAD_CRC_OFFSET], sizeof(p_head_obj->crc)); } return status; @@ -300,6 +211,11 @@ static par_status_t par_nvm_read_header(par_nvm_head_obj_t * const p_head_obj) /** * @brief Write parameter NVM header. * + * @details The serialized header always stores the current table-ID digest + * in the native byte order of the running target. + * The compatibility comparison against the live digest still runs only when + * table-ID checking is enabled. + * * @param num_of_par Number of persistent parameters that are stored in NVM. * @return Status of operation. */ @@ -307,25 +223,19 @@ static par_status_t par_nvm_write_header(const uint16_t num_of_par) { par_status_t status = ePAR_OK; par_nvm_head_obj_t head_obj = { 0 }; - -#if (1 == PAR_CFG_TABLE_ID_CHECK_EN) - const uint32_t table_id = par_nvm_table_id_calc(); - - status = par_nvm_write_table_id(table_id); - if (ePAR_OK != status) - { - PAR_DBG_PRINT("PAR_NVM: table-ID write failed, %u", (unsigned)status); - return status; - } -#endif + uint8_t head_buf[PAR_NVM_HEAD_SIZE] = { 0U }; head_obj.obj_nb = num_of_par; - head_obj.crc = par_nvm_calc_crc((uint8_t *)&head_obj.obj_nb, sizeof(head_obj.obj_nb)); + head_obj.table_id = par_nvm_table_id_calc(); + head_obj.crc = par_nvm_calc_head_crc(&head_obj); head_obj.sign = PAR_NVM_SIGN; - const par_status_t store_status = gp_store->write(PAR_NVM_HEAD_ADDR, - (uint32_t)sizeof(par_nvm_head_obj_t), - (const uint8_t *)&head_obj); + memcpy(&head_buf[PAR_NVM_HEAD_SIGN_ADDR], &head_obj.sign, sizeof(head_obj.sign)); + memcpy(&head_buf[PAR_NVM_HEAD_OBJ_NB_OFFSET], &head_obj.obj_nb, sizeof(head_obj.obj_nb)); + memcpy(&head_buf[PAR_NVM_HEAD_TABLE_ID_OFFSET], &head_obj.table_id, sizeof(head_obj.table_id)); + memcpy(&head_buf[PAR_NVM_HEAD_CRC_OFFSET], &head_obj.crc, sizeof(head_obj.crc)); + + const par_status_t store_status = gp_store->write(PAR_NVM_HEAD_ADDR, PAR_NVM_HEAD_SIZE, head_buf); if (ePAR_OK != store_status) { status = ePAR_ERROR_NVM; @@ -340,26 +250,28 @@ static par_status_t par_nvm_write_header(const uint16_t num_of_par) /** * @brief Validate parameter NVM header. * - * @param p_num_of_par Pointer to number of persistent parameters that are stored in NVM. + * @details The header CRC covers both the stored persistent-object count and + * the stored table-ID digest bytes. This distinguishes header corruption from + * a valid-but-incompatible table-ID mismatch. + * + * @param p_head_obj Pointer to validated header structure. * @return Status of operation. */ -static par_status_t par_nvm_validate_header(uint16_t * const p_num_of_par) +static par_status_t par_nvm_validate_header(par_nvm_head_obj_t * const p_head_obj) { par_status_t status = ePAR_OK; - par_nvm_head_obj_t obj_head = { 0 }; - uint16_t crc_calc = 0; - status = par_nvm_read_header(&obj_head); + uint16_t crc_calc = 0U; + + status = par_nvm_read_header(p_head_obj); if (ePAR_ERROR_NVM != status) { - if (PAR_NVM_SIGN == obj_head.sign) + if (PAR_NVM_SIGN == p_head_obj->sign) { - crc_calc = par_nvm_calc_crc((uint8_t *)&obj_head.obj_nb, sizeof(obj_head.obj_nb)); - if (crc_calc == obj_head.crc) + crc_calc = par_nvm_calc_head_crc(p_head_obj); + if (crc_calc == p_head_obj->crc) { - *p_num_of_par = obj_head.obj_nb; - PAR_DBG_PRINT("PAR_NVM: HVM header OK! Nb. of stored obj: %d", obj_head.obj_nb); + PAR_DBG_PRINT("PAR_NVM: NVM header OK! Nb. of stored obj: %d", p_head_obj->obj_nb); } - else { status = ePAR_ERROR_CRC; @@ -376,56 +288,51 @@ static par_status_t par_nvm_validate_header(uint16_t * const p_num_of_par) return status; } /** - * @brief Calculate CRC-16. + * @brief Calculate serialized-header CRC-16. * - * @param p_data Pointer to data. - * @param size Size of data to calc crc. - * @return Calculated CRC. + * @details The header CRC is accumulated field-by-field over the serialized + * native-order bytes of obj_nb and table_id. This avoids hashing compiler + * padding bytes inside par_nvm_head_obj_t while intentionally keeping the + * persisted format and table-ID comparison tied to the current target + * architecture. + * + * @param p_head_obj Pointer to header object. + * @return Calculated CRC-16 value. */ -static uint16_t par_nvm_calc_crc(const uint8_t * const p_data, const uint8_t size) +static uint16_t par_nvm_calc_head_crc(const par_nvm_head_obj_t * const p_head_obj) { - const uint16_t poly = 0x1021U; // CRC-16-CCITT - const uint16_t seed = 0x1234U; // Custom seed - uint16_t crc16 = seed; - PAR_ASSERT(NULL != p_data); - PAR_ASSERT(size > 0); + uint16_t crc = PAR_IF_CRC16_INIT; - for (uint8_t i = 0; i < size; i++) - { - crc16 = (crc16 ^ (p_data[i] << 8U)); + PAR_ASSERT(NULL != p_head_obj); - for (uint8_t j = 0U; j < 8U; j++) - { - if (crc16 & 0x8000) - { - crc16 = ((crc16 << 1U) ^ poly); - } - else - { - crc16 = (crc16 << 1U); - } - } - } + crc = par_if_crc16_accumulate(crc, (const uint8_t * const)&p_head_obj->obj_nb, (uint32_t)sizeof(p_head_obj->obj_nb)); + crc = par_if_crc16_accumulate(crc, (const uint8_t * const)&p_head_obj->table_id, (uint32_t)sizeof(p_head_obj->table_id)); - return crc16; + return crc; } /** - * @brief Calculate parameter data object CRC. + * @brief Calculate per-record CRC-8 over serialized id/size/data bytes. + * + * @details The record CRC is accumulated field-by-field over the serialized + * native-order bytes of id, size, and the fixed 4-byte payload slot. * * @param p_obj Pointer to data object. - * @return Calculated CRC. + * @return Calculated CRC-8 value. */ static uint8_t par_nvm_calc_obj_crc(const par_nvm_data_obj_t * const p_obj) { - uint16_t crc = 0; - uint8_t rtn_crc = 0; + uint32_t data_raw = 0U; + uint8_t crc = PAR_IF_CRC8_INIT; - crc = par_nvm_calc_crc((const uint8_t *)&p_obj->id, 2U); - crc ^= par_nvm_calc_crc((const uint8_t *)&p_obj->size, 1U); - crc ^= par_nvm_calc_crc((const uint8_t *)&p_obj->data.u8, 4U); - rtn_crc = (crc & 0xFFU); + PAR_ASSERT(NULL != p_obj); - return rtn_crc; + memcpy(&data_raw, &p_obj->data, sizeof(data_raw)); + + crc = par_if_crc8_accumulate(crc, (const uint8_t * const)&p_obj->id, (uint32_t)sizeof(p_obj->id)); + crc = par_if_crc8_accumulate(crc, (const uint8_t * const)&p_obj->size, (uint32_t)sizeof(p_obj->size)); + crc = par_if_crc8_accumulate(crc, (const uint8_t * const)&data_raw, (uint32_t)sizeof(data_raw)); + + return crc; } /** * @brief Load all parameters value from NVM. @@ -446,8 +353,8 @@ static par_status_t par_nvm_load_all(const uint16_t num_of_par) uint16_t new_par_cnt = 0; for (i = 0; i < num_of_par; i++) { - /* Each NVM object currently occupies 8 bytes. */ - obj_addr = ((8 * i) + PAR_NVM_FIRST_DATA_OBJ_ADDR); + /* NVM persistence is a dense linear array of fixed 8-byte objects. */ + obj_addr = ((PAR_NVM_DATA_OBJ_STRIDE * i) + PAR_NVM_FIRST_DATA_OBJ_ADDR); store_status = gp_store->read(obj_addr, (uint32_t)sizeof(par_nvm_data_obj_t), (uint8_t *)&obj_data); if (ePAR_OK == store_status) { @@ -465,7 +372,7 @@ static par_status_t par_nvm_load_all(const uint16_t num_of_par) g_par_nvm_data_obj_addr[per_par_nb].addr = obj_addr; g_par_nvm_data_obj_addr[per_par_nb].valid = true; /* Restore through the internal fast path. */ - (void)par_nvm_restore_fast(par_num, &obj_data.data); + (void)par_set_fast(par_num, &obj_data.data); per_par_nb++; } } @@ -501,7 +408,7 @@ static par_status_t par_nvm_load_all(const uint16_t num_of_par) { /* Extend the LUT for newly added persistent parameters. */ g_par_nvm_data_obj_addr[per_par_nb].id = par_cfg->id; - g_par_nvm_data_obj_addr[per_par_nb].addr = obj_addr + (8U * (new_par_cnt + 1U)); + g_par_nvm_data_obj_addr[per_par_nb].addr = obj_addr + (PAR_NVM_DATA_OBJ_STRIDE * (new_par_cnt + 1U)); g_par_nvm_data_obj_addr[per_par_nb].valid = true; par_save(i); @@ -534,8 +441,12 @@ static par_status_t par_nvm_load_all(const uint16_t num_of_par) return status; } /** - * @brief Build new parameter NVM LUT table. - + * @brief Build a fresh NVM lookup table from the live persistent set. + * + * @details The LUT maps external parameter IDs to their fixed-record address in + * the linear persisted-object array. This keeps load/save paths anchored on ID + * instead of assuming that a given parameter meaning is tied permanently to one + * absolute slot position. */ static void par_nvm_build_new_nvm_lut(void) { @@ -551,10 +462,10 @@ static void par_nvm_build_new_nvm_lut(void) g_par_nvm_data_obj_addr[per_par_nb].addr = PAR_NVM_FIRST_DATA_OBJ_ADDR; } - /* NVM data objects currently use a fixed 8-byte stride. */ + /* NVM persistence uses a linear list of fixed-size 8-byte objects. */ else { - g_par_nvm_data_obj_addr[per_par_nb].addr = (g_par_nvm_data_obj_addr[per_par_nb - 1].addr + 8U); + g_par_nvm_data_obj_addr[per_par_nb].addr = (g_par_nvm_data_obj_addr[per_par_nb - 1].addr + PAR_NVM_DATA_OBJ_STRIDE); } g_par_nvm_data_obj_addr[per_par_nb].id = par_cfg->id; @@ -666,6 +577,8 @@ static par_status_t par_nvm_init_nvm(void) * The recovery flow is centralized and cumulative: * - header validation runs first; * - table-ID validation runs only when the header is valid; + * - header CRC validation covers both the stored object count and the stored + * table-ID digest bytes; * - payload loading runs only when both checks pass; * - then the collected error bits decide whether startup should restore live * RAM values to defaults only, or restore defaults and also rebuild the @@ -680,7 +593,7 @@ par_status_t par_nvm_init(void) { par_status_t status = ePAR_OK; par_status_t detect_status = ePAR_OK; - uint16_t obj_nb = 0U; + par_nvm_head_obj_t head_obj = { 0 }; uint16_t per_par_nb = 0U; bool need_set_default = false; bool need_rewrite_nvm = false; @@ -700,21 +613,24 @@ par_status_t par_nvm_init(void) } /* Step 1: validate header */ - detect_status = par_nvm_validate_header(&obj_nb); + detect_status = par_nvm_validate_header(&head_obj); #if (1 == PAR_CFG_TABLE_ID_CHECK_EN) /* Step 2: validate table-ID only when header is valid */ if (ePAR_OK == (detect_status & ePAR_STATUS_ERROR_MASK)) { const uint32_t live_table_id = par_nvm_table_id_calc(); - detect_status |= par_nvm_check_table_id(live_table_id); + if (head_obj.table_id != live_table_id) + { + detect_status |= ePAR_ERROR_TABLE_ID; + } } #endif /* Step 3: load payload only when previous checks are valid */ if (ePAR_OK == (detect_status & ePAR_STATUS_ERROR_MASK)) { - detect_status |= par_nvm_load_all(obj_nb); + detect_status |= par_nvm_load_all(head_obj.obj_nb); } /* Step 4: classify recovery action from detected issues */ @@ -851,8 +767,8 @@ par_status_t par_nvm_write(const par_num_t par_num, const bool nvm_sync) par_get(par_num, (uint32_t *)&obj_data.data); obj_data.id = par_cfg->id; - /* Current implementation stores fixed-size 8-byte objects. */ - obj_data.size = 4U; + /* size is a descriptor/check field; current fixed-slot format always stores 4. */ + obj_data.size = PAR_NVM_DATA_SLOT_SIZE; obj_data.crc = par_nvm_calc_obj_crc(&obj_data); par_addr = par_nvm_get_nvm_lut_addr(obj_data.id); store_status = gp_store->write(par_addr, @@ -912,7 +828,7 @@ par_status_t par_nvm_write_all(void) { /* Mark the header invalid before bulk rewrite and commit that state. */ { - const par_status_t store_status = gp_store->erase(PAR_NVM_HEAD_SIGN_ADDR, (uint32_t)sizeof(uint32_t)); + const par_status_t store_status = gp_store->erase(PAR_NVM_HEAD_SIGN_ADDR, PAR_NVM_HEAD_SIGN_SIZE); if (ePAR_OK != store_status) { status |= ePAR_ERROR_NVM; diff --git a/src/persist/par_nvm_table_id.c b/src/persist/par_nvm_table_id.c index f0cee3c..efcb76f 100644 --- a/src/persist/par_nvm_table_id.c +++ b/src/persist/par_nvm_table_id.c @@ -13,8 +13,6 @@ * 2026-03-30 1.0 wdfk-prog first version */ -#include - #include "par.h" #include "persist/fnv.h" #include "persist/par_nvm_table_id.h" @@ -31,93 +29,30 @@ enum }; /** - * @brief Convert 16-bit value from host endianness to little-endian storage order. - */ -static uint16_t par_nvm_table_id_to_le16(const uint16_t value) -{ -#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) -#if defined(__GNUC__) || defined(__clang__) - return __builtin_bswap16(value); -#else - return (uint16_t)(((value & 0x00FFU) << 8U) | ((value & 0xFF00U) >> 8U)); -#endif -#else - return value; -#endif -} - -/** - * @brief Convert 32-bit value from host endianness to little-endian storage order. - */ -static uint32_t par_nvm_table_id_to_le32(const uint32_t value) -{ -#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) -#if defined(__GNUC__) || defined(__clang__) - return __builtin_bswap32(value); -#else - return (((value & 0x000000FFUL) << 24U) | - ((value & 0x0000FF00UL) << 8U) | - ((value & 0x00FF0000UL) >> 8U) | - ((value & 0xFF000000UL) >> 24U)); -#endif -#else - return value; -#endif -} - -/** - * @brief Update FNV-1a context with one little-endian serialized scalar. + * @brief Update FNV-1a context with one platform-native scalar image. * * @param p_hval Pointer to rolling FNV-1a state. * @param p_serialized_size Pointer to serialized byte counter. * @param p_value Pointer to source scalar. * @param value_size Scalar width in bytes. Supported values: 1, 2, 4. */ -static void par_nvm_table_id_hash_update_le(Fnv32_t * const p_hval, - uint32_t * const p_serialized_size, - const void * const p_value, - const uint32_t value_size) +static void par_nvm_table_id_hash_update(Fnv32_t * const p_hval, + uint32_t * const p_serialized_size, + const void * const p_value, + const uint32_t value_size) { - uint8_t serialized[sizeof(uint32_t)] = { 0U }; - - PAR_ASSERT(NULL != p_hval); - PAR_ASSERT(NULL != p_serialized_size); - PAR_ASSERT(NULL != p_value); - PAR_ASSERT((1U == value_size) || (2U == value_size) || (4U == value_size)); - - if (1U == value_size) - { - serialized[0] = *(const uint8_t *)p_value; - } - else if (2U == value_size) - { - const uint16_t value_le = par_nvm_table_id_to_le16(*(const uint16_t *)p_value); - memcpy(serialized, &value_le, sizeof(value_le)); - } - else - { - const uint32_t value_le = par_nvm_table_id_to_le32(*(const uint32_t *)p_value); - memcpy(serialized, &value_le, sizeof(value_le)); - } - - *p_hval = fnv_32a_buf(serialized, (size_t)value_size, *p_hval); + *p_hval = fnv_32a_buf((void *)p_value, (size_t)value_size, *p_hval); *p_serialized_size += value_size; } -/** - * @brief Convert host-endian digest to stored little-endian representation. - */ -uint32_t par_nvm_table_id_to_storage(const uint32_t table_id) -{ - return par_nvm_table_id_to_le32(table_id); -} - /** * @brief Calculate live parameter-table ID. * * @details The digest covers only metadata that affects the NVM storage * compatibility of persisted parameters: schema version, persisted-parameter - * count, persisted-parameter order, type, and optional ID field. + * count, persisted-parameter order, type, and optional ID field. Under the + * single-target native-endian profile, each scalar is hashed exactly as it is + * represented in memory on the running platform. */ uint32_t par_nvm_table_id_calc(void) { @@ -127,8 +62,8 @@ uint32_t par_nvm_table_id_calc(void) const uint16_t persistent_count = (uint16_t)PAR_PERSISTENT_COMPILE_COUNT; const uint32_t expected_size = (uint32_t)sizeof(schema_version) + (uint32_t)sizeof(persistent_count) + ((uint32_t)persistent_count * (uint32_t)PAR_NVM_TABLE_ID_REC_SIZE); - par_nvm_table_id_hash_update_le(&hval, &serialized_size, &schema_version, (uint32_t)sizeof(schema_version)); - par_nvm_table_id_hash_update_le(&hval, &serialized_size, &persistent_count, (uint32_t)sizeof(persistent_count)); + par_nvm_table_id_hash_update(&hval, &serialized_size, &schema_version, (uint32_t)sizeof(schema_version)); + par_nvm_table_id_hash_update(&hval, &serialized_size, &persistent_count, (uint32_t)sizeof(persistent_count)); for (par_num_t par_num = 0U; par_num < ePAR_NUM_OF; par_num++) { @@ -142,9 +77,9 @@ uint32_t par_nvm_table_id_calc(void) } #endif - par_nvm_table_id_hash_update_le(&hval, &serialized_size, &type, (uint32_t)sizeof(type)); + par_nvm_table_id_hash_update(&hval, &serialized_size, &type, (uint32_t)sizeof(type)); #if (1 == PAR_CFG_ENABLE_ID) - par_nvm_table_id_hash_update_le(&hval, &serialized_size, &p_cfg->id, (uint32_t)sizeof(p_cfg->id)); + par_nvm_table_id_hash_update(&hval, &serialized_size, &p_cfg->id, (uint32_t)sizeof(p_cfg->id)); #endif } diff --git a/src/persist/par_nvm_table_id.h b/src/persist/par_nvm_table_id.h index 509ec75..27a5dea 100644 --- a/src/persist/par_nvm_table_id.h +++ b/src/persist/par_nvm_table_id.h @@ -16,16 +16,6 @@ #define _PAR_NVM_TABLE_ID_H_ #include - -/** - * @brief Persisted table-ID width in bytes. - * - * @note The package fixes the table-ID algorithm to 32-bit FNV-1a whenever - * PAR_CFG_TABLE_ID_CHECK_EN is enabled, so the stored digest width is exactly - * one 32-bit word. - */ -#define PAR_NVM_TABLE_ID_SIZE ((uint32_t)sizeof(uint32_t)) - /** * @brief Calculate the live parameter-table ID. * @@ -41,16 +31,8 @@ * intentionally excluded because they do not change the serialized NVM object * layout used by par_nvm.c. * - * @return Host-endian 32-bit FNV-1a digest. + * @return Platform-native 32-bit FNV-1a digest. */ uint32_t par_nvm_table_id_calc(void); -/** - * @brief Convert a host-endian table-ID value into stored little-endian form. - * - * @param table_id Host-endian table-ID digest. - * @return 32-bit value encoded in the on-storage little-endian byte order. - */ -uint32_t par_nvm_table_id_to_storage(const uint32_t table_id); - #endif /* _PAR_NVM_TABLE_ID_H_ */ diff --git a/src/port/par_if.c b/src/port/par_if.c index 03fb958..b0c16b6 100644 --- a/src/port/par_if.c +++ b/src/port/par_if.c @@ -78,6 +78,86 @@ PAR_PORT_WEAK void par_if_release_mutex(const par_num_t par_num) { (void)par_num; } +/** + * @brief Accumulate CRC-16/CCITT-FALSE over serialized header bytes. + * + * @details Default weak implementation used when the integrator does not + * provide a platform-specific override. + * + * @param crc Current CRC-16 accumulator value. + * @param p_data Pointer to serialized bytes. + * @param size Number of bytes to process. + * @return Updated CRC-16 value. + */ +PAR_PORT_WEAK uint16_t par_if_crc16_accumulate(uint16_t crc, const uint8_t * const p_data, const uint32_t size) +{ + const uint16_t poly = 0x1021U; + + if (0U == size) + { + return crc; + } + + PAR_ASSERT(NULL != p_data); + + for (uint32_t i = 0U; i < size; ++i) + { + crc ^= (uint16_t)((uint16_t)p_data[i] << 8U); + for (uint8_t bit = 0U; bit < 8U; ++bit) + { + if (0U != (crc & 0x8000U)) + { + crc = (uint16_t)((crc << 1U) ^ poly); + } + else + { + crc = (uint16_t)(crc << 1U); + } + } + } + + return crc; +} +/** + * @brief Accumulate CRC-8 over serialized record bytes. + * + * @details Default weak implementation used when the integrator does not + * provide a platform-specific override. + * + * @param crc Current CRC-8 accumulator value. + * @param p_data Pointer to serialized bytes. + * @param size Number of bytes to process. + * @return Updated CRC-8 value. + */ +PAR_PORT_WEAK uint8_t par_if_crc8_accumulate(uint8_t crc, const uint8_t * const p_data, const uint32_t size) +{ + const uint8_t poly = 0x07U; + + if (0U == size) + { + return crc; + } + + PAR_ASSERT(NULL != p_data); + + for (uint32_t i = 0U; i < size; ++i) + { + crc ^= p_data[i]; + for (uint8_t bit = 0U; bit < 8U; ++bit) + { + if (0U != (crc & 0x80U)) + { + crc = (uint8_t)((crc << 1U) ^ poly); + } + else + { + crc = (uint8_t)(crc << 1U); + } + } + } + + return crc; +} #else diff --git a/src/port/par_if.h b/src/port/par_if.h index 45b9632..117a535 100644 --- a/src/port/par_if.h +++ b/src/port/par_if.h @@ -28,6 +28,15 @@ /** * @brief Compile-time definitions. */ +/** + * @brief Initial value for CRC-16/CCITT-FALSE accumulation. + */ +#define PAR_IF_CRC16_INIT ((uint16_t)0xFFFFU) + +/** + * @brief Initial value for CRC-8 accumulation. + */ +#define PAR_IF_CRC8_INIT ((uint8_t)0x00U) /** * @brief Function declarations. */ @@ -52,5 +61,31 @@ par_status_t par_if_aquire_mutex(const par_num_t par_num); * @param par_num Parameter number. */ void par_if_release_mutex(const par_num_t par_num); +/** + * @brief Accumulate CRC-16 for serialized NVM header content. + * + * @details The portable core provides a weak software default in + * src/port/par_if.c. The integrator may replace it with a strong platform + * implementation, for example one backed by a hardware CRC engine. + * + * @param crc Current CRC-16 accumulator value. + * @param p_data Pointer to serialized bytes. + * @param size Number of bytes to process. + * @return Updated CRC-16 value. + */ +uint16_t par_if_crc16_accumulate(uint16_t crc, const uint8_t * const p_data, const uint32_t size); +/** + * @brief Accumulate CRC-8 for one serialized NVM data record. + * + * @details The portable core provides a weak software default in + * src/port/par_if.c. The integrator may replace it with a strong platform + * implementation, for example one backed by a hardware CRC engine. + * + * @param crc Current CRC-8 accumulator value. + * @param p_data Pointer to serialized bytes. + * @param size Number of bytes to process. + * @return Updated CRC-8 value. + */ +uint8_t par_if_crc8_accumulate(uint8_t crc, const uint8_t * const p_data, const uint32_t size); #endif /* _PAR_IF_H_ */ From c9d1414d38219dd0babc62fb0db001f9549f9f06 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Thu, 2 Apr 2026 11:56:22 +0800 Subject: [PATCH 29/36] fix(core): Stabilize persistent slot mapping for parameter NVM --- README.md | 3 +- docs/api-reference.md | 4 +- docs/architecture.md | 6 +- docs/getting-started.md | 2 +- src/def/par_def.c | 45 +++- src/def/par_def.h | 41 ++- src/par.c | 32 +-- src/par.h | 3 +- src/par_cfg.h | 23 +- src/persist/par_nvm.c | 569 +++++++++++++++++++++++++--------------- 10 files changed, 462 insertions(+), 266 deletions(-) diff --git a/README.md b/README.md index 2fdc69a..12ceb16 100644 --- a/README.md +++ b/README.md @@ -139,10 +139,11 @@ This repository contains the reusable module core and templates. A real integrat - Unchecked setter APIs skip runtime validation callbacks and on-change callbacks, so they should be reserved for tightly controlled hot paths. Bitwise fast setters are further restricted to `U8` / `U16` / `U32` flags or bitmask parameters. Legacy `*_fast()` names remain as deprecated aliases. - NVM support is optional. When enabled, `src/persist/par_nvm.c` depends on a mounted storage backend interface and on ID and persistence metadata being enabled. The package can build the `GeneralEmbeddedCLibraries/nvm` adapter from `src/persist/backend/`, or the application can provide its own `par_store_backend_get_api()` implementation. - Live RAM layout and persisted NVM layout are intentionally different. RAM storage is grouped by value width, while the persistence area is a linear list of fixed 8-byte objects: `id(2) + size(1) + crc8(1) + data(4)`. -- The persisted `id` is the primary self-description field, so startup and rewrite logic do not rely only on hard-coded slot meaning. The current `size` field is still always written as `4`, so it is mainly a descriptor/integrity helper rather than a space-saving variable-width encoding. +- Compile-time persistent order is the primary slot layout contract of the managed NVM image. The stored `id` remains in each record as an integrity and diagnostics field, and the current `size` field is still always written as `4`, so it is mainly a descriptor/integrity helper rather than a space-saving variable-width encoding. - The serialized NVM header is written explicitly as a fixed 12-byte storage image (`sign(4) + obj_nb(2) + table_id(4) + crc16(2)`), so on-storage layout does not depend on compiler struct padding. Header CRC-16 covers the serialized `obj_nb + table_id` bytes, while each data record carries its own CRC-8 over `id + size + data`. - CRC calculation is routed through port hooks with bundled software defaults. In this single-target profile the persisted image and the table-ID digest both use the native byte order of the running platform, so no additional byte-order conversion hook is required by the persistence path. - When `PAR_CFG_TABLE_ID_CHECK_EN = 1`, startup compares the stored table-ID against the live table-ID to validate persisted-image compatibility. The table-ID covers `PAR_CFG_TABLE_ID_SCHEMA_VER`, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID, hashed as platform-native scalar bytes under the single-target profile. It intentionally does not cover defaults, ranges, names, units, descriptions, or access flags. +- A table-ID mismatch is treated as an incompatible persisted-layout change, not as a warning-only condition. Startup restores defaults and rebuilds the managed NVM image. Typical triggers are add/remove/reorder/type/ID changes of persistent parameters and transitions between persistent and non-persistent state. A stored header count smaller than the compile-time persistent count is repaired by appending the missing tail slots from current defaults and rewriting the header count; a stored count larger than the compile-time count is treated as incompatible and rebuilt. - `PAR_CFG_TABLE_ID_SCHEMA_VER` defaults in `src/par_cfg.h` and may be overridden in `port/par_cfg_port.h`; the integrator should bump it when intentionally changing the serialized table-ID schema. - `par_init()` applies startup default values directly to live storage. Integer default values from `par_table.def` are compiled into a grouped width-based storage object, while `F32` default values are applied to the 32-bit storage group after layout offsets are available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. Because this startup initialization does not go through the public setter path, it does not invoke runtime validation or on-change callbacks. - `PAR_CFG_ENABLE_RESET_ALL_RAW` controls whether raw reset-all support and grouped default mirror snapshot support are enabled. diff --git a/docs/api-reference.md b/docs/api-reference.md index 0970474..63488dc 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -182,9 +182,9 @@ These APIs do not follow the same runtime usage pattern as the value access APIs Available only when `PAR_CFG_NVM_EN = 1` and a concrete parameter-storage backend is linked. -The managed NVM payload area is a linear list of fixed 8-byte objects, not a width-group partition like the live RAM layout. Each object stores `id(2) + size(1) + crc8(1) + data(4)`. The `id` is the main self-description field used to match persisted records back to live parameters. The current implementation still writes `size = 4` for all persisted parameters, so that field is primarily descriptive and used in the integrity model rather than for NVM compaction. +The managed NVM payload area is a linear list of fixed 8-byte objects, not a width-group partition like the live RAM layout. Each object stores `id(2) + size(1) + crc8(1) + data(4)`. Slot order is derived directly from the compile-time persistent order in `par_table.def`, so the loader restores slot `i` to compile-time persistent slot `i`. If the stored count is larger than the current compile-time persistent count, startup treats the image as incompatible and the managed NVM area is rebuilt from live defaults. If the stored count is smaller, the stored prefix is restored and the missing tail slots are appended from current defaults before the header count is rewritten. The stored `id` is still kept inside each object as a validation and diagnostics field, but it is no longer the primary lookup key during startup repair. The current implementation still writes `size = 4` for all persisted parameters, so that field is primarily descriptive and used in the integrity model rather than for NVM compaction. -The serialized NVM header is emitted explicitly as a fixed 12-byte image (`sign + obj_nb + table_id + crc16`) rather than by writing raw struct memory. Header CRC-16 covers the serialized native-order `obj_nb + table_id` bytes, so corrupted header bytes fail header validation before any compatibility decision is made. Each small data record uses CRC-8 over its serialized native-order `id + size + data` bytes. When `PAR_CFG_TABLE_ID_CHECK_EN = 1`, `par_nvm_init()` compares the stored table-ID directly against the live table digest. The table-ID covers `PAR_CFG_TABLE_ID_SCHEMA_VER`, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID for parameters that are actually persisted, hashed as native platform scalar bytes. Startup first validates header CRC/signature, then validates the table-ID, then loads payload objects only if both checks pass. The collected error bits then drive a centralized recovery flow: NVM access errors restore live RAM values to defaults, while table-ID/header/CRC mismatches restore defaults and rebuild the managed NVM image. CRC calculation is exposed through port hooks with bundled software defaults. +The serialized NVM header is emitted explicitly as a fixed 12-byte image (`sign + obj_nb + table_id + crc16`) rather than by writing raw struct memory. Header CRC-16 covers the serialized native-order `obj_nb + table_id` bytes, so corrupted header bytes fail header validation before any compatibility decision is made. Each small data record uses CRC-8 over its serialized native-order `id + size + data` bytes. When `PAR_CFG_TABLE_ID_CHECK_EN = 1`, `par_nvm_init()` compares the stored table-ID directly against the live table digest. The table-ID covers `PAR_CFG_TABLE_ID_SCHEMA_VER`, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID for parameters that are actually persisted, hashed as native platform scalar bytes. Startup first validates header CRC/signature, then validates the table-ID, then loads payload objects only if both checks pass. If the stored count is smaller than the compile-time persistent count, the loader restores the stored prefix, appends the missing tail slots from live defaults, and rewrites the header count. If the stored count is larger than the compile-time persistent count, the image is treated as incompatible and rebuilt. The collected error bits then drive a centralized recovery flow: NVM access errors restore live RAM values to defaults, while table-ID/header/CRC mismatches restore defaults and rebuild the managed NVM image. This compatibility boundary explicitly covers add/remove/reorder/type/ID changes of persistent parameters and transitions between persistent and non-persistent state. CRC calculation is exposed through port hooks with bundled software defaults. | Function | Description | | --- | --- | diff --git a/docs/architecture.md b/docs/architecture.md index 73f60fa..121ecad 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -322,11 +322,11 @@ When `PAR_CFG_NVM_EN = 1`, the module can persist selected parameters to NVM. NVM persistence uses a fixed-size record format that is intentionally different from the live RAM layout. Live RAM storage is grouped by value width (`U8` / `U16` / `U32` / optional `F32`) for efficient access and raw reset, while the managed NVM area is a dense linear list of fixed 8-byte objects. Each persisted object stores: external parameter ID (2 bytes), payload-size descriptor (1 byte), per-record CRC-8 (1 byte), and one fixed 4-byte payload slot. -The external ID is the main self-description field of the persisted image. Loader and rewrite paths use it to match records to live parameters, so the persistence model is less dependent on one parameter meaning being bound forever to one absolute slot position. The `size` field is currently written as `4` for all persisted parameters. In other words, the current implementation does not save NVM space for `U8`/`U16`; `size` is kept mainly as a descriptor and integrity-assistance field for the fixed-slot format. +The compile-time persistent order is the primary layout contract of the managed NVM image. Loader and rewrite paths restore slot `i` into compile-time persistent slot `i`, while the stored `id` remains inside each object as an integrity and diagnostics field rather than as the main startup lookup key. Count asymmetry is handled intentionally: a stored count larger than the live compile-time persistent count is treated as an incompatible image and triggers full rebuild, while a stored count smaller than the live count restores the common prefix first and then appends newly introduced tail slots from current defaults. The `size` field is currently written as `4` for all persisted parameters. In other words, the current implementation does not save NVM space for `U8`/`U16`; `size` is kept mainly as a descriptor and integrity-assistance field for the fixed-slot format. -The serialized NVM header is a fixed 12-byte storage image (`sign + obj_nb + table_id + crc16`) emitted explicitly instead of by writing raw struct memory. The header CRC covers the serialized native-order `obj_nb + table_id` bytes, so header corruption is detected separately from a valid header that simply belongs to another parameter-table schema. Each data object uses CRC-8 over its serialized native-order `id + size + data` bytes. This profile intentionally ties the persisted image to the current target architecture and does not normalize byte order for cross-platform migration. CRC calculation is routed through port hooks with bundled software defaults. The table-ID digest follows the same rule and hashes each scalar exactly as represented in native platform memory. The digest covers only metadata that changes binary compatibility of persisted records: `PAR_CFG_TABLE_ID_SCHEMA_VER`, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID. Default values, ranges, names, units, descriptions, and access flags are intentionally excluded because they do not change the serialized NVM object layout used by `par_nvm.c`. +The serialized NVM header is a fixed 12-byte storage image (`sign + obj_nb + table_id + crc16`) emitted explicitly instead of by writing raw struct memory. The header CRC covers the serialized native-order `obj_nb + table_id` bytes, so header corruption is detected separately from a valid header that simply belongs to another parameter-table schema. Each data object uses CRC-8 over its serialized native-order `id + size + data` bytes. This profile intentionally ties the persisted image to the current target architecture and does not normalize byte order for cross-platform migration. CRC calculation is routed through port hooks with bundled software defaults. The table-ID digest follows the same rule and hashes each scalar exactly as represented in native platform memory. The digest covers only metadata that changes binary compatibility of persisted records: `PAR_CFG_TABLE_ID_SCHEMA_VER`, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID. Default values, ranges, names, units, descriptions, and access flags are intentionally excluded because they do not change the serialized NVM object layout used by `par_nvm.c`. When table-ID checking is enabled, changes such as add/remove/reorder/type/ID updates of persistent parameters and persistent<->non-persistent transitions are treated as incompatible schema changes and the managed NVM image is rebuilt from defaults at startup. -If the stored table-ID mismatches the live table-ID, the module treats the persisted image as incompatible and rebuilds the managed NVM area from current default values and the current schema. This can happen after an intentional schema/version bump, after parameter persistence layout changes, or after stored-image corruption. The recovery action is centralized in `par_nvm_init()`: it accumulates status bits from header validation, table-ID validation, and payload loading, then decides whether to restore defaults only or restore defaults plus rewrite the managed NVM image. +If the stored table-ID mismatches the live table-ID, the module treats the persisted image as incompatible and rebuilds the managed NVM area from current default values and the current schema. This can happen after an intentional schema/version bump, after parameter persistence layout changes, after stored-image corruption, or when the stored header count is larger than the current compile-time persistent count. If the stored count is smaller than the compile-time count, startup restores the stored prefix, appends the missing tail slots from live defaults, and rewrites the header count to the current schema width. The recovery action is centralized in `par_nvm_init()`: it accumulates status bits from header validation, table-ID validation, and payload loading, then decides whether to restore defaults only or restore defaults plus rewrite the managed NVM image. For this feature, `par_nvm.c` mounts a parameter-storage backend interface during initialization. The packaged `GeneralEmbeddedCLibraries/nvm` adapter is one option, but the core no longer depends directly on a specific NVM repository layout. diff --git a/docs/getting-started.md b/docs/getting-started.md index 6eb7519..dad5b43 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -145,7 +145,7 @@ ID-based lookup is generated statically when `PAR_CFG_ENABLE_ID = 1`. Optional s When NVM is enabled, the parameters module requires a concrete storage backend implementation. `src/persist/par_nvm.c` resolves and validates the backend API once during initialization, then uses the mounted callbacks directly for later reads, writes, erases, and sync operations. The package can build the `GeneralEmbeddedCLibraries/nvm` adapter from `parameters/src/persist/backend/`, or the application can provide `par_store_backend_get_api()` itself. The module can reuse an already-initialized backend or initialize it on demand and later deinitialize it only when it owns that initialization. Module deinit is conservative: it attempts backend and interface cleanup, and it clears the top-level module init state only after the owned child deinit steps succeed. -The persisted NVM image uses fixed 8-byte objects (`id + size + crc8 + 4-byte data slot`) instead of the grouped-width RAM layout used at runtime. The persisted `id` keeps records self-describing across load/rewrite paths. The current implementation still writes `size = 4` for every persisted parameter, so narrow values do not save NVM bytes yet. The header is serialized explicitly as a fixed 12-byte image (`sign + obj_nb + table_id + crc16`). Under the current single-target assumption, fields are stored, validated, and hashed in the native byte order of the running platform instead of being normalized to a cross-platform storage endianness. Header CRC-16 therefore covers the serialized native-order `obj_nb + table_id` bytes, each data object uses CRC-8 over native-order `id + size + data`, and the table-ID digest is computed from native-order scalar images as well. The bundled defaults are provided through `par_if_crc16_accumulate()` and `par_if_crc8_accumulate()`. +The persisted NVM image uses fixed 8-byte objects (`id + size + crc8 + 4-byte data slot`) instead of the grouped-width RAM layout used at runtime. Compile-time persistent order is the primary slot layout contract for startup restore, while the stored `id` remains a validation and diagnostics field inside each record. The current implementation still writes `size = 4` for every persisted parameter, so narrow values do not save NVM bytes yet. The header is serialized explicitly as a fixed 12-byte image (`sign + obj_nb + table_id + crc16`). Under the current single-target assumption, fields are stored, validated, and hashed in the native byte order of the running platform instead of being normalized to a cross-platform storage endianness. Header CRC-16 therefore covers the serialized native-order `obj_nb + table_id` bytes, each data object uses CRC-8 over native-order `id + size + data`, and the table-ID digest is computed from native-order scalar images as well. When the stored count is smaller than the compile-time persistent count, startup appends the missing tail slots from current defaults and rewrites the header count; when the stored count is larger, startup treats the image as incompatible and rebuilds it. The bundled defaults are provided through `par_if_crc16_accumulate()` and `par_if_crc8_accumulate()`. Backend choices: diff --git a/src/def/par_def.c b/src/def/par_def.c index 0ed8c97..b55b141 100644 --- a/src/def/par_def.c +++ b/src/def/par_def.c @@ -251,9 +251,30 @@ PAR_STATIC_ASSERT(par_compile_check_hash_bucket_collision_ref, (sizeof(&par_comp #endif #if (1 == PAR_CFG_ENABLE_PERSIST) -#define PAR_INIT_PERSIST(pers_) .persistent = (pers_), +/** + * @brief Translate the X-Macro persistence column into a stored persist slot index. + * + * @details The pers_ argument in par_table.def is written as true/false. Because + * expands those tokens to 1/0, the two-step helper first lets pers_ + * expand normally, then token-pastes the result into either: + * - PAR_PERSIST_IDX_VALUE_1(enum_) for persistent entries + * - PAR_PERSIST_IDX_VALUE_0(enum_) for non-persistent entries + * + * The _1 branch returns the dense compile-time slot constant + * PAR_PERSIST_IDX_. + * The _0 branch returns PAR_PERSIST_IDX_INVALID, because non-persistent + * parameters do not own any slot in the managed NVM image. + * + * Two macro layers are required here because macro arguments are not expanded + * before token pasting with ##. GCC documents this prescan rule explicitly. + */ +#define PAR_PERSIST_IDX_VALUE(enum_, pers_) PAR_PERSIST_IDX_VALUE_I(enum_, pers_) +#define PAR_PERSIST_IDX_VALUE_I(enum_, pers_) PAR_PERSIST_IDX_VALUE_##pers_(enum_) +#define PAR_PERSIST_IDX_VALUE_1(enum_) PAR_PERSIST_IDX_##enum_ +#define PAR_PERSIST_IDX_VALUE_0(enum_) PAR_PERSIST_IDX_INVALID +#define PAR_INIT_PERSIST(enum_, pers_) .persistent = (pers_), .persist_idx = PAR_PERSIST_IDX_VALUE(enum_, pers_), #else -#define PAR_INIT_PERSIST(pers_) +#define PAR_INIT_PERSIST(enum_, pers_) #endif #if (1 == PAR_CFG_ENABLE_DESC) @@ -271,7 +292,7 @@ PAR_STATIC_ASSERT(par_compile_check_hash_bucket_collision_ref, (sizeof(&par_comp PAR_INIT_UNIT(unit_) \ .type = ePAR_TYPE_U8, \ PAR_INIT_ACCESS(access_) \ - PAR_INIT_PERSIST(pers_) \ + PAR_INIT_PERSIST(enum_, pers_) \ PAR_INIT_DESC(desc_) \ }, @@ -284,7 +305,7 @@ PAR_STATIC_ASSERT(par_compile_check_hash_bucket_collision_ref, (sizeof(&par_comp PAR_INIT_UNIT(unit_) \ .type = ePAR_TYPE_U16, \ PAR_INIT_ACCESS(access_) \ - PAR_INIT_PERSIST(pers_) \ + PAR_INIT_PERSIST(enum_, pers_) \ PAR_INIT_DESC(desc_) \ }, @@ -297,7 +318,7 @@ PAR_STATIC_ASSERT(par_compile_check_hash_bucket_collision_ref, (sizeof(&par_comp PAR_INIT_UNIT(unit_) \ .type = ePAR_TYPE_U32, \ PAR_INIT_ACCESS(access_) \ - PAR_INIT_PERSIST(pers_) \ + PAR_INIT_PERSIST(enum_, pers_) \ PAR_INIT_DESC(desc_) \ }, @@ -310,7 +331,7 @@ PAR_STATIC_ASSERT(par_compile_check_hash_bucket_collision_ref, (sizeof(&par_comp PAR_INIT_UNIT(unit_) \ .type = ePAR_TYPE_I8, \ PAR_INIT_ACCESS(access_) \ - PAR_INIT_PERSIST(pers_) \ + PAR_INIT_PERSIST(enum_, pers_) \ PAR_INIT_DESC(desc_) \ }, @@ -323,7 +344,7 @@ PAR_STATIC_ASSERT(par_compile_check_hash_bucket_collision_ref, (sizeof(&par_comp PAR_INIT_UNIT(unit_) \ .type = ePAR_TYPE_I16, \ PAR_INIT_ACCESS(access_) \ - PAR_INIT_PERSIST(pers_) \ + PAR_INIT_PERSIST(enum_, pers_) \ PAR_INIT_DESC(desc_) \ }, @@ -336,7 +357,7 @@ PAR_STATIC_ASSERT(par_compile_check_hash_bucket_collision_ref, (sizeof(&par_comp PAR_INIT_UNIT(unit_) \ .type = ePAR_TYPE_I32, \ PAR_INIT_ACCESS(access_) \ - PAR_INIT_PERSIST(pers_) \ + PAR_INIT_PERSIST(enum_, pers_) \ PAR_INIT_DESC(desc_) \ }, @@ -349,7 +370,7 @@ PAR_STATIC_ASSERT(par_compile_check_hash_bucket_collision_ref, (sizeof(&par_comp PAR_INIT_UNIT(unit_) \ .type = ePAR_TYPE_F32, \ PAR_INIT_ACCESS(access_) \ - PAR_INIT_PERSIST(pers_) \ + PAR_INIT_PERSIST(enum_, pers_) \ PAR_INIT_DESC(desc_) \ }, /**< Dispatch map for table initialization. */ #define PAR_ITEM_U8 PAR_INIT_U8 @@ -392,6 +413,12 @@ static const par_cfg_t g_par_table[ePAR_NUM_OF] = { #undef PAR_INIT_ACCESS #undef PAR_INIT_PERSIST #undef PAR_INIT_DESC +#if (1 == PAR_CFG_ENABLE_PERSIST) +#undef PAR_PERSIST_IDX_VALUE +#undef PAR_PERSIST_IDX_VALUE_I +#undef PAR_PERSIST_IDX_VALUE_1 +#undef PAR_PERSIST_IDX_VALUE_0 +#endif /** * @brief Table size in bytes. diff --git a/src/def/par_def.h b/src/def/par_def.h index f644eaf..b499f72 100644 --- a/src/def/par_def.h +++ b/src/def/par_def.h @@ -64,6 +64,11 @@ enum }; #undef PAR_ITEM_ENUM typedef uint16_t par_num_t; + +/** + * @brief Sentinel used by par_cfg_t.persist_idx for non-persistent parameters. + */ +#define PAR_PERSIST_IDX_INVALID UINT16_MAX /** * @brief Compile-time storage group counts derived from par_table.def. * @note These constants are used by layout and static storage allocation. @@ -136,17 +141,26 @@ enum PAR_LAYOUT_COMPILE_COUNT_SUM = (PAR_LAYOUT_COMPILE_COUNT8 + PAR_LAYOUT_COMPILE_COUNT16 + PAR_LAYOUT_COMPILE_COUNT32) }; -#define PAR_ITEM_PERSIST_COUNT(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) + ((pers_) ? 1u : 0u) +/** + * @brief Compile-time persistent-slot enumeration derived from par_table.def. + * @details Only entries flagged with pers_ == true contribute a slot. The + * resulting PAR_PERSIST_IDX_ constants are dense and ordered exactly as + * the source table. + */ +#define PAR_PERSIST_ENUM_SELECT(enum_, pers_) PAR_PERSIST_ENUM_SELECT_I(enum_, pers_) +#define PAR_PERSIST_ENUM_SELECT_I(enum_, pers_) PAR_PERSIST_ENUM_SELECT_##pers_(enum_) +#define PAR_PERSIST_ENUM_SELECT_1(enum_) PAR_PERSIST_IDX_##enum_, +#define PAR_PERSIST_ENUM_SELECT_0(enum_) +#define PAR_ITEM_PERSIST_ENUM(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_PERSIST_ENUM_SELECT(enum_, pers_) enum { - PAR_PERSISTENT_COMPILE_COUNT = 0u -#define PAR_ITEM_U8 PAR_ITEM_PERSIST_COUNT -#define PAR_ITEM_U16 PAR_ITEM_PERSIST_COUNT -#define PAR_ITEM_U32 PAR_ITEM_PERSIST_COUNT -#define PAR_ITEM_I8 PAR_ITEM_PERSIST_COUNT -#define PAR_ITEM_I16 PAR_ITEM_PERSIST_COUNT -#define PAR_ITEM_I32 PAR_ITEM_PERSIST_COUNT -#define PAR_ITEM_F32 PAR_ITEM_PERSIST_COUNT +#define PAR_ITEM_U8 PAR_ITEM_PERSIST_ENUM +#define PAR_ITEM_U16 PAR_ITEM_PERSIST_ENUM +#define PAR_ITEM_U32 PAR_ITEM_PERSIST_ENUM +#define PAR_ITEM_I8 PAR_ITEM_PERSIST_ENUM +#define PAR_ITEM_I16 PAR_ITEM_PERSIST_ENUM +#define PAR_ITEM_I32 PAR_ITEM_PERSIST_ENUM +#define PAR_ITEM_F32 PAR_ITEM_PERSIST_ENUM #include "../../par_table.def" #undef PAR_ITEM_U8 #undef PAR_ITEM_U16 @@ -155,8 +169,15 @@ enum #undef PAR_ITEM_I16 #undef PAR_ITEM_I32 #undef PAR_ITEM_F32 + + PAR_PERSISTENT_COMPILE_COUNT, + PAR_PERSIST_SLOT_MAP_CAPACITY = (PAR_PERSISTENT_COMPILE_COUNT > 0U) ? PAR_PERSISTENT_COMPILE_COUNT : 1U }; -#undef PAR_ITEM_PERSIST_COUNT +#undef PAR_ITEM_PERSIST_ENUM +#undef PAR_PERSIST_ENUM_SELECT +#undef PAR_PERSIST_ENUM_SELECT_I +#undef PAR_PERSIST_ENUM_SELECT_1 +#undef PAR_PERSIST_ENUM_SELECT_0 #undef PAR_ITEM_COUNT_ONE #undef PAR_ITEM_COUNT_ZERO diff --git a/src/par.c b/src/par.c index c7ea134..5a94401 100644 --- a/src/par.c +++ b/src/par.c @@ -1210,20 +1210,18 @@ par_status_t par_get_num_by_id(const uint16_t id, par_num_t * const p_par_num) return ePAR_ERROR_PARAM; } - { - const uint32_t bucket_idx = par_hash_id(id); - const par_id_map_entry_t * const bucket = &g_par_id_map_static[bucket_idx]; + const uint32_t bucket_idx = par_hash_id(id); + const par_id_map_entry_t * const bucket = &g_par_id_map_static[bucket_idx]; - if ((0u != bucket->used) && (id == bucket->id)) + if ((0u != bucket->used) && (id == bucket->id)) + { + if (bucket->par_num >= ePAR_NUM_OF) { - if (bucket->par_num >= ePAR_NUM_OF) - { - return ePAR_ERROR_PAR_NUM; - } - - *p_par_num = bucket->par_num; - return ePAR_OK; + return ePAR_ERROR_PAR_NUM; } + + *p_par_num = bucket->par_num; + return ePAR_OK; } return ePAR_ERROR; @@ -1247,14 +1245,12 @@ par_status_t par_get_id_by_num(const par_num_t par_num, uint16_t * const p_id) return ePAR_ERROR_PAR_NUM; } - { - const par_cfg_t * const par_cfg = par_get_config(par_num); + const par_cfg_t * const par_cfg = par_get_config(par_num); - if (NULL != par_cfg) - { - *p_id = par_cfg->id; - return ePAR_OK; - } + if (NULL != par_cfg) + { + *p_id = par_cfg->id; + return ePAR_OK; } return ePAR_ERROR; diff --git a/src/par.h b/src/par.h index dddd807..aed5a4a 100644 --- a/src/par.h +++ b/src/par.h @@ -120,7 +120,7 @@ typedef struct /** * @brief Parameter data settings. - * @note A single parameter object occupies 28 bytes with arm-gcc. + * @note The exact object size depends on enabled metadata fields and target ABI. */ typedef struct par_cfg_s { @@ -146,6 +146,7 @@ typedef struct par_cfg_s #endif #if (1 == PAR_CFG_ENABLE_PERSIST) bool persistent; /**< Parameter persistence flag. */ + uint16_t persist_idx; /**< Persistent slot index or PAR_PERSIST_IDX_INVALID. */ #endif } par_cfg_t; /** diff --git a/src/par_cfg.h b/src/par_cfg.h index bc42095..c47cd56 100644 --- a/src/par_cfg.h +++ b/src/par_cfg.h @@ -70,25 +70,22 @@ #endif /** - * @brief Enable/Disable parameter table unique ID checking. + * @brief Enable/Disable parameter-table compatibility checking. * - * @note Base on hash unique ID is being calculated with purpose to detect. - * device and stored parameter table difference. + * @note The stored NVM image header carries a table-ID digest that covers. + * PAR_CFG_TABLE_ID_SCHEMA_VER, persistent-parameter count, persistent order, + * parameter type, and parameter ID. * - * Must be disabled once the device is released in order to prevent. - * loss of calibrated data stored in NVM. + * When enabled, any persisted-layout incompatibility is treated as a managed. + * schema change: startup restores defaults and rebuilds the managed NVM image. + * This includes add/remove/reorder/type/ID changes of persistent parameters, + * and transitions between persistent and non-persistent state. * - * @pre "PAR_CFG_NVM_EN" must be enabled otherwise it does not make sense - * to calculate ID at all. + * @pre "PAR_CFG_NVM_EN" must be enabled, otherwise table-ID checking does. + * not apply. */ #ifndef PAR_CFG_TABLE_ID_CHECK_EN #define PAR_CFG_TABLE_ID_CHECK_EN (0) -#if (1 == PAR_CFG_NVM_EN) -#ifndef DEBUG -#undef PAR_CFG_TABLE_ID_CHECK_EN -#define PAR_CFG_TABLE_ID_CHECK_EN (0) -#endif -#endif #endif /** diff --git a/src/persist/par_nvm.c b/src/persist/par_nvm.c index a2fb3eb..26cc53e 100644 --- a/src/persist/par_nvm.c +++ b/src/persist/par_nvm.c @@ -140,16 +140,59 @@ typedef struct #define PAR_NVM_DATA_OBJ_STRIDE ((uint32_t)sizeof(par_nvm_data_obj_t)) #define PAR_NVM_DATA_SLOT_SIZE ((uint8_t)sizeof(((par_nvm_data_obj_t *)0)->data)) - /** - * @brief Parameter NVM LUT talbe. + * @brief Runtime persistence-slot state. */ typedef struct { - uint32_t addr; /**< Start address of parameter. */ - uint16_t id; /**< ID of stored parameter. */ - bool valid; /**< Valid entry. */ -} par_nvm_lut_t; + bool loaded_slots[PAR_PERSIST_SLOT_MAP_CAPACITY]; /**< Runtime-loaded flag for each compiled persistent slot. */ + uint16_t loaded_count; /**< Number of runtime-loaded persistent slots. */ +} par_nvm_slot_runtime_t; + +#if (1 == PAR_CFG_ENABLE_NAME) +#define PAR_NVM_DBG_NAME_ARG(cfg_) (((const par_cfg_t *)(cfg_) != NULL) && (((const par_cfg_t *)(cfg_))->name != NULL) ? ((const par_cfg_t *)(cfg_))->name : "") +#else +#define PAR_NVM_DBG_NAME_ARG(cfg_) "" +#endif + +#define PAR_PERSIST_SLOT_ENTRY_SELECT(enum_, pers_) PAR_PERSIST_SLOT_ENTRY_SELECT_I(enum_, pers_) +#define PAR_PERSIST_SLOT_ENTRY_SELECT_I(enum_, pers_) PAR_PERSIST_SLOT_ENTRY_SELECT_##pers_(enum_) +#define PAR_PERSIST_SLOT_ENTRY_SELECT_true(enum_) [PAR_PERSIST_IDX_##enum_] = enum_, +#define PAR_PERSIST_SLOT_ENTRY_SELECT_false(enum_) +#define PAR_PERSIST_SLOT_ENTRY_SELECT_1(enum_) [PAR_PERSIST_IDX_##enum_] = enum_, +#define PAR_PERSIST_SLOT_ENTRY_SELECT_0(enum_) +#define PAR_ITEM_PERSIST_SLOT(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_PERSIST_SLOT_ENTRY_SELECT(enum_, pers_) +/** + * @brief Compile-time mapping from persistent slot to live parameter number. + * + * @details This table is derived directly from par_table.def. Slot order is the + * single source of truth for the managed NVM payload layout used by par_nvm.c. + */ +static const par_num_t g_par_persist_slot_to_par_num[PAR_PERSIST_SLOT_MAP_CAPACITY] = { +#define PAR_ITEM_U8 PAR_ITEM_PERSIST_SLOT +#define PAR_ITEM_U16 PAR_ITEM_PERSIST_SLOT +#define PAR_ITEM_U32 PAR_ITEM_PERSIST_SLOT +#define PAR_ITEM_I8 PAR_ITEM_PERSIST_SLOT +#define PAR_ITEM_I16 PAR_ITEM_PERSIST_SLOT +#define PAR_ITEM_I32 PAR_ITEM_PERSIST_SLOT +#define PAR_ITEM_F32 PAR_ITEM_PERSIST_SLOT +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +}; +#undef PAR_ITEM_PERSIST_SLOT +#undef PAR_PERSIST_SLOT_ENTRY_SELECT +#undef PAR_PERSIST_SLOT_ENTRY_SELECT_I +#undef PAR_PERSIST_SLOT_ENTRY_SELECT_true +#undef PAR_PERSIST_SLOT_ENTRY_SELECT_false +#undef PAR_PERSIST_SLOT_ENTRY_SELECT_1 +#undef PAR_PERSIST_SLOT_ENTRY_SELECT_0 + /** * @brief Module-scope variables. */ @@ -170,15 +213,58 @@ static bool gb_is_nvm_owner = false; */ static const par_store_backend_api_t *gp_store = NULL; /** - * @brief Parameter NVM lut. + * @brief Runtime state of compiled persistent slots. */ -static par_nvm_lut_t g_par_nvm_data_obj_addr[ePAR_NUM_OF] = { 0 }; +static par_nvm_slot_runtime_t g_par_nvm_slot_runtime = { 0 }; + +/** + * @brief Calculate serialized-header CRC-16. + * + * @details The header CRC is accumulated field-by-field over the serialized + * native-order bytes of obj_nb and table_id. This avoids hashing compiler + * padding bytes inside par_nvm_head_obj_t while intentionally keeping the + * persisted format and table-ID comparison tied to the current target + * architecture. + * + * @param p_head_obj Pointer to header object. + * @return Calculated CRC-16 value. + */ +static uint16_t par_nvm_calc_head_crc(const par_nvm_head_obj_t * const p_head_obj) +{ + uint16_t crc = PAR_IF_CRC16_INIT; + + PAR_ASSERT(NULL != p_head_obj); + + crc = par_if_crc16_accumulate(crc, (const uint8_t * const)&p_head_obj->obj_nb, (uint32_t)sizeof(p_head_obj->obj_nb)); + crc = par_if_crc16_accumulate(crc, (const uint8_t * const)&p_head_obj->table_id, (uint32_t)sizeof(p_head_obj->table_id)); + + return crc; +} /** - * @brief Function declarations. + * @brief Calculate per-record CRC-8 over serialized id/size/data bytes. + * + * @details The record CRC is accumulated field-by-field over the serialized + * native-order bytes of id, size, and the fixed 4-byte payload slot. + * + * @param p_obj Pointer to data object. + * @return Calculated CRC-8 value. */ -static uint16_t par_nvm_calc_head_crc(const par_nvm_head_obj_t * const p_head_obj); -static uint8_t par_nvm_calc_obj_crc(const par_nvm_data_obj_t * const p_obj); -static bool par_nvm_is_in_nvm_lut(const uint16_t id); +static uint8_t par_nvm_calc_obj_crc(const par_nvm_data_obj_t * const p_obj) +{ + uint32_t data_raw = 0U; + uint8_t crc = PAR_IF_CRC8_INIT; + + PAR_ASSERT(NULL != p_obj); + + memcpy(&data_raw, &p_obj->data, sizeof(data_raw)); + + crc = par_if_crc8_accumulate(crc, (const uint8_t * const)&p_obj->id, (uint32_t)sizeof(p_obj->id)); + crc = par_if_crc8_accumulate(crc, (const uint8_t * const)&p_obj->size, (uint32_t)sizeof(p_obj->size)); + crc = par_if_crc8_accumulate(crc, (const uint8_t * const)&data_raw, (uint32_t)sizeof(data_raw)); + + return crc; +} + /** * @brief Read parameter NVM header. * @@ -287,233 +373,317 @@ static par_status_t par_nvm_validate_header(par_nvm_head_obj_t * const p_head_ob return status; } + /** - * @brief Calculate serialized-header CRC-16. + * @brief Resolve a persistent slot index to a live parameter number. * - * @details The header CRC is accumulated field-by-field over the serialized - * native-order bytes of obj_nb and table_id. This avoids hashing compiler - * padding bytes inside par_nvm_head_obj_t while intentionally keeping the - * persisted format and table-ID comparison tied to the current target - * architecture. + * @param persist_idx Persistent slot index. + * @param p_par_num Output live parameter number. + * @return Operation status. + */ +static par_status_t par_nvm_get_num_by_persist_idx(const uint16_t persist_idx, par_num_t * const p_par_num) +{ + if ((persist_idx >= PAR_PERSISTENT_COMPILE_COUNT) || (NULL == p_par_num)) + { + return ePAR_ERROR; + } + + *p_par_num = g_par_persist_slot_to_par_num[persist_idx]; + return ePAR_OK; +} +/** + * @brief Convert a compile-time persistent slot index into the managed NVM object address. * - * @param p_head_obj Pointer to header object. - * @return Calculated CRC-16 value. + * @details The managed NVM payload area is a dense linear array of fixed-size + * par_nvm_data_obj_t records. Therefore the slot address is derived directly as: + * first-data-object-address + slot-index * object-stride. + * + * @param persist_idx Persistent slot index. + * @return Start address of the slot inside the managed NVM payload area. */ -static uint16_t par_nvm_calc_head_crc(const par_nvm_head_obj_t * const p_head_obj) +static uint32_t par_nvm_addr_from_persist_idx(const uint16_t persist_idx) { - uint16_t crc = PAR_IF_CRC16_INIT; + return (PAR_NVM_FIRST_DATA_OBJ_ADDR + (PAR_NVM_DATA_OBJ_STRIDE * persist_idx)); +} - PAR_ASSERT(NULL != p_head_obj); +/** + * @brief Print the compiled persistent-slot map and current runtime load state. + * + * This function is intended for debug use only. It prints, for each compiled + * persistent slot, the slot index, parameter ID, computed NVM address, runtime + * loaded flag, and parameter name when name support is enabled. + * + * The slot-to-parameter relationship is derived from the compile-time persistent + * mapping table, while the loaded flag reflects whether the slot was loaded + * successfully during the current NVM initialization/load flow. + * + * @return ePAR_OK Debug information was printed. + * @return ePAR_ERROR Debug print is disabled at build time. + */ +par_status_t par_nvm_print_nvm_lut(void) +{ + par_status_t status = ePAR_OK; - crc = par_if_crc16_accumulate(crc, (const uint8_t * const)&p_head_obj->obj_nb, (uint32_t)sizeof(p_head_obj->obj_nb)); - crc = par_if_crc16_accumulate(crc, (const uint8_t * const)&p_head_obj->table_id, (uint32_t)sizeof(p_head_obj->table_id)); +#if (1 == PAR_CFG_DEBUG_EN) + PAR_DBG_PRINT("PAR_NVM: Parameter NVM look-up table:"); + PAR_DBG_PRINT(" #\tID\tAddr\tLoaded\tname"); + PAR_DBG_PRINT("================================"); - return crc; + for (uint16_t persist_idx = 0U; persist_idx < PAR_PERSISTENT_COMPILE_COUNT; persist_idx++) + { + PAR_DBG_PRINT( + " %u\t%u\t0x%08lX\t%u\t%s", + (unsigned)persist_idx, + (unsigned)par_get_config(g_par_persist_slot_to_par_num[persist_idx])->id, + (unsigned long)par_nvm_addr_from_persist_idx(persist_idx), + (unsigned)g_par_nvm_slot_runtime.loaded_slots[persist_idx], + PAR_NVM_DBG_NAME_ARG(par_get_config(g_par_persist_slot_to_par_num[persist_idx]))); + } +#else + status = ePAR_ERROR; +#endif + + return status; } /** - * @brief Calculate per-record CRC-8 over serialized id/size/data bytes. + * @brief Clear the runtime-loaded persistent-slot view. + */ +static void par_nvm_clear_lut(void) +{ + (void)memset(&g_par_nvm_slot_runtime, 0, sizeof(g_par_nvm_slot_runtime)); +} +/** + * @brief Mark every compiled persistent slot as available in the runtime view. * - * @details The record CRC is accumulated field-by-field over the serialized - * native-order bytes of id, size, and the fixed 4-byte payload slot. + * @details This helper is used after a full rewrite path where the managed NVM + * image is regenerated for the current compile-time persistent schema. + */ +static void par_nvm_build_new_nvm_lut(void) +{ + par_nvm_clear_lut(); + (void)memset(g_par_nvm_slot_runtime.loaded_slots, true, sizeof(g_par_nvm_slot_runtime.loaded_slots)); + g_par_nvm_slot_runtime.loaded_count = PAR_PERSISTENT_COMPILE_COUNT; + par_nvm_print_nvm_lut(); +} +/** + * @brief Get parameter NVM object start address based on its ID. * - * @param p_obj Pointer to data object. - * @return Calculated CRC-8 value. + * @note In case ID is not found there is a problem with building the. + * NVM lut!!! + * + * @param id Parameter ID. + * @return NVM address of object with ID. */ -static uint8_t par_nvm_calc_obj_crc(const par_nvm_data_obj_t * const p_obj) +static uint32_t par_nvm_get_nvm_lut_addr(const uint16_t id) { - uint32_t data_raw = 0U; - uint8_t crc = PAR_IF_CRC8_INIT; + par_num_t par_num = 0U; + const par_cfg_t *par_cfg = NULL; - PAR_ASSERT(NULL != p_obj); - - memcpy(&data_raw, &p_obj->data, sizeof(data_raw)); + if (ePAR_OK != par_get_num_by_id(id, &par_num)) + { + return 0U; + } - crc = par_if_crc8_accumulate(crc, (const uint8_t * const)&p_obj->id, (uint32_t)sizeof(p_obj->id)); - crc = par_if_crc8_accumulate(crc, (const uint8_t * const)&p_obj->size, (uint32_t)sizeof(p_obj->size)); - crc = par_if_crc8_accumulate(crc, (const uint8_t * const)&data_raw, (uint32_t)sizeof(data_raw)); + par_cfg = par_get_config(par_num); + if ((NULL == par_cfg) || (false == par_cfg->persistent) || (par_cfg->persist_idx >= PAR_PERSISTENT_COMPILE_COUNT)) + { + return 0U; + } - return crc; + return par_nvm_addr_from_persist_idx(par_cfg->persist_idx); } + /** - * @brief Load all parameters value from NVM. + * @brief Load all parameter values from NVM. + * + * @details Two stored-count asymmetries are handled explicitly: + * - If the stored header count is greater than the compile-time persistent + * slot count, the image is treated as incompatible and the caller rebuilds + * the managed NVM area from current defaults. + * - If the stored header count is smaller than the compile-time persistent + * slot count, the stored prefix is restored first and the missing tail slots + * are appended from live defaults before the header count is rewritten. * * @param num_of_par Number of stored parameters inside NVM. * @return Status of operation. */ static par_status_t par_nvm_load_all(const uint16_t num_of_par) { + typedef struct + { + const char *reason; + uint16_t stored_id; + } par_nvm_load_error_ctx_t; + par_status_t status = ePAR_OK; - par_num_t par_num = 0; - uint16_t i = 0; - uint32_t obj_addr = 0; + par_status_t op_status = ePAR_OK; + par_num_t par_num = 0U; + uint16_t i = 0U; par_nvm_data_obj_t obj_data = { 0 }; - par_status_t store_status = ePAR_OK; - uint8_t crc_calc = 0; - uint16_t per_par_nb = 0; - uint16_t new_par_cnt = 0; - for (i = 0; i < num_of_par; i++) - { - /* NVM persistence is a dense linear array of fixed 8-byte objects. */ - obj_addr = ((PAR_NVM_DATA_OBJ_STRIDE * i) + PAR_NVM_FIRST_DATA_OBJ_ADDR); - store_status = gp_store->read(obj_addr, (uint32_t)sizeof(par_nvm_data_obj_t), (uint8_t *)&obj_data); - if (ePAR_OK == store_status) - { - crc_calc = par_nvm_calc_obj_crc(&obj_data); - if (crc_calc == obj_data.crc) - { - if (ePAR_OK == par_get_num_by_id(obj_data.id, &par_num)) - { - /* Restore only parameters that still exist and remain persistent. */ - if (true == par_get_config(par_num)->persistent) - { - if (false == par_nvm_is_in_nvm_lut(obj_data.id)) - { - g_par_nvm_data_obj_addr[per_par_nb].id = obj_data.id; - g_par_nvm_data_obj_addr[per_par_nb].addr = obj_addr; - g_par_nvm_data_obj_addr[per_par_nb].valid = true; - /* Restore through the internal fast path. */ - (void)par_set_fast(par_num, &obj_data.data); - per_par_nb++; - } - } - } - } + uint8_t crc_calc = 0U; + uint16_t new_par_cnt = 0U; + par_nvm_load_error_ctx_t err = { 0 }; - else - { - status = ePAR_ERROR_CRC; - break; - } - } - else + par_nvm_clear_lut(); + + /* TODO: Revisit deployed-schema migration for persistent-slot shrink/reorder. + * Consider introducing tombstone/delete-marker handling so removed slots can be represented + * without forcing a destructive rebuild when relaxed compatibility rules are desired. + */ + if (num_of_par > PAR_PERSISTENT_COMPILE_COUNT) + { + status = ePAR_ERROR; + i = PAR_PERSISTENT_COMPILE_COUNT; + err.reason = "stored-count-overflow"; + op_status = status; + goto out; + } + + /* Restore the stored prefix that still exists in the current compile-time schema. */ + for (i = 0U; i < num_of_par; i++) + { + const par_cfg_t *par_cfg = NULL; + const uint32_t obj_addr = par_nvm_addr_from_persist_idx(i); + + op_status = gp_store->read(obj_addr, (uint32_t)sizeof(par_nvm_data_obj_t), (uint8_t *)&obj_data); + if (ePAR_OK != op_status) { status = ePAR_ERROR_NVM; - PAR_DBG_PRINT("PAR_NVM: object read failed, %u", (unsigned)store_status); - break; + err.reason = "read-failed"; + goto out; } - } - PAR_DBG_PRINT("PAR_NVM: Loading all persistent parameters with status: %s", par_get_status_str(status)); - PAR_DBG_PRINT("PAR_NVM: Nb. of stored pars in NVM: %d", num_of_par); - PAR_DBG_PRINT("PAR_NVM: Nb. of live persistent: \t%d", PAR_PERSISTENT_COMPILE_COUNT); - if (ePAR_OK == status) - { - for (i = 0; i < ePAR_NUM_OF; i++) + crc_calc = par_nvm_calc_obj_crc(&obj_data); + if (crc_calc != obj_data.crc) { - const par_cfg_t * const par_cfg = par_get_config(i); + status = ePAR_ERROR_CRC; + err.reason = "crc-mismatch"; + err.stored_id = obj_data.id; + op_status = status; + goto out; + } - if (true == par_cfg->persistent) - { - if (false == par_nvm_is_in_nvm_lut(par_cfg->id)) - { - /* Extend the LUT for newly added persistent parameters. */ - g_par_nvm_data_obj_addr[per_par_nb].id = par_cfg->id; - g_par_nvm_data_obj_addr[per_par_nb].addr = obj_addr + (PAR_NVM_DATA_OBJ_STRIDE * (new_par_cnt + 1U)); - g_par_nvm_data_obj_addr[per_par_nb].valid = true; - par_save(i); - - per_par_nb++; - new_par_cnt++; - } - } + op_status = par_nvm_get_num_by_persist_idx(i, &par_num); + if (ePAR_OK != op_status) + { + status = ePAR_ERROR; + err.reason = "persist-slot-invalid"; + err.stored_id = obj_data.id; + goto out; } - if (new_par_cnt > 0) + par_cfg = par_get_config(par_num); + if ((NULL == par_cfg) || (false == par_cfg->persistent) || (par_cfg->persist_idx != i)) { - /* The object count only grows when new persistent parameters appear. */ - status |= par_nvm_write_header(num_of_par + new_par_cnt); - if (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK)) - { - const par_status_t sync_status = gp_store->sync(); - if (ePAR_OK != sync_status) - { - status |= ePAR_ERROR_NVM; - PAR_DBG_PRINT("PAR_NVM: sync failed, %u", (unsigned)sync_status); - } - } + status = ePAR_ERROR; + err.reason = "persist-map-invalid"; + err.stored_id = obj_data.id; + op_status = status; + goto out; + } -#if (PAR_CFG_DEBUG_EN) - PAR_DBG_PRINT("PAR_NVM: Added %d new parameters to NVM LUT table!", new_par_cnt); -#endif + if (obj_data.id != par_cfg->id) + { + status = ePAR_ERROR; + err.reason = "id-mismatch"; + err.stored_id = obj_data.id; + op_status = status; + goto out; } + + op_status = par_set_fast(par_num, &obj_data.data); + if (ePAR_OK != op_status) + { + status |= op_status; + err.reason = "restore-failed"; + err.stored_id = obj_data.id; + goto out; + } + + g_par_nvm_slot_runtime.loaded_slots[i] = true; } - return status; -} -/** - * @brief Build a fresh NVM lookup table from the live persistent set. - * - * @details The LUT maps external parameter IDs to their fixed-record address in - * the linear persisted-object array. This keeps load/save paths anchored on ID - * instead of assuming that a given parameter meaning is tied permanently to one - * absolute slot position. - */ -static void par_nvm_build_new_nvm_lut(void) -{ - uint16_t per_par_nb = 0U; - for (par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++) + /* Append any newly introduced persistent slots after restoring the stored prefix. */ + for (i = num_of_par; i < PAR_PERSISTENT_COMPILE_COUNT; i++) { - const par_cfg_t * const par_cfg = par_get_config(par_num); + const par_cfg_t *par_cfg = NULL; - if (true == par_cfg->persistent) + op_status = par_nvm_get_num_by_persist_idx(i, &par_num); + if (ePAR_OK != op_status) { - if (0 == per_par_nb) - { - g_par_nvm_data_obj_addr[per_par_nb].addr = PAR_NVM_FIRST_DATA_OBJ_ADDR; - } + status = ePAR_ERROR; + err.reason = "persist-slot-invalid"; + goto out; + } - /* NVM persistence uses a linear list of fixed-size 8-byte objects. */ - else - { - g_par_nvm_data_obj_addr[per_par_nb].addr = (g_par_nvm_data_obj_addr[per_par_nb - 1].addr + PAR_NVM_DATA_OBJ_STRIDE); - } + par_cfg = par_get_config(par_num); + if ((NULL == par_cfg) || (false == par_cfg->persistent) || (par_cfg->persist_idx != i)) + { + status = ePAR_ERROR; + err.reason = "persist-map-invalid"; + op_status = status; + goto out; + } - g_par_nvm_data_obj_addr[per_par_nb].id = par_cfg->id; - g_par_nvm_data_obj_addr[per_par_nb].valid = true; - per_par_nb++; + status |= par_save(par_num); + if (ePAR_OK != (status & ePAR_STATUS_ERROR_MASK)) + { + err.reason = "append-save-failed"; + err.stored_id = par_cfg->id; + op_status = status; + goto out; } + + g_par_nvm_slot_runtime.loaded_slots[i] = true; + new_par_cnt++; } + g_par_nvm_slot_runtime.loaded_count = i; - par_nvm_print_nvm_lut(); -} -/** - * @brief Get parameter NVM object start address based on its ID. - * - * @note In case ID is not found there is a problem with building the. - * NVM lut!!! - * - * @param id Parameter ID. - * @return NVM address of object with ID. - */ -static uint32_t par_nvm_get_nvm_lut_addr(const uint16_t id) -{ - for (par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++) + if (new_par_cnt > 0U) { - if (id == g_par_nvm_data_obj_addr[par_num].id) + /* Missing stored slots were appended from live defaults; commit the new count. */ + status |= par_nvm_write_header(PAR_PERSISTENT_COMPILE_COUNT); + if (ePAR_OK != (status & ePAR_STATUS_ERROR_MASK)) { - return g_par_nvm_data_obj_addr[par_num].addr; + err.reason = "rewrite-header-failed"; + op_status = status; + goto out; } - } - return 0; -} -/** - * @brief Check if parameter is in NVM LUT. - * - * @param id Parameter ID. - * @return Flag that indicated if object is in NVM lut. - */ -static bool par_nvm_is_in_nvm_lut(const uint16_t id) -{ - for (par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++) - { - if ((id == g_par_nvm_data_obj_addr[par_num].id) && (true == g_par_nvm_data_obj_addr[par_num].valid)) + op_status = gp_store->sync(); + if (ePAR_OK != op_status) { - return true; + status |= ePAR_ERROR_NVM; + err.reason = "sync-failed"; + goto out; } + PAR_DBG_PRINT("PAR_NVM: appended %u new persistent slots and rewrote header count to %u", + (unsigned)new_par_cnt, + (unsigned)PAR_PERSISTENT_COMPILE_COUNT); } - return false; +out: +#if (1 == PAR_CFG_DEBUG_EN) + PAR_DBG_PRINT("PAR_NVM: Loading all persistent parameters with status: %s", par_get_status_str(status)); + PAR_DBG_PRINT("PAR_NVM: Nb. of stored pars in NVM: %u", (unsigned)num_of_par); + PAR_DBG_PRINT("PAR_NVM: Nb. of live persistent: %u", (unsigned)PAR_PERSISTENT_COMPILE_COUNT); + + if (NULL != err.reason) + { + PAR_DBG_PRINT( + "PAR_NVM: load error slot=%u, addr=0x%08lX, stored_id=%u, par_num=%u, expected_id=%u, name=%s, reason=%s, op_status=%s, final_status=%s", + (unsigned)i, + (unsigned long)par_nvm_addr_from_persist_idx(i), + (unsigned)err.stored_id, + (unsigned)((i < PAR_PERSIST_SLOT_MAP_CAPACITY) ? g_par_persist_slot_to_par_num[i] : 0U), + (unsigned)((i < PAR_PERSIST_SLOT_MAP_CAPACITY) ? par_get_config(g_par_persist_slot_to_par_num[i])->id : 0U), + PAR_NVM_DBG_NAME_ARG((i < PAR_PERSIST_SLOT_MAP_CAPACITY) ? par_get_config(g_par_persist_slot_to_par_num[i]) : NULL), + err.reason, + par_get_status_str(op_status), + par_get_status_str(status)); + } +#endif + return status; } /** * @brief Resolve, validate, and initialize the mounted storage backend. @@ -862,6 +1032,15 @@ par_status_t par_nvm_write_all(void) status |= par_nvm_write_header(PAR_PERSISTENT_COMPILE_COUNT); } + if (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK)) + { + par_nvm_build_new_nvm_lut(); + } + else + { + par_nvm_clear_lut(); + } + PAR_DBG_PRINT("PAR_NVM: Storing all to NVM status: %s", par_get_status_str(status)); } else @@ -885,7 +1064,6 @@ par_status_t par_nvm_reset_all(void) if (true == gb_is_init) { - par_nvm_build_new_nvm_lut(); status |= par_nvm_write_all(); } else @@ -895,31 +1073,6 @@ par_status_t par_nvm_reset_all(void) return status; } -/** - * @brief Print parameter NVM table. - * - * @note Only for debugging purposes. - */ -par_status_t par_nvm_print_nvm_lut(void) -{ - par_status_t status = ePAR_OK; - -#if (PAR_CFG_DEBUG_EN) - PAR_DBG_PRINT("PAR_NVM: Parameter NVM look-up table:"); - PAR_DBG_PRINT(" %s\t%s\t%s\t\t%s", "#", "ID", "Addr", "Valid"); - PAR_DBG_PRINT("==============================="); - - for (par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++) - { - PAR_DBG_PRINT(" %d\t%d\t0x%04X\t%d", par_num, g_par_nvm_data_obj_addr[par_num].id, - g_par_nvm_data_obj_addr[par_num].addr, - g_par_nvm_data_obj_addr[par_num].valid); - PAR_DBG_PRINT("-----------------------------"); - } -#endif - - return status; -} /** * @} */ From 453d11ab3ba3bc029ae1b1aadda33ec703b30f35 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Mon, 6 Apr 2026 11:09:31 +0800 Subject: [PATCH 30/36] refactor(core): Unify persistence guards with NVM and improve package logging --- README.md | 3 +- docs/api-reference.md | 1 + docs/getting-started.md | 7 +- src/def/par_def.c | 4 +- src/detail/par_typed_impl.inc | 47 ++++--- src/layout/par_layout.c | 5 +- src/par.c | 62 +++++++-- src/par.h | 4 +- src/par_cfg.h | 87 +++++++++--- src/persist/par_nvm.c | 235 +++++++++++++++++---------------- src/persist/par_nvm_table_id.c | 2 +- 11 files changed, 282 insertions(+), 175 deletions(-) diff --git a/README.md b/README.md index 12ceb16..1736d23 100644 --- a/README.md +++ b/README.md @@ -134,10 +134,11 @@ This repository contains the reusable module core and templates. A real integrat - `par_cfg.h` includes `par_cfg_port.h` unconditionally, so your build must provide that header and make its directory visible to the compiler. - `PAR_CFG_ENABLE_TYPE_F32` controls whether floating-point parameter support and the related typed APIs are compiled in. - `PAR_CFG_ENABLE_RUNTIME_VALIDATION` and `PAR_CFG_ENABLE_CHANGE_CALLBACK` control whether normal setters include runtime validation callbacks and on-change callbacks. +- The RT-Thread port exposes leveled package logging hooks (`INFO` / `DEBUG` / `WARN` / `ERROR`). Enable them with `AUTOGEN_PM_USING_DEBUG` in package Kconfig. - The module separates **internal parameter enumeration** (`par_num_t`) from **external parameter IDs** (`id`). - The current ID lookup implementation uses a one-entry-per-bucket hash map generated at compile time from `par_table.def`. External IDs must therefore be not only unique, but also collision-free under the configured hash geometry. Optional runtime diagnostic scans can be enabled with `PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK` and `PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK` when additional startup logs are useful. See `docs/architecture.md` for the collision rule and avoidance guidance. - Unchecked setter APIs skip runtime validation callbacks and on-change callbacks, so they should be reserved for tightly controlled hot paths. Bitwise fast setters are further restricted to `U8` / `U16` / `U32` flags or bitmask parameters. Legacy `*_fast()` names remain as deprecated aliases. -- NVM support is optional. When enabled, `src/persist/par_nvm.c` depends on a mounted storage backend interface and on ID and persistence metadata being enabled. The package can build the `GeneralEmbeddedCLibraries/nvm` adapter from `src/persist/backend/`, or the application can provide its own `par_store_backend_get_api()` implementation. +- NVM support is optional. When enabled, `src/persist/par_nvm.c` depends on a mounted storage backend interface and on ID support being enabled. Persistence metadata is compiled in automatically under `PAR_CFG_NVM_EN`. The package can build the `GeneralEmbeddedCLibraries/nvm` adapter from `src/persist/backend/`, or the application can provide its own `par_store_backend_get_api()` implementation. - Live RAM layout and persisted NVM layout are intentionally different. RAM storage is grouped by value width, while the persistence area is a linear list of fixed 8-byte objects: `id(2) + size(1) + crc8(1) + data(4)`. - Compile-time persistent order is the primary slot layout contract of the managed NVM image. The stored `id` remains in each record as an integrity and diagnostics field, and the current `size` field is still always written as `4`, so it is mainly a descriptor/integrity helper rather than a space-saving variable-width encoding. - The serialized NVM header is written explicitly as a fixed 12-byte storage image (`sign(4) + obj_nb(2) + table_id(4) + crc16(2)`), so on-storage layout does not depend on compiler struct padding. Header CRC-16 covers the serialized `obj_nb + table_id` bytes, while each data record carries its own CRC-8 over `id + size + data`. diff --git a/docs/api-reference.md b/docs/api-reference.md index 63488dc..b3c87dc 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -12,6 +12,7 @@ This document groups the public API from `src/par.h` by responsibility. - `F32` typed APIs depend on `PAR_CFG_ENABLE_TYPE_F32 = 1`. - Validation registration APIs depend on `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 1`. - On-change registration APIs depend on `PAR_CFG_ENABLE_CHANGE_CALLBACK = 1`. +- Logging output depends on `PAR_CFG_DEBUG_EN = 1`. When the RT-Thread package port is used, log output is split into `INFO` / `DEBUG` / `WARN` / `ERROR` levels. ## Compile-time availability notes diff --git a/docs/getting-started.md b/docs/getting-started.md index dad5b43..5e7221a 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -136,7 +136,8 @@ Relevant options in `par_cfg.h`: - `PAR_CFG_NVM_EN` - `PAR_CFG_NVM_REGION` - `PAR_CFG_ENABLE_ID` -- `PAR_CFG_ENABLE_PERSIST` + +When `PAR_CFG_NVM_EN = 1`, persistence metadata in the parameter table is compiled in automatically. There is no separate `PAR_CFG_ENABLE_PERSIST` switch anymore. ID-based lookup is generated statically when `PAR_CFG_ENABLE_ID = 1`. Optional startup diagnostics can be enabled with: @@ -154,6 +155,10 @@ Backend choices: If `PAR_CFG_NVM_EN = 1` and no backend implementation is linked, the build fails at link time by design. +### Logging + +Use `AUTOGEN_PM_USING_DEBUG` in the RT-Thread package Kconfig to enable package logs. The port layer now exposes leveled logging hooks (`INFO`, `DEBUG`, `WARN`, `ERROR`) and routes them through RT-Thread `rtdbg`/ULOG when enabled. + ### Layout source Choose one of these two modes: diff --git a/src/def/par_def.c b/src/def/par_def.c index b55b141..fa4b6ea 100644 --- a/src/def/par_def.c +++ b/src/def/par_def.c @@ -250,7 +250,7 @@ PAR_STATIC_ASSERT(par_compile_check_hash_bucket_collision_ref, (sizeof(&par_comp #define PAR_INIT_ACCESS(access_) #endif -#if (1 == PAR_CFG_ENABLE_PERSIST) +#if (1 == PAR_CFG_NVM_EN) /** * @brief Translate the X-Macro persistence column into a stored persist slot index. * @@ -413,7 +413,7 @@ static const par_cfg_t g_par_table[ePAR_NUM_OF] = { #undef PAR_INIT_ACCESS #undef PAR_INIT_PERSIST #undef PAR_INIT_DESC -#if (1 == PAR_CFG_ENABLE_PERSIST) +#if (1 == PAR_CFG_NVM_EN) #undef PAR_PERSIST_IDX_VALUE #undef PAR_PERSIST_IDX_VALUE_I #undef PAR_PERSIST_IDX_VALUE_1 diff --git a/src/detail/par_typed_impl.inc b/src/detail/par_typed_impl.inc index 06d1b57..0e58e16 100644 --- a/src/detail/par_typed_impl.inc +++ b/src/detail/par_typed_impl.inc @@ -43,25 +43,29 @@ PAR_TYPED_ROWS_F32(X) #if (1 == PAR_CFG_ENABLE_RANGE) -#define PAR_TYPED_FAST_SET_BODY(PRIV, FIELD, par_num, val) \ - do \ - { \ - const par_range_t range = par_get_range(par_num); \ - if ((val) > range.max.FIELD) \ - { \ - PAR_SET_##PRIV##_PRIV((par_num), range.max.FIELD); \ - return ePAR_WAR_LIMITED; \ - } \ - else if ((val) < range.min.FIELD) \ - { \ - PAR_SET_##PRIV##_PRIV((par_num), range.min.FIELD); \ - return ePAR_WAR_LIMITED; \ - } \ - else \ - { \ - PAR_SET_##PRIV##_PRIV((par_num), (val)); \ - return ePAR_OK; \ - } \ +#define PAR_TYPED_FAST_SET_BODY(PRIV, FIELD, par_num, val) \ + do \ + { \ + const par_range_t range = par_get_range(par_num); \ + if ((val) > range.max.FIELD) \ + { \ + PAR_SET_##PRIV##_PRIV((par_num), range.max.FIELD); \ + PAR_WARN_PRINT("PAR: value limited to configured maximum, par_num=%u", \ + (unsigned)(par_num)); \ + return ePAR_WAR_LIMITED; \ + } \ + else if ((val) < range.min.FIELD) \ + { \ + PAR_SET_##PRIV##_PRIV((par_num), range.min.FIELD); \ + PAR_WARN_PRINT("PAR: value limited to configured minimum, par_num=%u", \ + (unsigned)(par_num)); \ + return ePAR_WAR_LIMITED; \ + } \ + else \ + { \ + PAR_SET_##PRIV##_PRIV((par_num), (val)); \ + return ePAR_OK; \ + } \ } while (0) #else #define PAR_TYPED_FAST_SET_BODY(PRIV, FIELD, par_num, val) \ @@ -193,6 +197,9 @@ static par_status_t par_validate_expected_type(const par_cfg_t * const p_cfg, PAR_ASSERT(expected_type == p_cfg->type); if (expected_type != p_cfg->type) { + PAR_ERR_PRINT("PAR: type mismatch, expected=%u actual=%u", + (unsigned)expected_type, + (unsigned)p_cfg->type); return ePAR_ERROR_TYPE; } @@ -241,7 +248,7 @@ static par_status_t par_set_checked_core(const par_num_t par_num, const par_type #if (1 == PAR_CFG_ENABLE_ACCESS) if (ePAR_ACCESS_RW != par_cfg->access) { - PAR_DBG_PRINT("PAR: write denied for par_num=%u", (unsigned)par_num); + PAR_INFO_PRINT("PAR: write denied by access policy, par_num=%u", (unsigned)par_num); return ePAR_ERROR_ACCESS; } #endif diff --git a/src/layout/par_layout.c b/src/layout/par_layout.c index 837b933..a537a6b 100644 --- a/src/layout/par_layout.c +++ b/src/layout/par_layout.c @@ -89,7 +89,7 @@ void par_layout_init(void) case ePAR_TYPE_NUM_OF: default: - PAR_DBG_PRINT("ERR, PAR layout unsupported type at par_num=%u", (unsigned)par_it); + PAR_ERR_PRINT("PAR: layout encountered unsupported type at par_num=%u", (unsigned)par_it); PAR_ASSERT(0); return; } @@ -97,7 +97,7 @@ void par_layout_init(void) if ((scan_count.count8 != gs_layout_count.count8) || (scan_count.count16 != gs_layout_count.count16) || (scan_count.count32 != gs_layout_count.count32)) { - PAR_DBG_PRINT("ERR, PAR layout count mismatch: scan=(%u,%u,%u), cfg=(%u,%u,%u)", + PAR_ERR_PRINT("PAR: layout count mismatch, scan=(%u,%u,%u) cfg=(%u,%u,%u)", (unsigned)scan_count.count8, (unsigned)scan_count.count16, (unsigned)scan_count.count32, @@ -110,6 +110,7 @@ void par_layout_init(void) #else /* Script layout mode: consume provided static layout directly with no runtime validation. */ gsp_active_offset = PAR_LAYOUT_STATIC_OFFSET_TABLE; + PAR_DBG_PRINT("PAR: layout initialized from generated static table"); return; #endif } diff --git a/src/par.c b/src/par.c index 5a94401..3288249 100644 --- a/src/par.c +++ b/src/par.c @@ -303,10 +303,17 @@ static void par_patch_f32_defaults_from_table(void) */ static void par_bind_storage_layout(void) { + const par_layout_count_t layout_count = par_layout_get_count(); + par_layout_init(); - PAR_DBG_PRINT("Total RAM consumption for parameters value: %u bytes", (unsigned)(((uint32_t)par_layout_get_count().count32 * 4u) + - ((uint32_t)par_layout_get_count().count16 * 2u) + - ((uint32_t)par_layout_get_count().count8))); + PAR_DBG_PRINT("PAR: layout bound, count8=%u count16=%u count32=%u", + (unsigned)layout_count.count8, + (unsigned)layout_count.count16, + (unsigned)layout_count.count32); + PAR_DBG_PRINT("PAR: total RAM consumption for parameter values: %u bytes", + (unsigned)(((uint32_t)layout_count.count32 * 4u) + + ((uint32_t)layout_count.count16 * 2u) + + ((uint32_t)layout_count.count8))); } /** * @brief Hash parameter ID to bucket index. @@ -353,7 +360,7 @@ static par_status_t par_runtime_validate_id_table(const par_cfg_t * const p_par_ #if (1 == PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK) if (bucket->id == id) { - PAR_DBG_PRINT("ERR, Duplicate parameter ID %u!", (unsigned)id); + PAR_ERR_PRINT("PAR: duplicate parameter ID %u detected during runtime diagnostic scan", (unsigned)id); PAR_ASSERT(0); return ePAR_ERROR_INIT; } @@ -362,8 +369,10 @@ static par_status_t par_runtime_validate_id_table(const par_cfg_t * const p_par_ #if (1 == PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK) if (bucket->id != id) { - PAR_DBG_PRINT("ERR, Hash collision: ID %u conflicts with ID %u at bucket %u!", - (unsigned)id, (unsigned)bucket->id, (unsigned)bucket_idx); + PAR_ERR_PRINT("PAR: hash collision detected during runtime diagnostic scan, id=%u conflicts_with=%u bucket=%u", + (unsigned)id, + (unsigned)bucket->id, + (unsigned)bucket_idx); PAR_ASSERT(0); return ePAR_ERROR_INIT; } @@ -410,7 +419,7 @@ static par_status_t par_check_table_validity(const par_cfg_t * const p_par_cfg) if (NULL == p_par_cfg[i].name) { status = ePAR_ERROR_INIT; - PAR_DBG_PRINT("ERR, Parameter %d name missing!", i); + PAR_ERR_PRINT("PAR: parameter %u name missing", (unsigned)i); PAR_ASSERT(0); break; } @@ -420,7 +429,7 @@ static par_status_t par_check_table_validity(const par_cfg_t * const p_par_cfg) if (NULL == p_par_cfg[i].desc) { status = ePAR_ERROR_INIT; - PAR_DBG_PRINT("ERR, Parameter %d description missing!", i); + PAR_ERR_PRINT("PAR: parameter %u description missing", (unsigned)i); PAR_ASSERT(0); break; } @@ -430,7 +439,7 @@ static par_status_t par_check_table_validity(const par_cfg_t * const p_par_cfg) if (false == par_port_is_desc_valid(p_par_cfg[i].desc)) { status = ePAR_ERROR_INIT; - PAR_DBG_PRINT("ERR, Parameter %d description is invalid!", i); + PAR_ERR_PRINT("PAR: parameter %u description invalid", (unsigned)i); PAR_ASSERT(0); break; } @@ -466,6 +475,8 @@ par_status_t par_init(void) PAR_ASSERT(false == par_is_init()); if (false != par_is_init()) return ePAR_ERROR_INIT; + + PAR_DBG_PRINT("PAR: initialization started"); status |= par_check_table_validity(par_cfg_get_table()); par_bind_storage_layout(); status |= par_if_init(); @@ -476,20 +487,23 @@ par_status_t par_init(void) /* Patch F32 defaults after layout offsets become available. */ #if (1 == PAR_CFG_ENABLE_TYPE_F32) par_patch_f32_defaults_from_table(); + PAR_DBG_PRINT("PAR: F32 defaults patched into 32-bit storage group"); #endif #if (1 == PAR_CFG_ENABLE_RESET_ALL_RAW) /* Snapshot defaults before optional NVM restore. */ memcpy(&gs_par_default_mirror, &gs_par_storage, sizeof(gs_par_storage)); + PAR_DBG_PRINT("PAR: default mirror snapshot captured for raw reset path"); #endif #if (1 == PAR_CFG_NVM_EN) /* Restore persisted values after default initialization. */ + PAR_DBG_PRINT("PAR: restoring persistent values from NVM"); status |= par_nvm_init(); #endif } - PAR_DBG_PRINT("PAR: Parameters initialized with status: %s", par_get_status_str(status)); + PAR_INFO_PRINT("PAR: initialization finished with status=%s", par_get_status_str(status)); return status; } @@ -507,6 +521,7 @@ par_status_t par_deinit(void) if (true != par_is_init()) return ePAR_ERROR_INIT; + PAR_DBG_PRINT("PAR: deinitialization started"); #if (1 == PAR_CFG_NVM_EN) deinit_status = par_nvm_deinit(); status |= deinit_status; @@ -515,6 +530,7 @@ par_status_t par_deinit(void) deinit_status = par_if_deinit(); status |= deinit_status; gb_is_init = false; + PAR_INFO_PRINT("PAR: deinitialization finished with status=%s", par_get_status_str(status)); return status; } @@ -732,6 +748,16 @@ par_status_t par_set_to_default(const par_num_t par_num) } par_release_mutex(par_num); + + if (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK)) + { + PAR_DBG_PRINT("PAR: restored default value, par_num=%u", (unsigned)par_num); + } + else + { + PAR_ERR_PRINT("PAR: failed to restore default value, par_num=%u status=%s", (unsigned)par_num, par_get_status_str(status)); + } + return status; } @@ -769,7 +795,7 @@ par_status_t par_reset_all_to_default_raw(void) par_release_mutex((par_num_t)0); - PAR_DBG_PRINT("PAR: Raw reset all parameters to default"); + PAR_DBG_PRINT("PAR: raw reset all parameters to defaults"); return ePAR_OK; } #endif @@ -801,7 +827,7 @@ par_status_t par_set_all_to_default(void) status |= par_set_to_default(par_num); } - PAR_DBG_PRINT("PAR: Setting all parameters to default"); + PAR_DBG_PRINT("PAR: setting all parameters to defaults"); return status; #endif } @@ -1178,7 +1204,7 @@ par_access_t par_get_access(const par_num_t par_num) * @param par_num Parameter number (enumeration). * @return True if parameter is persistent. */ -#if (1 == PAR_CFG_ENABLE_PERSIST) +#if (1 == PAR_CFG_NVM_EN) bool par_is_persistent(const par_num_t par_num) { const par_cfg_t *par_cfg = NULL; @@ -1387,7 +1413,7 @@ par_status_t par_set_n_save(const par_num_t par_num, const void *p_val) if (ePAR_OK != status) { - PAR_DBG_PRINT("PAR: failed to read current value before set_n_save for par_num=%u with status=%s", (unsigned)par_num, par_get_status_str(status)); + PAR_ERR_PRINT("PAR: failed to read current value before set_n_save, par_num=%u status=%s", (unsigned)par_num, par_get_status_str(status)); PAR_ASSERT(0); return status; } @@ -1396,8 +1422,13 @@ par_status_t par_set_n_save(const par_num_t par_num, const void *p_val) /* Persist only when the value actually changed. */ if ((ePAR_OK == status) && value_change) { + PAR_DBG_PRINT("PAR: set_n_save detected value change, par_num=%u", (unsigned)par_num); status |= par_save(par_num); } + else if (ePAR_OK == status) + { + PAR_DBG_PRINT("PAR: set_n_save skipped NVM write because value is unchanged, par_num=%u", (unsigned)par_num); + } return status; } @@ -1415,6 +1446,7 @@ par_status_t par_save_all(void) if (true != par_is_init()) return ePAR_ERROR_INIT; + PAR_DBG_PRINT("PAR: persisting all configured parameters to NVM"); return par_nvm_write_all(); } /** @@ -1432,6 +1464,7 @@ par_status_t par_save(const par_num_t par_num) if (true != par_is_init()) return ePAR_ERROR_INIT; + PAR_DBG_PRINT("PAR: persisting par_num=%u to NVM", (unsigned)par_num); return par_nvm_write(par_num, true); } /** @@ -1468,6 +1501,7 @@ par_status_t par_save_by_id(const uint16_t par_id) return par_save(par_num); } + PAR_WARN_PRINT("PAR: persist-by-id could not resolve parameter id=%u", (unsigned)par_id); return ePAR_ERROR; } #endif diff --git a/src/par.h b/src/par.h index aed5a4a..1ad484d 100644 --- a/src/par.h +++ b/src/par.h @@ -144,7 +144,7 @@ typedef struct par_cfg_s #if (1 == PAR_CFG_ENABLE_ACCESS) par_access_t access; /**< Parameter access from external device point-of-view. */ #endif -#if (1 == PAR_CFG_ENABLE_PERSIST) +#if (1 == PAR_CFG_NVM_EN) bool persistent; /**< Parameter persistence flag. */ uint16_t persist_idx; /**< Persistent slot index or PAR_PERSIST_IDX_INVALID. */ #endif @@ -585,7 +585,7 @@ par_type_list_t par_get_type(const par_num_t par_num); */ par_access_t par_get_access(const par_num_t par_num); #endif -#if (1 == PAR_CFG_ENABLE_PERSIST) +#if (1 == PAR_CFG_NVM_EN) /** * @brief Report whether one parameter is marked persistent. * @param par_num Parameter number. diff --git a/src/par_cfg.h b/src/par_cfg.h index c47cd56..434a957 100644 --- a/src/par_cfg.h +++ b/src/par_cfg.h @@ -54,6 +54,10 @@ */ /** * @brief Enable/Disable storing persistent parameters to NVM. + * + * @note This switch is also the single compile-time gate for persistence + * metadata in the parameter table. There is no separate PAR_CFG_ENABLE_PERSIST + * override anymore. */ #ifndef PAR_CFG_NVM_EN #define PAR_CFG_NVM_EN (1) @@ -132,11 +136,31 @@ } while (0) #endif -#ifndef PAR_PORT_LOG -#define PAR_PORT_LOG(tag, ...) \ +#ifndef PAR_PORT_LOG_INFO +#define PAR_PORT_LOG_INFO(...) \ + do \ + { \ + } while (0) +#endif + +#ifndef PAR_PORT_LOG_DEBUG +#define PAR_PORT_LOG_DEBUG(...) \ + do \ + { \ + } while (0) +#endif + +#ifndef PAR_PORT_LOG_WARN +#define PAR_PORT_LOG_WARN(...) \ do \ { \ - (void)(tag); \ + } while (0) +#endif + +#ifndef PAR_PORT_LOG_ERROR +#define PAR_PORT_LOG_ERROR(...) \ + do \ + { \ } while (0) #endif @@ -193,21 +217,63 @@ #ifndef PAR_CFG_DIRECT_LOG #define PAR_CFG_DIRECT_LOG(...) (cli_printf((char *)__VA_ARGS__)) #endif -#define PAR_DBG_PRINT(...) PAR_CFG_DIRECT_LOG(__VA_ARGS__) +#ifndef PAR_CFG_DIRECT_INFO_LOG +#define PAR_CFG_DIRECT_INFO_LOG(...) PAR_CFG_DIRECT_LOG(__VA_ARGS__) +#endif +#ifndef PAR_CFG_DIRECT_DEBUG_LOG +#define PAR_CFG_DIRECT_DEBUG_LOG(...) PAR_CFG_DIRECT_LOG(__VA_ARGS__) +#endif +#ifndef PAR_CFG_DIRECT_WARN_LOG +#define PAR_CFG_DIRECT_WARN_LOG(...) PAR_CFG_DIRECT_LOG(__VA_ARGS__) +#endif +#ifndef PAR_CFG_DIRECT_ERROR_LOG +#define PAR_CFG_DIRECT_ERROR_LOG(...) PAR_CFG_DIRECT_LOG(__VA_ARGS__) +#endif +#define PAR_INFO_PRINT(...) PAR_CFG_DIRECT_INFO_LOG(__VA_ARGS__) +#define PAR_DBG_PRINT(...) PAR_CFG_DIRECT_DEBUG_LOG(__VA_ARGS__) +#define PAR_WARN_PRINT(...) PAR_CFG_DIRECT_WARN_LOG(__VA_ARGS__) +#define PAR_ERR_PRINT(...) PAR_CFG_DIRECT_ERROR_LOG(__VA_ARGS__) #else +#define PAR_INFO_PRINT(...) \ + { \ + ; \ + } #define PAR_DBG_PRINT(...) \ { \ ; \ } +#define PAR_WARN_PRINT(...) \ + { \ + ; \ + } +#define PAR_ERR_PRINT(...) \ + { \ + ; \ + } #endif #else #if (1 == PAR_CFG_DEBUG_EN) -#define PAR_DBG_PRINT(...) PAR_PORT_LOG(__VA_ARGS__) +#define PAR_INFO_PRINT(...) PAR_PORT_LOG_INFO(__VA_ARGS__) +#define PAR_DBG_PRINT(...) PAR_PORT_LOG_DEBUG(__VA_ARGS__) +#define PAR_WARN_PRINT(...) PAR_PORT_LOG_WARN(__VA_ARGS__) +#define PAR_ERR_PRINT(...) PAR_PORT_LOG_ERROR(__VA_ARGS__) #else +#define PAR_INFO_PRINT(...) \ + { \ + ; \ + } #define PAR_DBG_PRINT(...) \ { \ ; \ } +#define PAR_WARN_PRINT(...) \ + { \ + ; \ + } +#define PAR_ERR_PRINT(...) \ + { \ + ; \ + } #endif #endif @@ -464,13 +530,6 @@ PAR_STATIC_ASSERT(par_id_hash_bits_valid, ((PAR_ID_HASH_BITS > 0u) && (PAR_ID_HA #define PAR_CFG_ENABLE_ACCESS (1) #endif -/** - * @brief Enable/Disable parameter persistence metadata. - */ -#ifndef PAR_CFG_ENABLE_PERSIST -#define PAR_CFG_ENABLE_PERSIST (1) -#endif - /** * @brief Enable/Disable description check. * @@ -487,10 +546,6 @@ PAR_STATIC_ASSERT(par_id_hash_bits_valid, ((PAR_ID_HASH_BITS > 0u) && (PAR_ID_HA #error "Parameter settings invalid: NVM requires PAR_CFG_ENABLE_ID = 1!" #endif -#if (1 == PAR_CFG_NVM_EN) && (0 == PAR_CFG_ENABLE_PERSIST) -#error "Parameter settings invalid: NVM requires PAR_CFG_ENABLE_PERSIST = 1!" -#endif - #if (0 == PAR_CFG_ENABLE_ID) && (1 == PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK) #error "Parameter settings invalid: runtime duplicate-ID diagnostics require PAR_CFG_ENABLE_ID = 1!" #endif diff --git a/src/persist/par_nvm.c b/src/persist/par_nvm.c index 26cc53e..12507a8 100644 --- a/src/persist/par_nvm.c +++ b/src/persist/par_nvm.c @@ -282,7 +282,7 @@ static par_status_t par_nvm_read_header(par_nvm_head_obj_t * const p_head_obj) if (ePAR_OK != store_status) { status = ePAR_ERROR_NVM; - PAR_DBG_PRINT("PAR_NVM: header read failed, %u", (unsigned)store_status); + PAR_ERR_PRINT("PAR_NVM: header read failed, err=%u", (unsigned)store_status); } else { @@ -325,11 +325,11 @@ static par_status_t par_nvm_write_header(const uint16_t num_of_par) if (ePAR_OK != store_status) { status = ePAR_ERROR_NVM; - PAR_DBG_PRINT("PAR_NVM: header write failed, %u", (unsigned)store_status); + PAR_ERR_PRINT("PAR_NVM: header write failed, err=%u", (unsigned)store_status); return status; } - PAR_DBG_PRINT("PAR_NVM: Write NVM header with %d nb. of object", num_of_par); + PAR_DBG_PRINT("PAR_NVM: writing header with obj_count=%d", num_of_par); return status; } @@ -356,18 +356,18 @@ static par_status_t par_nvm_validate_header(par_nvm_head_obj_t * const p_head_ob crc_calc = par_nvm_calc_head_crc(p_head_obj); if (crc_calc == p_head_obj->crc) { - PAR_DBG_PRINT("PAR_NVM: NVM header OK! Nb. of stored obj: %d", p_head_obj->obj_nb); + PAR_DBG_PRINT("PAR_NVM: header validated, stored_obj_count=%d", p_head_obj->obj_nb); } else { status = ePAR_ERROR_CRC; - PAR_DBG_PRINT("PAR_NVM: Header CRC corrupted!"); + PAR_WARN_PRINT("PAR_NVM: header CRC corrupted"); } } else { status = ePAR_ERROR; - PAR_DBG_PRINT("PAR_NVM: Signature corrupted!"); + PAR_WARN_PRINT("PAR_NVM: header signature corrupted"); } } @@ -657,16 +657,16 @@ static par_status_t par_nvm_load_all(const uint16_t num_of_par) err.reason = "sync-failed"; goto out; } - PAR_DBG_PRINT("PAR_NVM: appended %u new persistent slots and rewrote header count to %u", - (unsigned)new_par_cnt, - (unsigned)PAR_PERSISTENT_COMPILE_COUNT); + PAR_INFO_PRINT("PAR_NVM: appended %u new persistent slots and rewrote header count to %u", + (unsigned)new_par_cnt, + (unsigned)PAR_PERSISTENT_COMPILE_COUNT); } out: #if (1 == PAR_CFG_DEBUG_EN) - PAR_DBG_PRINT("PAR_NVM: Loading all persistent parameters with status: %s", par_get_status_str(status)); - PAR_DBG_PRINT("PAR_NVM: Nb. of stored pars in NVM: %u", (unsigned)num_of_par); - PAR_DBG_PRINT("PAR_NVM: Nb. of live persistent: %u", (unsigned)PAR_PERSISTENT_COMPILE_COUNT); + PAR_INFO_PRINT("PAR_NVM: load-all finished with status=%s", par_get_status_str(status)); + PAR_INFO_PRINT("PAR_NVM: stored parameter count in NVM=%u", (unsigned)num_of_par); + PAR_INFO_PRINT("PAR_NVM: live persistent parameter count=%u", (unsigned)PAR_PERSISTENT_COMPILE_COUNT); if (NULL != err.reason) { @@ -695,6 +695,7 @@ static par_status_t par_nvm_init_nvm(void) par_status_t status = ePAR_OK; bool is_nvm_init = false; + PAR_DBG_PRINT("PAR_NVM: resolving storage backend"); gp_store = par_store_backend_get_api(); gb_is_nvm_owner = false; @@ -703,7 +704,7 @@ static par_status_t par_nvm_init_nvm(void) (NULL == gp_store->is_init) || (NULL == gp_store->read) || (NULL == gp_store->write) || (NULL == gp_store->erase) || (NULL == gp_store->sync)) { - PAR_DBG_PRINT("PAR_NVM: No valid parameter storage backend is wired!"); + PAR_ERR_PRINT("PAR_NVM: no valid parameter storage backend is wired"); status = ePAR_ERROR_INIT; } @@ -718,13 +719,18 @@ static par_status_t par_nvm_init_nvm(void) if (ePAR_OK != store_status) { status = ePAR_ERROR_INIT; - PAR_DBG_PRINT("PAR_NVM: backend init failed, %u", (unsigned)store_status); + PAR_ERR_PRINT("PAR_NVM: backend init failed, err=%u", (unsigned)store_status); } else { gb_is_nvm_owner = true; + PAR_DBG_PRINT("PAR_NVM: backend initialized by parameter module"); } } + else if (ePAR_OK == status) + { + PAR_DBG_PRINT("PAR_NVM: reusing already-initialized storage backend"); + } return status; } @@ -768,6 +774,7 @@ par_status_t par_nvm_init(void) bool need_set_default = false; bool need_rewrite_nvm = false; + PAR_DBG_PRINT("PAR_NVM: initialization started"); status = par_nvm_init_nvm(); if (ePAR_OK != status) { @@ -779,6 +786,7 @@ par_status_t par_nvm_init(void) if (per_par_nb == 0U) { + PAR_INFO_PRINT("PAR_NVM: no persistent parameters configured"); return (par_status_t)(status | ePAR_WAR_NO_PERSISTENT); } @@ -806,14 +814,14 @@ par_status_t par_nvm_init(void) /* Step 4: classify recovery action from detected issues */ if (0U != (detect_status & ePAR_ERROR_TABLE_ID)) { - PAR_DBG_PRINT("PAR_NVM: Table-ID mismatch detected; restoring defaults and rebuilding managed NVM image."); + PAR_WARN_PRINT("PAR_NVM: table-ID mismatch detected, restoring defaults and rebuilding managed NVM image"); need_set_default = true; need_rewrite_nvm = true; } if (0U != (detect_status & ePAR_ERROR_CRC)) { - PAR_DBG_PRINT("PAR_NVM: CRC corruption detected; rebuilding NVM from defaults."); + PAR_WARN_PRINT("PAR_NVM: CRC corruption detected, rebuilding NVM from defaults"); need_set_default = true; need_rewrite_nvm = true; } @@ -824,7 +832,7 @@ par_status_t par_nvm_init(void) */ if (0U != (detect_status & ePAR_ERROR)) { - PAR_DBG_PRINT("PAR_NVM: Header/signature mismatch detected; rebuilding NVM from defaults."); + PAR_WARN_PRINT("PAR_NVM: header/signature mismatch detected, rebuilding NVM from defaults"); need_set_default = true; need_rewrite_nvm = true; } @@ -835,7 +843,7 @@ par_status_t par_nvm_init(void) */ if (0U != (detect_status & ePAR_ERROR_NVM)) { - PAR_DBG_PRINT("PAR_NVM: NVM access error detected; restoring live values to defaults without forced rewrite."); + PAR_WARN_PRINT("PAR_NVM: NVM access error detected, restoring live values to defaults without forced rewrite"); need_set_default = true; } @@ -866,6 +874,7 @@ par_status_t par_nvm_init(void) status |= ePAR_ERROR_NVM; } + PAR_INFO_PRINT("PAR_NVM: initialization finished with status=%s", par_get_status_str(status)); return status; } /** @@ -877,6 +886,7 @@ par_status_t par_nvm_deinit(void) { par_status_t status = ePAR_OK; + PAR_DBG_PRINT("PAR_NVM: deinitialization started"); if (true == gb_is_init) { if (true == gb_is_nvm_owner) @@ -885,7 +895,7 @@ par_status_t par_nvm_deinit(void) if (ePAR_OK != store_status) { status = ePAR_ERROR; - PAR_DBG_PRINT("PAR_NVM: backend deinit failed, %u", (unsigned)store_status); + PAR_ERR_PRINT("PAR_NVM: backend deinit failed, err=%u", (unsigned)store_status); } } @@ -901,6 +911,7 @@ par_status_t par_nvm_deinit(void) status = ePAR_ERROR; } + PAR_INFO_PRINT("PAR_NVM: deinitialization finished with status=%s", par_get_status_str(status)); return status; } /** @@ -919,67 +930,57 @@ par_status_t par_nvm_deinit(void) */ par_status_t par_nvm_write(const par_num_t par_num, const bool nvm_sync) { + if (true != gb_is_init) + { + return ePAR_ERROR_INIT; + } + + if (par_num >= ePAR_NUM_OF) + { + return ePAR_ERROR; + } + + const par_cfg_t * const par_cfg = par_get_config(par_num); + if (true != par_cfg->persistent) + { + PAR_DBG_PRINT("PAR_NVM: skip write for non-persistent parameter, par_num=%u", (unsigned)par_num); + return ePAR_ERROR; + } + par_status_t status = ePAR_OK; par_nvm_data_obj_t obj_data = { 0 }; uint32_t par_addr = 0UL; - - PAR_ASSERT(true == gb_is_init); - PAR_ASSERT(par_num < ePAR_NUM_OF); - - if (true == gb_is_init) + par_status_t store_status = ePAR_OK; + + PAR_DBG_PRINT("PAR_NVM: writing persistent parameter, par_num=%u id=%u", (unsigned)par_num, (unsigned)par_cfg->id); + par_get(par_num, (uint32_t *)&obj_data.data); + obj_data.id = par_cfg->id; + /* size is a descriptor/check field; current fixed-slot format always stores 4. */ + obj_data.size = PAR_NVM_DATA_SLOT_SIZE; + obj_data.crc = par_nvm_calc_obj_crc(&obj_data); + par_addr = par_nvm_get_nvm_lut_addr(obj_data.id); + store_status = gp_store->write(par_addr, (uint32_t)sizeof(par_nvm_data_obj_t), (const uint8_t *)&obj_data); + if (ePAR_OK != store_status) { - if (par_num < ePAR_NUM_OF) - { - const par_cfg_t * const par_cfg = par_get_config(par_num); - if (true == par_cfg->persistent) - { - par_status_t store_status = ePAR_OK; - - par_get(par_num, (uint32_t *)&obj_data.data); - obj_data.id = par_cfg->id; - /* size is a descriptor/check field; current fixed-slot format always stores 4. */ - obj_data.size = PAR_NVM_DATA_SLOT_SIZE; - obj_data.crc = par_nvm_calc_obj_crc(&obj_data); - par_addr = par_nvm_get_nvm_lut_addr(obj_data.id); - store_status = gp_store->write(par_addr, - (uint32_t)sizeof(par_nvm_data_obj_t), - (const uint8_t *)&obj_data); - if (ePAR_OK != store_status) - { - status |= ePAR_ERROR_NVM; - PAR_DBG_PRINT("PAR_NVM: parameter write failed, par_num=%u id=%u addr=0x%08lX err=%u", - (unsigned)par_num, - (unsigned)obj_data.id, - (unsigned long)par_addr, - (unsigned)store_status); - } + status |= ePAR_ERROR_NVM; + PAR_ERR_PRINT("PAR_NVM: parameter write failed, par_num=%u id=%u addr=0x%08lX err=%u", + (unsigned)par_num, + (unsigned)obj_data.id, + (unsigned long)par_addr, + (unsigned)store_status); + } - if ((true == nvm_sync) && (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK))) - { - const par_status_t sync_status = gp_store->sync(); - if (ePAR_OK != sync_status) - { - status |= ePAR_ERROR_NVM; - PAR_DBG_PRINT("PAR_NVM: sync failed after parameter write, par_num=%u err=%u", - (unsigned)par_num, - (unsigned)sync_status); - } - } - } - else - { - status = ePAR_ERROR; - } - } - else + if ((true == nvm_sync) && (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK))) + { + const par_status_t sync_status = gp_store->sync(); + if (ePAR_OK != sync_status) { - status = ePAR_ERROR; + status |= ePAR_ERROR_NVM; + PAR_ERR_PRINT("PAR_NVM: sync failed after parameter write, par_num=%u err=%u", + (unsigned)par_num, + (unsigned)sync_status); } } - else - { - status = ePAR_ERROR_INIT; - } return status; } @@ -990,64 +991,59 @@ par_status_t par_nvm_write(const par_num_t par_num, const bool nvm_sync) */ par_status_t par_nvm_write_all(void) { - par_status_t status = ePAR_OK; + if (true != gb_is_init) + { + return ePAR_ERROR_INIT; + } - PAR_ASSERT(true == gb_is_init); + par_status_t status = ePAR_OK; + PAR_DBG_PRINT("PAR_NVM: storing all persistent parameters to NVM"); - if (true == gb_is_init) + /* Mark the header invalid before bulk rewrite and commit that state. */ + const par_status_t store_status = gp_store->erase(PAR_NVM_HEAD_SIGN_ADDR, PAR_NVM_HEAD_SIGN_SIZE); + if (ePAR_OK != store_status) { - /* Mark the header invalid before bulk rewrite and commit that state. */ - { - const par_status_t store_status = gp_store->erase(PAR_NVM_HEAD_SIGN_ADDR, PAR_NVM_HEAD_SIGN_SIZE); - if (ePAR_OK != store_status) - { - status |= ePAR_ERROR_NVM; - PAR_DBG_PRINT("PAR_NVM: signature erase failed, %u", (unsigned)store_status); - } - } + status |= ePAR_ERROR_NVM; + PAR_ERR_PRINT("PAR_NVM: signature erase failed, err=%u", (unsigned)store_status); + } - if (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK)) + if (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK)) + { + for (par_num_t par_num = 0U; par_num < ePAR_NUM_OF; par_num++) { - for (par_num_t par_num = 0U; par_num < ePAR_NUM_OF; par_num++) + if (true == par_is_persistent(par_num)) { - if (true == par_is_persistent(par_num)) + status |= par_nvm_write(par_num, false); + if (ePAR_OK != (status & ePAR_STATUS_ERROR_MASK)) { - status |= par_nvm_write(par_num, false); - if (ePAR_OK != (status & ePAR_STATUS_ERROR_MASK)) - { - PAR_DBG_PRINT("PAR_NVM: bulk write aborted, par_num=%u id=%u addr=0x%08lX err=%u", - (unsigned)par_num, - (unsigned)par_get_config(par_num)->id, - (unsigned long)par_nvm_get_nvm_lut_addr(par_get_config(par_num)->id), - (unsigned)status); - break; - } + PAR_ERR_PRINT("PAR_NVM: bulk write aborted, par_num=%u id=%u addr=0x%08lX err=%u", + (unsigned)par_num, + (unsigned)par_get_config(par_num)->id, + (unsigned long)par_nvm_get_nvm_lut_addr(par_get_config(par_num)->id), + (unsigned)status); + break; } } } + } - /* Restore a valid header only after the full rewrite completes successfully. */ - if (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK)) - { - status |= par_nvm_write_header(PAR_PERSISTENT_COMPILE_COUNT); - } - - if (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK)) - { - par_nvm_build_new_nvm_lut(); - } - else - { - par_nvm_clear_lut(); - } + /* Restore a valid header only after the full rewrite completes successfully. */ + if (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK)) + { + status |= par_nvm_write_header(PAR_PERSISTENT_COMPILE_COUNT); + } - PAR_DBG_PRINT("PAR_NVM: Storing all to NVM status: %s", par_get_status_str(status)); + if (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK)) + { + par_nvm_build_new_nvm_lut(); } else { - status = ePAR_ERROR_INIT; + par_nvm_clear_lut(); } + PAR_INFO_PRINT("PAR_NVM: store-all finished with status=%s", par_get_status_str(status)); + return status; } /** @@ -1061,14 +1057,21 @@ par_status_t par_nvm_reset_all(void) par_status_t status = ePAR_OK; PAR_ASSERT(true == gb_is_init); + PAR_DBG_PRINT("PAR_NVM: rebuild-all requested"); - if (true == gb_is_init) + if (true != gb_is_init) { - status |= par_nvm_write_all(); + return ePAR_ERROR_INIT; + } + + status |= par_nvm_write_all(); + if (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK)) + { + PAR_DBG_PRINT("PAR_NVM: rebuild-all finished successfully"); } else { - status = ePAR_ERROR_INIT; + PAR_ERR_PRINT("PAR_NVM: rebuild-all failed, status=%s", par_get_status_str(status)); } return status; diff --git a/src/persist/par_nvm_table_id.c b/src/persist/par_nvm_table_id.c index efcb76f..571414e 100644 --- a/src/persist/par_nvm_table_id.c +++ b/src/persist/par_nvm_table_id.c @@ -70,7 +70,7 @@ uint32_t par_nvm_table_id_calc(void) const par_cfg_t * const p_cfg = par_get_config(par_num); const uint8_t type = (uint8_t)p_cfg->type; -#if (1 == PAR_CFG_ENABLE_PERSIST) +#if (1 == PAR_CFG_NVM_EN) if (false == p_cfg->persistent) { continue; From a492ae8f451010a6e4903b074a7120fcbce6b2e2 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Fri, 10 Apr 2026 13:52:44 +0800 Subject: [PATCH 31/36] feat[NVM]: add selectable persistent record layouts add packaged backend and persisted record-layout selection in Kconfig split layout-specific serialization logic out of par_nvm.c into dedicated implementations include selected record layout in table-id compatibility checks and update related docs --- README.md | 12 +- docs/api-reference.md | 7 +- docs/architecture.md | 10 +- docs/getting-started.md | 10 +- src/par_cfg.h | 24 +- src/persist/par_nvm.c | 165 ++++----- src/persist/par_nvm_layout.h | 77 ++++ src/persist/par_nvm_layout_compact_payload.c | 337 ++++++++++++++++++ .../par_nvm_layout_fixed_slot_no_size.c | 143 ++++++++ .../par_nvm_layout_fixed_slot_with_size.c | 139 ++++++++ src/persist/par_nvm_table_id.c | 10 +- src/persist/par_nvm_table_id.h | 1 + template/par_cfg_port.htmp | 3 + 13 files changed, 818 insertions(+), 120 deletions(-) create mode 100644 src/persist/par_nvm_layout.h create mode 100644 src/persist/par_nvm_layout_compact_payload.c create mode 100644 src/persist/par_nvm_layout_fixed_slot_no_size.c create mode 100644 src/persist/par_nvm_layout_fixed_slot_with_size.c diff --git a/README.md b/README.md index 1736d23..6fbaade 100644 --- a/README.md +++ b/README.md @@ -138,14 +138,16 @@ This repository contains the reusable module core and templates. A real integrat - The module separates **internal parameter enumeration** (`par_num_t`) from **external parameter IDs** (`id`). - The current ID lookup implementation uses a one-entry-per-bucket hash map generated at compile time from `par_table.def`. External IDs must therefore be not only unique, but also collision-free under the configured hash geometry. Optional runtime diagnostic scans can be enabled with `PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK` and `PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK` when additional startup logs are useful. See `docs/architecture.md` for the collision rule and avoidance guidance. - Unchecked setter APIs skip runtime validation callbacks and on-change callbacks, so they should be reserved for tightly controlled hot paths. Bitwise fast setters are further restricted to `U8` / `U16` / `U32` flags or bitmask parameters. Legacy `*_fast()` names remain as deprecated aliases. -- NVM support is optional. When enabled, `src/persist/par_nvm.c` depends on a mounted storage backend interface and on ID support being enabled. Persistence metadata is compiled in automatically under `PAR_CFG_NVM_EN`. The package can build the `GeneralEmbeddedCLibraries/nvm` adapter from `src/persist/backend/`, or the application can provide its own `par_store_backend_get_api()` implementation. -- Live RAM layout and persisted NVM layout are intentionally different. RAM storage is grouped by value width, while the persistence area is a linear list of fixed 8-byte objects: `id(2) + size(1) + crc8(1) + data(4)`. -- Compile-time persistent order is the primary slot layout contract of the managed NVM image. The stored `id` remains in each record as an integrity and diagnostics field, and the current `size` field is still always written as `4`, so it is mainly a descriptor/integrity helper rather than a space-saving variable-width encoding. -- The serialized NVM header is written explicitly as a fixed 12-byte storage image (`sign(4) + obj_nb(2) + table_id(4) + crc16(2)`), so on-storage layout does not depend on compiler struct padding. Header CRC-16 covers the serialized `obj_nb + table_id` bytes, while each data record carries its own CRC-8 over `id + size + data`. +- NVM support is optional. When enabled, `src/persist/par_nvm.c` depends on a mounted storage backend interface and on ID support being enabled. Persistence metadata is compiled in automatically under `PAR_CFG_NVM_EN`. The package builds one packaged backend adapter selected from Kconfig. The RT-Thread AT24CXX backend is available today, and the generic flash backend entry is reserved as a placeholder for later implementation. +- Live RAM layout and persisted NVM layout are intentionally different. RAM storage is grouped by value width, while the persistence area stores a compile-time ordered slot list using one selected serialized record layout: fixed 4-byte payload slot with size descriptor, fixed 4-byte payload slot without size descriptor, or compact natural-width payload with size descriptor. +- Compile-time persistent order is the primary slot layout contract of the managed NVM image. The stored `id` remains in each record as an integrity and diagnostics field. The fixed-slot layouts keep one 4-byte payload slot per persistent parameter, while the compact-payload layout stores only the natural 1/2/4-byte payload width. +- The serialized NVM header is written explicitly as a fixed 12-byte storage image (`sign(4) + obj_nb(2) + table_id(4) + crc16(2)`), so on-storage layout does not depend on compiler struct padding. Header CRC-16 covers the serialized `obj_nb + table_id` bytes, while each data record carries its own CRC-8 according to the selected record layout. - CRC calculation is routed through port hooks with bundled software defaults. In this single-target profile the persisted image and the table-ID digest both use the native byte order of the running platform, so no additional byte-order conversion hook is required by the persistence path. -- When `PAR_CFG_TABLE_ID_CHECK_EN = 1`, startup compares the stored table-ID against the live table-ID to validate persisted-image compatibility. The table-ID covers `PAR_CFG_TABLE_ID_SCHEMA_VER`, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID, hashed as platform-native scalar bytes under the single-target profile. It intentionally does not cover defaults, ranges, names, units, descriptions, or access flags. +- When `PAR_CFG_TABLE_ID_CHECK_EN = 1`, startup compares the stored table-ID against the live table-ID to validate persisted-image compatibility. The table-ID covers `PAR_CFG_TABLE_ID_SCHEMA_VER`, the selected persisted record layout, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID, hashed as platform-native scalar bytes under the single-target profile. It intentionally does not cover defaults, ranges, names, units, descriptions, or access flags. - A table-ID mismatch is treated as an incompatible persisted-layout change, not as a warning-only condition. Startup restores defaults and rebuilds the managed NVM image. Typical triggers are add/remove/reorder/type/ID changes of persistent parameters and transitions between persistent and non-persistent state. A stored header count smaller than the compile-time persistent count is repaired by appending the missing tail slots from current defaults and rewriting the header count; a stored count larger than the compile-time count is treated as incompatible and rebuilt. - `PAR_CFG_TABLE_ID_SCHEMA_VER` defaults in `src/par_cfg.h` and may be overridden in `port/par_cfg_port.h`; the integrator should bump it when intentionally changing the serialized table-ID schema. +- The fixed 4-byte payload slot without size descriptor is not available when a flash backend requires 8-byte aligned writes, because that layout serializes each record to 7 bytes. +- The package intentionally keeps a compile-time ordered slot image instead of introducing a free-layout scanned log. That keeps the address model deterministic and avoids boot-time scan, compaction, and tombstone handling in the common parameter path. - `par_init()` applies startup default values directly to live storage. Integer default values from `par_table.def` are compiled into a grouped width-based storage object, while `F32` default values are applied to the 32-bit storage group after layout offsets are available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. Because this startup initialization does not go through the public setter path, it does not invoke runtime validation or on-change callbacks. - `PAR_CFG_ENABLE_RESET_ALL_RAW` controls whether raw reset-all support and grouped default mirror snapshot support are enabled. diff --git a/docs/api-reference.md b/docs/api-reference.md index b3c87dc..c383904 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -183,9 +183,9 @@ These APIs do not follow the same runtime usage pattern as the value access APIs Available only when `PAR_CFG_NVM_EN = 1` and a concrete parameter-storage backend is linked. -The managed NVM payload area is a linear list of fixed 8-byte objects, not a width-group partition like the live RAM layout. Each object stores `id(2) + size(1) + crc8(1) + data(4)`. Slot order is derived directly from the compile-time persistent order in `par_table.def`, so the loader restores slot `i` to compile-time persistent slot `i`. If the stored count is larger than the current compile-time persistent count, startup treats the image as incompatible and the managed NVM area is rebuilt from live defaults. If the stored count is smaller, the stored prefix is restored and the missing tail slots are appended from current defaults before the header count is rewritten. The stored `id` is still kept inside each object as a validation and diagnostics field, but it is no longer the primary lookup key during startup repair. The current implementation still writes `size = 4` for all persisted parameters, so that field is primarily descriptive and used in the integrity model rather than for NVM compaction. +The managed NVM payload area is a compile-time ordered slot list, not a width-group partition like the live RAM layout. Each slot uses the selected serialized record layout: fixed 4-byte payload slot with size descriptor, fixed 4-byte payload slot without size descriptor, or compact natural-width payload with size descriptor. Slot order is derived directly from the compile-time persistent order in `par_table.def`, so the loader restores slot `i` to compile-time persistent slot `i`. If the stored count is larger than the current compile-time persistent count, startup treats the image as incompatible and the managed NVM area is rebuilt from live defaults. If the stored count is smaller, the stored prefix is restored and the missing tail slots are appended from current defaults before the header count is rewritten. The stored `id` is still kept inside each object as a validation and diagnostics field, but it is no longer the primary lookup key during startup repair. -The serialized NVM header is emitted explicitly as a fixed 12-byte image (`sign + obj_nb + table_id + crc16`) rather than by writing raw struct memory. Header CRC-16 covers the serialized native-order `obj_nb + table_id` bytes, so corrupted header bytes fail header validation before any compatibility decision is made. Each small data record uses CRC-8 over its serialized native-order `id + size + data` bytes. When `PAR_CFG_TABLE_ID_CHECK_EN = 1`, `par_nvm_init()` compares the stored table-ID directly against the live table digest. The table-ID covers `PAR_CFG_TABLE_ID_SCHEMA_VER`, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID for parameters that are actually persisted, hashed as native platform scalar bytes. Startup first validates header CRC/signature, then validates the table-ID, then loads payload objects only if both checks pass. If the stored count is smaller than the compile-time persistent count, the loader restores the stored prefix, appends the missing tail slots from live defaults, and rewrites the header count. If the stored count is larger than the compile-time persistent count, the image is treated as incompatible and rebuilt. The collected error bits then drive a centralized recovery flow: NVM access errors restore live RAM values to defaults, while table-ID/header/CRC mismatches restore defaults and rebuild the managed NVM image. This compatibility boundary explicitly covers add/remove/reorder/type/ID changes of persistent parameters and transitions between persistent and non-persistent state. CRC calculation is exposed through port hooks with bundled software defaults. +The serialized NVM header is emitted explicitly as a fixed 12-byte image (`sign + obj_nb + table_id + crc16`) rather than by writing raw struct memory. Header CRC-16 covers the serialized native-order `obj_nb + table_id` bytes, so corrupted header bytes fail header validation before any compatibility decision is made. Each small data record uses CRC-8 according to the selected record layout. When `PAR_CFG_TABLE_ID_CHECK_EN = 1`, `par_nvm_init()` compares the stored table-ID directly against the live table digest. The table-ID covers `PAR_CFG_TABLE_ID_SCHEMA_VER`, the selected persisted record layout, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID for parameters that are actually persisted, hashed as native platform scalar bytes. Startup first validates header CRC/signature, then validates the table-ID, then loads payload objects only if both checks pass. If the stored count is smaller than the compile-time persistent count, the loader restores the stored prefix, appends the missing tail slots from live defaults, and rewrites the header count. If the stored count is larger than the compile-time persistent count, the image is treated as incompatible and rebuilt. The collected error bits then drive a centralized recovery flow: NVM access errors restore live RAM values to defaults, while table-ID/header/CRC mismatches restore defaults and rebuild the managed NVM image. This compatibility boundary explicitly covers add/remove/reorder/type/ID changes of persistent parameters, record-layout changes, and transitions between persistent and non-persistent state. CRC calculation is exposed through port hooks with bundled software defaults. | Function | Description | | --- | --- | @@ -271,3 +271,6 @@ Common values include: - `ePAR_WAR_NVM_REWRITTEN` - `ePAR_WAR_NO_PERSISTENT` - `ePAR_WAR_LIMITED` + + +The fixed 4-byte payload slot without a size descriptor cannot be used on a flash backend that requires 8-byte aligned writes, because the serialized record width is 7 bytes. diff --git a/docs/architecture.md b/docs/architecture.md index 121ecad..3930eb3 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -320,15 +320,17 @@ When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`, `par_set_all_to_default()` also uses th When `PAR_CFG_NVM_EN = 1`, the module can persist selected parameters to NVM. -NVM persistence uses a fixed-size record format that is intentionally different from the live RAM layout. Live RAM storage is grouped by value width (`U8` / `U16` / `U32` / optional `F32`) for efficient access and raw reset, while the managed NVM area is a dense linear list of fixed 8-byte objects. Each persisted object stores: external parameter ID (2 bytes), payload-size descriptor (1 byte), per-record CRC-8 (1 byte), and one fixed 4-byte payload slot. +NVM persistence uses a compile-time selected record layout that is intentionally different from the live RAM layout. Live RAM storage is grouped by value width (`U8` / `U16` / `U32` / optional `F32`) for efficient access and raw reset, while the managed NVM area stores a compile-time ordered slot list. The selected slot serializer can keep a fixed 4-byte payload slot with a size descriptor, keep the same fixed 4-byte payload slot without the size descriptor, or store the natural payload width together with the size descriptor. -The compile-time persistent order is the primary layout contract of the managed NVM image. Loader and rewrite paths restore slot `i` into compile-time persistent slot `i`, while the stored `id` remains inside each object as an integrity and diagnostics field rather than as the main startup lookup key. Count asymmetry is handled intentionally: a stored count larger than the live compile-time persistent count is treated as an incompatible image and triggers full rebuild, while a stored count smaller than the live count restores the common prefix first and then appends newly introduced tail slots from current defaults. The `size` field is currently written as `4` for all persisted parameters. In other words, the current implementation does not save NVM space for `U8`/`U16`; `size` is kept mainly as a descriptor and integrity-assistance field for the fixed-slot format. +The compile-time persistent order is the primary layout contract of the managed NVM image. Loader and rewrite paths restore slot `i` into compile-time persistent slot `i`, while the stored `id` remains inside each object as an integrity and diagnostics field rather than as the main startup lookup key. Count asymmetry is handled intentionally: a stored count larger than the live compile-time persistent count is treated as an incompatible image and triggers full rebuild, while a stored count smaller than the live count restores the common prefix first and then appends newly introduced tail slots from current defaults. The fixed-slot layouts keep one 4-byte payload slot per persistent parameter. The compact-payload layout stores only the natural 1/2/4-byte payload width and therefore saves bytes for narrow types. The fixed-slot layout without a size descriptor is intentionally not offered when a flash backend requires 8-byte aligned writes, because its serialized record width is 7 bytes. -The serialized NVM header is a fixed 12-byte storage image (`sign + obj_nb + table_id + crc16`) emitted explicitly instead of by writing raw struct memory. The header CRC covers the serialized native-order `obj_nb + table_id` bytes, so header corruption is detected separately from a valid header that simply belongs to another parameter-table schema. Each data object uses CRC-8 over its serialized native-order `id + size + data` bytes. This profile intentionally ties the persisted image to the current target architecture and does not normalize byte order for cross-platform migration. CRC calculation is routed through port hooks with bundled software defaults. The table-ID digest follows the same rule and hashes each scalar exactly as represented in native platform memory. The digest covers only metadata that changes binary compatibility of persisted records: `PAR_CFG_TABLE_ID_SCHEMA_VER`, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID. Default values, ranges, names, units, descriptions, and access flags are intentionally excluded because they do not change the serialized NVM object layout used by `par_nvm.c`. When table-ID checking is enabled, changes such as add/remove/reorder/type/ID updates of persistent parameters and persistent<->non-persistent transitions are treated as incompatible schema changes and the managed NVM image is rebuilt from defaults at startup. +The serialized NVM header is a fixed 12-byte storage image (`sign + obj_nb + table_id + crc16`) emitted explicitly instead of by writing raw struct memory. The header CRC covers the serialized native-order `obj_nb + table_id` bytes, so header corruption is detected separately from a valid header that simply belongs to another parameter-table schema. Each data object uses CRC-8 according to the selected record layout. This profile intentionally ties the persisted image to the current target architecture and does not normalize byte order for cross-platform migration. CRC calculation is routed through port hooks with bundled software defaults. The table-ID digest follows the same rule and hashes each scalar exactly as represented in native platform memory. The digest covers only metadata that changes binary compatibility of persisted records: `PAR_CFG_TABLE_ID_SCHEMA_VER`, selected persisted record layout, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID. Default values, ranges, names, units, descriptions, and access flags are intentionally excluded because they do not change the serialized NVM object layout used by `par_nvm.c`. When table-ID checking is enabled, changes such as add/remove/reorder/type/ID updates of persistent parameters, record-layout changes, and persistent<->non-persistent transitions are treated as incompatible schema changes and the managed NVM image is rebuilt from defaults at startup. If the stored table-ID mismatches the live table-ID, the module treats the persisted image as incompatible and rebuilds the managed NVM area from current default values and the current schema. This can happen after an intentional schema/version bump, after parameter persistence layout changes, after stored-image corruption, or when the stored header count is larger than the current compile-time persistent count. If the stored count is smaller than the compile-time count, startup restores the stored prefix, appends the missing tail slots from live defaults, and rewrites the header count to the current schema width. The recovery action is centralized in `par_nvm_init()`: it accumulates status bits from header validation, table-ID validation, and payload loading, then decides whether to restore defaults only or restore defaults plus rewrite the managed NVM image. -For this feature, `par_nvm.c` mounts a parameter-storage backend interface during initialization. The packaged `GeneralEmbeddedCLibraries/nvm` adapter is one option, but the core no longer depends directly on a specific NVM repository layout. +For this feature, `par_nvm.c` mounts a parameter-storage backend interface during initialization. The core no longer depends directly on a specific repository layout, while Kconfig selects which packaged backend adapter is built. + +The module intentionally keeps a compile-time ordered slot image instead of introducing a free-layout scanned log. A scanned log would make layout recovery more flexible, but it would also add boot-time scan, compaction, and tombstone handling to a path whose slot order is already fixed by the parameter table. ## Portability model diff --git a/docs/getting-started.md b/docs/getting-started.md index 5e7221a..0a56913 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -144,16 +144,16 @@ ID-based lookup is generated statically when `PAR_CFG_ENABLE_ID = 1`. Optional s - `PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK` - `PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK` -When NVM is enabled, the parameters module requires a concrete storage backend implementation. `src/persist/par_nvm.c` resolves and validates the backend API once during initialization, then uses the mounted callbacks directly for later reads, writes, erases, and sync operations. The package can build the `GeneralEmbeddedCLibraries/nvm` adapter from `parameters/src/persist/backend/`, or the application can provide `par_store_backend_get_api()` itself. The module can reuse an already-initialized backend or initialize it on demand and later deinitialize it only when it owns that initialization. Module deinit is conservative: it attempts backend and interface cleanup, and it clears the top-level module init state only after the owned child deinit steps succeed. +When NVM is enabled, the parameters module requires a concrete packaged storage backend implementation. `src/persist/par_nvm.c` resolves and validates the backend API once during initialization, then uses the mounted callbacks directly for later reads, writes, erases, and sync operations. The RT-Thread AT24CXX backend is available today, while the generic flash backend Kconfig entry is currently only a placeholder and does not build an implementation yet. The module can reuse an already-initialized backend or initialize it on demand and later deinitialize it only when it owns that initialization. Module deinit is conservative: it attempts backend and interface cleanup, and it clears the top-level module init state only after the owned child deinit steps succeed. -The persisted NVM image uses fixed 8-byte objects (`id + size + crc8 + 4-byte data slot`) instead of the grouped-width RAM layout used at runtime. Compile-time persistent order is the primary slot layout contract for startup restore, while the stored `id` remains a validation and diagnostics field inside each record. The current implementation still writes `size = 4` for every persisted parameter, so narrow values do not save NVM bytes yet. The header is serialized explicitly as a fixed 12-byte image (`sign + obj_nb + table_id + crc16`). Under the current single-target assumption, fields are stored, validated, and hashed in the native byte order of the running platform instead of being normalized to a cross-platform storage endianness. Header CRC-16 therefore covers the serialized native-order `obj_nb + table_id` bytes, each data object uses CRC-8 over native-order `id + size + data`, and the table-ID digest is computed from native-order scalar images as well. When the stored count is smaller than the compile-time persistent count, startup appends the missing tail slots from current defaults and rewrites the header count; when the stored count is larger, startup treats the image as incompatible and rebuilds it. The bundled defaults are provided through `par_if_crc16_accumulate()` and `par_if_crc8_accumulate()`. +The persisted NVM image uses a compile-time selected record layout instead of the grouped-width RAM layout used at runtime. Compile-time persistent order remains the primary slot layout contract for startup restore, while the stored `id` remains a validation and diagnostics field inside each record. The fixed-slot layouts keep one 4-byte payload slot per persistent parameter, and the compact-payload layout stores only the natural 1/2/4-byte payload width. The fixed-slot layout without a size descriptor is intentionally unavailable for a flash backend that requires 8-byte aligned writes, because that serializer uses a 7-byte record. The header is serialized explicitly as a fixed 12-byte image (`sign + obj_nb + table_id + crc16`). Under the current single-target assumption, fields are stored, validated, and hashed in the native byte order of the running platform instead of being normalized to a cross-platform storage endianness. Header CRC-16 therefore covers the serialized native-order `obj_nb + table_id` bytes, each data object uses layout-specific CRC-8, and the table-ID digest is computed from native-order scalar images including the selected record layout. When the stored count is smaller than the compile-time persistent count, startup appends the missing tail slots from current defaults and rewrites the header count; when the stored count is larger, startup treats the image as incompatible and rebuilds it. The bundled defaults are provided through `par_if_crc16_accumulate()` and `par_if_crc8_accumulate()`. Backend choices: -- enable the packaged `GeneralEmbeddedCLibraries/nvm` adapter -- provide exactly one application-owned `par_store_backend_get_api()` implementation +- enable the packaged RT-Thread AT24CXX backend +- or reserve the generic flash backend configuration entry for later implementation -If `PAR_CFG_NVM_EN = 1` and no backend implementation is linked, the build fails at link time by design. +If `PAR_CFG_NVM_EN = 1` and the selected packaged backend implementation is not linked, the build fails at link time by design. ### Logging diff --git a/src/par_cfg.h b/src/par_cfg.h index 434a957..4b7a60d 100644 --- a/src/par_cfg.h +++ b/src/par_cfg.h @@ -76,9 +76,9 @@ /** * @brief Enable/Disable parameter-table compatibility checking. * - * @note The stored NVM image header carries a table-ID digest that covers. - * PAR_CFG_TABLE_ID_SCHEMA_VER, persistent-parameter count, persistent order, - * parameter type, and parameter ID. + * @note The stored NVM image header carries a table-ID digest that covers + * PAR_CFG_TABLE_ID_SCHEMA_VER, selected record layout, persistent-parameter + * count, persistent order, parameter type, and parameter ID. * * When enabled, any persisted-layout incompatibility is treated as a managed. * schema change: startup restores defaults and rebuilds the managed NVM image. @@ -103,6 +103,24 @@ #define PAR_CFG_TABLE_ID_SCHEMA_VER (1U) #endif +#ifndef PAR_CFG_NVM_BACKEND_FLASH_EN +#define PAR_CFG_NVM_BACKEND_FLASH_EN (0) +#endif + +/** + * @brief Select persisted record layout. + * + * @note The chosen layout is also included in the table-ID digest so layout + * changes are treated as managed compatibility changes. + */ +#define PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE (0U) +#define PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE (1U) +#define PAR_CFG_NVM_RECORD_LAYOUT_COMPACT_PAYLOAD (2U) + +#ifndef PAR_CFG_NVM_RECORD_LAYOUT +#define PAR_CFG_NVM_RECORD_LAYOUT (PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE) +#endif + /** * @brief Enable/Disable debug mode. */ diff --git a/src/persist/par_nvm.c b/src/persist/par_nvm.c index 12507a8..9ec72e5 100644 --- a/src/persist/par_nvm.c +++ b/src/persist/par_nvm.c @@ -66,6 +66,7 @@ #include #include "persist/backend/par_store_backend.h" +#include "persist/par_nvm_layout.h" #include "persist/par_nvm_table_id.h" /** * @brief Parameter NVM header object. @@ -104,42 +105,17 @@ typedef struct #define PAR_NVM_FIRST_DATA_OBJ_ADDR (PAR_NVM_HEAD_ADDR + PAR_NVM_HEAD_SIZE) /** - * @brief Persisted parameter object layout (current implementation). + * @brief Persisted parameter payload view used by the common load/save flow. * - * @details Each persistent parameter is serialized as one fixed-size 8-byte - * object in the managed NVM area: + * @details + * Live RAM layout and persisted NVM layout are intentionally different. RAM + * storage is grouped by value width, while the persistence area stores a + * compile-time ordered slot list using one selected serialized record layout. * - * Byte offset: 0 1 2 3 4 5 6 7 - * +-------+-------+-------+-------+-------+-------+-------+-------+ - * Field: | id | size | crc8 | data slot | - * +-------+-------+-------+-------+-------+-------+-------+-------+ - * Width: |<---- 2 bytes ---->| 1 B | 1 B |<------ 4 bytes ------->| - * - * Meaning: - * - id : external parameter ID of this persisted entry. Its main value is - * that persisted records stay self-describing instead of depending - * only on fixed slot positions. - * - size : payload-width descriptor. The current implementation always writes - * the full 4-byte payload-slot width, so this field does not save - * NVM space yet; it is kept as a descriptor and integrity helper. - * - crc : per-record CRC-8 calculated over the serialized id/size/data bytes. - * - data : 32-bit payload slot used to store the parameter value. Even U8/U16 - * parameters still occupy the same 4-byte payload slot in NVM. - * - * @note Live RAM layout and persisted NVM layout are intentionally different. - * RAM storage is grouped by value width, while the NVM persistence area is a - * linear list of fixed 8-byte objects. + * Layout-specific serialization and validation are implemented in dedicated + * layout source files so the top-level NVM flow does not carry per-layout + * branching in every read/write path. */ -typedef struct -{ - uint16_t id; /**< Parameter ID. */ - uint8_t size; /**< Payload-width descriptor. */ - uint8_t crc; /**< CRC-8 over id, size, and 4-byte payload slot. */ - par_type_t data; /**< Fixed 4-byte payload slot for the parameter value. */ -} par_nvm_data_obj_t; - -#define PAR_NVM_DATA_OBJ_STRIDE ((uint32_t)sizeof(par_nvm_data_obj_t)) -#define PAR_NVM_DATA_SLOT_SIZE ((uint8_t)sizeof(((par_nvm_data_obj_t *)0)->data)) /** * @brief Runtime persistence-slot state. */ @@ -217,6 +193,36 @@ static const par_store_backend_api_t *gp_store = NULL; */ static par_nvm_slot_runtime_t g_par_nvm_slot_runtime = { 0 }; +/** + * @brief Calculate per-record CRC-8 over serialized record bytes. + * + * @param id Parameter ID. + * @param size_desc Serialized size descriptor. + * @param p_payload Pointer to payload bytes. + * @param payload_size Number of payload bytes. + * @param include_size_desc True when the layout includes a size field. + * @return Calculated CRC-8 value. + */ +uint8_t par_nvm_layout_calc_crc(const uint16_t id, + const uint8_t size_desc, + const uint8_t * const p_payload, + const uint8_t payload_size, + const bool include_size_desc) +{ + uint8_t crc = PAR_IF_CRC8_INIT; + + PAR_ASSERT(NULL != p_payload); + + crc = par_if_crc8_accumulate(crc, (const uint8_t * const)&id, (uint32_t)sizeof(id)); + if (include_size_desc) + { + crc = par_if_crc8_accumulate(crc, (const uint8_t * const)&size_desc, (uint32_t)sizeof(size_desc)); + } + crc = par_if_crc8_accumulate(crc, p_payload, (uint32_t)payload_size); + + return crc; +} + /** * @brief Calculate serialized-header CRC-16. * @@ -240,31 +246,6 @@ static uint16_t par_nvm_calc_head_crc(const par_nvm_head_obj_t * const p_head_ob return crc; } -/** - * @brief Calculate per-record CRC-8 over serialized id/size/data bytes. - * - * @details The record CRC is accumulated field-by-field over the serialized - * native-order bytes of id, size, and the fixed 4-byte payload slot. - * - * @param p_obj Pointer to data object. - * @return Calculated CRC-8 value. - */ -static uint8_t par_nvm_calc_obj_crc(const par_nvm_data_obj_t * const p_obj) -{ - uint32_t data_raw = 0U; - uint8_t crc = PAR_IF_CRC8_INIT; - - PAR_ASSERT(NULL != p_obj); - - memcpy(&data_raw, &p_obj->data, sizeof(data_raw)); - - crc = par_if_crc8_accumulate(crc, (const uint8_t * const)&p_obj->id, (uint32_t)sizeof(p_obj->id)); - crc = par_if_crc8_accumulate(crc, (const uint8_t * const)&p_obj->size, (uint32_t)sizeof(p_obj->size)); - crc = par_if_crc8_accumulate(crc, (const uint8_t * const)&data_raw, (uint32_t)sizeof(data_raw)); - - return crc; -} - /** * @brief Read parameter NVM header. * @@ -394,28 +375,28 @@ static par_status_t par_nvm_get_num_by_persist_idx(const uint16_t persist_idx, p /** * @brief Convert a compile-time persistent slot index into the managed NVM object address. * - * @details The managed NVM payload area is a dense linear array of fixed-size - * par_nvm_data_obj_t records. Therefore the slot address is derived directly as: - * first-data-object-address + slot-index * object-stride. + * @details The public NVM flow keeps this common entry point, while the active + * layout implementation owns the actual address calculation. * * @param persist_idx Persistent slot index. * @return Start address of the slot inside the managed NVM payload area. */ static uint32_t par_nvm_addr_from_persist_idx(const uint16_t persist_idx) { - return (PAR_NVM_FIRST_DATA_OBJ_ADDR + (PAR_NVM_DATA_OBJ_STRIDE * persist_idx)); + return par_nvm_layout_addr_from_persist_idx(PAR_NVM_FIRST_DATA_OBJ_ADDR, persist_idx, g_par_persist_slot_to_par_num); } /** * @brief Print the compiled persistent-slot map and current runtime load state. * * This function is intended for debug use only. It prints, for each compiled - * persistent slot, the slot index, parameter ID, computed NVM address, runtime - * loaded flag, and parameter name when name support is enabled. + * persistent slot, the slot index, parameter ID, computed NVM address, + * serialized record size, runtime loaded flag, and parameter name when name + * support is enabled. * - * The slot-to-parameter relationship is derived from the compile-time persistent - * mapping table, while the loaded flag reflects whether the slot was loaded - * successfully during the current NVM initialization/load flow. + * The slot-to-parameter relationship is derived from the compile-time + * persistent mapping table, while the loaded flag reflects whether the slot + * was loaded successfully during the current NVM initialization/load flow. * * @return ePAR_OK Debug information was printed. * @return ePAR_ERROR Debug print is disabled at build time. @@ -426,18 +407,20 @@ par_status_t par_nvm_print_nvm_lut(void) #if (1 == PAR_CFG_DEBUG_EN) PAR_DBG_PRINT("PAR_NVM: Parameter NVM look-up table:"); - PAR_DBG_PRINT(" #\tID\tAddr\tLoaded\tname"); - PAR_DBG_PRINT("================================"); + PAR_DBG_PRINT(" # ID Addr Size Loaded name"); + PAR_DBG_PRINT("================================================"); for (uint16_t persist_idx = 0U; persist_idx < PAR_PERSISTENT_COMPILE_COUNT; persist_idx++) { + const par_num_t par_num = g_par_persist_slot_to_par_num[persist_idx]; PAR_DBG_PRINT( - " %u\t%u\t0x%08lX\t%u\t%s", + " %u %u 0x%08lX %lu %u %s", (unsigned)persist_idx, - (unsigned)par_get_config(g_par_persist_slot_to_par_num[persist_idx])->id, + (unsigned)par_get_config(par_num)->id, (unsigned long)par_nvm_addr_from_persist_idx(persist_idx), + (unsigned long)par_nvm_layout_record_size_from_par_num(par_num), (unsigned)g_par_nvm_slot_runtime.loaded_slots[persist_idx], - PAR_NVM_DBG_NAME_ARG(par_get_config(g_par_persist_slot_to_par_num[persist_idx]))); + PAR_NVM_DBG_NAME_ARG(par_get_config(par_num))); } #else status = ePAR_ERROR; @@ -445,6 +428,7 @@ par_status_t par_nvm_print_nvm_lut(void) return status; } + /** * @brief Clear the runtime-loaded persistent-slot view. */ @@ -520,7 +504,6 @@ static par_status_t par_nvm_load_all(const uint16_t num_of_par) par_num_t par_num = 0U; uint16_t i = 0U; par_nvm_data_obj_t obj_data = { 0 }; - uint8_t crc_calc = 0U; uint16_t new_par_cnt = 0U; par_nvm_load_error_ctx_t err = { 0 }; @@ -545,30 +528,11 @@ static par_status_t par_nvm_load_all(const uint16_t num_of_par) const par_cfg_t *par_cfg = NULL; const uint32_t obj_addr = par_nvm_addr_from_persist_idx(i); - op_status = gp_store->read(obj_addr, (uint32_t)sizeof(par_nvm_data_obj_t), (uint8_t *)&obj_data); - if (ePAR_OK != op_status) - { - status = ePAR_ERROR_NVM; - err.reason = "read-failed"; - goto out; - } - - crc_calc = par_nvm_calc_obj_crc(&obj_data); - if (crc_calc != obj_data.crc) - { - status = ePAR_ERROR_CRC; - err.reason = "crc-mismatch"; - err.stored_id = obj_data.id; - op_status = status; - goto out; - } - op_status = par_nvm_get_num_by_persist_idx(i, &par_num); if (ePAR_OK != op_status) { status = ePAR_ERROR; err.reason = "persist-slot-invalid"; - err.stored_id = obj_data.id; goto out; } @@ -577,11 +541,19 @@ static par_status_t par_nvm_load_all(const uint16_t num_of_par) { status = ePAR_ERROR; err.reason = "persist-map-invalid"; - err.stored_id = obj_data.id; op_status = status; goto out; } + op_status = par_nvm_layout_read(gp_store, obj_addr, par_num, &obj_data); + if (ePAR_OK != op_status) + { + status = op_status; + err.reason = (ePAR_ERROR_CRC == op_status) ? "crc-mismatch" : "read-failed"; + err.stored_id = obj_data.id; + goto out; + } + if (obj_data.id != par_cfg->id) { status = ePAR_ERROR; @@ -953,13 +925,10 @@ par_status_t par_nvm_write(const par_num_t par_num, const bool nvm_sync) par_status_t store_status = ePAR_OK; PAR_DBG_PRINT("PAR_NVM: writing persistent parameter, par_num=%u id=%u", (unsigned)par_num, (unsigned)par_cfg->id); - par_get(par_num, (uint32_t *)&obj_data.data); + (void)par_get(par_num, &obj_data.data); obj_data.id = par_cfg->id; - /* size is a descriptor/check field; current fixed-slot format always stores 4. */ - obj_data.size = PAR_NVM_DATA_SLOT_SIZE; - obj_data.crc = par_nvm_calc_obj_crc(&obj_data); par_addr = par_nvm_get_nvm_lut_addr(obj_data.id); - store_status = gp_store->write(par_addr, (uint32_t)sizeof(par_nvm_data_obj_t), (const uint8_t *)&obj_data); + store_status = par_nvm_layout_write(gp_store, par_addr, par_num, &obj_data); if (ePAR_OK != store_status) { status |= ePAR_ERROR_NVM; diff --git a/src/persist/par_nvm_layout.h b/src/persist/par_nvm_layout.h new file mode 100644 index 0000000..539e054 --- /dev/null +++ b/src/persist/par_nvm_layout.h @@ -0,0 +1,77 @@ +/** + * @file par_nvm_layout.h + * @brief Declare private persisted-record layout interfaces. + * @author wdfk-prog () + * @version 1.0 + * @date 2026-04-06 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-06 1.0 wdfk-prog first version + */ +#ifndef _PAR_NVM_LAYOUT_H_ +#define _PAR_NVM_LAYOUT_H_ + +#include +#include + +#include "par.h" +#include "persist/backend/par_store_backend.h" + +#if (1 == PAR_CFG_NVM_EN) + +#define PAR_NVM_RECORD_ID_SIZE ((uint32_t)sizeof(uint16_t)) +#define PAR_NVM_RECORD_SIZE_FIELD_SIZE ((uint32_t)sizeof(uint8_t)) +#define PAR_NVM_RECORD_CRC_SIZE ((uint32_t)sizeof(uint8_t)) +#define PAR_NVM_RECORD_DATA_SLOT_SIZE ((uint8_t)sizeof(par_type_t)) + +#if (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE) +#define PAR_NVM_LAYOUT_OBJ_HAS_SIZE_FIELD (0) +#else +#define PAR_NVM_LAYOUT_OBJ_HAS_SIZE_FIELD (1) +#endif + +/** + * @brief Canonical payload view used by the common NVM flow. + * + * @details This type is intentionally not the same thing as every serialized + * on-storage record. It represents only the logical payload that the common + * load/save flow needs: identifier, optional layout-selected size descriptor, + * and parameter bytes. Layouts that place CRC bytes inside the serialized + * record may use a private local record type in their `.c` file. + */ +typedef struct +{ + uint16_t id; /**< Parameter ID. */ +#if (1 == PAR_NVM_LAYOUT_OBJ_HAS_SIZE_FIELD) + uint8_t size; /**< Layout-selected payload size descriptor. */ +#endif + par_type_t data; /**< Parameter value bytes in native target order. */ +} par_nvm_data_obj_t; + +uint8_t par_nvm_layout_calc_crc(const uint16_t id, + const uint8_t size_desc, + const uint8_t * const p_payload, + const uint8_t payload_size, + const bool include_size_desc); + +uint32_t par_nvm_layout_record_size_from_par_num(const par_num_t par_num); +uint32_t par_nvm_layout_addr_from_persist_idx(const uint32_t first_data_obj_addr, + const uint16_t persist_idx, + const par_num_t * const p_persist_slot_to_par_num); + +par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + par_nvm_data_obj_t * const p_obj); +par_status_t par_nvm_layout_write(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj); + +#endif /* 1 == PAR_CFG_NVM_EN */ + +#endif /* _PAR_NVM_LAYOUT_H_ */ diff --git a/src/persist/par_nvm_layout_compact_payload.c b/src/persist/par_nvm_layout_compact_payload.c new file mode 100644 index 0000000..9f86ed6 --- /dev/null +++ b/src/persist/par_nvm_layout_compact_payload.c @@ -0,0 +1,337 @@ +/** + * @file par_nvm_layout_compact_payload.c + * @brief Implement the compact persisted-record layout with natural payload width. + * @author wdfk-prog () + * @version 1.0 + * @date 2026-04-06 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @details + * This compact layout stores only the natural payload width of each parameter + * type (1/2/4 bytes). For that reason, serialization and deserialization are + * implemented through explicit type-matched local objects instead of by taking + * the first N bytes from a wider 32-bit temporary. + * + * Copying partial bytes from a uint32_t object is endianness-sensitive. + * On little-endian targets the first bytes in memory happen to contain the + * narrow U8/U16 payload, but on big-endian targets those first bytes belong + * to the high-order part of the 32-bit object representation. That would make + * narrow-value storage and CRC calculation depend on target endianness in the + * wrong way. + * + * To keep the native-endian storage model self-consistent on both little- + * endian and big-endian MCUs, this implementation packs and unpacks + * U8/I8, U16/I16, and U32/I32/F32 through explicit same-width typed objects + * and copies exactly that full object representation. + * + * @par Change Log: + * Date Version Author Description + * 2026-04-06 1.0 wdfk-prog first version + */ +#include "persist/par_nvm_layout.h" + +#if (1 == PAR_CFG_NVM_EN) && (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_COMPACT_PAYLOAD) + +#include + +#define PAR_NVM_LAYOUT_RECORD_OVERHEAD (PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_SIZE_FIELD_SIZE + PAR_NVM_RECORD_CRC_SIZE) +#define PAR_NVM_LAYOUT_RECORD_MAX_SIZE (PAR_NVM_LAYOUT_RECORD_OVERHEAD + PAR_NVM_RECORD_DATA_SLOT_SIZE) + +/** + * @brief Resolve natural payload size from parameter type. + * + * @param type Parameter type. + * @return Natural payload width in bytes. + */ +static uint8_t par_nvm_layout_payload_size_from_type(const par_type_list_t type) +{ + switch (type) + { + case ePAR_TYPE_U8: + case ePAR_TYPE_I8: + return 1U; + + case ePAR_TYPE_U16: + case ePAR_TYPE_I16: + return 2U; + + case ePAR_TYPE_U32: + case ePAR_TYPE_I32: +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + case ePAR_TYPE_F32: +#endif + return 4U; + + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + return PAR_NVM_RECORD_DATA_SLOT_SIZE; + } +} + +/** + * @brief Serialize one parameter value to native-endian payload bytes. + * + * @details Narrow integer types are serialized through explicit typed locals + * so the compact layout does not depend on the byte placement of a 32-bit + * temporary carrier object. + * + * @param type Parameter type. + * @param p_data Canonical parameter value. + * @param p_payload Output payload buffer. + */ +static void par_nvm_layout_pack_payload_bytes(const par_type_list_t type, + const par_type_t * const p_data, + uint8_t * const p_payload) +{ + PAR_ASSERT((NULL != p_data) && (NULL != p_payload)); + + switch (type) + { + case ePAR_TYPE_U8: + { + const uint8_t value = (uint8_t)(*p_data); + memcpy(p_payload, &value, sizeof(value)); + break; + } + + case ePAR_TYPE_I8: + { + const int8_t value = (int8_t)(*p_data); + memcpy(p_payload, &value, sizeof(value)); + break; + } + + case ePAR_TYPE_U16: + { + const uint16_t value = (uint16_t)(*p_data); + memcpy(p_payload, &value, sizeof(value)); + break; + } + + case ePAR_TYPE_I16: + { + const int16_t value = (int16_t)(*p_data); + memcpy(p_payload, &value, sizeof(value)); + break; + } + + case ePAR_TYPE_U32: + case ePAR_TYPE_I32: +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + case ePAR_TYPE_F32: +#endif + memcpy(p_payload, p_data, PAR_NVM_RECORD_DATA_SLOT_SIZE); + break; + + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + break; + } +} + +/** + * @brief Deserialize native-endian payload bytes into the canonical value carrier. + * + * @details Narrow integer types are reconstructed through explicit typed locals + * so the compact layout does not depend on partial writes into a 32-bit + * carrier object's storage representation. + * + * @param type Parameter type. + * @param p_payload Input payload buffer. + * @param p_data Output canonical parameter value. + */ +static void par_nvm_layout_unpack_payload_bytes(const par_type_list_t type, + const uint8_t * const p_payload, + par_type_t * const p_data) +{ + PAR_ASSERT((NULL != p_payload) && (NULL != p_data)); + + switch (type) + { + case ePAR_TYPE_U8: + { + uint8_t value = 0U; + memcpy(&value, p_payload, sizeof(value)); + *p_data = (par_type_t)value; + break; + } + + case ePAR_TYPE_I8: + { + int8_t value = 0; + memcpy(&value, p_payload, sizeof(value)); + *p_data = (par_type_t)value; + break; + } + + case ePAR_TYPE_U16: + { + uint16_t value = 0U; + memcpy(&value, p_payload, sizeof(value)); + *p_data = (par_type_t)value; + break; + } + + case ePAR_TYPE_I16: + { + int16_t value = 0; + memcpy(&value, p_payload, sizeof(value)); + *p_data = (par_type_t)value; + break; + } + + case ePAR_TYPE_U32: + case ePAR_TYPE_I32: +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + case ePAR_TYPE_F32: +#endif + memcpy(p_data, p_payload, PAR_NVM_RECORD_DATA_SLOT_SIZE); + break; + + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + break; + } +} + +/** + * @brief Resolve serialized record size from payload width. + * + * @param payload_size Payload width in bytes. + * @return Serialized compact-record size in bytes. + */ +static uint32_t par_nvm_layout_record_size_from_payload_size(const uint8_t payload_size) +{ + return (PAR_NVM_LAYOUT_RECORD_OVERHEAD + (uint32_t)payload_size); +} + +/** + * @brief Get serialized record size for one live parameter. + * + * @param par_num Live parameter number. + * @return Serialized compact-record size in bytes. + */ +uint32_t par_nvm_layout_record_size_from_par_num(const par_num_t par_num) +{ + const par_cfg_t * const p_cfg = par_get_config(par_num); + + PAR_ASSERT(NULL != p_cfg); + return par_nvm_layout_record_size_from_payload_size(par_nvm_layout_payload_size_from_type(p_cfg->type)); +} + +/** + * @brief Resolve record address from persistent slot index. + * + * @param first_data_obj_addr Start address of the first data record. + * @param persist_idx Persistent slot index. + * @param p_persist_slot_to_par_num Compile-time persistent slot map. + * @return Start address of the serialized record. + */ +uint32_t par_nvm_layout_addr_from_persist_idx(const uint32_t first_data_obj_addr, + const uint16_t persist_idx, + const par_num_t * const p_persist_slot_to_par_num) +{ + uint32_t addr = first_data_obj_addr; + + PAR_ASSERT(NULL != p_persist_slot_to_par_num); + + for (uint16_t it = 0U; it < persist_idx; it++) + { + addr += par_nvm_layout_record_size_from_par_num(p_persist_slot_to_par_num[it]); + } + + return addr; +} + +/** + * @brief Read one compact-payload record. + * + * @param p_store Mounted storage backend. + * @param addr Serialized record address. + * @param par_num Live parameter number. + * @param p_obj Output canonical payload view. + * @return Operation status. + */ +par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + par_nvm_data_obj_t * const p_obj) +{ + uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_MAX_SIZE] = { 0U }; + uint8_t size_desc = 0U; + uint8_t crc_stored = 0U; + uint8_t crc_calc = 0U; + const uint8_t * const p_payload = &record_buf[PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_SIZE_FIELD_SIZE + PAR_NVM_RECORD_CRC_SIZE]; + const par_cfg_t * const p_cfg = par_get_config(par_num); + const uint8_t expected_payload_size = par_nvm_layout_payload_size_from_type(p_cfg->type); + const uint32_t record_size = par_nvm_layout_record_size_from_payload_size(expected_payload_size); + + PAR_ASSERT(NULL != p_cfg); + + PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); + memset(p_obj, 0, sizeof(*p_obj)); + + if (ePAR_OK != p_store->read(addr, record_size, record_buf)) + { + return ePAR_ERROR_NVM; + } + + memcpy(&p_obj->id, &record_buf[0], sizeof(p_obj->id)); + size_desc = record_buf[PAR_NVM_RECORD_ID_SIZE]; + crc_stored = record_buf[PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_SIZE_FIELD_SIZE]; + + if (size_desc != expected_payload_size) + { + return ePAR_ERROR; + } + + crc_calc = par_nvm_layout_calc_crc(p_obj->id, size_desc, p_payload, size_desc, true); + if (crc_calc != crc_stored) + { + return ePAR_ERROR_CRC; + } + + p_obj->size = size_desc; + par_nvm_layout_unpack_payload_bytes(p_cfg->type, p_payload, &p_obj->data); + return ePAR_OK; +} + +/** + * @brief Write one compact-payload record. + * + * @param p_store Mounted storage backend. + * @param addr Serialized record address. + * @param par_num Live parameter number. + * @param p_obj Input canonical payload view. + * @return Operation status. + */ +par_status_t par_nvm_layout_write(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) +{ + uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_MAX_SIZE] = { 0U }; + uint8_t * const p_payload = &record_buf[PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_SIZE_FIELD_SIZE + PAR_NVM_RECORD_CRC_SIZE]; + uint8_t crc = 0U; + const par_cfg_t * const p_cfg = par_get_config(par_num); + const uint8_t payload_size = par_nvm_layout_payload_size_from_type(p_cfg->type); + const uint32_t record_size = par_nvm_layout_record_size_from_payload_size(payload_size); + + PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); + PAR_ASSERT(NULL != p_cfg); + + par_nvm_layout_pack_payload_bytes(p_cfg->type, &p_obj->data, p_payload); + crc = par_nvm_layout_calc_crc(p_obj->id, payload_size, p_payload, payload_size, true); + + memcpy(&record_buf[0], &p_obj->id, sizeof(p_obj->id)); + record_buf[PAR_NVM_RECORD_ID_SIZE] = payload_size; + record_buf[PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_SIZE_FIELD_SIZE] = crc; + + return (ePAR_OK == p_store->write(addr, record_size, record_buf)) ? ePAR_OK : ePAR_ERROR_NVM; +} + +#endif /* compact-payload */ diff --git a/src/persist/par_nvm_layout_fixed_slot_no_size.c b/src/persist/par_nvm_layout_fixed_slot_no_size.c new file mode 100644 index 0000000..c8e1a95 --- /dev/null +++ b/src/persist/par_nvm_layout_fixed_slot_no_size.c @@ -0,0 +1,143 @@ +/** + * @file par_nvm_layout_fixed_slot_no_size.c + * @brief Implement the fixed-slot persisted-record layout without a size descriptor. + * @author wdfk-prog () + * @version 1.0 + * @date 2026-04-06 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-06 1.0 wdfk-prog first version + */ +#include "persist/par_nvm_layout.h" + +#if (1 == PAR_CFG_NVM_EN) && (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE) + +#include + +#if (1 == PAR_CFG_NVM_BACKEND_FLASH_EN) +#error "PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE is not supported with the flash backend because it serializes to 7 bytes." +#endif + +/** + * @brief Serialized record size in bytes. + * + * @details This backend stores `id(2) + crc(1) + data(4)` as a 7-byte record. + * A raw C struct is intentionally not used for on-storage I/O here because the + * 7-byte wire format does not naturally match the default alignment rules of + * ordinary structs on common targets. + */ +#define PAR_NVM_LAYOUT_RECORD_SIZE (PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_CRC_SIZE + PAR_NVM_RECORD_DATA_SLOT_SIZE) + +/** + * @brief Get serialized record size for one live parameter. + * + * @param par_num Live parameter number. + * @return Fixed serialized record size in bytes. + */ +uint32_t par_nvm_layout_record_size_from_par_num(const par_num_t par_num) +{ + (void)par_num; + return PAR_NVM_LAYOUT_RECORD_SIZE; +} + +/** + * @brief Resolve record address from persistent slot index. + * + * @param first_data_obj_addr Start address of the first data record. + * @param persist_idx Persistent slot index. + * @param p_persist_slot_to_par_num Unused compile-time slot map. + * @return Start address of the serialized record. + */ +uint32_t par_nvm_layout_addr_from_persist_idx(const uint32_t first_data_obj_addr, + const uint16_t persist_idx, + const par_num_t * const p_persist_slot_to_par_num) +{ + (void)p_persist_slot_to_par_num; + return (first_data_obj_addr + ((uint32_t)persist_idx * PAR_NVM_LAYOUT_RECORD_SIZE)); +} + +/** + * @brief Read one fixed-slot-without-size record. + * + * @details Byte-buffer deserialization is used on purpose instead of direct + * struct I/O so the 7-byte on-storage format stays independent of compiler + * padding, packing pragmas, and unaligned member-access code generation. + * + * @param p_store Mounted storage backend. + * @param addr Serialized record address. + * @param par_num Unused live parameter number. + * @param p_obj Output canonical payload view. + * @return Operation status. + */ +par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + par_nvm_data_obj_t * const p_obj) +{ + uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_SIZE] = { 0U }; + uint32_t payload_raw = 0U; + uint8_t crc_stored = 0U; + uint8_t crc_calc = 0U; + + (void)par_num; + PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); + memset(p_obj, 0, sizeof(*p_obj)); + + if (ePAR_OK != p_store->read(addr, PAR_NVM_LAYOUT_RECORD_SIZE, record_buf)) + { + return ePAR_ERROR_NVM; + } + + memcpy(&p_obj->id, &record_buf[0], sizeof(p_obj->id)); + crc_stored = record_buf[PAR_NVM_RECORD_ID_SIZE]; + memcpy(&payload_raw, &record_buf[PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_CRC_SIZE], sizeof(payload_raw)); + + crc_calc = par_nvm_layout_calc_crc(p_obj->id, 0U, (const uint8_t * const)&payload_raw, PAR_NVM_RECORD_DATA_SLOT_SIZE, false); + if (crc_calc != crc_stored) + { + return ePAR_ERROR_CRC; + } + + memcpy(&p_obj->data, &payload_raw, sizeof(payload_raw)); + return ePAR_OK; +} + +/** + * @brief Write one fixed-slot-without-size record. + * + * @details Byte-buffer serialization is used on purpose instead of direct + * struct I/O so the 7-byte on-storage format stays stable across compiler + * padding and alignment choices. + * + * @param p_store Mounted storage backend. + * @param addr Serialized record address. + * @param par_num Unused live parameter number. + * @param p_obj Input canonical payload view. + * @return Operation status. + */ +par_status_t par_nvm_layout_write(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) +{ + uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_SIZE] = { 0U }; + uint32_t payload_raw = 0U; + uint8_t crc = 0U; + + (void)par_num; + PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); + memcpy(&payload_raw, &p_obj->data, sizeof(payload_raw)); + crc = par_nvm_layout_calc_crc(p_obj->id, 0U, (const uint8_t * const)&payload_raw, PAR_NVM_RECORD_DATA_SLOT_SIZE, false); + + memcpy(&record_buf[0], &p_obj->id, sizeof(p_obj->id)); + record_buf[PAR_NVM_RECORD_ID_SIZE] = crc; + memcpy(&record_buf[PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_CRC_SIZE], &payload_raw, sizeof(payload_raw)); + + return (ePAR_OK == p_store->write(addr, PAR_NVM_LAYOUT_RECORD_SIZE, record_buf)) ? ePAR_OK : ePAR_ERROR_NVM; +} + +#endif /* fixed-slot-no-size */ diff --git a/src/persist/par_nvm_layout_fixed_slot_with_size.c b/src/persist/par_nvm_layout_fixed_slot_with_size.c new file mode 100644 index 0000000..81a2409 --- /dev/null +++ b/src/persist/par_nvm_layout_fixed_slot_with_size.c @@ -0,0 +1,139 @@ +/** + * @file par_nvm_layout_fixed_slot_with_size.c + * @brief Implement the fixed-slot persisted-record layout with a size descriptor. + * @author wdfk-prog () + * @version 1.0 + * @date 2026-04-06 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-06 1.0 wdfk-prog first version + */ +#include "persist/par_nvm_layout.h" + +#if (1 == PAR_CFG_NVM_EN) && (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE) + +#include + +/** + * @brief Serialized fixed-slot record with explicit size descriptor. + * + * @details This is a private on-storage record view used only by the + * fixed-slot-with-size backend. It is kept separate from + * `par_nvm_data_obj_t` because the serialized record also contains the CRC + * byte between the size field and the data payload. + */ +typedef struct +{ + uint16_t id; /**< Parameter ID. */ + uint8_t size; /**< Serialized payload-size descriptor. */ + uint8_t crc; /**< CRC-8 over id, size, and payload bytes. */ + par_type_t data; /**< Fixed 4-byte payload slot. */ +} par_nvm_layout_record_t; + +#define PAR_NVM_LAYOUT_RECORD_SIZE ((uint32_t)sizeof(par_nvm_layout_record_t)) + +PAR_STATIC_ASSERT(par_nvm_layout_fixed_with_size_record_is_8_bytes, (sizeof(par_nvm_layout_record_t) == 8u)); + +/** + * @brief Get serialized record size for one live parameter. + * + * @param par_num Live parameter number. + * @return Fixed serialized record size in bytes. + */ +uint32_t par_nvm_layout_record_size_from_par_num(const par_num_t par_num) +{ + (void)par_num; + return PAR_NVM_LAYOUT_RECORD_SIZE; +} + +/** + * @brief Resolve record address from persistent slot index. + * + * @param first_data_obj_addr Start address of the first data record. + * @param persist_idx Persistent slot index. + * @param p_persist_slot_to_par_num Unused compile-time slot map. + * @return Start address of the serialized record. + */ +uint32_t par_nvm_layout_addr_from_persist_idx(const uint32_t first_data_obj_addr, + const uint16_t persist_idx, + const par_num_t * const p_persist_slot_to_par_num) +{ + (void)p_persist_slot_to_par_num; + return (first_data_obj_addr + ((uint32_t)persist_idx * PAR_NVM_LAYOUT_RECORD_SIZE)); +} + +/** + * @brief Read one fixed-slot-with-size record. + * + * @param p_store Mounted storage backend. + * @param addr Serialized record address. + * @param par_num Unused live parameter number. + * @param p_obj Output canonical payload view. + * @return Operation status. + */ +par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + par_nvm_data_obj_t * const p_obj) +{ + par_nvm_layout_record_t record = { 0U }; + uint8_t crc_calc = 0U; + + (void)par_num; + PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); + memset(p_obj, 0, sizeof(*p_obj)); + + if (ePAR_OK != p_store->read(addr, PAR_NVM_LAYOUT_RECORD_SIZE, (uint8_t *)&record)) + { + return ePAR_ERROR_NVM; + } + + if (record.size != PAR_NVM_RECORD_DATA_SLOT_SIZE) + { + return ePAR_ERROR; + } + + crc_calc = par_nvm_layout_calc_crc(record.id, record.size, (const uint8_t * const)&record.data, PAR_NVM_RECORD_DATA_SLOT_SIZE, true); + if (crc_calc != record.crc) + { + return ePAR_ERROR_CRC; + } + + p_obj->id = record.id; + p_obj->size = PAR_NVM_RECORD_DATA_SLOT_SIZE; + p_obj->data = record.data; + return ePAR_OK; +} + +/** + * @brief Write one fixed-slot-with-size record. + * + * @param p_store Mounted storage backend. + * @param addr Serialized record address. + * @param par_num Unused live parameter number. + * @param p_obj Input canonical payload view. + * @return Operation status. + */ +par_status_t par_nvm_layout_write(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) +{ + par_nvm_layout_record_t record = { 0U }; + + (void)par_num; + PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); + + record.id = p_obj->id; + record.size = PAR_NVM_RECORD_DATA_SLOT_SIZE; + record.data = p_obj->data; + record.crc = par_nvm_layout_calc_crc(record.id, record.size, (const uint8_t * const)&record.data, PAR_NVM_RECORD_DATA_SLOT_SIZE, true); + + return (ePAR_OK == p_store->write(addr, PAR_NVM_LAYOUT_RECORD_SIZE, (const uint8_t *)&record)) ? ePAR_OK : ePAR_ERROR_NVM; +} + +#endif /* fixed-slot-with-size */ diff --git a/src/persist/par_nvm_table_id.c b/src/persist/par_nvm_table_id.c index 571414e..5834fda 100644 --- a/src/persist/par_nvm_table_id.c +++ b/src/persist/par_nvm_table_id.c @@ -49,8 +49,9 @@ static void par_nvm_table_id_hash_update(Fnv32_t * const p_hval, * @brief Calculate live parameter-table ID. * * @details The digest covers only metadata that affects the NVM storage - * compatibility of persisted parameters: schema version, persisted-parameter - * count, persisted-parameter order, type, and optional ID field. Under the + * compatibility of persisted parameters: schema version, selected persisted + * record layout, persisted-parameter count, persisted-parameter order, type, + * and optional ID field. Under the * single-target native-endian profile, each scalar is hashed exactly as it is * represented in memory on the running platform. */ @@ -59,10 +60,13 @@ uint32_t par_nvm_table_id_calc(void) Fnv32_t hval = FNV1_32A_INIT; uint32_t serialized_size = 0U; const uint32_t schema_version = (uint32_t)PAR_CFG_TABLE_ID_SCHEMA_VER; + const uint32_t record_layout = (uint32_t)PAR_CFG_NVM_RECORD_LAYOUT; const uint16_t persistent_count = (uint16_t)PAR_PERSISTENT_COMPILE_COUNT; - const uint32_t expected_size = (uint32_t)sizeof(schema_version) + (uint32_t)sizeof(persistent_count) + ((uint32_t)persistent_count * (uint32_t)PAR_NVM_TABLE_ID_REC_SIZE); + const uint32_t expected_size = (uint32_t)sizeof(schema_version) + (uint32_t)sizeof(record_layout) + + (uint32_t)sizeof(persistent_count) + ((uint32_t)persistent_count * (uint32_t)PAR_NVM_TABLE_ID_REC_SIZE); par_nvm_table_id_hash_update(&hval, &serialized_size, &schema_version, (uint32_t)sizeof(schema_version)); + par_nvm_table_id_hash_update(&hval, &serialized_size, &record_layout, (uint32_t)sizeof(record_layout)); par_nvm_table_id_hash_update(&hval, &serialized_size, &persistent_count, (uint32_t)sizeof(persistent_count)); for (par_num_t par_num = 0U; par_num < ePAR_NUM_OF; par_num++) diff --git a/src/persist/par_nvm_table_id.h b/src/persist/par_nvm_table_id.h index 27a5dea..ff2e376 100644 --- a/src/persist/par_nvm_table_id.h +++ b/src/persist/par_nvm_table_id.h @@ -22,6 +22,7 @@ * @details The digest covers only metadata that changes the binary * compatibility of the persisted NVM image: * - PAR_CFG_TABLE_ID_SCHEMA_VER + * - selected persisted record layout * - persistent-parameter count * - persistent-parameter order * - parameter type diff --git a/template/par_cfg_port.htmp b/template/par_cfg_port.htmp index a505216..1e9dbad 100644 --- a/template/par_cfg_port.htmp +++ b/template/par_cfg_port.htmp @@ -3,4 +3,7 @@ /* Optional platform overrides */ /* #define PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK ( 0 ) */ /* #define PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK ( 0 ) */ +/* #define PAR_CFG_NVM_RECORD_LAYOUT ( PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE ) */ +/* or PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE */ +/* or PAR_CFG_NVM_RECORD_LAYOUT_COMPACT_PAYLOAD */ #endif \ No newline at end of file From 1cce92c02b224055adf55fcb31ac17eac4dc2a55 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Mon, 13 Apr 2026 08:32:15 +0800 Subject: [PATCH 32/36] feat[autogen_parameter_manager][nvm]: add payload-only persistent layouts add fixed and grouped payload-only NVM record layouts relax the NVM dependency on runtime parameter IDs and move compatibility checks to stored-prefix based table-id calculation update Kconfig, build selection, persistence flow, and documentation to cover no-ID layouts, prefix append handling, and backend constraints --- README.md | 10 +- docs/api-reference.md | 4 +- docs/architecture.md | 8 +- docs/getting-started.md | 2 +- src/def/par_def.c | 35 ++ src/def/par_def.h | 11 + src/par_cfg.h | 64 ++- src/persist/par_nvm.c | 389 ++++++++++++++++-- src/persist/par_nvm_layout.h | 133 +++++- src/persist/par_nvm_layout_compact_payload.c | 251 ++--------- .../par_nvm_layout_fixed_payload_only.c | 158 +++++++ .../par_nvm_layout_fixed_slot_no_size.c | 79 ++-- .../par_nvm_layout_fixed_slot_with_size.c | 72 ++-- .../par_nvm_layout_grouped_payload_only.c | 201 +++++++++ src/persist/par_nvm_table_id.c | 63 +-- src/persist/par_nvm_table_id.h | 22 +- template/par_cfg_port.htmp | 4 +- 17 files changed, 1095 insertions(+), 411 deletions(-) create mode 100644 src/persist/par_nvm_layout_fixed_payload_only.c create mode 100644 src/persist/par_nvm_layout_grouped_payload_only.c diff --git a/README.md b/README.md index 6fbaade..a38c0ff 100644 --- a/README.md +++ b/README.md @@ -138,13 +138,13 @@ This repository contains the reusable module core and templates. A real integrat - The module separates **internal parameter enumeration** (`par_num_t`) from **external parameter IDs** (`id`). - The current ID lookup implementation uses a one-entry-per-bucket hash map generated at compile time from `par_table.def`. External IDs must therefore be not only unique, but also collision-free under the configured hash geometry. Optional runtime diagnostic scans can be enabled with `PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK` and `PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK` when additional startup logs are useful. See `docs/architecture.md` for the collision rule and avoidance guidance. - Unchecked setter APIs skip runtime validation callbacks and on-change callbacks, so they should be reserved for tightly controlled hot paths. Bitwise fast setters are further restricted to `U8` / `U16` / `U32` flags or bitmask parameters. Legacy `*_fast()` names remain as deprecated aliases. -- NVM support is optional. When enabled, `src/persist/par_nvm.c` depends on a mounted storage backend interface and on ID support being enabled. Persistence metadata is compiled in automatically under `PAR_CFG_NVM_EN`. The package builds one packaged backend adapter selected from Kconfig. The RT-Thread AT24CXX backend is available today, and the generic flash backend entry is reserved as a placeholder for later implementation. -- Live RAM layout and persisted NVM layout are intentionally different. RAM storage is grouped by value width, while the persistence area stores a compile-time ordered slot list using one selected serialized record layout: fixed 4-byte payload slot with size descriptor, fixed 4-byte payload slot without size descriptor, or compact natural-width payload with size descriptor. -- Compile-time persistent order is the primary slot layout contract of the managed NVM image. The stored `id` remains in each record as an integrity and diagnostics field. The fixed-slot layouts keep one 4-byte payload slot per persistent parameter, while the compact-payload layout stores only the natural 1/2/4-byte payload width. +- NVM support is optional. When enabled, `src/persist/par_nvm.c` depends on a mounted storage backend interface. Whether runtime ID support must also be enabled depends on the selected persisted record layout and backend constraints: the payload-only layouts may be used with `PAR_CFG_ENABLE_ID = 0` only when `PAR_CFG_TABLE_ID_CHECK_EN = 1`, while the stored-ID layouts and the current flash backend path require `PAR_CFG_ENABLE_ID = 1`. Persistence metadata is compiled in automatically under `PAR_CFG_NVM_EN`. The package builds one packaged backend adapter selected from Kconfig. The RT-Thread AT24CXX backend is available today, and the generic flash backend entry is reserved as a placeholder for later implementation. +- Live RAM layout and persisted NVM layout are intentionally different. RAM storage is grouped by value width, while the persistence area stores a compile-time ordered slot list using one selected serialized record layout: fixed 4-byte payload slot with size descriptor, fixed 4-byte payload slot without size descriptor, compact natural-width payload with size descriptor, fixed natural-width payload without stored ID, or grouped natural-width payload without stored ID. +- Compile-time persistent order is the primary slot layout contract of the managed NVM image. The stored-ID layouts keep `id` in each record as an integrity and diagnostics field, while the payload-only layouts omit stored `id` and instead rely on compile-time slot order plus table-ID validation. The fixed-slot layouts keep one 4-byte payload slot per persistent parameter, while the compact and payload-only layouts store only the natural 1/2/4-byte payload width. - The serialized NVM header is written explicitly as a fixed 12-byte storage image (`sign(4) + obj_nb(2) + table_id(4) + crc16(2)`), so on-storage layout does not depend on compiler struct padding. Header CRC-16 covers the serialized `obj_nb + table_id` bytes, while each data record carries its own CRC-8 according to the selected record layout. - CRC calculation is routed through port hooks with bundled software defaults. In this single-target profile the persisted image and the table-ID digest both use the native byte order of the running platform, so no additional byte-order conversion hook is required by the persistence path. -- When `PAR_CFG_TABLE_ID_CHECK_EN = 1`, startup compares the stored table-ID against the live table-ID to validate persisted-image compatibility. The table-ID covers `PAR_CFG_TABLE_ID_SCHEMA_VER`, the selected persisted record layout, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID, hashed as platform-native scalar bytes under the single-target profile. It intentionally does not cover defaults, ranges, names, units, descriptions, or access flags. -- A table-ID mismatch is treated as an incompatible persisted-layout change, not as a warning-only condition. Startup restores defaults and rebuilds the managed NVM image. Typical triggers are add/remove/reorder/type/ID changes of persistent parameters and transitions between persistent and non-persistent state. A stored header count smaller than the compile-time persistent count is repaired by appending the missing tail slots from current defaults and rewriting the header count; a stored count larger than the compile-time count is treated as incompatible and rebuilt. +- When `PAR_CFG_TABLE_ID_CHECK_EN = 1`, startup compares the stored table-ID against the live compatibility digest for the stored persistent prefix size from the header (`obj_nb`). Layouts with stored IDs hash external parameter IDs, so prefix ID renumbering still invalidates the image there. `FIXED_PAYLOAD_ONLY` intentionally excludes external parameter IDs and validates only prefix byte-layout compatibility (`obj_nb`, persistent order, and parameter type), which allows pure external-ID renumbering and compatible tail growth without a rebuild there. `GROUPED_PAYLOAD_ONLY` also excludes external parameter IDs, but because regrouped addresses depend on the full live persistent set it rebuilds whenever `stored_count != live_count`. Semantic-only prefix remaps that preserve the same byte layout must still be paired with an explicit `PAR_CFG_TABLE_ID_SCHEMA_VER` bump. Defaults, ranges, names, units, descriptions, and access flags remain outside the digest. +- A table-ID mismatch is treated as an incompatible persisted-layout change, not as a warning-only condition. Startup restores defaults and rebuilds the managed NVM image. Typical triggers are add/remove/reorder/type/ID changes of persistent parameters and transitions between persistent and non-persistent state. When the stored header count is smaller than the compile-time persistent count, layouts with stable prefix addresses repair the image by appending the missing tail slots from current defaults and rewriting the header count; `GROUPED_PAYLOAD_ONLY` instead treats any stored/live count mismatch as incompatible and rebuilds. A stored count larger than the compile-time count is always treated as incompatible and rebuilt. - `PAR_CFG_TABLE_ID_SCHEMA_VER` defaults in `src/par_cfg.h` and may be overridden in `port/par_cfg_port.h`; the integrator should bump it when intentionally changing the serialized table-ID schema. - The fixed 4-byte payload slot without size descriptor is not available when a flash backend requires 8-byte aligned writes, because that layout serializes each record to 7 bytes. - The package intentionally keeps a compile-time ordered slot image instead of introducing a free-layout scanned log. That keeps the address model deterministic and avoids boot-time scan, compaction, and tombstone handling in the common parameter path. diff --git a/docs/api-reference.md b/docs/api-reference.md index c383904..79b145a 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -183,9 +183,9 @@ These APIs do not follow the same runtime usage pattern as the value access APIs Available only when `PAR_CFG_NVM_EN = 1` and a concrete parameter-storage backend is linked. -The managed NVM payload area is a compile-time ordered slot list, not a width-group partition like the live RAM layout. Each slot uses the selected serialized record layout: fixed 4-byte payload slot with size descriptor, fixed 4-byte payload slot without size descriptor, or compact natural-width payload with size descriptor. Slot order is derived directly from the compile-time persistent order in `par_table.def`, so the loader restores slot `i` to compile-time persistent slot `i`. If the stored count is larger than the current compile-time persistent count, startup treats the image as incompatible and the managed NVM area is rebuilt from live defaults. If the stored count is smaller, the stored prefix is restored and the missing tail slots are appended from current defaults before the header count is rewritten. The stored `id` is still kept inside each object as a validation and diagnostics field, but it is no longer the primary lookup key during startup repair. +The managed NVM payload area is a compile-time ordered slot list, not a width-group partition like the live RAM layout. Each slot uses the selected serialized record layout: fixed 4-byte payload slot with size descriptor, fixed 4-byte payload slot without size descriptor, compact natural-width payload with size descriptor, fixed natural-width payload without stored ID, or grouped natural-width payload without stored ID. Slot order is derived directly from the compile-time persistent order in `par_table.def`, so the loader restores slot `i` to compile-time persistent slot `i`. If the stored count is larger than the current compile-time persistent count, startup treats the image as incompatible and the managed NVM area is rebuilt from live defaults. If the stored count is smaller, layouts with stable prefix addresses restore the stored prefix and append the missing tail slots from current defaults before the header count is rewritten; `GROUPED_PAYLOAD_ONLY` instead rebuilds on any stored/live count mismatch. Self-describing layouts keep `id` inside each object as a validation and diagnostics field, while the payload-only layouts resolve records entirely from the compile-time slot map. -The serialized NVM header is emitted explicitly as a fixed 12-byte image (`sign + obj_nb + table_id + crc16`) rather than by writing raw struct memory. Header CRC-16 covers the serialized native-order `obj_nb + table_id` bytes, so corrupted header bytes fail header validation before any compatibility decision is made. Each small data record uses CRC-8 according to the selected record layout. When `PAR_CFG_TABLE_ID_CHECK_EN = 1`, `par_nvm_init()` compares the stored table-ID directly against the live table digest. The table-ID covers `PAR_CFG_TABLE_ID_SCHEMA_VER`, the selected persisted record layout, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID for parameters that are actually persisted, hashed as native platform scalar bytes. Startup first validates header CRC/signature, then validates the table-ID, then loads payload objects only if both checks pass. If the stored count is smaller than the compile-time persistent count, the loader restores the stored prefix, appends the missing tail slots from live defaults, and rewrites the header count. If the stored count is larger than the compile-time persistent count, the image is treated as incompatible and rebuilt. The collected error bits then drive a centralized recovery flow: NVM access errors restore live RAM values to defaults, while table-ID/header/CRC mismatches restore defaults and rebuild the managed NVM image. This compatibility boundary explicitly covers add/remove/reorder/type/ID changes of persistent parameters, record-layout changes, and transitions between persistent and non-persistent state. CRC calculation is exposed through port hooks with bundled software defaults. +The serialized NVM header is emitted explicitly as a fixed 12-byte image (`sign + obj_nb + table_id + crc16`) rather than by writing raw struct memory. Header CRC-16 covers the serialized native-order `obj_nb + table_id` bytes, so corrupted header bytes fail header validation before any compatibility decision is made. Each small data record uses CRC-8 according to the selected record layout. When `PAR_CFG_TABLE_ID_CHECK_EN = 1`, `par_nvm_init()` compares the stored table-ID against the live compatibility digest for the stored persistent prefix length from the header. Layouts with stored IDs hash parameter IDs inside that prefix. Payload-only layouts hash only the stored prefix byte layout (`obj_nb`, persistent order, and parameter type) while intentionally excluding external parameter IDs. Startup first validates header CRC/signature, then validates compatibility, then loads payload objects only if both checks pass. If the stored count is smaller than the compile-time persistent count, layouts with stable prefix addresses restore the stored prefix, append the missing tail slots from live defaults, and rewrite the header count. `GROUPED_PAYLOAD_ONLY` is excluded from that repair path and rebuilds on any stored/live count mismatch. If the stored count is larger than the compile-time persistent count, the image is treated as incompatible and rebuilt. The collected error bits then drive a centralized recovery flow: NVM access errors restore live RAM values to defaults, while table-ID/header/CRC mismatches restore defaults and rebuild the managed NVM image. Compatible tail growth is therefore allowed for layouts with stable prefix addresses, while prefix add/remove/reorder/type changes still rebuild; layouts with stored IDs additionally rebuild on prefix ID changes, `FIXED_PAYLOAD_ONLY` additionally allows pure external-ID renumbering, and `GROUPED_PAYLOAD_ONLY` rebuilds whenever the stored/live counts differ. When a payload-only prefix is semantically remapped without changing its byte layout, the integrator must bump `PAR_CFG_TABLE_ID_SCHEMA_VER` explicitly. CRC calculation is exposed through port hooks with bundled software defaults. | Function | Description | | --- | --- | diff --git a/docs/architecture.md b/docs/architecture.md index 3930eb3..7f26d36 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -320,13 +320,13 @@ When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`, `par_set_all_to_default()` also uses th When `PAR_CFG_NVM_EN = 1`, the module can persist selected parameters to NVM. -NVM persistence uses a compile-time selected record layout that is intentionally different from the live RAM layout. Live RAM storage is grouped by value width (`U8` / `U16` / `U32` / optional `F32`) for efficient access and raw reset, while the managed NVM area stores a compile-time ordered slot list. The selected slot serializer can keep a fixed 4-byte payload slot with a size descriptor, keep the same fixed 4-byte payload slot without the size descriptor, or store the natural payload width together with the size descriptor. +NVM persistence uses a compile-time selected record layout that is intentionally different from the live RAM layout. Live RAM storage is grouped by value width (`U8` / `U16` / `U32` / optional `F32`) for efficient access and raw reset, while the managed NVM area stores a compile-time ordered slot list. The selected slot serializer can keep a fixed 4-byte payload slot with a size descriptor, keep the same fixed 4-byte payload slot without the size descriptor, store the natural payload width together with the size descriptor, store only `crc + payload` in compile-time persistent order, or store only `crc + payload` in grouped 8/16/32-bit persistent order. The two payload-only layouts intentionally rely on table-ID checking instead of per-record IDs to detect schema drift. -The compile-time persistent order is the primary layout contract of the managed NVM image. Loader and rewrite paths restore slot `i` into compile-time persistent slot `i`, while the stored `id` remains inside each object as an integrity and diagnostics field rather than as the main startup lookup key. Count asymmetry is handled intentionally: a stored count larger than the live compile-time persistent count is treated as an incompatible image and triggers full rebuild, while a stored count smaller than the live count restores the common prefix first and then appends newly introduced tail slots from current defaults. The fixed-slot layouts keep one 4-byte payload slot per persistent parameter. The compact-payload layout stores only the natural 1/2/4-byte payload width and therefore saves bytes for narrow types. The fixed-slot layout without a size descriptor is intentionally not offered when a flash backend requires 8-byte aligned writes, because its serialized record width is 7 bytes. +The compile-time persistent order is the primary layout contract of the managed NVM image. Loader and rewrite paths restore slot `i` into compile-time persistent slot `i`, while the stored `id` remains inside each object as an integrity and diagnostics field rather than as the main startup lookup key. Count asymmetry is handled intentionally: a stored count larger than the live compile-time persistent count is treated as an incompatible image and triggers full rebuild, while a stored count smaller than the live count restores the common prefix first and then appends newly introduced tail slots from current defaults only for layouts whose prefix addresses stay stable. `GROUPED_PAYLOAD_ONLY` is excluded from that repair path and rebuilds on any stored/live count mismatch. Compatibility is checked against the stored prefix for every layout. Layouts with stored IDs include parameter IDs in that prefix digest, while the payload-only layouts exclude external parameter IDs and validate only prefix byte-layout compatibility; pure external-ID renumbering therefore does not invalidate an otherwise compatible stored prefix there. The fixed-slot layouts keep one 4-byte payload slot per persistent parameter. The compact-payload layout stores only the natural 1/2/4-byte payload width and therefore saves bytes for narrow types. The fixed-slot layout without a size descriptor is intentionally not offered when a flash backend requires 8-byte aligned writes, because its serialized record width is 7 bytes. -The serialized NVM header is a fixed 12-byte storage image (`sign + obj_nb + table_id + crc16`) emitted explicitly instead of by writing raw struct memory. The header CRC covers the serialized native-order `obj_nb + table_id` bytes, so header corruption is detected separately from a valid header that simply belongs to another parameter-table schema. Each data object uses CRC-8 according to the selected record layout. This profile intentionally ties the persisted image to the current target architecture and does not normalize byte order for cross-platform migration. CRC calculation is routed through port hooks with bundled software defaults. The table-ID digest follows the same rule and hashes each scalar exactly as represented in native platform memory. The digest covers only metadata that changes binary compatibility of persisted records: `PAR_CFG_TABLE_ID_SCHEMA_VER`, selected persisted record layout, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID. Default values, ranges, names, units, descriptions, and access flags are intentionally excluded because they do not change the serialized NVM object layout used by `par_nvm.c`. When table-ID checking is enabled, changes such as add/remove/reorder/type/ID updates of persistent parameters, record-layout changes, and persistent<->non-persistent transitions are treated as incompatible schema changes and the managed NVM image is rebuilt from defaults at startup. +The serialized NVM header is a fixed 12-byte storage image (`sign + obj_nb + table_id + crc16`) emitted explicitly instead of by writing raw struct memory. The header CRC covers the serialized native-order `obj_nb + table_id` bytes, so header corruption is detected separately from a valid header that simply belongs to another parameter-table schema. Each data object uses CRC-8 according to the selected record layout. This profile intentionally ties the persisted image to the current target architecture and does not normalize byte order for cross-platform migration. CRC calculation is routed through port hooks with bundled software defaults. The table-ID digest follows the same rule and hashes each scalar exactly as represented in native platform memory. For self-describing layouts, the digest covers `PAR_CFG_TABLE_ID_SCHEMA_VER`, selected persisted record layout, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID. For payload-only layouts, the digest covers the stored persistent prefix count, persistent-parameter order, and parameter type, and intentionally excludes external parameter IDs. Default values, ranges, names, units, descriptions, and access flags are intentionally excluded because they do not change the serialized NVM object layout used by `par_nvm.c`. When table-ID checking is enabled, self-describing layouts treat add/remove/reorder/type/ID updates of persistent parameters as incompatible schema changes, `FIXED_PAYLOAD_ONLY` allows pure external-ID renumbering and compatible tail growth without forcing a rebuild, and `GROUPED_PAYLOAD_ONLY` still rebuilds on any stored/live count mismatch. If a payload-only prefix is semantically remapped while keeping the same serialized byte layout, the integrator must bump `PAR_CFG_TABLE_ID_SCHEMA_VER` explicitly because that remap is outside the automatic byte-layout digest. -If the stored table-ID mismatches the live table-ID, the module treats the persisted image as incompatible and rebuilds the managed NVM area from current default values and the current schema. This can happen after an intentional schema/version bump, after parameter persistence layout changes, after stored-image corruption, or when the stored header count is larger than the current compile-time persistent count. If the stored count is smaller than the compile-time count, startup restores the stored prefix, appends the missing tail slots from live defaults, and rewrites the header count to the current schema width. The recovery action is centralized in `par_nvm_init()`: it accumulates status bits from header validation, table-ID validation, and payload loading, then decides whether to restore defaults only or restore defaults plus rewrite the managed NVM image. +If the stored table-ID mismatches the live table-ID, the module treats the persisted image as incompatible and rebuilds the managed NVM area from current default values and the current schema. This can happen after an intentional schema/version bump, after parameter persistence layout changes, after stored-image corruption, or when the stored header count is larger than the current compile-time persistent count. If the stored count is smaller than the compile-time count, layouts with stable prefix addresses restore the stored prefix, append the missing tail slots from live defaults, and rewrite the header count to the current schema width; `GROUPED_PAYLOAD_ONLY` instead rebuilds on any stored/live count mismatch. The recovery action is centralized in `par_nvm_init()`: it accumulates status bits from header validation, table-ID validation, and payload loading, then decides whether to restore defaults only or restore defaults plus rewrite the managed NVM image. For this feature, `par_nvm.c` mounts a parameter-storage backend interface during initialization. The core no longer depends directly on a specific repository layout, while Kconfig selects which packaged backend adapter is built. diff --git a/docs/getting-started.md b/docs/getting-started.md index 0a56913..f77ca11 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -146,7 +146,7 @@ ID-based lookup is generated statically when `PAR_CFG_ENABLE_ID = 1`. Optional s When NVM is enabled, the parameters module requires a concrete packaged storage backend implementation. `src/persist/par_nvm.c` resolves and validates the backend API once during initialization, then uses the mounted callbacks directly for later reads, writes, erases, and sync operations. The RT-Thread AT24CXX backend is available today, while the generic flash backend Kconfig entry is currently only a placeholder and does not build an implementation yet. The module can reuse an already-initialized backend or initialize it on demand and later deinitialize it only when it owns that initialization. Module deinit is conservative: it attempts backend and interface cleanup, and it clears the top-level module init state only after the owned child deinit steps succeed. -The persisted NVM image uses a compile-time selected record layout instead of the grouped-width RAM layout used at runtime. Compile-time persistent order remains the primary slot layout contract for startup restore, while the stored `id` remains a validation and diagnostics field inside each record. The fixed-slot layouts keep one 4-byte payload slot per persistent parameter, and the compact-payload layout stores only the natural 1/2/4-byte payload width. The fixed-slot layout without a size descriptor is intentionally unavailable for a flash backend that requires 8-byte aligned writes, because that serializer uses a 7-byte record. The header is serialized explicitly as a fixed 12-byte image (`sign + obj_nb + table_id + crc16`). Under the current single-target assumption, fields are stored, validated, and hashed in the native byte order of the running platform instead of being normalized to a cross-platform storage endianness. Header CRC-16 therefore covers the serialized native-order `obj_nb + table_id` bytes, each data object uses layout-specific CRC-8, and the table-ID digest is computed from native-order scalar images including the selected record layout. When the stored count is smaller than the compile-time persistent count, startup appends the missing tail slots from current defaults and rewrites the header count; when the stored count is larger, startup treats the image as incompatible and rebuilds it. The bundled defaults are provided through `par_if_crc16_accumulate()` and `par_if_crc8_accumulate()`. +The persisted NVM image uses a compile-time selected record layout instead of the grouped-width RAM layout used at runtime. Compile-time persistent order remains the primary slot layout contract for startup restore. Layouts that serialize `id` still use it as a validation and diagnostics field inside each record, while the two payload-only fixed layouts omit the serialized `id` and resolve addresses entirely from the compile-time slot map. The fixed-slot layouts keep one 4-byte payload slot per persistent parameter, the compact-payload layout stores only the natural 1/2/4-byte payload width together with a size descriptor, and the two payload-only layouts store only `crc + payload` bytes. Because they do not serialize `id`, they require table-ID checking to stay enabled so schema changes still trigger a rebuild; their compatibility digest is computed from the stored persistent prefix and excludes external parameter IDs. `FIXED_PAYLOAD_ONLY` therefore allows pure ID renumbering and tail-slot growth without forcing a rebuild, while `GROUPED_PAYLOAD_ONLY` still rebuilds whenever the stored/live counts differ because regrouped addresses depend on the full live persistent set. If a payload-only prefix is semantically remapped while keeping the same serialized byte layout, the integrator must bump `PAR_CFG_TABLE_ID_SCHEMA_VER` explicitly because that change is outside the automatic digest. The fixed-slot layout without a size descriptor is intentionally unavailable for a flash backend that requires 8-byte aligned writes, because that serializer uses a 7-byte record, and the two variable-width payload-only layouts are hidden there for the same alignment reason. The header is serialized explicitly as a fixed 12-byte image (`sign + obj_nb + table_id + crc16`). Under the current single-target assumption, fields are stored, validated, and hashed in the native byte order of the running platform instead of being normalized to a cross-platform storage endianness. Header CRC-16 therefore covers the serialized native-order `obj_nb + table_id` bytes, each data object uses layout-specific CRC-8, and the compatibility digest is computed from native-order scalar images including the selected record layout. When the stored count is smaller than the compile-time persistent count, layouts with stable prefix addresses append the missing tail slots from current defaults and rewrite the header count; `GROUPED_PAYLOAD_ONLY` instead treats any stored/live count mismatch as incompatible and rebuilds. When the stored count is larger, startup treats the image as incompatible and rebuilds it. The bundled defaults are provided through `par_if_crc16_accumulate()` and `par_if_crc8_accumulate()`. Backend choices: diff --git a/src/def/par_def.c b/src/def/par_def.c index fa4b6ea..5f8998b 100644 --- a/src/def/par_def.c +++ b/src/def/par_def.c @@ -420,6 +420,29 @@ static const par_cfg_t g_par_table[ePAR_NUM_OF] = { #undef PAR_PERSIST_IDX_VALUE_0 #endif +/** + * @brief Configuration-independent compile-time parameter-ID table. + */ +#define PAR_ITEM_ID_VALUE(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) [enum_] = (uint16_t)(id_), +static const uint16_t g_par_id_table[ePAR_NUM_OF] = { +#define PAR_ITEM_U8 PAR_ITEM_ID_VALUE +#define PAR_ITEM_U16 PAR_ITEM_ID_VALUE +#define PAR_ITEM_U32 PAR_ITEM_ID_VALUE +#define PAR_ITEM_I8 PAR_ITEM_ID_VALUE +#define PAR_ITEM_I16 PAR_ITEM_ID_VALUE +#define PAR_ITEM_I32 PAR_ITEM_ID_VALUE +#define PAR_ITEM_F32 PAR_ITEM_ID_VALUE +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +}; +#undef PAR_ITEM_ID_VALUE + /** * @brief Table size in bytes. */ @@ -450,6 +473,18 @@ const par_cfg_t *par_cfg_get(const par_num_t par_num) { return &g_par_table[par_num]; } + +/** + * @brief Return the compile-time parameter ID for one entry. + * + * @param par_num Parameter number. + * @return Parameter ID from the generated parameter table. + */ +uint16_t par_cfg_get_param_id_const(const par_num_t par_num) +{ + PAR_ASSERT(par_num < ePAR_NUM_OF); + return g_par_id_table[par_num]; +} /** * @brief Get configuration table size in bytes. * diff --git a/src/def/par_def.h b/src/def/par_def.h index b499f72..c7d3850 100644 --- a/src/def/par_def.h +++ b/src/def/par_def.h @@ -186,6 +186,17 @@ enum */ const par_cfg_t *par_cfg_get_table(void); const par_cfg_t *par_cfg_get(const par_num_t par_num); +/** + * @brief Return the compile-time parameter ID for one entry. + * + * @details This accessor is configuration-independent and remains available + * even when PAR_CFG_ENABLE_ID disables the runtime metadata field inside + * par_cfg_t. + * + * @param par_num Parameter number. + * @return Parameter ID from par_table.def. + */ +uint16_t par_cfg_get_param_id_const(const par_num_t par_num); /** * @brief Return the number of configuration entries. * @return Configuration table size. diff --git a/src/par_cfg.h b/src/par_cfg.h index 4b7a60d..0c5e8fe 100644 --- a/src/par_cfg.h +++ b/src/par_cfg.h @@ -77,13 +77,20 @@ * @brief Enable/Disable parameter-table compatibility checking. * * @note The stored NVM image header carries a table-ID digest that covers - * PAR_CFG_TABLE_ID_SCHEMA_VER, selected record layout, persistent-parameter - * count, persistent order, parameter type, and parameter ID. + * PAR_CFG_TABLE_ID_SCHEMA_VER, selected record layout, and the stored + * persistent prefix. Self-describing layouts include parameter IDs in that + * digest. Payload-only layouts intentionally exclude external parameter IDs + * and hash only prefix count, persistent order, and parameter type so stored + * prefixes with identical byte layout remain compatible. * - * When enabled, any persisted-layout incompatibility is treated as a managed. + * When enabled, any persisted-layout incompatibility is treated as a managed * schema change: startup restores defaults and rebuilds the managed NVM image. - * This includes add/remove/reorder/type/ID changes of persistent parameters, - * and transitions between persistent and non-persistent state. + * Layouts with stable prefix addresses allow compatible tail-slot growth when + * the stored prefix still matches the live prefix. The grouped payload-only + * layout is excluded from that repair path and rebuilds on any stored/live + * count mismatch. Payload-only layouts therefore still require the integrator + * to bump PAR_CFG_TABLE_ID_SCHEMA_VER whenever a prefix parameter is + * semantically remapped without changing its serialized byte layout. * * @pre "PAR_CFG_NVM_EN" must be enabled, otherwise table-ID checking does. * not apply. @@ -116,11 +123,34 @@ #define PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE (0U) #define PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE (1U) #define PAR_CFG_NVM_RECORD_LAYOUT_COMPACT_PAYLOAD (2U) +#define PAR_CFG_NVM_RECORD_LAYOUT_FIXED_PAYLOAD_ONLY (3U) +#define PAR_CFG_NVM_RECORD_LAYOUT_GROUPED_PAYLOAD_ONLY (4U) #ifndef PAR_CFG_NVM_RECORD_LAYOUT #define PAR_CFG_NVM_RECORD_LAYOUT (PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE) #endif +/** + * @brief Derived layout capability: serialized records store a parameter ID. + */ +#if (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE) || \ + (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE) || \ + (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_COMPACT_PAYLOAD) +#define PAR_CFG_NVM_RECORD_LAYOUT_HAS_STORED_ID (1) +#else +#define PAR_CFG_NVM_RECORD_LAYOUT_HAS_STORED_ID (0) +#endif + +/** + * @brief Derived layout capability: serialized records store a size descriptor. + */ +#if (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE) || \ + (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_COMPACT_PAYLOAD) +#define PAR_CFG_NVM_RECORD_LAYOUT_HAS_SIZE_DESC (1) +#else +#define PAR_CFG_NVM_RECORD_LAYOUT_HAS_SIZE_DESC (0) +#endif + /** * @brief Enable/Disable debug mode. */ @@ -560,8 +590,28 @@ PAR_STATIC_ASSERT(par_id_hash_bits_valid, ((PAR_ID_HASH_BITS > 0u) && (PAR_ID_HA /** * @brief Configuration dependency checks for optional fields/features. */ -#if (1 == PAR_CFG_NVM_EN) && (0 == PAR_CFG_ENABLE_ID) -#error "Parameter settings invalid: NVM requires PAR_CFG_ENABLE_ID = 1!" +#if (1 == PAR_CFG_NVM_EN) && (0 == PAR_CFG_ENABLE_ID) && \ + (PAR_CFG_NVM_RECORD_LAYOUT != PAR_CFG_NVM_RECORD_LAYOUT_FIXED_PAYLOAD_ONLY) && \ + (PAR_CFG_NVM_RECORD_LAYOUT != PAR_CFG_NVM_RECORD_LAYOUT_GROUPED_PAYLOAD_ONLY) +#error "Parameter settings invalid: selected NVM layout requires PAR_CFG_ENABLE_ID = 1!" +#endif + +#if (1 == PAR_CFG_NVM_BACKEND_FLASH_EN) && (0 == PAR_CFG_ENABLE_ID) +#error "Parameter settings invalid: flash backend requires PAR_CFG_ENABLE_ID = 1!" +#endif + +#if (1 == PAR_CFG_NVM_EN) && \ + ((PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_PAYLOAD_ONLY) || \ + (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_GROUPED_PAYLOAD_ONLY)) && \ + (0 == PAR_CFG_TABLE_ID_CHECK_EN) +#error "Parameter settings invalid: payload-only NVM layouts require PAR_CFG_TABLE_ID_CHECK_EN = 1!" +#endif + +#if (1 == PAR_CFG_NVM_BACKEND_FLASH_EN) && \ + ((PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE) || \ + (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_PAYLOAD_ONLY) || \ + (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_GROUPED_PAYLOAD_ONLY)) +#error "Parameter settings invalid: selected NVM layout is not supported by the flash backend!" #endif #if (0 == PAR_CFG_ENABLE_ID) && (1 == PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK) diff --git a/src/persist/par_nvm.c b/src/persist/par_nvm.c index 9ec72e5..5f1504c 100644 --- a/src/persist/par_nvm.c +++ b/src/persist/par_nvm.c @@ -47,8 +47,12 @@ * the stored digest when table-ID checking is enabled. * * A mismatch means the managed NVM image is no longer compatible with the live - * firmware image. Typical causes are an intentional schema-version bump, a - * change in persisted-parameter order/type/ID, or stored-image corruption. + * firmware image. Typical causes are an intentional schema-version bump, + * stored-prefix layout drift, or stored-image corruption. Self-describing + * layouts treat persisted-parameter ID changes as incompatible. The fixed + * payload-only layout intentionally allows pure external-ID renumbering and + * compatible tail growth, while the grouped payload-only layout rebuilds on + * any stored/live count mismatch. * The recovery path is centralized in par_nvm_init(): restore defaults first, * then rebuild the managed NVM image only for errors that require a rewrite. */ @@ -125,12 +129,28 @@ typedef struct uint16_t loaded_count; /**< Number of runtime-loaded persistent slots. */ } par_nvm_slot_runtime_t; +/** + * @brief Result of comparing the stored NVM image against the live schema. + */ +typedef enum +{ + ePAR_NVM_COMPAT_REBUILD = 0, /**< Stored image is incompatible and must be rebuilt. */ + ePAR_NVM_COMPAT_EXACT_MATCH, /**< Stored image matches the live schema exactly. */ + ePAR_NVM_COMPAT_PREFIX_APPEND /**< Stored prefix is compatible and new tail slots may be appended. */ +} par_nvm_compat_result_t; + #if (1 == PAR_CFG_ENABLE_NAME) #define PAR_NVM_DBG_NAME_ARG(cfg_) (((const par_cfg_t *)(cfg_) != NULL) && (((const par_cfg_t *)(cfg_))->name != NULL) ? ((const par_cfg_t *)(cfg_))->name : "") #else #define PAR_NVM_DBG_NAME_ARG(cfg_) "" #endif +#if (1 == PAR_CFG_ENABLE_ID) +#define PAR_NVM_CFG_ID_VALUE(cfg_) (((const par_cfg_t *)(cfg_)) != NULL ? ((const par_cfg_t *)(cfg_))->id : 0U) +#else +#define PAR_NVM_CFG_ID_VALUE(cfg_) (0U) +#endif + #define PAR_PERSIST_SLOT_ENTRY_SELECT(enum_, pers_) PAR_PERSIST_SLOT_ENTRY_SELECT_I(enum_, pers_) #define PAR_PERSIST_SLOT_ENTRY_SELECT_I(enum_, pers_) PAR_PERSIST_SLOT_ENTRY_SELECT_##pers_(enum_) #define PAR_PERSIST_SLOT_ENTRY_SELECT_true(enum_) [PAR_PERSIST_IDX_##enum_] = enum_, @@ -194,17 +214,214 @@ static const par_store_backend_api_t *gp_store = NULL; static par_nvm_slot_runtime_t g_par_nvm_slot_runtime = { 0 }; /** - * @brief Calculate per-record CRC-8 over serialized record bytes. + * @brief Resolve natural payload size from parameter type. + * + * @param type Parameter type. + * @return Natural payload width in bytes. + */ +uint8_t par_nvm_layout_payload_size_from_type(const par_type_list_t type) +{ + switch (type) + { + case ePAR_TYPE_U8: + case ePAR_TYPE_I8: + return 1U; + + case ePAR_TYPE_U16: + case ePAR_TYPE_I16: + return 2U; + + case ePAR_TYPE_U32: + case ePAR_TYPE_I32: +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + case ePAR_TYPE_F32: +#endif + return 4U; + + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + return PAR_NVM_RECORD_DATA_SLOT_SIZE; + } +} + +/** + * @brief Resolve one persistent slot payload width. + * + * @param par_num Live parameter number. + * @return Natural payload width in bytes. + */ +uint8_t par_nvm_layout_payload_size_from_par_num(const par_num_t par_num) +{ + const par_cfg_t * const p_cfg = par_get_config(par_num); + + PAR_ASSERT(NULL != p_cfg); + return par_nvm_layout_payload_size_from_type(p_cfg->type); +} + +/** + * @brief Serialize one parameter value to native-endian payload bytes. + * + * @param type Parameter type. + * @param p_data Canonical parameter value. + * @param p_payload Output payload buffer. + */ +void par_nvm_layout_pack_payload_bytes(const par_type_list_t type, + const par_type_t * const p_data, + uint8_t * const p_payload) +{ + PAR_ASSERT((NULL != p_data) && (NULL != p_payload)); + + switch (type) + { + case ePAR_TYPE_U8: + { + const uint8_t value = p_data->u8; + memcpy(p_payload, &value, sizeof(value)); + break; + } + + case ePAR_TYPE_I8: + { + const int8_t value = p_data->i8; + memcpy(p_payload, &value, sizeof(value)); + break; + } + + case ePAR_TYPE_U16: + { + const uint16_t value = p_data->u16; + memcpy(p_payload, &value, sizeof(value)); + break; + } + + case ePAR_TYPE_I16: + { + const int16_t value = p_data->i16; + memcpy(p_payload, &value, sizeof(value)); + break; + } + + case ePAR_TYPE_U32: + { + const uint32_t value = p_data->u32; + memcpy(p_payload, &value, sizeof(value)); + break; + } + + case ePAR_TYPE_I32: + { + const int32_t value = p_data->i32; + memcpy(p_payload, &value, sizeof(value)); + break; + } + +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + case ePAR_TYPE_F32: + { + const float32_t value = p_data->f32; + memcpy(p_payload, &value, sizeof(value)); + break; + } +#endif + + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + break; + } +} + +/** + * @brief Deserialize native-endian payload bytes into the canonical value carrier. + * + * @param type Parameter type. + * @param p_payload Input payload buffer. + * @param p_data Output canonical parameter value. + */ +void par_nvm_layout_unpack_payload_bytes(const par_type_list_t type, + const uint8_t * const p_payload, + par_type_t * const p_data) +{ + PAR_ASSERT((NULL != p_payload) && (NULL != p_data)); + + switch (type) + { + case ePAR_TYPE_U8: + { + uint8_t value = 0U; + memcpy(&value, p_payload, sizeof(value)); + p_data->u8 = value; + break; + } + + case ePAR_TYPE_I8: + { + int8_t value = 0; + memcpy(&value, p_payload, sizeof(value)); + p_data->i8 = value; + break; + } + + case ePAR_TYPE_U16: + { + uint16_t value = 0U; + memcpy(&value, p_payload, sizeof(value)); + p_data->u16 = value; + break; + } + + case ePAR_TYPE_I16: + { + int16_t value = 0; + memcpy(&value, p_payload, sizeof(value)); + p_data->i16 = value; + break; + } + + case ePAR_TYPE_U32: + { + uint32_t value = 0U; + memcpy(&value, p_payload, sizeof(value)); + p_data->u32 = value; + break; + } + + case ePAR_TYPE_I32: + { + int32_t value = 0; + memcpy(&value, p_payload, sizeof(value)); + p_data->i32 = value; + break; + } + +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + case ePAR_TYPE_F32: + { + float32_t value = 0.0f; + memcpy(&value, p_payload, sizeof(value)); + p_data->f32 = value; + break; + } +#endif + + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + break; + } +} + +/** + * @brief Calculate per-record CRC-8 over serialized record bytes without an ID field. * - * @param id Parameter ID. * @param size_desc Serialized size descriptor. * @param p_payload Pointer to payload bytes. * @param payload_size Number of payload bytes. * @param include_size_desc True when the layout includes a size field. * @return Calculated CRC-8 value. */ -uint8_t par_nvm_layout_calc_crc(const uint16_t id, - const uint8_t size_desc, +uint8_t par_nvm_layout_calc_crc(const uint8_t size_desc, const uint8_t * const p_payload, const uint8_t payload_size, const bool include_size_desc) @@ -213,6 +430,35 @@ uint8_t par_nvm_layout_calc_crc(const uint16_t id, PAR_ASSERT(NULL != p_payload); + if (include_size_desc) + { + crc = par_if_crc8_accumulate(crc, (const uint8_t * const)&size_desc, (uint32_t)sizeof(size_desc)); + } + crc = par_if_crc8_accumulate(crc, p_payload, (uint32_t)payload_size); + + return crc; +} + +/** + * @brief Calculate per-record CRC-8 over serialized record bytes with an ID field. + * + * @param id Parameter ID. + * @param size_desc Serialized size descriptor. + * @param p_payload Pointer to payload bytes. + * @param payload_size Number of payload bytes. + * @param include_size_desc True when the layout includes a size field. + * @return Calculated CRC-8 value. + */ +uint8_t par_nvm_layout_calc_crc_with_id(const uint16_t id, + const uint8_t size_desc, + const uint8_t * const p_payload, + const uint8_t payload_size, + const bool include_size_desc) +{ + uint8_t crc = PAR_IF_CRC8_INIT; + + PAR_ASSERT(NULL != p_payload); + crc = par_if_crc8_accumulate(crc, (const uint8_t * const)&id, (uint32_t)sizeof(id)); if (include_size_desc) { @@ -293,7 +539,7 @@ static par_status_t par_nvm_write_header(const uint16_t num_of_par) uint8_t head_buf[PAR_NVM_HEAD_SIZE] = { 0U }; head_obj.obj_nb = num_of_par; - head_obj.table_id = par_nvm_table_id_calc(); + head_obj.table_id = par_nvm_table_id_calc_for_count(num_of_par); head_obj.crc = par_nvm_calc_head_crc(&head_obj); head_obj.sign = PAR_NVM_SIGN; @@ -416,7 +662,7 @@ par_status_t par_nvm_print_nvm_lut(void) PAR_DBG_PRINT( " %u %u 0x%08lX %lu %u %s", (unsigned)persist_idx, - (unsigned)par_get_config(par_num)->id, + (unsigned)PAR_NVM_CFG_ID_VALUE(par_get_config(par_num)), (unsigned long)par_nvm_addr_from_persist_idx(persist_idx), (unsigned long)par_nvm_layout_record_size_from_par_num(par_num), (unsigned)g_par_nvm_slot_runtime.loaded_slots[persist_idx], @@ -450,31 +696,66 @@ static void par_nvm_build_new_nvm_lut(void) par_nvm_print_nvm_lut(); } /** - * @brief Get parameter NVM object start address based on its ID. + * @brief Get parameter NVM object start address from the compile-time persistent slot. * - * @note In case ID is not found there is a problem with building the. - * NVM lut!!! - * - * @param id Parameter ID. - * @return NVM address of object with ID. + * @param par_num Live parameter number. + * @return NVM address of the persistent slot, or 0 when mapping is invalid. */ -static uint32_t par_nvm_get_nvm_lut_addr(const uint16_t id) +static uint32_t par_nvm_get_nvm_lut_addr(const par_num_t par_num) { - par_num_t par_num = 0U; - const par_cfg_t *par_cfg = NULL; + const par_cfg_t * const par_cfg = par_get_config(par_num); - if (ePAR_OK != par_get_num_by_id(id, &par_num)) + if ((NULL == par_cfg) || (false == par_cfg->persistent) || (par_cfg->persist_idx >= PAR_PERSISTENT_COMPILE_COUNT)) { return 0U; } - par_cfg = par_get_config(par_num); - if ((NULL == par_cfg) || (false == par_cfg->persistent) || (par_cfg->persist_idx >= PAR_PERSISTENT_COMPILE_COUNT)) + return par_nvm_addr_from_persist_idx(par_cfg->persist_idx); +} + +/** + * @brief Check compatibility between the stored image and the live schema. + * + * @details Compatibility is evaluated against the stored persistent prefix + * size from the validated NVM header. Every layout requires that the live + * firmware still exposes at least that many persistent slots and that the + * stored digest matches the live digest for exactly that prefix length. + * Layouts with stored IDs include the external parameter ID in the digest, + * so prefix ID renumbering still rebuilds the image there. The fixed + * payload-only layout excludes external parameter IDs and validates only + * stored-prefix byte-layout compatibility, so pure external-ID renumbering + * stays compatible there. The grouped payload-only layout is stricter: + * because regrouping depends on the full live persistent set, any stored/live + * count mismatch rebuilds instead of attempting tail append. Semantic-only + * prefix remaps that keep the same byte layout still require an explicit + * PAR_CFG_TABLE_ID_SCHEMA_VER bump. + * + * @param p_head_obj Validated stored NVM header. + * @return Compatibility result. + */ +static par_nvm_compat_result_t par_nvm_check_compat(const par_nvm_head_obj_t * const p_head_obj) +{ + PAR_ASSERT(NULL != p_head_obj); + + if (p_head_obj->obj_nb > (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) { - return 0U; + return ePAR_NVM_COMPAT_REBUILD; } - return par_nvm_addr_from_persist_idx(par_cfg->persist_idx); + if (p_head_obj->table_id != par_nvm_table_id_calc_for_count(p_head_obj->obj_nb)) + { + return ePAR_NVM_COMPAT_REBUILD; + } + +#if (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_GROUPED_PAYLOAD_ONLY) + return (p_head_obj->obj_nb == (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) ? + ePAR_NVM_COMPAT_EXACT_MATCH : + ePAR_NVM_COMPAT_REBUILD; +#else + return (p_head_obj->obj_nb == (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) ? + ePAR_NVM_COMPAT_EXACT_MATCH : + ePAR_NVM_COMPAT_PREFIX_APPEND; +#endif } /** @@ -485,8 +766,10 @@ static uint32_t par_nvm_get_nvm_lut_addr(const uint16_t id) * slot count, the image is treated as incompatible and the caller rebuilds * the managed NVM area from current defaults. * - If the stored header count is smaller than the compile-time persistent - * slot count, the stored prefix is restored first and the missing tail slots - * are appended from live defaults before the header count is rewritten. + * slot count, compatible layouts restore the stored prefix first and append + * the missing tail slots from live defaults before rewriting the header + * count. The grouped payload-only layout is excluded from that repair path + * and rebuilds on any stored/live count mismatch. * * @param num_of_par Number of stored parameters inside NVM. * @return Status of operation. @@ -550,25 +833,39 @@ static par_status_t par_nvm_load_all(const uint16_t num_of_par) { status = op_status; err.reason = (ePAR_ERROR_CRC == op_status) ? "crc-mismatch" : "read-failed"; +#if (1 == PAR_CFG_NVM_RECORD_LAYOUT_HAS_STORED_ID) err.stored_id = obj_data.id; +#else + err.stored_id = (uint16_t)PAR_NVM_CFG_ID_VALUE(par_cfg); +#endif goto out; } - if (obj_data.id != par_cfg->id) +#if (1 == PAR_CFG_NVM_RECORD_LAYOUT_HAS_STORED_ID) + if (obj_data.id != PAR_NVM_CFG_ID_VALUE(par_cfg)) { status = ePAR_ERROR; err.reason = "id-mismatch"; +#if (1 == PAR_CFG_NVM_RECORD_LAYOUT_HAS_STORED_ID) err.stored_id = obj_data.id; +#else + err.stored_id = (uint16_t)PAR_NVM_CFG_ID_VALUE(par_cfg); +#endif op_status = status; goto out; } +#endif op_status = par_set_fast(par_num, &obj_data.data); if (ePAR_OK != op_status) { status |= op_status; err.reason = "restore-failed"; +#if (1 == PAR_CFG_NVM_RECORD_LAYOUT_HAS_STORED_ID) err.stored_id = obj_data.id; +#else + err.stored_id = (uint16_t)PAR_NVM_CFG_ID_VALUE(par_cfg); +#endif goto out; } @@ -601,7 +898,7 @@ static par_status_t par_nvm_load_all(const uint16_t num_of_par) if (ePAR_OK != (status & ePAR_STATUS_ERROR_MASK)) { err.reason = "append-save-failed"; - err.stored_id = par_cfg->id; + err.stored_id = (uint16_t)PAR_NVM_CFG_ID_VALUE(par_cfg); op_status = status; goto out; } @@ -648,7 +945,7 @@ static par_status_t par_nvm_load_all(const uint16_t num_of_par) (unsigned long)par_nvm_addr_from_persist_idx(i), (unsigned)err.stored_id, (unsigned)((i < PAR_PERSIST_SLOT_MAP_CAPACITY) ? g_par_persist_slot_to_par_num[i] : 0U), - (unsigned)((i < PAR_PERSIST_SLOT_MAP_CAPACITY) ? par_get_config(g_par_persist_slot_to_par_num[i])->id : 0U), + (unsigned)((i < PAR_PERSIST_SLOT_MAP_CAPACITY) ? PAR_NVM_CFG_ID_VALUE(par_get_config(g_par_persist_slot_to_par_num[i])) : 0U), PAR_NVM_DBG_NAME_ARG((i < PAR_PERSIST_SLOT_MAP_CAPACITY) ? par_get_config(g_par_persist_slot_to_par_num[i]) : NULL), err.reason, par_get_status_str(op_status), @@ -766,22 +1063,34 @@ par_status_t par_nvm_init(void) detect_status = par_nvm_validate_header(&head_obj); #if (1 == PAR_CFG_TABLE_ID_CHECK_EN) - /* Step 2: validate table-ID only when header is valid */ + /* Step 2: validate table compatibility only when header is valid */ if (ePAR_OK == (detect_status & ePAR_STATUS_ERROR_MASK)) { - const uint32_t live_table_id = par_nvm_table_id_calc(); - if (head_obj.table_id != live_table_id) + const par_nvm_compat_result_t compat = par_nvm_check_compat(&head_obj); + + if (ePAR_NVM_COMPAT_REBUILD == compat) { detect_status |= ePAR_ERROR_TABLE_ID; } - } -#endif + else + { + if (ePAR_NVM_COMPAT_PREFIX_APPEND == compat) + { + PAR_INFO_PRINT("PAR_NVM: stored persistent prefix is compatible, restore=%u append=%u", + (unsigned)head_obj.obj_nb, + (unsigned)((uint16_t)PAR_PERSISTENT_COMPILE_COUNT - head_obj.obj_nb)); + } - /* Step 3: load payload only when previous checks are valid */ + detect_status |= par_nvm_load_all(head_obj.obj_nb); + } + } +#else + /* Step 2: load payload only when header is valid */ if (ePAR_OK == (detect_status & ePAR_STATUS_ERROR_MASK)) { detect_status |= par_nvm_load_all(head_obj.obj_nb); } +#endif /* Step 4: classify recovery action from detected issues */ if (0U != (detect_status & ePAR_ERROR_TABLE_ID)) @@ -924,17 +1233,19 @@ par_status_t par_nvm_write(const par_num_t par_num, const bool nvm_sync) uint32_t par_addr = 0UL; par_status_t store_status = ePAR_OK; - PAR_DBG_PRINT("PAR_NVM: writing persistent parameter, par_num=%u id=%u", (unsigned)par_num, (unsigned)par_cfg->id); + PAR_DBG_PRINT("PAR_NVM: writing persistent parameter, par_num=%u id=%u", (unsigned)par_num, (unsigned)PAR_NVM_CFG_ID_VALUE(par_cfg)); (void)par_get(par_num, &obj_data.data); - obj_data.id = par_cfg->id; - par_addr = par_nvm_get_nvm_lut_addr(obj_data.id); +#if (1 == PAR_CFG_NVM_RECORD_LAYOUT_HAS_STORED_ID) + obj_data.id = (uint16_t)PAR_NVM_CFG_ID_VALUE(par_cfg); +#endif + par_addr = par_nvm_get_nvm_lut_addr(par_num); store_status = par_nvm_layout_write(gp_store, par_addr, par_num, &obj_data); if (ePAR_OK != store_status) { status |= ePAR_ERROR_NVM; PAR_ERR_PRINT("PAR_NVM: parameter write failed, par_num=%u id=%u addr=0x%08lX err=%u", (unsigned)par_num, - (unsigned)obj_data.id, + (unsigned)PAR_NVM_CFG_ID_VALUE(par_cfg), (unsigned long)par_addr, (unsigned)store_status); } @@ -987,8 +1298,8 @@ par_status_t par_nvm_write_all(void) { PAR_ERR_PRINT("PAR_NVM: bulk write aborted, par_num=%u id=%u addr=0x%08lX err=%u", (unsigned)par_num, - (unsigned)par_get_config(par_num)->id, - (unsigned long)par_nvm_get_nvm_lut_addr(par_get_config(par_num)->id), + (unsigned)PAR_NVM_CFG_ID_VALUE(par_get_config(par_num)), + (unsigned long)par_nvm_get_nvm_lut_addr(par_num), (unsigned)status); break; } diff --git a/src/persist/par_nvm_layout.h b/src/persist/par_nvm_layout.h index 539e054..24db435 100644 --- a/src/persist/par_nvm_layout.h +++ b/src/persist/par_nvm_layout.h @@ -3,7 +3,7 @@ * @brief Declare private persisted-record layout interfaces. * @author wdfk-prog () * @version 1.0 - * @date 2026-04-06 + * @date 2026-04-11 * * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. * @@ -11,6 +11,7 @@ * @par Change Log: * Date Version Author Description * 2026-04-06 1.0 wdfk-prog first version + * 2026-04-11 1.1 wdfk-prog split layout structs by selected NVM layout */ #ifndef _PAR_NVM_LAYOUT_H_ #define _PAR_NVM_LAYOUT_H_ @@ -28,35 +29,131 @@ #define PAR_NVM_RECORD_CRC_SIZE ((uint32_t)sizeof(uint8_t)) #define PAR_NVM_RECORD_DATA_SLOT_SIZE ((uint8_t)sizeof(par_type_t)) -#if (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE) -#define PAR_NVM_LAYOUT_OBJ_HAS_SIZE_FIELD (0) -#else -#define PAR_NVM_LAYOUT_OBJ_HAS_SIZE_FIELD (1) -#endif +#if (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE) +/** + * @brief Selected fixed-slot layout with explicit size descriptor. + */ +typedef struct +{ + uint16_t id; /**< Parameter ID. */ + uint8_t size; /**< Serialized payload-size descriptor. */ + uint8_t crc; /**< CRC-8 over id, size, and payload bytes. */ + par_type_t data; /**< Fixed 4-byte payload slot. */ +} par_nvm_layout_fixed_slot_with_size_record_t; +typedef struct +{ + uint16_t id; /**< Parameter ID. */ + uint8_t size; /**< Serialized payload-size descriptor. */ + par_type_t data; /**< Canonical parameter value. */ +} par_nvm_layout_fixed_slot_with_size_data_obj_t; + +typedef par_nvm_layout_fixed_slot_with_size_data_obj_t par_nvm_data_obj_t; +#elif (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE) /** - * @brief Canonical payload view used by the common NVM flow. + * @brief Selected fixed-slot layout without a size descriptor. * - * @details This type is intentionally not the same thing as every serialized - * on-storage record. It represents only the logical payload that the common - * load/save flow needs: identifier, optional layout-selected size descriptor, - * and parameter bytes. Layouts that place CRC bytes inside the serialized - * record may use a private local record type in their `.c` file. + * @note The serialized image is 7 bytes: id[2] + crc[1] + payload[4]. + * Do not derive the persisted size from sizeof(this type) because some + * compilers may append trailing padding to the in-memory view. */ +typedef struct +{ + uint16_t id; /**< Parameter ID. */ + uint8_t crc; /**< CRC-8 over id and payload bytes. */ + uint8_t payload[PAR_NVM_RECORD_DATA_SLOT_SIZE]; /**< Fixed 4-byte payload slot. */ +} par_nvm_layout_fixed_slot_no_size_record_t; + typedef struct { uint16_t id; /**< Parameter ID. */ -#if (1 == PAR_NVM_LAYOUT_OBJ_HAS_SIZE_FIELD) - uint8_t size; /**< Layout-selected payload size descriptor. */ + par_type_t data; /**< Canonical parameter value. */ +} par_nvm_layout_fixed_slot_no_size_data_obj_t; + +typedef par_nvm_layout_fixed_slot_no_size_data_obj_t par_nvm_data_obj_t; +#elif (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_COMPACT_PAYLOAD) +/** + * @brief Selected compact layout with id, size, crc, and variable payload bytes. + * + * @note The payload width is variable. Only the first @ref size bytes inside + * @ref payload belong to the serialized record. + */ +typedef struct +{ + uint16_t id; /**< Parameter ID. */ + uint8_t size; /**< Serialized payload-size descriptor. */ + uint8_t crc; /**< CRC-8 over id, size, and payload bytes. */ + uint8_t payload[PAR_NVM_RECORD_DATA_SLOT_SIZE]; /**< Maximum payload storage. */ +} par_nvm_layout_compact_payload_record_t; + +typedef struct +{ + uint16_t id; /**< Parameter ID. */ + uint8_t size; /**< Serialized payload-size descriptor. */ + par_type_t data; /**< Canonical parameter value. */ +} par_nvm_layout_compact_payload_data_obj_t; + +typedef par_nvm_layout_compact_payload_data_obj_t par_nvm_data_obj_t; +#elif (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_PAYLOAD_ONLY) +/** + * @brief Selected fixed persistent-order payload-only layout. + * + * @note Only crc + active payload bytes are serialized. The persisted record + * does not contain an ID field or a size descriptor. + */ +typedef struct +{ + uint8_t crc; /**< CRC-8 over payload bytes. */ + uint8_t payload[PAR_NVM_RECORD_DATA_SLOT_SIZE]; /**< Maximum payload storage. */ +} par_nvm_layout_fixed_payload_only_record_t; + +typedef struct +{ + par_type_t data; /**< Canonical parameter value. */ +} par_nvm_layout_fixed_payload_only_data_obj_t; + +typedef par_nvm_layout_fixed_payload_only_data_obj_t par_nvm_data_obj_t; +#elif (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_GROUPED_PAYLOAD_ONLY) +/** + * @brief Selected grouped payload-only layout. + * + * @note Only crc + active payload bytes are serialized. Persistent-slot order + * is regrouped into 8-bit, 16-bit, and 32-bit payload bands. + */ +typedef struct +{ + uint8_t crc; /**< CRC-8 over payload bytes. */ + uint8_t payload[PAR_NVM_RECORD_DATA_SLOT_SIZE]; /**< Maximum payload storage. */ +} par_nvm_layout_grouped_payload_only_record_t; + +typedef struct +{ + par_type_t data; /**< Canonical parameter value. */ +} par_nvm_layout_grouped_payload_only_data_obj_t; + +typedef par_nvm_layout_grouped_payload_only_data_obj_t par_nvm_data_obj_t; +#else +#error "Unsupported PAR_CFG_NVM_RECORD_LAYOUT selection." #endif - par_type_t data; /**< Parameter value bytes in native target order. */ -} par_nvm_data_obj_t; -uint8_t par_nvm_layout_calc_crc(const uint16_t id, - const uint8_t size_desc, +uint8_t par_nvm_layout_payload_size_from_type(const par_type_list_t type); +uint8_t par_nvm_layout_payload_size_from_par_num(const par_num_t par_num); +void par_nvm_layout_pack_payload_bytes(const par_type_list_t type, + const par_type_t * const p_data, + uint8_t * const p_payload); +void par_nvm_layout_unpack_payload_bytes(const par_type_list_t type, + const uint8_t * const p_payload, + par_type_t * const p_data); + +uint8_t par_nvm_layout_calc_crc(const uint8_t size_desc, const uint8_t * const p_payload, const uint8_t payload_size, const bool include_size_desc); +uint8_t par_nvm_layout_calc_crc_with_id(const uint16_t id, + const uint8_t size_desc, + const uint8_t * const p_payload, + const uint8_t payload_size, + const bool include_size_desc); uint32_t par_nvm_layout_record_size_from_par_num(const par_num_t par_num); uint32_t par_nvm_layout_addr_from_persist_idx(const uint32_t first_data_obj_addr, diff --git a/src/persist/par_nvm_layout_compact_payload.c b/src/persist/par_nvm_layout_compact_payload.c index 9f86ed6..5659b19 100644 --- a/src/persist/par_nvm_layout_compact_payload.c +++ b/src/persist/par_nvm_layout_compact_payload.c @@ -1,30 +1,13 @@ /** * @file par_nvm_layout_compact_payload.c - * @brief Implement the compact persisted-record layout with natural payload width. + * @brief Implement the compact persisted-record layout with id, size, crc, and payload bytes. * @author wdfk-prog () * @version 1.0 * @date 2026-04-06 * * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. * - * @details - * This compact layout stores only the natural payload width of each parameter - * type (1/2/4 bytes). For that reason, serialization and deserialization are - * implemented through explicit type-matched local objects instead of by taking - * the first N bytes from a wider 32-bit temporary. - * - * Copying partial bytes from a uint32_t object is endianness-sensitive. - * On little-endian targets the first bytes in memory happen to contain the - * narrow U8/U16 payload, but on big-endian targets those first bytes belong - * to the high-order part of the 32-bit object representation. That would make - * narrow-value storage and CRC calculation depend on target endianness in the - * wrong way. - * - * To keep the native-endian storage model self-consistent on both little- - * endian and big-endian MCUs, this implementation packs and unpacks - * U8/I8, U16/I16, and U32/I32/F32 through explicit same-width typed objects - * and copies exactly that full object representation. - * + * @note : * @par Change Log: * Date Version Author Description * 2026-04-06 1.0 wdfk-prog first version @@ -35,174 +18,23 @@ #include -#define PAR_NVM_LAYOUT_RECORD_OVERHEAD (PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_SIZE_FIELD_SIZE + PAR_NVM_RECORD_CRC_SIZE) -#define PAR_NVM_LAYOUT_RECORD_MAX_SIZE (PAR_NVM_LAYOUT_RECORD_OVERHEAD + PAR_NVM_RECORD_DATA_SLOT_SIZE) - /** - * @brief Resolve natural payload size from parameter type. - * - * @param type Parameter type. - * @return Natural payload width in bytes. - */ -static uint8_t par_nvm_layout_payload_size_from_type(const par_type_list_t type) -{ - switch (type) - { - case ePAR_TYPE_U8: - case ePAR_TYPE_I8: - return 1U; - - case ePAR_TYPE_U16: - case ePAR_TYPE_I16: - return 2U; - - case ePAR_TYPE_U32: - case ePAR_TYPE_I32: -#if (1 == PAR_CFG_ENABLE_TYPE_F32) - case ePAR_TYPE_F32: -#endif - return 4U; - - case ePAR_TYPE_NUM_OF: - default: - PAR_ASSERT(0); - return PAR_NVM_RECORD_DATA_SLOT_SIZE; - } -} - -/** - * @brief Serialize one parameter value to native-endian payload bytes. - * - * @details Narrow integer types are serialized through explicit typed locals - * so the compact layout does not depend on the byte placement of a 32-bit - * temporary carrier object. - * - * @param type Parameter type. - * @param p_data Canonical parameter value. - * @param p_payload Output payload buffer. + * @brief Serialized overhead of one compact-payload record. */ -static void par_nvm_layout_pack_payload_bytes(const par_type_list_t type, - const par_type_t * const p_data, - uint8_t * const p_payload) -{ - PAR_ASSERT((NULL != p_data) && (NULL != p_payload)); - - switch (type) - { - case ePAR_TYPE_U8: - { - const uint8_t value = (uint8_t)(*p_data); - memcpy(p_payload, &value, sizeof(value)); - break; - } - - case ePAR_TYPE_I8: - { - const int8_t value = (int8_t)(*p_data); - memcpy(p_payload, &value, sizeof(value)); - break; - } - - case ePAR_TYPE_U16: - { - const uint16_t value = (uint16_t)(*p_data); - memcpy(p_payload, &value, sizeof(value)); - break; - } - - case ePAR_TYPE_I16: - { - const int16_t value = (int16_t)(*p_data); - memcpy(p_payload, &value, sizeof(value)); - break; - } - - case ePAR_TYPE_U32: - case ePAR_TYPE_I32: -#if (1 == PAR_CFG_ENABLE_TYPE_F32) - case ePAR_TYPE_F32: -#endif - memcpy(p_payload, p_data, PAR_NVM_RECORD_DATA_SLOT_SIZE); - break; - - case ePAR_TYPE_NUM_OF: - default: - PAR_ASSERT(0); - break; - } -} - +#define PAR_NVM_LAYOUT_RECORD_OVERHEAD (PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_SIZE_FIELD_SIZE + PAR_NVM_RECORD_CRC_SIZE) /** - * @brief Deserialize native-endian payload bytes into the canonical value carrier. - * - * @details Narrow integer types are reconstructed through explicit typed locals - * so the compact layout does not depend on partial writes into a 32-bit - * carrier object's storage representation. - * - * @param type Parameter type. - * @param p_payload Input payload buffer. - * @param p_data Output canonical parameter value. + * @brief Maximum serialized size of one compact-payload record. */ -static void par_nvm_layout_unpack_payload_bytes(const par_type_list_t type, - const uint8_t * const p_payload, - par_type_t * const p_data) -{ - PAR_ASSERT((NULL != p_payload) && (NULL != p_data)); - - switch (type) - { - case ePAR_TYPE_U8: - { - uint8_t value = 0U; - memcpy(&value, p_payload, sizeof(value)); - *p_data = (par_type_t)value; - break; - } - - case ePAR_TYPE_I8: - { - int8_t value = 0; - memcpy(&value, p_payload, sizeof(value)); - *p_data = (par_type_t)value; - break; - } - - case ePAR_TYPE_U16: - { - uint16_t value = 0U; - memcpy(&value, p_payload, sizeof(value)); - *p_data = (par_type_t)value; - break; - } - - case ePAR_TYPE_I16: - { - int16_t value = 0; - memcpy(&value, p_payload, sizeof(value)); - *p_data = (par_type_t)value; - break; - } - - case ePAR_TYPE_U32: - case ePAR_TYPE_I32: -#if (1 == PAR_CFG_ENABLE_TYPE_F32) - case ePAR_TYPE_F32: -#endif - memcpy(p_data, p_payload, PAR_NVM_RECORD_DATA_SLOT_SIZE); - break; +#define PAR_NVM_LAYOUT_RECORD_MAX_SIZE (PAR_NVM_LAYOUT_RECORD_OVERHEAD + PAR_NVM_RECORD_DATA_SLOT_SIZE) - case ePAR_TYPE_NUM_OF: - default: - PAR_ASSERT(0); - break; - } -} +PAR_STATIC_ASSERT(par_nvm_layout_compact_payload_record_payload_slot_is_4_bytes, + (sizeof(((par_nvm_layout_compact_payload_record_t *)0)->payload) == 4u)); /** - * @brief Resolve serialized record size from payload width. + * @brief Resolve serialized record size from the active payload width. * - * @param payload_size Payload width in bytes. - * @return Serialized compact-record size in bytes. + * @param payload_size Active payload width in bytes. + * @return Serialized record size in bytes. */ static uint32_t par_nvm_layout_record_size_from_payload_size(const uint8_t payload_size) { @@ -210,26 +42,23 @@ static uint32_t par_nvm_layout_record_size_from_payload_size(const uint8_t paylo } /** - * @brief Get serialized record size for one live parameter. + * @brief Get serialized record size for one persistent parameter. * * @param par_num Live parameter number. - * @return Serialized compact-record size in bytes. + * @return Serialized record size in bytes. */ uint32_t par_nvm_layout_record_size_from_par_num(const par_num_t par_num) { - const par_cfg_t * const p_cfg = par_get_config(par_num); - - PAR_ASSERT(NULL != p_cfg); - return par_nvm_layout_record_size_from_payload_size(par_nvm_layout_payload_size_from_type(p_cfg->type)); + return par_nvm_layout_record_size_from_payload_size(par_nvm_layout_payload_size_from_par_num(par_num)); } /** - * @brief Resolve record address from persistent slot index. + * @brief Resolve record address for the compact-payload layout. * - * @param first_data_obj_addr Start address of the first data record. - * @param persist_idx Persistent slot index. - * @param p_persist_slot_to_par_num Compile-time persistent slot map. - * @return Start address of the serialized record. + * @param first_data_obj_addr Start address of the first persisted object. + * @param persist_idx Compile-time persistent slot index. + * @param p_persist_slot_to_par_num Persistent-slot to live-parameter mapping. + * @return Absolute NVM address of the selected record. */ uint32_t par_nvm_layout_addr_from_persist_idx(const uint32_t first_data_obj_addr, const uint16_t persist_idx, @@ -248,12 +77,12 @@ uint32_t par_nvm_layout_addr_from_persist_idx(const uint32_t first_data_obj_addr } /** - * @brief Read one compact-payload record. + * @brief Read one compact-payload record from NVM. * - * @param p_store Mounted storage backend. - * @param addr Serialized record address. + * @param p_store Storage backend API. + * @param addr Record start address. * @param par_num Live parameter number. - * @param p_obj Output canonical payload view. + * @param p_obj Output canonical object. * @return Operation status. */ par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, @@ -262,17 +91,15 @@ par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, par_nvm_data_obj_t * const p_obj) { uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_MAX_SIZE] = { 0U }; - uint8_t size_desc = 0U; - uint8_t crc_stored = 0U; - uint8_t crc_calc = 0U; - const uint8_t * const p_payload = &record_buf[PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_SIZE_FIELD_SIZE + PAR_NVM_RECORD_CRC_SIZE]; const par_cfg_t * const p_cfg = par_get_config(par_num); - const uint8_t expected_payload_size = par_nvm_layout_payload_size_from_type(p_cfg->type); + const uint8_t expected_payload_size = par_nvm_layout_payload_size_from_par_num(par_num); const uint32_t record_size = par_nvm_layout_record_size_from_payload_size(expected_payload_size); - - PAR_ASSERT(NULL != p_cfg); + uint8_t size_desc = 0; + const uint8_t * const p_payload = &record_buf[PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_SIZE_FIELD_SIZE + PAR_NVM_RECORD_CRC_SIZE]; + uint8_t crc_calc = 0U; PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); + PAR_ASSERT(NULL != p_cfg); memset(p_obj, 0, sizeof(*p_obj)); if (ePAR_OK != p_store->read(addr, record_size, record_buf)) @@ -282,15 +109,13 @@ par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, memcpy(&p_obj->id, &record_buf[0], sizeof(p_obj->id)); size_desc = record_buf[PAR_NVM_RECORD_ID_SIZE]; - crc_stored = record_buf[PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_SIZE_FIELD_SIZE]; - if (size_desc != expected_payload_size) { return ePAR_ERROR; } - crc_calc = par_nvm_layout_calc_crc(p_obj->id, size_desc, p_payload, size_desc, true); - if (crc_calc != crc_stored) + crc_calc = par_nvm_layout_calc_crc_with_id(p_obj->id, size_desc, p_payload, size_desc, true); + if (crc_calc != record_buf[PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_SIZE_FIELD_SIZE]) { return ePAR_ERROR_CRC; } @@ -301,12 +126,12 @@ par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, } /** - * @brief Write one compact-payload record. + * @brief Write one compact-payload record to NVM. * - * @param p_store Mounted storage backend. - * @param addr Serialized record address. + * @param p_store Storage backend API. + * @param addr Record start address. * @param par_num Live parameter number. - * @param p_obj Input canonical payload view. + * @param p_obj Canonical object to serialize. * @return Operation status. */ par_status_t par_nvm_layout_write(const par_store_backend_api_t * const p_store, @@ -315,17 +140,17 @@ par_status_t par_nvm_layout_write(const par_store_backend_api_t * const p_store, const par_nvm_data_obj_t * const p_obj) { uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_MAX_SIZE] = { 0U }; - uint8_t * const p_payload = &record_buf[PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_SIZE_FIELD_SIZE + PAR_NVM_RECORD_CRC_SIZE]; - uint8_t crc = 0U; const par_cfg_t * const p_cfg = par_get_config(par_num); - const uint8_t payload_size = par_nvm_layout_payload_size_from_type(p_cfg->type); + const uint8_t payload_size = par_nvm_layout_payload_size_from_par_num(par_num); const uint32_t record_size = par_nvm_layout_record_size_from_payload_size(payload_size); + uint8_t * const p_payload = &record_buf[PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_SIZE_FIELD_SIZE + PAR_NVM_RECORD_CRC_SIZE]; + uint8_t crc = 0U; PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); PAR_ASSERT(NULL != p_cfg); par_nvm_layout_pack_payload_bytes(p_cfg->type, &p_obj->data, p_payload); - crc = par_nvm_layout_calc_crc(p_obj->id, payload_size, p_payload, payload_size, true); + crc = par_nvm_layout_calc_crc_with_id(p_obj->id, payload_size, p_payload, payload_size, true); memcpy(&record_buf[0], &p_obj->id, sizeof(p_obj->id)); record_buf[PAR_NVM_RECORD_ID_SIZE] = payload_size; diff --git a/src/persist/par_nvm_layout_fixed_payload_only.c b/src/persist/par_nvm_layout_fixed_payload_only.c new file mode 100644 index 0000000..cb0da22 --- /dev/null +++ b/src/persist/par_nvm_layout_fixed_payload_only.c @@ -0,0 +1,158 @@ +/** + * @file par_nvm_layout_fixed_payload_only.c + * @brief Implement the fixed persistent-order payload-only NVM layout. + * @author wdfk-prog () + * @version 1.0 + * @date 2026-04-11 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-10 1.0 wdfk-prog first version + * 2026-04-11 1.1 wdfk-prog restore layout comments and split layout structs + */ +#include "persist/par_nvm_layout.h" + +#if (1 == PAR_CFG_NVM_EN) && (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_PAYLOAD_ONLY) + +#include + +#if (1 == PAR_CFG_NVM_BACKEND_FLASH_EN) +#error "Payload-only NVM layouts are not supported with the flash backend because records are variable width and not guaranteed to stay 8-byte aligned." +#endif + +/** + * @brief Serialized overhead of one payload-only record. + */ +#define PAR_NVM_LAYOUT_RECORD_OVERHEAD ((uint32_t)PAR_NVM_RECORD_CRC_SIZE) + +/** + * @brief Maximum serialized size of one payload-only record. + */ +#define PAR_NVM_LAYOUT_RECORD_MAX_SIZE (PAR_NVM_LAYOUT_RECORD_OVERHEAD + PAR_NVM_RECORD_DATA_SLOT_SIZE) + +PAR_STATIC_ASSERT(par_nvm_layout_fixed_payload_only_record_payload_slot_is_4_bytes, + (sizeof(((par_nvm_layout_fixed_payload_only_record_t *)0)->payload) == 4u)); + +/** + * @brief Resolve serialized record size from the active payload width. + * + * @param payload_size Active payload width in bytes. + * @return Serialized record size in bytes. + */ +static uint32_t par_nvm_layout_record_size_from_payload_size(const uint8_t payload_size) +{ + return (PAR_NVM_LAYOUT_RECORD_OVERHEAD + (uint32_t)payload_size); +} + +/** + * @brief Get serialized record size for one persistent parameter. + * + * @param par_num Live parameter number. + * @return Serialized record size in bytes. + */ +uint32_t par_nvm_layout_record_size_from_par_num(const par_num_t par_num) +{ + return par_nvm_layout_record_size_from_payload_size(par_nvm_layout_payload_size_from_par_num(par_num)); +} + +/** + * @brief Resolve record address for the fixed persistent-order payload-only layout. + * + * @details This layout does not store a size field in NVM. Address resolution + * therefore walks the compile-time persistent-order table and accumulates the + * natural serialized width of each preceding slot. + * + * @param first_data_obj_addr Start address of the first persisted object. + * @param persist_idx Compile-time persistent slot index. + * @param p_persist_slot_to_par_num Persistent-slot to live-parameter mapping. + * @return Absolute NVM address of the selected record. + */ +uint32_t par_nvm_layout_addr_from_persist_idx(const uint32_t first_data_obj_addr, + const uint16_t persist_idx, + const par_num_t * const p_persist_slot_to_par_num) +{ + uint32_t addr = first_data_obj_addr; + + PAR_ASSERT(NULL != p_persist_slot_to_par_num); + + for (uint16_t it = 0U; it < persist_idx; it++) + { + addr += par_nvm_layout_record_size_from_par_num(p_persist_slot_to_par_num[it]); + } + + return addr; +} + +/** + * @brief Read one payload-only record from NVM. + * + * @param p_store Storage backend API. + * @param addr Record start address. + * @param par_num Live parameter number. + * @param p_obj Output canonical object. + * @return Operation status. + */ +par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + par_nvm_data_obj_t * const p_obj) +{ + uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_MAX_SIZE] = { 0U }; + const par_cfg_t * const p_cfg = par_get_config(par_num); + const uint8_t payload_size = par_nvm_layout_payload_size_from_par_num(par_num); + const uint32_t record_size = par_nvm_layout_record_size_from_payload_size(payload_size); + const uint8_t * const p_payload = &record_buf[PAR_NVM_RECORD_CRC_SIZE]; + uint8_t crc_calc = 0U; + + PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); + PAR_ASSERT(NULL != p_cfg); + memset(p_obj, 0, sizeof(*p_obj)); + + if (ePAR_OK != p_store->read(addr, record_size, record_buf)) + { + return ePAR_ERROR_NVM; + } + + crc_calc = par_nvm_layout_calc_crc(0U, p_payload, payload_size, false); + if (crc_calc != record_buf[0]) + { + return ePAR_ERROR_CRC; + } + + par_nvm_layout_unpack_payload_bytes(p_cfg->type, p_payload, &p_obj->data); + return ePAR_OK; +} + +/** + * @brief Write one payload-only record to NVM. + * + * @param p_store Storage backend API. + * @param addr Record start address. + * @param par_num Live parameter number. + * @param p_obj Canonical object to serialize. + * @return Operation status. + */ +par_status_t par_nvm_layout_write(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) +{ + uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_MAX_SIZE] = { 0U }; + const par_cfg_t * const p_cfg = par_get_config(par_num); + const uint8_t payload_size = par_nvm_layout_payload_size_from_par_num(par_num); + const uint32_t record_size = par_nvm_layout_record_size_from_payload_size(payload_size); + uint8_t * const p_payload = &record_buf[PAR_NVM_RECORD_CRC_SIZE]; + + PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); + PAR_ASSERT(NULL != p_cfg); + + par_nvm_layout_pack_payload_bytes(p_cfg->type, &p_obj->data, p_payload); + record_buf[0] = par_nvm_layout_calc_crc(0U, p_payload, payload_size, false); + + return (ePAR_OK == p_store->write(addr, record_size, record_buf)) ? ePAR_OK : ePAR_ERROR_NVM; +} + +#endif /* fixed-payload-only */ diff --git a/src/persist/par_nvm_layout_fixed_slot_no_size.c b/src/persist/par_nvm_layout_fixed_slot_no_size.c index c8e1a95..2ef0b03 100644 --- a/src/persist/par_nvm_layout_fixed_slot_no_size.c +++ b/src/persist/par_nvm_layout_fixed_slot_no_size.c @@ -23,20 +23,21 @@ #endif /** - * @brief Serialized record size in bytes. + * @brief Serialized size of one fixed-slot record without a size field. * - * @details This backend stores `id(2) + crc(1) + data(4)` as a 7-byte record. - * A raw C struct is intentionally not used for on-storage I/O here because the - * 7-byte wire format does not naturally match the default alignment rules of - * ordinary structs on common targets. + * @note The persisted image is always 7 bytes. The macro intentionally avoids + * sizeof(record-type) so trailing padding cannot change the serialized format. */ -#define PAR_NVM_LAYOUT_RECORD_SIZE (PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_CRC_SIZE + PAR_NVM_RECORD_DATA_SLOT_SIZE) +#define PAR_NVM_LAYOUT_RECORD_SIZE (PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_CRC_SIZE + (uint32_t)PAR_NVM_RECORD_DATA_SLOT_SIZE) + +PAR_STATIC_ASSERT(par_nvm_layout_fixed_no_size_payload_slot_is_4_bytes, + (sizeof(((par_nvm_layout_fixed_slot_no_size_record_t *)0)->payload) == 4u)); /** - * @brief Get serialized record size for one live parameter. + * @brief Get serialized record size for one persistent parameter. * * @param par_num Live parameter number. - * @return Fixed serialized record size in bytes. + * @return Serialized record size in bytes. */ uint32_t par_nvm_layout_record_size_from_par_num(const par_num_t par_num) { @@ -45,12 +46,12 @@ uint32_t par_nvm_layout_record_size_from_par_num(const par_num_t par_num) } /** - * @brief Resolve record address from persistent slot index. + * @brief Resolve record address for the fixed-slot-no-size layout. * - * @param first_data_obj_addr Start address of the first data record. - * @param persist_idx Persistent slot index. - * @param p_persist_slot_to_par_num Unused compile-time slot map. - * @return Start address of the serialized record. + * @param first_data_obj_addr Start address of the first persisted object. + * @param persist_idx Compile-time persistent slot index. + * @param p_persist_slot_to_par_num Persistent-slot to live-parameter mapping. + * @return Absolute NVM address of the selected record. */ uint32_t par_nvm_layout_addr_from_persist_idx(const uint32_t first_data_obj_addr, const uint16_t persist_idx, @@ -61,16 +62,12 @@ uint32_t par_nvm_layout_addr_from_persist_idx(const uint32_t first_data_obj_addr } /** - * @brief Read one fixed-slot-without-size record. - * - * @details Byte-buffer deserialization is used on purpose instead of direct - * struct I/O so the 7-byte on-storage format stays independent of compiler - * padding, packing pragmas, and unaligned member-access code generation. + * @brief Read one fixed-slot-no-size record from NVM. * - * @param p_store Mounted storage backend. - * @param addr Serialized record address. - * @param par_num Unused live parameter number. - * @param p_obj Output canonical payload view. + * @param p_store Storage backend API. + * @param addr Record start address. + * @param par_num Live parameter number. + * @param p_obj Output canonical object. * @return Operation status. */ par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, @@ -79,8 +76,7 @@ par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, par_nvm_data_obj_t * const p_obj) { uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_SIZE] = { 0U }; - uint32_t payload_raw = 0U; - uint8_t crc_stored = 0U; + par_type_t payload_raw = { 0U }; uint8_t crc_calc = 0U; (void)par_num; @@ -93,30 +89,29 @@ par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, } memcpy(&p_obj->id, &record_buf[0], sizeof(p_obj->id)); - crc_stored = record_buf[PAR_NVM_RECORD_ID_SIZE]; memcpy(&payload_raw, &record_buf[PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_CRC_SIZE], sizeof(payload_raw)); - crc_calc = par_nvm_layout_calc_crc(p_obj->id, 0U, (const uint8_t * const)&payload_raw, PAR_NVM_RECORD_DATA_SLOT_SIZE, false); - if (crc_calc != crc_stored) + crc_calc = par_nvm_layout_calc_crc_with_id(p_obj->id, + 0U, + (const uint8_t * const)&payload_raw, + PAR_NVM_RECORD_DATA_SLOT_SIZE, + false); + if (crc_calc != record_buf[PAR_NVM_RECORD_ID_SIZE]) { return ePAR_ERROR_CRC; } - memcpy(&p_obj->data, &payload_raw, sizeof(payload_raw)); + p_obj->data = payload_raw; return ePAR_OK; } /** - * @brief Write one fixed-slot-without-size record. + * @brief Write one fixed-slot-no-size record to NVM. * - * @details Byte-buffer serialization is used on purpose instead of direct - * struct I/O so the 7-byte on-storage format stays stable across compiler - * padding and alignment choices. - * - * @param p_store Mounted storage backend. - * @param addr Serialized record address. - * @param par_num Unused live parameter number. - * @param p_obj Input canonical payload view. + * @param p_store Storage backend API. + * @param addr Record start address. + * @param par_num Live parameter number. + * @param p_obj Canonical object to serialize. * @return Operation status. */ par_status_t par_nvm_layout_write(const par_store_backend_api_t * const p_store, @@ -125,13 +120,17 @@ par_status_t par_nvm_layout_write(const par_store_backend_api_t * const p_store, const par_nvm_data_obj_t * const p_obj) { uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_SIZE] = { 0U }; - uint32_t payload_raw = 0U; + par_type_t payload_raw = p_obj->data; uint8_t crc = 0U; (void)par_num; PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); - memcpy(&payload_raw, &p_obj->data, sizeof(payload_raw)); - crc = par_nvm_layout_calc_crc(p_obj->id, 0U, (const uint8_t * const)&payload_raw, PAR_NVM_RECORD_DATA_SLOT_SIZE, false); + + crc = par_nvm_layout_calc_crc_with_id(p_obj->id, + 0U, + (const uint8_t * const)&payload_raw, + PAR_NVM_RECORD_DATA_SLOT_SIZE, + false); memcpy(&record_buf[0], &p_obj->id, sizeof(p_obj->id)); record_buf[PAR_NVM_RECORD_ID_SIZE] = crc; diff --git a/src/persist/par_nvm_layout_fixed_slot_with_size.c b/src/persist/par_nvm_layout_fixed_slot_with_size.c index 81a2409..31282c5 100644 --- a/src/persist/par_nvm_layout_fixed_slot_with_size.c +++ b/src/persist/par_nvm_layout_fixed_slot_with_size.c @@ -19,30 +19,18 @@ #include /** - * @brief Serialized fixed-slot record with explicit size descriptor. - * - * @details This is a private on-storage record view used only by the - * fixed-slot-with-size backend. It is kept separate from - * `par_nvm_data_obj_t` because the serialized record also contains the CRC - * byte between the size field and the data payload. + * @brief Serialized size of one fixed-slot record with an explicit size field. */ -typedef struct -{ - uint16_t id; /**< Parameter ID. */ - uint8_t size; /**< Serialized payload-size descriptor. */ - uint8_t crc; /**< CRC-8 over id, size, and payload bytes. */ - par_type_t data; /**< Fixed 4-byte payload slot. */ -} par_nvm_layout_record_t; - -#define PAR_NVM_LAYOUT_RECORD_SIZE ((uint32_t)sizeof(par_nvm_layout_record_t)) +#define PAR_NVM_LAYOUT_RECORD_SIZE ((uint32_t)sizeof(par_nvm_layout_fixed_slot_with_size_record_t)) -PAR_STATIC_ASSERT(par_nvm_layout_fixed_with_size_record_is_8_bytes, (sizeof(par_nvm_layout_record_t) == 8u)); +PAR_STATIC_ASSERT(par_nvm_layout_fixed_with_size_record_is_8_bytes, + (sizeof(par_nvm_layout_fixed_slot_with_size_record_t) == 8u)); /** - * @brief Get serialized record size for one live parameter. + * @brief Get serialized record size for one persistent parameter. * * @param par_num Live parameter number. - * @return Fixed serialized record size in bytes. + * @return Serialized record size in bytes. */ uint32_t par_nvm_layout_record_size_from_par_num(const par_num_t par_num) { @@ -51,12 +39,12 @@ uint32_t par_nvm_layout_record_size_from_par_num(const par_num_t par_num) } /** - * @brief Resolve record address from persistent slot index. + * @brief Resolve record address for the fixed-slot-with-size layout. * - * @param first_data_obj_addr Start address of the first data record. - * @param persist_idx Persistent slot index. - * @param p_persist_slot_to_par_num Unused compile-time slot map. - * @return Start address of the serialized record. + * @param first_data_obj_addr Start address of the first persisted object. + * @param persist_idx Compile-time persistent slot index. + * @param p_persist_slot_to_par_num Persistent-slot to live-parameter mapping. + * @return Absolute NVM address of the selected record. */ uint32_t par_nvm_layout_addr_from_persist_idx(const uint32_t first_data_obj_addr, const uint16_t persist_idx, @@ -67,12 +55,12 @@ uint32_t par_nvm_layout_addr_from_persist_idx(const uint32_t first_data_obj_addr } /** - * @brief Read one fixed-slot-with-size record. + * @brief Read one fixed-slot-with-size record from NVM. * - * @param p_store Mounted storage backend. - * @param addr Serialized record address. - * @param par_num Unused live parameter number. - * @param p_obj Output canonical payload view. + * @param p_store Storage backend API. + * @param addr Record start address. + * @param par_num Live parameter number. + * @param p_obj Output canonical object. * @return Operation status. */ par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, @@ -80,7 +68,7 @@ par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, const par_num_t par_num, par_nvm_data_obj_t * const p_obj) { - par_nvm_layout_record_t record = { 0U }; + par_nvm_layout_fixed_slot_with_size_record_t record = { 0U }; uint8_t crc_calc = 0U; (void)par_num; @@ -97,25 +85,29 @@ par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, return ePAR_ERROR; } - crc_calc = par_nvm_layout_calc_crc(record.id, record.size, (const uint8_t * const)&record.data, PAR_NVM_RECORD_DATA_SLOT_SIZE, true); + crc_calc = par_nvm_layout_calc_crc_with_id(record.id, + record.size, + (const uint8_t * const)&record.data, + PAR_NVM_RECORD_DATA_SLOT_SIZE, + true); if (crc_calc != record.crc) { return ePAR_ERROR_CRC; } p_obj->id = record.id; - p_obj->size = PAR_NVM_RECORD_DATA_SLOT_SIZE; + p_obj->size = record.size; p_obj->data = record.data; return ePAR_OK; } /** - * @brief Write one fixed-slot-with-size record. + * @brief Write one fixed-slot-with-size record to NVM. * - * @param p_store Mounted storage backend. - * @param addr Serialized record address. - * @param par_num Unused live parameter number. - * @param p_obj Input canonical payload view. + * @param p_store Storage backend API. + * @param addr Record start address. + * @param par_num Live parameter number. + * @param p_obj Canonical object to serialize. * @return Operation status. */ par_status_t par_nvm_layout_write(const par_store_backend_api_t * const p_store, @@ -123,7 +115,7 @@ par_status_t par_nvm_layout_write(const par_store_backend_api_t * const p_store, const par_num_t par_num, const par_nvm_data_obj_t * const p_obj) { - par_nvm_layout_record_t record = { 0U }; + par_nvm_layout_fixed_slot_with_size_record_t record = { 0U }; (void)par_num; PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); @@ -131,7 +123,11 @@ par_status_t par_nvm_layout_write(const par_store_backend_api_t * const p_store, record.id = p_obj->id; record.size = PAR_NVM_RECORD_DATA_SLOT_SIZE; record.data = p_obj->data; - record.crc = par_nvm_layout_calc_crc(record.id, record.size, (const uint8_t * const)&record.data, PAR_NVM_RECORD_DATA_SLOT_SIZE, true); + record.crc = par_nvm_layout_calc_crc_with_id(record.id, + record.size, + (const uint8_t * const)&record.data, + PAR_NVM_RECORD_DATA_SLOT_SIZE, + true); return (ePAR_OK == p_store->write(addr, PAR_NVM_LAYOUT_RECORD_SIZE, (const uint8_t *)&record)) ? ePAR_OK : ePAR_ERROR_NVM; } diff --git a/src/persist/par_nvm_layout_grouped_payload_only.c b/src/persist/par_nvm_layout_grouped_payload_only.c new file mode 100644 index 0000000..179dd10 --- /dev/null +++ b/src/persist/par_nvm_layout_grouped_payload_only.c @@ -0,0 +1,201 @@ +/** + * @file par_nvm_layout_grouped_payload_only.c + * @brief Implement the grouped payload-only NVM layout. + * @author wdfk-prog () + * @version 1.0 + * @date 2026-04-11 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-10 1.0 wdfk-prog first version + * 2026-04-11 1.1 wdfk-prog restore layout comments and split layout structs + */ +#include "persist/par_nvm_layout.h" + +#if (1 == PAR_CFG_NVM_EN) && (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_GROUPED_PAYLOAD_ONLY) + +#include + +#if (1 == PAR_CFG_NVM_BACKEND_FLASH_EN) +#error "Payload-only NVM layouts are not supported with the flash backend because records are variable width and not guaranteed to stay 8-byte aligned." +#endif + +/** + * @brief Serialized overhead of one grouped payload-only record. + */ +#define PAR_NVM_LAYOUT_RECORD_OVERHEAD ((uint32_t)PAR_NVM_RECORD_CRC_SIZE) + +/** + * @brief Maximum serialized size of one grouped payload-only record. + */ +#define PAR_NVM_LAYOUT_RECORD_MAX_SIZE (PAR_NVM_LAYOUT_RECORD_OVERHEAD + PAR_NVM_RECORD_DATA_SLOT_SIZE) + +PAR_STATIC_ASSERT(par_nvm_layout_grouped_payload_only_record_payload_slot_is_4_bytes, + (sizeof(((par_nvm_layout_grouped_payload_only_record_t *)0)->payload) == 4u)); + +/** + * @brief Resolve serialized record size from the active payload width. + * + * @param payload_size Active payload width in bytes. + * @return Serialized record size in bytes. + */ +static uint32_t par_nvm_layout_record_size_from_payload_size(const uint8_t payload_size) +{ + return (PAR_NVM_LAYOUT_RECORD_OVERHEAD + (uint32_t)payload_size); +} + +/** + * @brief Get serialized record size for one persistent parameter. + * + * @param par_num Live parameter number. + * @return Serialized record size in bytes. + */ +uint32_t par_nvm_layout_record_size_from_par_num(const par_num_t par_num) +{ + return par_nvm_layout_record_size_from_payload_size(par_nvm_layout_payload_size_from_par_num(par_num)); +} + +/** + * @brief Resolve record address for the grouped payload-only layout. + * + * @details Persistent records are regrouped into 8-bit, 16-bit, and 32-bit + * payload bands. Address resolution therefore sums complete preceding groups + * and then adds the same-group prefix before the target record. + * + * @param first_data_obj_addr Start address of the first persisted object. + * @param persist_idx Compile-time persistent slot index. + * @param p_persist_slot_to_par_num Persistent-slot to live-parameter mapping. + * @return Absolute NVM address of the selected record. + */ +uint32_t par_nvm_layout_addr_from_persist_idx(const uint32_t first_data_obj_addr, + const uint16_t persist_idx, + const par_num_t * const p_persist_slot_to_par_num) +{ + uint32_t total_size_8 = 0U; + uint32_t total_size_16 = 0U; + uint32_t prefix_same_group = 0U; + uint8_t target_payload_size = 0U; + + PAR_ASSERT(NULL != p_persist_slot_to_par_num); + PAR_ASSERT(persist_idx < PAR_PERSISTENT_COMPILE_COUNT); + target_payload_size = par_nvm_layout_payload_size_from_par_num(p_persist_slot_to_par_num[persist_idx]); + + for (uint16_t it = 0U; it < PAR_PERSISTENT_COMPILE_COUNT; it++) + { + const uint8_t payload_size = par_nvm_layout_payload_size_from_par_num(p_persist_slot_to_par_num[it]); + const uint32_t record_size = par_nvm_layout_record_size_from_payload_size(payload_size); + + switch (payload_size) + { + case 1U: + total_size_8 += record_size; + break; + + case 2U: + total_size_16 += record_size; + break; + + case 4U: + break; + + default: + PAR_ASSERT(0); + break; + } + + if ((it < persist_idx) && (payload_size == target_payload_size)) + { + prefix_same_group += record_size; + } + } + + switch (target_payload_size) + { + case 1U: + return (first_data_obj_addr + prefix_same_group); + + case 2U: + return (first_data_obj_addr + total_size_8 + prefix_same_group); + + case 4U: + return (first_data_obj_addr + total_size_8 + total_size_16 + prefix_same_group); + + default: + PAR_ASSERT(0); + return first_data_obj_addr; + } +} + +/** + * @brief Read one grouped payload-only record from NVM. + * + * @param p_store Storage backend API. + * @param addr Record start address. + * @param par_num Live parameter number. + * @param p_obj Output canonical object. + * @return Operation status. + */ +par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + par_nvm_data_obj_t * const p_obj) +{ + uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_MAX_SIZE] = { 0U }; + const par_cfg_t * const p_cfg = par_get_config(par_num); + const uint8_t payload_size = par_nvm_layout_payload_size_from_par_num(par_num); + const uint32_t record_size = par_nvm_layout_record_size_from_payload_size(payload_size); + const uint8_t * const p_payload = &record_buf[PAR_NVM_RECORD_CRC_SIZE]; + uint8_t crc_calc = 0U; + + PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); + PAR_ASSERT(NULL != p_cfg); + memset(p_obj, 0, sizeof(*p_obj)); + + if (ePAR_OK != p_store->read(addr, record_size, record_buf)) + { + return ePAR_ERROR_NVM; + } + + crc_calc = par_nvm_layout_calc_crc(0U, p_payload, payload_size, false); + if (crc_calc != record_buf[0]) + { + return ePAR_ERROR_CRC; + } + + par_nvm_layout_unpack_payload_bytes(p_cfg->type, p_payload, &p_obj->data); + return ePAR_OK; +} + +/** + * @brief Write one grouped payload-only record to NVM. + * + * @param p_store Storage backend API. + * @param addr Record start address. + * @param par_num Live parameter number. + * @param p_obj Canonical object to serialize. + * @return Operation status. + */ +par_status_t par_nvm_layout_write(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) +{ + uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_MAX_SIZE] = { 0U }; + const par_cfg_t * const p_cfg = par_get_config(par_num); + const uint8_t payload_size = par_nvm_layout_payload_size_from_par_num(par_num); + const uint32_t record_size = par_nvm_layout_record_size_from_payload_size(payload_size); + uint8_t * const p_payload = &record_buf[PAR_NVM_RECORD_CRC_SIZE]; + + PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); + PAR_ASSERT(NULL != p_cfg); + + par_nvm_layout_pack_payload_bytes(p_cfg->type, &p_obj->data, p_payload); + record_buf[0] = par_nvm_layout_calc_crc(0U, p_payload, payload_size, false); + + return (ePAR_OK == p_store->write(addr, record_size, record_buf)) ? ePAR_OK : ePAR_ERROR_NVM; +} + +#endif /* grouped-payload-only */ diff --git a/src/persist/par_nvm_table_id.c b/src/persist/par_nvm_table_id.c index 5834fda..a035385 100644 --- a/src/persist/par_nvm_table_id.c +++ b/src/persist/par_nvm_table_id.c @@ -2,8 +2,8 @@ * @file par_nvm_table_id.c * @brief Implement the parameter-table ID hash adapter. * @author wdfk-prog () - * @version 1.0 - * @date 2026-03-30 + * @version 1.1 + * @date 2026-04-11 * * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. * @@ -11,23 +11,13 @@ * @par Change Log: * Date Version Author Description * 2026-03-30 1.0 wdfk-prog first version + * 2026-04-11 1.1 wdfk-prog add stored-prefix table-ID calculation */ #include "par.h" #include "persist/fnv.h" #include "persist/par_nvm_table_id.h" -/** - * @brief Serialized table-ID record size for one persisted parameter. - */ -enum -{ - PAR_NVM_TABLE_ID_REC_SIZE = sizeof(((par_cfg_t *)0)->type) -#if (1 == PAR_CFG_ENABLE_ID) - + sizeof(((par_cfg_t *)0)->id) -#endif -}; - /** * @brief Update FNV-1a context with one platform-native scalar image. * @@ -46,24 +36,30 @@ static void par_nvm_table_id_hash_update(Fnv32_t * const p_hval, } /** - * @brief Calculate live parameter-table ID. + * @brief Calculate the live compatibility digest for one stored persistent prefix. + * + * @details The digest covers only metadata that affects the managed NVM image + * compatibility of the stored persistent prefix: schema version, selected + * record layout, stored persistent-object count, persistent-parameter order, + * and parameter type. Self-describing layouts additionally hash the external + * parameter ID. Payload-only layouts intentionally exclude external parameter + * IDs and rely on PAR_CFG_TABLE_ID_SCHEMA_VER to invalidate semantic-only + * prefix remaps that keep the same byte layout. Under the single-target + * native-endian profile, each scalar is hashed exactly as it is represented in + * memory on the running platform. * - * @details The digest covers only metadata that affects the NVM storage - * compatibility of persisted parameters: schema version, selected persisted - * record layout, persisted-parameter count, persisted-parameter order, type, - * and optional ID field. Under the - * single-target native-endian profile, each scalar is hashed exactly as it is - * represented in memory on the running platform. + * @param persistent_count Number of persistent slots covered by the digest. + * @return Live digest for that stored prefix. */ -uint32_t par_nvm_table_id_calc(void) +uint32_t par_nvm_table_id_calc_for_count(const uint16_t persistent_count) { Fnv32_t hval = FNV1_32A_INIT; uint32_t serialized_size = 0U; + uint16_t hashed_count = 0U; const uint32_t schema_version = (uint32_t)PAR_CFG_TABLE_ID_SCHEMA_VER; const uint32_t record_layout = (uint32_t)PAR_CFG_NVM_RECORD_LAYOUT; - const uint16_t persistent_count = (uint16_t)PAR_PERSISTENT_COMPILE_COUNT; - const uint32_t expected_size = (uint32_t)sizeof(schema_version) + (uint32_t)sizeof(record_layout) + - (uint32_t)sizeof(persistent_count) + ((uint32_t)persistent_count * (uint32_t)PAR_NVM_TABLE_ID_REC_SIZE); + + PAR_ASSERT(persistent_count <= (uint16_t)PAR_PERSISTENT_COMPILE_COUNT); par_nvm_table_id_hash_update(&hval, &serialized_size, &schema_version, (uint32_t)sizeof(schema_version)); par_nvm_table_id_hash_update(&hval, &serialized_size, &record_layout, (uint32_t)sizeof(record_layout)); @@ -74,19 +70,28 @@ uint32_t par_nvm_table_id_calc(void) const par_cfg_t * const p_cfg = par_get_config(par_num); const uint8_t type = (uint8_t)p_cfg->type; -#if (1 == PAR_CFG_NVM_EN) if (false == p_cfg->persistent) { continue; } -#endif + + if (hashed_count >= persistent_count) + { + break; + } par_nvm_table_id_hash_update(&hval, &serialized_size, &type, (uint32_t)sizeof(type)); -#if (1 == PAR_CFG_ENABLE_ID) - par_nvm_table_id_hash_update(&hval, &serialized_size, &p_cfg->id, (uint32_t)sizeof(p_cfg->id)); + +#if (1 == PAR_CFG_NVM_RECORD_LAYOUT_HAS_STORED_ID) + { + const uint16_t parameter_id = par_cfg_get_param_id_const(par_num); + par_nvm_table_id_hash_update(&hval, &serialized_size, ¶meter_id, (uint32_t)sizeof(parameter_id)); + } #endif + hashed_count++; } - PAR_ASSERT(serialized_size == expected_size); + PAR_ASSERT(hashed_count == persistent_count); + PAR_ASSERT(serialized_size > 0U); return (uint32_t)hval; } diff --git a/src/persist/par_nvm_table_id.h b/src/persist/par_nvm_table_id.h index ff2e376..f2f1b9e 100644 --- a/src/persist/par_nvm_table_id.h +++ b/src/persist/par_nvm_table_id.h @@ -17,23 +17,17 @@ #include /** - * @brief Calculate the live parameter-table ID. + * @brief Calculate the live compatibility digest for one stored persistent prefix. * - * @details The digest covers only metadata that changes the binary - * compatibility of the persisted NVM image: - * - PAR_CFG_TABLE_ID_SCHEMA_VER - * - selected persisted record layout - * - persistent-parameter count - * - persistent-parameter order - * - parameter type - * - parameter ID - * - * Default values, ranges, names, units, descriptions, and access flags are - * intentionally excluded because they do not change the serialized NVM object - * layout used by par_nvm.c. + * @details The caller provides the number of stored persistent slots that are + * part of the managed NVM image. The digest always covers that stored prefix. + * Layouts with stored IDs additionally hash external parameter IDs, while + * payload-only layouts intentionally exclude external parameter IDs and track + * only byte-layout compatibility for that prefix. * + * @param persistent_count Number of persistent slots covered by the digest. * @return Platform-native 32-bit FNV-1a digest. */ -uint32_t par_nvm_table_id_calc(void); +uint32_t par_nvm_table_id_calc_for_count(uint16_t persistent_count); #endif /* _PAR_NVM_TABLE_ID_H_ */ diff --git a/template/par_cfg_port.htmp b/template/par_cfg_port.htmp index 1e9dbad..71d96d3 100644 --- a/template/par_cfg_port.htmp +++ b/template/par_cfg_port.htmp @@ -6,4 +6,6 @@ /* #define PAR_CFG_NVM_RECORD_LAYOUT ( PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE ) */ /* or PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE */ /* or PAR_CFG_NVM_RECORD_LAYOUT_COMPACT_PAYLOAD */ -#endif \ No newline at end of file +/* or PAR_CFG_NVM_RECORD_LAYOUT_FIXED_PAYLOAD_ONLY */ +/* or PAR_CFG_NVM_RECORD_LAYOUT_GROUPED_PAYLOAD_ONLY */ +#endif From b1fc438a0f882ac6e1525ec6e15a8e6d1f99b4d9 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Mon, 13 Apr 2026 10:55:19 +0800 Subject: [PATCH 33/36] feat[parameters][nvm]: add layout-driven write readback verification introduce a configurable NVM write readback verification path for records and headers move layout-specific address, validation, compatibility, and compare logic behind layout ops document the new write verification flow and clarify that ECC handling policy belongs to the business layer --- README.md | 2 + docs/api-reference.md | 3 + docs/architecture.md | 2 + docs/getting-started.md | 2 + src/par_cfg.h | 21 +- src/persist/backend/par_store_backend.h | 83 ++++- src/persist/par_nvm.c | 298 +++++++++++------- src/persist/par_nvm_layout.h | 195 ++++++++++-- src/persist/par_nvm_layout_compact_payload.c | 237 +++++++++++--- .../par_nvm_layout_fixed_payload_only.c | 218 ++++++++++--- .../par_nvm_layout_fixed_slot_no_size.c | 211 +++++++++++-- .../par_nvm_layout_fixed_slot_with_size.c | 213 +++++++++++-- .../par_nvm_layout_grouped_payload_only.c | 221 ++++++++++--- template/par_cfg_port.htmp | 1 + 14 files changed, 1345 insertions(+), 362 deletions(-) diff --git a/README.md b/README.md index a38c0ff..fa2f413 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,8 @@ This repository contains the reusable module core and templates. A real integrat - Live RAM layout and persisted NVM layout are intentionally different. RAM storage is grouped by value width, while the persistence area stores a compile-time ordered slot list using one selected serialized record layout: fixed 4-byte payload slot with size descriptor, fixed 4-byte payload slot without size descriptor, compact natural-width payload with size descriptor, fixed natural-width payload without stored ID, or grouped natural-width payload without stored ID. - Compile-time persistent order is the primary slot layout contract of the managed NVM image. The stored-ID layouts keep `id` in each record as an integrity and diagnostics field, while the payload-only layouts omit stored `id` and instead rely on compile-time slot order plus table-ID validation. The fixed-slot layouts keep one 4-byte payload slot per persistent parameter, while the compact and payload-only layouts store only the natural 1/2/4-byte payload width. - The serialized NVM header is written explicitly as a fixed 12-byte storage image (`sign(4) + obj_nb(2) + table_id(4) + crc16(2)`), so on-storage layout does not depend on compiler struct padding. Header CRC-16 covers the serialized `obj_nb + table_id` bytes, while each data record carries its own CRC-8 according to the selected record layout. +- `par_nvm.c` now binds one selected persisted-record layout adapter during initialization. Record address calculation, object preparation, stored-object validation, compatibility policy, and optional write-readback comparison are all delegated through that layout ops table, so the common NVM flow no longer carries layout-specific branching. When `PAR_CFG_NVM_WRITE_VERIFY_EN = 1`, parameter writes and header commits force a backend sync and then perform layout-aware readback verification before the write is considered committed. +- If the selected storage medium has ECC or other read-health reporting, treat that as a product-level policy input rather than as an automatic parameter-core decision. The business layer should decide whether an ECC event only needs reporting, should trigger parameter reset or rebuild, or should escalate to a wider system fault response. - CRC calculation is routed through port hooks with bundled software defaults. In this single-target profile the persisted image and the table-ID digest both use the native byte order of the running platform, so no additional byte-order conversion hook is required by the persistence path. - When `PAR_CFG_TABLE_ID_CHECK_EN = 1`, startup compares the stored table-ID against the live compatibility digest for the stored persistent prefix size from the header (`obj_nb`). Layouts with stored IDs hash external parameter IDs, so prefix ID renumbering still invalidates the image there. `FIXED_PAYLOAD_ONLY` intentionally excludes external parameter IDs and validates only prefix byte-layout compatibility (`obj_nb`, persistent order, and parameter type), which allows pure external-ID renumbering and compatible tail growth without a rebuild there. `GROUPED_PAYLOAD_ONLY` also excludes external parameter IDs, but because regrouped addresses depend on the full live persistent set it rebuilds whenever `stored_count != live_count`. Semantic-only prefix remaps that preserve the same byte layout must still be paired with an explicit `PAR_CFG_TABLE_ID_SCHEMA_VER` bump. Defaults, ranges, names, units, descriptions, and access flags remain outside the digest. - A table-ID mismatch is treated as an incompatible persisted-layout change, not as a warning-only condition. Startup restores defaults and rebuilds the managed NVM image. Typical triggers are add/remove/reorder/type/ID changes of persistent parameters and transitions between persistent and non-persistent state. When the stored header count is smaller than the compile-time persistent count, layouts with stable prefix addresses repair the image by appending the missing tail slots from current defaults and rewriting the header count; `GROUPED_PAYLOAD_ONLY` instead treats any stored/live count mismatch as incompatible and rebuilds. A stored count larger than the compile-time count is always treated as incompatible and rebuilt. diff --git a/docs/api-reference.md b/docs/api-reference.md index 79b145a..96ff68b 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -186,6 +186,9 @@ Available only when `PAR_CFG_NVM_EN = 1` and a concrete parameter-storage backen The managed NVM payload area is a compile-time ordered slot list, not a width-group partition like the live RAM layout. Each slot uses the selected serialized record layout: fixed 4-byte payload slot with size descriptor, fixed 4-byte payload slot without size descriptor, compact natural-width payload with size descriptor, fixed natural-width payload without stored ID, or grouped natural-width payload without stored ID. Slot order is derived directly from the compile-time persistent order in `par_table.def`, so the loader restores slot `i` to compile-time persistent slot `i`. If the stored count is larger than the current compile-time persistent count, startup treats the image as incompatible and the managed NVM area is rebuilt from live defaults. If the stored count is smaller, layouts with stable prefix addresses restore the stored prefix and append the missing tail slots from current defaults before the header count is rewritten; `GROUPED_PAYLOAD_ONLY` instead rebuilds on any stored/live count mismatch. Self-describing layouts keep `id` inside each object as a validation and diagnostics field, while the payload-only layouts resolve records entirely from the compile-time slot map. The serialized NVM header is emitted explicitly as a fixed 12-byte image (`sign + obj_nb + table_id + crc16`) rather than by writing raw struct memory. Header CRC-16 covers the serialized native-order `obj_nb + table_id` bytes, so corrupted header bytes fail header validation before any compatibility decision is made. Each small data record uses CRC-8 according to the selected record layout. When `PAR_CFG_TABLE_ID_CHECK_EN = 1`, `par_nvm_init()` compares the stored table-ID against the live compatibility digest for the stored persistent prefix length from the header. Layouts with stored IDs hash parameter IDs inside that prefix. Payload-only layouts hash only the stored prefix byte layout (`obj_nb`, persistent order, and parameter type) while intentionally excluding external parameter IDs. Startup first validates header CRC/signature, then validates compatibility, then loads payload objects only if both checks pass. If the stored count is smaller than the compile-time persistent count, layouts with stable prefix addresses restore the stored prefix, append the missing tail slots from live defaults, and rewrite the header count. `GROUPED_PAYLOAD_ONLY` is excluded from that repair path and rebuilds on any stored/live count mismatch. If the stored count is larger than the compile-time persistent count, the image is treated as incompatible and rebuilt. The collected error bits then drive a centralized recovery flow: NVM access errors restore live RAM values to defaults, while table-ID/header/CRC mismatches restore defaults and rebuild the managed NVM image. Compatible tail growth is therefore allowed for layouts with stable prefix addresses, while prefix add/remove/reorder/type changes still rebuild; layouts with stored IDs additionally rebuild on prefix ID changes, `FIXED_PAYLOAD_ONLY` additionally allows pure external-ID renumbering, and `GROUPED_PAYLOAD_ONLY` rebuilds whenever the stored/live counts differ. When a payload-only prefix is semantically remapped without changing its byte layout, the integrator must bump `PAR_CFG_TABLE_ID_SCHEMA_VER` explicitly. CRC calculation is exposed through port hooks with bundled software defaults. +The runtime NVM core binds one compile-time selected layout ops table during initialization. That table owns address calculation, persisted object preparation, stored-object validation, compatibility policy, and optional readback-compare behavior. When `PAR_CFG_NVM_WRITE_VERIFY_EN = 1`, record writes and header writes both force a backend sync and then run layout-aware readback verification before the write is treated as committed. + +If the selected storage backend or storage medium exposes ECC status, the parameter core still does not hard-code the recovery policy. Whether an ECC event should only be reported, should trigger parameter reset/rebuild, or should escalate into a wider system fault response must be decided by the business layer. | Function | Description | | --- | --- | diff --git a/docs/architecture.md b/docs/architecture.md index 7f26d36..15be542 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -329,6 +329,8 @@ The serialized NVM header is a fixed 12-byte storage image (`sign + obj_nb + tab If the stored table-ID mismatches the live table-ID, the module treats the persisted image as incompatible and rebuilds the managed NVM area from current default values and the current schema. This can happen after an intentional schema/version bump, after parameter persistence layout changes, after stored-image corruption, or when the stored header count is larger than the current compile-time persistent count. If the stored count is smaller than the compile-time count, layouts with stable prefix addresses restore the stored prefix, append the missing tail slots from live defaults, and rewrite the header count to the current schema width; `GROUPED_PAYLOAD_ONLY` instead rebuilds on any stored/live count mismatch. The recovery action is centralized in `par_nvm_init()`: it accumulates status bits from header validation, table-ID validation, and payload loading, then decides whether to restore defaults only or restore defaults plus rewrite the managed NVM image. For this feature, `par_nvm.c` mounts a parameter-storage backend interface during initialization. The core no longer depends directly on a specific repository layout, while Kconfig selects which packaged backend adapter is built. +At the same time, the NVM core binds one compile-time selected persisted-record layout adapter. That adapter owns record address calculation, live-to-persist object preparation, stored-object validation, compatibility policy, and optional readback-compare logic. The common NVM flow therefore stays layout-agnostic even when layouts differ in stored `id`, size descriptors, prefix-append compatibility, or post-write verification semantics. +If the selected storage technology provides ECC or other media-health reporting, that signal is intentionally treated as input to the product policy rather than as an automatic parameter-core action. The business layer should decide whether to log only, rebuild/reset parameters, or escalate to a broader system-level error response. The module intentionally keeps a compile-time ordered slot image instead of introducing a free-layout scanned log. A scanned log would make layout recovery more flexible, but it would also add boot-time scan, compaction, and tombstone handling to a path whose slot order is already fixed by the parameter table. diff --git a/docs/getting-started.md b/docs/getting-started.md index f77ca11..75f0dfe 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -145,6 +145,8 @@ ID-based lookup is generated statically when `PAR_CFG_ENABLE_ID = 1`. Optional s - `PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK` When NVM is enabled, the parameters module requires a concrete packaged storage backend implementation. `src/persist/par_nvm.c` resolves and validates the backend API once during initialization, then uses the mounted callbacks directly for later reads, writes, erases, and sync operations. The RT-Thread AT24CXX backend is available today, while the generic flash backend Kconfig entry is currently only a placeholder and does not build an implementation yet. The module can reuse an already-initialized backend or initialize it on demand and later deinitialize it only when it owns that initialization. Module deinit is conservative: it attempts backend and interface cleanup, and it clears the top-level module init state only after the owned child deinit steps succeed. +When `PAR_CFG_NVM_WRITE_VERIFY_EN = 1`, the NVM path also performs a backend sync plus readback verification after record writes and after header commits. The exact comparison rules are owned by the selected persisted-record layout adapter, not hard-coded in `par_nvm.c`. +If your storage medium provides ECC or similar read-health status, keep the policy decision in the business layer. The parameter module does not decide whether that condition means report-only, parameter reset/rebuild, or a wider product fault response. The persisted NVM image uses a compile-time selected record layout instead of the grouped-width RAM layout used at runtime. Compile-time persistent order remains the primary slot layout contract for startup restore. Layouts that serialize `id` still use it as a validation and diagnostics field inside each record, while the two payload-only fixed layouts omit the serialized `id` and resolve addresses entirely from the compile-time slot map. The fixed-slot layouts keep one 4-byte payload slot per persistent parameter, the compact-payload layout stores only the natural 1/2/4-byte payload width together with a size descriptor, and the two payload-only layouts store only `crc + payload` bytes. Because they do not serialize `id`, they require table-ID checking to stay enabled so schema changes still trigger a rebuild; their compatibility digest is computed from the stored persistent prefix and excludes external parameter IDs. `FIXED_PAYLOAD_ONLY` therefore allows pure ID renumbering and tail-slot growth without forcing a rebuild, while `GROUPED_PAYLOAD_ONLY` still rebuilds whenever the stored/live counts differ because regrouped addresses depend on the full live persistent set. If a payload-only prefix is semantically remapped while keeping the same serialized byte layout, the integrator must bump `PAR_CFG_TABLE_ID_SCHEMA_VER` explicitly because that change is outside the automatic digest. The fixed-slot layout without a size descriptor is intentionally unavailable for a flash backend that requires 8-byte aligned writes, because that serializer uses a 7-byte record, and the two variable-width payload-only layouts are hidden there for the same alignment reason. The header is serialized explicitly as a fixed 12-byte image (`sign + obj_nb + table_id + crc16`). Under the current single-target assumption, fields are stored, validated, and hashed in the native byte order of the running platform instead of being normalized to a cross-platform storage endianness. Header CRC-16 therefore covers the serialized native-order `obj_nb + table_id` bytes, each data object uses layout-specific CRC-8, and the compatibility digest is computed from native-order scalar images including the selected record layout. When the stored count is smaller than the compile-time persistent count, layouts with stable prefix addresses append the missing tail slots from current defaults and rewrite the header count; `GROUPED_PAYLOAD_ONLY` instead treats any stored/live count mismatch as incompatible and rebuilds. When the stored count is larger, startup treats the image as incompatible and rebuilds it. The bundled defaults are provided through `par_if_crc16_accumulate()` and `par_if_crc8_accumulate()`. diff --git a/src/par_cfg.h b/src/par_cfg.h index 0c5e8fe..fc25621 100644 --- a/src/par_cfg.h +++ b/src/par_cfg.h @@ -114,6 +114,17 @@ #define PAR_CFG_NVM_BACKEND_FLASH_EN (0) #endif +/** + * @brief Enable/Disable write-path readback verification for persisted data. + * + * @details When enabled, each persisted-record write and each header write are + * followed by a backend sync and a readback verification step. This improves + * reliability at the cost of additional latency and backend traffic. + */ +#ifndef PAR_CFG_NVM_WRITE_VERIFY_EN +#define PAR_CFG_NVM_WRITE_VERIFY_EN (0) +#endif + /** * @brief Select persisted record layout. * @@ -134,7 +145,7 @@ * @brief Derived layout capability: serialized records store a parameter ID. */ #if (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE) || \ - (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE) || \ + (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE) || \ (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_COMPACT_PAYLOAD) #define PAR_CFG_NVM_RECORD_LAYOUT_HAS_STORED_ID (1) #else @@ -590,7 +601,7 @@ PAR_STATIC_ASSERT(par_id_hash_bits_valid, ((PAR_ID_HASH_BITS > 0u) && (PAR_ID_HA /** * @brief Configuration dependency checks for optional fields/features. */ -#if (1 == PAR_CFG_NVM_EN) && (0 == PAR_CFG_ENABLE_ID) && \ +#if (1 == PAR_CFG_NVM_EN) && (0 == PAR_CFG_ENABLE_ID) && \ (PAR_CFG_NVM_RECORD_LAYOUT != PAR_CFG_NVM_RECORD_LAYOUT_FIXED_PAYLOAD_ONLY) && \ (PAR_CFG_NVM_RECORD_LAYOUT != PAR_CFG_NVM_RECORD_LAYOUT_GROUPED_PAYLOAD_ONLY) #error "Parameter settings invalid: selected NVM layout requires PAR_CFG_ENABLE_ID = 1!" @@ -600,14 +611,14 @@ PAR_STATIC_ASSERT(par_id_hash_bits_valid, ((PAR_ID_HASH_BITS > 0u) && (PAR_ID_HA #error "Parameter settings invalid: flash backend requires PAR_CFG_ENABLE_ID = 1!" #endif -#if (1 == PAR_CFG_NVM_EN) && \ - ((PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_PAYLOAD_ONLY) || \ +#if (1 == PAR_CFG_NVM_EN) && \ + ((PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_PAYLOAD_ONLY) || \ (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_GROUPED_PAYLOAD_ONLY)) && \ (0 == PAR_CFG_TABLE_ID_CHECK_EN) #error "Parameter settings invalid: payload-only NVM layouts require PAR_CFG_TABLE_ID_CHECK_EN = 1!" #endif -#if (1 == PAR_CFG_NVM_BACKEND_FLASH_EN) && \ +#if (1 == PAR_CFG_NVM_BACKEND_FLASH_EN) && \ ((PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE) || \ (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_PAYLOAD_ONLY) || \ (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_GROUPED_PAYLOAD_ONLY)) diff --git a/src/persist/backend/par_store_backend.h b/src/persist/backend/par_store_backend.h index 103daa2..c3477d3 100644 --- a/src/persist/backend/par_store_backend.h +++ b/src/persist/backend/par_store_backend.h @@ -29,24 +29,91 @@ * @details All offsets are relative to the storage region reserved for the * parameter image. The backend owns any region, partition, or device-specific * context needed to execute the operation. + * + * If the selected storage medium provides ECC or other read-health reporting, + * policy handling is intentionally left above this abstraction. The backend + * may log or expose such information through its own implementation-specific + * means, but the parameter core does not mandate whether the application must + * rebuild, reset, continue running, or only report the event. That recovery + * decision belongs to the business layer because acceptable behavior is + * product-specific. */ typedef struct { - /** @brief Initialize the storage backend. */ + /** + * @brief Initialize the storage backend. + * + * @details Prepare the reserved storage region for later byte-addressable + * access. This may mount a partition, initialize a driver, or verify the + * backend context required by the concrete implementation. + * + * @return ePAR_OK on success, otherwise an implementation-defined error. + */ par_status_t (*init)(void); - /** @brief Deinitialize the storage backend. */ + /** + * @brief Deinitialize the storage backend. + * + * @details Release resources acquired by @ref init when the parameter + * module owns backend initialization. + * + * @return ePAR_OK on success, otherwise an implementation-defined error. + */ par_status_t (*deinit)(void); - /** @brief Query whether the storage backend is initialized. */ + /** + * @brief Query whether the storage backend is initialized. + * + * @param[out] p_is_init Receives the backend initialization state. + * Must not be NULL. + * + * @return ePAR_OK when the state was reported successfully, otherwise an + * implementation-defined error. + */ par_status_t (*is_init)(bool * const p_is_init); - /** @brief Read raw bytes from the storage backend. */ + /** + * @brief Read raw bytes from the storage backend. + * + * @param[in] addr Byte offset inside the reserved parameter-storage region. + * @param[in] size Number of bytes to read. + * @param[out] p_buf Destination buffer that receives @p size bytes. + * Must not be NULL. + * + * @return ePAR_OK on success, otherwise an implementation-defined error. + */ par_status_t (*read)(const uint32_t addr, const uint32_t size, uint8_t * const p_buf); - /** @brief Write raw bytes to the storage backend. */ + /** + * @brief Write raw bytes to the storage backend. + * + * @param[in] addr Byte offset inside the reserved parameter-storage region. + * @param[in] size Number of bytes to write. + * @param[in] p_buf Source buffer that provides @p size bytes. + * Must not be NULL. + * + * @return ePAR_OK on success, otherwise an implementation-defined error. + */ par_status_t (*write)(const uint32_t addr, const uint32_t size, const uint8_t * const p_buf); - /** @brief Erase raw bytes in the storage backend. */ + /** + * @brief Erase raw bytes in the storage backend. + * + * @param[in] addr Byte offset inside the reserved parameter-storage region. + * @param[in] size Number of bytes to erase. + * + * @return ePAR_OK on success, otherwise an implementation-defined error. + */ par_status_t (*erase)(const uint32_t addr, const uint32_t size); - /** @brief Flush pending backend data to the final storage medium. */ + /** + * @brief Flush pending backend data to the final storage medium. + * + * @details Call this after writes or erases when the backend may stage data + * in RAM, caches, controller FIFOs, or deferred commit queues. + * + * @return ePAR_OK on success, otherwise an implementation-defined error. + */ par_status_t (*sync)(void); - /** @brief Optional backend name for diagnostics. */ + /** + * @brief Optional backend name for diagnostics. + * + * @details Set to NULL when no human-readable backend name is available. + */ const char *name; } par_store_backend_api_t; diff --git a/src/persist/par_nvm.c b/src/persist/par_nvm.c index 5f1504c..111644e 100644 --- a/src/persist/par_nvm.c +++ b/src/persist/par_nvm.c @@ -72,16 +72,6 @@ #include "persist/backend/par_store_backend.h" #include "persist/par_nvm_layout.h" #include "persist/par_nvm_table_id.h" -/** - * @brief Parameter NVM header object. - */ -typedef struct -{ - uint32_t sign; /**< Signature in host order. */ - uint16_t obj_nb; /**< Stored data object number in host order. */ - uint32_t table_id; /**< Stored parameter-table ID in platform-native order. */ - uint16_t crc; /**< Header CRC-16 over serialized obj_nb and table_id. */ -} par_nvm_head_obj_t; /** * @brief Compile-time definitions. */ @@ -129,16 +119,6 @@ typedef struct uint16_t loaded_count; /**< Number of runtime-loaded persistent slots. */ } par_nvm_slot_runtime_t; -/** - * @brief Result of comparing the stored NVM image against the live schema. - */ -typedef enum -{ - ePAR_NVM_COMPAT_REBUILD = 0, /**< Stored image is incompatible and must be rebuilt. */ - ePAR_NVM_COMPAT_EXACT_MATCH, /**< Stored image matches the live schema exactly. */ - ePAR_NVM_COMPAT_PREFIX_APPEND /**< Stored prefix is compatible and new tail slots may be appended. */ -} par_nvm_compat_result_t; - #if (1 == PAR_CFG_ENABLE_NAME) #define PAR_NVM_DBG_NAME_ARG(cfg_) (((const par_cfg_t *)(cfg_) != NULL) && (((const par_cfg_t *)(cfg_))->name != NULL) ? ((const par_cfg_t *)(cfg_))->name : "") #else @@ -208,6 +188,10 @@ static bool gb_is_nvm_owner = false; * specific repository layout. */ static const par_store_backend_api_t *gp_store = NULL; +/** + * @brief Selected persisted-record layout adapter. + */ +static const par_nvm_layout_api_t *gp_layout = NULL; /** * @brief Runtime state of compiled persistent slots. */ @@ -521,6 +505,113 @@ static par_status_t par_nvm_read_header(par_nvm_head_obj_t * const p_head_obj) return status; } + +/** + * @brief Flush pending backend data and normalize sync failures. + * + * @param p_context Short log context for diagnostics. + * @return Operation status. + */ +static par_status_t par_nvm_sync_backend(const char * const p_context) +{ + const par_status_t sync_status = gp_store->sync(); + + if (ePAR_OK != sync_status) + { + PAR_ERR_PRINT("PAR_NVM: sync failed%s%s err=%u", + (NULL != p_context) ? " " : "", + (NULL != p_context) ? p_context : "", + (unsigned)sync_status); + return ePAR_ERROR_NVM; + } + + return ePAR_OK; +} + +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) +/** + * @brief Verify a freshly written header by reading it back. + * + * @param p_expected Expected serialized header fields. + * @return Operation status. + */ +static par_status_t par_nvm_verify_header_readback(const par_nvm_head_obj_t * const p_expected) +{ + par_nvm_head_obj_t readback = { 0 }; + par_status_t status = ePAR_OK; + + PAR_ASSERT(NULL != p_expected); + + status = par_nvm_read_header(&readback); + if (ePAR_OK != status) + { + return status; + } + + if ((readback.sign != p_expected->sign) || + (readback.obj_nb != p_expected->obj_nb) || + (readback.table_id != p_expected->table_id) || + (readback.crc != p_expected->crc)) + { + PAR_ERR_PRINT("PAR_NVM: header readback mismatch exp(sign=0x%08lX,obj=%u,table=0x%08lX,crc=0x%04X) got(sign=0x%08lX,obj=%u,table=0x%08lX,crc=0x%04X)", + (unsigned long)p_expected->sign, + (unsigned)p_expected->obj_nb, + (unsigned long)p_expected->table_id, + (unsigned)p_expected->crc, + (unsigned long)readback.sign, + (unsigned)readback.obj_nb, + (unsigned long)readback.table_id, + (unsigned)readback.crc); + return ePAR_ERROR_NVM; + } + + if (par_nvm_calc_head_crc(&readback) != readback.crc) + { + PAR_ERR_PRINT("PAR_NVM: header readback CRC validation failed"); + return (par_status_t)(ePAR_ERROR_NVM | ePAR_ERROR_CRC); + } + + return ePAR_OK; +} + +/** + * @brief Verify one freshly written persisted record by reading it back. + * + * @param addr Record start address. + * @param par_num Live parameter number. + * @param p_expected Expected canonical object. + * @return Operation status. + */ +static par_status_t par_nvm_verify_record_readback(const uint32_t addr, + const par_num_t par_num, + const par_nvm_data_obj_t * const p_expected) +{ + par_nvm_data_obj_t readback = { 0 }; + par_status_t status = ePAR_OK; + + PAR_ASSERT(NULL != p_expected); + + status = gp_layout->read(gp_store, addr, par_num, &readback); + if (ePAR_OK != status) + { + PAR_ERR_PRINT("PAR_NVM: record readback failed, par_num=%u addr=0x%08lX err=%u", + (unsigned)par_num, + (unsigned long)addr, + (unsigned)status); + return (par_status_t)(ePAR_ERROR_NVM | (status & ePAR_ERROR_CRC)); + } + + if (false == gp_layout->data_obj_matches(par_num, p_expected, &readback)) + { + PAR_ERR_PRINT("PAR_NVM: record readback mismatch, par_num=%u addr=0x%08lX", + (unsigned)par_num, + (unsigned long)addr); + return ePAR_ERROR_NVM; + } + + return ePAR_OK; +} +#endif /* 1 == PAR_CFG_NVM_WRITE_VERIFY_EN */ /** * @brief Write parameter NVM header. * @@ -556,6 +647,21 @@ static par_status_t par_nvm_write_header(const uint16_t num_of_par) return status; } + status = par_nvm_sync_backend("after header write"); + if (ePAR_OK != status) + { + return status; + } + +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) + status = par_nvm_verify_header_readback(&head_obj); + if (ePAR_OK != status) + { + PAR_ERR_PRINT("PAR_NVM: header readback verification failed, err=%u", (unsigned)status); + return status; + } +#endif + PAR_DBG_PRINT("PAR_NVM: writing header with obj_count=%d", num_of_par); return status; @@ -629,7 +735,7 @@ static par_status_t par_nvm_get_num_by_persist_idx(const uint16_t persist_idx, p */ static uint32_t par_nvm_addr_from_persist_idx(const uint16_t persist_idx) { - return par_nvm_layout_addr_from_persist_idx(PAR_NVM_FIRST_DATA_OBJ_ADDR, persist_idx, g_par_persist_slot_to_par_num); + return gp_layout->addr_from_persist_idx(PAR_NVM_FIRST_DATA_OBJ_ADDR, persist_idx, g_par_persist_slot_to_par_num); } /** @@ -664,7 +770,7 @@ par_status_t par_nvm_print_nvm_lut(void) (unsigned)persist_idx, (unsigned)PAR_NVM_CFG_ID_VALUE(par_get_config(par_num)), (unsigned long)par_nvm_addr_from_persist_idx(persist_idx), - (unsigned long)par_nvm_layout_record_size_from_par_num(par_num), + (unsigned long)gp_layout->record_size_from_par_num(par_num), (unsigned)g_par_nvm_slot_runtime.loaded_slots[persist_idx], PAR_NVM_DBG_NAME_ARG(par_get_config(par_num))); } @@ -713,51 +819,6 @@ static uint32_t par_nvm_get_nvm_lut_addr(const par_num_t par_num) return par_nvm_addr_from_persist_idx(par_cfg->persist_idx); } -/** - * @brief Check compatibility between the stored image and the live schema. - * - * @details Compatibility is evaluated against the stored persistent prefix - * size from the validated NVM header. Every layout requires that the live - * firmware still exposes at least that many persistent slots and that the - * stored digest matches the live digest for exactly that prefix length. - * Layouts with stored IDs include the external parameter ID in the digest, - * so prefix ID renumbering still rebuilds the image there. The fixed - * payload-only layout excludes external parameter IDs and validates only - * stored-prefix byte-layout compatibility, so pure external-ID renumbering - * stays compatible there. The grouped payload-only layout is stricter: - * because regrouping depends on the full live persistent set, any stored/live - * count mismatch rebuilds instead of attempting tail append. Semantic-only - * prefix remaps that keep the same byte layout still require an explicit - * PAR_CFG_TABLE_ID_SCHEMA_VER bump. - * - * @param p_head_obj Validated stored NVM header. - * @return Compatibility result. - */ -static par_nvm_compat_result_t par_nvm_check_compat(const par_nvm_head_obj_t * const p_head_obj) -{ - PAR_ASSERT(NULL != p_head_obj); - - if (p_head_obj->obj_nb > (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) - { - return ePAR_NVM_COMPAT_REBUILD; - } - - if (p_head_obj->table_id != par_nvm_table_id_calc_for_count(p_head_obj->obj_nb)) - { - return ePAR_NVM_COMPAT_REBUILD; - } - -#if (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_GROUPED_PAYLOAD_ONLY) - return (p_head_obj->obj_nb == (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) ? - ePAR_NVM_COMPAT_EXACT_MATCH : - ePAR_NVM_COMPAT_REBUILD; -#else - return (p_head_obj->obj_nb == (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) ? - ePAR_NVM_COMPAT_EXACT_MATCH : - ePAR_NVM_COMPAT_PREFIX_APPEND; -#endif -} - /** * @brief Load all parameter values from NVM. * @@ -828,44 +889,32 @@ static par_status_t par_nvm_load_all(const uint16_t num_of_par) goto out; } - op_status = par_nvm_layout_read(gp_store, obj_addr, par_num, &obj_data); + op_status = gp_layout->read(gp_store, obj_addr, par_num, &obj_data); if (ePAR_OK != op_status) { status = op_status; err.reason = (ePAR_ERROR_CRC == op_status) ? "crc-mismatch" : "read-failed"; -#if (1 == PAR_CFG_NVM_RECORD_LAYOUT_HAS_STORED_ID) - err.stored_id = obj_data.id; -#else - err.stored_id = (uint16_t)PAR_NVM_CFG_ID_VALUE(par_cfg); -#endif + err.stored_id = gp_layout->get_error_stored_id(par_num, &obj_data); goto out; } -#if (1 == PAR_CFG_NVM_RECORD_LAYOUT_HAS_STORED_ID) - if (obj_data.id != PAR_NVM_CFG_ID_VALUE(par_cfg)) + op_status = gp_layout->validate_loaded_obj(par_num, &obj_data, &err.reason, &err.stored_id); + if (ePAR_OK != op_status) { status = ePAR_ERROR; - err.reason = "id-mismatch"; -#if (1 == PAR_CFG_NVM_RECORD_LAYOUT_HAS_STORED_ID) - err.stored_id = obj_data.id; -#else - err.stored_id = (uint16_t)PAR_NVM_CFG_ID_VALUE(par_cfg); -#endif - op_status = status; + if (NULL == err.reason) + { + err.reason = "layout-validate-failed"; + } goto out; } -#endif op_status = par_set_fast(par_num, &obj_data.data); if (ePAR_OK != op_status) { status |= op_status; err.reason = "restore-failed"; -#if (1 == PAR_CFG_NVM_RECORD_LAYOUT_HAS_STORED_ID) - err.stored_id = obj_data.id; -#else - err.stored_id = (uint16_t)PAR_NVM_CFG_ID_VALUE(par_cfg); -#endif + err.stored_id = gp_layout->get_error_stored_id(par_num, &obj_data); goto out; } @@ -919,13 +968,6 @@ static par_status_t par_nvm_load_all(const uint16_t num_of_par) goto out; } - op_status = gp_store->sync(); - if (ePAR_OK != op_status) - { - status |= ePAR_ERROR_NVM; - err.reason = "sync-failed"; - goto out; - } PAR_INFO_PRINT("PAR_NVM: appended %u new persistent slots and rewrote header count to %u", (unsigned)new_par_cnt, (unsigned)PAR_PERSISTENT_COMPILE_COUNT); @@ -966,12 +1008,28 @@ static par_status_t par_nvm_init_nvm(void) PAR_DBG_PRINT("PAR_NVM: resolving storage backend"); gp_store = par_store_backend_get_api(); + gp_layout = par_nvm_layout_init(); gb_is_nvm_owner = false; + if ((NULL == gp_layout) || (NULL == gp_layout->record_size_from_par_num) || + (NULL == gp_layout->addr_from_persist_idx) || (NULL == gp_layout->populate_data_obj) || + (NULL == gp_layout->read) || (NULL == gp_layout->write) || + (NULL == gp_layout->validate_loaded_obj) || (NULL == gp_layout->get_error_stored_id) || + (NULL == gp_layout->check_compat) +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) + || (NULL == gp_layout->data_obj_matches) +#endif + ) + { + PAR_ERR_PRINT("PAR_NVM: no valid persisted-record layout adapter is wired"); + status = ePAR_ERROR_INIT; + } + /* Validate the mounted backend once before any operation callback is used. */ - if ((NULL == gp_store) || (NULL == gp_store->init) || (NULL == gp_store->deinit) || - (NULL == gp_store->is_init) || (NULL == gp_store->read) || (NULL == gp_store->write) || - (NULL == gp_store->erase) || (NULL == gp_store->sync)) + if ((ePAR_OK == status) && + ((NULL == gp_store) || (NULL == gp_store->init) || (NULL == gp_store->deinit) || + (NULL == gp_store->is_init) || (NULL == gp_store->read) || (NULL == gp_store->write) || + (NULL == gp_store->erase) || (NULL == gp_store->sync))) { PAR_ERR_PRINT("PAR_NVM: no valid parameter storage backend is wired"); status = ePAR_ERROR_INIT; @@ -1066,7 +1124,7 @@ par_status_t par_nvm_init(void) /* Step 2: validate table compatibility only when header is valid */ if (ePAR_OK == (detect_status & ePAR_STATUS_ERROR_MASK)) { - const par_nvm_compat_result_t compat = par_nvm_check_compat(&head_obj); + const par_nvm_compat_result_t compat = gp_layout->check_compat(&head_obj); if (ePAR_NVM_COMPAT_REBUILD == compat) { @@ -1185,6 +1243,7 @@ par_status_t par_nvm_deinit(void) gb_is_init = false; gb_is_nvm_owner = false; gp_store = NULL; + gp_layout = NULL; } } else @@ -1206,7 +1265,10 @@ par_status_t par_nvm_deinit(void) * is copied to FLASH. * * @param par_num Parameter enumeration number. - * @param nvm_sync Perform NVM sync after parameter write. + * @param nvm_sync Perform NVM sync after parameter write. When + * PAR_CFG_NVM_WRITE_VERIFY_EN is enabled, write verification also + * forces a backend sync before the readback step even if this flag is + * false. * @return Status of operation. */ par_status_t par_nvm_write(const par_num_t par_num, const bool nvm_sync) @@ -1230,16 +1292,15 @@ par_status_t par_nvm_write(const par_num_t par_num, const bool nvm_sync) par_status_t status = ePAR_OK; par_nvm_data_obj_t obj_data = { 0 }; + par_type_t live_data = { 0 }; uint32_t par_addr = 0UL; par_status_t store_status = ePAR_OK; PAR_DBG_PRINT("PAR_NVM: writing persistent parameter, par_num=%u id=%u", (unsigned)par_num, (unsigned)PAR_NVM_CFG_ID_VALUE(par_cfg)); - (void)par_get(par_num, &obj_data.data); -#if (1 == PAR_CFG_NVM_RECORD_LAYOUT_HAS_STORED_ID) - obj_data.id = (uint16_t)PAR_NVM_CFG_ID_VALUE(par_cfg); -#endif + (void)par_get(par_num, &live_data); + gp_layout->populate_data_obj(par_num, &live_data, &obj_data); par_addr = par_nvm_get_nvm_lut_addr(par_num); - store_status = par_nvm_layout_write(gp_store, par_addr, par_num, &obj_data); + store_status = gp_layout->write(gp_store, par_addr, par_num, &obj_data); if (ePAR_OK != store_status) { status |= ePAR_ERROR_NVM; @@ -1250,16 +1311,29 @@ par_status_t par_nvm_write(const par_num_t par_num, const bool nvm_sync) (unsigned)store_status); } - if ((true == nvm_sync) && (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK))) + if (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK)) { - const par_status_t sync_status = gp_store->sync(); - if (ePAR_OK != sync_status) +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) + (void)nvm_sync; + status |= par_nvm_sync_backend("before parameter readback verify"); + if (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK)) { - status |= ePAR_ERROR_NVM; - PAR_ERR_PRINT("PAR_NVM: sync failed after parameter write, par_num=%u err=%u", - (unsigned)par_num, - (unsigned)sync_status); + status |= par_nvm_verify_record_readback(par_addr, par_num, &obj_data); + if (ePAR_OK != (status & ePAR_STATUS_ERROR_MASK)) + { + PAR_ERR_PRINT("PAR_NVM: parameter readback verification failed, par_num=%u id=%u addr=0x%08lX err=%u", + (unsigned)par_num, + (unsigned)PAR_NVM_CFG_ID_VALUE(par_cfg), + (unsigned long)par_addr, + (unsigned)status); + } } +#else + if (true == nvm_sync) + { + status |= par_nvm_sync_backend("after parameter write"); + } +#endif } return status; diff --git a/src/persist/par_nvm_layout.h b/src/persist/par_nvm_layout.h index 24db435..cf5e32f 100644 --- a/src/persist/par_nvm_layout.h +++ b/src/persist/par_nvm_layout.h @@ -2,8 +2,8 @@ * @file par_nvm_layout.h * @brief Declare private persisted-record layout interfaces. * @author wdfk-prog () - * @version 1.0 - * @date 2026-04-11 + * @version 1.2 + * @date 2026-04-13 * * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. * @@ -12,6 +12,7 @@ * Date Version Author Description * 2026-04-06 1.0 wdfk-prog first version * 2026-04-11 1.1 wdfk-prog split layout structs by selected NVM layout + * 2026-04-13 1.2 wdfk-prog add layout-ops registration for NVM core */ #ifndef _PAR_NVM_LAYOUT_H_ #define _PAR_NVM_LAYOUT_H_ @@ -29,22 +30,47 @@ #define PAR_NVM_RECORD_CRC_SIZE ((uint32_t)sizeof(uint8_t)) #define PAR_NVM_RECORD_DATA_SLOT_SIZE ((uint8_t)sizeof(par_type_t)) +/** + * @brief Parameter NVM header object. + * + * @details This private header shape is shared between the common NVM flow and + * the selected record-layout adapter so layout-specific compatibility policy + * can be implemented below the top-level `par_nvm.c` logic. + */ +typedef struct +{ + uint32_t sign; /**< Signature in host order. */ + uint16_t obj_nb; /**< Stored data object number in host order. */ + uint32_t table_id; /**< Stored parameter-table ID in platform-native order. */ + uint16_t crc; /**< Header CRC-16 over serialized obj_nb and table_id. */ +} par_nvm_head_obj_t; + +/** + * @brief Result of comparing the stored NVM image against the live schema. + */ +typedef enum +{ + ePAR_NVM_COMPAT_REBUILD = 0, /**< Stored image is incompatible and must be rebuilt. */ + ePAR_NVM_COMPAT_EXACT_MATCH, /**< Stored image matches the live schema exactly. */ + ePAR_NVM_COMPAT_PREFIX_APPEND /**< Stored prefix is compatible and new tail slots may be appended. */ +} par_nvm_compat_result_t; + #if (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE) /** * @brief Selected fixed-slot layout with explicit size descriptor. */ typedef struct { - uint16_t id; /**< Parameter ID. */ - uint8_t size; /**< Serialized payload-size descriptor. */ - uint8_t crc; /**< CRC-8 over id, size, and payload bytes. */ + uint16_t id; /**< Parameter ID. */ + uint8_t size; /**< Serialized payload-size descriptor. */ + uint8_t crc; /**< CRC-8 over id, size, and payload bytes. */ par_type_t data; /**< Fixed 4-byte payload slot. */ } par_nvm_layout_fixed_slot_with_size_record_t; typedef struct { - uint16_t id; /**< Parameter ID. */ - uint8_t size; /**< Serialized payload-size descriptor. */ + uint16_t id; /**< Parameter ID. */ + uint8_t size; /**< Serialized payload-size descriptor. */ par_type_t data; /**< Canonical parameter value. */ } par_nvm_layout_fixed_slot_with_size_data_obj_t; @@ -59,14 +85,14 @@ typedef par_nvm_layout_fixed_slot_with_size_data_obj_t par_nvm_data_obj_t; */ typedef struct { - uint16_t id; /**< Parameter ID. */ - uint8_t crc; /**< CRC-8 over id and payload bytes. */ - uint8_t payload[PAR_NVM_RECORD_DATA_SLOT_SIZE]; /**< Fixed 4-byte payload slot. */ + uint16_t id; /**< Parameter ID. */ + uint8_t crc; /**< CRC-8 over id and payload bytes. */ + uint8_t payload[PAR_NVM_RECORD_DATA_SLOT_SIZE]; /**< Fixed 4-byte payload slot. */ } par_nvm_layout_fixed_slot_no_size_record_t; typedef struct { - uint16_t id; /**< Parameter ID. */ + uint16_t id; /**< Parameter ID. */ par_type_t data; /**< Canonical parameter value. */ } par_nvm_layout_fixed_slot_no_size_data_obj_t; @@ -80,16 +106,16 @@ typedef par_nvm_layout_fixed_slot_no_size_data_obj_t par_nvm_data_obj_t; */ typedef struct { - uint16_t id; /**< Parameter ID. */ - uint8_t size; /**< Serialized payload-size descriptor. */ - uint8_t crc; /**< CRC-8 over id, size, and payload bytes. */ - uint8_t payload[PAR_NVM_RECORD_DATA_SLOT_SIZE]; /**< Maximum payload storage. */ + uint16_t id; /**< Parameter ID. */ + uint8_t size; /**< Serialized payload-size descriptor. */ + uint8_t crc; /**< CRC-8 over id, size, and payload bytes. */ + uint8_t payload[PAR_NVM_RECORD_DATA_SLOT_SIZE]; /**< Maximum payload storage. */ } par_nvm_layout_compact_payload_record_t; typedef struct { - uint16_t id; /**< Parameter ID. */ - uint8_t size; /**< Serialized payload-size descriptor. */ + uint16_t id; /**< Parameter ID. */ + uint8_t size; /**< Serialized payload-size descriptor. */ par_type_t data; /**< Canonical parameter value. */ } par_nvm_layout_compact_payload_data_obj_t; @@ -103,8 +129,8 @@ typedef par_nvm_layout_compact_payload_data_obj_t par_nvm_data_obj_t; */ typedef struct { - uint8_t crc; /**< CRC-8 over payload bytes. */ - uint8_t payload[PAR_NVM_RECORD_DATA_SLOT_SIZE]; /**< Maximum payload storage. */ + uint8_t crc; /**< CRC-8 over payload bytes. */ + uint8_t payload[PAR_NVM_RECORD_DATA_SLOT_SIZE]; /**< Maximum payload storage. */ } par_nvm_layout_fixed_payload_only_record_t; typedef struct @@ -122,8 +148,8 @@ typedef par_nvm_layout_fixed_payload_only_data_obj_t par_nvm_data_obj_t; */ typedef struct { - uint8_t crc; /**< CRC-8 over payload bytes. */ - uint8_t payload[PAR_NVM_RECORD_DATA_SLOT_SIZE]; /**< Maximum payload storage. */ + uint8_t crc; /**< CRC-8 over payload bytes. */ + uint8_t payload[PAR_NVM_RECORD_DATA_SLOT_SIZE]; /**< Maximum payload storage. */ } par_nvm_layout_grouped_payload_only_record_t; typedef struct @@ -136,6 +162,114 @@ typedef par_nvm_layout_grouped_payload_only_data_obj_t par_nvm_data_obj_t; #error "Unsupported PAR_CFG_NVM_RECORD_LAYOUT selection." #endif +/** + * @brief Selected persisted-record layout vtable. + * + * @details Each concrete layout owns its own object preparation, serialized + * read/write path, compatibility decision, and optional readback-compare + * policy. `par_nvm.c` binds this table once during initialization and then + * operates only through these callbacks. + */ +typedef struct +{ + /** + * @brief Return the serialized byte size of one persisted record. + * + * @param par_num Live parameter number that owns the persistent slot. + * @return Serialized record size in bytes for the selected layout. + */ + uint32_t (*record_size_from_par_num)(const par_num_t par_num); + /** + * @brief Translate one persistent slot index into its serialized storage address. + * + * @param first_data_obj_addr Absolute start address of the first persisted record. + * @param persist_idx Compile-time persistent slot index. + * @param p_persist_slot_to_par_num Compile-time slot-to-parameter mapping table. + * @return Absolute address of the selected serialized record. + */ + uint32_t (*addr_from_persist_idx)(const uint32_t first_data_obj_addr, + const uint16_t persist_idx, + const par_num_t * const p_persist_slot_to_par_num); + /** + * @brief Populate one canonical NVM data object from the live RAM value. + * + * @param par_num Live parameter number. + * @param p_live_data Pointer to the live parameter value in canonical form. + * @param p_obj Output canonical NVM object prepared for the selected layout. + */ + void (*populate_data_obj)(const par_num_t par_num, + const par_type_t * const p_live_data, + par_nvm_data_obj_t * const p_obj); + /** + * @brief Read and validate one serialized record from the backend. + * + * @param p_store Active storage backend API. + * @param addr Absolute record address inside the managed NVM image. + * @param par_num Live parameter number associated with the slot. + * @param p_obj Output canonical NVM object loaded from storage. + * @return Operation status. Layout-level CRC and size validation are handled here. + */ + par_status_t (*read)(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + par_nvm_data_obj_t * const p_obj); + /** + * @brief Serialize and write one canonical NVM object to storage. + * + * @param p_store Active storage backend API. + * @param addr Absolute record address inside the managed NVM image. + * @param par_num Live parameter number associated with the slot. + * @param p_obj Canonical NVM object to serialize and persist. + * @return Operation status. + */ + par_status_t (*write)(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj); + /** + * @brief Validate one already loaded canonical object against the live schema. + * + * @param par_num Live parameter number expected at this slot. + * @param p_obj Canonical NVM object that was read from storage. + * @param pp_reason Output short mismatch reason string for diagnostics. + * @param p_stored_id Output stored ID value when the layout carries one. + * @return `ePAR_OK` when the object matches the current live schema. + */ + par_status_t (*validate_loaded_obj)(const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj, + const char ** const pp_reason, + uint16_t * const p_stored_id); + /** + * @brief Return the best stored-ID diagnostic value for an error path. + * + * @param par_num Live parameter number associated with the slot. + * @param p_obj Canonical NVM object that was read from storage. + * @return Stored ID for diagnostics, or a layout-defined fallback value. + */ + uint16_t (*get_error_stored_id)(const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj); + /** + * @brief Decide whether the stored header remains layout-compatible. + * + * @param p_head_obj Validated NVM header loaded from storage. + * @return Layout-specific compatibility decision for rebuild/exact/prefix-append. + */ + par_nvm_compat_result_t (*check_compat)(const par_nvm_head_obj_t * const p_head_obj); +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) + /** + * @brief Compare an expected object against a read-back object. + * + * @param par_num Live parameter number associated with the slot. + * @param p_expected Canonical object prepared from the live value before write. + * @param p_actual Canonical object reloaded from storage after write. + * @return True when the write-readback comparison passes for this layout. + */ + bool (*data_obj_matches)(const par_num_t par_num, + const par_nvm_data_obj_t * const p_expected, + const par_nvm_data_obj_t * const p_actual); +#endif +} par_nvm_layout_api_t; + uint8_t par_nvm_layout_payload_size_from_type(const par_type_list_t type); uint8_t par_nvm_layout_payload_size_from_par_num(const par_num_t par_num); void par_nvm_layout_pack_payload_bytes(const par_type_list_t type, @@ -155,19 +289,12 @@ uint8_t par_nvm_layout_calc_crc_with_id(const uint16_t id, const uint8_t payload_size, const bool include_size_desc); -uint32_t par_nvm_layout_record_size_from_par_num(const par_num_t par_num); -uint32_t par_nvm_layout_addr_from_persist_idx(const uint32_t first_data_obj_addr, - const uint16_t persist_idx, - const par_num_t * const p_persist_slot_to_par_num); - -par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, - const uint32_t addr, - const par_num_t par_num, - par_nvm_data_obj_t * const p_obj); -par_status_t par_nvm_layout_write(const par_store_backend_api_t * const p_store, - const uint32_t addr, - const par_num_t par_num, - const par_nvm_data_obj_t * const p_obj); +/** + * @brief Bind and return the concrete layout-ops table selected at compile time. + * + * @return Non-null pointer to the selected layout adapter. + */ +const par_nvm_layout_api_t *par_nvm_layout_init(void); #endif /* 1 == PAR_CFG_NVM_EN */ diff --git a/src/persist/par_nvm_layout_compact_payload.c b/src/persist/par_nvm_layout_compact_payload.c index 5659b19..35f6fb0 100644 --- a/src/persist/par_nvm_layout_compact_payload.c +++ b/src/persist/par_nvm_layout_compact_payload.c @@ -2,8 +2,8 @@ * @file par_nvm_layout_compact_payload.c * @brief Implement the compact persisted-record layout with id, size, crc, and payload bytes. * @author wdfk-prog () - * @version 1.0 - * @date 2026-04-06 + * @version 1.1 + * @date 2026-04-13 * * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. * @@ -11,6 +11,7 @@ * @par Change Log: * Date Version Author Description * 2026-04-06 1.0 wdfk-prog first version + * 2026-04-13 1.1 wdfk-prog add layout-ops adapter */ #include "persist/par_nvm_layout.h" @@ -18,51 +19,47 @@ #include -/** - * @brief Serialized overhead of one compact-payload record. - */ +#include "persist/par_nvm_table_id.h" + #define PAR_NVM_LAYOUT_RECORD_OVERHEAD (PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_SIZE_FIELD_SIZE + PAR_NVM_RECORD_CRC_SIZE) -/** - * @brief Maximum serialized size of one compact-payload record. - */ #define PAR_NVM_LAYOUT_RECORD_MAX_SIZE (PAR_NVM_LAYOUT_RECORD_OVERHEAD + PAR_NVM_RECORD_DATA_SLOT_SIZE) PAR_STATIC_ASSERT(par_nvm_layout_compact_payload_record_payload_slot_is_4_bytes, (sizeof(((par_nvm_layout_compact_payload_record_t *)0)->payload) == 4u)); /** - * @brief Resolve serialized record size from the active payload width. + * @brief Return the serialized record size for one compact-payload record width. * - * @param payload_size Active payload width in bytes. + * @param payload_size Natural payload width in bytes. * @return Serialized record size in bytes. */ -static uint32_t par_nvm_layout_record_size_from_payload_size(const uint8_t payload_size) +static uint32_t par_nvm_layout_compact_payload_record_size_from_payload_size(const uint8_t payload_size) { return (PAR_NVM_LAYOUT_RECORD_OVERHEAD + (uint32_t)payload_size); } /** - * @brief Get serialized record size for one persistent parameter. + * @brief Return the serialized byte size of one persisted record for this layout. * - * @param par_num Live parameter number. + * @param par_num Live parameter number associated with the slot. * @return Serialized record size in bytes. */ -uint32_t par_nvm_layout_record_size_from_par_num(const par_num_t par_num) +static uint32_t par_nvm_layout_compact_payload_record_size_from_par_num(const par_num_t par_num) { - return par_nvm_layout_record_size_from_payload_size(par_nvm_layout_payload_size_from_par_num(par_num)); + return par_nvm_layout_compact_payload_record_size_from_payload_size(par_nvm_layout_payload_size_from_par_num(par_num)); } /** - * @brief Resolve record address for the compact-payload layout. + * @brief Translate one persistent slot index into its serialized record address. * - * @param first_data_obj_addr Start address of the first persisted object. + * @param first_data_obj_addr Absolute address of the first persisted record. * @param persist_idx Compile-time persistent slot index. - * @param p_persist_slot_to_par_num Persistent-slot to live-parameter mapping. - * @return Absolute NVM address of the selected record. + * @param p_persist_slot_to_par_num Compile-time slot-to-parameter mapping table. + * @return Absolute address of the selected record. */ -uint32_t par_nvm_layout_addr_from_persist_idx(const uint32_t first_data_obj_addr, - const uint16_t persist_idx, - const par_num_t * const p_persist_slot_to_par_num) +static uint32_t par_nvm_layout_compact_payload_addr_from_persist_idx(const uint32_t first_data_obj_addr, + const uint16_t persist_idx, + const par_num_t * const p_persist_slot_to_par_num) { uint32_t addr = first_data_obj_addr; @@ -70,31 +67,50 @@ uint32_t par_nvm_layout_addr_from_persist_idx(const uint32_t first_data_obj_addr for (uint16_t it = 0U; it < persist_idx; it++) { - addr += par_nvm_layout_record_size_from_par_num(p_persist_slot_to_par_num[it]); + addr += par_nvm_layout_compact_payload_record_size_from_par_num(p_persist_slot_to_par_num[it]); } return addr; } /** - * @brief Read one compact-payload record from NVM. + * @brief Populate one canonical NVM object from the live parameter value. * - * @param p_store Storage backend API. - * @param addr Record start address. * @param par_num Live parameter number. - * @param p_obj Output canonical object. + * @param p_live_data Pointer to the live canonical parameter value. + * @param p_obj Output canonical NVM object. + */ +static void par_nvm_layout_compact_payload_populate_data_obj(const par_num_t par_num, + const par_type_t * const p_live_data, + par_nvm_data_obj_t * const p_obj) +{ + PAR_ASSERT((NULL != p_live_data) && (NULL != p_obj)); + + memset(p_obj, 0, sizeof(*p_obj)); + p_obj->id = par_cfg_get_param_id_const(par_num); + p_obj->size = par_nvm_layout_payload_size_from_par_num(par_num); + p_obj->data = *p_live_data; +} + +/** + * @brief Read and validate one serialized record from storage. + * + * @param p_store Active storage backend API. + * @param addr Absolute record address inside the managed NVM image. + * @param par_num Live parameter number associated with the slot. + * @param p_obj Output canonical NVM object. * @return Operation status. */ -par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, - const uint32_t addr, - const par_num_t par_num, - par_nvm_data_obj_t * const p_obj) +static par_status_t par_nvm_layout_compact_payload_read(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + par_nvm_data_obj_t * const p_obj) { uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_MAX_SIZE] = { 0U }; const par_cfg_t * const p_cfg = par_get_config(par_num); const uint8_t expected_payload_size = par_nvm_layout_payload_size_from_par_num(par_num); - const uint32_t record_size = par_nvm_layout_record_size_from_payload_size(expected_payload_size); - uint8_t size_desc = 0; + const uint32_t record_size = par_nvm_layout_compact_payload_record_size_from_payload_size(expected_payload_size); + uint8_t size_desc = 0U; const uint8_t * const p_payload = &record_buf[PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_SIZE_FIELD_SIZE + PAR_NVM_RECORD_CRC_SIZE]; uint8_t crc_calc = 0U; @@ -126,23 +142,23 @@ par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, } /** - * @brief Write one compact-payload record to NVM. + * @brief Serialize and write one canonical NVM object to storage. * - * @param p_store Storage backend API. - * @param addr Record start address. - * @param par_num Live parameter number. - * @param p_obj Canonical object to serialize. + * @param p_store Active storage backend API. + * @param addr Absolute record address inside the managed NVM image. + * @param par_num Live parameter number associated with the slot. + * @param p_obj Canonical NVM object to serialize. * @return Operation status. */ -par_status_t par_nvm_layout_write(const par_store_backend_api_t * const p_store, - const uint32_t addr, - const par_num_t par_num, - const par_nvm_data_obj_t * const p_obj) +static par_status_t par_nvm_layout_compact_payload_write(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) { uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_MAX_SIZE] = { 0U }; const par_cfg_t * const p_cfg = par_get_config(par_num); const uint8_t payload_size = par_nvm_layout_payload_size_from_par_num(par_num); - const uint32_t record_size = par_nvm_layout_record_size_from_payload_size(payload_size); + const uint32_t record_size = par_nvm_layout_compact_payload_record_size_from_payload_size(payload_size); uint8_t * const p_payload = &record_buf[PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_SIZE_FIELD_SIZE + PAR_NVM_RECORD_CRC_SIZE]; uint8_t crc = 0U; @@ -159,4 +175,139 @@ par_status_t par_nvm_layout_write(const par_store_backend_api_t * const p_store, return (ePAR_OK == p_store->write(addr, record_size, record_buf)) ? ePAR_OK : ePAR_ERROR_NVM; } +/** + * @brief Validate one loaded canonical object against the current live schema. + * + * @param par_num Live parameter number expected at this slot. + * @param p_obj Canonical object loaded from NVM. + * @param pp_reason Output short mismatch reason for diagnostics. + * @param p_stored_id Output stored ID value when the layout carries one. + * @return Operation status. + */ +static par_status_t par_nvm_layout_compact_payload_validate_loaded_obj(const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj, + const char ** const pp_reason, + uint16_t * const p_stored_id) +{ + const uint16_t expected_id = par_cfg_get_param_id_const(par_num); + const uint8_t expected_payload_size = par_nvm_layout_payload_size_from_par_num(par_num); + + PAR_ASSERT((NULL != p_obj) && (NULL != pp_reason) && (NULL != p_stored_id)); + + *pp_reason = NULL; + *p_stored_id = p_obj->id; + + if (p_obj->id != expected_id) + { + *pp_reason = "id-mismatch"; + return ePAR_ERROR; + } + + if (p_obj->size != expected_payload_size) + { + *pp_reason = "size-mismatch"; + return ePAR_ERROR; + } + + return ePAR_OK; +} + +/** + * @brief Return the stored-ID diagnostic value for an error path. + * + * @param par_num Live parameter number associated with the slot. + * @param p_obj Canonical object loaded from NVM. + * @return Stored ID or a layout-defined fallback value. + */ +static uint16_t par_nvm_layout_compact_payload_get_error_stored_id(const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) +{ + (void)par_num; + return (NULL != p_obj) ? p_obj->id : 0U; +} + +/** + * @brief Decide whether the stored header remains compatible with this layout. + * + * @param p_head_obj Validated NVM header object. + * @return Layout-specific compatibility decision. + */ +static par_nvm_compat_result_t par_nvm_layout_compact_payload_check_compat(const par_nvm_head_obj_t * const p_head_obj) +{ + PAR_ASSERT(NULL != p_head_obj); + + if (p_head_obj->obj_nb > (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) + { + return ePAR_NVM_COMPAT_REBUILD; + } + + if (p_head_obj->table_id != par_nvm_table_id_calc_for_count(p_head_obj->obj_nb)) + { + return ePAR_NVM_COMPAT_REBUILD; + } + + return (p_head_obj->obj_nb == (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) ? + ePAR_NVM_COMPAT_EXACT_MATCH : + ePAR_NVM_COMPAT_PREFIX_APPEND; +} + +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) +/** + * @brief Compare an expected object against a post-write read-back object. + * + * @param par_num Live parameter number associated with the slot. + * @param p_expected Expected canonical NVM object. + * @param p_actual Canonical object reloaded from storage. + * @return True when both objects match under this layout policy. + */ +static bool par_nvm_layout_compact_payload_data_obj_matches(const par_num_t par_num, + const par_nvm_data_obj_t * const p_expected, + const par_nvm_data_obj_t * const p_actual) +{ + const par_cfg_t * const p_cfg = par_get_config(par_num); + const uint8_t payload_size = par_nvm_layout_payload_size_from_par_num(par_num); + uint8_t expected_payload[PAR_NVM_RECORD_DATA_SLOT_SIZE] = { 0U }; + uint8_t actual_payload[PAR_NVM_RECORD_DATA_SLOT_SIZE] = { 0U }; + + PAR_ASSERT((NULL != p_cfg) && (NULL != p_expected) && (NULL != p_actual)); + + if ((p_expected->id != p_actual->id) || + (p_actual->size != payload_size)) + { + return false; + } + + par_nvm_layout_pack_payload_bytes(p_cfg->type, &p_expected->data, expected_payload); + par_nvm_layout_pack_payload_bytes(p_cfg->type, &p_actual->data, actual_payload); + return (0 == memcmp(expected_payload, actual_payload, payload_size)); +} +#endif + +/** + * @brief Concrete layout adapter bound by the common NVM core. + */ +static const par_nvm_layout_api_t g_par_nvm_layout_api = { + .record_size_from_par_num = par_nvm_layout_compact_payload_record_size_from_par_num, + .addr_from_persist_idx = par_nvm_layout_compact_payload_addr_from_persist_idx, + .populate_data_obj = par_nvm_layout_compact_payload_populate_data_obj, + .read = par_nvm_layout_compact_payload_read, + .write = par_nvm_layout_compact_payload_write, + .validate_loaded_obj = par_nvm_layout_compact_payload_validate_loaded_obj, + .get_error_stored_id = par_nvm_layout_compact_payload_get_error_stored_id, + .check_compat = par_nvm_layout_compact_payload_check_compat, +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) + .data_obj_matches = par_nvm_layout_compact_payload_data_obj_matches, +#endif +}; + +/** + * @brief Return the concrete layout adapter selected for this build. + * + * @return Non-null pointer to this layout adapter. + */ +const par_nvm_layout_api_t *par_nvm_layout_init(void) +{ + return &g_par_nvm_layout_api; +} + #endif /* compact-payload */ diff --git a/src/persist/par_nvm_layout_fixed_payload_only.c b/src/persist/par_nvm_layout_fixed_payload_only.c index cb0da22..00cd25b 100644 --- a/src/persist/par_nvm_layout_fixed_payload_only.c +++ b/src/persist/par_nvm_layout_fixed_payload_only.c @@ -2,8 +2,8 @@ * @file par_nvm_layout_fixed_payload_only.c * @brief Implement the fixed persistent-order payload-only NVM layout. * @author wdfk-prog () - * @version 1.0 - * @date 2026-04-11 + * @version 1.2 + * @date 2026-04-13 * * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. * @@ -12,6 +12,7 @@ * Date Version Author Description * 2026-04-10 1.0 wdfk-prog first version * 2026-04-11 1.1 wdfk-prog restore layout comments and split layout structs + * 2026-04-13 1.2 wdfk-prog add layout-ops adapter */ #include "persist/par_nvm_layout.h" @@ -19,60 +20,51 @@ #include +#include "persist/par_nvm_table_id.h" + #if (1 == PAR_CFG_NVM_BACKEND_FLASH_EN) #error "Payload-only NVM layouts are not supported with the flash backend because records are variable width and not guaranteed to stay 8-byte aligned." #endif -/** - * @brief Serialized overhead of one payload-only record. - */ #define PAR_NVM_LAYOUT_RECORD_OVERHEAD ((uint32_t)PAR_NVM_RECORD_CRC_SIZE) - -/** - * @brief Maximum serialized size of one payload-only record. - */ #define PAR_NVM_LAYOUT_RECORD_MAX_SIZE (PAR_NVM_LAYOUT_RECORD_OVERHEAD + PAR_NVM_RECORD_DATA_SLOT_SIZE) PAR_STATIC_ASSERT(par_nvm_layout_fixed_payload_only_record_payload_slot_is_4_bytes, (sizeof(((par_nvm_layout_fixed_payload_only_record_t *)0)->payload) == 4u)); /** - * @brief Resolve serialized record size from the active payload width. + * @brief Return the serialized record size for one payload-only record width. * - * @param payload_size Active payload width in bytes. + * @param payload_size Natural payload width in bytes. * @return Serialized record size in bytes. */ -static uint32_t par_nvm_layout_record_size_from_payload_size(const uint8_t payload_size) +static uint32_t par_nvm_layout_fixed_payload_only_record_size_from_payload_size(const uint8_t payload_size) { return (PAR_NVM_LAYOUT_RECORD_OVERHEAD + (uint32_t)payload_size); } /** - * @brief Get serialized record size for one persistent parameter. + * @brief Return the serialized byte size of one persisted record for this layout. * - * @param par_num Live parameter number. + * @param par_num Live parameter number associated with the slot. * @return Serialized record size in bytes. */ -uint32_t par_nvm_layout_record_size_from_par_num(const par_num_t par_num) +static uint32_t par_nvm_layout_fixed_payload_only_record_size_from_par_num(const par_num_t par_num) { - return par_nvm_layout_record_size_from_payload_size(par_nvm_layout_payload_size_from_par_num(par_num)); + return par_nvm_layout_fixed_payload_only_record_size_from_payload_size(par_nvm_layout_payload_size_from_par_num(par_num)); } /** - * @brief Resolve record address for the fixed persistent-order payload-only layout. + * @brief Translate one persistent slot index into its serialized record address. * - * @details This layout does not store a size field in NVM. Address resolution - * therefore walks the compile-time persistent-order table and accumulates the - * natural serialized width of each preceding slot. - * - * @param first_data_obj_addr Start address of the first persisted object. + * @param first_data_obj_addr Absolute address of the first persisted record. * @param persist_idx Compile-time persistent slot index. - * @param p_persist_slot_to_par_num Persistent-slot to live-parameter mapping. - * @return Absolute NVM address of the selected record. + * @param p_persist_slot_to_par_num Compile-time slot-to-parameter mapping table. + * @return Absolute address of the selected record. */ -uint32_t par_nvm_layout_addr_from_persist_idx(const uint32_t first_data_obj_addr, - const uint16_t persist_idx, - const par_num_t * const p_persist_slot_to_par_num) +static uint32_t par_nvm_layout_fixed_payload_only_addr_from_persist_idx(const uint32_t first_data_obj_addr, + const uint16_t persist_idx, + const par_num_t * const p_persist_slot_to_par_num) { uint32_t addr = first_data_obj_addr; @@ -80,30 +72,48 @@ uint32_t par_nvm_layout_addr_from_persist_idx(const uint32_t first_data_obj_addr for (uint16_t it = 0U; it < persist_idx; it++) { - addr += par_nvm_layout_record_size_from_par_num(p_persist_slot_to_par_num[it]); + addr += par_nvm_layout_fixed_payload_only_record_size_from_par_num(p_persist_slot_to_par_num[it]); } return addr; } /** - * @brief Read one payload-only record from NVM. + * @brief Populate one canonical NVM object from the live parameter value. * - * @param p_store Storage backend API. - * @param addr Record start address. * @param par_num Live parameter number. - * @param p_obj Output canonical object. + * @param p_live_data Pointer to the live canonical parameter value. + * @param p_obj Output canonical NVM object. + */ +static void par_nvm_layout_fixed_payload_only_populate_data_obj(const par_num_t par_num, + const par_type_t * const p_live_data, + par_nvm_data_obj_t * const p_obj) +{ + (void)par_num; + PAR_ASSERT((NULL != p_live_data) && (NULL != p_obj)); + + memset(p_obj, 0, sizeof(*p_obj)); + p_obj->data = *p_live_data; +} + +/** + * @brief Read and validate one serialized record from storage. + * + * @param p_store Active storage backend API. + * @param addr Absolute record address inside the managed NVM image. + * @param par_num Live parameter number associated with the slot. + * @param p_obj Output canonical NVM object. * @return Operation status. */ -par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, - const uint32_t addr, - const par_num_t par_num, - par_nvm_data_obj_t * const p_obj) +static par_status_t par_nvm_layout_fixed_payload_only_read(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + par_nvm_data_obj_t * const p_obj) { uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_MAX_SIZE] = { 0U }; const par_cfg_t * const p_cfg = par_get_config(par_num); const uint8_t payload_size = par_nvm_layout_payload_size_from_par_num(par_num); - const uint32_t record_size = par_nvm_layout_record_size_from_payload_size(payload_size); + const uint32_t record_size = par_nvm_layout_fixed_payload_only_record_size_from_payload_size(payload_size); const uint8_t * const p_payload = &record_buf[PAR_NVM_RECORD_CRC_SIZE]; uint8_t crc_calc = 0U; @@ -127,23 +137,23 @@ par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, } /** - * @brief Write one payload-only record to NVM. + * @brief Serialize and write one canonical NVM object to storage. * - * @param p_store Storage backend API. - * @param addr Record start address. - * @param par_num Live parameter number. - * @param p_obj Canonical object to serialize. + * @param p_store Active storage backend API. + * @param addr Absolute record address inside the managed NVM image. + * @param par_num Live parameter number associated with the slot. + * @param p_obj Canonical NVM object to serialize. * @return Operation status. */ -par_status_t par_nvm_layout_write(const par_store_backend_api_t * const p_store, - const uint32_t addr, - const par_num_t par_num, - const par_nvm_data_obj_t * const p_obj) +static par_status_t par_nvm_layout_fixed_payload_only_write(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) { uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_MAX_SIZE] = { 0U }; const par_cfg_t * const p_cfg = par_get_config(par_num); const uint8_t payload_size = par_nvm_layout_payload_size_from_par_num(par_num); - const uint32_t record_size = par_nvm_layout_record_size_from_payload_size(payload_size); + const uint32_t record_size = par_nvm_layout_fixed_payload_only_record_size_from_payload_size(payload_size); uint8_t * const p_payload = &record_buf[PAR_NVM_RECORD_CRC_SIZE]; PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); @@ -155,4 +165,118 @@ par_status_t par_nvm_layout_write(const par_store_backend_api_t * const p_store, return (ePAR_OK == p_store->write(addr, record_size, record_buf)) ? ePAR_OK : ePAR_ERROR_NVM; } +/** + * @brief Validate one loaded canonical object against the current live schema. + * + * @param par_num Live parameter number expected at this slot. + * @param p_obj Canonical object loaded from NVM. + * @param pp_reason Output short mismatch reason for diagnostics. + * @param p_stored_id Output stored ID value when the layout carries one. + * @return Operation status. + */ +static par_status_t par_nvm_layout_fixed_payload_only_validate_loaded_obj(const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj, + const char ** const pp_reason, + uint16_t * const p_stored_id) +{ + PAR_ASSERT((NULL != p_obj) && (NULL != pp_reason) && (NULL != p_stored_id)); + + (void)p_obj; + *pp_reason = NULL; + *p_stored_id = par_cfg_get_param_id_const(par_num); + return ePAR_OK; +} + +/** + * @brief Return the stored-ID diagnostic value for an error path. + * + * @param par_num Live parameter number associated with the slot. + * @param p_obj Canonical object loaded from NVM. + * @return Stored ID or a layout-defined fallback value. + */ +static uint16_t par_nvm_layout_fixed_payload_only_get_error_stored_id(const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) +{ + (void)p_obj; + return par_cfg_get_param_id_const(par_num); +} + +/** + * @brief Decide whether the stored header remains compatible with this layout. + * + * @param p_head_obj Validated NVM header object. + * @return Layout-specific compatibility decision. + */ +static par_nvm_compat_result_t par_nvm_layout_fixed_payload_only_check_compat(const par_nvm_head_obj_t * const p_head_obj) +{ + PAR_ASSERT(NULL != p_head_obj); + + if (p_head_obj->obj_nb > (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) + { + return ePAR_NVM_COMPAT_REBUILD; + } + + if (p_head_obj->table_id != par_nvm_table_id_calc_for_count(p_head_obj->obj_nb)) + { + return ePAR_NVM_COMPAT_REBUILD; + } + + return (p_head_obj->obj_nb == (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) ? + ePAR_NVM_COMPAT_EXACT_MATCH : + ePAR_NVM_COMPAT_PREFIX_APPEND; +} + +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) +/** + * @brief Compare an expected object against a post-write read-back object. + * + * @param par_num Live parameter number associated with the slot. + * @param p_expected Expected canonical NVM object. + * @param p_actual Canonical object reloaded from storage. + * @return True when both objects match under this layout policy. + */ +static bool par_nvm_layout_fixed_payload_only_data_obj_matches(const par_num_t par_num, + const par_nvm_data_obj_t * const p_expected, + const par_nvm_data_obj_t * const p_actual) +{ + const par_cfg_t * const p_cfg = par_get_config(par_num); + const uint8_t payload_size = par_nvm_layout_payload_size_from_par_num(par_num); + uint8_t expected_payload[PAR_NVM_RECORD_DATA_SLOT_SIZE] = { 0U }; + uint8_t actual_payload[PAR_NVM_RECORD_DATA_SLOT_SIZE] = { 0U }; + + PAR_ASSERT((NULL != p_cfg) && (NULL != p_expected) && (NULL != p_actual)); + + par_nvm_layout_pack_payload_bytes(p_cfg->type, &p_expected->data, expected_payload); + par_nvm_layout_pack_payload_bytes(p_cfg->type, &p_actual->data, actual_payload); + return (0 == memcmp(expected_payload, actual_payload, payload_size)); +} +#endif + +/** + * @brief Concrete layout adapter bound by the common NVM core. + */ +static const par_nvm_layout_api_t g_par_nvm_layout_api = { + .record_size_from_par_num = par_nvm_layout_fixed_payload_only_record_size_from_par_num, + .addr_from_persist_idx = par_nvm_layout_fixed_payload_only_addr_from_persist_idx, + .populate_data_obj = par_nvm_layout_fixed_payload_only_populate_data_obj, + .read = par_nvm_layout_fixed_payload_only_read, + .write = par_nvm_layout_fixed_payload_only_write, + .validate_loaded_obj = par_nvm_layout_fixed_payload_only_validate_loaded_obj, + .get_error_stored_id = par_nvm_layout_fixed_payload_only_get_error_stored_id, + .check_compat = par_nvm_layout_fixed_payload_only_check_compat, +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) + .data_obj_matches = par_nvm_layout_fixed_payload_only_data_obj_matches, +#endif +}; + +/** + * @brief Return the concrete layout adapter selected for this build. + * + * @return Non-null pointer to this layout adapter. + */ +const par_nvm_layout_api_t *par_nvm_layout_init(void) +{ + return &g_par_nvm_layout_api; +} + #endif /* fixed-payload-only */ diff --git a/src/persist/par_nvm_layout_fixed_slot_no_size.c b/src/persist/par_nvm_layout_fixed_slot_no_size.c index 2ef0b03..d5e266b 100644 --- a/src/persist/par_nvm_layout_fixed_slot_no_size.c +++ b/src/persist/par_nvm_layout_fixed_slot_no_size.c @@ -2,8 +2,8 @@ * @file par_nvm_layout_fixed_slot_no_size.c * @brief Implement the fixed-slot persisted-record layout without a size descriptor. * @author wdfk-prog () - * @version 1.0 - * @date 2026-04-06 + * @version 1.1 + * @date 2026-04-13 * * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. * @@ -11,6 +11,7 @@ * @par Change Log: * Date Version Author Description * 2026-04-06 1.0 wdfk-prog first version + * 2026-04-13 1.1 wdfk-prog add layout-ops adapter */ #include "persist/par_nvm_layout.h" @@ -18,62 +19,76 @@ #include +#include "persist/par_nvm_table_id.h" + #if (1 == PAR_CFG_NVM_BACKEND_FLASH_EN) #error "PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE is not supported with the flash backend because it serializes to 7 bytes." #endif -/** - * @brief Serialized size of one fixed-slot record without a size field. - * - * @note The persisted image is always 7 bytes. The macro intentionally avoids - * sizeof(record-type) so trailing padding cannot change the serialized format. - */ #define PAR_NVM_LAYOUT_RECORD_SIZE (PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_CRC_SIZE + (uint32_t)PAR_NVM_RECORD_DATA_SLOT_SIZE) PAR_STATIC_ASSERT(par_nvm_layout_fixed_no_size_payload_slot_is_4_bytes, (sizeof(((par_nvm_layout_fixed_slot_no_size_record_t *)0)->payload) == 4u)); /** - * @brief Get serialized record size for one persistent parameter. + * @brief Return the serialized byte size of one persisted record for this layout. * - * @param par_num Live parameter number. + * @param par_num Live parameter number associated with the slot. * @return Serialized record size in bytes. */ -uint32_t par_nvm_layout_record_size_from_par_num(const par_num_t par_num) +static uint32_t par_nvm_layout_fixed_slot_no_size_record_size_from_par_num(const par_num_t par_num) { (void)par_num; return PAR_NVM_LAYOUT_RECORD_SIZE; } /** - * @brief Resolve record address for the fixed-slot-no-size layout. + * @brief Translate one persistent slot index into its serialized record address. * - * @param first_data_obj_addr Start address of the first persisted object. + * @param first_data_obj_addr Absolute address of the first persisted record. * @param persist_idx Compile-time persistent slot index. - * @param p_persist_slot_to_par_num Persistent-slot to live-parameter mapping. - * @return Absolute NVM address of the selected record. + * @param p_persist_slot_to_par_num Compile-time slot-to-parameter mapping table. + * @return Absolute address of the selected record. */ -uint32_t par_nvm_layout_addr_from_persist_idx(const uint32_t first_data_obj_addr, - const uint16_t persist_idx, - const par_num_t * const p_persist_slot_to_par_num) +static uint32_t par_nvm_layout_fixed_slot_no_size_addr_from_persist_idx(const uint32_t first_data_obj_addr, + const uint16_t persist_idx, + const par_num_t * const p_persist_slot_to_par_num) { (void)p_persist_slot_to_par_num; return (first_data_obj_addr + ((uint32_t)persist_idx * PAR_NVM_LAYOUT_RECORD_SIZE)); } /** - * @brief Read one fixed-slot-no-size record from NVM. + * @brief Populate one canonical NVM object from the live parameter value. * - * @param p_store Storage backend API. - * @param addr Record start address. * @param par_num Live parameter number. - * @param p_obj Output canonical object. + * @param p_live_data Pointer to the live canonical parameter value. + * @param p_obj Output canonical NVM object. + */ +static void par_nvm_layout_fixed_slot_no_size_populate_data_obj(const par_num_t par_num, + const par_type_t * const p_live_data, + par_nvm_data_obj_t * const p_obj) +{ + PAR_ASSERT((NULL != p_live_data) && (NULL != p_obj)); + + memset(p_obj, 0, sizeof(*p_obj)); + p_obj->id = par_cfg_get_param_id_const(par_num); + p_obj->data = *p_live_data; +} + +/** + * @brief Read and validate one serialized record from storage. + * + * @param p_store Active storage backend API. + * @param addr Absolute record address inside the managed NVM image. + * @param par_num Live parameter number associated with the slot. + * @param p_obj Output canonical NVM object. * @return Operation status. */ -par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, - const uint32_t addr, - const par_num_t par_num, - par_nvm_data_obj_t * const p_obj) +static par_status_t par_nvm_layout_fixed_slot_no_size_read(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + par_nvm_data_obj_t * const p_obj) { uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_SIZE] = { 0U }; par_type_t payload_raw = { 0U }; @@ -106,18 +121,18 @@ par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, } /** - * @brief Write one fixed-slot-no-size record to NVM. + * @brief Serialize and write one canonical NVM object to storage. * - * @param p_store Storage backend API. - * @param addr Record start address. - * @param par_num Live parameter number. - * @param p_obj Canonical object to serialize. + * @param p_store Active storage backend API. + * @param addr Absolute record address inside the managed NVM image. + * @param par_num Live parameter number associated with the slot. + * @param p_obj Canonical NVM object to serialize. * @return Operation status. */ -par_status_t par_nvm_layout_write(const par_store_backend_api_t * const p_store, - const uint32_t addr, - const par_num_t par_num, - const par_nvm_data_obj_t * const p_obj) +static par_status_t par_nvm_layout_fixed_slot_no_size_write(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) { uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_SIZE] = { 0U }; par_type_t payload_raw = p_obj->data; @@ -139,4 +154,130 @@ par_status_t par_nvm_layout_write(const par_store_backend_api_t * const p_store, return (ePAR_OK == p_store->write(addr, PAR_NVM_LAYOUT_RECORD_SIZE, record_buf)) ? ePAR_OK : ePAR_ERROR_NVM; } +/** + * @brief Validate one loaded canonical object against the current live schema. + * + * @param par_num Live parameter number expected at this slot. + * @param p_obj Canonical object loaded from NVM. + * @param pp_reason Output short mismatch reason for diagnostics. + * @param p_stored_id Output stored ID value when the layout carries one. + * @return Operation status. + */ +static par_status_t par_nvm_layout_fixed_slot_no_size_validate_loaded_obj(const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj, + const char ** const pp_reason, + uint16_t * const p_stored_id) +{ + const uint16_t expected_id = par_cfg_get_param_id_const(par_num); + + PAR_ASSERT((NULL != p_obj) && (NULL != pp_reason) && (NULL != p_stored_id)); + + *pp_reason = NULL; + *p_stored_id = p_obj->id; + + if (p_obj->id != expected_id) + { + *pp_reason = "id-mismatch"; + return ePAR_ERROR; + } + + return ePAR_OK; +} + +/** + * @brief Return the stored-ID diagnostic value for an error path. + * + * @param par_num Live parameter number associated with the slot. + * @param p_obj Canonical object loaded from NVM. + * @return Stored ID or a layout-defined fallback value. + */ +static uint16_t par_nvm_layout_fixed_slot_no_size_get_error_stored_id(const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) +{ + (void)par_num; + return (NULL != p_obj) ? p_obj->id : 0U; +} + +/** + * @brief Decide whether the stored header remains compatible with this layout. + * + * @param p_head_obj Validated NVM header object. + * @return Layout-specific compatibility decision. + */ +static par_nvm_compat_result_t par_nvm_layout_fixed_slot_no_size_check_compat(const par_nvm_head_obj_t * const p_head_obj) +{ + PAR_ASSERT(NULL != p_head_obj); + + if (p_head_obj->obj_nb > (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) + { + return ePAR_NVM_COMPAT_REBUILD; + } + + if (p_head_obj->table_id != par_nvm_table_id_calc_for_count(p_head_obj->obj_nb)) + { + return ePAR_NVM_COMPAT_REBUILD; + } + + return (p_head_obj->obj_nb == (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) ? + ePAR_NVM_COMPAT_EXACT_MATCH : + ePAR_NVM_COMPAT_PREFIX_APPEND; +} + +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) +/** + * @brief Compare an expected object against a post-write read-back object. + * + * @param par_num Live parameter number associated with the slot. + * @param p_expected Expected canonical NVM object. + * @param p_actual Canonical object reloaded from storage. + * @return True when both objects match under this layout policy. + */ +static bool par_nvm_layout_fixed_slot_no_size_data_obj_matches(const par_num_t par_num, + const par_nvm_data_obj_t * const p_expected, + const par_nvm_data_obj_t * const p_actual) +{ + uint8_t expected_payload[PAR_NVM_RECORD_DATA_SLOT_SIZE] = { 0U }; + uint8_t actual_payload[PAR_NVM_RECORD_DATA_SLOT_SIZE] = { 0U }; + + (void)par_num; + PAR_ASSERT((NULL != p_expected) && (NULL != p_actual)); + + if (p_expected->id != p_actual->id) + { + return false; + } + + memcpy(expected_payload, &p_expected->data, sizeof(p_expected->data)); + memcpy(actual_payload, &p_actual->data, sizeof(p_actual->data)); + return (0 == memcmp(expected_payload, actual_payload, PAR_NVM_RECORD_DATA_SLOT_SIZE)); +} +#endif + +/** + * @brief Concrete layout adapter bound by the common NVM core. + */ +static const par_nvm_layout_api_t g_par_nvm_layout_api = { + .record_size_from_par_num = par_nvm_layout_fixed_slot_no_size_record_size_from_par_num, + .addr_from_persist_idx = par_nvm_layout_fixed_slot_no_size_addr_from_persist_idx, + .populate_data_obj = par_nvm_layout_fixed_slot_no_size_populate_data_obj, + .read = par_nvm_layout_fixed_slot_no_size_read, + .write = par_nvm_layout_fixed_slot_no_size_write, + .validate_loaded_obj = par_nvm_layout_fixed_slot_no_size_validate_loaded_obj, + .get_error_stored_id = par_nvm_layout_fixed_slot_no_size_get_error_stored_id, + .check_compat = par_nvm_layout_fixed_slot_no_size_check_compat, +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) + .data_obj_matches = par_nvm_layout_fixed_slot_no_size_data_obj_matches, +#endif +}; + +/** + * @brief Return the concrete layout adapter selected for this build. + * + * @return Non-null pointer to this layout adapter. + */ +const par_nvm_layout_api_t *par_nvm_layout_init(void) +{ + return &g_par_nvm_layout_api; +} + #endif /* fixed-slot-no-size */ diff --git a/src/persist/par_nvm_layout_fixed_slot_with_size.c b/src/persist/par_nvm_layout_fixed_slot_with_size.c index 31282c5..7c6d275 100644 --- a/src/persist/par_nvm_layout_fixed_slot_with_size.c +++ b/src/persist/par_nvm_layout_fixed_slot_with_size.c @@ -2,8 +2,8 @@ * @file par_nvm_layout_fixed_slot_with_size.c * @brief Implement the fixed-slot persisted-record layout with a size descriptor. * @author wdfk-prog () - * @version 1.0 - * @date 2026-04-06 + * @version 1.1 + * @date 2026-04-13 * * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. * @@ -11,6 +11,7 @@ * @par Change Log: * Date Version Author Description * 2026-04-06 1.0 wdfk-prog first version + * 2026-04-13 1.1 wdfk-prog add layout-ops adapter */ #include "persist/par_nvm_layout.h" @@ -18,6 +19,8 @@ #include +#include "persist/par_nvm_table_id.h" + /** * @brief Serialized size of one fixed-slot record with an explicit size field. */ @@ -27,46 +30,65 @@ PAR_STATIC_ASSERT(par_nvm_layout_fixed_with_size_record_is_8_bytes, (sizeof(par_nvm_layout_fixed_slot_with_size_record_t) == 8u)); /** - * @brief Get serialized record size for one persistent parameter. + * @brief Return the serialized byte size of one persisted record for this layout. * - * @param par_num Live parameter number. + * @param par_num Live parameter number associated with the slot. * @return Serialized record size in bytes. */ -uint32_t par_nvm_layout_record_size_from_par_num(const par_num_t par_num) +static uint32_t par_nvm_layout_fixed_slot_with_size_record_size_from_par_num(const par_num_t par_num) { (void)par_num; return PAR_NVM_LAYOUT_RECORD_SIZE; } /** - * @brief Resolve record address for the fixed-slot-with-size layout. + * @brief Translate one persistent slot index into its serialized record address. * - * @param first_data_obj_addr Start address of the first persisted object. + * @param first_data_obj_addr Absolute address of the first persisted record. * @param persist_idx Compile-time persistent slot index. - * @param p_persist_slot_to_par_num Persistent-slot to live-parameter mapping. - * @return Absolute NVM address of the selected record. + * @param p_persist_slot_to_par_num Compile-time slot-to-parameter mapping table. + * @return Absolute address of the selected record. */ -uint32_t par_nvm_layout_addr_from_persist_idx(const uint32_t first_data_obj_addr, - const uint16_t persist_idx, - const par_num_t * const p_persist_slot_to_par_num) +static uint32_t par_nvm_layout_fixed_slot_with_size_addr_from_persist_idx(const uint32_t first_data_obj_addr, + const uint16_t persist_idx, + const par_num_t * const p_persist_slot_to_par_num) { (void)p_persist_slot_to_par_num; return (first_data_obj_addr + ((uint32_t)persist_idx * PAR_NVM_LAYOUT_RECORD_SIZE)); } /** - * @brief Read one fixed-slot-with-size record from NVM. + * @brief Populate one canonical NVM object from the live parameter value. * - * @param p_store Storage backend API. - * @param addr Record start address. * @param par_num Live parameter number. - * @param p_obj Output canonical object. + * @param p_live_data Pointer to the live canonical parameter value. + * @param p_obj Output canonical NVM object. + */ +static void par_nvm_layout_fixed_slot_with_size_populate_data_obj(const par_num_t par_num, + const par_type_t * const p_live_data, + par_nvm_data_obj_t * const p_obj) +{ + PAR_ASSERT((NULL != p_live_data) && (NULL != p_obj)); + + memset(p_obj, 0, sizeof(*p_obj)); + p_obj->id = par_cfg_get_param_id_const(par_num); + p_obj->size = PAR_NVM_RECORD_DATA_SLOT_SIZE; + p_obj->data = *p_live_data; +} + +/** + * @brief Read and validate one serialized record from storage. + * + * @param p_store Active storage backend API. + * @param addr Absolute record address inside the managed NVM image. + * @param par_num Live parameter number associated with the slot. + * @param p_obj Output canonical NVM object. * @return Operation status. */ -par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, - const uint32_t addr, - const par_num_t par_num, - par_nvm_data_obj_t * const p_obj) +static par_status_t par_nvm_layout_fixed_slot_with_size_read(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + par_nvm_data_obj_t * const p_obj) { par_nvm_layout_fixed_slot_with_size_record_t record = { 0U }; uint8_t crc_calc = 0U; @@ -102,18 +124,18 @@ par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, } /** - * @brief Write one fixed-slot-with-size record to NVM. + * @brief Serialize and write one canonical NVM object to storage. * - * @param p_store Storage backend API. - * @param addr Record start address. - * @param par_num Live parameter number. - * @param p_obj Canonical object to serialize. + * @param p_store Active storage backend API. + * @param addr Absolute record address inside the managed NVM image. + * @param par_num Live parameter number associated with the slot. + * @param p_obj Canonical NVM object to serialize. * @return Operation status. */ -par_status_t par_nvm_layout_write(const par_store_backend_api_t * const p_store, - const uint32_t addr, - const par_num_t par_num, - const par_nvm_data_obj_t * const p_obj) +static par_status_t par_nvm_layout_fixed_slot_with_size_write(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) { par_nvm_layout_fixed_slot_with_size_record_t record = { 0U }; @@ -132,4 +154,137 @@ par_status_t par_nvm_layout_write(const par_store_backend_api_t * const p_store, return (ePAR_OK == p_store->write(addr, PAR_NVM_LAYOUT_RECORD_SIZE, (const uint8_t *)&record)) ? ePAR_OK : ePAR_ERROR_NVM; } +/** + * @brief Validate one loaded canonical object against the current live schema. + * + * @param par_num Live parameter number expected at this slot. + * @param p_obj Canonical object loaded from NVM. + * @param pp_reason Output short mismatch reason for diagnostics. + * @param p_stored_id Output stored ID value when the layout carries one. + * @return Operation status. + */ +static par_status_t par_nvm_layout_fixed_slot_with_size_validate_loaded_obj(const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj, + const char ** const pp_reason, + uint16_t * const p_stored_id) +{ + const uint16_t expected_id = par_cfg_get_param_id_const(par_num); + + PAR_ASSERT((NULL != p_obj) && (NULL != pp_reason) && (NULL != p_stored_id)); + + *pp_reason = NULL; + *p_stored_id = p_obj->id; + + if (p_obj->id != expected_id) + { + *pp_reason = "id-mismatch"; + return ePAR_ERROR; + } + + if (p_obj->size != PAR_NVM_RECORD_DATA_SLOT_SIZE) + { + *pp_reason = "size-mismatch"; + return ePAR_ERROR; + } + + return ePAR_OK; +} + +/** + * @brief Return the stored-ID diagnostic value for an error path. + * + * @param par_num Live parameter number associated with the slot. + * @param p_obj Canonical object loaded from NVM. + * @return Stored ID or a layout-defined fallback value. + */ +static uint16_t par_nvm_layout_fixed_slot_with_size_get_error_stored_id(const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) +{ + (void)par_num; + return (NULL != p_obj) ? p_obj->id : 0U; +} + +/** + * @brief Decide whether the stored header remains compatible with this layout. + * + * @param p_head_obj Validated NVM header object. + * @return Layout-specific compatibility decision. + */ +static par_nvm_compat_result_t par_nvm_layout_fixed_slot_with_size_check_compat(const par_nvm_head_obj_t * const p_head_obj) +{ + PAR_ASSERT(NULL != p_head_obj); + + if (p_head_obj->obj_nb > (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) + { + return ePAR_NVM_COMPAT_REBUILD; + } + + if (p_head_obj->table_id != par_nvm_table_id_calc_for_count(p_head_obj->obj_nb)) + { + return ePAR_NVM_COMPAT_REBUILD; + } + + return (p_head_obj->obj_nb == (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) ? + ePAR_NVM_COMPAT_EXACT_MATCH : + ePAR_NVM_COMPAT_PREFIX_APPEND; +} + +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) +/** + * @brief Compare an expected object against a post-write read-back object. + * + * @param par_num Live parameter number associated with the slot. + * @param p_expected Expected canonical NVM object. + * @param p_actual Canonical object reloaded from storage. + * @return True when both objects match under this layout policy. + */ +static bool par_nvm_layout_fixed_slot_with_size_data_obj_matches(const par_num_t par_num, + const par_nvm_data_obj_t * const p_expected, + const par_nvm_data_obj_t * const p_actual) +{ + uint8_t expected_payload[PAR_NVM_RECORD_DATA_SLOT_SIZE] = { 0U }; + uint8_t actual_payload[PAR_NVM_RECORD_DATA_SLOT_SIZE] = { 0U }; + + (void)par_num; + PAR_ASSERT((NULL != p_expected) && (NULL != p_actual)); + + if ((p_expected->id != p_actual->id) || + (p_actual->size != PAR_NVM_RECORD_DATA_SLOT_SIZE)) + { + return false; + } + + memcpy(expected_payload, &p_expected->data, sizeof(p_expected->data)); + memcpy(actual_payload, &p_actual->data, sizeof(p_actual->data)); + return (0 == memcmp(expected_payload, actual_payload, PAR_NVM_RECORD_DATA_SLOT_SIZE)); +} +#endif + +/** + * @brief Concrete layout adapter bound by the common NVM core. + */ +static const par_nvm_layout_api_t g_par_nvm_layout_api = { + .record_size_from_par_num = par_nvm_layout_fixed_slot_with_size_record_size_from_par_num, + .addr_from_persist_idx = par_nvm_layout_fixed_slot_with_size_addr_from_persist_idx, + .populate_data_obj = par_nvm_layout_fixed_slot_with_size_populate_data_obj, + .read = par_nvm_layout_fixed_slot_with_size_read, + .write = par_nvm_layout_fixed_slot_with_size_write, + .validate_loaded_obj = par_nvm_layout_fixed_slot_with_size_validate_loaded_obj, + .get_error_stored_id = par_nvm_layout_fixed_slot_with_size_get_error_stored_id, + .check_compat = par_nvm_layout_fixed_slot_with_size_check_compat, +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) + .data_obj_matches = par_nvm_layout_fixed_slot_with_size_data_obj_matches, +#endif +}; + +/** + * @brief Return the concrete layout adapter selected for this build. + * + * @return Non-null pointer to this layout adapter. + */ +const par_nvm_layout_api_t *par_nvm_layout_init(void) +{ + return &g_par_nvm_layout_api; +} + #endif /* fixed-slot-with-size */ diff --git a/src/persist/par_nvm_layout_grouped_payload_only.c b/src/persist/par_nvm_layout_grouped_payload_only.c index 179dd10..74152cb 100644 --- a/src/persist/par_nvm_layout_grouped_payload_only.c +++ b/src/persist/par_nvm_layout_grouped_payload_only.c @@ -2,16 +2,16 @@ * @file par_nvm_layout_grouped_payload_only.c * @brief Implement the grouped payload-only NVM layout. * @author wdfk-prog () - * @version 1.0 - * @date 2026-04-11 + * @version 1.1 + * @date 2026-04-13 * * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. * * @note : * @par Change Log: * Date Version Author Description - * 2026-04-10 1.0 wdfk-prog first version - * 2026-04-11 1.1 wdfk-prog restore layout comments and split layout structs + * 2026-04-11 1.0 wdfk-prog first version + * 2026-04-13 1.1 wdfk-prog add layout-ops adapter */ #include "persist/par_nvm_layout.h" @@ -19,60 +19,51 @@ #include +#include "persist/par_nvm_table_id.h" + #if (1 == PAR_CFG_NVM_BACKEND_FLASH_EN) #error "Payload-only NVM layouts are not supported with the flash backend because records are variable width and not guaranteed to stay 8-byte aligned." #endif -/** - * @brief Serialized overhead of one grouped payload-only record. - */ #define PAR_NVM_LAYOUT_RECORD_OVERHEAD ((uint32_t)PAR_NVM_RECORD_CRC_SIZE) - -/** - * @brief Maximum serialized size of one grouped payload-only record. - */ #define PAR_NVM_LAYOUT_RECORD_MAX_SIZE (PAR_NVM_LAYOUT_RECORD_OVERHEAD + PAR_NVM_RECORD_DATA_SLOT_SIZE) PAR_STATIC_ASSERT(par_nvm_layout_grouped_payload_only_record_payload_slot_is_4_bytes, (sizeof(((par_nvm_layout_grouped_payload_only_record_t *)0)->payload) == 4u)); /** - * @brief Resolve serialized record size from the active payload width. + * @brief Return the serialized record size for one grouped payload-only record width. * - * @param payload_size Active payload width in bytes. + * @param payload_size Natural payload width in bytes. * @return Serialized record size in bytes. */ -static uint32_t par_nvm_layout_record_size_from_payload_size(const uint8_t payload_size) +static uint32_t par_nvm_layout_grouped_payload_only_record_size_from_payload_size(const uint8_t payload_size) { return (PAR_NVM_LAYOUT_RECORD_OVERHEAD + (uint32_t)payload_size); } /** - * @brief Get serialized record size for one persistent parameter. + * @brief Return the serialized byte size of one persisted record for this layout. * - * @param par_num Live parameter number. + * @param par_num Live parameter number associated with the slot. * @return Serialized record size in bytes. */ -uint32_t par_nvm_layout_record_size_from_par_num(const par_num_t par_num) +static uint32_t par_nvm_layout_grouped_payload_only_record_size_from_par_num(const par_num_t par_num) { - return par_nvm_layout_record_size_from_payload_size(par_nvm_layout_payload_size_from_par_num(par_num)); + return par_nvm_layout_grouped_payload_only_record_size_from_payload_size(par_nvm_layout_payload_size_from_par_num(par_num)); } /** - * @brief Resolve record address for the grouped payload-only layout. + * @brief Translate one persistent slot index into its serialized record address. * - * @details Persistent records are regrouped into 8-bit, 16-bit, and 32-bit - * payload bands. Address resolution therefore sums complete preceding groups - * and then adds the same-group prefix before the target record. - * - * @param first_data_obj_addr Start address of the first persisted object. + * @param first_data_obj_addr Absolute address of the first persisted record. * @param persist_idx Compile-time persistent slot index. - * @param p_persist_slot_to_par_num Persistent-slot to live-parameter mapping. - * @return Absolute NVM address of the selected record. + * @param p_persist_slot_to_par_num Compile-time slot-to-parameter mapping table. + * @return Absolute address of the selected record. */ -uint32_t par_nvm_layout_addr_from_persist_idx(const uint32_t first_data_obj_addr, - const uint16_t persist_idx, - const par_num_t * const p_persist_slot_to_par_num) +static uint32_t par_nvm_layout_grouped_payload_only_addr_from_persist_idx(const uint32_t first_data_obj_addr, + const uint16_t persist_idx, + const par_num_t * const p_persist_slot_to_par_num) { uint32_t total_size_8 = 0U; uint32_t total_size_16 = 0U; @@ -86,7 +77,7 @@ uint32_t par_nvm_layout_addr_from_persist_idx(const uint32_t first_data_obj_addr for (uint16_t it = 0U; it < PAR_PERSISTENT_COMPILE_COUNT; it++) { const uint8_t payload_size = par_nvm_layout_payload_size_from_par_num(p_persist_slot_to_par_num[it]); - const uint32_t record_size = par_nvm_layout_record_size_from_payload_size(payload_size); + const uint32_t record_size = par_nvm_layout_grouped_payload_only_record_size_from_payload_size(payload_size); switch (payload_size) { @@ -130,23 +121,41 @@ uint32_t par_nvm_layout_addr_from_persist_idx(const uint32_t first_data_obj_addr } /** - * @brief Read one grouped payload-only record from NVM. + * @brief Populate one canonical NVM object from the live parameter value. * - * @param p_store Storage backend API. - * @param addr Record start address. * @param par_num Live parameter number. - * @param p_obj Output canonical object. + * @param p_live_data Pointer to the live canonical parameter value. + * @param p_obj Output canonical NVM object. + */ +static void par_nvm_layout_grouped_payload_only_populate_data_obj(const par_num_t par_num, + const par_type_t * const p_live_data, + par_nvm_data_obj_t * const p_obj) +{ + (void)par_num; + PAR_ASSERT((NULL != p_live_data) && (NULL != p_obj)); + + memset(p_obj, 0, sizeof(*p_obj)); + p_obj->data = *p_live_data; +} + +/** + * @brief Read and validate one serialized record from storage. + * + * @param p_store Active storage backend API. + * @param addr Absolute record address inside the managed NVM image. + * @param par_num Live parameter number associated with the slot. + * @param p_obj Output canonical NVM object. * @return Operation status. */ -par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, - const uint32_t addr, - const par_num_t par_num, - par_nvm_data_obj_t * const p_obj) +static par_status_t par_nvm_layout_grouped_payload_only_read(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + par_nvm_data_obj_t * const p_obj) { uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_MAX_SIZE] = { 0U }; const par_cfg_t * const p_cfg = par_get_config(par_num); const uint8_t payload_size = par_nvm_layout_payload_size_from_par_num(par_num); - const uint32_t record_size = par_nvm_layout_record_size_from_payload_size(payload_size); + const uint32_t record_size = par_nvm_layout_grouped_payload_only_record_size_from_payload_size(payload_size); const uint8_t * const p_payload = &record_buf[PAR_NVM_RECORD_CRC_SIZE]; uint8_t crc_calc = 0U; @@ -170,23 +179,23 @@ par_status_t par_nvm_layout_read(const par_store_backend_api_t * const p_store, } /** - * @brief Write one grouped payload-only record to NVM. + * @brief Serialize and write one canonical NVM object to storage. * - * @param p_store Storage backend API. - * @param addr Record start address. - * @param par_num Live parameter number. - * @param p_obj Canonical object to serialize. + * @param p_store Active storage backend API. + * @param addr Absolute record address inside the managed NVM image. + * @param par_num Live parameter number associated with the slot. + * @param p_obj Canonical NVM object to serialize. * @return Operation status. */ -par_status_t par_nvm_layout_write(const par_store_backend_api_t * const p_store, - const uint32_t addr, - const par_num_t par_num, - const par_nvm_data_obj_t * const p_obj) +static par_status_t par_nvm_layout_grouped_payload_only_write(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) { uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_MAX_SIZE] = { 0U }; const par_cfg_t * const p_cfg = par_get_config(par_num); const uint8_t payload_size = par_nvm_layout_payload_size_from_par_num(par_num); - const uint32_t record_size = par_nvm_layout_record_size_from_payload_size(payload_size); + const uint32_t record_size = par_nvm_layout_grouped_payload_only_record_size_from_payload_size(payload_size); uint8_t * const p_payload = &record_buf[PAR_NVM_RECORD_CRC_SIZE]; PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); @@ -198,4 +207,118 @@ par_status_t par_nvm_layout_write(const par_store_backend_api_t * const p_store, return (ePAR_OK == p_store->write(addr, record_size, record_buf)) ? ePAR_OK : ePAR_ERROR_NVM; } +/** + * @brief Validate one loaded canonical object against the current live schema. + * + * @param par_num Live parameter number expected at this slot. + * @param p_obj Canonical object loaded from NVM. + * @param pp_reason Output short mismatch reason for diagnostics. + * @param p_stored_id Output stored ID value when the layout carries one. + * @return Operation status. + */ +static par_status_t par_nvm_layout_grouped_payload_only_validate_loaded_obj(const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj, + const char ** const pp_reason, + uint16_t * const p_stored_id) +{ + PAR_ASSERT((NULL != p_obj) && (NULL != pp_reason) && (NULL != p_stored_id)); + + (void)p_obj; + *pp_reason = NULL; + *p_stored_id = par_cfg_get_param_id_const(par_num); + return ePAR_OK; +} + +/** + * @brief Return the stored-ID diagnostic value for an error path. + * + * @param par_num Live parameter number associated with the slot. + * @param p_obj Canonical object loaded from NVM. + * @return Stored ID or a layout-defined fallback value. + */ +static uint16_t par_nvm_layout_grouped_payload_only_get_error_stored_id(const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) +{ + (void)p_obj; + return par_cfg_get_param_id_const(par_num); +} + +/** + * @brief Decide whether the stored header remains compatible with this layout. + * + * @param p_head_obj Validated NVM header object. + * @return Layout-specific compatibility decision. + */ +static par_nvm_compat_result_t par_nvm_layout_grouped_payload_only_check_compat(const par_nvm_head_obj_t * const p_head_obj) +{ + PAR_ASSERT(NULL != p_head_obj); + + if (p_head_obj->obj_nb > (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) + { + return ePAR_NVM_COMPAT_REBUILD; + } + + if (p_head_obj->table_id != par_nvm_table_id_calc_for_count(p_head_obj->obj_nb)) + { + return ePAR_NVM_COMPAT_REBUILD; + } + + return (p_head_obj->obj_nb == (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) ? + ePAR_NVM_COMPAT_EXACT_MATCH : + ePAR_NVM_COMPAT_REBUILD; +} + +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) +/** + * @brief Compare an expected object against a post-write read-back object. + * + * @param par_num Live parameter number associated with the slot. + * @param p_expected Expected canonical NVM object. + * @param p_actual Canonical object reloaded from storage. + * @return True when both objects match under this layout policy. + */ +static bool par_nvm_layout_grouped_payload_only_data_obj_matches(const par_num_t par_num, + const par_nvm_data_obj_t * const p_expected, + const par_nvm_data_obj_t * const p_actual) +{ + const par_cfg_t * const p_cfg = par_get_config(par_num); + const uint8_t payload_size = par_nvm_layout_payload_size_from_par_num(par_num); + uint8_t expected_payload[PAR_NVM_RECORD_DATA_SLOT_SIZE] = { 0U }; + uint8_t actual_payload[PAR_NVM_RECORD_DATA_SLOT_SIZE] = { 0U }; + + PAR_ASSERT((NULL != p_cfg) && (NULL != p_expected) && (NULL != p_actual)); + + par_nvm_layout_pack_payload_bytes(p_cfg->type, &p_expected->data, expected_payload); + par_nvm_layout_pack_payload_bytes(p_cfg->type, &p_actual->data, actual_payload); + return (0 == memcmp(expected_payload, actual_payload, payload_size)); +} +#endif + +/** + * @brief Concrete layout adapter bound by the common NVM core. + */ +static const par_nvm_layout_api_t g_par_nvm_layout_api = { + .record_size_from_par_num = par_nvm_layout_grouped_payload_only_record_size_from_par_num, + .addr_from_persist_idx = par_nvm_layout_grouped_payload_only_addr_from_persist_idx, + .populate_data_obj = par_nvm_layout_grouped_payload_only_populate_data_obj, + .read = par_nvm_layout_grouped_payload_only_read, + .write = par_nvm_layout_grouped_payload_only_write, + .validate_loaded_obj = par_nvm_layout_grouped_payload_only_validate_loaded_obj, + .get_error_stored_id = par_nvm_layout_grouped_payload_only_get_error_stored_id, + .check_compat = par_nvm_layout_grouped_payload_only_check_compat, +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) + .data_obj_matches = par_nvm_layout_grouped_payload_only_data_obj_matches, +#endif +}; + +/** + * @brief Return the concrete layout adapter selected for this build. + * + * @return Non-null pointer to this layout adapter. + */ +const par_nvm_layout_api_t *par_nvm_layout_init(void) +{ + return &g_par_nvm_layout_api; +} + #endif /* grouped-payload-only */ diff --git a/template/par_cfg_port.htmp b/template/par_cfg_port.htmp index 71d96d3..ca075bc 100644 --- a/template/par_cfg_port.htmp +++ b/template/par_cfg_port.htmp @@ -3,6 +3,7 @@ /* Optional platform overrides */ /* #define PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK ( 0 ) */ /* #define PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK ( 0 ) */ +/* #define PAR_CFG_NVM_WRITE_VERIFY_EN ( 0 ) */ /* #define PAR_CFG_NVM_RECORD_LAYOUT ( PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE ) */ /* or PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE */ /* or PAR_CFG_NVM_RECORD_LAYOUT_COMPACT_PAYLOAD */ From 6900dc85960ffe22d2e26cb5de49826a6577f860 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Mon, 13 Apr 2026 14:32:20 +0800 Subject: [PATCH 34/36] perf[persist][nvm]: generate compile-time address LUTs for record layouts --- src/persist/par_nvm_layout_compact_payload.c | 94 +++++++++-- .../par_nvm_layout_fixed_payload_only.c | 92 ++++++++-- .../par_nvm_layout_grouped_payload_only.c | 159 ++++++++++++------ 3 files changed, 269 insertions(+), 76 deletions(-) diff --git a/src/persist/par_nvm_layout_compact_payload.c b/src/persist/par_nvm_layout_compact_payload.c index 35f6fb0..cae5c4d 100644 --- a/src/persist/par_nvm_layout_compact_payload.c +++ b/src/persist/par_nvm_layout_compact_payload.c @@ -2,7 +2,7 @@ * @file par_nvm_layout_compact_payload.c * @brief Implement the compact persisted-record layout with id, size, crc, and payload bytes. * @author wdfk-prog () - * @version 1.1 + * @version 1.2 * @date 2026-04-13 * * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. @@ -12,11 +12,13 @@ * Date Version Author Description * 2026-04-06 1.0 wdfk-prog first version * 2026-04-13 1.1 wdfk-prog add layout-ops adapter + * 2026-04-13 1.2 wdfk-prog auto-generate compile-time compact-payload address LUT */ #include "persist/par_nvm_layout.h" #if (1 == PAR_CFG_NVM_EN) && (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_COMPACT_PAYLOAD) +#include #include #include "persist/par_nvm_table_id.h" @@ -49,6 +51,79 @@ static uint32_t par_nvm_layout_compact_payload_record_size_from_par_num(const pa return par_nvm_layout_compact_payload_record_size_from_payload_size(par_nvm_layout_payload_size_from_par_num(par_num)); } +/** + * @brief Compile-time packed offset map for the compact-payload layout. + * + * @details The generated struct keeps one byte of anchor storage first so the + * type remains valid even when no parameters are persistent. Every persistent + * record then contributes one tightly packed `uint8_t[]` member whose size is + * derived from `par_table.def`. `offsetof()` on these members therefore yields + * the exact prefix-sum byte offset without any handwritten constants. + */ +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_OFFSET_MAP_BASE_SIZE 1U +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_RECORD_SIZE_BYTES(payload_size_) (PAR_NVM_LAYOUT_RECORD_OVERHEAD + (uint32_t)(payload_size_)) +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(enum_, pers_, emit_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT_I(enum_, pers_, emit_) +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT_I(enum_, pers_, emit_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT_##pers_(enum_, emit_) +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT_1(enum_, emit_) emit_(enum_) +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT_0(enum_, emit_) +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_1(enum_) uint8_t slot_##enum_[PAR_NVM_LAYOUT_COMPACT_PAYLOAD_RECORD_SIZE_BYTES(1U)]; +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_2(enum_) uint8_t slot_##enum_[PAR_NVM_LAYOUT_COMPACT_PAYLOAD_RECORD_SIZE_BYTES(2U)]; +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_4(enum_) uint8_t slot_##enum_[PAR_NVM_LAYOUT_COMPACT_PAYLOAD_RECORD_SIZE_BYTES(4U)]; +typedef struct +{ + uint8_t base__[PAR_NVM_LAYOUT_COMPACT_PAYLOAD_OFFSET_MAP_BASE_SIZE]; +#define PAR_ITEM_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_1) +#define PAR_ITEM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_2) +#define PAR_ITEM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_4) +#define PAR_ITEM_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_1) +#define PAR_ITEM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_2) +#define PAR_ITEM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_4) +#define PAR_ITEM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_4) +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +} par_nvm_layout_compact_payload_offset_map_t; +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_OFFSET_OF(enum_) ((uint32_t)offsetof(par_nvm_layout_compact_payload_offset_map_t, slot_##enum_) - PAR_NVM_LAYOUT_COMPACT_PAYLOAD_OFFSET_MAP_BASE_SIZE) +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(enum_, pers_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT_I(enum_, pers_) +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT_I(enum_, pers_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT_##pers_(enum_) +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT_1(enum_) [PAR_PERSIST_IDX_##enum_] = PAR_NVM_LAYOUT_COMPACT_PAYLOAD_OFFSET_OF(enum_), +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT_0(enum_) +static const uint32_t g_par_nvm_layout_compact_payload_addr_lut[PAR_PERSIST_SLOT_MAP_CAPACITY] = { +#define PAR_ITEM_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(enum_, pers_) +#define PAR_ITEM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(enum_, pers_) +#define PAR_ITEM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(enum_, pers_) +#define PAR_ITEM_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(enum_, pers_) +#define PAR_ITEM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(enum_, pers_) +#define PAR_ITEM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(enum_, pers_) +#define PAR_ITEM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(enum_, pers_) +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +}; +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT_I +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT_1 +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT_0 +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_OFFSET_OF +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_1 +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_2 +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_4 +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT_I +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT_1 +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT_0 +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_RECORD_SIZE_BYTES + /** * @brief Translate one persistent slot index into its serialized record address. * @@ -61,16 +136,11 @@ static uint32_t par_nvm_layout_compact_payload_addr_from_persist_idx(const uint3 const uint16_t persist_idx, const par_num_t * const p_persist_slot_to_par_num) { - uint32_t addr = first_data_obj_addr; - PAR_ASSERT(NULL != p_persist_slot_to_par_num); + PAR_ASSERT(persist_idx < PAR_PERSISTENT_COMPILE_COUNT); + (void)p_persist_slot_to_par_num; - for (uint16_t it = 0U; it < persist_idx; it++) - { - addr += par_nvm_layout_compact_payload_record_size_from_par_num(p_persist_slot_to_par_num[it]); - } - - return addr; + return (first_data_obj_addr + g_par_nvm_layout_compact_payload_addr_lut[persist_idx]); } /** @@ -220,7 +290,7 @@ static par_status_t par_nvm_layout_compact_payload_validate_loaded_obj(const par * @return Stored ID or a layout-defined fallback value. */ static uint16_t par_nvm_layout_compact_payload_get_error_stored_id(const par_num_t par_num, - const par_nvm_data_obj_t * const p_obj) + const par_nvm_data_obj_t * const p_obj) { (void)par_num; return (NULL != p_obj) ? p_obj->id : 0U; @@ -246,9 +316,7 @@ static par_nvm_compat_result_t par_nvm_layout_compact_payload_check_compat(const return ePAR_NVM_COMPAT_REBUILD; } - return (p_head_obj->obj_nb == (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) ? - ePAR_NVM_COMPAT_EXACT_MATCH : - ePAR_NVM_COMPAT_PREFIX_APPEND; + return (p_head_obj->obj_nb == (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) ? ePAR_NVM_COMPAT_EXACT_MATCH : ePAR_NVM_COMPAT_PREFIX_APPEND; } #if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) diff --git a/src/persist/par_nvm_layout_fixed_payload_only.c b/src/persist/par_nvm_layout_fixed_payload_only.c index 00cd25b..c789f0b 100644 --- a/src/persist/par_nvm_layout_fixed_payload_only.c +++ b/src/persist/par_nvm_layout_fixed_payload_only.c @@ -2,7 +2,7 @@ * @file par_nvm_layout_fixed_payload_only.c * @brief Implement the fixed persistent-order payload-only NVM layout. * @author wdfk-prog () - * @version 1.2 + * @version 1.3 * @date 2026-04-13 * * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. @@ -13,11 +13,13 @@ * 2026-04-10 1.0 wdfk-prog first version * 2026-04-11 1.1 wdfk-prog restore layout comments and split layout structs * 2026-04-13 1.2 wdfk-prog add layout-ops adapter + * 2026-04-13 1.3 wdfk-prog auto-generate compile-time fixed-payload-only address LUT */ #include "persist/par_nvm_layout.h" #if (1 == PAR_CFG_NVM_EN) && (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_PAYLOAD_ONLY) +#include #include #include "persist/par_nvm_table_id.h" @@ -54,6 +56,79 @@ static uint32_t par_nvm_layout_fixed_payload_only_record_size_from_par_num(const return par_nvm_layout_fixed_payload_only_record_size_from_payload_size(par_nvm_layout_payload_size_from_par_num(par_num)); } +/** + * @brief Compile-time packed offset map for the fixed-payload-only layout. + * + * @details The generated struct keeps one anchor byte so the type remains valid + * even when no parameters are persistent. Every persistent record then expands + * into one tightly packed `uint8_t[]` member sized from `par_table.def`, and + * `offsetof()` yields the exact persistent prefix sum without handwritten + * offsets. + */ +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_OFFSET_MAP_BASE_SIZE 1U +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_RECORD_SIZE_BYTES(payload_size_) (PAR_NVM_LAYOUT_RECORD_OVERHEAD + (uint32_t)(payload_size_)) +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, emit_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT_I(enum_, pers_, emit_) +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT_I(enum_, pers_, emit_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT_##pers_(enum_, emit_) +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT_1(enum_, emit_) emit_(enum_) +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT_0(enum_, emit_) +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_1(enum_) uint8_t slot_##enum_[PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_RECORD_SIZE_BYTES(1U)]; +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_2(enum_) uint8_t slot_##enum_[PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_RECORD_SIZE_BYTES(2U)]; +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_4(enum_) uint8_t slot_##enum_[PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_RECORD_SIZE_BYTES(4U)]; +typedef struct +{ + uint8_t base__[PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_OFFSET_MAP_BASE_SIZE]; +#define PAR_ITEM_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_1) +#define PAR_ITEM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_2) +#define PAR_ITEM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_4) +#define PAR_ITEM_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_1) +#define PAR_ITEM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_2) +#define PAR_ITEM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_4) +#define PAR_ITEM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_4) +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +} par_nvm_layout_fixed_payload_only_offset_map_t; +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_OFFSET_OF(enum_) ((uint32_t)offsetof(par_nvm_layout_fixed_payload_only_offset_map_t, slot_##enum_) - PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_OFFSET_MAP_BASE_SIZE) +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_I(enum_, pers_) +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_I(enum_, pers_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_##pers_(enum_) +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_1(enum_) [PAR_PERSIST_IDX_##enum_] = PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_OFFSET_OF(enum_), +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_0(enum_) +static const uint32_t g_par_nvm_layout_fixed_payload_only_addr_lut[PAR_PERSIST_SLOT_MAP_CAPACITY] = { +#define PAR_ITEM_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) +#define PAR_ITEM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) +#define PAR_ITEM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) +#define PAR_ITEM_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) +#define PAR_ITEM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) +#define PAR_ITEM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) +#define PAR_ITEM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +}; +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_I +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_1 +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_0 +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_OFFSET_OF +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_1 +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_2 +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_4 +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT_I +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT_1 +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT_0 +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_RECORD_SIZE_BYTES + /** * @brief Translate one persistent slot index into its serialized record address. * @@ -66,16 +141,11 @@ static uint32_t par_nvm_layout_fixed_payload_only_addr_from_persist_idx(const ui const uint16_t persist_idx, const par_num_t * const p_persist_slot_to_par_num) { - uint32_t addr = first_data_obj_addr; - PAR_ASSERT(NULL != p_persist_slot_to_par_num); + PAR_ASSERT(persist_idx < PAR_PERSISTENT_COMPILE_COUNT); + (void)p_persist_slot_to_par_num; - for (uint16_t it = 0U; it < persist_idx; it++) - { - addr += par_nvm_layout_fixed_payload_only_record_size_from_par_num(p_persist_slot_to_par_num[it]); - } - - return addr; + return (first_data_obj_addr + g_par_nvm_layout_fixed_payload_only_addr_lut[persist_idx]); } /** @@ -221,9 +291,7 @@ static par_nvm_compat_result_t par_nvm_layout_fixed_payload_only_check_compat(co return ePAR_NVM_COMPAT_REBUILD; } - return (p_head_obj->obj_nb == (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) ? - ePAR_NVM_COMPAT_EXACT_MATCH : - ePAR_NVM_COMPAT_PREFIX_APPEND; + return (p_head_obj->obj_nb == (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) ? ePAR_NVM_COMPAT_EXACT_MATCH : ePAR_NVM_COMPAT_PREFIX_APPEND; } #if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) diff --git a/src/persist/par_nvm_layout_grouped_payload_only.c b/src/persist/par_nvm_layout_grouped_payload_only.c index 74152cb..098b0e2 100644 --- a/src/persist/par_nvm_layout_grouped_payload_only.c +++ b/src/persist/par_nvm_layout_grouped_payload_only.c @@ -2,7 +2,7 @@ * @file par_nvm_layout_grouped_payload_only.c * @brief Implement the grouped payload-only NVM layout. * @author wdfk-prog () - * @version 1.1 + * @version 1.2 * @date 2026-04-13 * * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. @@ -12,11 +12,13 @@ * Date Version Author Description * 2026-04-11 1.0 wdfk-prog first version * 2026-04-13 1.1 wdfk-prog add layout-ops adapter + * 2026-04-13 1.2 wdfk-prog auto-generate compile-time grouped-payload-only address LUT */ #include "persist/par_nvm_layout.h" #if (1 == PAR_CFG_NVM_EN) && (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_GROUPED_PAYLOAD_ONLY) +#include #include #include "persist/par_nvm_table_id.h" @@ -53,6 +55,109 @@ static uint32_t par_nvm_layout_grouped_payload_only_record_size_from_par_num(con return par_nvm_layout_grouped_payload_only_record_size_from_payload_size(par_nvm_layout_payload_size_from_par_num(par_num)); } +/** + * @brief Compile-time packed offset map for the grouped-payload-only layout. + * + * @details The generated struct keeps one anchor byte so the type remains valid + * even when no parameters are persistent. Persistent items are then emitted in + * three compile-time passes: 8-bit payloads first, then 16-bit payloads, then + * 32-bit payloads. `offsetof()` on the generated members therefore yields the + * exact grouped-layout address without runtime scans or handwritten offsets. + */ +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_OFFSET_MAP_BASE_SIZE 1U +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_RECORD_SIZE_BYTES(payload_size_) (PAR_NVM_LAYOUT_RECORD_OVERHEAD + (uint32_t)(payload_size_)) +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, emit_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_I(enum_, pers_, emit_) +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_I(enum_, pers_, emit_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_##pers_(enum_, emit_) +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_1(enum_, emit_) emit_(enum_) +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_0(enum_, emit_) +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_1(enum_) uint8_t slot_##enum_[PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_RECORD_SIZE_BYTES(1U)]; +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_2(enum_) uint8_t slot_##enum_[PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_RECORD_SIZE_BYTES(2U)]; +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_4(enum_) uint8_t slot_##enum_[PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_RECORD_SIZE_BYTES(4U)]; +typedef struct +{ + uint8_t base__[PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_OFFSET_MAP_BASE_SIZE]; +#define PAR_ITEM_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_1) +#define PAR_ITEM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#define PAR_ITEM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#define PAR_ITEM_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_1) +#define PAR_ITEM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#define PAR_ITEM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#define PAR_ITEM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +#define PAR_ITEM_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#define PAR_ITEM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_2) +#define PAR_ITEM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#define PAR_ITEM_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#define PAR_ITEM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_2) +#define PAR_ITEM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#define PAR_ITEM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +#define PAR_ITEM_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#define PAR_ITEM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#define PAR_ITEM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_4) +#define PAR_ITEM_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#define PAR_ITEM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#define PAR_ITEM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_4) +#define PAR_ITEM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_4) +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +} par_nvm_layout_grouped_payload_only_offset_map_t; +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_OFFSET_OF(enum_) ((uint32_t)offsetof(par_nvm_layout_grouped_payload_only_offset_map_t, slot_##enum_) - PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_OFFSET_MAP_BASE_SIZE) +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_I(enum_, pers_) +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_I(enum_, pers_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_##pers_(enum_) +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_1(enum_) [PAR_PERSIST_IDX_##enum_] = PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_OFFSET_OF(enum_), +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_0(enum_) +static const uint32_t g_par_nvm_layout_grouped_payload_only_addr_lut[PAR_PERSIST_SLOT_MAP_CAPACITY] = { +#define PAR_ITEM_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) +#define PAR_ITEM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) +#define PAR_ITEM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) +#define PAR_ITEM_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) +#define PAR_ITEM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) +#define PAR_ITEM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) +#define PAR_ITEM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +}; +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_I +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_1 +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_0 +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_OFFSET_OF +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_1 +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_2 +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_4 +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_I +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_1 +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_0 +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_RECORD_SIZE_BYTES + /** * @brief Translate one persistent slot index into its serialized record address. * @@ -65,59 +170,11 @@ static uint32_t par_nvm_layout_grouped_payload_only_addr_from_persist_idx(const const uint16_t persist_idx, const par_num_t * const p_persist_slot_to_par_num) { - uint32_t total_size_8 = 0U; - uint32_t total_size_16 = 0U; - uint32_t prefix_same_group = 0U; - uint8_t target_payload_size = 0U; - PAR_ASSERT(NULL != p_persist_slot_to_par_num); PAR_ASSERT(persist_idx < PAR_PERSISTENT_COMPILE_COUNT); - target_payload_size = par_nvm_layout_payload_size_from_par_num(p_persist_slot_to_par_num[persist_idx]); + (void)p_persist_slot_to_par_num; - for (uint16_t it = 0U; it < PAR_PERSISTENT_COMPILE_COUNT; it++) - { - const uint8_t payload_size = par_nvm_layout_payload_size_from_par_num(p_persist_slot_to_par_num[it]); - const uint32_t record_size = par_nvm_layout_grouped_payload_only_record_size_from_payload_size(payload_size); - - switch (payload_size) - { - case 1U: - total_size_8 += record_size; - break; - - case 2U: - total_size_16 += record_size; - break; - - case 4U: - break; - - default: - PAR_ASSERT(0); - break; - } - - if ((it < persist_idx) && (payload_size == target_payload_size)) - { - prefix_same_group += record_size; - } - } - - switch (target_payload_size) - { - case 1U: - return (first_data_obj_addr + prefix_same_group); - - case 2U: - return (first_data_obj_addr + total_size_8 + prefix_same_group); - - case 4U: - return (first_data_obj_addr + total_size_8 + total_size_16 + prefix_same_group); - - default: - PAR_ASSERT(0); - return first_data_obj_addr; - } + return (first_data_obj_addr + g_par_nvm_layout_grouped_payload_only_addr_lut[persist_idx]); } /** From a598050126d87f6536cc0265a4b68e2f42ca7a83 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Sun, 19 Apr 2026 15:46:52 +0800 Subject: [PATCH 35/36] feat[flash-ee]: add generic flash emulated EEPROM backend add a portable flash emulated EEPROM backend core for parameter persistence document flash-ee recovery, cache window, and integration constraints --- README.md | 98 +- .../backend/par_store_backend_flash_ee_fal.c | 276 +++ .../par_store_backend_flash_ee_native.c | 264 +++ docs/api-reference.md | 20 + docs/architecture.md | 7 +- docs/flash-ee-backend-design.md | 291 +++ docs/getting-started.md | 4 +- src/def/par_def.c | 7 +- src/def/par_def.h | 7 +- src/def/par_id_map_static.c | 8 +- src/def/par_id_map_static.h | 6 +- src/detail/par_bitwise_impl.inc | 7 +- src/detail/par_storage_init.inc | 7 +- src/detail/par_typed_impl.inc | 7 +- src/layout/par_layout.c | 7 +- src/layout/par_layout.h | 7 +- src/par.c | 3 +- src/par.h | 2 +- src/par_cfg.h | 126 +- src/persist/backend/par_store_backend.h | 27 +- .../backend/par_store_backend_flash_ee.c | 1940 +++++++++++++++++ .../backend/par_store_backend_flash_ee.h | 297 +++ .../backend/par_store_backend_flash_ee_cfg.h | 72 + .../backend/par_store_backend_gel_nvm.c | 79 +- src/persist/fnv.h | 12 +- src/persist/hash_32a.c | 16 +- src/persist/par_nvm.c | 57 +- src/persist/par_nvm.h | 12 +- src/persist/par_nvm_cfg.h | 126 ++ src/persist/par_nvm_layout.h | 10 +- src/persist/par_nvm_layout_compact_payload.c | 10 +- .../par_nvm_layout_fixed_payload_only.c | 16 +- .../par_nvm_layout_fixed_slot_no_size.c | 12 +- .../par_nvm_layout_fixed_slot_with_size.c | 8 +- .../par_nvm_layout_grouped_payload_only.c | 14 +- src/persist/par_nvm_table_id.c | 9 +- src/persist/par_nvm_table_id.h | 6 +- src/port/par_atomic.h | 7 +- src/port/par_if.c | 3 +- src/port/par_if.h | 3 +- 40 files changed, 3592 insertions(+), 298 deletions(-) create mode 100644 Third_Party/autogen_parameter_manager/backend/par_store_backend_flash_ee_fal.c create mode 100644 Third_Party/autogen_parameter_manager/backend/par_store_backend_flash_ee_native.c create mode 100644 docs/flash-ee-backend-design.md create mode 100644 src/persist/backend/par_store_backend_flash_ee.c create mode 100644 src/persist/backend/par_store_backend_flash_ee.h create mode 100644 src/persist/backend/par_store_backend_flash_ee_cfg.h create mode 100644 src/persist/par_nvm_cfg.h diff --git a/README.md b/README.md index fa2f413..fbf32c3 100644 --- a/README.md +++ b/README.md @@ -62,48 +62,58 @@ static void app_init(void) - [Getting started](docs/getting-started.md) for integration steps, required files, and first-use examples - [Architecture](docs/architecture.md) for storage model, validation flow, ID lookup, and layout design - [API reference](docs/api-reference.md) for the public API grouped by responsibility +- [Flash-ee backend design](docs/flash-ee-backend-design.md) for the portable flash-emulated EEPROM core and adapter model ## Package layout ```text -parameters/ -├── README.md -├── CHANGE_LOG.md -├── docs/ -│ ├── DeviceParameter_VerificationReport.xlsx -│ ├── api-reference.md -│ ├── architecture.md -│ └── getting-started.md -├── src/ -│ ├── par.c -│ ├── par.h -│ ├── par_cfg.h -│ ├── def/ -│ │ ├── par_def.c -│ │ ├── par_def.h -│ │ ├── par_id_map_static.c -│ │ └── par_id_map_static.h -│ ├── detail/ -│ │ ├── par_bitwise_impl.inc -│ │ ├── par_storage_init.inc -│ │ └── par_typed_impl.inc -│ ├── layout/ -│ │ ├── par_layout.c -│ │ └── par_layout.h -│ ├── persist/ -│ │ ├── backend/ -│ │ │ ├── par_store_backend.h -│ │ │ └── par_store_backend_gel_nvm.c -│ │ ├── par_nvm.c -│ │ └── par_nvm.h -│ └── port/ -│ ├── par_atomic.h -│ ├── par_if.c -│ └── par_if.h -└── template/ - ├── par_cfg_port.htmp - ├── par_layout_static.htmp - └── par_table.deftmp +. +├── Kconfig +├── SConscript +├── backend/ +│ ├── par_store_backend_flash_ee_fal.c +│ ├── par_store_backend_flash_ee_native.c +│ └── par_store_backend_rtt_at24cxx.c +├── par_table.def +├── parameters/ +│ ├── README.md +│ ├── CHANGE_LOG.md +│ ├── docs/ +│ │ ├── DeviceParameter_VerificationReport.xlsx +│ │ ├── api-reference.md +│ │ ├── architecture.md +│ │ ├── flash-ee-backend-design.md +│ │ └── getting-started.md +│ ├── src/ +│ │ ├── par.c +│ │ ├── par.h +│ │ ├── par_cfg.h +│ │ ├── def/ +│ │ ├── detail/ +│ │ ├── layout/ +│ │ ├── persist/ +│ │ │ ├── backend/ +│ │ │ │ ├── par_store_backend.h +│ │ │ │ ├── par_store_backend_flash_ee.c +│ │ │ │ ├── par_store_backend_flash_ee.h +│ │ │ │ ├── par_store_backend_flash_ee_cfg.h +│ │ │ │ └── par_store_backend_gel_nvm.c +│ │ │ ├── fnv.h +│ │ │ ├── hash_32a.c +│ │ │ ├── par_nvm.c +│ │ │ ├── par_nvm.h +│ │ │ ├── par_nvm_cfg.h +│ │ │ ├── par_nvm_layout*.c +│ │ │ └── par_nvm_table_id.* +│ │ └── port/ +│ └── template/ +│ ├── par_cfg_port.htmp +│ ├── par_layout_static.htmp +│ └── par_table.deftmp +└── port/ + ├── par_atomic_port.h + ├── par_cfg_port.h + └── par_if_port.c ``` ## Required integration files @@ -138,11 +148,16 @@ This repository contains the reusable module core and templates. A real integrat - The module separates **internal parameter enumeration** (`par_num_t`) from **external parameter IDs** (`id`). - The current ID lookup implementation uses a one-entry-per-bucket hash map generated at compile time from `par_table.def`. External IDs must therefore be not only unique, but also collision-free under the configured hash geometry. Optional runtime diagnostic scans can be enabled with `PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK` and `PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK` when additional startup logs are useful. See `docs/architecture.md` for the collision rule and avoidance guidance. - Unchecked setter APIs skip runtime validation callbacks and on-change callbacks, so they should be reserved for tightly controlled hot paths. Bitwise fast setters are further restricted to `U8` / `U16` / `U32` flags or bitmask parameters. Legacy `*_fast()` names remain as deprecated aliases. -- NVM support is optional. When enabled, `src/persist/par_nvm.c` depends on a mounted storage backend interface. Whether runtime ID support must also be enabled depends on the selected persisted record layout and backend constraints: the payload-only layouts may be used with `PAR_CFG_ENABLE_ID = 0` only when `PAR_CFG_TABLE_ID_CHECK_EN = 1`, while the stored-ID layouts and the current flash backend path require `PAR_CFG_ENABLE_ID = 1`. Persistence metadata is compiled in automatically under `PAR_CFG_NVM_EN`. The package builds one packaged backend adapter selected from Kconfig. The RT-Thread AT24CXX backend is available today, and the generic flash backend entry is reserved as a placeholder for later implementation. +- NVM support is optional. When enabled, `src/persist/par_nvm.c` depends on a mounted storage backend interface. Whether runtime ID support must also be enabled depends on the selected persisted record layout and backend constraints: the payload-only layouts may be used with `PAR_CFG_ENABLE_ID = 0` only when `PAR_CFG_TABLE_ID_CHECK_EN = 1`, while the stored-ID layouts and the current flash backend path require `PAR_CFG_ENABLE_ID = 1`. Persistence metadata is compiled in automatically under `PAR_CFG_NVM_EN`. The package builds one packaged backend adapter selected from Kconfig. The available packaged paths now include the RT-Thread AT24CXX adapter and the portable flash-ee backend core, which can be bound either to FAL through the repository-root `backend/par_store_backend_flash_ee_fal.c` bridge or to product-specific native flash hooks through the repository-root `backend/par_store_backend_flash_ee_native.c` adapter. - Live RAM layout and persisted NVM layout are intentionally different. RAM storage is grouped by value width, while the persistence area stores a compile-time ordered slot list using one selected serialized record layout: fixed 4-byte payload slot with size descriptor, fixed 4-byte payload slot without size descriptor, compact natural-width payload with size descriptor, fixed natural-width payload without stored ID, or grouped natural-width payload without stored ID. - Compile-time persistent order is the primary slot layout contract of the managed NVM image. The stored-ID layouts keep `id` in each record as an integrity and diagnostics field, while the payload-only layouts omit stored `id` and instead rely on compile-time slot order plus table-ID validation. The fixed-slot layouts keep one 4-byte payload slot per persistent parameter, while the compact and payload-only layouts store only the natural 1/2/4-byte payload width. - The serialized NVM header is written explicitly as a fixed 12-byte storage image (`sign(4) + obj_nb(2) + table_id(4) + crc16(2)`), so on-storage layout does not depend on compiler struct padding. Header CRC-16 covers the serialized `obj_nb + table_id` bytes, while each data record carries its own CRC-8 according to the selected record layout. - `par_nvm.c` now binds one selected persisted-record layout adapter during initialization. Record address calculation, object preparation, stored-object validation, compatibility policy, and optional write-readback comparison are all delegated through that layout ops table, so the common NVM flow no longer carries layout-specific branching. When `PAR_CFG_NVM_WRITE_VERIFY_EN = 1`, parameter writes and header commits force a backend sync and then perform layout-aware readback verification before the write is considered committed. +- `par_nvm_write(par_num, false)` requests a normal parameter save without an additional caller-forced sync step. It does **not** guarantee RAM-only staging. Backend contracts may still persist data before the call returns, and a successful return means the backend-side persistence work required for that request is complete. +- The flash-emulated EEPROM backend may synchronize one dirty cache window before loading the next window during one logical write or erase. Successful multi-window requests therefore finish fully persisted, but failures are still non-transactional and may leave earlier windows committed while later windows remain old. +- For flash-emulated EEPROM integrations, size `PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE` so common parameter objects fit inside one cache window whenever practical. Also keep one must-stay-consistent parameter group within one independently committed window, or add an application-level consistency/version marker if that is not possible. +- Flash-ee build-time configuration now rejects invalid pure-configuration geometry combinations such as logical-size/cache-size values that do not divide by the configured line size or program-size values that do not divide the 64-byte bank header exactly. +- Native flash-ee integrations should provide strong definitions for the required public native hook ABI declared in `parameters/src/persist/backend/par_store_backend_flash_ee.h`; missing required operational or geometry hooks are intended to fail the final link instead of degrading into runtime stub behavior, while benign helper hooks may still use package defaults. - If the selected storage medium has ECC or other read-health reporting, treat that as a product-level policy input rather than as an automatic parameter-core decision. The business layer should decide whether an ECC event only needs reporting, should trigger parameter reset or rebuild, or should escalate to a wider system fault response. - CRC calculation is routed through port hooks with bundled software defaults. In this single-target profile the persisted image and the table-ID digest both use the native byte order of the running platform, so no additional byte-order conversion hook is required by the persistence path. - When `PAR_CFG_TABLE_ID_CHECK_EN = 1`, startup compares the stored table-ID against the live compatibility digest for the stored persistent prefix size from the header (`obj_nb`). Layouts with stored IDs hash external parameter IDs, so prefix ID renumbering still invalidates the image there. `FIXED_PAYLOAD_ONLY` intentionally excludes external parameter IDs and validates only prefix byte-layout compatibility (`obj_nb`, persistent order, and parameter type), which allows pure external-ID renumbering and compatible tail growth without a rebuild there. `GROUPED_PAYLOAD_ONLY` also excludes external parameter IDs, but because regrouped addresses depend on the full live persistent set it rebuilds whenever `stored_count != live_count`. Semantic-only prefix remaps that preserve the same byte layout must still be paired with an explicit `PAR_CFG_TABLE_ID_SCHEMA_VER` bump. Defaults, ranges, names, units, descriptions, and access flags remain outside the digest. @@ -158,3 +173,8 @@ This repository contains the reusable module core and templates. A real integrat - [CLI module](https://github.com/GeneralEmbeddedCLibraries/cli) - [NVM module](https://github.com/GeneralEmbeddedCLibraries/nvm) - [General Embedded C Library Manual](https://github.com/GeneralEmbeddedCLibraries/documentation/blob/develop/General_Embedded_C_Library_Manual.pdf) + + +## Flash-emulated EEPROM backend + +See `parameters/docs/flash-ee-backend-design.md` for the portable core, configuration header, commit/recovery model, cross-window write semantics, and FAL/native integration guidance. diff --git a/Third_Party/autogen_parameter_manager/backend/par_store_backend_flash_ee_fal.c b/Third_Party/autogen_parameter_manager/backend/par_store_backend_flash_ee_fal.c new file mode 100644 index 0000000..1511a37 --- /dev/null +++ b/Third_Party/autogen_parameter_manager/backend/par_store_backend_flash_ee_fal.c @@ -0,0 +1,276 @@ +/** + * @file par_store_backend_flash_ee_fal.c + * @brief Bind the generic flash-emulated EEPROM core to a FAL partition. + * @author wdfk-prog + * @version 1.0 + * @date 2026-04-14 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-14 1.0 wdfk-prog first version + */ +#include "../parameters/src/par_cfg.h" +#include "../parameters/src/persist/backend/par_store_backend_flash_ee.h" + +#if (1 == PAR_CFG_NVM_EN) && (1 == PAR_CFG_NVM_BACKEND_FLASH_EE_EN) && (1 == PAR_CFG_NVM_BACKEND_FLASH_EE_PORT_FAL_EN) + +#include + +#include + +/** + * @brief Hold the runtime context for the FAL flash-ee adapter. + */ +typedef struct +{ + const struct fal_partition *p_part; /**< Bound FAL partition handle. */ + const struct fal_flash_dev *p_flash; /**< Underlying flash device for the bound partition. */ + const char *partition_name; /**< Configured FAL partition name. */ + bool is_init; /**< True after the adapter resolves the partition. */ +} par_store_flash_ee_fal_ctx_t; + +/** + * @brief Store the singleton FAL adapter context. + */ +static par_store_flash_ee_fal_ctx_t g_par_store_flash_ee_fal_ctx = { + .p_part = NULL, + .p_flash = NULL, + .partition_name = PAR_CFG_NVM_BACKEND_FLASH_EE_FAL_PARTITION_NAME, + .is_init = false, +}; + +/** + * @brief Initialize the FAL-backed flash port. + * + * @param[in,out] p_ctx Mutable FAL port context. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_fal_init(void *p_ctx) +{ + par_store_flash_ee_fal_ctx_t *p_fal_ctx = (par_store_flash_ee_fal_ctx_t *)p_ctx; + + if (NULL == p_fal_ctx) + { + return ePAR_ERROR_PARAM; + } + + if (true == p_fal_ctx->is_init) + { + return ePAR_OK; + } + + p_fal_ctx->p_part = fal_partition_find(p_fal_ctx->partition_name); + if (NULL == p_fal_ctx->p_part) + { + PAR_ERR_PRINT("PAR_FLASH_EE_FAL: partition not found, name=%s", p_fal_ctx->partition_name); + return ePAR_ERROR_INIT; + } + + p_fal_ctx->p_flash = fal_flash_device_find(p_fal_ctx->p_part->flash_name); + if (NULL == p_fal_ctx->p_flash) + { + PAR_ERR_PRINT("PAR_FLASH_EE_FAL: flash device not found, partition=%s flash=%s", + p_fal_ctx->partition_name, + p_fal_ctx->p_part->flash_name); + p_fal_ctx->p_part = NULL; + return ePAR_ERROR_INIT; + } + + p_fal_ctx->is_init = true; + return ePAR_OK; +} + +/** + * @brief Deinitialize the FAL-backed flash port. + * + * @param[in,out] p_ctx Mutable FAL port context. + * @return ePAR_OK on success. + */ +static par_status_t par_store_flash_ee_fal_deinit(void *p_ctx) +{ + par_store_flash_ee_fal_ctx_t *p_fal_ctx = (par_store_flash_ee_fal_ctx_t *)p_ctx; + + if (NULL == p_fal_ctx) + { + return ePAR_ERROR_PARAM; + } + + p_fal_ctx->p_part = NULL; + p_fal_ctx->p_flash = NULL; + p_fal_ctx->is_init = false; + return ePAR_OK; +} + +/** + * @brief Report whether the FAL-backed flash port is initialized. + * + * @param[in] p_ctx Immutable FAL port context. + * @param[out] p_is_init Receives the initialization state. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_fal_is_init(const void *p_ctx, bool *p_is_init) +{ + const par_store_flash_ee_fal_ctx_t *p_fal_ctx = (const par_store_flash_ee_fal_ctx_t *)p_ctx; + + if ((NULL == p_fal_ctx) || (NULL == p_is_init)) + { + return ePAR_ERROR_PARAM; + } + + *p_is_init = p_fal_ctx->is_init; + return ePAR_OK; +} + +/** + * @brief Read raw bytes from the FAL partition. + * + * @param[in] p_ctx Immutable FAL port context. + * @param[in] addr Byte offset inside the bound partition. + * @param[in] size Number of bytes to read. + * @param[out] p_buf Destination buffer. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_fal_read(const void *p_ctx, uint32_t addr, uint32_t size, uint8_t *p_buf) +{ + const par_store_flash_ee_fal_ctx_t *p_fal_ctx = (const par_store_flash_ee_fal_ctx_t *)p_ctx; + + if ((NULL == p_fal_ctx) || (NULL == p_fal_ctx->p_part) || (NULL == p_buf)) + { + return ePAR_ERROR_PARAM; + } + + return ((int)size == fal_partition_read(p_fal_ctx->p_part, (long)addr, p_buf, (size_t)size)) ? ePAR_OK : ePAR_ERROR_NVM; +} + +/** + * @brief Program raw bytes into the FAL partition. + * + * @param[in] p_ctx Immutable FAL port context. + * @param[in] addr Byte offset inside the bound partition. + * @param[in] size Number of bytes to program. + * @param[in] p_buf Source buffer. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_fal_program(const void *p_ctx, uint32_t addr, uint32_t size, const uint8_t *p_buf) +{ + const par_store_flash_ee_fal_ctx_t *p_fal_ctx = (const par_store_flash_ee_fal_ctx_t *)p_ctx; + + if ((NULL == p_fal_ctx) || (NULL == p_fal_ctx->p_part) || (NULL == p_buf)) + { + return ePAR_ERROR_PARAM; + } + + return ((int)size == fal_partition_write(p_fal_ctx->p_part, (long)addr, p_buf, (size_t)size)) ? ePAR_OK : ePAR_ERROR_NVM; +} + +/** + * @brief Erase bytes inside the FAL partition. + * + * @param[in] p_ctx Immutable FAL port context. + * @param[in] addr Byte offset inside the bound partition. + * @param[in] size Number of bytes to erase. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_fal_erase(const void *p_ctx, uint32_t addr, uint32_t size) +{ + const par_store_flash_ee_fal_ctx_t *p_fal_ctx = (const par_store_flash_ee_fal_ctx_t *)p_ctx; + + if ((NULL == p_fal_ctx) || (NULL == p_fal_ctx->p_part)) + { + return ePAR_ERROR_PARAM; + } + + return ((int)size == fal_partition_erase(p_fal_ctx->p_part, (long)addr, (size_t)size)) ? ePAR_OK : ePAR_ERROR_NVM; +} + +/** + * @brief Return the total FAL partition size. + * + * @param[in] p_ctx Immutable FAL port context. + * @return Partition size in bytes. + */ +static uint32_t par_store_flash_ee_fal_get_region_size(const void *p_ctx) +{ + const par_store_flash_ee_fal_ctx_t *p_fal_ctx = (const par_store_flash_ee_fal_ctx_t *)p_ctx; + return ((NULL != p_fal_ctx) && (NULL != p_fal_ctx->p_part)) ? (uint32_t)p_fal_ctx->p_part->len : 0u; +} + +/** + * @brief Return the FAL partition erase size. + * + * @param[in] p_ctx Immutable FAL port context. + * @return Erase size in bytes. + */ +static uint32_t par_store_flash_ee_fal_get_erase_size(const void *p_ctx) +{ + const par_store_flash_ee_fal_ctx_t *p_fal_ctx = (const par_store_flash_ee_fal_ctx_t *)p_ctx; + return ((NULL != p_fal_ctx) && (NULL != p_fal_ctx->p_flash)) ? (uint32_t)p_fal_ctx->p_flash->blk_size : 0u; +} + +/** + * @brief Return the FAL program size. + * + * @param[in] p_ctx Immutable FAL port context. + * @return Program size in bytes. + * + * @note FAL does not expose one universal program granularity field. The + * backend therefore uses the package configuration value. + */ +static uint32_t par_store_flash_ee_fal_get_program_size(const void *p_ctx) +{ + (void)p_ctx; + return PAR_CFG_NVM_BACKEND_FLASH_EE_PROGRAM_SIZE; +} + +/** + * @brief Return the port name used by the FAL adapter. + * + * @param[in] p_ctx Immutable FAL port context. + * @return Constant name string. + */ +static const char *par_store_flash_ee_fal_get_name(const void *p_ctx) +{ + const par_store_flash_ee_fal_ctx_t *p_fal_ctx = (const par_store_flash_ee_fal_ctx_t *)p_ctx; + return ((NULL != p_fal_ctx) && (NULL != p_fal_ctx->partition_name)) ? p_fal_ctx->partition_name : "fal"; +} + +/** + * @brief Expose the FAL-backed flash port operations to the generic core. + */ +static const par_store_flash_ee_port_api_t g_par_store_flash_ee_fal_port = { + .init = par_store_flash_ee_fal_init, + .deinit = par_store_flash_ee_fal_deinit, + .is_init = par_store_flash_ee_fal_is_init, + .read = par_store_flash_ee_fal_read, + .program = par_store_flash_ee_fal_program, + .erase = par_store_flash_ee_fal_erase, + .get_region_size = par_store_flash_ee_fal_get_region_size, + .get_erase_size = par_store_flash_ee_fal_get_erase_size, + .get_program_size = par_store_flash_ee_fal_get_program_size, + .get_name = par_store_flash_ee_fal_get_name, +}; + +/** + * @brief Bind the FAL adapter to the generic flash-ee backend core. + * + * @return ePAR_OK on success, otherwise an error code from the generic core. + */ +par_status_t par_store_backend_bind(void) +{ + return par_store_backend_flash_ee_bind_port(&g_par_store_flash_ee_fal_port, &g_par_store_flash_ee_fal_ctx); +} + +/** + * @brief Return the generic flash-ee backend API after adapter binding. + * + * @return Generic flash-ee backend API table. + */ +const par_store_backend_api_t * par_store_backend_get_api(void) +{ + return par_store_backend_flash_ee_get_api(); +} + +#endif /* flash-ee + fal */ diff --git a/Third_Party/autogen_parameter_manager/backend/par_store_backend_flash_ee_native.c b/Third_Party/autogen_parameter_manager/backend/par_store_backend_flash_ee_native.c new file mode 100644 index 0000000..90ba141 --- /dev/null +++ b/Third_Party/autogen_parameter_manager/backend/par_store_backend_flash_ee_native.c @@ -0,0 +1,264 @@ +/** + * @file par_store_backend_flash_ee_native.c + * @brief Bind the generic flash-emulated EEPROM core to native flash hooks. + * @author wdfk-prog + * @version 1.0 + * @date 2026-04-14 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-14 1.0 wdfk-prog first version + */ +#include "../parameters/src/par_cfg.h" +#include "../parameters/src/persist/backend/par_store_backend_flash_ee.h" + +#if (1 == PAR_CFG_NVM_EN) && (1 == PAR_CFG_NVM_BACKEND_FLASH_EE_EN) && (1 == PAR_CFG_NVM_BACKEND_FLASH_EE_PORT_NATIVE_EN) + +/** + * @brief Hold runtime geometry and state for the native flash-ee adapter. + */ +typedef struct +{ + bool is_init; /**< True after the native hooks finish initialization. */ + uint32_t region_size; /**< Total persistence-region size reported by the hooks. */ + uint32_t erase_size; /**< Physical erase granularity reported by the hooks. */ + uint32_t program_size; /**< Physical program granularity reported by the hooks. */ + const char *name; /**< Diagnostic port name reported by the hooks. */ +} par_store_flash_ee_native_ctx_t; + +/** + * @brief Weak default native-port deinitializer. + * + * @return ePAR_OK because the default implementation owns no resources. + */ +PAR_PORT_WEAK par_status_t par_store_flash_ee_native_port_deinit(void) +{ + return ePAR_OK; +} + +/** + * @brief Weak default native-port name hook. + * + * @return Default diagnostic name for the native adapter. + */ +PAR_PORT_WEAK const char *par_store_flash_ee_native_port_name(void) +{ + return "native"; +} + +/** + * @brief Store the singleton native-adapter context. + */ +static par_store_flash_ee_native_ctx_t g_par_store_flash_ee_native_ctx = { + .is_init = false, + .region_size = 0u, + .erase_size = 0u, + .program_size = 0u, + .name = "native", +}; + +/** + * @brief Initialize the native-flash port. + * + * @param[in,out] p_ctx Mutable native-port context. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_native_init(void *p_ctx) +{ + par_store_flash_ee_native_ctx_t *p_native_ctx = (par_store_flash_ee_native_ctx_t *)p_ctx; + par_status_t status = ePAR_OK; + + if (NULL == p_native_ctx) + { + return ePAR_ERROR_PARAM; + } + + if (true == p_native_ctx->is_init) + { + return ePAR_OK; + } + + status = par_store_flash_ee_native_port_init(); + if (ePAR_OK == status) + { + p_native_ctx->region_size = par_store_flash_ee_native_port_region_size(); + p_native_ctx->erase_size = par_store_flash_ee_native_port_erase_size(); + p_native_ctx->program_size = par_store_flash_ee_native_port_program_size(); + p_native_ctx->name = par_store_flash_ee_native_port_name(); + p_native_ctx->is_init = true; + } + + return status; +} + +/** + * @brief Deinitialize the native-flash port. + * + * @param[in,out] p_ctx Mutable native-port context. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_native_deinit(void *p_ctx) +{ + par_store_flash_ee_native_ctx_t *p_native_ctx = (par_store_flash_ee_native_ctx_t *)p_ctx; + + if (NULL == p_native_ctx) + { + return ePAR_ERROR_PARAM; + } + + p_native_ctx->is_init = false; + return par_store_flash_ee_native_port_deinit(); +} + +/** + * @brief Report whether the native-flash port is initialized. + * + * @param[in] p_ctx Immutable native-port context. + * @param[out] p_is_init Receives the initialization state. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_native_is_init(const void *p_ctx, bool *p_is_init) +{ + const par_store_flash_ee_native_ctx_t *p_native_ctx = (const par_store_flash_ee_native_ctx_t *)p_ctx; + + if ((NULL == p_native_ctx) || (NULL == p_is_init)) + { + return ePAR_ERROR_PARAM; + } + + *p_is_init = p_native_ctx->is_init; + return ePAR_OK; +} + +/** + * @brief Read raw bytes through the native-flash hooks. + * + * @param[in] p_ctx Immutable native-port context. + * @param[in] addr Byte offset inside the bound region. + * @param[in] size Number of bytes to read. + * @param[out] p_buf Destination buffer. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_native_read(const void *p_ctx, uint32_t addr, uint32_t size, uint8_t *p_buf) +{ + (void)p_ctx; + return par_store_flash_ee_native_port_read(addr, size, p_buf); +} + +/** + * @brief Program raw bytes through the native-flash hooks. + * + * @param[in] p_ctx Immutable native-port context. + * @param[in] addr Byte offset inside the bound region. + * @param[in] size Number of bytes to program. + * @param[in] p_buf Source buffer. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_native_program(const void *p_ctx, uint32_t addr, uint32_t size, const uint8_t *p_buf) +{ + (void)p_ctx; + return par_store_flash_ee_native_port_program(addr, size, p_buf); +} + +/** + * @brief Erase bytes through the native-flash hooks. + * + * @param[in] p_ctx Immutable native-port context. + * @param[in] addr Byte offset inside the bound region. + * @param[in] size Number of bytes to erase. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_native_erase(const void *p_ctx, uint32_t addr, uint32_t size) +{ + (void)p_ctx; + return par_store_flash_ee_native_port_erase(addr, size); +} + +/** + * @brief Return the native-flash region size. + * + * @param[in] p_ctx Immutable native-port context. + * @return Region size in bytes. + */ +static uint32_t par_store_flash_ee_native_get_region_size(const void *p_ctx) +{ + const par_store_flash_ee_native_ctx_t *p_native_ctx = (const par_store_flash_ee_native_ctx_t *)p_ctx; + return (NULL != p_native_ctx) ? p_native_ctx->region_size : 0u; +} + +/** + * @brief Return the native-flash erase size. + * + * @param[in] p_ctx Immutable native-port context. + * @return Erase size in bytes. + */ +static uint32_t par_store_flash_ee_native_get_erase_size(const void *p_ctx) +{ + const par_store_flash_ee_native_ctx_t *p_native_ctx = (const par_store_flash_ee_native_ctx_t *)p_ctx; + return (NULL != p_native_ctx) ? p_native_ctx->erase_size : 0u; +} + +/** + * @brief Return the native-flash program size. + * + * @param[in] p_ctx Immutable native-port context. + * @return Program size in bytes. + */ +static uint32_t par_store_flash_ee_native_get_program_size(const void *p_ctx) +{ + const par_store_flash_ee_native_ctx_t *p_native_ctx = (const par_store_flash_ee_native_ctx_t *)p_ctx; + return (NULL != p_native_ctx) ? p_native_ctx->program_size : 0u; +} + +/** + * @brief Return the native-flash port name. + * + * @param[in] p_ctx Immutable native-port context. + * @return Constant name string. + */ +static const char *par_store_flash_ee_native_get_name(const void *p_ctx) +{ + const par_store_flash_ee_native_ctx_t *p_native_ctx = (const par_store_flash_ee_native_ctx_t *)p_ctx; + return ((NULL != p_native_ctx) && (NULL != p_native_ctx->name)) ? p_native_ctx->name : "native"; +} + +/** + * @brief Expose the native flash port operations to the generic core. + */ +static const par_store_flash_ee_port_api_t g_par_store_flash_ee_native_port = { + .init = par_store_flash_ee_native_init, + .deinit = par_store_flash_ee_native_deinit, + .is_init = par_store_flash_ee_native_is_init, + .read = par_store_flash_ee_native_read, + .program = par_store_flash_ee_native_program, + .erase = par_store_flash_ee_native_erase, + .get_region_size = par_store_flash_ee_native_get_region_size, + .get_erase_size = par_store_flash_ee_native_get_erase_size, + .get_program_size = par_store_flash_ee_native_get_program_size, + .get_name = par_store_flash_ee_native_get_name, +}; + +/** + * @brief Bind the native adapter to the generic flash-ee backend core. + * + * @return ePAR_OK on success, otherwise an error code from the generic core. + */ +par_status_t par_store_backend_bind(void) +{ + return par_store_backend_flash_ee_bind_port(&g_par_store_flash_ee_native_port, &g_par_store_flash_ee_native_ctx); +} + +/** + * @brief Return the generic flash-ee backend API after adapter binding. + * + * @return Generic flash-ee backend API table. + */ +const par_store_backend_api_t * par_store_backend_get_api(void) +{ + return par_store_backend_flash_ee_get_api(); +} + +#endif /* flash-ee + native */ diff --git a/docs/api-reference.md b/docs/api-reference.md index 96ff68b..7ae6893 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -190,6 +190,26 @@ The runtime NVM core binds one compile-time selected layout ops table during ini If the selected storage backend or storage medium exposes ECC status, the parameter core still does not hard-code the recovery policy. Whether an ECC event should only be reported, should trigger parameter reset/rebuild, or should escalate into a wider system fault response must be decided by the business layer. +### `par_nvm_write(..., nvm_sync)` semantics + +Treat `nvm_sync` as a request for an additional explicit backend sync step, not as a universal promise that `false` means RAM-only staging. + +- `nvm_sync = false` means the common layer does not request an extra sync after the write path. Backend-specific contracts may still persist data before the call returns. +- `nvm_sync = true` asks the common layer to issue an explicit backend sync after the write path, in addition to any backend-internal persistence already required by that backend contract. +- A successful `par_nvm_write()` return means the backend-side persistence work required for that request has completed. +- This API does not provide transactional atomicity across backend-specific internal chunking. + +For the flash-emulated EEPROM backend specifically: + +- one dirty cache window may be synchronized before the next cache window is staged during one logical write or erase; +- successful multi-window requests return only after the final dirty window is also durable; +- failed multi-window requests may still leave earlier windows committed while later windows remain old. + +Integration guidance: + +- size the flash-ee cache window so common parameter objects fit within one window whenever practical; +- avoid splitting one must-stay-consistent parameter group across multiple independently committed windows unless the application already has its own consistency/version marker. + | Function | Description | | --- | --- | | `par_set_n_save(par_num, p_val)` | Set one parameter and persist it immediately. | diff --git a/docs/architecture.md b/docs/architecture.md index 15be542..8ebfdb2 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -360,4 +360,9 @@ Implemented by the integrator as needed: - `par_if_port.c` (optional strong override for the weak defaults in `src/port/par_if.c`, including mutex and CRC helpers) - `par_atomic_port.h` -This separation makes the core reusable while still allowing the target platform to provide mutexes, logging, assertions, and atomic primitives. Packaged storage backend adapters stay inside the `src/persist/backend/` subtree because they are reusable module integrations rather than board-specific port code. +This separation makes the core reusable while still allowing the target platform to provide mutexes, logging, assertions, and atomic primitives. Packaged storage backend adapters stay inside the `src/persist/backend/` subtree because they are reusable module integrations rather than board-specific port code, while repository-root files under `backend/` bind those portable backends to one concrete platform environment such as RT-Thread FAL or a product-specific native flash port. + + +## Flash-emulated EEPROM backend + +See `./flash-ee-backend-design.md` for the portable core and adapter design. diff --git a/docs/flash-ee-backend-design.md b/docs/flash-ee-backend-design.md new file mode 100644 index 0000000..9489f74 --- /dev/null +++ b/docs/flash-ee-backend-design.md @@ -0,0 +1,291 @@ +# Flash-Emulated EEPROM Backend Design + +This document describes the portable flash-emulated EEPROM backend used by the parameters package. It focuses on architecture, integration contracts, runtime constraints, and the public backend-facing API. + +## 1. Scope and intent + +The backend exposes a byte-addressable NVM interface to `par_nvm.c`, while persisting data on erase-before-write flash media. The implementation keeps the reusable core free of RT-Thread, FAL, HAL, or vendor flash SDK dependencies. Platform-specific flash access is supplied through a thin port adapter. + +The design target is parameter persistence, not a general-purpose flash translation layer. It is optimized for deterministic layout, bounded RAM, append-only commits, and simple recovery. + +## 2. High-level architecture + +```mermaid +flowchart TD + A[par_nvm.c] --> B[flash_ee backend API] + B --> C[RAM cache window] + B --> D[append log in active bank] + D --> E[bank header] + B --> F[checkpoint / bank swap] + F --> G[inactive bank] + B --> H[flash port adapter] + H --> I[FAL partition or native hooks] +``` + +The flash region exported by the port is split into exactly two banks: + +- one **active bank** that accepts append records +- one **inactive bank** used as the checkpoint target during rollover + +Each bank contains: + +- one fixed header +- zero or more fixed-size append records + +## 3. On-flash model + +### 3.1 Bank header + +The bank header stores: + +- magic +- backend format version +- logical size +- line size +- record size +- bank size +- monotonic sequence number +- header CRC +- state word + +A bank is considered selectable only when the header is structurally valid and matches the live geometry. + +### 3.2 Append record + +Each append record stores, in on-flash write order: + +- one fixed-size payload block +- one metadata block containing logical line index, payload size, and one record CRC over the payload plus those semantic metadata fields +- optional erased padding used to align the final commit unit +- one dedicated commit unit written last + +The payload block always has `line_size` bytes. Logical data is therefore reconstructed line-by-line rather than byte-by-byte. A record is treated as visible only when its final commit unit matches the committed pattern exactly. + +## 4. RAM cache model + +The backend keeps one bounded RAM cache window rather than a full logical image. + +- `logical_size` is the logical EEPROM address space exported upward. +- `cache_size` is the RAM staging window size. +- `line_size` is the granularity of one cached and appended line. + +The current implementation supports **exactly one dirty cache window at a time**. + +### Practical consequence + +- `read()` may reconstruct data outside the currently loaded clean window. +- `write()` and `erase()` may span multiple cache windows. +- before staging moves to a new window, the backend synchronizes the current dirty window automatically. +- a successful multi-window write or erase returns only after the final dirty window is synchronized as well. + +This is still a bounded single-window staging design, not a transparent multi-window write-back cache. It improves durability-on-success, but it is still not a transactional multi-window commit engine. + +## 5. Initialization and bank selection + +At init time the backend: + +1. binds and initializes the physical flash port +2. validates geometry and compile-time configuration +3. scans both banks +4. chooses the newest valid active bank by sequence number +5. formats bank 0 when neither bank is valid + +### Accepted end-of-log conditions during scan + +A bank scan stops successfully only when it reaches one of these conditions: + +1. one fully erased record slot, meaning the append log ended normally +2. one or more programmed slots whose final commit unit was never fully completed + +Any slot whose commit unit still contains erased bytes is treated as an uncommitted append and ignored during reconstruction. Any slot with a closed but invalid commit unit, and any committed record with invalid metadata or record CRC, makes the bank unusable. The record CRC covers the payload, logical line index, and payload size so corrupted metadata cannot silently redirect a valid payload to the wrong logical line. This fail-closed rule avoids silently accepting corrupted committed history while still tolerating interrupted appends. + +The append-record CRC semantics in backend format version `1` are part of the persisted-image contract, so banks written with incompatible record-integrity rules must still be rejected by the header version check and reformatted rather than being misinterpreted under a different integrity rule. + +## 6. Commit and checkpoint flow + +### 6.1 Normal sync + +`sync()` converts each dirty cache line into one append record in the active bank. + +For each appended line, the write order is: + +1. program the payload bytes +2. program the metadata block and any erased alignment padding +3. program the dedicated commit unit last + +There is no in-place record repair path. If power is lost before the final commit write completes, the partially written slot is treated as an uncommitted append and skipped during the next scan. Later sync operations append into subsequent free slots; cleanup happens only when checkpoint or bank rollover compacts the live image. + +### 6.2 Rollover / checkpoint + +When the active bank does not have enough space for the required append records, the backend: + +1. erases and prepares the inactive bank +2. reconstructs the live logical lines +3. appends the compacted image into the inactive bank +4. marks the new bank active by header-state transition +5. switches bank roles in RAM + +## 7. Recovery guarantees + +### 7.1 Request-completion semantics + +For this backend, `par_nvm_write(..., false)` must be read as "do not request an additional common-layer sync step" rather than "guarantee RAM-only staging until a later sync call". + +- the backend may synchronize an earlier dirty window before loading the next cache window; +- a successful write or erase returns only after the final dirty window from that request is durable in flash; +- a failed multi-window request is still non-transactional and may leave earlier windows committed while later windows remain old. + +### 7.2 Recovery guarantees + +The design intentionally prefers conservative recovery: + +- invalid headers reject the bank +- any programmed slot whose commit unit still contains erased bytes is ignored as uncommitted +- invalid committed records reject the bank +- uncommitted slots are skipped rather than repaired in place +- header state transition is used to make bank activation explicit + +This keeps recovery deterministic and avoids accepting ambiguous partially corrupted histories. + +## 8. Public flash-ee API + +## 8.1 Port binding + +### `par_store_backend_flash_ee_bind_port()` + +Binds one physical flash port implementation to the reusable core. + +The port implementation must provide: + +- `init` +- `deinit` +- `is_init` +- `read` +- `program` +- `erase` +- `get_region_size` +- `get_erase_size` +- `get_program_size` +- optional `get_name` + +## 8.2 Backend export + +### `par_store_backend_flash_ee_get_api()` + +Returns the backend API table consumed by the persistence layer. + +## 8.3 Diagnostics + +### `par_store_backend_flash_ee_get_diag()` + +Returns the latest backend-specific diagnostic code. + +### `par_store_backend_flash_ee_get_diag_str()` + +Returns a readable string for the diagnostic code. + +## 8.4 Diagnostic intent + +Important diagnostic classes include: + +- port binding / init / geometry errors +- header incompatibility errors +- invalid commit marker / range / CRC errors +- `record_tail` when one or more uncommitted append slots were skipped +- cache window contract violations +- capacity and checkpoint failures + +## 8.5 Record visibility contract + +The backend exposes a simple recovery contract to upper layers: + +- a record is visible only after its final commit unit is fully programmed +- payload and metadata written without the final commit unit are ignored after reboot +- the backend does not attempt in-place repair of an uncommitted append slot +- an uncommitted slot consumes physical space until the next checkpoint or bank rollover + +## 9. Integration constraints + +The following constraints are part of the backend contract. + +### 9.1 Flash region constraints + +The bound flash region must provide enough capacity for: + +- two banks +- one header per bank +- one fully compacted logical image in each bank +- at least one additional append-record slot in each bank after compaction + +The bank size must be erase-aligned. + +### 9.2 Program-size constraints + +The header size and record size must both be integer multiples of the physical program size. The record layout reserves one dedicated final commit unit whose size equals the physical program granularity, and the metadata area is padded so that commit-unit write can be issued as the final standalone program operation. + +### 9.3 Cache-window constraints + +The current implementation is a **single-dirty-window** design. + +Therefore: + +- upper layers must not assume arbitrary multi-window write coalescing +- a write or erase that crosses a cache-window boundary will trigger a sync before staging continues in the next window +- `cache_size` no longer has to cover the largest parameter payload, but it still shapes commit frequency and protection granularity + +If that trade-off is unacceptable for the target workload, this backend is the wrong persistence strategy for that integration. + +### 9.4 Parameter-layout guidance + +For parameter persistence workloads, the safest integration pattern is to reduce how often one logical save request must cross cache-window boundaries. + +Recommended practice: + +- choose `cache_size` so the common parameter object sizes for the product fit within one cache window whenever practical; +- avoid placing one must-stay-consistent parameter group across multiple independently committed windows; +- when one logically coupled group cannot fit in one window, add an application-level generation/version field or equivalent consistency marker so business logic can detect mixed-old/new state after a failed write. + +These recommendations are usually sufficient for ordinary configuration parameters where "latest save may be lost" is acceptable, but they are not a substitute for a true transactional storage format. + +### 9.5 Workload fit + +This backend fits: + +- parameter persistence with bounded update sets +- deterministic configuration blobs +- infrequent sync-driven commits + +This backend is not intended as: + +- a transparent multi-window random-write cache +- a general-purpose flash filesystem +- a high-frequency log store with large dirty working sets + +## 10. Port adapters in this package + +Two adapters are currently provided. + +### 10.1 FAL adapter + +`backend/par_store_backend_flash_ee_fal.c` + +Binds the core to one FAL partition. + +### 10.2 Native adapter + +`backend/par_store_backend_flash_ee_native.c` + +Binds the core to user-supplied native flash hooks without pulling in RT-Thread-specific headers. +The required hook ABI is declared in `parameters/src/persist/backend/par_store_backend_flash_ee.h`, and native-port integrations are expected to provide strong definitions for the required operational and geometry hooks at link time. + +## 11. Recommended configuration guidance + +Choose values so that: + +- `logical_size` matches the parameter NVM address space expected by `par_nvm.c` +- `line_size` balances append overhead against write amplification +- `cache_size` is large enough that common parameter objects usually fit inside one window +- any parameter set that must stay mutually consistent is either kept within one independently committed window or protected by an application-level consistency marker +- `program_size` matches the minimum flash program granularity of the concrete port + +For deployments that depend on full-image rewrite paths such as package-level store-all operations, choose `cache_size` based on the acceptable sync cadence and lock-hold budget rather than forcing `cache_size >= logical_size`. diff --git a/docs/getting-started.md b/docs/getting-started.md index 75f0dfe..72ed5a4 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -144,7 +144,7 @@ ID-based lookup is generated statically when `PAR_CFG_ENABLE_ID = 1`. Optional s - `PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK` - `PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK` -When NVM is enabled, the parameters module requires a concrete packaged storage backend implementation. `src/persist/par_nvm.c` resolves and validates the backend API once during initialization, then uses the mounted callbacks directly for later reads, writes, erases, and sync operations. The RT-Thread AT24CXX backend is available today, while the generic flash backend Kconfig entry is currently only a placeholder and does not build an implementation yet. The module can reuse an already-initialized backend or initialize it on demand and later deinitialize it only when it owns that initialization. Module deinit is conservative: it attempts backend and interface cleanup, and it clears the top-level module init state only after the owned child deinit steps succeed. +When NVM is enabled, the parameters module requires a concrete packaged storage backend implementation. `src/persist/par_nvm.c` resolves and validates the backend API once during initialization, then uses the mounted callbacks directly for later reads, writes, erases, and sync operations. The RT-Thread AT24CXX backend and the portable flash-ee backend are both available. The flash-ee core is packaged under `src/persist/backend/` and can be integrated either through the repository-root `backend/par_store_backend_flash_ee_fal.c` bridge or through product-specific native flash hooks in the repository-root `backend/par_store_backend_flash_ee_native.c` adapter. The module can reuse an already-initialized backend or initialize it on demand and later deinitialize it only when it owns that initialization. Module deinit is conservative: it attempts backend and interface cleanup, and it clears the top-level module init state only after the owned child deinit steps succeed. When `PAR_CFG_NVM_WRITE_VERIFY_EN = 1`, the NVM path also performs a backend sync plus readback verification after record writes and after header commits. The exact comparison rules are owned by the selected persisted-record layout adapter, not hard-coded in `par_nvm.c`. If your storage medium provides ECC or similar read-health status, keep the policy decision in the business layer. The parameter module does not decide whether that condition means report-only, parameter reset/rebuild, or a wider product fault response. @@ -153,7 +153,7 @@ The persisted NVM image uses a compile-time selected record layout instead of th Backend choices: - enable the packaged RT-Thread AT24CXX backend -- or reserve the generic flash backend configuration entry for later implementation +- or enable the packaged flash-ee backend and choose either the FAL bridge or the native-flash hook path If `PAR_CFG_NVM_EN = 1` and the selected packaged backend implementation is not linked, the build fails at link time by design. diff --git a/src/def/par_def.c b/src/def/par_def.c index 5f8998b..221bddb 100644 --- a/src/def/par_def.c +++ b/src/def/par_def.c @@ -1,7 +1,7 @@ /** * @file par_def.c * @brief Build parameter-definition tables and derived metadata. - * @author wdfk-prog () + * @author wdfk-prog * @version 1.0 * @date 2026-03-27 * @@ -9,10 +9,9 @@ * * @note : * @par Change Log: - * Date Version Author Description - * 2026-03-27 1.0 wdfk-prog first version + * Date Version Author Description + * 2026-03-27 1.0 wdfk-prog first version */ - /** * @addtogroup PAR_CFG * @{ diff --git a/src/def/par_def.h b/src/def/par_def.h index c7d3850..cb4a976 100644 --- a/src/def/par_def.h +++ b/src/def/par_def.h @@ -1,7 +1,7 @@ /** * @file par_def.h * @brief Declare parameter-definition types and compile-time enumerations. - * @author wdfk-prog () + * @author wdfk-prog * @version 1.0 * @date 2026-03-27 * @@ -9,10 +9,9 @@ * * @note : * @par Change Log: - * Date Version Author Description - * 2026-03-27 1.0 wdfk-prog first version + * Date Version Author Description + * 2026-03-27 1.0 wdfk-prog first version */ - /** * @addtogroup PAR_DEF * @{ diff --git a/src/def/par_id_map_static.c b/src/def/par_id_map_static.c index 3a549b2..f9a2f45 100644 --- a/src/def/par_id_map_static.c +++ b/src/def/par_id_map_static.c @@ -1,7 +1,7 @@ /** * @file par_id_map_static.c - * @brief Compile-time generated ID lookup map - * @author wdfk-prog () + * @brief Compile-time generated ID lookup map. + * @author wdfk-prog * @version 1.0 * @date 2026-03-24 * @@ -9,8 +9,8 @@ * * @note : * @par Change Log: - * Date Version Author Description - * 2026-03-24 1.0 wdfk-prog first version + * Date Version Author Description + * 2026-03-24 1.0 wdfk-prog first version */ #include "def/par_id_map_static.h" diff --git a/src/def/par_id_map_static.h b/src/def/par_id_map_static.h index b088eed..30f9062 100644 --- a/src/def/par_id_map_static.h +++ b/src/def/par_id_map_static.h @@ -1,7 +1,7 @@ /** * @file par_id_map_static.h * @brief Declare the compile-time generated static ID lookup map. - * @author wdfk-prog () + * @author wdfk-prog * @version 1.0 * @date 2026-03-24 * @@ -9,8 +9,8 @@ * * @note : * @par Change Log: - * Date Version Author Description - * 2026-03-24 1.0 wdfk-prog first version + * Date Version Author Description + * 2026-03-24 1.0 wdfk-prog first version */ #ifndef _PAR_ID_MAP_STATIC_H_ #define _PAR_ID_MAP_STATIC_H_ diff --git a/src/detail/par_bitwise_impl.inc b/src/detail/par_bitwise_impl.inc index 94d0a1c..0412e10 100644 --- a/src/detail/par_bitwise_impl.inc +++ b/src/detail/par_bitwise_impl.inc @@ -1,7 +1,7 @@ /** * @file par_bitwise_impl.inc * @brief Generate bitwise fast setter implementations. - * @author wdfk-prog () + * @author wdfk-prog * @version 1.0 * @date 2026-03-27 * @@ -9,10 +9,9 @@ * * @note : * @par Change Log: - * Date Version Author Description - * 2026-03-27 1.0 wdfk-prog first version + * Date Version Author Description + * 2026-03-27 1.0 wdfk-prog first version */ - /** * @brief Private include fragment. Included only by par.c. */ diff --git a/src/detail/par_storage_init.inc b/src/detail/par_storage_init.inc index b3e411d..3020e6f 100644 --- a/src/detail/par_storage_init.inc +++ b/src/detail/par_storage_init.inc @@ -1,7 +1,7 @@ /** * @file par_storage_init.inc * @brief Generate parameter storage initialization code. - * @author wdfk-prog () + * @author wdfk-prog * @version 1.0 * @date 2026-03-27 * @@ -9,10 +9,9 @@ * * @note : * @par Change Log: - * Date Version Author Description - * 2026-03-27 1.0 wdfk-prog first version + * Date Version Author Description + * 2026-03-27 1.0 wdfk-prog first version */ - /** * @brief Private include fragment. Included only by par.c. * @details Expands compile-time default initializers for the grouped live storage. diff --git a/src/detail/par_typed_impl.inc b/src/detail/par_typed_impl.inc index 0e58e16..8146820 100644 --- a/src/detail/par_typed_impl.inc +++ b/src/detail/par_typed_impl.inc @@ -1,7 +1,7 @@ /** * @file par_typed_impl.inc * @brief Generate typed getter and setter implementations. - * @author wdfk-prog () + * @author wdfk-prog * @version 1.0 * @date 2026-03-27 * @@ -9,10 +9,9 @@ * * @note : * @par Change Log: - * Date Version Author Description - * 2026-03-27 1.0 wdfk-prog first version + * Date Version Author Description + * 2026-03-27 1.0 wdfk-prog first version */ - /** * @brief Macro-generated typed parameter APIs and checked write core. * @details This fragment is included only by par.c and expands the typed setter/getter. diff --git a/src/layout/par_layout.c b/src/layout/par_layout.c index a537a6b..e3be847 100644 --- a/src/layout/par_layout.c +++ b/src/layout/par_layout.c @@ -1,7 +1,7 @@ /** * @file par_layout.c * @brief Implement parameter storage layout helpers. - * @author wdfk-prog () + * @author wdfk-prog * @version 1.0 * @date 2026-03-27 * @@ -9,10 +9,9 @@ * * @note : * @par Change Log: - * Date Version Author Description - * 2026-03-27 1.0 wdfk-prog first version + * Date Version Author Description + * 2026-03-27 1.0 wdfk-prog first version */ - /** * @addtogroup PAR_LAYOUT * @{ diff --git a/src/layout/par_layout.h b/src/layout/par_layout.h index 5895412..b1a5964 100644 --- a/src/layout/par_layout.h +++ b/src/layout/par_layout.h @@ -1,7 +1,7 @@ /** * @file par_layout.h * @brief Declare parameter storage layout helpers. - * @author wdfk-prog () + * @author wdfk-prog * @version 1.0 * @date 2026-03-27 * @@ -9,10 +9,9 @@ * * @note : * @par Change Log: - * Date Version Author Description - * 2026-03-27 1.0 wdfk-prog first version + * Date Version Author Description + * 2026-03-27 1.0 wdfk-prog first version */ - #ifndef _PAR_LAYOUT_H_ #define _PAR_LAYOUT_H_ /** diff --git a/src/par.c b/src/par.c index 3288249..fed7675 100644 --- a/src/par.c +++ b/src/par.c @@ -10,9 +10,8 @@ * @note : * @par Change Log: * Date Version Author Description - * 2026-01-29 V3.0.1 Ziga Miklosic + * 2026-01-29 V3.0.1 Ziga Miklosic first version */ - /** * @addtogroup PARAMETERS_API * @{ diff --git a/src/par.h b/src/par.h index 1ad484d..bf06b95 100644 --- a/src/par.h +++ b/src/par.h @@ -10,7 +10,7 @@ * @note : * @par Change Log: * Date Version Author Description - * 2026-01-29 V3.0.1 Ziga Miklosic + * 2026-01-29 V3.0.1 Ziga Miklosic first version */ /** * @addtogroup PARAMETERS_API diff --git a/src/par_cfg.h b/src/par_cfg.h index fc25621..29bcd87 100644 --- a/src/par_cfg.h +++ b/src/par_cfg.h @@ -3,16 +3,15 @@ * @brief Provide compile-time configuration for the parameter module. * @author Ziga Miklosic * @version 1.0 - * @date 2026-03-27 + * @date 2026-01-29 * * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. * * @note : * @par Change Log: * Date Version Author Description - * 2026-01-29 V3.0.1 Ziga Miklosic + * 2026-01-29 1.0 Ziga Miklosic first version */ - /** * @addtogroup PAR_CFG * @{ @@ -52,115 +51,7 @@ /** * @brief Compile-time definitions. */ -/** - * @brief Enable/Disable storing persistent parameters to NVM. - * - * @note This switch is also the single compile-time gate for persistence - * metadata in the parameter table. There is no separate PAR_CFG_ENABLE_PERSIST - * override anymore. - */ -#ifndef PAR_CFG_NVM_EN -#define PAR_CFG_NVM_EN (1) -#endif - -/** - * @brief Enable/Disable the legacy GeneralEmbeddedCLibraries/nvm backend. - * - * @note Keep disabled when the application provides an out-of-package storage - * backend, such as the RT-Thread AT24CXX adapter. - */ -#ifndef PAR_CFG_NVM_BACKEND_GEL_EN -#define PAR_CFG_NVM_BACKEND_GEL_EN (0) -#endif - -/** - * @brief Enable/Disable parameter-table compatibility checking. - * - * @note The stored NVM image header carries a table-ID digest that covers - * PAR_CFG_TABLE_ID_SCHEMA_VER, selected record layout, and the stored - * persistent prefix. Self-describing layouts include parameter IDs in that - * digest. Payload-only layouts intentionally exclude external parameter IDs - * and hash only prefix count, persistent order, and parameter type so stored - * prefixes with identical byte layout remain compatible. - * - * When enabled, any persisted-layout incompatibility is treated as a managed - * schema change: startup restores defaults and rebuilds the managed NVM image. - * Layouts with stable prefix addresses allow compatible tail-slot growth when - * the stored prefix still matches the live prefix. The grouped payload-only - * layout is excluded from that repair path and rebuilds on any stored/live - * count mismatch. Payload-only layouts therefore still require the integrator - * to bump PAR_CFG_TABLE_ID_SCHEMA_VER whenever a prefix parameter is - * semantically remapped without changing its serialized byte layout. - * - * @pre "PAR_CFG_NVM_EN" must be enabled, otherwise table-ID checking does. - * not apply. - */ -#ifndef PAR_CFG_TABLE_ID_CHECK_EN -#define PAR_CFG_TABLE_ID_CHECK_EN (0) -#endif - -/** - * @brief Parameter-table ID schema version. - * - * @note Increase this value when the serialized table-ID composition changes. - * The integrator owns this version number and may override it in - * port/par_cfg_port.h before this header provides the default. - */ -#ifndef PAR_CFG_TABLE_ID_SCHEMA_VER -#define PAR_CFG_TABLE_ID_SCHEMA_VER (1U) -#endif - -#ifndef PAR_CFG_NVM_BACKEND_FLASH_EN -#define PAR_CFG_NVM_BACKEND_FLASH_EN (0) -#endif - -/** - * @brief Enable/Disable write-path readback verification for persisted data. - * - * @details When enabled, each persisted-record write and each header write are - * followed by a backend sync and a readback verification step. This improves - * reliability at the cost of additional latency and backend traffic. - */ -#ifndef PAR_CFG_NVM_WRITE_VERIFY_EN -#define PAR_CFG_NVM_WRITE_VERIFY_EN (0) -#endif - -/** - * @brief Select persisted record layout. - * - * @note The chosen layout is also included in the table-ID digest so layout - * changes are treated as managed compatibility changes. - */ -#define PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE (0U) -#define PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE (1U) -#define PAR_CFG_NVM_RECORD_LAYOUT_COMPACT_PAYLOAD (2U) -#define PAR_CFG_NVM_RECORD_LAYOUT_FIXED_PAYLOAD_ONLY (3U) -#define PAR_CFG_NVM_RECORD_LAYOUT_GROUPED_PAYLOAD_ONLY (4U) - -#ifndef PAR_CFG_NVM_RECORD_LAYOUT -#define PAR_CFG_NVM_RECORD_LAYOUT (PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE) -#endif - -/** - * @brief Derived layout capability: serialized records store a parameter ID. - */ -#if (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE) || \ - (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE) || \ - (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_COMPACT_PAYLOAD) -#define PAR_CFG_NVM_RECORD_LAYOUT_HAS_STORED_ID (1) -#else -#define PAR_CFG_NVM_RECORD_LAYOUT_HAS_STORED_ID (0) -#endif - -/** - * @brief Derived layout capability: serialized records store a size descriptor. - */ -#if (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE) || \ - (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_COMPACT_PAYLOAD) -#define PAR_CFG_NVM_RECORD_LAYOUT_HAS_SIZE_DESC (1) -#else -#define PAR_CFG_NVM_RECORD_LAYOUT_HAS_SIZE_DESC (0) -#endif +#include "persist/par_nvm_cfg.h" /** * @brief Enable/Disable debug mode. @@ -607,10 +498,6 @@ PAR_STATIC_ASSERT(par_id_hash_bits_valid, ((PAR_ID_HASH_BITS > 0u) && (PAR_ID_HA #error "Parameter settings invalid: selected NVM layout requires PAR_CFG_ENABLE_ID = 1!" #endif -#if (1 == PAR_CFG_NVM_BACKEND_FLASH_EN) && (0 == PAR_CFG_ENABLE_ID) -#error "Parameter settings invalid: flash backend requires PAR_CFG_ENABLE_ID = 1!" -#endif - #if (1 == PAR_CFG_NVM_EN) && \ ((PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_PAYLOAD_ONLY) || \ (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_GROUPED_PAYLOAD_ONLY)) && \ @@ -618,13 +505,6 @@ PAR_STATIC_ASSERT(par_id_hash_bits_valid, ((PAR_ID_HASH_BITS > 0u) && (PAR_ID_HA #error "Parameter settings invalid: payload-only NVM layouts require PAR_CFG_TABLE_ID_CHECK_EN = 1!" #endif -#if (1 == PAR_CFG_NVM_BACKEND_FLASH_EN) && \ - ((PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE) || \ - (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_PAYLOAD_ONLY) || \ - (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_GROUPED_PAYLOAD_ONLY)) -#error "Parameter settings invalid: selected NVM layout is not supported by the flash backend!" -#endif - #if (0 == PAR_CFG_ENABLE_ID) && (1 == PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK) #error "Parameter settings invalid: runtime duplicate-ID diagnostics require PAR_CFG_ENABLE_ID = 1!" #endif diff --git a/src/persist/backend/par_store_backend.h b/src/persist/backend/par_store_backend.h index c3477d3..a2846da 100644 --- a/src/persist/backend/par_store_backend.h +++ b/src/persist/backend/par_store_backend.h @@ -1,18 +1,19 @@ /** * @file par_store_backend.h * @brief Declare the abstract parameter-storage backend interface. - * @author wdfk-prog () + * @author wdfk-prog * @version 1.0 * @date 2026-03-29 - * - * @copyright Copyright (c) 2026 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. * * @details par_nvm.c uses this interface instead of depending directly on a * concrete NVM repository layout. Integrators may provide any storage backend * that supports the required byte-addressable operations. + * @note : * @par Change Log: - * Date Version Author Description - * 2026-03-29 1.0 wdfk-prog first version + * Date Version Author Description + * 2026-03-29 1.0 wdfk-prog first version */ #ifndef _PAR_STORE_BACKEND_H_ #define _PAR_STORE_BACKEND_H_ @@ -117,12 +118,26 @@ typedef struct const char *name; } par_store_backend_api_t; +/** + * @brief Bind or prepare the active parameter-storage backend. + * + * @details Call this before @ref par_store_backend_get_api when the selected + * backend needs to attach one concrete storage port, partition, or device + * context. Pure backends that do not need a pre-bind step should return + * @ref ePAR_OK without side effects. + * + * @return ePAR_OK on success, otherwise an implementation-defined error. + */ +par_status_t par_store_backend_bind(void); + /** * @brief Resolve the active parameter-storage backend API. * * @details Link exactly one concrete implementation when `PAR_CFG_NVM_EN = 1`. * The package can provide the GeneralEmbeddedCLibraries/nvm adapter, or the - * application can provide its own implementation. + * application can provide its own implementation. This accessor must be side + * effect free; any backend-specific binding work belongs in + * @ref par_store_backend_bind. * * @return Pointer to backend API, or NULL if no backend is available. */ diff --git a/src/persist/backend/par_store_backend_flash_ee.c b/src/persist/backend/par_store_backend_flash_ee.c new file mode 100644 index 0000000..1a208a7 --- /dev/null +++ b/src/persist/backend/par_store_backend_flash_ee.c @@ -0,0 +1,1940 @@ +/** + * @file par_store_backend_flash_ee.c + * @brief Implement the generic flash-emulated EEPROM backend core. + * @author wdfk-prog + * @version 1.0 + * @date 2026-04-14 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @details The backend exposes a byte-addressable persistence interface to + * @ref par_nvm.c, while storing data on erase-before-write flash media. + * + * The core keeps a bounded write-back cache and appends fixed-size records to + * the active flash bank during @ref sync. The flash region is split into two + * banks. When the active bank becomes full, the backend performs a streaming + * checkpoint into the inactive bank and then switches banks atomically through + * a header state transition. The generic core does not depend on RT-Thread or + * any flash SDK. Platform adapters provide the physical flash access through + * @ref par_store_flash_ee_port_api_t. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-14 1.0 wdfk-prog first version + */ +#include "persist/backend/par_store_backend_flash_ee.h" + +#if (1 == PAR_CFG_NVM_EN) && (1 == PAR_CFG_NVM_BACKEND_FLASH_EE_EN) + +#include +#include +#include "port/par_if.h" + + +#define PAR_STORE_FLASH_EE_BANK_COUNT (2u) +#define PAR_STORE_FLASH_EE_HEADER_MAGIC (0x50454548u) /* PEEH */ +#define PAR_STORE_FLASH_EE_RECORD_MAGIC (0x50454552u) /* PEER */ +#define PAR_STORE_FLASH_EE_HEADER_PREPARE (0xFFFFFF00u) +#define PAR_STORE_FLASH_EE_HEADER_ACTIVE (0xFFFF0000u) +#define PAR_STORE_FLASH_EE_ERASE_VALUE (0xFFu) +#define PAR_STORE_FLASH_EE_HEADER_SIZE (64u) +#define PAR_STORE_FLASH_EE_RECORD_META_SIZE (12u) +#define PAR_STORE_FLASH_EE_RECORD_BUF_SIZE (PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE + PAR_STORE_FLASH_EE_RECORD_META_SIZE + (2u * PAR_CFG_NVM_BACKEND_FLASH_EE_PROGRAM_SIZE)) +#define PAR_STORE_FLASH_EE_STATE_PATCH_BUF_SIZE \ + (((2u * PAR_CFG_NVM_BACKEND_FLASH_EE_PROGRAM_SIZE) >= 4u) ? (2u * PAR_CFG_NVM_BACKEND_FLASH_EE_PROGRAM_SIZE) : 4u) +#define PAR_STORE_FLASH_EE_CACHE_LINE_COUNT (PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE / PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE) +#define PAR_STORE_FLASH_EE_DIRTY_BYTES ((PAR_STORE_FLASH_EE_CACHE_LINE_COUNT + 7u) / 8u) + +/** + * @brief Describe one persistent flash-bank header. + */ +typedef struct +{ + uint32_t magic; /**< Header magic used to identify a valid bank. */ + uint32_t version; /**< On-flash backend format version. */ + uint32_t logical_size; /**< Exposed logical EEPROM size in bytes. */ + uint32_t line_size; /**< Fixed payload size carried by each append record. */ + uint32_t record_size; /**< Total append-record size including payload, metadata, padding, and commit unit. */ + uint32_t bank_size; /**< Physical size of one bank inside the persistence region. */ + uint32_t seq; /**< Monotonic bank sequence used to pick the newest bank. */ + uint32_t cfg_crc; /**< CRC of the geometry fields stored in the header. */ + uint32_t state; /**< Header state word, for example prepare or active. */ + uint32_t reserved[7]; /**< Reserved words kept erased for later format extension. */ +} par_store_flash_ee_bank_header_t; + +/** + * @brief Describe the metadata stored before the final commit unit. + * + * @details Each record stores the payload first, then this metadata block, + * optional erased padding, and finally one dedicated commit unit. The record + * becomes visible only after the commit unit is programmed successfully. + */ +typedef struct +{ + uint32_t line_index; /**< Logical line index updated by this record. */ + uint16_t payload_size; /**< Payload size in bytes. Always equals line_size. */ + uint16_t record_crc; /**< CRC of the payload bytes plus semantic metadata fields. */ + uint32_t reserved; /**< Reserved metadata word for future extension. */ +} par_store_flash_ee_record_meta_t; + +/** + * @brief Hold the live runtime state of the backend core. + * + * @details The context tracks the selected active bank, the current append + * tail, and the location of the in-RAM cache window. The cache window is only + * a bounded staging area; committed data remains in flash append records. + */ +typedef struct +{ + bool is_bound; /**< True once one physical port is bound. */ + bool is_init; /**< True once the backend core finished initialization. */ + bool cache_valid; /**< True once the cache window contains reconstructed data. */ + const par_store_flash_ee_port_api_t *p_port_api; /**< Bound physical port API table. */ + void *p_port_ctx; /**< Opaque physical port context. */ + uint32_t region_size; /**< Full persistence-region size in bytes. */ + uint32_t bank_size; /**< Size of one physical bank in bytes. */ + uint32_t erase_size; /**< Physical erase granularity in bytes. */ + uint32_t program_size; /**< Physical program granularity in bytes. */ + uint32_t logical_size; /**< Exposed logical EEPROM size in bytes. */ + uint32_t line_size; /**< Fixed logical line size in bytes. */ + uint32_t line_count; /**< Number of logical lines in the address space. */ + uint32_t record_size; /**< Total append-record size in bytes. */ + uint32_t active_bank_base; /**< Base address of the currently active bank. */ + uint32_t inactive_bank_base; /**< Base address of the checkpoint target bank. */ + uint32_t active_seq; /**< Sequence number of the active bank. */ + uint32_t active_next_offset; /**< Next free append offset inside the active bank. */ + uint32_t cache_base; /**< Logical base address covered by the cache window. */ + uint32_t cache_size; /**< Actual cache-window size in bytes. */ + par_store_flash_ee_diag_t diag; /**< Latest diagnostic code. */ +} par_store_flash_ee_ctx_t; + +/** + * @brief Hold the single backend instance state. + */ +static par_store_flash_ee_ctx_t g_par_store_flash_ee_ctx = { + .is_bound = false, + .is_init = false, + .cache_valid = false, + .p_port_api = NULL, + .p_port_ctx = NULL, + .region_size = 0u, + .bank_size = 0u, + .erase_size = 0u, + .program_size = 0u, + .logical_size = 0u, + .line_size = PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE, + .line_count = 0u, + .record_size = 0u, + .active_bank_base = 0u, + .inactive_bank_base = 0u, + .active_seq = 0u, + .active_next_offset = 0u, + .cache_base = 0u, + .cache_size = 0u, + .diag = ePAR_STORE_FLASH_EE_DIAG_NONE, +}; + +/** + * @brief Store the bounded RAM cache window. + * + * @details The cache does not mirror the full logical EEPROM space. It only + * keeps one aligned window so write, erase, and read operations can work on a + * small hot region without allocating logical_size bytes of RAM. + */ +static uint8_t g_par_store_flash_ee_cache[PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE]; + +/** + * @brief Store one dirty bit per cached line. + * + * @details A dirty bit marks that the corresponding cache line differs from + * the committed flash view and still needs to be appended during sync. + */ +static uint8_t g_par_store_flash_ee_dirty[PAR_STORE_FLASH_EE_DIRTY_BYTES]; + +/** + * @brief Flush staged cache data into physical flash. + * + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_sync(void); + +/** + * @brief Clear all cache dirty bits. + */ +static void par_store_flash_ee_clear_dirty(void); + +/** + * @brief Return true when one memory block is fully erased. + * + * @param[in] p_buf Buffer to inspect. + * @param[in] size Buffer length in bytes. + * @return true when the block contains only erased bytes; otherwise false. + */ +static bool par_store_flash_ee_is_erased_block(const uint8_t *p_buf, uint32_t size) +{ + uint32_t idx = 0u; + + for (idx = 0u; idx < size; ++idx) + { + if (PAR_STORE_FLASH_EE_ERASE_VALUE != p_buf[idx]) + { + return false; + } + } + + return true; +} + +/** + * @brief Update the backend diagnostic code. + * + * @param[in] diag New diagnostic code. + */ +static void par_store_flash_ee_set_diag(par_store_flash_ee_diag_t diag) +{ + g_par_store_flash_ee_ctx.diag = diag; +} + +/** + * @brief Normalize a top-level backend return status. + * + * @details Successful top-level operations clear the last diagnostic so + * @ref par_store_backend_flash_ee_get_diag reports the latest backend event + * instead of keeping an older transient warning or failure forever. + * + * @param[in] status Operation result to normalize. + * @return The original status value. + */ +static par_status_t par_store_flash_ee_complete_status(par_status_t status) +{ + if (ePAR_OK == status) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_NONE); + } + + return status; +} + +/** + * @brief Reset transient runtime state after a failed or completed session. + * + * @details Binding information stays intact so the same adapter can try + * initialization again, but all derived geometry, bank-selection, and cache + * state is cleared to avoid exposing a half-initialized backend view. + */ +static void par_store_flash_ee_reset_runtime_state(void) +{ + g_par_store_flash_ee_ctx.is_init = false; + g_par_store_flash_ee_ctx.cache_valid = false; + g_par_store_flash_ee_ctx.region_size = 0u; + g_par_store_flash_ee_ctx.bank_size = 0u; + g_par_store_flash_ee_ctx.erase_size = 0u; + g_par_store_flash_ee_ctx.program_size = 0u; + g_par_store_flash_ee_ctx.logical_size = 0u; + g_par_store_flash_ee_ctx.line_size = PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE; + g_par_store_flash_ee_ctx.line_count = 0u; + g_par_store_flash_ee_ctx.record_size = 0u; + g_par_store_flash_ee_ctx.active_bank_base = 0u; + g_par_store_flash_ee_ctx.inactive_bank_base = 0u; + g_par_store_flash_ee_ctx.active_seq = 0u; + g_par_store_flash_ee_ctx.active_next_offset = 0u; + g_par_store_flash_ee_ctx.cache_base = 0u; + g_par_store_flash_ee_ctx.cache_size = 0u; + par_store_flash_ee_clear_dirty(); +} + +/** + * @brief Best-effort cleanup after backend init fails after the port opened. + * + * @details Once the physical port init succeeded, later failures must unwind + * the opened port and clear transient runtime state so the next init attempt + * does not inherit a partially initialized backend context. + */ +static void par_store_flash_ee_cleanup_failed_init(void) +{ + if ((NULL != g_par_store_flash_ee_ctx.p_port_api) && + (NULL != g_par_store_flash_ee_ctx.p_port_api->deinit)) + { + const par_status_t cleanup_status = + g_par_store_flash_ee_ctx.p_port_api->deinit(g_par_store_flash_ee_ctx.p_port_ctx); + + if (ePAR_OK != cleanup_status) + { + PAR_ERR_PRINT("PAR_FLASH_EE: port deinit after failed init failed, err=%u", + (unsigned)cleanup_status); + } + } + + par_store_flash_ee_reset_runtime_state(); +} + +/** + * @brief Return true when a failed bank scan may be recovered by reformatting. + * + * @details Scan failures caused by structural incompatibility or corrupted + * on-flash contents may be recovered by rebuilding the banks. Transport-level + * flash read failures must not be treated as safe-to-format because they do + * not prove that both banks are actually empty or unusable. + * + * @param[in] diag Diagnostic captured from the failed scan. + * @return true when automatic reformat is allowed; otherwise false. + */ +static bool par_store_flash_ee_scan_diag_allows_reformat(par_store_flash_ee_diag_t diag) +{ + switch (diag) + { + case ePAR_STORE_FLASH_EE_DIAG_BANK_ERASED: + case ePAR_STORE_FLASH_EE_DIAG_HEADER_MAGIC: + case ePAR_STORE_FLASH_EE_DIAG_HEADER_VERSION: + case ePAR_STORE_FLASH_EE_DIAG_HEADER_STATE: + case ePAR_STORE_FLASH_EE_DIAG_HEADER_CRC: + case ePAR_STORE_FLASH_EE_DIAG_HEADER_LAYOUT: + case ePAR_STORE_FLASH_EE_DIAG_RECORD_MAGIC: + case ePAR_STORE_FLASH_EE_DIAG_RECORD_RANGE: + case ePAR_STORE_FLASH_EE_DIAG_RECORD_CRC: + return true; + + default: + return false; + } +} + +/** + * @brief Read raw bytes from the bound physical flash port. + * + * @param[in] addr Byte offset inside the persistence region. + * @param[in] size Number of bytes to read. + * @param[out] p_buf Destination buffer. + * @return ePAR_OK on success, otherwise @ref ePAR_ERROR_NVM. + */ +static par_status_t par_store_flash_ee_port_read(uint32_t addr, uint32_t size, uint8_t *p_buf) +{ + par_status_t status = ePAR_ERROR_NVM; + + status = g_par_store_flash_ee_ctx.p_port_api->read(g_par_store_flash_ee_ctx.p_port_ctx, addr, size, p_buf); + if (ePAR_OK != status) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_PORT_READ); + PAR_ERR_PRINT("PAR_FLASH_EE: port read failed, addr=%lu, size=%lu", (unsigned long)addr, (unsigned long)size); + } + + return status; +} + +/** + * @brief Program raw bytes through the bound physical flash port. + * + * @param[in] addr Byte offset inside the persistence region. + * @param[in] size Number of bytes to program. + * @param[in] p_buf Source buffer. + * @return ePAR_OK on success, otherwise @ref ePAR_ERROR_NVM. + */ +static par_status_t par_store_flash_ee_port_program(uint32_t addr, uint32_t size, const uint8_t *p_buf) +{ + par_status_t status = ePAR_ERROR_NVM; + + status = g_par_store_flash_ee_ctx.p_port_api->program(g_par_store_flash_ee_ctx.p_port_ctx, addr, size, p_buf); + if (ePAR_OK != status) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_PORT_PROGRAM); + PAR_ERR_PRINT("PAR_FLASH_EE: port program failed, addr=%lu, size=%lu", (unsigned long)addr, (unsigned long)size); + } + + return status; +} + +/** + * @brief Erase raw bytes through the bound physical flash port. + * + * @param[in] addr Byte offset inside the persistence region. + * @param[in] size Number of bytes to erase. + * @return ePAR_OK on success, otherwise @ref ePAR_ERROR_NVM. + */ +static par_status_t par_store_flash_ee_port_erase(uint32_t addr, uint32_t size) +{ + par_status_t status = ePAR_ERROR_NVM; + + status = g_par_store_flash_ee_ctx.p_port_api->erase(g_par_store_flash_ee_ctx.p_port_ctx, addr, size); + if (ePAR_OK != status) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_PORT_ERASE); + PAR_ERR_PRINT("PAR_FLASH_EE: port erase failed, addr=%lu, size=%lu", (unsigned long)addr, (unsigned long)size); + } + + return status; +} + +/** + * @brief Calculate the persistent bank-header CRC. + * + * @param[in] p_header Header to hash. + * @return CRC16 value widened to 32 bits. + */ +static uint32_t par_store_flash_ee_calc_header_crc(const par_store_flash_ee_bank_header_t *p_header) +{ + uint16_t crc = PAR_IF_CRC16_INIT; + uint32_t size = (uint32_t)offsetof(par_store_flash_ee_bank_header_t, cfg_crc); + + crc = par_if_crc16_accumulate(crc, (const uint8_t *)p_header, size); + return (uint32_t)crc; +} + +/** + * @brief Calculate one append-record integrity CRC. + * + * @details The CRC covers the payload bytes and the metadata fields that define + * the record semantics during reconstruction. This prevents silently accepting + * a committed payload under a corrupted logical line index or payload size. + * + * @param[in] p_payload Payload bytes. + * @param[in] payload_size Payload length in bytes. + * @param[in] line_index Logical line index stored in the record metadata. + * @return CRC16 value. + */ +static uint16_t par_store_flash_ee_calc_record_crc(const uint8_t *p_payload, uint16_t payload_size, uint32_t line_index) +{ + uint16_t crc = PAR_IF_CRC16_INIT; + + crc = par_if_crc16_accumulate(crc, p_payload, (uint32_t)payload_size); + crc = par_if_crc16_accumulate(crc, (const uint8_t *)&line_index, sizeof(line_index)); + crc = par_if_crc16_accumulate(crc, (const uint8_t *)&payload_size, sizeof(payload_size)); + return crc; +} + +/** + * @brief Return one bank base offset. + * + * @param[in] bank_index Bank index, either 0 or 1. + * @return Bank base offset in bytes. + */ +static uint32_t par_store_flash_ee_bank_base(uint32_t bank_index) +{ + return (bank_index * g_par_store_flash_ee_ctx.bank_size); +} + +/** + * @brief Test whether one cache line is marked dirty. + * + * @param[in] cache_line Cache-line index inside the current cache window. + * @return true when dirty; otherwise false. + */ +static bool par_store_flash_ee_is_dirty(uint32_t cache_line) +{ + return (0u != (g_par_store_flash_ee_dirty[cache_line / 8u] & (uint8_t)(1u << (cache_line % 8u)))); +} + +/** + * @brief Mark one cache line as dirty. + * + * @param[in] cache_line Cache-line index inside the current cache window. + */ +static void par_store_flash_ee_mark_dirty(uint32_t cache_line) +{ + g_par_store_flash_ee_dirty[cache_line / 8u] |= (uint8_t)(1u << (cache_line % 8u)); +} + +/** + * @brief Clear all cache dirty bits. + */ +static void par_store_flash_ee_clear_dirty(void) +{ + (void)memset(g_par_store_flash_ee_dirty, 0, sizeof(g_par_store_flash_ee_dirty)); +} + +/** + * @brief Return true when the current cache contains dirty lines. + * + * @return true when dirty data is pending; otherwise false. + */ +static bool par_store_flash_ee_has_dirty(void) +{ + uint32_t idx = 0u; + + for (idx = 0u; idx < (uint32_t)sizeof(g_par_store_flash_ee_dirty); ++idx) + { + if (0u != g_par_store_flash_ee_dirty[idx]) + { + return true; + } + } + + return false; +} + + +/** + * @brief Count dirty cache lines inside the active cache window. + * + * @details The returned value is later converted into the number of append + * records needed during sync. Each dirty cache line becomes exactly one record + * append unless a checkpoint is triggered first. + * + * @return Number of dirty cache lines. + */ +static uint32_t par_store_flash_ee_count_dirty(void) +{ + uint32_t count = 0u; + uint32_t cache_line = 0u; + + for (cache_line = 0u; cache_line < (g_par_store_flash_ee_ctx.cache_size / g_par_store_flash_ee_ctx.line_size); ++cache_line) + { + if (true == par_store_flash_ee_is_dirty(cache_line)) + { + ++count; + } + } + + return count; +} + +/** + * @brief Align one value upward to the requested granularity. + * + * @param[in] value Input value to align. + * @param[in] align Alignment granularity in bytes. + * @return Aligned value. + */ +static uint32_t par_store_flash_ee_align_up(uint32_t value, uint32_t align) +{ + return ((value + align - 1u) / align) * align; +} + +/** + * @brief Return the metadata offset inside one append record. + * + * @return Metadata offset in bytes. + */ +static uint32_t par_store_flash_ee_record_meta_offset(void) +{ + return g_par_store_flash_ee_ctx.line_size; +} + +/** + * @brief Return the commit-unit offset inside one append record. + * + * @return Commit-unit offset in bytes. + */ +static uint32_t par_store_flash_ee_record_commit_offset(void) +{ + return par_store_flash_ee_align_up(par_store_flash_ee_record_meta_offset() + PAR_STORE_FLASH_EE_RECORD_META_SIZE, + g_par_store_flash_ee_ctx.program_size); +} + +/** + * @brief Build the expected commit-unit bytes. + * + * @param[out] p_commit_buf Destination buffer with program-size bytes. + */ +static void par_store_flash_ee_build_commit_unit(uint8_t *p_commit_buf) +{ + uint32_t magic = PAR_STORE_FLASH_EE_RECORD_MAGIC; + uint8_t magic_bytes[sizeof(uint32_t)]; + uint32_t idx = 0u; + + (void)memcpy(magic_bytes, &magic, sizeof(magic_bytes)); + for (idx = 0u; idx < g_par_store_flash_ee_ctx.program_size; ++idx) + { + p_commit_buf[idx] = magic_bytes[idx % (uint32_t)sizeof(magic_bytes)]; + } +} + +/** + * @brief Return true when one commit unit matches the committed pattern. + * + * @param[in] p_commit_buf Commit-unit bytes read from flash. + * @return true when the record is committed; otherwise false. + */ +static bool par_store_flash_ee_is_valid_commit_unit(const uint8_t *p_commit_buf) +{ + uint8_t expected_commit[PAR_CFG_NVM_BACKEND_FLASH_EE_PROGRAM_SIZE]; + + if (NULL == p_commit_buf) + { + return false; + } + + par_store_flash_ee_build_commit_unit(expected_commit); + return (0 == memcmp(p_commit_buf, expected_commit, g_par_store_flash_ee_ctx.program_size)); +} + +/** + * @brief Return true when one commit unit still contains erased bytes. + * + * @param[in] p_commit_buf Commit-unit bytes read from flash. + * @return true when the commit unit is still open; otherwise false. + */ +static bool par_store_flash_ee_commit_unit_has_erased_byte(const uint8_t *p_commit_buf) +{ + uint32_t idx = 0u; + + if (NULL == p_commit_buf) + { + return false; + } + + for (idx = 0u; idx < g_par_store_flash_ee_ctx.program_size; ++idx) + { + if (PAR_STORE_FLASH_EE_ERASE_VALUE == p_commit_buf[idx]) + { + return true; + } + } + + return false; +} + +/** + * @brief Parse one on-flash record metadata block. + * + * @param[in] p_record_buf Full record bytes. + * @param[out] p_meta Receives the parsed metadata. + */ +static void par_store_flash_ee_parse_record_meta(const uint8_t *p_record_buf, par_store_flash_ee_record_meta_t *p_meta) +{ + (void)memcpy(p_meta, + &p_record_buf[par_store_flash_ee_record_meta_offset()], + sizeof(*p_meta)); +} + +/** + * @brief Validate static and runtime backend geometry. + * + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_validate_geometry(void) +{ + uint32_t max_records = 0u; + + if ((0u == g_par_store_flash_ee_ctx.region_size) || + (0u == g_par_store_flash_ee_ctx.erase_size) || + (0u == g_par_store_flash_ee_ctx.program_size)) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_PORT_GEOMETRY); + return ePAR_ERROR_INIT; + } + + if (g_par_store_flash_ee_ctx.program_size > PAR_CFG_NVM_BACKEND_FLASH_EE_PROGRAM_SIZE) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_CONFIG); + return ePAR_ERROR_INIT; + } + + if ((0u == PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE) || + (0u == PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE) || + (0u == PAR_CFG_NVM_BACKEND_FLASH_EE_LOGICAL_SIZE) || + (0u != (PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE % PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE)) || + (0u != (PAR_CFG_NVM_BACKEND_FLASH_EE_LOGICAL_SIZE % PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE))) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_CONFIG); + return ePAR_ERROR_INIT; + } + + g_par_store_flash_ee_ctx.bank_size = g_par_store_flash_ee_ctx.region_size / PAR_STORE_FLASH_EE_BANK_COUNT; + g_par_store_flash_ee_ctx.logical_size = PAR_CFG_NVM_BACKEND_FLASH_EE_LOGICAL_SIZE; + g_par_store_flash_ee_ctx.line_size = PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE; + g_par_store_flash_ee_ctx.line_count = (g_par_store_flash_ee_ctx.logical_size + g_par_store_flash_ee_ctx.line_size - 1u) / + g_par_store_flash_ee_ctx.line_size; + g_par_store_flash_ee_ctx.record_size = par_store_flash_ee_record_commit_offset() + g_par_store_flash_ee_ctx.program_size; + + if ((0u != (PAR_STORE_FLASH_EE_HEADER_SIZE % g_par_store_flash_ee_ctx.program_size)) || + (0u != (g_par_store_flash_ee_ctx.record_size % g_par_store_flash_ee_ctx.program_size))) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_CONFIG); + return ePAR_ERROR_INIT; + } + + if ((0u != (g_par_store_flash_ee_ctx.bank_size % g_par_store_flash_ee_ctx.erase_size)) || + (g_par_store_flash_ee_ctx.bank_size <= PAR_STORE_FLASH_EE_HEADER_SIZE)) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_PORT_GEOMETRY); + return ePAR_ERROR_INIT; + } + + max_records = (g_par_store_flash_ee_ctx.bank_size - PAR_STORE_FLASH_EE_HEADER_SIZE) / + g_par_store_flash_ee_ctx.record_size; + if (max_records <= g_par_store_flash_ee_ctx.line_count) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_CAPACITY); + PAR_ERR_PRINT("PAR_FLASH_EE: bank capacity too small, max_records=%lu, line_count=%lu (need one extra append slot)", + (unsigned long)max_records, + (unsigned long)g_par_store_flash_ee_ctx.line_count); + return ePAR_ERROR_INIT; + } + + return ePAR_OK; +} + +/** + * @brief Probe one bank header and report its compatibility result. + + * + * @param[in] p_header Bank header bytes. + * @return Diagnostic code describing the probe result. + */ +static par_store_flash_ee_diag_t par_store_flash_ee_probe_header(const par_store_flash_ee_bank_header_t *p_header) +{ + if (true == par_store_flash_ee_is_erased_block((const uint8_t *)p_header, sizeof(*p_header))) + { + return ePAR_STORE_FLASH_EE_DIAG_BANK_ERASED; + } + + if (PAR_STORE_FLASH_EE_HEADER_MAGIC != p_header->magic) + { + return ePAR_STORE_FLASH_EE_DIAG_HEADER_MAGIC; + } + + if (PAR_CFG_NVM_BACKEND_FLASH_EE_VERSION != p_header->version) + { + return ePAR_STORE_FLASH_EE_DIAG_HEADER_VERSION; + } + + if (PAR_STORE_FLASH_EE_HEADER_ACTIVE != p_header->state) + { + return ePAR_STORE_FLASH_EE_DIAG_HEADER_STATE; + } + + if (par_store_flash_ee_calc_header_crc(p_header) != p_header->cfg_crc) + { + return ePAR_STORE_FLASH_EE_DIAG_HEADER_CRC; + } + + if ((g_par_store_flash_ee_ctx.logical_size != p_header->logical_size) || + (g_par_store_flash_ee_ctx.line_size != p_header->line_size) || + (g_par_store_flash_ee_ctx.record_size != p_header->record_size) || + (g_par_store_flash_ee_ctx.bank_size != p_header->bank_size)) + { + return ePAR_STORE_FLASH_EE_DIAG_HEADER_LAYOUT; + } + + return ePAR_STORE_FLASH_EE_DIAG_NONE; +} + +/** + * @brief Return true when one programmed slot is still uncommitted. + * + * @details The flash-ee protocol writes payload and metadata first, then + * programs one dedicated commit unit last. A slot is therefore treated as + * uncommitted when it is not fully erased and its commit unit still contains at + * least one erased byte. + * + * @param[in] p_record_buf Full record-sized buffer read from flash. + * @return true when the slot is uncommitted; otherwise false. + */ +static bool par_store_flash_ee_is_uncommitted_record_slot(const uint8_t *p_record_buf) +{ + const uint32_t commit_offset = par_store_flash_ee_record_commit_offset(); + + if (NULL == p_record_buf) + { + return false; + } + + if (true == par_store_flash_ee_is_erased_block(p_record_buf, g_par_store_flash_ee_ctx.record_size)) + { + return false; + } + + return par_store_flash_ee_commit_unit_has_erased_byte(&p_record_buf[commit_offset]); +} + +/** + * @brief Scan one active bank and return the first free record offset. + * + * @param[in] bank_base Bank base offset. + * @param[out] p_seq Receives the header sequence number. + * @param[out] p_next_offset Receives the next append offset inside the bank. + * @return ePAR_OK when the bank is valid enough to use; otherwise an error. + * + * @note Scan accepts one fully erased record slot as the end of the append + * region. It also skips any programmed slot whose final commit unit still + * contains erased bytes, because such a slot never became visible. Any slot + * with a closed but invalid commit unit, or any committed record with invalid + * metadata or CRC, makes the bank unusable. + */ +static par_status_t par_store_flash_ee_scan_bank(uint32_t bank_base, uint32_t *p_seq, uint32_t *p_next_offset) +{ + par_store_flash_ee_bank_header_t header; + par_store_flash_ee_record_meta_t record_meta; + uint8_t line_buf[PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE]; + uint8_t record_buf[PAR_STORE_FLASH_EE_RECORD_BUF_SIZE]; + const uint32_t commit_offset = par_store_flash_ee_record_commit_offset(); + par_store_flash_ee_diag_t probe = ePAR_STORE_FLASH_EE_DIAG_NONE; + uint32_t offset = PAR_STORE_FLASH_EE_HEADER_SIZE; + par_status_t status = ePAR_OK; + + status = par_store_flash_ee_port_read(bank_base, sizeof(header), (uint8_t *)&header); + if (ePAR_OK != status) + { + return status; + } + + probe = par_store_flash_ee_probe_header(&header); + if (ePAR_STORE_FLASH_EE_DIAG_NONE != probe) + { + par_store_flash_ee_set_diag(probe); + return ePAR_ERROR_NVM; + } + + while ((offset + g_par_store_flash_ee_ctx.record_size) <= g_par_store_flash_ee_ctx.bank_size) + { + status = par_store_flash_ee_port_read(bank_base + offset, + g_par_store_flash_ee_ctx.record_size, + record_buf); + if (ePAR_OK != status) + { + return status; + } + + if (true == par_store_flash_ee_is_erased_block(record_buf, g_par_store_flash_ee_ctx.record_size)) + { + break; + } + + if (false == par_store_flash_ee_is_valid_commit_unit(&record_buf[commit_offset])) + { + if (true == par_store_flash_ee_is_uncommitted_record_slot(record_buf)) + { + if (ePAR_STORE_FLASH_EE_DIAG_RECORD_TAIL != g_par_store_flash_ee_ctx.diag) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_RECORD_TAIL); + } + offset += g_par_store_flash_ee_ctx.record_size; + continue; + } + + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_RECORD_MAGIC); + return ePAR_ERROR_NVM; + } + + par_store_flash_ee_parse_record_meta(record_buf, &record_meta); + if ((record_meta.line_index >= g_par_store_flash_ee_ctx.line_count) || + (record_meta.payload_size != g_par_store_flash_ee_ctx.line_size)) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_RECORD_RANGE); + return ePAR_ERROR_NVM; + } + + (void)memcpy(line_buf, record_buf, g_par_store_flash_ee_ctx.line_size); + if (par_store_flash_ee_calc_record_crc(line_buf, + record_meta.payload_size, + record_meta.line_index) != record_meta.record_crc) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_RECORD_CRC); + return ePAR_ERROR_NVM; + } + + offset += g_par_store_flash_ee_ctx.record_size; + } + + *p_seq = header.seq; + *p_next_offset = offset; + return ePAR_OK; +} + +/** + * @brief Format one bank header in RAM. + * + * @param[out] p_header Destination header. + * @param[in] seq Bank sequence number. + * @param[in] state Header state value. + */ +static void par_store_flash_ee_build_header(par_store_flash_ee_bank_header_t *p_header, uint32_t seq, uint32_t state) +{ + (void)memset(p_header, 0xFF, sizeof(*p_header)); + p_header->magic = PAR_STORE_FLASH_EE_HEADER_MAGIC; + p_header->version = PAR_CFG_NVM_BACKEND_FLASH_EE_VERSION; + p_header->logical_size = g_par_store_flash_ee_ctx.logical_size; + p_header->line_size = g_par_store_flash_ee_ctx.line_size; + p_header->record_size = g_par_store_flash_ee_ctx.record_size; + p_header->bank_size = g_par_store_flash_ee_ctx.bank_size; + p_header->seq = seq; + p_header->state = state; + p_header->cfg_crc = par_store_flash_ee_calc_header_crc(p_header); +} + +/** + * @brief Prepare one empty bank for later record appends. + * + * @param[in] bank_base Bank base offset. + * @param[in] seq Sequence number to place into the new header. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_prepare_bank(uint32_t bank_base, uint32_t seq) +{ + par_store_flash_ee_bank_header_t header; + par_status_t status = ePAR_OK; + + status = par_store_flash_ee_port_erase(bank_base, g_par_store_flash_ee_ctx.bank_size); + if (ePAR_OK != status) + { + return status; + } + + par_store_flash_ee_build_header(&header, seq, PAR_STORE_FLASH_EE_HEADER_PREPARE); + status = par_store_flash_ee_port_program(bank_base, sizeof(header), (const uint8_t *)&header); + return status; +} + +/** + * @brief Activate one prepared bank by patching the state word. + * + * @param[in] bank_base Bank base offset. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_activate_bank(uint32_t bank_base) +{ + uint8_t state_patch[PAR_STORE_FLASH_EE_STATE_PATCH_BUF_SIZE]; + const uint32_t state_addr = bank_base + (uint32_t)offsetof(par_store_flash_ee_bank_header_t, state); + const uint32_t patch_addr = state_addr - (state_addr % g_par_store_flash_ee_ctx.program_size); + const uint32_t patch_offset = state_addr - patch_addr; + const uint32_t patch_size = (((patch_offset + (uint32_t)sizeof(uint32_t)) + g_par_store_flash_ee_ctx.program_size - 1u) / + g_par_store_flash_ee_ctx.program_size) * + g_par_store_flash_ee_ctx.program_size; + const uint32_t active_state = PAR_STORE_FLASH_EE_HEADER_ACTIVE; + par_status_t status = ePAR_OK; + + status = par_store_flash_ee_port_read(patch_addr, patch_size, state_patch); + if (ePAR_OK != status) + { + return status; + } + + (void)memcpy(&state_patch[patch_offset], &active_state, sizeof(active_state)); + + return par_store_flash_ee_port_program(patch_addr, + patch_size, + state_patch); +} + +/** + * @brief Resolve one logical line from the active flash bank. + * + * @param[in] line_index Logical line index. + * @param[out] p_line_buf Destination line buffer. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_resolve_line_from_bank(uint32_t line_index, uint8_t *p_line_buf) +{ + par_store_flash_ee_record_meta_t record_meta; + uint8_t record_buf[PAR_STORE_FLASH_EE_RECORD_BUF_SIZE]; + const uint32_t commit_offset = par_store_flash_ee_record_commit_offset(); + par_status_t status = ePAR_OK; + uint32_t offset = g_par_store_flash_ee_ctx.active_next_offset; + + (void)memset(p_line_buf, PAR_STORE_FLASH_EE_ERASE_VALUE, g_par_store_flash_ee_ctx.line_size); + + while (offset > PAR_STORE_FLASH_EE_HEADER_SIZE) + { + offset -= g_par_store_flash_ee_ctx.record_size; + + status = par_store_flash_ee_port_read(g_par_store_flash_ee_ctx.active_bank_base + offset, + g_par_store_flash_ee_ctx.record_size, + record_buf); + if (ePAR_OK != status) + { + return status; + } + + if (false == par_store_flash_ee_is_valid_commit_unit(&record_buf[commit_offset])) + { + continue; + } + + par_store_flash_ee_parse_record_meta(record_buf, &record_meta); + if ((record_meta.line_index != line_index) || + (record_meta.payload_size != g_par_store_flash_ee_ctx.line_size)) + { + continue; + } + + (void)memcpy(p_line_buf, record_buf, g_par_store_flash_ee_ctx.line_size); + if (par_store_flash_ee_calc_record_crc(p_line_buf, + record_meta.payload_size, + record_meta.line_index) == record_meta.record_crc) + { + return ePAR_OK; + } + } + + return ePAR_OK; +} + +/** + * @brief Resolve one logical line from the current live backend view. + + * + * @details The backend first checks whether the requested line belongs to the + * currently loaded cache window. If yes, the cache line is returned directly + * because it already contains any staged updates. Otherwise the function walks + * the committed append log of the active bank backwards until it finds the + * newest record for the line. + * + * @param[in] line_index Logical line index. + * @param[out] p_line_buf Destination line buffer. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_resolve_live_line(uint32_t line_index, uint8_t *p_line_buf) +{ + uint32_t line_addr = line_index * g_par_store_flash_ee_ctx.line_size; + + if ((true == g_par_store_flash_ee_ctx.cache_valid) && + (line_addr >= g_par_store_flash_ee_ctx.cache_base) && + ((line_addr + g_par_store_flash_ee_ctx.line_size) <= + (g_par_store_flash_ee_ctx.cache_base + g_par_store_flash_ee_ctx.cache_size))) + { + uint32_t cache_offset = line_addr - g_par_store_flash_ee_ctx.cache_base; + (void)memcpy(p_line_buf, &g_par_store_flash_ee_cache[cache_offset], g_par_store_flash_ee_ctx.line_size); + return ePAR_OK; + } + + return par_store_flash_ee_resolve_line_from_bank(line_index, p_line_buf); +} + +/** + * @brief Read bytes directly from the current live backend view. + * + * @details This helper reconstructs bytes line by line without moving the + * active cache window. It is used when a dirty cache window already exists and + * the caller needs to read another logical window without committing pending + * updates. + * + * @param[in] addr Start address inside the logical address space. + * @param[in] size Number of bytes to read. + * @param[out] p_buf Destination buffer. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_read_live_range(uint32_t addr, uint32_t size, uint8_t *p_buf) +{ + par_status_t status = ePAR_OK; + uint8_t line_buf[PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE]; + uint32_t remaining = size; + uint32_t cur_addr = addr; + uint8_t *p_dst = p_buf; + + while (remaining > 0u) + { + const uint32_t line_index = cur_addr / g_par_store_flash_ee_ctx.line_size; + const uint32_t line_offset = cur_addr % g_par_store_flash_ee_ctx.line_size; + uint32_t chunk = g_par_store_flash_ee_ctx.line_size - line_offset; + + status = par_store_flash_ee_resolve_live_line(line_index, line_buf); + if (ePAR_OK != status) + { + return status; + } + + if (chunk > remaining) + { + chunk = remaining; + } + + (void)memcpy(p_dst, &line_buf[line_offset], chunk); + remaining -= chunk; + cur_addr += chunk; + p_dst += chunk; + } + + return ePAR_OK; +} + +/** + * @brief Return the cache-window base that covers one logical address. + * + * @param[in] addr Logical address inside the emulated EEPROM space. + * @return Cache-window base address. + */ +static uint32_t par_store_flash_ee_window_base(uint32_t addr) +{ + return (addr / PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE) * PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE; +} + +/** + * @brief Return true when one logical range stays inside the emulated EEPROM space. + * + * @param[in] addr Start address inside the logical address space. + * @param[in] size Number of bytes in the request. + * @return true when the full range is valid; otherwise false. + */ +static bool par_store_flash_ee_range_is_valid(uint32_t addr, uint32_t size) +{ + if ((0u == size) || (addr >= g_par_store_flash_ee_ctx.logical_size)) + { + return false; + } + + return (size <= (g_par_store_flash_ee_ctx.logical_size - addr)); +} + +/** + * @brief Append one fixed-size line record into the target bank. + * + * @details The function writes the payload and metadata first, leaves the + * dedicated commit unit erased, and then programs that commit unit last. A + * record therefore becomes visible only after the final commit write succeeds. + * + * @param[in] bank_base Bank base offset. + * @param[in,out] p_next_offset Current append offset. Advanced once the + * slot payload and metadata have been programmed, even if the final commit + * unit write later fails. + * @param[in] line_index Logical line index. + * @param[in] p_line_buf Payload bytes for this logical line. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_append_record(uint32_t bank_base, + uint32_t *p_next_offset, + uint32_t line_index, + const uint8_t *p_line_buf) +{ + uint8_t record_buf[PAR_STORE_FLASH_EE_RECORD_BUF_SIZE]; + par_store_flash_ee_record_meta_t record_meta; + const uint32_t meta_offset = par_store_flash_ee_record_meta_offset(); + const uint32_t commit_offset = par_store_flash_ee_record_commit_offset(); + + if ((*p_next_offset + g_par_store_flash_ee_ctx.record_size) > g_par_store_flash_ee_ctx.bank_size) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_CAPACITY); + return ePAR_ERROR_NVM; + } + + (void)memset(record_buf, 0xFF, g_par_store_flash_ee_ctx.record_size); + (void)memcpy(record_buf, p_line_buf, g_par_store_flash_ee_ctx.line_size); + + record_meta.line_index = line_index; + record_meta.payload_size = (uint16_t)g_par_store_flash_ee_ctx.line_size; + record_meta.record_crc = par_store_flash_ee_calc_record_crc(p_line_buf, + (uint16_t)g_par_store_flash_ee_ctx.line_size, + line_index); + record_meta.reserved = 0xFFFFFFFFu; + (void)memcpy(&record_buf[meta_offset], &record_meta, sizeof(record_meta)); + + { + const uint32_t slot_offset = *p_next_offset; + + if (ePAR_OK != par_store_flash_ee_port_program(bank_base + slot_offset, + commit_offset, + record_buf)) + { + return ePAR_ERROR_NVM; + } + + *p_next_offset += g_par_store_flash_ee_ctx.record_size; + + par_store_flash_ee_build_commit_unit(&record_buf[commit_offset]); + if (ePAR_OK != par_store_flash_ee_port_program(bank_base + slot_offset + commit_offset, + g_par_store_flash_ee_ctx.program_size, + &record_buf[commit_offset])) + { + return ePAR_ERROR_NVM; + } + } + + return ePAR_OK; +} + +/** + * @brief Flush the current cache window by appending all dirty lines. + * + * @details The cache window is scanned line by line. Each dirty line is + * converted into one append record and written to the current active bank in + * ascending line order. The cache contents remain valid after the flush, but + * the dirty bitmap is cleared because the committed flash state now matches + * the cached state for this window. + * + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_flush_cache(void) +{ + par_status_t status = ePAR_OK; + uint32_t cache_line = 0u; + + if ((false == g_par_store_flash_ee_ctx.cache_valid) || (false == par_store_flash_ee_has_dirty())) + { + return ePAR_OK; + } + + for (cache_line = 0u; cache_line < (g_par_store_flash_ee_ctx.cache_size / g_par_store_flash_ee_ctx.line_size); ++cache_line) + { + if (false == par_store_flash_ee_is_dirty(cache_line)) + { + continue; + } + + if ((g_par_store_flash_ee_ctx.active_next_offset + g_par_store_flash_ee_ctx.record_size) > g_par_store_flash_ee_ctx.bank_size) + { + return ePAR_ERROR_NVM; + } + + status = par_store_flash_ee_append_record(g_par_store_flash_ee_ctx.active_bank_base, + &g_par_store_flash_ee_ctx.active_next_offset, + (g_par_store_flash_ee_ctx.cache_base / g_par_store_flash_ee_ctx.line_size) + cache_line, + &g_par_store_flash_ee_cache[cache_line * g_par_store_flash_ee_ctx.line_size]); + if (ePAR_OK != status) + { + return status; + } + } + + par_store_flash_ee_clear_dirty(); + return ePAR_OK; +} + +/** + * @brief Load one aligned cache window from the live backend view. + * + * @details The loader only reconstructs the requested window in RAM. It never + * flushes dirty data implicitly. Callers that try to move away from a dirty + * cache window must either sync first or keep reading through the live view + * helper so pending updates stay staged in RAM. + * + * @param[in] window_base Start address of the new cache window. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_load_cache(uint32_t window_base) +{ + par_status_t status = ePAR_OK; + uint32_t line = 0u; + + if ((window_base >= g_par_store_flash_ee_ctx.logical_size) || + (0u != (window_base % g_par_store_flash_ee_ctx.line_size))) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_CACHE_WINDOW); + return ePAR_ERROR_PARAM; + } + + if ((true == g_par_store_flash_ee_ctx.cache_valid) && + (g_par_store_flash_ee_ctx.cache_base == window_base)) + { + return ePAR_OK; + } + + if ((true == g_par_store_flash_ee_ctx.cache_valid) && (true == par_store_flash_ee_has_dirty())) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_CACHE_DIRTY_WINDOW); + return ePAR_ERROR_NVM; + } + + g_par_store_flash_ee_ctx.cache_base = window_base; + g_par_store_flash_ee_ctx.cache_size = g_par_store_flash_ee_ctx.logical_size - window_base; + if (g_par_store_flash_ee_ctx.cache_size > PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE) + { + g_par_store_flash_ee_ctx.cache_size = PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE; + } + + (void)memset(g_par_store_flash_ee_cache, PAR_STORE_FLASH_EE_ERASE_VALUE, g_par_store_flash_ee_ctx.cache_size); + for (line = 0u; line < (g_par_store_flash_ee_ctx.cache_size / g_par_store_flash_ee_ctx.line_size); ++line) + { + status = par_store_flash_ee_resolve_line_from_bank((window_base / g_par_store_flash_ee_ctx.line_size) + line, + &g_par_store_flash_ee_cache[line * g_par_store_flash_ee_ctx.line_size]); + if (ePAR_OK != status) + { + return status; + } + } + + g_par_store_flash_ee_ctx.cache_valid = true; + par_store_flash_ee_clear_dirty(); + return ePAR_OK; +} + +/** + * @brief Ensure the requested cache window is ready for staging. + * + * @details The backend still keeps only one dirty cache window in RAM at a + * time. When write or erase processing needs to move staging to another + * window, this helper synchronizes the current dirty window first and then + * loads the next window. + * + * @param[in] window_base Start address of the required cache window. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_prepare_cache_window(uint32_t window_base) +{ + par_status_t status = ePAR_OK; + + if ((true == g_par_store_flash_ee_ctx.cache_valid) && + (g_par_store_flash_ee_ctx.cache_base == window_base)) + { + return ePAR_OK; + } + + if ((true == g_par_store_flash_ee_ctx.cache_valid) && + (true == par_store_flash_ee_has_dirty()) && + (g_par_store_flash_ee_ctx.cache_base != window_base)) + { + status = par_store_flash_ee_sync(); + if (ePAR_OK != status) + { + return status; + } + } + + return par_store_flash_ee_load_cache(window_base); +} + +/** + * @brief Commit the currently staged cache window when it is dirty. + * + * @details This helper is used at the end of logical write and erase requests + * so a successful backend call never leaves the final modified cache window in + * RAM only. The backend may still synchronize earlier windows while processing + * a multi-window request, but returning @ref ePAR_OK guarantees that the last + * staged window is also durable in flash. Multi-window requests are still not + * transactional: an error reported after an earlier window was synchronized + * does not roll that earlier committed window back. + * + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_commit_staged_cache(void) +{ + if ((false == g_par_store_flash_ee_ctx.cache_valid) || + (false == par_store_flash_ee_has_dirty())) + { + return ePAR_OK; + } + + return par_store_flash_ee_sync(); +} + +/** + * @brief Build a compacted checkpoint inside the inactive bank. + * + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_checkpoint(void) +{ + par_status_t status = ePAR_OK; + uint32_t line_index = 0u; + uint32_t next_offset = PAR_STORE_FLASH_EE_HEADER_SIZE; + uint32_t target_bank = g_par_store_flash_ee_ctx.inactive_bank_base; + uint8_t line_buf[PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE]; + + status = par_store_flash_ee_prepare_bank(target_bank, g_par_store_flash_ee_ctx.active_seq + 1u); + if (ePAR_OK != status) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_CHECKPOINT); + return status; + } + + for (line_index = 0u; line_index < g_par_store_flash_ee_ctx.line_count; ++line_index) + { + status = par_store_flash_ee_resolve_live_line(line_index, line_buf); + if (ePAR_OK != status) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_CHECKPOINT); + return status; + } + + if (true == par_store_flash_ee_is_erased_block(line_buf, g_par_store_flash_ee_ctx.line_size)) + { + continue; + } + + status = par_store_flash_ee_append_record(target_bank, &next_offset, line_index, line_buf); + if (ePAR_OK != status) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_CHECKPOINT); + return status; + } + } + + status = par_store_flash_ee_activate_bank(target_bank); + if (ePAR_OK != status) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_CHECKPOINT); + return status; + } + + g_par_store_flash_ee_ctx.active_bank_base = target_bank; + g_par_store_flash_ee_ctx.inactive_bank_base = (0u == target_bank) ? g_par_store_flash_ee_ctx.bank_size : 0u; + g_par_store_flash_ee_ctx.active_seq += 1u; + g_par_store_flash_ee_ctx.active_next_offset = next_offset; + par_store_flash_ee_clear_dirty(); + PAR_WARN_PRINT("PAR_FLASH_EE: checkpoint complete, seq=%lu, next_offset=%lu", + (unsigned long)g_par_store_flash_ee_ctx.active_seq, + (unsigned long)g_par_store_flash_ee_ctx.active_next_offset); + return ePAR_OK; +} + +/** + * @brief Ensure the active bank has room for at least one more append record. + * + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_ensure_capacity(uint32_t needed_records) +{ + if ((g_par_store_flash_ee_ctx.active_next_offset + (needed_records * g_par_store_flash_ee_ctx.record_size)) <= + g_par_store_flash_ee_ctx.bank_size) + { + return ePAR_OK; + } + + return par_store_flash_ee_checkpoint(); +} + +/** + * @brief Initialize the generic flash-emulated EEPROM backend. + * + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_init(void) +{ + par_status_t status = ePAR_OK; + par_status_t scan_status0 = ePAR_ERROR_NVM; + par_status_t scan_status1 = ePAR_ERROR_NVM; + bool port_is_init = false; + uint32_t seq0 = 0u; + uint32_t seq1 = 0u; + uint32_t next0 = 0u; + uint32_t next1 = 0u; + bool valid0 = false; + bool valid1 = false; + par_store_flash_ee_diag_t scan_diag0 = ePAR_STORE_FLASH_EE_DIAG_NONE; + par_store_flash_ee_diag_t scan_diag1 = ePAR_STORE_FLASH_EE_DIAG_NONE; + + if ((false == g_par_store_flash_ee_ctx.is_bound) || (NULL == g_par_store_flash_ee_ctx.p_port_api)) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_PORT_NOT_BOUND); + return ePAR_ERROR_INIT; + } + + if (true == g_par_store_flash_ee_ctx.is_init) + { + return ePAR_OK; + } + + status = g_par_store_flash_ee_ctx.p_port_api->init(g_par_store_flash_ee_ctx.p_port_ctx); + if (ePAR_OK != status) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_PORT_INIT); + return ePAR_ERROR_INIT; + } + + status = g_par_store_flash_ee_ctx.p_port_api->is_init(g_par_store_flash_ee_ctx.p_port_ctx, &port_is_init); + if ((ePAR_OK != status) || (false == port_is_init)) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_PORT_INIT); + status = ePAR_ERROR_INIT; + goto fail_after_port_init; + } + + g_par_store_flash_ee_ctx.region_size = g_par_store_flash_ee_ctx.p_port_api->get_region_size(g_par_store_flash_ee_ctx.p_port_ctx); + g_par_store_flash_ee_ctx.erase_size = g_par_store_flash_ee_ctx.p_port_api->get_erase_size(g_par_store_flash_ee_ctx.p_port_ctx); + g_par_store_flash_ee_ctx.program_size = g_par_store_flash_ee_ctx.p_port_api->get_program_size(g_par_store_flash_ee_ctx.p_port_ctx); + + status = par_store_flash_ee_validate_geometry(); + if (ePAR_OK != status) + { + goto fail_after_port_init; + } + + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_NONE); + scan_status0 = par_store_flash_ee_scan_bank(par_store_flash_ee_bank_base(0u), &seq0, &next0); + scan_diag0 = g_par_store_flash_ee_ctx.diag; + if (ePAR_OK == scan_status0) + { + valid0 = true; + } + + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_NONE); + scan_status1 = par_store_flash_ee_scan_bank(par_store_flash_ee_bank_base(1u), &seq1, &next1); + scan_diag1 = g_par_store_flash_ee_ctx.diag; + if (ePAR_OK == scan_status1) + { + valid1 = true; + } + + if ((false == valid0) && (false == valid1)) + { + if ((false == par_store_flash_ee_scan_diag_allows_reformat(scan_diag0)) || + (false == par_store_flash_ee_scan_diag_allows_reformat(scan_diag1))) + { + par_store_flash_ee_set_diag((false == par_store_flash_ee_scan_diag_allows_reformat(scan_diag0)) ? scan_diag0 : scan_diag1); + PAR_ERR_PRINT("PAR_FLASH_EE: refusing auto-format after fatal bank scan failure, bank0_diag=%u bank1_diag=%u", + (unsigned)scan_diag0, + (unsigned)scan_diag1); + status = ePAR_ERROR_INIT; + goto fail_after_port_init; + } + + status = par_store_flash_ee_prepare_bank(par_store_flash_ee_bank_base(0u), 1u); + if (ePAR_OK != status) + { + goto fail_after_port_init; + } + + status = par_store_flash_ee_activate_bank(par_store_flash_ee_bank_base(0u)); + if (ePAR_OK != status) + { + goto fail_after_port_init; + } + + g_par_store_flash_ee_ctx.active_bank_base = par_store_flash_ee_bank_base(0u); + g_par_store_flash_ee_ctx.inactive_bank_base = par_store_flash_ee_bank_base(1u); + g_par_store_flash_ee_ctx.active_seq = 1u; + g_par_store_flash_ee_ctx.active_next_offset = PAR_STORE_FLASH_EE_HEADER_SIZE; + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_NONE); + PAR_WARN_PRINT("PAR_FLASH_EE: formatted empty persistence banks"); + } + else if ((true == valid0) && ((false == valid1) || (seq0 >= seq1))) + { + g_par_store_flash_ee_ctx.active_bank_base = par_store_flash_ee_bank_base(0u); + g_par_store_flash_ee_ctx.inactive_bank_base = par_store_flash_ee_bank_base(1u); + g_par_store_flash_ee_ctx.active_seq = seq0; + g_par_store_flash_ee_ctx.active_next_offset = next0; + par_store_flash_ee_set_diag(scan_diag0); + } + else + { + g_par_store_flash_ee_ctx.active_bank_base = par_store_flash_ee_bank_base(1u); + g_par_store_flash_ee_ctx.inactive_bank_base = par_store_flash_ee_bank_base(0u); + g_par_store_flash_ee_ctx.active_seq = seq1; + g_par_store_flash_ee_ctx.active_next_offset = next1; + par_store_flash_ee_set_diag(scan_diag1); + } + + g_par_store_flash_ee_ctx.cache_valid = false; + g_par_store_flash_ee_ctx.is_init = true; + par_store_flash_ee_clear_dirty(); + PAR_DBG_PRINT("PAR_FLASH_EE: init ok, port=%s, bank_size=%lu, logical_size=%lu, line_size=%lu", + (NULL != g_par_store_flash_ee_ctx.p_port_api->get_name) + ? g_par_store_flash_ee_ctx.p_port_api->get_name(g_par_store_flash_ee_ctx.p_port_ctx) + : "unknown", + (unsigned long)g_par_store_flash_ee_ctx.bank_size, + (unsigned long)g_par_store_flash_ee_ctx.logical_size, + (unsigned long)g_par_store_flash_ee_ctx.line_size); + return ePAR_OK; + +fail_after_port_init: + par_store_flash_ee_cleanup_failed_init(); + return status; +} + +/** + * @brief Deinitialize the generic flash-emulated EEPROM backend. + * + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_deinit(void) +{ + par_status_t status = ePAR_OK; + + if ((false == g_par_store_flash_ee_ctx.is_init) || (NULL == g_par_store_flash_ee_ctx.p_port_api)) + { + return ePAR_OK; + } + + status = par_store_flash_ee_sync(); + if (ePAR_OK != status) + { + return status; + } + + status = g_par_store_flash_ee_ctx.p_port_api->deinit(g_par_store_flash_ee_ctx.p_port_ctx); + if (ePAR_OK != status) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_PORT_INIT); + return ePAR_ERROR; + } + + g_par_store_flash_ee_ctx.is_init = false; + g_par_store_flash_ee_ctx.cache_valid = false; + return par_store_flash_ee_complete_status(ePAR_OK); +} + +/** + * @brief Report whether the generic backend is initialized. + * + * @param[out] p_is_init Receives the initialization state. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_is_init(bool *p_is_init) +{ + if (NULL == p_is_init) + { + return ePAR_ERROR_PARAM; + } + + *p_is_init = g_par_store_flash_ee_ctx.is_init; + return ePAR_OK; +} + +/** + * @brief Read bytes from the flash-emulated EEPROM logical address space. + * + * @details The read path is cache-aware. The backend calculates the cache + * window that covers the requested address range, loads that window on demand, + * and then copies bytes from the RAM cache. If another dirty window is already + * staged, reads fall back to the live-view helper so data can be reconstructed + * without committing pending updates. + * + * @param[in] addr Start address inside the logical address space. + * @param[in] size Number of bytes to read. + * @param[out] p_buf Destination buffer. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_read(const uint32_t addr, const uint32_t size, uint8_t *p_buf) +{ + par_status_t status = ePAR_OK; + uint32_t remaining = size; + uint32_t cur_addr = addr; + uint8_t *p_dst = p_buf; + + if ((NULL == p_buf) || (false == par_store_flash_ee_range_is_valid(addr, size))) + { + return ePAR_ERROR_PARAM; + } + + if (false == g_par_store_flash_ee_ctx.is_init) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_NOT_INIT); + return ePAR_ERROR_INIT; + } + + while (remaining > 0u) + { + uint32_t window_base = par_store_flash_ee_window_base(cur_addr); + uint32_t window_offset = cur_addr - window_base; + uint32_t chunk = 0u; + + if ((false == g_par_store_flash_ee_ctx.cache_valid) || (g_par_store_flash_ee_ctx.cache_base != window_base)) + { + if ((true == g_par_store_flash_ee_ctx.cache_valid) && + (true == par_store_flash_ee_has_dirty()) && + (g_par_store_flash_ee_ctx.cache_base != window_base)) + { + chunk = g_par_store_flash_ee_ctx.logical_size - window_base; + if (chunk > PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE) + { + chunk = PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE; + } + chunk -= window_offset; + if (chunk > remaining) + { + chunk = remaining; + } + + status = par_store_flash_ee_read_live_range(cur_addr, chunk, p_dst); + if (ePAR_OK != status) + { + return status; + } + + remaining -= chunk; + cur_addr += chunk; + p_dst += chunk; + continue; + } + + status = par_store_flash_ee_load_cache(window_base); + if (ePAR_OK != status) + { + return status; + } + } + + chunk = g_par_store_flash_ee_ctx.cache_size - window_offset; + if (chunk > remaining) + { + chunk = remaining; + } + + (void)memcpy(p_dst, &g_par_store_flash_ee_cache[window_offset], chunk); + remaining -= chunk; + cur_addr += chunk; + p_dst += chunk; + } + + return par_store_flash_ee_complete_status(ePAR_OK); +} + +/** + * @brief Write bytes into the flash-emulated EEPROM logical address space. + * + * @details The write path never programs flash directly. It loads the cache + * window that covers the target logical address, updates the bytes in RAM, and + * marks all overlapped cache lines dirty. The modified lines are later turned + * into append records during sync. Requests may span multiple cache windows. + * Before staging bytes in a new window, the backend synchronizes the current + * dirty window and then continues with the next chunk. Before returning + * success, the backend also synchronizes the final dirty window so the full + * completed request is durable in flash. Requests that span multiple cache + * windows are not transactional across those windows. + * + * @param[in] addr Start address inside the logical address space. + * @param[in] size Number of bytes to write. + * @param[in] p_buf Source buffer. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_write(const uint32_t addr, const uint32_t size, const uint8_t *p_buf) +{ + par_status_t status = ePAR_OK; + uint32_t remaining = size; + uint32_t cur_addr = addr; + const uint8_t *p_src = p_buf; + + if ((NULL == p_buf) || (false == par_store_flash_ee_range_is_valid(addr, size))) + { + return ePAR_ERROR_PARAM; + } + + if (false == g_par_store_flash_ee_ctx.is_init) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_NOT_INIT); + return ePAR_ERROR_INIT; + } + + while (remaining > 0u) + { + uint32_t window_base = par_store_flash_ee_window_base(cur_addr); + uint32_t window_offset = cur_addr - window_base; + uint32_t chunk = 0u; + uint32_t first_line = 0u; + uint32_t last_line = 0u; + uint32_t line = 0u; + + status = par_store_flash_ee_prepare_cache_window(window_base); + if (ePAR_OK != status) + { + return status; + } + + chunk = g_par_store_flash_ee_ctx.cache_size - window_offset; + if (chunk > remaining) + { + chunk = remaining; + } + + (void)memcpy(&g_par_store_flash_ee_cache[window_offset], p_src, chunk); + + first_line = window_offset / g_par_store_flash_ee_ctx.line_size; + last_line = (window_offset + chunk - 1u) / g_par_store_flash_ee_ctx.line_size; + for (line = first_line; line <= last_line; ++line) + { + par_store_flash_ee_mark_dirty(line); + } + + remaining -= chunk; + cur_addr += chunk; + p_src += chunk; + } + + status = par_store_flash_ee_commit_staged_cache(); + return par_store_flash_ee_complete_status(status); +} + +/** + * @brief Erase bytes inside the flash-emulated EEPROM logical address space. + * + * @details The logical erase path mirrors the write path. The target bytes in + * the RAM cache are set to the erased value and all overlapped cache lines are + * marked dirty so sync can append their new erased image later. Requests may + * span multiple cache windows. Before staging bytes in a new window, the + * backend synchronizes the current dirty window and then continues with the + * next chunk. Before returning success, the backend also synchronizes the + * final dirty window so the full completed request is durable in flash. + * Requests that span multiple cache windows are not transactional across + * those windows. + * + * @param[in] addr Start address inside the logical address space. + * @param[in] size Number of bytes to erase. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_erase(const uint32_t addr, const uint32_t size) +{ + par_status_t status = ePAR_OK; + uint32_t remaining = size; + uint32_t cur_addr = addr; + + if (false == par_store_flash_ee_range_is_valid(addr, size)) + { + return ePAR_ERROR_PARAM; + } + + if (false == g_par_store_flash_ee_ctx.is_init) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_NOT_INIT); + return ePAR_ERROR_INIT; + } + + while (remaining > 0u) + { + uint32_t window_base = par_store_flash_ee_window_base(cur_addr); + uint32_t window_offset = cur_addr - window_base; + uint32_t chunk = 0u; + uint32_t first_line = 0u; + uint32_t last_line = 0u; + uint32_t line = 0u; + + status = par_store_flash_ee_prepare_cache_window(window_base); + if (ePAR_OK != status) + { + return status; + } + + chunk = g_par_store_flash_ee_ctx.cache_size - window_offset; + if (chunk > remaining) + { + chunk = remaining; + } + + (void)memset(&g_par_store_flash_ee_cache[window_offset], PAR_STORE_FLASH_EE_ERASE_VALUE, chunk); + + first_line = window_offset / g_par_store_flash_ee_ctx.line_size; + last_line = (window_offset + chunk - 1u) / g_par_store_flash_ee_ctx.line_size; + for (line = first_line; line <= last_line; ++line) + { + par_store_flash_ee_mark_dirty(line); + } + + remaining -= chunk; + cur_addr += chunk; + } + + status = par_store_flash_ee_commit_staged_cache(); + return par_store_flash_ee_complete_status(status); +} + +/** + * @brief Flush staged cache data into physical flash. + * + * @details Sync first estimates how many append records are needed for the + * current dirty lines. If the active bank does not have enough free space, the + * backend performs a checkpoint into the inactive bank. After capacity is + * guaranteed, each dirty cache line is appended as one new record. + * + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_sync(void) +{ + par_status_t status = ePAR_OK; + + if (false == g_par_store_flash_ee_ctx.is_init) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_NOT_INIT); + return ePAR_ERROR_INIT; + } + + status = par_store_flash_ee_ensure_capacity(par_store_flash_ee_count_dirty()); + if (ePAR_OK != status) + { + return status; + } + + status = par_store_flash_ee_flush_cache(); + if (ePAR_OK != status) + { + if (ePAR_OK == par_store_flash_ee_checkpoint()) + { + status = par_store_flash_ee_flush_cache(); + } + } + + return par_store_flash_ee_complete_status(status); +} + +/** + * @brief Expose the backend operations to the persistence framework. + */ +static const par_store_backend_api_t g_par_store_flash_ee_api = { + .init = par_store_flash_ee_init, + .deinit = par_store_flash_ee_deinit, + .is_init = par_store_flash_ee_is_init, + .read = par_store_flash_ee_read, + .write = par_store_flash_ee_write, + .erase = par_store_flash_ee_erase, + .sync = par_store_flash_ee_sync, + .name = "flash_ee", +}; + +/** + * @brief Bind one physical flash port to the backend core. + * + * @param[in] p_port_api Physical flash port API table. + * @param[in,out] p_port_ctx Mutable port context passed back to the adapter. + * @return ePAR_OK on success, otherwise an error code. + */ +par_status_t par_store_backend_flash_ee_bind_port(const par_store_flash_ee_port_api_t *p_port_api, void *p_port_ctx) +{ + if ((NULL == p_port_api) || (NULL == p_port_api->init) || (NULL == p_port_api->deinit) || + (NULL == p_port_api->is_init) || (NULL == p_port_api->read) || (NULL == p_port_api->program) || + (NULL == p_port_api->erase) || (NULL == p_port_api->get_region_size) || + (NULL == p_port_api->get_erase_size) || (NULL == p_port_api->get_program_size)) + { + return ePAR_ERROR_PARAM; + } + + if ((true == g_par_store_flash_ee_ctx.is_bound) && + (g_par_store_flash_ee_ctx.p_port_api == p_port_api) && + (g_par_store_flash_ee_ctx.p_port_ctx == p_port_ctx)) + { + return ePAR_OK; + } + + g_par_store_flash_ee_ctx.p_port_api = p_port_api; + g_par_store_flash_ee_ctx.p_port_ctx = p_port_ctx; + g_par_store_flash_ee_ctx.is_bound = true; + g_par_store_flash_ee_ctx.is_init = false; + g_par_store_flash_ee_ctx.cache_valid = false; + par_store_flash_ee_clear_dirty(); + return ePAR_OK; +} + +/** + * @brief Return the generic backend API table. + * + * @return Pointer to the backend API table. + */ +const par_store_backend_api_t *par_store_backend_flash_ee_get_api(void) +{ + return &g_par_store_flash_ee_api; +} + +/** + * @brief Return the latest backend diagnostic code. + * + * @return Latest backend diagnostic code. + */ +par_store_flash_ee_diag_t par_store_backend_flash_ee_get_diag(void) +{ + return g_par_store_flash_ee_ctx.diag; +} + +/** + * @brief Return the diagnostic string for one backend diagnostic code. + * + * @param[in] diag Diagnostic code to translate. + * @return Constant string representation. + */ +const char *par_store_backend_flash_ee_get_diag_str(par_store_flash_ee_diag_t diag) +{ + switch (diag) + { + case ePAR_STORE_FLASH_EE_DIAG_NONE: return "none"; + case ePAR_STORE_FLASH_EE_DIAG_PORT_NOT_BOUND: return "port_not_bound"; + case ePAR_STORE_FLASH_EE_DIAG_PORT_INIT: return "port_init"; + case ePAR_STORE_FLASH_EE_DIAG_PORT_READ: return "port_read"; + case ePAR_STORE_FLASH_EE_DIAG_PORT_PROGRAM: return "port_program"; + case ePAR_STORE_FLASH_EE_DIAG_PORT_ERASE: return "port_erase"; + case ePAR_STORE_FLASH_EE_DIAG_PORT_GEOMETRY: return "port_geometry"; + case ePAR_STORE_FLASH_EE_DIAG_CONFIG: return "config"; + case ePAR_STORE_FLASH_EE_DIAG_BANK_ERASED: return "bank_erased"; + case ePAR_STORE_FLASH_EE_DIAG_HEADER_MAGIC: return "header_magic"; + case ePAR_STORE_FLASH_EE_DIAG_HEADER_VERSION: return "header_version"; + case ePAR_STORE_FLASH_EE_DIAG_HEADER_STATE: return "header_state"; + case ePAR_STORE_FLASH_EE_DIAG_HEADER_CRC: return "header_crc"; + case ePAR_STORE_FLASH_EE_DIAG_HEADER_LAYOUT: return "header_layout"; + case ePAR_STORE_FLASH_EE_DIAG_RECORD_MAGIC: return "record_commit"; + case ePAR_STORE_FLASH_EE_DIAG_RECORD_RANGE: return "record_range"; + case ePAR_STORE_FLASH_EE_DIAG_RECORD_CRC: return "record_crc"; + case ePAR_STORE_FLASH_EE_DIAG_RECORD_TAIL: return "record_tail"; + case ePAR_STORE_FLASH_EE_DIAG_CACHE_WINDOW: return "cache_window"; + case ePAR_STORE_FLASH_EE_DIAG_CACHE_DIRTY_WINDOW: return "cache_dirty_window"; + case ePAR_STORE_FLASH_EE_DIAG_CAPACITY: return "capacity"; + case ePAR_STORE_FLASH_EE_DIAG_CHECKPOINT: return "checkpoint"; + case ePAR_STORE_FLASH_EE_DIAG_NOT_INIT: return "not_init"; + default: return "unknown"; + } +} + +#endif /* (1 == PAR_CFG_NVM_EN) && (1 == PAR_CFG_NVM_BACKEND_FLASH_EE_EN) */ diff --git a/src/persist/backend/par_store_backend_flash_ee.h b/src/persist/backend/par_store_backend_flash_ee.h new file mode 100644 index 0000000..29a494a --- /dev/null +++ b/src/persist/backend/par_store_backend_flash_ee.h @@ -0,0 +1,297 @@ +/** + * @file par_store_backend_flash_ee.h + * @brief Declare the generic flash-emulated EEPROM backend core. + * @author wdfk-prog + * @version 1.0 + * @date 2026-04-14 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @details This file declares the portable execution core used by the + * flash-emulated EEPROM backend. The core does not depend on RT-Thread, + * FAL, HAL, or any other platform SDK directly. A platform adapter binds the + * core to one concrete physical flash implementation through the port API + * defined in this header. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-14 1.0 wdfk-prog first version + */ +#ifndef PAR_STORE_BACKEND_FLASH_EE_H +#define PAR_STORE_BACKEND_FLASH_EE_H + +#include +#include + +#include "par.h" +#include "persist/backend/par_store_backend.h" + +#if (1 == PAR_CFG_NVM_EN) && (1 == PAR_CFG_NVM_BACKEND_FLASH_EE_EN) + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Diagnose the latest flash-emulated EEPROM backend event. + * + * @details The backend still returns the package-level @ref par_status_t codes + * to preserve the existing persistence ABI. This diagnostic enumeration keeps a + * more specific reason code for logging, debugging, and later extension. + */ +typedef enum +{ + ePAR_STORE_FLASH_EE_DIAG_NONE = 0, /**< No diagnostic information is pending. */ + ePAR_STORE_FLASH_EE_DIAG_PORT_NOT_BOUND, /**< No physical flash port was bound to the core. */ + ePAR_STORE_FLASH_EE_DIAG_PORT_INIT, /**< Physical port initialization failed. */ + ePAR_STORE_FLASH_EE_DIAG_PORT_READ, /**< Physical flash read failed. */ + ePAR_STORE_FLASH_EE_DIAG_PORT_PROGRAM, /**< Physical flash program failed. */ + ePAR_STORE_FLASH_EE_DIAG_PORT_ERASE, /**< Physical flash erase failed. */ + ePAR_STORE_FLASH_EE_DIAG_PORT_GEOMETRY, /**< Physical flash geometry is invalid or unsupported. */ + ePAR_STORE_FLASH_EE_DIAG_CONFIG, /**< Compile-time configuration is invalid. */ + ePAR_STORE_FLASH_EE_DIAG_BANK_ERASED, /**< Flash bank is fully erased and contains no valid header. */ + ePAR_STORE_FLASH_EE_DIAG_HEADER_MAGIC, /**< Flash bank header magic does not match. */ + ePAR_STORE_FLASH_EE_DIAG_HEADER_VERSION, /**< Flash bank header version is not supported. */ + ePAR_STORE_FLASH_EE_DIAG_HEADER_STATE, /**< Flash bank header state is not active. */ + ePAR_STORE_FLASH_EE_DIAG_HEADER_CRC, /**< Flash bank header CRC is invalid. */ + ePAR_STORE_FLASH_EE_DIAG_HEADER_LAYOUT, /**< Flash bank header geometry does not match the live configuration. */ + ePAR_STORE_FLASH_EE_DIAG_RECORD_MAGIC, /**< Append record commit marker is closed but invalid. */ + ePAR_STORE_FLASH_EE_DIAG_RECORD_RANGE, /**< Append record line index exceeds the logical address space. */ + ePAR_STORE_FLASH_EE_DIAG_RECORD_CRC, /**< Append record payload or semantic metadata CRC is invalid. */ + ePAR_STORE_FLASH_EE_DIAG_RECORD_TAIL, /**< One or more uncommitted append slots were skipped. */ + ePAR_STORE_FLASH_EE_DIAG_CACHE_WINDOW, /**< Cache window alignment or bounds are invalid. */ + ePAR_STORE_FLASH_EE_DIAG_CACHE_DIRTY_WINDOW, /**< A dirty cache window blocks switching to another window before sync. */ + ePAR_STORE_FLASH_EE_DIAG_CAPACITY, /**< Active bank does not have enough capacity. */ + ePAR_STORE_FLASH_EE_DIAG_CHECKPOINT, /**< Bank checkpoint or rollover failed. */ + ePAR_STORE_FLASH_EE_DIAG_NOT_INIT /**< Backend was used before successful initialization. */ +} par_store_flash_ee_diag_t; + +/** + * @brief Describe one concrete physical flash port. + * + * @details All offsets are relative to the beginning of the persistence region + * owned by the backend adapter. The generic core splits that region into two + * banks and never accesses flash outside the supplied region. + * + * @note The backend uses a data-first, commit-last append protocol. Only + * fully committed records are visible during scan and recovery. The backend + * still keeps only one dirty cache window at a time, but write and erase + * operations may span multiple cache windows. When staging moves to a new + * window, the current dirty window is synchronized first, and the final dirty + * window is synchronized before a successful write or erase call returns. + * Multi-window requests are therefore durable on success, but they are not + * transactional across windows when a later step fails. Integrations should + * size the cache so common parameter objects fit within one window, and should + * avoid splitting one must-stay-consistent parameter group across multiple + * independently committed windows. + */ +typedef struct +{ + /** + * @brief Initialize the physical flash port. + * + * @param[in,out] p_ctx Mutable port context owned by the adapter. + * @return ePAR_OK on success, otherwise a package error code. + */ + par_status_t (*init)(void *p_ctx); + + /** + * @brief Deinitialize the physical flash port. + * + * @param[in,out] p_ctx Mutable port context owned by the adapter. + * @return ePAR_OK on success, otherwise a package error code. + */ + par_status_t (*deinit)(void *p_ctx); + + /** + * @brief Report whether the physical flash port is initialized. + * + * @param[in] p_ctx Immutable port context. + * @param[out] p_is_init Receives the initialization state. + * @return ePAR_OK on success, otherwise a package error code. + */ + par_status_t (*is_init)(const void *p_ctx, bool *p_is_init); + + /** + * @brief Read raw bytes from the physical flash region. + * + * @param[in] p_ctx Immutable port context. + * @param[in] addr Byte offset relative to the bound persistence region. + * @param[in] size Number of bytes to read. + * @param[out] p_buf Destination buffer. + * @return ePAR_OK on success, otherwise a package error code. + */ + par_status_t (*read)(const void *p_ctx, uint32_t addr, uint32_t size, uint8_t *p_buf); + + /** + * @brief Program raw bytes into the physical flash region. + * + * @param[in] p_ctx Immutable port context. + * @param[in] addr Byte offset relative to the bound persistence region. + * @param[in] size Number of bytes to program. + * @param[in] p_buf Source buffer. + * @return ePAR_OK on success, otherwise a package error code. + */ + par_status_t (*program)(const void *p_ctx, uint32_t addr, uint32_t size, const uint8_t *p_buf); + + /** + * @brief Erase raw bytes inside the physical flash region. + * + * @param[in] p_ctx Immutable port context. + * @param[in] addr Byte offset relative to the bound persistence region. + * @param[in] size Number of bytes to erase. + * @return ePAR_OK on success, otherwise a package error code. + */ + par_status_t (*erase)(const void *p_ctx, uint32_t addr, uint32_t size); + + /** + * @brief Return the total persistence-region size in bytes. + * + * @param[in] p_ctx Immutable port context. + * @return Region size in bytes. + */ + uint32_t (*get_region_size)(const void *p_ctx); + + /** + * @brief Return the physical flash erase granularity in bytes. + * + * @param[in] p_ctx Immutable port context. + * @return Erase granularity in bytes. + */ + uint32_t (*get_erase_size)(const void *p_ctx); + + /** + * @brief Return the physical flash program granularity in bytes. + * + * @param[in] p_ctx Immutable port context. + * @return Program granularity in bytes. + */ + uint32_t (*get_program_size)(const void *p_ctx); + + /** + * @brief Return a short port name for diagnostics. + * + * @param[in] p_ctx Immutable port context. + * @return Null-terminated name string, or NULL when unavailable. + */ + const char *(*get_name)(const void *p_ctx); +} par_store_flash_ee_port_api_t; + +#if (1 == PAR_CFG_NVM_BACKEND_FLASH_EE_PORT_NATIVE_EN) +/** + * @brief Initialize the user-supplied native flash port. + * + * @details The native adapter expects the application or BSP to provide strong + * definitions for the required operational and geometry hooks declared in this + * header. Missing required definitions should fail at link time rather than + * falling back to runtime stub behavior. The package may still provide benign + * defaults for optional helper hooks such as deinit or diagnostic naming. + * + * @return ePAR_OK on success, otherwise an implementation-defined package error. + */ +par_status_t par_store_flash_ee_native_port_init(void); + +/** + * @brief Deinitialize the user-supplied native flash port. + * + * @return ePAR_OK on success, otherwise an implementation-defined package error. + */ +par_status_t par_store_flash_ee_native_port_deinit(void); + +/** + * @brief Read raw bytes from the user-supplied native flash region. + * + * @param[in] addr Byte offset inside the bound persistence region. + * @param[in] size Number of bytes to read. + * @param[out] p_buf Destination buffer. + * @return ePAR_OK on success, otherwise an implementation-defined package error. + */ +par_status_t par_store_flash_ee_native_port_read(uint32_t addr, uint32_t size, uint8_t *p_buf); + +/** + * @brief Program raw bytes into the user-supplied native flash region. + * + * @param[in] addr Byte offset inside the bound persistence region. + * @param[in] size Number of bytes to program. + * @param[in] p_buf Source buffer. + * @return ePAR_OK on success, otherwise an implementation-defined package error. + */ +par_status_t par_store_flash_ee_native_port_program(uint32_t addr, uint32_t size, const uint8_t *p_buf); + +/** + * @brief Erase raw bytes inside the user-supplied native flash region. + * + * @param[in] addr Byte offset inside the bound persistence region. + * @param[in] size Number of bytes to erase. + * @return ePAR_OK on success, otherwise an implementation-defined package error. + */ +par_status_t par_store_flash_ee_native_port_erase(uint32_t addr, uint32_t size); + +/** + * @brief Return the total size of the user-supplied native flash region. + * + * @return Region size in bytes. + */ +uint32_t par_store_flash_ee_native_port_region_size(void); + +/** + * @brief Return the erase granularity of the user-supplied native flash region. + * + * @return Erase size in bytes. + */ +uint32_t par_store_flash_ee_native_port_erase_size(void); + +/** + * @brief Return the program granularity of the user-supplied native flash region. + * + * @return Program size in bytes. + */ +uint32_t par_store_flash_ee_native_port_program_size(void); + +/** + * @brief Return a short diagnostic name for the user-supplied native flash port. + * + * @return Null-terminated port name string. + */ +const char *par_store_flash_ee_native_port_name(void); +#endif /* (1 == PAR_CFG_NVM_BACKEND_FLASH_EE_PORT_NATIVE_EN) */ + +/** + * @brief Bind one physical flash port to the generic flash-emulated EEPROM core. + * + * @param[in] p_port_api Physical port API table. + * @param[in,out] p_port_ctx Mutable physical port context. + * @return ePAR_OK on success, otherwise a package error code. + */ +par_status_t par_store_backend_flash_ee_bind_port(const par_store_flash_ee_port_api_t *p_port_api, void *p_port_ctx); + +/** + * @brief Return the generic flash-emulated EEPROM backend API. + * + * @return Backend API pointer. + */ +const par_store_backend_api_t *par_store_backend_flash_ee_get_api(void); + +/** + * @brief Return the latest backend diagnostic code. + * + * @return Latest diagnostic code. + */ +par_store_flash_ee_diag_t par_store_backend_flash_ee_get_diag(void); + +/** + * @brief Return a human-readable string for one diagnostic code. + * + * @param[in] diag Diagnostic code. + * @return Constant string representation. + */ +const char *par_store_backend_flash_ee_get_diag_str(par_store_flash_ee_diag_t diag); + +#ifdef __cplusplus +} +#endif + +#endif /* (1 == PAR_CFG_NVM_EN) && (1 == PAR_CFG_NVM_BACKEND_FLASH_EE_EN) */ + +#endif /* PAR_STORE_BACKEND_FLASH_EE_H */ diff --git a/src/persist/backend/par_store_backend_flash_ee_cfg.h b/src/persist/backend/par_store_backend_flash_ee_cfg.h new file mode 100644 index 0000000..d87d383 --- /dev/null +++ b/src/persist/backend/par_store_backend_flash_ee_cfg.h @@ -0,0 +1,72 @@ +/** + * @file par_store_backend_flash_ee_cfg.h + * @brief Provide compile-time configuration defaults for the flash-ee backend. + * @author wdfk-prog + * @version 1.0 + * @date 2026-04-19 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-19 1.0 wdfk-prog first version + */ +#ifndef _PAR_STORE_BACKEND_FLASH_EE_CFG_H_ +#define _PAR_STORE_BACKEND_FLASH_EE_CFG_H_ + +#ifndef PAR_CFG_NVM_BACKEND_FLASH_EE_EN +#define PAR_CFG_NVM_BACKEND_FLASH_EE_EN (0) +#endif + +#ifndef PAR_CFG_NVM_BACKEND_FLASH_EE_LOGICAL_SIZE +#define PAR_CFG_NVM_BACKEND_FLASH_EE_LOGICAL_SIZE (4096u) +#endif + +#ifndef PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE +#define PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE (4096u) +#endif + +#ifndef PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE +#define PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE (32u) +#endif + +#ifndef PAR_CFG_NVM_BACKEND_FLASH_EE_PROGRAM_SIZE +#define PAR_CFG_NVM_BACKEND_FLASH_EE_PROGRAM_SIZE (8u) +#endif + +#ifndef PAR_CFG_NVM_BACKEND_FLASH_EE_PORT_FAL_EN +#define PAR_CFG_NVM_BACKEND_FLASH_EE_PORT_FAL_EN (0) +#endif + +#ifndef PAR_CFG_NVM_BACKEND_FLASH_EE_PORT_NATIVE_EN +#define PAR_CFG_NVM_BACKEND_FLASH_EE_PORT_NATIVE_EN (0) +#endif + +#ifndef PAR_CFG_NVM_BACKEND_FLASH_EE_FAL_PARTITION_NAME +#define PAR_CFG_NVM_BACKEND_FLASH_EE_FAL_PARTITION_NAME "autogen_pm" +#endif + +#ifndef PAR_CFG_NVM_BACKEND_FLASH_EE_VERSION +#define PAR_CFG_NVM_BACKEND_FLASH_EE_VERSION (1u) +#endif + +#if (1 == PAR_CFG_NVM_BACKEND_FLASH_EE_EN) +#if (0u == PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE) +#error "Parameter settings invalid: flash-ee line size must be greater than 0!" +#elif (0u == PAR_CFG_NVM_BACKEND_FLASH_EE_LOGICAL_SIZE) +#error "Parameter settings invalid: flash-ee logical size must be greater than 0!" +#elif (0u != (PAR_CFG_NVM_BACKEND_FLASH_EE_LOGICAL_SIZE % PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE)) +#error "Parameter settings invalid: flash-ee logical size must be an integer multiple of the line size!" +#elif (0u == PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE) +#error "Parameter settings invalid: flash-ee cache size must be greater than 0!" +#elif (0u != (PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE % PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE)) +#error "Parameter settings invalid: flash-ee cache size must be an integer multiple of the line size!" +#elif (0u == PAR_CFG_NVM_BACKEND_FLASH_EE_PROGRAM_SIZE) +#error "Parameter settings invalid: flash-ee program size must be greater than 0!" +#elif (0u != (64u % PAR_CFG_NVM_BACKEND_FLASH_EE_PROGRAM_SIZE)) +#error "Parameter settings invalid: flash-ee program size must divide the 64-byte bank header exactly!" +#endif +#endif + +#endif /* _PAR_STORE_BACKEND_FLASH_EE_CFG_H_ */ diff --git a/src/persist/backend/par_store_backend_gel_nvm.c b/src/persist/backend/par_store_backend_gel_nvm.c index e275f52..2a63550 100644 --- a/src/persist/backend/par_store_backend_gel_nvm.c +++ b/src/persist/backend/par_store_backend_gel_nvm.c @@ -1,18 +1,19 @@ /** * @file par_store_backend_gel_nvm.c * @brief Adapt GeneralEmbeddedCLibraries/nvm to the packaged parameter-storage backend interface. - * @author wdfk-prog () + * @author wdfk-prog * @version 1.0 * @date 2026-03-29 - * - * @copyright Copyright (c) 2026 - * - * @details This adapter is optional. Enable it only when the project includes + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @details This adapter is optional. Enable it only when the project includes * the GeneralEmbeddedCLibraries/nvm module and a valid PAR_CFG_NVM_REGION is * configured for parameter storage. + * @note : * @par Change Log: - * Date Version Author Description - * 2026-03-29 1.0 wdfk-prog first version + * Date Version Author Description + * 2026-03-29 1.0 wdfk-prog first version */ #include "par_cfg.h" #include "persist/backend/par_store_backend.h" @@ -26,16 +27,32 @@ _Static_assert(2 == NVM_VER_MAJOR); _Static_assert(1 <= NVM_VER_MINOR); +/** + * @brief Initialize the GeneralEmbeddedCLibraries NVM backend. + * + * @return ePAR_OK on success, otherwise an initialization error. + */ static par_status_t par_store_gel_init(void) { return (eNVM_OK == nvm_init()) ? ePAR_OK : ePAR_ERROR_INIT; } +/** + * @brief Deinitialize the GeneralEmbeddedCLibraries NVM backend. + * + * @return ePAR_OK on success, otherwise a backend error. + */ static par_status_t par_store_gel_deinit(void) { return (eNVM_OK == nvm_deinit()) ? ePAR_OK : ePAR_ERROR; } +/** + * @brief Report whether the GeneralEmbeddedCLibraries NVM backend is initialized. + * + * @param[out] p_is_init Receives the initialization state. + * @return ePAR_OK on success, otherwise an error code. + */ static par_status_t par_store_gel_is_init(bool * const p_is_init) { if (NULL == p_is_init) @@ -46,6 +63,14 @@ static par_status_t par_store_gel_is_init(bool * const p_is_init) return (eNVM_OK == nvm_is_init(p_is_init)) ? ePAR_OK : ePAR_ERROR_INIT; } +/** + * @brief Read raw bytes from the configured NVM region. + * + * @param[in] addr Byte offset inside the parameter NVM region. + * @param[in] size Number of bytes to read. + * @param[out] p_buf Destination buffer. + * @return ePAR_OK on success, otherwise an NVM error. + */ static par_status_t par_store_gel_read(const uint32_t addr, const uint32_t size, uint8_t * const p_buf) { if (NULL == p_buf) @@ -56,6 +81,14 @@ static par_status_t par_store_gel_read(const uint32_t addr, const uint32_t size, return (eNVM_OK == nvm_read(PAR_CFG_NVM_REGION, addr, size, p_buf)) ? ePAR_OK : ePAR_ERROR_NVM; } +/** + * @brief Write raw bytes into the configured NVM region. + * + * @param[in] addr Byte offset inside the parameter NVM region. + * @param[in] size Number of bytes to write. + * @param[in] p_buf Source buffer. + * @return ePAR_OK on success, otherwise an NVM error. + */ static par_status_t par_store_gel_write(const uint32_t addr, const uint32_t size, const uint8_t * const p_buf) { if (NULL == p_buf) @@ -66,16 +99,43 @@ static par_status_t par_store_gel_write(const uint32_t addr, const uint32_t size return (eNVM_OK == nvm_write(PAR_CFG_NVM_REGION, addr, size, p_buf)) ? ePAR_OK : ePAR_ERROR_NVM; } +/** + * @brief Erase bytes inside the configured NVM region. + * + * @param[in] addr Byte offset inside the parameter NVM region. + * @param[in] size Number of bytes to erase. + * @return ePAR_OK on success, otherwise an NVM error. + */ static par_status_t par_store_gel_erase(const uint32_t addr, const uint32_t size) { return (eNVM_OK == nvm_erase(PAR_CFG_NVM_REGION, addr, size)) ? ePAR_OK : ePAR_ERROR_NVM; } +/** + * @brief Flush pending writes inside the configured NVM region. + * + * @return ePAR_OK on success, otherwise an NVM error. + */ static par_status_t par_store_gel_sync(void) { return (eNVM_OK == nvm_sync(PAR_CFG_NVM_REGION)) ? ePAR_OK : ePAR_ERROR_NVM; } +/** + * @brief Prepare the GeneralEmbeddedCLibraries adapter before API resolution. + * + * @details This backend has no extra bind step, so the function is a no-op. + * + * @return ePAR_OK. + */ +par_status_t par_store_backend_bind(void) +{ + return ePAR_OK; +} + +/** + * @brief Concrete GeneralEmbeddedCLibraries NVM backend API table. + */ static const par_store_backend_api_t g_par_store_backend_gel = { .init = par_store_gel_init, @@ -88,6 +148,11 @@ static const par_store_backend_api_t g_par_store_backend_gel = .name = "gel_nvm", }; +/** + * @brief Return the GeneralEmbeddedCLibraries NVM backend API. + * + * @return Backend API table. + */ const par_store_backend_api_t * par_store_backend_get_api(void) { return &g_par_store_backend_gel; diff --git a/src/persist/fnv.h b/src/persist/fnv.h index f295d7a..6d4bc2a 100644 --- a/src/persist/fnv.h +++ b/src/persist/fnv.h @@ -1,8 +1,16 @@ /** * @file fnv.h - * @brief Minimal FNV declarations required by the bundled hash_32a.c implementation. + * @brief Declare the bundled 32-bit FNV-1a hash helpers used by table-ID support. + * @author wdfk-prog + * @version 1.0 + * @date 2026-04-19 * - * @note This header exists to support the public-domain lcn2/fnv hash_32a.c source. + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-19 1.0 wdfk-prog first version */ #ifndef _PAR_PERSIST_FNV_H_ #define _PAR_PERSIST_FNV_H_ diff --git a/src/persist/hash_32a.c b/src/persist/hash_32a.c index c118eb2..746279a 100644 --- a/src/persist/hash_32a.c +++ b/src/persist/hash_32a.c @@ -1,3 +1,18 @@ +/** + * @file hash_32a.c + * @brief Provide bundled 32-bit FNV-1a hash support for table-ID calculation. + * @author Landon Curt Noll + * @version 1.0 + * @date 2026-04-19 + * + * @copyright Public-domain upstream FNV source bundled with this repository. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-19 1.0 wdfk-prog add project file header for bundled FNV source + */ + /* * hash_32 - 32 bit Fowler/Noll/Vo FNV-1a hash code * @@ -72,7 +87,6 @@ * Share and enjoy! :-) */ - #include #include "fnv.h" diff --git a/src/persist/par_nvm.c b/src/persist/par_nvm.c index 111644e..6b10f59 100644 --- a/src/persist/par_nvm.c +++ b/src/persist/par_nvm.c @@ -10,10 +10,8 @@ * @note : * @par Change Log: * Date Version Author Description - * 2026-01-29 V3.0.1 Ziga Miklosic * 2026-03-30 1.2 wdfk-prog simplify NVM flow and table-ID handling */ - /** * @addtogroup PAR_NVM * @{ @@ -98,20 +96,11 @@ #define PAR_NVM_HEAD_SIZE (PAR_NVM_HEAD_CRC_OFFSET + PAR_NVM_HEAD_CRC_SIZE) #define PAR_NVM_FIRST_DATA_OBJ_ADDR (PAR_NVM_HEAD_ADDR + PAR_NVM_HEAD_SIZE) -/** - * @brief Persisted parameter payload view used by the common load/save flow. - * - * @details - * Live RAM layout and persisted NVM layout are intentionally different. RAM - * storage is grouped by value width, while the persistence area stores a - * compile-time ordered slot list using one selected serialized record layout. - * - * Layout-specific serialization and validation are implemented in dedicated - * layout source files so the top-level NVM flow does not carry per-layout - * branching in every read/write path. - */ /** * @brief Runtime persistence-slot state. + * + * @details The common NVM flow uses this structure to track which compiled + * persistent slots were reconstructed successfully from the serialized image. */ typedef struct { @@ -837,10 +826,11 @@ static uint32_t par_nvm_get_nvm_lut_addr(const par_num_t par_num) */ static par_status_t par_nvm_load_all(const uint16_t num_of_par) { + /* Hold context for one load-path validation failure. */ typedef struct { - const char *reason; - uint16_t stored_id; + const char *reason; /**< Short description of the validation failure. */ + uint16_t stored_id; /**< Stored ID reported by the selected layout, when available. */ } par_nvm_load_error_ctx_t; par_status_t status = ePAR_OK; @@ -996,6 +986,19 @@ static par_status_t par_nvm_load_all(const uint16_t num_of_par) #endif return status; } +/** + * @brief Bind or prepare the active parameter-storage backend. + * + * @details This weak default keeps existing backends source-compatible when + * they do not need an explicit pre-bind step. Backends that must attach one + * concrete port or device context should provide a strong override. + * + * @return ePAR_OK because the default implementation has no work to perform. + */ +PAR_PORT_WEAK par_status_t par_store_backend_bind(void) +{ + return ePAR_OK; +} /** * @brief Resolve, validate, and initialize the mounted storage backend. * @@ -1007,7 +1010,17 @@ static par_status_t par_nvm_init_nvm(void) bool is_nvm_init = false; PAR_DBG_PRINT("PAR_NVM: resolving storage backend"); - gp_store = par_store_backend_get_api(); + status = par_store_backend_bind(); + if (ePAR_OK != status) + { + PAR_ERR_PRINT("PAR_NVM: backend bind failed, err=%u", (unsigned)status); + status = ePAR_ERROR_INIT; + } + + if (ePAR_OK == status) + { + gp_store = par_store_backend_get_api(); + } gp_layout = par_nvm_layout_init(); gb_is_nvm_owner = false; @@ -1265,10 +1278,16 @@ par_status_t par_nvm_deinit(void) * is copied to FLASH. * * @param par_num Parameter enumeration number. - * @param nvm_sync Perform NVM sync after parameter write. When + * @param nvm_sync Request an explicit backend sync after parameter write. When * PAR_CFG_NVM_WRITE_VERIFY_EN is enabled, write verification also * forces a backend sync before the readback step even if this flag is - * false. + * false. Backend implementations may also persist data before this + * function returns when their write contract requires completed writes + * to be durable on success. Therefore false does not mean RAM-only + * staging, and a successful return means the backend-side work needed + * for this request has completed. This flag still does not guarantee + * transactional atomicity across any backend-specific internal + * chunking. * @return Status of operation. */ par_status_t par_nvm_write(const par_num_t par_num, const bool nvm_sync) diff --git a/src/persist/par_nvm.h b/src/persist/par_nvm.h index 7b61477..fda18aa 100644 --- a/src/persist/par_nvm.h +++ b/src/persist/par_nvm.h @@ -10,9 +10,8 @@ * @note : * @par Change Log: * Date Version Author Description - * 2026-01-29 V3.0.1 Ziga Miklosic + * 2026-01-29 V3.0.1 Ziga Miklosic first version */ - /** * @addtogroup PAR_NVM * @{ @@ -46,7 +45,14 @@ par_status_t par_nvm_deinit(void); /** * @brief Persist one parameter to non-volatile storage. * @param par_num Parameter number. - * @param nvm_sync Synchronize the underlying NVM device after the write when true. + * @param nvm_sync Request an explicit backend sync after the write when true. + * Some backends may still persist data before this call returns even + * when this flag is false, as long as the backend-specific API contract + * allows it. A successful call still means that the backend has + * finished the persistence work required by its write contract for this + * request. This flag does not guarantee that false means RAM-only + * staging, and it also does not guarantee transactional atomicity + * across any backend-specific internal chunking. * @return Operation status. */ par_status_t par_nvm_write(const par_num_t par_num, const bool nvm_sync); diff --git a/src/persist/par_nvm_cfg.h b/src/persist/par_nvm_cfg.h new file mode 100644 index 0000000..6bd81ee --- /dev/null +++ b/src/persist/par_nvm_cfg.h @@ -0,0 +1,126 @@ +/** + * @file par_nvm_cfg.h + * @brief Provide compile-time configuration defaults for parameter persistence. + * @author wdfk-prog + * @version 1.0 + * @date 2026-04-19 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-19 1.0 wdfk-prog first version + */ +#ifndef _PAR_NVM_CFG_H_ +#define _PAR_NVM_CFG_H_ + +/** + * @brief Enable/Disable storing persistent parameters to NVM. + * + * @note This switch is also the single compile-time gate for persistence + * metadata in the parameter table. There is no separate PAR_CFG_ENABLE_PERSIST + * override anymore. + */ +#ifndef PAR_CFG_NVM_EN +#define PAR_CFG_NVM_EN (1) +#endif + +/** + * @brief Enable/Disable the legacy GeneralEmbeddedCLibraries/nvm backend. + * + * @note Keep disabled when the application provides an out-of-package storage + * backend, such as the RT-Thread AT24CXX adapter. + */ +#ifndef PAR_CFG_NVM_BACKEND_GEL_EN +#define PAR_CFG_NVM_BACKEND_GEL_EN (0) +#endif + +/** + * @brief Enable/Disable parameter-table compatibility checking. + * + * @note The stored NVM image header carries a table-ID digest that covers + * PAR_CFG_TABLE_ID_SCHEMA_VER, selected record layout, and the stored + * persistent prefix. Self-describing layouts include parameter IDs in that + * digest. Payload-only layouts intentionally exclude external parameter IDs + * and hash only prefix count, persistent order, and parameter type so stored + * prefixes with identical byte layout remain compatible. + * + * When enabled, any persisted-layout incompatibility is treated as a managed + * schema change: startup restores defaults and rebuilds the managed NVM image. + * Layouts with stable prefix addresses allow compatible tail-slot growth when + * the stored prefix still matches the live prefix. The grouped payload-only + * layout is excluded from that repair path and rebuilds on any stored/live + * count mismatch. Payload-only layouts therefore still require the integrator + * to bump PAR_CFG_TABLE_ID_SCHEMA_VER whenever a prefix parameter is + * semantically remapped without changing its serialized byte layout. + * + * @pre "PAR_CFG_NVM_EN" must be enabled, otherwise table-ID checking does. + * not apply. + */ +#ifndef PAR_CFG_TABLE_ID_CHECK_EN +#define PAR_CFG_TABLE_ID_CHECK_EN (0) +#endif + +/** + * @brief Parameter-table ID schema version. + * + * @note Increase this value when the serialized table-ID composition changes. + * The integrator owns this version number and may override it in + * port/par_cfg_port.h before this header provides the default. + */ +#ifndef PAR_CFG_TABLE_ID_SCHEMA_VER +#define PAR_CFG_TABLE_ID_SCHEMA_VER (1U) +#endif + +#include "persist/backend/par_store_backend_flash_ee_cfg.h" + +/** + * @brief Enable/Disable write-path readback verification for persisted data. + * + * @details When enabled, each persisted-record write and each header write are + * followed by a backend sync and a readback verification step. This improves + * reliability at the cost of additional latency and backend traffic. + */ +#ifndef PAR_CFG_NVM_WRITE_VERIFY_EN +#define PAR_CFG_NVM_WRITE_VERIFY_EN (0) +#endif + +/** + * @brief Select persisted record layout. + * + * @note The chosen layout is also included in the table-ID digest so layout + * changes are treated as managed compatibility changes. + */ +#define PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE (0U) +#define PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE (1U) +#define PAR_CFG_NVM_RECORD_LAYOUT_COMPACT_PAYLOAD (2U) +#define PAR_CFG_NVM_RECORD_LAYOUT_FIXED_PAYLOAD_ONLY (3U) +#define PAR_CFG_NVM_RECORD_LAYOUT_GROUPED_PAYLOAD_ONLY (4U) + +#ifndef PAR_CFG_NVM_RECORD_LAYOUT +#define PAR_CFG_NVM_RECORD_LAYOUT (PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE) +#endif + +/** + * @brief Derived layout capability: serialized records store a parameter ID. + */ +#if (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE) || \ + (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE) || \ + (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_COMPACT_PAYLOAD) +#define PAR_CFG_NVM_RECORD_LAYOUT_HAS_STORED_ID (1) +#else +#define PAR_CFG_NVM_RECORD_LAYOUT_HAS_STORED_ID (0) +#endif + +/** + * @brief Derived layout capability: serialized records store a size descriptor. + */ +#if (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE) || \ + (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_COMPACT_PAYLOAD) +#define PAR_CFG_NVM_RECORD_LAYOUT_HAS_SIZE_DESC (1) +#else +#define PAR_CFG_NVM_RECORD_LAYOUT_HAS_SIZE_DESC (0) +#endif + +#endif /* _PAR_NVM_CFG_H_ */ diff --git a/src/persist/par_nvm_layout.h b/src/persist/par_nvm_layout.h index cf5e32f..8fb87b0 100644 --- a/src/persist/par_nvm_layout.h +++ b/src/persist/par_nvm_layout.h @@ -1,7 +1,7 @@ /** * @file par_nvm_layout.h * @brief Declare private persisted-record layout interfaces. - * @author wdfk-prog () + * @author wdfk-prog * @version 1.2 * @date 2026-04-13 * @@ -9,10 +9,10 @@ * * @note : * @par Change Log: - * Date Version Author Description - * 2026-04-06 1.0 wdfk-prog first version - * 2026-04-11 1.1 wdfk-prog split layout structs by selected NVM layout - * 2026-04-13 1.2 wdfk-prog add layout-ops registration for NVM core + * Date Version Author Description + * 2026-04-06 1.0 wdfk-prog first version + * 2026-04-11 1.1 wdfk-prog split layout structs by selected NVM layout + * 2026-04-13 1.2 wdfk-prog add layout-ops registration for NVM core */ #ifndef _PAR_NVM_LAYOUT_H_ #define _PAR_NVM_LAYOUT_H_ diff --git a/src/persist/par_nvm_layout_compact_payload.c b/src/persist/par_nvm_layout_compact_payload.c index cae5c4d..6c36751 100644 --- a/src/persist/par_nvm_layout_compact_payload.c +++ b/src/persist/par_nvm_layout_compact_payload.c @@ -1,7 +1,7 @@ /** * @file par_nvm_layout_compact_payload.c * @brief Implement the compact persisted-record layout with id, size, crc, and payload bytes. - * @author wdfk-prog () + * @author wdfk-prog * @version 1.2 * @date 2026-04-13 * @@ -9,10 +9,10 @@ * * @note : * @par Change Log: - * Date Version Author Description - * 2026-04-06 1.0 wdfk-prog first version - * 2026-04-13 1.1 wdfk-prog add layout-ops adapter - * 2026-04-13 1.2 wdfk-prog auto-generate compile-time compact-payload address LUT + * Date Version Author Description + * 2026-04-06 1.0 wdfk-prog first version + * 2026-04-13 1.1 wdfk-prog add layout-ops adapter + * 2026-04-13 1.2 wdfk-prog auto-generate compile-time compact-payload address LUT */ #include "persist/par_nvm_layout.h" diff --git a/src/persist/par_nvm_layout_fixed_payload_only.c b/src/persist/par_nvm_layout_fixed_payload_only.c index c789f0b..8b3cbfe 100644 --- a/src/persist/par_nvm_layout_fixed_payload_only.c +++ b/src/persist/par_nvm_layout_fixed_payload_only.c @@ -1,7 +1,7 @@ /** * @file par_nvm_layout_fixed_payload_only.c * @brief Implement the fixed persistent-order payload-only NVM layout. - * @author wdfk-prog () + * @author wdfk-prog * @version 1.3 * @date 2026-04-13 * @@ -9,11 +9,11 @@ * * @note : * @par Change Log: - * Date Version Author Description - * 2026-04-10 1.0 wdfk-prog first version - * 2026-04-11 1.1 wdfk-prog restore layout comments and split layout structs - * 2026-04-13 1.2 wdfk-prog add layout-ops adapter - * 2026-04-13 1.3 wdfk-prog auto-generate compile-time fixed-payload-only address LUT + * Date Version Author Description + * 2026-04-10 1.0 wdfk-prog first version + * 2026-04-11 1.1 wdfk-prog restore layout comments and split layout structs + * 2026-04-13 1.2 wdfk-prog add layout-ops adapter + * 2026-04-13 1.3 wdfk-prog auto-generate compile-time fixed-payload-only address LUT */ #include "persist/par_nvm_layout.h" @@ -24,10 +24,6 @@ #include "persist/par_nvm_table_id.h" -#if (1 == PAR_CFG_NVM_BACKEND_FLASH_EN) -#error "Payload-only NVM layouts are not supported with the flash backend because records are variable width and not guaranteed to stay 8-byte aligned." -#endif - #define PAR_NVM_LAYOUT_RECORD_OVERHEAD ((uint32_t)PAR_NVM_RECORD_CRC_SIZE) #define PAR_NVM_LAYOUT_RECORD_MAX_SIZE (PAR_NVM_LAYOUT_RECORD_OVERHEAD + PAR_NVM_RECORD_DATA_SLOT_SIZE) diff --git a/src/persist/par_nvm_layout_fixed_slot_no_size.c b/src/persist/par_nvm_layout_fixed_slot_no_size.c index d5e266b..fa36f97 100644 --- a/src/persist/par_nvm_layout_fixed_slot_no_size.c +++ b/src/persist/par_nvm_layout_fixed_slot_no_size.c @@ -1,7 +1,7 @@ /** * @file par_nvm_layout_fixed_slot_no_size.c * @brief Implement the fixed-slot persisted-record layout without a size descriptor. - * @author wdfk-prog () + * @author wdfk-prog * @version 1.1 * @date 2026-04-13 * @@ -9,9 +9,9 @@ * * @note : * @par Change Log: - * Date Version Author Description - * 2026-04-06 1.0 wdfk-prog first version - * 2026-04-13 1.1 wdfk-prog add layout-ops adapter + * Date Version Author Description + * 2026-04-06 1.0 wdfk-prog first version + * 2026-04-13 1.1 wdfk-prog add layout-ops adapter */ #include "persist/par_nvm_layout.h" @@ -21,10 +21,6 @@ #include "persist/par_nvm_table_id.h" -#if (1 == PAR_CFG_NVM_BACKEND_FLASH_EN) -#error "PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE is not supported with the flash backend because it serializes to 7 bytes." -#endif - #define PAR_NVM_LAYOUT_RECORD_SIZE (PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_CRC_SIZE + (uint32_t)PAR_NVM_RECORD_DATA_SLOT_SIZE) PAR_STATIC_ASSERT(par_nvm_layout_fixed_no_size_payload_slot_is_4_bytes, diff --git a/src/persist/par_nvm_layout_fixed_slot_with_size.c b/src/persist/par_nvm_layout_fixed_slot_with_size.c index 7c6d275..68dc989 100644 --- a/src/persist/par_nvm_layout_fixed_slot_with_size.c +++ b/src/persist/par_nvm_layout_fixed_slot_with_size.c @@ -1,7 +1,7 @@ /** * @file par_nvm_layout_fixed_slot_with_size.c * @brief Implement the fixed-slot persisted-record layout with a size descriptor. - * @author wdfk-prog () + * @author wdfk-prog * @version 1.1 * @date 2026-04-13 * @@ -9,9 +9,9 @@ * * @note : * @par Change Log: - * Date Version Author Description - * 2026-04-06 1.0 wdfk-prog first version - * 2026-04-13 1.1 wdfk-prog add layout-ops adapter + * Date Version Author Description + * 2026-04-06 1.0 wdfk-prog first version + * 2026-04-13 1.1 wdfk-prog add layout-ops adapter */ #include "persist/par_nvm_layout.h" diff --git a/src/persist/par_nvm_layout_grouped_payload_only.c b/src/persist/par_nvm_layout_grouped_payload_only.c index 098b0e2..19a454c 100644 --- a/src/persist/par_nvm_layout_grouped_payload_only.c +++ b/src/persist/par_nvm_layout_grouped_payload_only.c @@ -1,7 +1,7 @@ /** * @file par_nvm_layout_grouped_payload_only.c * @brief Implement the grouped payload-only NVM layout. - * @author wdfk-prog () + * @author wdfk-prog * @version 1.2 * @date 2026-04-13 * @@ -9,10 +9,10 @@ * * @note : * @par Change Log: - * Date Version Author Description - * 2026-04-11 1.0 wdfk-prog first version - * 2026-04-13 1.1 wdfk-prog add layout-ops adapter - * 2026-04-13 1.2 wdfk-prog auto-generate compile-time grouped-payload-only address LUT + * Date Version Author Description + * 2026-04-11 1.0 wdfk-prog first version + * 2026-04-13 1.1 wdfk-prog add layout-ops adapter + * 2026-04-13 1.2 wdfk-prog auto-generate compile-time grouped-payload-only address LUT */ #include "persist/par_nvm_layout.h" @@ -23,10 +23,6 @@ #include "persist/par_nvm_table_id.h" -#if (1 == PAR_CFG_NVM_BACKEND_FLASH_EN) -#error "Payload-only NVM layouts are not supported with the flash backend because records are variable width and not guaranteed to stay 8-byte aligned." -#endif - #define PAR_NVM_LAYOUT_RECORD_OVERHEAD ((uint32_t)PAR_NVM_RECORD_CRC_SIZE) #define PAR_NVM_LAYOUT_RECORD_MAX_SIZE (PAR_NVM_LAYOUT_RECORD_OVERHEAD + PAR_NVM_RECORD_DATA_SLOT_SIZE) diff --git a/src/persist/par_nvm_table_id.c b/src/persist/par_nvm_table_id.c index a035385..2365d80 100644 --- a/src/persist/par_nvm_table_id.c +++ b/src/persist/par_nvm_table_id.c @@ -1,7 +1,7 @@ /** * @file par_nvm_table_id.c * @brief Implement the parameter-table ID hash adapter. - * @author wdfk-prog () + * @author wdfk-prog * @version 1.1 * @date 2026-04-11 * @@ -9,11 +9,10 @@ * * @note : * @par Change Log: - * Date Version Author Description - * 2026-03-30 1.0 wdfk-prog first version - * 2026-04-11 1.1 wdfk-prog add stored-prefix table-ID calculation + * Date Version Author Description + * 2026-03-30 1.0 wdfk-prog first version + * 2026-04-11 1.1 wdfk-prog add stored-prefix table-ID calculation */ - #include "par.h" #include "persist/fnv.h" #include "persist/par_nvm_table_id.h" diff --git a/src/persist/par_nvm_table_id.h b/src/persist/par_nvm_table_id.h index f2f1b9e..0109372 100644 --- a/src/persist/par_nvm_table_id.h +++ b/src/persist/par_nvm_table_id.h @@ -1,7 +1,7 @@ /** * @file par_nvm_table_id.h * @brief Declare the parameter-table ID hash adapter. - * @author wdfk-prog () + * @author wdfk-prog * @version 1.0 * @date 2026-03-30 * @@ -9,8 +9,8 @@ * * @note : * @par Change Log: - * Date Version Author Description - * 2026-03-30 1.0 wdfk-prog first version + * Date Version Author Description + * 2026-03-30 1.0 wdfk-prog first version */ #ifndef _PAR_NVM_TABLE_ID_H_ #define _PAR_NVM_TABLE_ID_H_ diff --git a/src/port/par_atomic.h b/src/port/par_atomic.h index 8a29008..c37a3c9 100644 --- a/src/port/par_atomic.h +++ b/src/port/par_atomic.h @@ -1,7 +1,7 @@ /** * @file par_atomic.h * @brief Define atomic helper types and macros for parameter storage. - * @author wdfk-prog () + * @author wdfk-prog * @version 1.0 * @date 2026-03-27 * @@ -9,10 +9,9 @@ * * @note : * @par Change Log: - * Date Version Author Description - * 2026-03-27 1.0 wdfk-prog first version + * Date Version Author Description + * 2026-03-27 1.0 wdfk-prog first version */ - #ifndef PAR_ATOMIC_H #define PAR_ATOMIC_H diff --git a/src/port/par_if.c b/src/port/par_if.c index b0c16b6..32bb640 100644 --- a/src/port/par_if.c +++ b/src/port/par_if.c @@ -10,9 +10,8 @@ * @note : * @par Change Log: * Date Version Author Description - * 2026-01-29 V3.0.1 Ziga Miklosic + * 2026-01-29 V3.0.1 Ziga Miklosic first version */ - /** * @addtogroup PAR_IF * @{ diff --git a/src/port/par_if.h b/src/port/par_if.h index 117a535..49c7427 100644 --- a/src/port/par_if.h +++ b/src/port/par_if.h @@ -10,9 +10,8 @@ * @note : * @par Change Log: * Date Version Author Description - * 2026-01-29 V3.0.1 Ziga Miklosic + * 2026-01-29 V3.0.1 Ziga Miklosic first version */ - /** * @addtogroup PAR_IF * @{ From 815883d58c0c213902e115d5f496f8e0a9705175 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Wed, 22 Apr 2026 11:43:19 +0800 Subject: [PATCH 36/36] feat[parameters]: add role policy metadata and access helpers extend par_table.def rows with read_roles/write_roles and add role policy config add role-aware metadata helpers and explicit access capability checks sync package documentation with the updated access and role policy behavior --- README.md | 23 +- docs/api-reference.md | 22 +- docs/architecture.md | 6 +- docs/getting-started.md | 17 +- src/def/par_def.c | 221 ++++++++++-------- src/def/par_def.h | 30 ++- src/def/par_id_map_static.c | 4 +- src/detail/par_storage_init.inc | 16 +- src/detail/par_typed_impl.inc | 8 +- src/par.c | 105 ++++++++- src/par.h | 95 ++++++-- src/par_cfg.h | 8 + src/persist/par_nvm.c | 2 +- src/persist/par_nvm_layout_compact_payload.c | 28 +-- .../par_nvm_layout_fixed_payload_only.c | 28 +-- .../par_nvm_layout_grouped_payload_only.c | 84 ++++--- template/par_cfg_port.htmp | 16 +- template/par_table.deftmp | 214 ++++++++--------- 18 files changed, 586 insertions(+), 341 deletions(-) diff --git a/README.md b/README.md index fbf32c3..d6e12a6 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ It is designed for projects that need a clean way to: - **Single source of truth** through `par_table.def` - **Typed APIs** for `U8`, `I8`, `U16`, `I16`, `U32`, `I32`, and, when enabled, `F32` -- **Optional metadata** such as name, unit, description, access, ID, and persistence flags +- **Optional metadata** such as name, unit, description, access, ID, persistence flags, and optional role-based read/write visibility metadata - **Validation pipeline** with compile-time checks for integer ranges and optional runtime hooks for dynamic rules - **Static live-value storage** grouped by width instead of heap allocation - **Fast external lookup by ID** through a compile-time generated static hash map @@ -31,7 +31,7 @@ It is designed for projects that need a clean way to: 3. Provide `port/par_cfg_port.h` in your include path. 4. Optionally provide `port/par_if_port.c` and `port/par_atomic_port.h` when your platform needs them. 5. Call `par_init()` before using runtime APIs. -6. Use the typed `par_set_*` / `par_get_*` APIs in application code. Getter APIs now use an explicit output pointer and return `par_status_t`. +6. Use the typed `par_set_*` / `par_get_*` APIs in application code. Getter APIs now use an explicit output pointer and return `par_status_t`. When `PAR_CFG_ENABLE_ACCESS = 1`, checked public read APIs also reject parameters that do not expose external read capability. A minimal example: @@ -57,6 +57,25 @@ static void app_init(void) } ``` + +## Optional role-policy metadata + +When `PAR_CFG_ENABLE_ROLE_POLICY = 1`, each row in `par_table.def` also carries: + +- `read_roles_` +- `write_roles_` + +These fields are **metadata and helper-policy inputs**, not a built-in login/session system. +The package exposes `par_can_read()` / `par_can_write()` so the integration layer +(such as msh, CLI, RPC, or diagnostic service code) can decide which caller roles +are currently active and enforce them consistently. + +The core checked value APIs still consume only the access capability bits (`ePAR_ACCESS_READ` / `ePAR_ACCESS_WRITE`). +Role masks are evaluated only when the integration calls `par_can_read()` / `par_can_write()` or wraps them in a higher-level public interface such as the packaged RT-Thread shell port. +Metadata getters and `par_get_default()` continue to read the configuration table directly and therefore do not consume role-policy metadata. + +`msh info` and `msh json` also show the configured role metadata when this option is enabled. In the packaged RT-Thread shell port, `par get`, `par set`, and the live-value fields printed by `par info` / `par json` are all filtered through the current shell role mask. + ## Documentation map - [Getting started](docs/getting-started.md) for integration steps, required files, and first-use examples diff --git a/docs/api-reference.md b/docs/api-reference.md index 7ae6893..36b3b66 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -37,7 +37,7 @@ The module conditionally compiles parts of the API based on configuration. ## Status notes -- `ePAR_ERROR_ACCESS` indicates that a checked public setter rejected a write to an `ePAR_ACCESS_RO` parameter. +- `ePAR_ERROR_ACCESS` indicates that a checked public value API rejected the requested access. Checked setters reject targets without `ePAR_ACCESS_WRITE`; checked getters reject targets without `ePAR_ACCESS_READ`. - Warning bit values were shifted to make room for the new access-denied error bit. ## Lifecycle @@ -61,7 +61,7 @@ These are relevant only when mutex support is enabled in the integration. | Function | Description | | --- | --- | -| `par_set(par_num, p_val)` | Set a parameter from a typed pointer. This public setter path enforces access policy and returns `ePAR_ERROR_ACCESS` when the target parameter is externally read-only. | +| `par_set(par_num, p_val)` | Set a parameter from a typed pointer. This checked public write path returns `ePAR_ERROR_ACCESS` when `PAR_CFG_ENABLE_ACCESS = 1` and the target lacks `ePAR_ACCESS_WRITE`. | | `par_set_fast(par_num, p_val)` | Set a parameter from a typed pointer through the unchecked fast path. This API resolves the runtime type and then dispatches to the matching `par_set_xxx_fast()` implementation. | | `par_set_by_id(id, p_val)` | Set a parameter using its external ID. This path resolves the ID to `par_num_t` and then uses the same checked setter flow as `par_set()`. | @@ -89,7 +89,7 @@ These are relevant only when mutex support is enabled in the integration. | `par_set_i32()` | Set an `I32` parameter. | | `par_set_f32()` | Set an `F32` parameter. Available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. | -Normal typed setters are the canonical checked setter path. They enforce access policy and may include runtime validation callbacks and on-change callbacks when the matching configuration options are enabled. +Normal typed setters are the canonical checked setter path. When `PAR_CFG_ENABLE_ACCESS = 1`, they enforce external write capability and return `ePAR_ERROR_ACCESS` when the target does not expose `ePAR_ACCESS_WRITE`. They may also include runtime validation callbacks and on-change callbacks when the matching configuration options are enabled. ## Fast setters @@ -144,8 +144,8 @@ These reset APIs are different from startup initialization: | Function | Description | | --- | --- | -| `par_get(par_num, p_val)` | Read a parameter into a typed destination pointer. | -| `par_get_by_id(id, p_val)` | Read a parameter using its external ID. | +| `par_get(par_num, p_val)` | Read a parameter into a typed destination pointer. This checked public read path returns `ePAR_ERROR_ACCESS` when `PAR_CFG_ENABLE_ACCESS = 1` and the target lacks `ePAR_ACCESS_READ`. | +| `par_get_by_id(id, p_val)` | Read a parameter using its external ID. After ID resolution, this uses the same checked public read flow as `par_get()`. | Typed getter macros are removed. Call the typed getter functions directly and always check the returned status. @@ -160,7 +160,9 @@ Typed getter macros are removed. Call the typed getter functions directly and al | `par_get_u32(par_num, p_val)` | Read a `U32` parameter into `*p_val`. Returns status. | | `par_get_i32(par_num, p_val)` | Read an `I32` parameter into `*p_val`. Returns status. | | `par_get_f32(par_num, p_val)` | Read an `F32` parameter into `*p_val`. Available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. Returns status. | -| `par_get_default(par_num, p_val)` | Read the configured default value for a parameter. | +| `par_get_default(par_num, p_val)` | Read the configured default value for a parameter from the metadata table. This API does not consume runtime read capability metadata. | + +Checked typed getters are the canonical public read path. When `PAR_CFG_ENABLE_ACCESS = 1`, they enforce external read capability and return `ePAR_ERROR_ACCESS` when the target does not expose `ePAR_ACCESS_READ`. ## Metadata access @@ -174,11 +176,17 @@ These APIs do not follow the same runtime usage pattern as the value access APIs | `par_get_unit(par_num)` | Return the engineering unit when unit metadata is enabled. | | `par_get_desc(par_num)` | Return the description string when description metadata is enabled. | | `par_get_type(par_num)` | Return the parameter type enum. | -| `par_get_access(par_num)` | Return read-only or read-write access metadata when enabled. Public checked setter APIs consume this metadata to enforce write access. | +| `par_get_access(par_num)` | Return the external access capability bit mask when enabled. Checked public value getters use its read bit; checked public setters use its write bit. | +| `par_get_read_roles(par_num)` | Return the configured read-role bit set when role policy metadata is enabled. | +| `par_get_write_roles(par_num)` | Return the configured write-role bit set when role policy metadata is enabled. | +| `par_can_read(par_num, roles)` | Test the supplied caller role bits (`par_role_t`) against the parameter read-role metadata. When access metadata is enabled, read capability must also be present. | +| `par_can_write(par_num, roles)` | Test the supplied caller role bits (`par_role_t`) against the parameter write-role metadata. When access metadata is enabled, write capability must also be present. | | `par_is_persistent(par_num)` | Return whether the parameter is marked persistent when enabled. | | `par_get_num_by_id(id, p_par_num)` | Convert an external ID to `par_num_t` through the compile-time generated static ID map. This metadata API does not require `par_init()`. | | `par_get_id_by_num(par_num, p_id)` | Convert `par_num_t` to external ID. | +The role-policy option does not own a global login/session state inside the core. Integrators are expected to supply the current caller role bits (`par_role_t`) from their own CLI, service, or transport context when calling `par_can_read()` / `par_can_write()`. Core value getters/setters do not implicitly consume role masks unless an integration layer chooses to wrap those APIs with role-aware policy checks. + ## NVM APIs Available only when `PAR_CFG_NVM_EN = 1` and a concrete parameter-storage backend is linked. diff --git a/docs/architecture.md b/docs/architecture.md index 8ebfdb2..dad13a3 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -225,9 +225,11 @@ This keeps runtime lookup simple and deterministic, but it also means a conflict ### Access enforcement boundary -`par_get_access()` remains metadata, but the metadata is now consumed by the checked setter core. Public write entry points enforce `ePAR_ACCESS_RW` and reject writes to `ePAR_ACCESS_RO` with `ePAR_ERROR_ACCESS`. +`par_get_access()` remains metadata, but the metadata is now consumed by the checked public value-access boundary. Public write entry points (`par_set()`, `par_set_by_id()`, and typed setters) enforce external write capability and reject targets without `ePAR_ACCESS_WRITE` with `ePAR_ERROR_ACCESS`. Public read entry points (`par_get()`, `par_get_by_id()`, and typed getters) enforce external read capability and reject targets without `ePAR_ACCESS_READ` with the same status. -Fast setters and internal restore paths intentionally remain outside that checked boundary. They are used for startup/default/NVM restore flows where the firmware must rehydrate trusted values without invoking public access-control policy. +Fast setters and internal restore paths intentionally remain outside that checked boundary. They are used for startup/default/NVM restore flows where the firmware must rehydrate trusted values without invoking public access-control policy. Metadata getters and `par_get_default()` also remain outside that boundary because they read the configuration table rather than the external-facing live-value path. + +Role-policy metadata is intentionally one layer higher. The core exposes `par_can_read()` / `par_can_write()` helper predicates, but it does not own login/session state or automatically apply role masks inside the generic value getters/setters. Integrators such as CLI, RPC, or diagnostic transports decide when to combine caller-role context with the access-capability bits. ### Hash geometry and collision rule diff --git a/docs/getting-started.md b/docs/getting-started.md index 72ed5a4..462f7bb 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -15,7 +15,7 @@ This guide shows how to integrate the `Device Parameters` module into a firmware - raw reset-all support - `F32` parameter support 5. Call `par_init()` before runtime access. -6. Use the typed APIs. Getter calls take an explicit output pointer and return `par_status_t`. +6. Use the typed APIs. Getter calls take an explicit output pointer and return `par_status_t`. When `PAR_CFG_ENABLE_ACCESS = 1`, checked public read APIs also return `ePAR_ERROR_ACCESS` for parameters without external read capability. ## Required files @@ -29,6 +29,7 @@ Each row defines one parameter and is reused to build: - the parameter configuration table - compile-time integer validation checks - compile-time storage counts +- optional role-based metadata columns used by `par_can_read()` / `par_can_write()` A minimal example: @@ -42,6 +43,8 @@ PAR_ITEM_U8 ( 0U, NULL, ePAR_ACCESS_RW, + ePAR_ROLE_ALL, + ePAR_ROLE_ALL, true, "Application operating mode" ) @@ -55,6 +58,8 @@ PAR_ITEM_F32( 25.0f, "degC", ePAR_ACCESS_RW, + ePAR_ROLE_ALL, + ePAR_ROLE_ALL, true, "Requested control target temperature" ) @@ -62,6 +67,8 @@ PAR_ITEM_F32( Use `template/par_table.deftmp` as the starting point. +The `read_roles_` / `write_roles_` columns are part of the fixed `PAR_ITEM_*` row signature even when role policy is disabled. If you keep role policy disabled, use `ePAR_ROLE_ALL` / `ePAR_ROLE_NONE` as neutral placeholders in the extra role columns, or keep the template defaults and leave enforcement to the access bit only. + This example requires `PAR_CFG_ENABLE_TYPE_F32 = 1`. If `F32` support is disabled, remove all `PAR_ITEM_F32(...)` rows from `par_table.def`. ### `port/par_cfg_port.h` @@ -433,13 +440,13 @@ src/par_cfg.h:160:53: note: in expansion of macro 'PAR_PORT_STATIC_ASSERT' 160 | #define PAR_STATIC_ASSERT(name, expn) PAR_PORT_STATIC_ASSERT(name, expn); | ^~~~~~~~~~~~~~~~~~~~~~ src/def/par_def.c:73:94: note: in expansion of macro 'PAR_STATIC_ASSERT' - 73 | #define PAR_CHECK_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_STATIC_ASSERT(enum_##_f32_type_is_disabled__remove_PAR_ITEM_F32, 0) + 73 | #define PAR_CHECK_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) PAR_STATIC_ASSERT(enum_##_f32_type_is_disabled__remove_PAR_ITEM_F32, 0) | ^~~~~~~~~~~~~~~~~ src/def/par_def.c:85:23: note: in expansion of macro 'PAR_CHECK_F32' 85 | #define PAR_ITEM_F32 PAR_CHECK_F32 | ^~~~~~~~~~~~~ par_table.def:189:1: note: in expansion of macro 'PAR_ITEM_F32' - 189 | PAR_ITEM_F32(ePAR_SYS_CPU_LOAD_MAX, 10011, "CPU Max. load", 0.0f, 100.0f, 0.0f, "%", ePAR_ACCESS_RO, false, "Maximum CPU load in %") + 189 | PAR_ITEM_F32(ePAR_SYS_CPU_LOAD_MAX, 10011, "CPU Max. load", 0.0f, 100.0f, 0.0f, "%", ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Maximum CPU load in %") | ^~~~~~~~~~~~ ``` @@ -457,7 +464,7 @@ Example: ```log par_table.def: In function 'par_compile_check_hash_bucket_collision': src/def/par_def.c:156:105: error: duplicate case value - 156 | #define PAR_CHECK_ID_BUCKET_CASE(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) case PAR_HASH_ID_CONST(id_): break; + 156 | #define PAR_CHECK_ID_BUCKET_CASE(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) case PAR_HASH_ID_CONST(id_): break; | ^~~~ src/def/par_def.c:162:31: note: in expansion of macro 'PAR_CHECK_ID_BUCKET_CASE' 162 | #define PAR_ITEM_U16 PAR_CHECK_ID_BUCKET_CASE @@ -466,7 +473,7 @@ par_table.def:141:1: note: in expansion of macro 'PAR_ITEM_U16' 141 | PAR_ITEM_U16(ePAR_CH3_VOL_RAW, 253, "Ch3 Raw Vout", ...) | ^~~~~~~~~~~~ src/def/par_def.c:156:105: note: previously used here - 156 | #define PAR_CHECK_ID_BUCKET_CASE(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) case PAR_HASH_ID_CONST(id_): break; + 156 | #define PAR_CHECK_ID_BUCKET_CASE(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) case PAR_HASH_ID_CONST(id_): break; | ^~~~ src/def/par_def.c:167:31: note: in expansion of macro 'PAR_CHECK_ID_BUCKET_CASE' 167 | #define PAR_ITEM_F32 PAR_CHECK_ID_BUCKET_CASE diff --git a/src/def/par_def.c b/src/def/par_def.c index 221bddb..ee96e92 100644 --- a/src/def/par_def.c +++ b/src/def/par_def.c @@ -41,21 +41,21 @@ /** * @brief Compile-time checks for each parameter value type. * @details Signature: - * (enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_). + * (enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_). */ -#define PAR_CHECK_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) -#define PAR_CHECK_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) -#define PAR_CHECK_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) -#define PAR_CHECK_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) -#define PAR_CHECK_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) -#define PAR_CHECK_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +#define PAR_CHECK_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +#define PAR_CHECK_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +#define PAR_CHECK_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +#define PAR_CHECK_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +#define PAR_CHECK_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +#define PAR_CHECK_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) #else -#define PAR_CHECK_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) -#define PAR_CHECK_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) -#define PAR_CHECK_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) -#define PAR_CHECK_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) -#define PAR_CHECK_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) -#define PAR_CHECK_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#define PAR_CHECK_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) +#define PAR_CHECK_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) +#define PAR_CHECK_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) +#define PAR_CHECK_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) +#define PAR_CHECK_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) +#define PAR_CHECK_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) #endif /** * @brief NOTE: F32 range checks are runtime-only. @@ -64,9 +64,9 @@ * "variably modified '_static_assert_...' at file scope". */ #if (1 == PAR_CFG_ENABLE_TYPE_F32) -#define PAR_CHECK_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#define PAR_CHECK_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) #else -#define PAR_CHECK_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_STATIC_ASSERT(enum_##_f32_type_is_disabled__remove_PAR_ITEM_F32, 0) +#define PAR_CHECK_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) PAR_STATIC_ASSERT(enum_##_f32_type_is_disabled__remove_PAR_ITEM_F32, 0) #endif /** @@ -107,8 +107,8 @@ * * @note Duplicate ID values trigger duplicated "case" labels. */ -#define PAR_CHECK_ID_DUPLICATE_CASE(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ - case ((uint32_t)(id_)): \ +#define PAR_CHECK_ID_DUPLICATE_CASE(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) \ + case ((uint32_t)(id_)): \ break; static void par_compile_check_duplicate_ids(void) { @@ -143,8 +143,8 @@ static void par_compile_check_duplicate_ids(void) * This check intentionally fails the build early by generating duplicated. * "case" labels for colliding bucket indices. */ -#define PAR_CHECK_ID_BUCKET_CASE(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ - case PAR_HASH_ID_CONST(id_): \ +#define PAR_CHECK_ID_BUCKET_CASE(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) \ + case PAR_HASH_ID_CONST(id_): \ break; static void par_compile_check_hash_bucket_collision(void) { @@ -205,7 +205,7 @@ PAR_STATIC_ASSERT(par_compile_check_hash_bucket_collision_ref, (sizeof(&par_comp /** * @brief X-Macro table initializers for each parameter value type. * @details Signature: - * (enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_). + * (enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_). */ #if (1 == PAR_CFG_ENABLE_ID) #define PAR_INIT_ID(id_) .id = (uint16_t)(id_), @@ -249,6 +249,14 @@ PAR_STATIC_ASSERT(par_compile_check_hash_bucket_collision_ref, (sizeof(&par_comp #define PAR_INIT_ACCESS(access_) #endif +#if (1 == PAR_CFG_ENABLE_ROLE_POLICY) +#define PAR_INIT_READ_ROLES(read_roles_) .read_roles = (read_roles_), +#define PAR_INIT_WRITE_ROLES(write_roles_) .write_roles = (write_roles_), +#else +#define PAR_INIT_READ_ROLES(read_roles_) +#define PAR_INIT_WRITE_ROLES(write_roles_) +#endif + #if (1 == PAR_CFG_NVM_EN) /** * @brief Translate the X-Macro persistence column into a stored persist slot index. @@ -282,96 +290,111 @@ PAR_STATIC_ASSERT(par_compile_check_hash_bucket_collision_ref, (sizeof(&par_comp #define PAR_INIT_DESC(desc_) #endif -#define PAR_INIT_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ - [enum_] = { \ - PAR_INIT_ID(id_) \ - PAR_INIT_NAME(name_) \ - PAR_INIT_RANGE_U8(min_, max_) \ - .def.u8 = (uint8_t)(def_), \ - PAR_INIT_UNIT(unit_) \ - .type = ePAR_TYPE_U8, \ - PAR_INIT_ACCESS(access_) \ - PAR_INIT_PERSIST(enum_, pers_) \ - PAR_INIT_DESC(desc_) \ +#define PAR_INIT_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_U8(min_, max_) \ + .def.u8 = (uint8_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_U8, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_READ_ROLES(read_roles_) \ + PAR_INIT_WRITE_ROLES(write_roles_) \ + PAR_INIT_PERSIST(enum_, pers_) \ + PAR_INIT_DESC(desc_) \ + }, + +#define PAR_INIT_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_U16(min_, max_) \ + .def.u16 = (uint16_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_U16, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_READ_ROLES(read_roles_) \ + PAR_INIT_WRITE_ROLES(write_roles_) \ + PAR_INIT_PERSIST(enum_, pers_) \ + PAR_INIT_DESC(desc_) \ }, -#define PAR_INIT_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ - [enum_] = { \ - PAR_INIT_ID(id_) \ - PAR_INIT_NAME(name_) \ - PAR_INIT_RANGE_U16(min_, max_) \ - .def.u16 = (uint16_t)(def_), \ - PAR_INIT_UNIT(unit_) \ - .type = ePAR_TYPE_U16, \ - PAR_INIT_ACCESS(access_) \ - PAR_INIT_PERSIST(enum_, pers_) \ - PAR_INIT_DESC(desc_) \ +#define PAR_INIT_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_U32(min_, max_) \ + .def.u32 = (uint32_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_U32, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_READ_ROLES(read_roles_) \ + PAR_INIT_WRITE_ROLES(write_roles_) \ + PAR_INIT_PERSIST(enum_, pers_) \ + PAR_INIT_DESC(desc_) \ }, -#define PAR_INIT_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ - [enum_] = { \ - PAR_INIT_ID(id_) \ - PAR_INIT_NAME(name_) \ - PAR_INIT_RANGE_U32(min_, max_) \ - .def.u32 = (uint32_t)(def_), \ - PAR_INIT_UNIT(unit_) \ - .type = ePAR_TYPE_U32, \ - PAR_INIT_ACCESS(access_) \ - PAR_INIT_PERSIST(enum_, pers_) \ - PAR_INIT_DESC(desc_) \ +#define PAR_INIT_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_I8(min_, max_) \ + .def.i8 = (int8_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_I8, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_READ_ROLES(read_roles_) \ + PAR_INIT_WRITE_ROLES(write_roles_) \ + PAR_INIT_PERSIST(enum_, pers_) \ + PAR_INIT_DESC(desc_) \ }, -#define PAR_INIT_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ - [enum_] = { \ - PAR_INIT_ID(id_) \ - PAR_INIT_NAME(name_) \ - PAR_INIT_RANGE_I8(min_, max_) \ - .def.i8 = (int8_t)(def_), \ - PAR_INIT_UNIT(unit_) \ - .type = ePAR_TYPE_I8, \ - PAR_INIT_ACCESS(access_) \ - PAR_INIT_PERSIST(enum_, pers_) \ - PAR_INIT_DESC(desc_) \ +#define PAR_INIT_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_I16(min_, max_) \ + .def.i16 = (int16_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_I16, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_READ_ROLES(read_roles_) \ + PAR_INIT_WRITE_ROLES(write_roles_) \ + PAR_INIT_PERSIST(enum_, pers_) \ + PAR_INIT_DESC(desc_) \ }, -#define PAR_INIT_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ - [enum_] = { \ - PAR_INIT_ID(id_) \ - PAR_INIT_NAME(name_) \ - PAR_INIT_RANGE_I16(min_, max_) \ - .def.i16 = (int16_t)(def_), \ - PAR_INIT_UNIT(unit_) \ - .type = ePAR_TYPE_I16, \ - PAR_INIT_ACCESS(access_) \ - PAR_INIT_PERSIST(enum_, pers_) \ - PAR_INIT_DESC(desc_) \ +#define PAR_INIT_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_I32(min_, max_) \ + .def.i32 = (int32_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_I32, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_READ_ROLES(read_roles_) \ + PAR_INIT_WRITE_ROLES(write_roles_) \ + PAR_INIT_PERSIST(enum_, pers_) \ + PAR_INIT_DESC(desc_) \ }, -#define PAR_INIT_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ - [enum_] = { \ - PAR_INIT_ID(id_) \ - PAR_INIT_NAME(name_) \ - PAR_INIT_RANGE_I32(min_, max_) \ - .def.i32 = (int32_t)(def_), \ - PAR_INIT_UNIT(unit_) \ - .type = ePAR_TYPE_I32, \ - PAR_INIT_ACCESS(access_) \ - PAR_INIT_PERSIST(enum_, pers_) \ - PAR_INIT_DESC(desc_) \ +#define PAR_INIT_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_F32(min_, max_) \ + .def.f32 = (float32_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_F32, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_READ_ROLES(read_roles_) \ + PAR_INIT_WRITE_ROLES(write_roles_) \ + PAR_INIT_PERSIST(enum_, pers_) \ + PAR_INIT_DESC(desc_) \ }, -#define PAR_INIT_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ - [enum_] = { \ - PAR_INIT_ID(id_) \ - PAR_INIT_NAME(name_) \ - PAR_INIT_RANGE_F32(min_, max_) \ - .def.f32 = (float32_t)(def_), \ - PAR_INIT_UNIT(unit_) \ - .type = ePAR_TYPE_F32, \ - PAR_INIT_ACCESS(access_) \ - PAR_INIT_PERSIST(enum_, pers_) \ - PAR_INIT_DESC(desc_) \ - }, /**< Dispatch map for table initialization. */ #define PAR_ITEM_U8 PAR_INIT_U8 #define PAR_ITEM_U16 PAR_INIT_U16 #define PAR_ITEM_U32 PAR_INIT_U32 @@ -422,7 +445,7 @@ static const par_cfg_t g_par_table[ePAR_NUM_OF] = { /** * @brief Configuration-independent compile-time parameter-ID table. */ -#define PAR_ITEM_ID_VALUE(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) [enum_] = (uint16_t)(id_), +#define PAR_ITEM_ID_VALUE(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) [enum_] = (uint16_t)(id_), static const uint16_t g_par_id_table[ePAR_NUM_OF] = { #define PAR_ITEM_U8 PAR_ITEM_ID_VALUE #define PAR_ITEM_U16 PAR_ITEM_ID_VALUE diff --git a/src/def/par_def.h b/src/def/par_def.h index cb4a976..b1bf927 100644 --- a/src/def/par_def.h +++ b/src/def/par_def.h @@ -36,11 +36,29 @@ extern "C" { * PAR_PERSISTENT_COMPILE_COUNT. */ typedef struct par_cfg_s par_cfg_t; +/** + * @brief Centralized argument extractors for par_table.def X-Macro rows. + * @details Keep row-signature changes localized here. Consumers can use + * variadic wrappers plus these selectors instead of repeating the full row + * signature in every macro definition. + */ +#define PAR_XARG_ENUM(enum_, ...) enum_ +#define PAR_XARG_ID(_enum, id_, ...) id_ +#define PAR_XARG_NAME(_enum, _id, name_, ...) name_ +#define PAR_XARG_MIN(_enum, _id, _name, min_, ...) min_ +#define PAR_XARG_MAX(_enum, _id, _name, _min, max_, ...) max_ +#define PAR_XARG_DEF(_enum, _id, _name, _min, _max, def_, ...) def_ +#define PAR_XARG_UNIT(_enum, _id, _name, _min, _max, _def, unit_, ...) unit_ +#define PAR_XARG_ACCESS(_enum, _id, _name, _min, _max, _def, _unit, access_, ...) access_ +#define PAR_XARG_READ_ROLES(_enum, _id, _name, _min, _max, _def, _unit, _access, read_roles_, ...) read_roles_ +#define PAR_XARG_WRITE_ROLES(_enum, _id, _name, _min, _max, _def, _unit, _access, _read_roles, write_roles_, ...) write_roles_ +#define PAR_XARG_PERS(_enum, _id, _name, _min, _max, _def, _unit, _access, _read_roles, _write_roles, pers_, ...) pers_ +#define PAR_XARG_DESC(_enum, _id, _name, _min, _max, _def, _unit, _access, _read_roles, _write_roles, _pers, desc_) desc_ /** * @brief List of device parameters. * @note Must be started with 0! @note Enum expansion is intentionally configuration-independent: PAR_ITEM_F32 always maps to PAR_ITEM_ENUM. F32 enable/disable fail-fast is enforced in par_def.c. */ -#define PAR_ITEM_ENUM(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) enum_, +#define PAR_ITEM_ENUM(...) PAR_XARG_ENUM(__VA_ARGS__), enum { #define PAR_ITEM_U8 PAR_ITEM_ENUM @@ -72,8 +90,8 @@ typedef uint16_t par_num_t; * @brief Compile-time storage group counts derived from par_table.def. * @note These constants are used by layout and static storage allocation. */ -#define PAR_ITEM_COUNT_ONE(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +1u -#define PAR_ITEM_COUNT_ZERO(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +0u +#define PAR_ITEM_COUNT_ONE(...) +1u +#define PAR_ITEM_COUNT_ZERO(...) +0u enum { @@ -146,11 +164,11 @@ enum * resulting PAR_PERSIST_IDX_ constants are dense and ordered exactly as * the source table. */ -#define PAR_PERSIST_ENUM_SELECT(enum_, pers_) PAR_PERSIST_ENUM_SELECT_I(enum_, pers_) +#define PAR_PERSIST_ENUM_SELECT(enum_, pers_) PAR_PERSIST_ENUM_SELECT_I(enum_, pers_) #define PAR_PERSIST_ENUM_SELECT_I(enum_, pers_) PAR_PERSIST_ENUM_SELECT_##pers_(enum_) -#define PAR_PERSIST_ENUM_SELECT_1(enum_) PAR_PERSIST_IDX_##enum_, +#define PAR_PERSIST_ENUM_SELECT_1(enum_) PAR_PERSIST_IDX_##enum_, #define PAR_PERSIST_ENUM_SELECT_0(enum_) -#define PAR_ITEM_PERSIST_ENUM(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_PERSIST_ENUM_SELECT(enum_, pers_) +#define PAR_ITEM_PERSIST_ENUM(...) PAR_PERSIST_ENUM_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) enum { #define PAR_ITEM_U8 PAR_ITEM_PERSIST_ENUM diff --git a/src/def/par_id_map_static.c b/src/def/par_id_map_static.c index f9a2f45..00a5d07 100644 --- a/src/def/par_id_map_static.c +++ b/src/def/par_id_map_static.c @@ -16,8 +16,8 @@ #if (1 == PAR_CFG_ENABLE_ID) -#define PAR_ID_MAP_ITEM(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) \ - [PAR_HASH_ID_CONST(id_)] = { .id = (uint16_t)(id_), .par_num = (enum_), .used = 1u }, +#define PAR_ID_MAP_ITEM(...) \ + [PAR_HASH_ID_CONST(PAR_XARG_ID(__VA_ARGS__))] = { .id = (uint16_t)(PAR_XARG_ID(__VA_ARGS__)), .par_num = (PAR_XARG_ENUM(__VA_ARGS__)), .used = 1u }, const par_id_map_entry_t g_par_id_map_static[PAR_ID_HASH_SIZE] = { #define PAR_ITEM_U8 PAR_ID_MAP_ITEM diff --git a/src/detail/par_storage_init.inc b/src/detail/par_storage_init.inc index 3020e6f..3c05dd4 100644 --- a/src/detail/par_storage_init.inc +++ b/src/detail/par_storage_init.inc @@ -18,15 +18,15 @@ * object and preserves the internal U8/U16/U32 width-group layout. */ -#define PAR_STORAGE_INIT_NOP(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) -#define PAR_STORAGE_U8_FROM_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u8_t)((uint8_t)(def_)), -#define PAR_STORAGE_U8_FROM_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u8_t)((uint8_t)(int8_t)(def_)), -#define PAR_STORAGE_U16_FROM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u16_t)((uint16_t)(def_)), -#define PAR_STORAGE_U16_FROM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u16_t)((uint16_t)(int16_t)(def_)), -#define PAR_STORAGE_U32_FROM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u32_t)((uint32_t)(def_)), -#define PAR_STORAGE_U32_FROM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u32_t)((uint32_t)(int32_t)(def_)), +#define PAR_STORAGE_INIT_NOP(...) +#define PAR_STORAGE_U8_FROM_U8(...) (par_atomic_u8_t)((uint8_t)(PAR_XARG_DEF(__VA_ARGS__))), +#define PAR_STORAGE_U8_FROM_I8(...) (par_atomic_u8_t)((uint8_t)(int8_t)(PAR_XARG_DEF(__VA_ARGS__))), +#define PAR_STORAGE_U16_FROM_U16(...) (par_atomic_u16_t)((uint16_t)(PAR_XARG_DEF(__VA_ARGS__))), +#define PAR_STORAGE_U16_FROM_I16(...) (par_atomic_u16_t)((uint16_t)(int16_t)(PAR_XARG_DEF(__VA_ARGS__))), +#define PAR_STORAGE_U32_FROM_U32(...) (par_atomic_u32_t)((uint32_t)(PAR_XARG_DEF(__VA_ARGS__))), +#define PAR_STORAGE_U32_FROM_I32(...) (par_atomic_u32_t)((uint32_t)(int32_t)(PAR_XARG_DEF(__VA_ARGS__))), #if (1 == PAR_CFG_ENABLE_TYPE_F32) -#define PAR_STORAGE_U32_FROM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) (par_atomic_u32_t)(0u), +#define PAR_STORAGE_U32_FROM_F32(...) (par_atomic_u32_t)(0u), #endif static par_storage_groups_t gs_par_storage = { diff --git a/src/detail/par_typed_impl.inc b/src/detail/par_typed_impl.inc index 8146820..06e6713 100644 --- a/src/detail/par_typed_impl.inc +++ b/src/detail/par_typed_impl.inc @@ -245,9 +245,9 @@ static par_status_t par_set_checked_core(const par_num_t par_num, const par_type } #if (1 == PAR_CFG_ENABLE_ACCESS) - if (ePAR_ACCESS_RW != par_cfg->access) + if (false == par_access_has_write(par_cfg->access)) { - PAR_INFO_PRINT("PAR: write denied by access policy, par_num=%u", (unsigned)par_num); + PAR_INFO_PRINT("PAR: write denied by access capability, par_num=%u", (unsigned)par_num); return ePAR_ERROR_ACCESS; } #endif @@ -342,6 +342,10 @@ PAR_TYPED_ROWS(PAR_TYPED_IMPL_FAST_SETTER) status = par_validate_expected_type(par_cfg, ETYPE); \ if (ePAR_OK != status) \ return status; \ +#if (1 == PAR_CFG_ENABLE_ACCESS) \ + if (false == par_access_has_read(par_cfg->access)) \ + return ePAR_ERROR_ACCESS; \ +#endif \ *p_val = PAR_GET_##PRIV##_PRIV(par_num); \ return ePAR_OK; \ } diff --git a/src/par.c b/src/par.c index fed7675..a124944 100644 --- a/src/par.c +++ b/src/par.c @@ -170,10 +170,43 @@ static const char *gs_status[] = { * @brief Function declarations. */ static par_status_t par_set_checked_core(const par_num_t par_num, const par_type_list_t expected_type, const par_type_t * const p_typed_val, const void * const p_ptr_val); +#if (1 == PAR_CFG_ENABLE_ACCESS) +static bool par_access_has_read(const par_access_t access); +static bool par_access_has_write(const par_access_t access); +#endif +#if (1 == PAR_CFG_ENABLE_ROLE_POLICY) +static bool par_roles_are_valid(const par_role_t roles); +#endif /** * @brief Function declarations and definitions. */ +#if (1 == PAR_CFG_ENABLE_ACCESS) +/** + * @brief Return whether the access mask contains read capability. + */ +static bool par_access_has_read(const par_access_t access) +{ + return (0U != ((uint32_t)access & (uint32_t)ePAR_ACCESS_READ)); +} + +/** + * @brief Return whether the access mask contains write capability. + */ +static bool par_access_has_write(const par_access_t access) +{ + return (0U != ((uint32_t)access & (uint32_t)ePAR_ACCESS_WRITE)); +} +#endif +#if (1 == PAR_CFG_ENABLE_ROLE_POLICY) +/** + * @brief Validate that a role mask contains only supported role bits. + */ +static bool par_roles_are_valid(const par_role_t roles) +{ + return (0U == ((uint32_t)roles & (uint32_t)(~((uint32_t)ePAR_ROLE_ALL)))); +} +#endif #if (1 == PAR_CFG_ENABLE_DESC) && (1 == PAR_CFG_ENABLE_DESC_CHECK) /** * @brief Validate parameter description string. @@ -1179,7 +1212,7 @@ par_type_list_t par_get_type(const par_num_t par_num) return ePAR_TYPE_NUM_OF; } /** - * @brief Get parameter access (RO, RW). + * @brief Get parameter external capability mask. * * @param par_num Parameter number (enumeration). * @return Parameter access. @@ -1194,8 +1227,76 @@ par_access_t par_get_access(const par_num_t par_num) return par_cfg->access; } - return ePAR_ACCESS_RO; + return ePAR_ACCESS_NONE; +} +#endif +#if (1 == PAR_CFG_ENABLE_ROLE_POLICY) +par_role_t par_get_read_roles(const par_num_t par_num) +{ + const par_cfg_t *par_cfg = NULL; + + if (ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) + { + return par_cfg->read_roles; + } + + return ePAR_ROLE_NONE; +} + +par_role_t par_get_write_roles(const par_num_t par_num) +{ + const par_cfg_t *par_cfg = NULL; + + if (ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) + { + return par_cfg->write_roles; + } + + return ePAR_ROLE_NONE; +} + +bool par_can_read(const par_num_t par_num, const par_role_t roles) +{ + const par_cfg_t *par_cfg = NULL; + + if ((false == par_roles_are_valid(roles)) || + (ePAR_OK != par_resolve_metadata(par_num, NULL, false, &par_cfg)) || + (NULL == par_cfg)) + { + return false; + } + +#if (1 == PAR_CFG_ENABLE_ACCESS) + if (false == par_access_has_read(par_cfg->access)) + { + return false; + } +#endif + + return (0U != ((uint32_t)par_cfg->read_roles & (uint32_t)roles)); +} + +bool par_can_write(const par_num_t par_num, const par_role_t roles) +{ + const par_cfg_t *par_cfg = NULL; + + if ((false == par_roles_are_valid(roles)) || + (ePAR_OK != par_resolve_metadata(par_num, NULL, false, &par_cfg)) || + (NULL == par_cfg)) + { + return false; + } + +#if (1 == PAR_CFG_ENABLE_ACCESS) + if (false == par_access_has_write(par_cfg->access)) + { + return false; + } +#endif + + return (0U != ((uint32_t)par_cfg->write_roles & (uint32_t)roles)); } + #endif /** * @brief Is parameter persistent (does it store to NVM). diff --git a/src/par.h b/src/par.h index bf06b95..55902be 100644 --- a/src/par.h +++ b/src/par.h @@ -40,7 +40,7 @@ /** * @brief Parameter status. */ -enum +typedef enum { ePAR_OK = 0U, /**< Normal operation. */ @@ -55,7 +55,7 @@ enum ePAR_ERROR_VALUE = 0x0040U, /**< Invalid parameter value (validation failed). */ ePAR_ERROR_PARAM = 0x0080U, /**< Invalid function argument. */ ePAR_ERROR_PAR_NUM = 0x0100U, /**< Invalid parameter number. */ - ePAR_ERROR_ACCESS = 0x0200U, /**< Write access denied by parameter access policy. */ + ePAR_ERROR_ACCESS = 0x0200U, /**< Access denied by parameter access policy. */ ePAR_ERROR_TABLE_ID = 0x0400U, /**< Stored parameter-table ID does not match the live table. */ /* Warning status bits. */ @@ -64,12 +64,11 @@ enum ePAR_WAR_NVM_REWRITTEN = 0x1000U, /**< NVM parameters area completely re-written. */ ePAR_WAR_NO_PERSISTENT = 0x2000U, /**< No persistent parameters -> set PAR_CFG_NVM_EN to 0. */ ePAR_WAR_LIMITED = 0x4000U, /**< Parameter value limited within [min,max]. */ -}; -typedef uint16_t par_status_t; +} par_status_t; /** * @brief Parameters type enumeration. */ -enum +typedef enum { ePAR_TYPE_U8 = 0, /**< Unsigned 8-bit value. */ ePAR_TYPE_U16, /**< Unsigned 16-bit value. */ @@ -79,17 +78,32 @@ enum ePAR_TYPE_I32, /**< Signed 32-bit value. */ ePAR_TYPE_F32, /**< 32-bit floating value. */ ePAR_TYPE_NUM_OF -}; -typedef uint8_t par_type_list_t; +} par_type_list_t; /** - * @brief Parameter R/W access. + * @brief Parameter read/write capability bit mask. */ -enum +typedef enum { - ePAR_ACCESS_RO = 0, /**< Parameter read only. */ - ePAR_ACCESS_RW /**< Parameter read/write. */ -}; -typedef uint8_t par_access_t; + ePAR_ACCESS_NONE = 0U, /**< No external read/write capability. */ + ePAR_ACCESS_READ = (1U << 0), /**< External read capability. */ + ePAR_ACCESS_WRITE = (1U << 1), /**< External write capability. */ + ePAR_ACCESS_RO = ePAR_ACCESS_READ, /**< Parameter read only. */ + ePAR_ACCESS_RW = (ePAR_ACCESS_READ | ePAR_ACCESS_WRITE), /**< Parameter read/write. */ +} par_access_t; +#if (1 == PAR_CFG_ENABLE_ROLE_POLICY) +/** + * @brief External role-mask bits used by optional parameter role policy. + */ +typedef enum +{ + ePAR_ROLE_NONE = 0U, /**< No external role granted. */ + ePAR_ROLE_PUBLIC = (1U << 0), /**< End-user/public role. */ + ePAR_ROLE_SERVICE = (1U << 1), /**< Service/maintenance role. */ + ePAR_ROLE_DEVELOPER = (1U << 2), /**< Developer/debug role. */ + ePAR_ROLE_MANUFACTURING = (1U << 3), /**< Manufacturing/production role. */ + ePAR_ROLE_ALL = (ePAR_ROLE_PUBLIC | ePAR_ROLE_SERVICE | ePAR_ROLE_DEVELOPER | ePAR_ROLE_MANUFACTURING), /**< All external roles. */ +} par_role_t; +#endif /** * @brief 32-bit floating data type definition. */ @@ -142,7 +156,11 @@ typedef struct par_cfg_s #endif par_type_list_t type; /**< Parameter type. */ #if (1 == PAR_CFG_ENABLE_ACCESS) - par_access_t access; /**< Parameter access from external device point-of-view. */ + par_access_t access; /**< External access capability mask. */ +#endif +#if (1 == PAR_CFG_ENABLE_ROLE_POLICY) + par_role_t read_roles; /**< External roles allowed to read the parameter. */ + par_role_t write_roles; /**< External roles allowed to write the parameter. */ #endif #if (1 == PAR_CFG_NVM_EN) bool persistent; /**< Parameter persistence flag. */ @@ -472,11 +490,19 @@ par_status_t par_has_changed(const par_num_t par_num, bool * const p_has_changed /** * @brief Getting parameter value API (module must be first initialized before using those func). */ +/** + * @brief Checked public value-read APIs. + * @details `par_get()`, `par_get_by_id()`, and the typed `par_get_xxx()` family enforce + * external read capability when `PAR_CFG_ENABLE_ACCESS = 1`. Metadata getters and + * `par_get_default()` read the configuration table directly and therefore remain outside + * that runtime access-check boundary. + */ /** * @brief Read one parameter into a typed output pointer. * @param par_num Parameter number. * @param p_val Pointer to the output value. - * @return Operation status. + * @return Operation status. Returns `ePAR_ERROR_ACCESS` when access metadata is enabled + * and the target parameter does not expose external read capability. */ par_status_t par_get(const par_num_t par_num, void * const p_val); #if (1 == PAR_CFG_ENABLE_ID) @@ -484,7 +510,8 @@ par_status_t par_get(const par_num_t par_num, void * const p_val); * @brief Read one parameter by external parameter ID. * @param id External parameter ID. * @param p_val Pointer to the output value. - * @return Operation status. + * @return Operation status. Returns `ePAR_ERROR_ACCESS` when access metadata is enabled + * and the target parameter does not expose external read capability. */ par_status_t par_get_by_id(const uint16_t id, void * const p_val); #endif @@ -540,7 +567,7 @@ par_status_t par_get_i32(const par_num_t par_num, int32_t * const p_val); par_status_t par_get_f32(const par_num_t par_num, float32_t * const p_val); #endif /** - * @brief Read the configured default value for one parameter. + * @brief Read the configured default value for one parameter from the metadata table. * @param par_num Parameter number. * @param p_val Pointer to the output value. * @return Operation status. @@ -575,15 +602,39 @@ const char *par_get_desc(const par_num_t par_num); par_type_list_t par_get_type(const par_num_t par_num); #if (1 == PAR_CFG_ENABLE_ACCESS) /** - * @brief Return parameter access metadata used by public setter access enforcement. - * @details when PAR_CFG_ENABLE_ACCESS = 1. + * @brief Return the configured external read/write capability mask for one parameter. + * @param par_num Parameter number. + * @return Configured external capability mask. + */ +par_access_t par_get_access(const par_num_t par_num); +#endif +#if (1 == PAR_CFG_ENABLE_ROLE_POLICY) +/** + * @brief Return the configured external read-role mask for one parameter. + * @param par_num Parameter number. + * @return Configured read-role mask. */ +par_role_t par_get_read_roles(const par_num_t par_num); /** - * @brief Return the configured external access policy for one parameter. + * @brief Return the configured external write-role mask for one parameter. * @param par_num Parameter number. - * @return Configured access policy. + * @return Configured write-role mask. */ -par_access_t par_get_access(const par_num_t par_num); +par_role_t par_get_write_roles(const par_num_t par_num); +/** + * @brief Test whether a caller role mask may read one parameter. + * @param par_num Parameter number. + * @param roles Caller role mask. + * @return True when read is allowed; otherwise false. + */ +bool par_can_read(const par_num_t par_num, const par_role_t roles); +/** + * @brief Test whether a caller role mask may write one parameter. + * @param par_num Parameter number. + * @param roles Caller role mask. + * @return True when write is allowed; otherwise false. + */ +bool par_can_write(const par_num_t par_num, const par_role_t roles); #endif #if (1 == PAR_CFG_NVM_EN) /** diff --git a/src/par_cfg.h b/src/par_cfg.h index 29bcd87..35119ec 100644 --- a/src/par_cfg.h +++ b/src/par_cfg.h @@ -480,6 +480,13 @@ PAR_STATIC_ASSERT(par_id_hash_bits_valid, ((PAR_ID_HASH_BITS > 0u) && (PAR_ID_HA #define PAR_CFG_ENABLE_ACCESS (1) #endif +/** + * @brief Enable/Disable optional external role-policy metadata and enforcement. + */ +#ifndef PAR_CFG_ENABLE_ROLE_POLICY +#define PAR_CFG_ENABLE_ROLE_POLICY (0) +#endif + /** * @brief Enable/Disable description check. * @@ -505,6 +512,7 @@ PAR_STATIC_ASSERT(par_id_hash_bits_valid, ((PAR_ID_HASH_BITS > 0u) && (PAR_ID_HA #error "Parameter settings invalid: payload-only NVM layouts require PAR_CFG_TABLE_ID_CHECK_EN = 1!" #endif + #if (0 == PAR_CFG_ENABLE_ID) && (1 == PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK) #error "Parameter settings invalid: runtime duplicate-ID diagnostics require PAR_CFG_ENABLE_ID = 1!" #endif diff --git a/src/persist/par_nvm.c b/src/persist/par_nvm.c index 6b10f59..051483a 100644 --- a/src/persist/par_nvm.c +++ b/src/persist/par_nvm.c @@ -126,7 +126,7 @@ typedef struct #define PAR_PERSIST_SLOT_ENTRY_SELECT_false(enum_) #define PAR_PERSIST_SLOT_ENTRY_SELECT_1(enum_) [PAR_PERSIST_IDX_##enum_] = enum_, #define PAR_PERSIST_SLOT_ENTRY_SELECT_0(enum_) -#define PAR_ITEM_PERSIST_SLOT(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_PERSIST_SLOT_ENTRY_SELECT(enum_, pers_) +#define PAR_ITEM_PERSIST_SLOT(...) PAR_PERSIST_SLOT_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) /** * @brief Compile-time mapping from persistent slot to live parameter number. * diff --git a/src/persist/par_nvm_layout_compact_payload.c b/src/persist/par_nvm_layout_compact_payload.c index 6c36751..0a1e183 100644 --- a/src/persist/par_nvm_layout_compact_payload.c +++ b/src/persist/par_nvm_layout_compact_payload.c @@ -72,13 +72,13 @@ static uint32_t par_nvm_layout_compact_payload_record_size_from_par_num(const pa typedef struct { uint8_t base__[PAR_NVM_LAYOUT_COMPACT_PAYLOAD_OFFSET_MAP_BASE_SIZE]; -#define PAR_ITEM_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_1) -#define PAR_ITEM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_2) -#define PAR_ITEM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_4) -#define PAR_ITEM_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_1) -#define PAR_ITEM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_2) -#define PAR_ITEM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_4) -#define PAR_ITEM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_4) +#define PAR_ITEM_U8(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_1) +#define PAR_ITEM_U16(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_2) +#define PAR_ITEM_U32(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_4) +#define PAR_ITEM_I8(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_1) +#define PAR_ITEM_I16(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_2) +#define PAR_ITEM_I32(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_4) +#define PAR_ITEM_F32(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_4) #include "../../par_table.def" #undef PAR_ITEM_U8 #undef PAR_ITEM_U16 @@ -94,13 +94,13 @@ typedef struct #define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT_1(enum_) [PAR_PERSIST_IDX_##enum_] = PAR_NVM_LAYOUT_COMPACT_PAYLOAD_OFFSET_OF(enum_), #define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT_0(enum_) static const uint32_t g_par_nvm_layout_compact_payload_addr_lut[PAR_PERSIST_SLOT_MAP_CAPACITY] = { -#define PAR_ITEM_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(enum_, pers_) -#define PAR_ITEM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(enum_, pers_) -#define PAR_ITEM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(enum_, pers_) -#define PAR_ITEM_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(enum_, pers_) -#define PAR_ITEM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(enum_, pers_) -#define PAR_ITEM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(enum_, pers_) -#define PAR_ITEM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(enum_, pers_) +#define PAR_ITEM_U8(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_U16(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_U32(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_I8(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_I16(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_I32(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_F32(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) #include "../../par_table.def" #undef PAR_ITEM_U8 #undef PAR_ITEM_U16 diff --git a/src/persist/par_nvm_layout_fixed_payload_only.c b/src/persist/par_nvm_layout_fixed_payload_only.c index 8b3cbfe..bd109af 100644 --- a/src/persist/par_nvm_layout_fixed_payload_only.c +++ b/src/persist/par_nvm_layout_fixed_payload_only.c @@ -73,13 +73,13 @@ static uint32_t par_nvm_layout_fixed_payload_only_record_size_from_par_num(const typedef struct { uint8_t base__[PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_OFFSET_MAP_BASE_SIZE]; -#define PAR_ITEM_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_1) -#define PAR_ITEM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_2) -#define PAR_ITEM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_4) -#define PAR_ITEM_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_1) -#define PAR_ITEM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_2) -#define PAR_ITEM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_4) -#define PAR_ITEM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_4) +#define PAR_ITEM_U8(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_1) +#define PAR_ITEM_U16(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_2) +#define PAR_ITEM_U32(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_4) +#define PAR_ITEM_I8(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_1) +#define PAR_ITEM_I16(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_2) +#define PAR_ITEM_I32(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_4) +#define PAR_ITEM_F32(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_4) #include "../../par_table.def" #undef PAR_ITEM_U8 #undef PAR_ITEM_U16 @@ -95,13 +95,13 @@ typedef struct #define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_1(enum_) [PAR_PERSIST_IDX_##enum_] = PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_OFFSET_OF(enum_), #define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_0(enum_) static const uint32_t g_par_nvm_layout_fixed_payload_only_addr_lut[PAR_PERSIST_SLOT_MAP_CAPACITY] = { -#define PAR_ITEM_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) -#define PAR_ITEM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) -#define PAR_ITEM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) -#define PAR_ITEM_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) -#define PAR_ITEM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) -#define PAR_ITEM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) -#define PAR_ITEM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) +#define PAR_ITEM_U8(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_U16(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_U32(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_I8(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_I16(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_I32(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_F32(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) #include "../../par_table.def" #undef PAR_ITEM_U8 #undef PAR_ITEM_U16 diff --git a/src/persist/par_nvm_layout_grouped_payload_only.c b/src/persist/par_nvm_layout_grouped_payload_only.c index 19a454c..6a329a6 100644 --- a/src/persist/par_nvm_layout_grouped_payload_only.c +++ b/src/persist/par_nvm_layout_grouped_payload_only.c @@ -60,25 +60,25 @@ static uint32_t par_nvm_layout_grouped_payload_only_record_size_from_par_num(con * 32-bit payloads. `offsetof()` on the generated members therefore yields the * exact grouped-layout address without runtime scans or handwritten offsets. */ -#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_OFFSET_MAP_BASE_SIZE 1U -#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_RECORD_SIZE_BYTES(payload_size_) (PAR_NVM_LAYOUT_RECORD_OVERHEAD + (uint32_t)(payload_size_)) -#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, emit_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_I(enum_, pers_, emit_) -#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_I(enum_, pers_, emit_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_##pers_(enum_, emit_) -#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_1(enum_, emit_) emit_(enum_) +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_OFFSET_MAP_BASE_SIZE 1U +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_RECORD_SIZE_BYTES(payload_size_) (PAR_NVM_LAYOUT_RECORD_OVERHEAD + (uint32_t)(payload_size_)) +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, emit_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_I(enum_, pers_, emit_) +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_I(enum_, pers_, emit_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_##pers_(enum_, emit_) +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_1(enum_, emit_) emit_(enum_) #define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_0(enum_, emit_) -#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_1(enum_) uint8_t slot_##enum_[PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_RECORD_SIZE_BYTES(1U)]; -#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_2(enum_) uint8_t slot_##enum_[PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_RECORD_SIZE_BYTES(2U)]; -#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_4(enum_) uint8_t slot_##enum_[PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_RECORD_SIZE_BYTES(4U)]; +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_1(enum_) uint8_t slot_##enum_[PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_RECORD_SIZE_BYTES(1U)]; +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_2(enum_) uint8_t slot_##enum_[PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_RECORD_SIZE_BYTES(2U)]; +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_4(enum_) uint8_t slot_##enum_[PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_RECORD_SIZE_BYTES(4U)]; typedef struct { uint8_t base__[PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_OFFSET_MAP_BASE_SIZE]; -#define PAR_ITEM_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_1) -#define PAR_ITEM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) -#define PAR_ITEM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) -#define PAR_ITEM_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_1) -#define PAR_ITEM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) -#define PAR_ITEM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) -#define PAR_ITEM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#define PAR_ITEM_U8(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_1) +#define PAR_ITEM_U16(...) +#define PAR_ITEM_U32(...) +#define PAR_ITEM_I8(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_1) +#define PAR_ITEM_I16(...) +#define PAR_ITEM_I32(...) +#define PAR_ITEM_F32(...) #include "../../par_table.def" #undef PAR_ITEM_U8 #undef PAR_ITEM_U16 @@ -87,13 +87,13 @@ typedef struct #undef PAR_ITEM_I16 #undef PAR_ITEM_I32 #undef PAR_ITEM_F32 -#define PAR_ITEM_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) -#define PAR_ITEM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_2) -#define PAR_ITEM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) -#define PAR_ITEM_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) -#define PAR_ITEM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_2) -#define PAR_ITEM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) -#define PAR_ITEM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) +#define PAR_ITEM_U8(...) +#define PAR_ITEM_U16(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_2) +#define PAR_ITEM_U32(...) +#define PAR_ITEM_I8(...) +#define PAR_ITEM_I16(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_2) +#define PAR_ITEM_I32(...) +#define PAR_ITEM_F32(...) #include "../../par_table.def" #undef PAR_ITEM_U8 #undef PAR_ITEM_U16 @@ -102,13 +102,13 @@ typedef struct #undef PAR_ITEM_I16 #undef PAR_ITEM_I32 #undef PAR_ITEM_F32 -#define PAR_ITEM_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) -#define PAR_ITEM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) -#define PAR_ITEM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_4) -#define PAR_ITEM_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) -#define PAR_ITEM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) -#define PAR_ITEM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_4) -#define PAR_ITEM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_4) +#define PAR_ITEM_U8(...) +#define PAR_ITEM_U16(...) +#define PAR_ITEM_U32(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_4) +#define PAR_ITEM_I8(...) +#define PAR_ITEM_I16(...) +#define PAR_ITEM_I32(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_4) +#define PAR_ITEM_F32(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_4) #include "../../par_table.def" #undef PAR_ITEM_U8 #undef PAR_ITEM_U16 @@ -118,19 +118,19 @@ typedef struct #undef PAR_ITEM_I32 #undef PAR_ITEM_F32 } par_nvm_layout_grouped_payload_only_offset_map_t; -#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_OFFSET_OF(enum_) ((uint32_t)offsetof(par_nvm_layout_grouped_payload_only_offset_map_t, slot_##enum_) - PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_OFFSET_MAP_BASE_SIZE) -#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_I(enum_, pers_) -#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_I(enum_, pers_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_##pers_(enum_) -#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_1(enum_) [PAR_PERSIST_IDX_##enum_] = PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_OFFSET_OF(enum_), +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_OFFSET_OF(enum_) ((uint32_t)offsetof(par_nvm_layout_grouped_payload_only_offset_map_t, slot_##enum_) - PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_OFFSET_MAP_BASE_SIZE) +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_I(enum_, pers_) +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_I(enum_, pers_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_##pers_(enum_) +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_1(enum_) [PAR_PERSIST_IDX_##enum_] = PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_OFFSET_OF(enum_), #define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_0(enum_) static const uint32_t g_par_nvm_layout_grouped_payload_only_addr_lut[PAR_PERSIST_SLOT_MAP_CAPACITY] = { -#define PAR_ITEM_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) -#define PAR_ITEM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) -#define PAR_ITEM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) -#define PAR_ITEM_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) -#define PAR_ITEM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) -#define PAR_ITEM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) -#define PAR_ITEM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) +#define PAR_ITEM_U8(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_U16(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_U32(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_I8(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_I16(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_I32(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_F32(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) #include "../../par_table.def" #undef PAR_ITEM_U8 #undef PAR_ITEM_U16 @@ -316,9 +316,7 @@ static par_nvm_compat_result_t par_nvm_layout_grouped_payload_only_check_compat( return ePAR_NVM_COMPAT_REBUILD; } - return (p_head_obj->obj_nb == (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) ? - ePAR_NVM_COMPAT_EXACT_MATCH : - ePAR_NVM_COMPAT_REBUILD; + return (p_head_obj->obj_nb == (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) ? ePAR_NVM_COMPAT_EXACT_MATCH : ePAR_NVM_COMPAT_REBUILD; } #if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) diff --git a/template/par_cfg_port.htmp b/template/par_cfg_port.htmp index ca075bc..0b40c7e 100644 --- a/template/par_cfg_port.htmp +++ b/template/par_cfg_port.htmp @@ -1,12 +1,16 @@ #ifndef _PAR_CFG_PORT_H_ #define _PAR_CFG_PORT_H_ + /* Optional platform overrides */ /* #define PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK ( 0 ) */ /* #define PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK ( 0 ) */ -/* #define PAR_CFG_NVM_WRITE_VERIFY_EN ( 0 ) */ -/* #define PAR_CFG_NVM_RECORD_LAYOUT ( PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE ) */ -/* or PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE */ -/* or PAR_CFG_NVM_RECORD_LAYOUT_COMPACT_PAYLOAD */ -/* or PAR_CFG_NVM_RECORD_LAYOUT_FIXED_PAYLOAD_ONLY */ -/* or PAR_CFG_NVM_RECORD_LAYOUT_GROUPED_PAYLOAD_ONLY */ +/* #define PAR_CFG_ENABLE_ACCESS ( 1 ) */ +/* #define PAR_CFG_ENABLE_ROLE_POLICY ( 0 ) */ +/* #define PAR_CFG_NVM_WRITE_VERIFY_EN ( 0 ) */ +/* #define PAR_CFG_NVM_RECORD_LAYOUT ( PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE ) */ +/* or PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE */ +/* or PAR_CFG_NVM_RECORD_LAYOUT_COMPACT_PAYLOAD */ +/* or PAR_CFG_NVM_RECORD_LAYOUT_FIXED_PAYLOAD_ONLY */ +/* or PAR_CFG_NVM_RECORD_LAYOUT_GROUPED_PAYLOAD_ONLY */ + #endif diff --git a/template/par_table.deftmp b/template/par_table.deftmp index 12537f4..77e1a78 100644 --- a/template/par_table.deftmp +++ b/template/par_table.deftmp @@ -5,13 +5,13 @@ * Do not add include guards and do not place executable code here. * * Supported item macros: - * PAR_ITEM_U8 (enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) - * PAR_ITEM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) - * PAR_ITEM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) - * PAR_ITEM_I8 (enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) - * PAR_ITEM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) - * PAR_ITEM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) - * PAR_ITEM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, pers_, desc_) + * PAR_ITEM_U8 (enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) + * PAR_ITEM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) + * PAR_ITEM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) + * PAR_ITEM_I8 (enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) + * PAR_ITEM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) + * PAR_ITEM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) + * PAR_ITEM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) * * Field description: * enum_ - Parameter enum index (used as designated initializer index) @@ -21,9 +21,11 @@ * max_ - Maximum allowed value * def_ - Default value, must be within [min_, max_] * unit_ - Engineering unit string, or NULL if not applicable - * access_ - External access type: ePAR_ACCESS_RO / ePAR_ACCESS_RW - * pers_ - Persistence flag: true if stored to NVM, otherwise false - * desc_ - Human-readable description + * access_ - External access capability mask: ePAR_ACCESS_RO / ePAR_ACCESS_RW + * read_roles_ - External role mask allowed to read this parameter + * write_roles_ - External role mask allowed to write this parameter + * pers_ - Persistence flag: true if stored to NVM, otherwise false + * desc_ - Human-readable description * * Formatting note: * Keep items grouped by subsystem/channel and aligned for readability. @@ -31,7 +33,7 @@ /* ============================================================================================================================================================= */ -/* enum_ id_ name_ min_ max_ def_ unit_ access_ pers_ desc_ */ +/* enum_ id_ name_ min_ max_ def_ unit_ access_ read_roles_ write_roles_ pers_ desc_ */ /* ============================================================================================================================================================= */ @@ -40,35 +42,35 @@ /* ============================================================================================================================= */ /* Channel 1 control */ -PAR_ITEM_U8 (ePAR_CH1_CTRL, 0, "Ch1 Control", 0U, 2U, 2U, NULL, ePAR_ACCESS_RW, false, "Channel 1 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!") -PAR_ITEM_U8 (ePAR_CH1_AFE_MEAS_EN, 1, "Ch1 AFE Measurement Control", 0U, 1U, 1U, NULL, ePAR_ACCESS_RW, true, "Channel 1 control AFE measurement usage for calculations: 0-Disable | 1-Enable") -PAR_ITEM_U8 (ePAR_CH1_TEST_MODE_EN, 2, "Ch1 Test Mode", 0U, 1U, 0U, NULL, ePAR_ACCESS_RW, false, "Channel 1 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!") -PAR_ITEM_U8 (ePAR_CH1_REF_SEL, 3, "Ch1 Reference Selection", 0U, 4U, 0U, NULL, ePAR_ACCESS_RW, true, "Channel 1 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset") -PAR_ITEM_F32(ePAR_CH1_REF_VAL, 4, "Ch1 Reference Value", -1E6f, 1E6f, 0.0f, NULL, ePAR_ACCESS_RW, true, "Channel 1 reference value based on control variable set") +PAR_ITEM_U8 (ePAR_CH1_CTRL , 0 , "Ch1 Control" , 0U , 2U , 2U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , false, "Channel 1 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!") +PAR_ITEM_U8 (ePAR_CH1_AFE_MEAS_EN , 1 , "Ch1 AFE Measurement Control", 0U , 1U , 1U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , true , "Channel 1 control AFE measurement usage for calculations: 0-Disable | 1-Enable") +PAR_ITEM_U8 (ePAR_CH1_TEST_MODE_EN, 2 , "Ch1 Test Mode" , 0U , 1U , 0U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , false, "Channel 1 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!") +PAR_ITEM_U8 (ePAR_CH1_REF_SEL , 3 , "Ch1 Reference Selection" , 0U , 4U , 0U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , true , "Channel 1 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset") +PAR_ITEM_F32 (ePAR_CH1_REF_VAL , 4 , "Ch1 Reference Value" , -1E6f , 1E6f , 0.0f , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , true , "Channel 1 reference value based on control variable set") /* Channel 1 status */ -PAR_ITEM_U8 (ePAR_CH1_STATUS, 10, "Ch1 Status", 0U, 3U, 3U, NULL, ePAR_ACCESS_RO, false, "Channel 1 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected") -PAR_ITEM_U8 (ePAR_CH1_FSM_STATE, 11, "Ch1 FSM State", 0U, 3U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 1 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal") +PAR_ITEM_U8 (ePAR_CH1_STATUS , 10 , "Ch1 Status" , 0U , 3U , 3U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected") +PAR_ITEM_U8 (ePAR_CH1_FSM_STATE , 11 , "Ch1 FSM State" , 0U , 3U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal") /* Channel 1 reference values */ -PAR_ITEM_F32(ePAR_CH1_TSIM, 21, "Ch1 Ref Temperature", -20.0f, 350.0f, 0.0f, "degC", ePAR_ACCESS_RO, false, "Channel 1 temperature in degree C") -PAR_ITEM_F32(ePAR_CH1_RSIM, 22, "Ch1 Ref Resistance", 920.0f, 2300.0f, 920.0f, "ohm", ePAR_ACCESS_RO, false, "Channel 1 resistance reference in ohm") -PAR_ITEM_F32(ePAR_CH1_VOUT, 23, "Ch1 Ref Vout", 0.85f, 1.58f, 0.85f, "V", ePAR_ACCESS_RO, false, "Channel 1 output voltage in V") -PAR_ITEM_F32(ePAR_CH1_ISINK, 24, "Ch1 Ref Isink", 658.5f, 950.0f, 658.5f, "uA", ePAR_ACCESS_RO, false, "Channel 1 sink current reference in uA") -PAR_ITEM_F32(ePAR_CH1_VSET, 25, "Ch1 Ref Vset", 1.7955f, 2.548f, 1.7955f, "V", ePAR_ACCESS_RO, false, "Channel 1 Vset (reference) voltage in V") -PAR_ITEM_F32(ePAR_CH1_VTH, 26, "Ch1 Ref Vth", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 1 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined.") -PAR_ITEM_F32(ePAR_CH1_VDAC_C, 27, "Ch1 Ref Vdac Coarse", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 1 Vdac coarse voltage in V") -PAR_ITEM_F32(ePAR_CH1_VDAC_F, 28, "Ch1 Ref Vdac Fine", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 1 Vdac fine voltage in V") -PAR_ITEM_U16(ePAR_CH1_VDAC_F_RAW, 29, "Ch1 Ref Raw DAC Fine", 0U, 4095U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 1 raw fine DAC value") -PAR_ITEM_U16(ePAR_CH1_VDAC_C_RAW, 30, "Ch1 Ref Raw DAC Coarse", 0U, 4095U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 1 raw coarse DAC value") +PAR_ITEM_F32 (ePAR_CH1_TSIM , 21 , "Ch1 Ref Temperature" , -20.0f , 350.0f , 0.0f , "degC", ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 temperature in degree C") +PAR_ITEM_F32 (ePAR_CH1_RSIM , 22 , "Ch1 Ref Resistance" , 920.0f , 2300.0f , 920.0f , "ohm" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 resistance reference in ohm") +PAR_ITEM_F32 (ePAR_CH1_VOUT , 23 , "Ch1 Ref Vout" , 0.85f , 1.58f , 0.85f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 output voltage in V") +PAR_ITEM_F32 (ePAR_CH1_ISINK , 24 , "Ch1 Ref Isink" , 658.5f , 950.0f , 658.5f , "uA" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 sink current reference in uA") +PAR_ITEM_F32 (ePAR_CH1_VSET , 25 , "Ch1 Ref Vset" , 1.7955f, 2.548f , 1.7955f, "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 Vset (reference) voltage in V") +PAR_ITEM_F32 (ePAR_CH1_VTH , 26 , "Ch1 Ref Vth" , 0.0f , 3.3f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined.") +PAR_ITEM_F32 (ePAR_CH1_VDAC_C , 27 , "Ch1 Ref Vdac Coarse" , 0.0f , 3.3f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 Vdac coarse voltage in V") +PAR_ITEM_F32 (ePAR_CH1_VDAC_F , 28 , "Ch1 Ref Vdac Fine" , 0.0f , 3.3f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 Vdac fine voltage in V") +PAR_ITEM_U16 (ePAR_CH1_VDAC_F_RAW , 29 , "Ch1 Ref Raw DAC Fine" , 0U , 4095U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 raw fine DAC value") +PAR_ITEM_U16 (ePAR_CH1_VDAC_C_RAW , 30 , "Ch1 Ref Raw DAC Coarse" , 0U , 4095U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 raw coarse DAC value") /* Channel 1 measurements */ -PAR_ITEM_F32(ePAR_CH1_CUR, 50, "Ch1 Act Isink", 0.0f, 5000.0f, 0.0f, "uA", ePAR_ACCESS_RO, false, "Channel 1 actual current sink value in uA") -PAR_ITEM_F32(ePAR_CH1_VOL, 52, "Ch1 Act Vout", 0.0f, 10.0f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 1 actual output voltage value in V") -PAR_ITEM_U16(ePAR_CH1_CUR_RAW, 53, "Ch1 Raw Isink", 0U, 8191U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 1 raw ADC current value") -PAR_ITEM_U16(ePAR_CH1_VOL_RAW, 54, "Ch1 Raw Vout", 0U, 8191U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 1 raw ADC voltage value") -PAR_ITEM_F32(ePAR_CH1_AFE_VCC, 55, "Ch1 AFE Vcc", 0.0f, 10.0f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 1 measured AFE Vcc voltage value in V") -PAR_ITEM_F32(ePAR_CH1_AFE_RES, 56, "Ch1 AFE Res", 0.0f, 5.0e3f, 0.0f, "ohm", ePAR_ACCESS_RO, false, "Channel 1 measured AFE resistance (pull-up + series) value in ohm") +PAR_ITEM_F32 (ePAR_CH1_CUR , 50 , "Ch1 Act Isink" , 0.0f , 5000.0f , 0.0f , "uA" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 actual current sink value in uA") +PAR_ITEM_F32 (ePAR_CH1_VOL , 52 , "Ch1 Act Vout" , 0.0f , 10.0f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 actual output voltage value in V") +PAR_ITEM_U16 (ePAR_CH1_CUR_RAW , 53 , "Ch1 Raw Isink" , 0U , 8191U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 raw ADC current value") +PAR_ITEM_U16 (ePAR_CH1_VOL_RAW , 54 , "Ch1 Raw Vout" , 0U , 8191U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 raw ADC voltage value") +PAR_ITEM_F32 (ePAR_CH1_AFE_VCC , 55 , "Ch1 AFE Vcc" , 0.0f , 10.0f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 measured AFE Vcc voltage value in V") +PAR_ITEM_F32 (ePAR_CH1_AFE_RES , 56 , "Ch1 AFE Res" , 0.0f , 5.0e3f , 0.0f , "ohm" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 measured AFE resistance (pull-up + series) value in ohm") /* ============================================================================================================================= */ @@ -76,35 +78,35 @@ PAR_ITEM_F32(ePAR_CH1_AFE_RES, 56, "Ch1 AFE Res", 0.0f, /* ============================================================================================================================= */ /* Channel 2 control */ -PAR_ITEM_U8 (ePAR_CH2_CTRL, 100, "Ch2 Control", 0U, 2U, 2U, NULL, ePAR_ACCESS_RW, false, "Channel 2 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!") -PAR_ITEM_U8 (ePAR_CH2_AFE_MEAS_EN, 101, "Ch2 AFE Measurement Control", 0U, 1U, 1U, NULL, ePAR_ACCESS_RW, true, "Channel 2 control AFE measurement usage for calculations: 0-Disable | 1-Enable") -PAR_ITEM_U8 (ePAR_CH2_TEST_MODE_EN, 102, "Ch2 Test Mode", 0U, 1U, 0U, NULL, ePAR_ACCESS_RW, false, "Channel 2 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!") -PAR_ITEM_U8 (ePAR_CH2_REF_SEL, 103, "Ch2 Reference Selection", 0U, 4U, 0U, NULL, ePAR_ACCESS_RW, true, "Channel 2 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset") -PAR_ITEM_F32(ePAR_CH2_REF_VAL, 104, "Ch2 Reference Value", -1E6f, 1E6f, 0.0f, NULL, ePAR_ACCESS_RW, true, "Channel 2 reference value based on control variable set") +PAR_ITEM_U8 (ePAR_CH2_CTRL , 100 , "Ch2 Control" , 0U , 2U , 2U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , false, "Channel 2 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!") +PAR_ITEM_U8 (ePAR_CH2_AFE_MEAS_EN , 101 , "Ch2 AFE Measurement Control", 0U , 1U , 1U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , true , "Channel 2 control AFE measurement usage for calculations: 0-Disable | 1-Enable") +PAR_ITEM_U8 (ePAR_CH2_TEST_MODE_EN, 102 , "Ch2 Test Mode" , 0U , 1U , 0U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , false, "Channel 2 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!") +PAR_ITEM_U8 (ePAR_CH2_REF_SEL , 103 , "Ch2 Reference Selection" , 0U , 4U , 0U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , true , "Channel 2 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset") +PAR_ITEM_F32 (ePAR_CH2_REF_VAL , 104 , "Ch2 Reference Value" , -1E6f , 1E6f , 0.0f , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , true , "Channel 2 reference value based on control variable set") /* Channel 2 status */ -PAR_ITEM_U8 (ePAR_CH2_STATUS, 111, "Ch2 Status", 0U, 3U, 3U, NULL, ePAR_ACCESS_RO, false, "Channel 2 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected") -PAR_ITEM_U8 (ePAR_CH2_FSM_STATE, 112, "Ch2 FSM State", 0U, 3U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 2 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal") +PAR_ITEM_U8 (ePAR_CH2_STATUS , 111 , "Ch2 Status" , 0U , 3U , 3U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected") +PAR_ITEM_U8 (ePAR_CH2_FSM_STATE , 112 , "Ch2 FSM State" , 0U , 3U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal") /* Channel 2 reference values */ -PAR_ITEM_F32(ePAR_CH2_TSIM, 120, "Ch2 Ref Temperature", -20.0f, 350.0f, 0.0f, "degC", ePAR_ACCESS_RO, false, "Channel 2 temperature in degree C") -PAR_ITEM_F32(ePAR_CH2_RSIM, 121, "Ch2 Ref Resistance", 920.0f, 2300.0f, 920.0f, "ohm", ePAR_ACCESS_RO, false, "Channel 2 resistance reference in ohm") -PAR_ITEM_F32(ePAR_CH2_VOUT, 122, "Ch2 Ref Vout", 0.85f, 1.58f, 0.85f, "V", ePAR_ACCESS_RO, false, "Channel 2 output voltage in V") -PAR_ITEM_F32(ePAR_CH2_ISINK, 123, "Ch2 Ref Isink", 658.5f, 950.0f, 658.5f, "uA", ePAR_ACCESS_RO, false, "Channel 2 sink current reference in uA") -PAR_ITEM_F32(ePAR_CH2_VSET, 124, "Ch2 Ref Vset", 1.7955f, 2.548f, 1.7955f, "V", ePAR_ACCESS_RO, false, "Channel 2 Vset (reference) voltage in V") -PAR_ITEM_F32(ePAR_CH2_VTH, 125, "Ch2 Ref Vth", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 2 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined.") -PAR_ITEM_F32(ePAR_CH2_VDAC_C, 126, "Ch2 Ref Vdac Coarse", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 2 Vdac coarse voltage in V") -PAR_ITEM_F32(ePAR_CH2_VDAC_F, 127, "Ch2 Ref Vdac Fine", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 2 Vdac fine voltage in V") -PAR_ITEM_U16(ePAR_CH2_VDAC_F_RAW, 128, "Ch2 Ref Raw DAC Fine", 0U, 4095U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 2 raw fine DAC value") -PAR_ITEM_U16(ePAR_CH2_VDAC_C_RAW, 129, "Ch2 Ref Raw DAC Coarse", 0U, 4095U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 2 raw coarse DAC value") +PAR_ITEM_F32 (ePAR_CH2_TSIM , 120 , "Ch2 Ref Temperature" , -20.0f , 350.0f , 0.0f , "degC", ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 temperature in degree C") +PAR_ITEM_F32 (ePAR_CH2_RSIM , 121 , "Ch2 Ref Resistance" , 920.0f , 2300.0f , 920.0f , "ohm" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 resistance reference in ohm") +PAR_ITEM_F32 (ePAR_CH2_VOUT , 122 , "Ch2 Ref Vout" , 0.85f , 1.58f , 0.85f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 output voltage in V") +PAR_ITEM_F32 (ePAR_CH2_ISINK , 123 , "Ch2 Ref Isink" , 658.5f , 950.0f , 658.5f , "uA" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 sink current reference in uA") +PAR_ITEM_F32 (ePAR_CH2_VSET , 124 , "Ch2 Ref Vset" , 1.7955f, 2.548f , 1.7955f, "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 Vset (reference) voltage in V") +PAR_ITEM_F32 (ePAR_CH2_VTH , 125 , "Ch2 Ref Vth" , 0.0f , 3.3f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined.") +PAR_ITEM_F32 (ePAR_CH2_VDAC_C , 126 , "Ch2 Ref Vdac Coarse" , 0.0f , 3.3f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 Vdac coarse voltage in V") +PAR_ITEM_F32 (ePAR_CH2_VDAC_F , 127 , "Ch2 Ref Vdac Fine" , 0.0f , 3.3f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 Vdac fine voltage in V") +PAR_ITEM_U16 (ePAR_CH2_VDAC_F_RAW , 128 , "Ch2 Ref Raw DAC Fine" , 0U , 4095U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 raw fine DAC value") +PAR_ITEM_U16 (ePAR_CH2_VDAC_C_RAW , 129 , "Ch2 Ref Raw DAC Coarse" , 0U , 4095U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 raw coarse DAC value") /* Channel 2 measurements */ -PAR_ITEM_F32(ePAR_CH2_CUR, 150, "Ch2 Act Isink", 0.0f, 5000.0f, 0.0f, "uA", ePAR_ACCESS_RO, false, "Channel 2 actual current sink value in uA") -PAR_ITEM_F32(ePAR_CH2_VOL, 151, "Ch2 Act Vout", 0.0f, 10.0f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 2 actual output voltage value in V") -PAR_ITEM_U16(ePAR_CH2_CUR_RAW, 152, "Ch2 Raw Isink", 0U, 8191U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 2 raw ADC current value") -PAR_ITEM_U16(ePAR_CH2_VOL_RAW, 153, "Ch2 Raw Vout", 0U, 8191U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 2 raw ADC voltage value") -PAR_ITEM_F32(ePAR_CH2_AFE_VCC, 154, "Ch2 AFE Vcc", 0.0f, 10.0f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 2 measured AFE Vcc voltage value in V") -PAR_ITEM_F32(ePAR_CH2_AFE_RES, 155, "Ch2 AFE Res", 0.0f, 5.0e3f, 0.0f, "ohm", ePAR_ACCESS_RO, false, "Channel 2 measured AFE resistance (pull-up + series) value in ohm") +PAR_ITEM_F32 (ePAR_CH2_CUR , 150 , "Ch2 Act Isink" , 0.0f , 5000.0f , 0.0f , "uA" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 actual current sink value in uA") +PAR_ITEM_F32 (ePAR_CH2_VOL , 151 , "Ch2 Act Vout" , 0.0f , 10.0f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 actual output voltage value in V") +PAR_ITEM_U16 (ePAR_CH2_CUR_RAW , 152 , "Ch2 Raw Isink" , 0U , 8191U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 raw ADC current value") +PAR_ITEM_U16 (ePAR_CH2_VOL_RAW , 153 , "Ch2 Raw Vout" , 0U , 8191U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 raw ADC voltage value") +PAR_ITEM_F32 (ePAR_CH2_AFE_VCC , 154 , "Ch2 AFE Vcc" , 0.0f , 10.0f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 measured AFE Vcc voltage value in V") +PAR_ITEM_F32 (ePAR_CH2_AFE_RES , 155 , "Ch2 AFE Res" , 0.0f , 5.0e3f , 0.0f , "ohm" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 measured AFE resistance (pull-up + series) value in ohm") /* ============================================================================================================================= */ @@ -112,35 +114,35 @@ PAR_ITEM_F32(ePAR_CH2_AFE_RES, 155, "Ch2 AFE Res", 0.0f, /* ============================================================================================================================= */ /* Channel 3 control */ -PAR_ITEM_U8 (ePAR_CH3_CTRL, 200, "Ch3 Control", 0U, 2U, 2U, NULL, ePAR_ACCESS_RW, false, "Channel 3 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!") -PAR_ITEM_U8 (ePAR_CH3_AFE_MEAS_EN, 201, "Ch3 AFE Measurement Control", 0U, 1U, 1U, NULL, ePAR_ACCESS_RW, true, "Channel 3 control AFE measurement usage for calculations: 0-Disable | 1-Enable") -PAR_ITEM_U8 (ePAR_CH3_TEST_MODE_EN, 202, "Ch3 Test Mode", 0U, 1U, 0U, NULL, ePAR_ACCESS_RW, false, "Channel 3 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!") -PAR_ITEM_U8 (ePAR_CH3_REF_SEL, 203, "Ch3 Reference Selection", 0U, 4U, 0U, NULL, ePAR_ACCESS_RW, true, "Channel 3 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset") -PAR_ITEM_F32(ePAR_CH3_REF_VAL, 204, "Ch3 Reference Value", -1E6f, 1E6f, 0.0f, NULL, ePAR_ACCESS_RW, true, "Channel 3 reference value based on control variable set") +PAR_ITEM_U8 (ePAR_CH3_CTRL , 200 , "Ch3 Control" , 0U , 2U , 2U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , false, "Channel 3 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!") +PAR_ITEM_U8 (ePAR_CH3_AFE_MEAS_EN , 201 , "Ch3 AFE Measurement Control", 0U , 1U , 1U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , true , "Channel 3 control AFE measurement usage for calculations: 0-Disable | 1-Enable") +PAR_ITEM_U8 (ePAR_CH3_TEST_MODE_EN, 202 , "Ch3 Test Mode" , 0U , 1U , 0U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , false, "Channel 3 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!") +PAR_ITEM_U8 (ePAR_CH3_REF_SEL , 203 , "Ch3 Reference Selection" , 0U , 4U , 0U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , true , "Channel 3 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset") +PAR_ITEM_F32 (ePAR_CH3_REF_VAL , 204 , "Ch3 Reference Value" , -1E6f , 1E6f , 0.0f , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , true , "Channel 3 reference value based on control variable set") /* Channel 3 status */ -PAR_ITEM_U8 (ePAR_CH3_STATUS, 210, "Ch3 Status", 0U, 3U, 3U, NULL, ePAR_ACCESS_RO, false, "Channel 3 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected") -PAR_ITEM_U8 (ePAR_CH3_FSM_STATE, 211, "Ch3 FSM State", 0U, 3U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 3 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal") +PAR_ITEM_U8 (ePAR_CH3_STATUS , 210 , "Ch3 Status" , 0U , 3U , 3U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected") +PAR_ITEM_U8 (ePAR_CH3_FSM_STATE , 211 , "Ch3 FSM State" , 0U , 3U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal") /* Channel 3 reference values */ -PAR_ITEM_F32(ePAR_CH3_TSIM, 220, "Ch3 Ref Temperature", -20.0f, 350.0f, 0.0f, "degC", ePAR_ACCESS_RO, false, "Channel 3 temperature in degree C") -PAR_ITEM_F32(ePAR_CH3_RSIM, 221, "Ch3 Ref Resistance", 920.0f, 2300.0f, 920.0f, "ohm", ePAR_ACCESS_RO, false, "Channel 3 resistance reference in ohm") -PAR_ITEM_F32(ePAR_CH3_VOUT, 222, "Ch3 Ref Vout", 0.85f, 1.58f, 0.85f, "V", ePAR_ACCESS_RO, false, "Channel 3 output voltage in V") -PAR_ITEM_F32(ePAR_CH3_ISINK, 223, "Ch3 Ref Isink", 658.5f, 950.0f, 658.5f, "uA", ePAR_ACCESS_RO, false, "Channel 3 sink current reference in uA") -PAR_ITEM_F32(ePAR_CH3_VSET, 224, "Ch3 Ref Vset", 1.7955f, 2.548f, 1.7955f, "V", ePAR_ACCESS_RO, false, "Channel 3 Vset (reference) voltage in V") -PAR_ITEM_F32(ePAR_CH3_VTH, 225, "Ch3 Ref Vth", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 3 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined.") -PAR_ITEM_F32(ePAR_CH3_VDAC_C, 226, "Ch3 Ref Vdac Coarse", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 3 Vdac coarse voltage in V") -PAR_ITEM_F32(ePAR_CH3_VDAC_F, 227, "Ch3 Ref Vdac Fine", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 3 Vdac fine voltage in V") -PAR_ITEM_U16(ePAR_CH3_VDAC_F_RAW, 228, "Ch3 Ref Raw DAC Fine", 0U, 4095U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 3 raw fine DAC value") -PAR_ITEM_U16(ePAR_CH3_VDAC_C_RAW, 229, "Ch3 Ref Raw DAC Coarse", 0U, 4095U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 3 raw coarse DAC value") +PAR_ITEM_F32 (ePAR_CH3_TSIM , 220 , "Ch3 Ref Temperature" , -20.0f , 350.0f , 0.0f , "degC", ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 temperature in degree C") +PAR_ITEM_F32 (ePAR_CH3_RSIM , 221 , "Ch3 Ref Resistance" , 920.0f , 2300.0f , 920.0f , "ohm" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 resistance reference in ohm") +PAR_ITEM_F32 (ePAR_CH3_VOUT , 222 , "Ch3 Ref Vout" , 0.85f , 1.58f , 0.85f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 output voltage in V") +PAR_ITEM_F32 (ePAR_CH3_ISINK , 223 , "Ch3 Ref Isink" , 658.5f , 950.0f , 658.5f , "uA" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 sink current reference in uA") +PAR_ITEM_F32 (ePAR_CH3_VSET , 224 , "Ch3 Ref Vset" , 1.7955f, 2.548f , 1.7955f, "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 Vset (reference) voltage in V") +PAR_ITEM_F32 (ePAR_CH3_VTH , 225 , "Ch3 Ref Vth" , 0.0f , 3.3f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined.") +PAR_ITEM_F32 (ePAR_CH3_VDAC_C , 226 , "Ch3 Ref Vdac Coarse" , 0.0f , 3.3f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 Vdac coarse voltage in V") +PAR_ITEM_F32 (ePAR_CH3_VDAC_F , 227 , "Ch3 Ref Vdac Fine" , 0.0f , 3.3f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 Vdac fine voltage in V") +PAR_ITEM_U16 (ePAR_CH3_VDAC_F_RAW , 228 , "Ch3 Ref Raw DAC Fine" , 0U , 4095U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 raw fine DAC value") +PAR_ITEM_U16 (ePAR_CH3_VDAC_C_RAW , 229 , "Ch3 Ref Raw DAC Coarse" , 0U , 4095U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 raw coarse DAC value") /* Channel 3 measurements */ -PAR_ITEM_F32(ePAR_CH3_CUR, 250, "Ch3 Act Isink", 0.0f, 5000.0f, 0.0f, "uA", ePAR_ACCESS_RO, false, "Channel 3 actual current sink value in uA") -PAR_ITEM_F32(ePAR_CH3_VOL, 251, "Ch3 Act Vout", 0.0f, 10.0f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 3 actual output voltage value in V") -PAR_ITEM_U16(ePAR_CH3_CUR_RAW, 252, "Ch3 Raw Isink", 0U, 8191U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 3 raw ADC current value") -PAR_ITEM_U16(ePAR_CH3_VOL_RAW, 253, "Ch3 Raw Vout", 0U, 8191U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 3 raw ADC voltage value") -PAR_ITEM_F32(ePAR_CH3_AFE_VCC, 254, "Ch3 AFE Vcc", 0.0f, 10.0f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 3 measured AFE Vcc voltage value in V") -PAR_ITEM_F32(ePAR_CH3_AFE_RES, 255, "Ch3 AFE Res", 0.0f, 5.0e3f, 0.0f, "ohm", ePAR_ACCESS_RO, false, "Channel 3 measured AFE resistance (pull-up + series) value in ohm") +PAR_ITEM_F32 (ePAR_CH3_CUR , 250 , "Ch3 Act Isink" , 0.0f , 5000.0f , 0.0f , "uA" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 actual current sink value in uA") +PAR_ITEM_F32 (ePAR_CH3_VOL , 251 , "Ch3 Act Vout" , 0.0f , 10.0f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 actual output voltage value in V") +PAR_ITEM_U16 (ePAR_CH3_CUR_RAW , 252 , "Ch3 Raw Isink" , 0U , 8191U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 raw ADC current value") +PAR_ITEM_U16 (ePAR_CH3_VOL_RAW , 253 , "Ch3 Raw Vout" , 0U , 8191U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 raw ADC voltage value") +PAR_ITEM_F32 (ePAR_CH3_AFE_VCC , 254 , "Ch3 AFE Vcc" , 0.0f , 10.0f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 measured AFE Vcc voltage value in V") +PAR_ITEM_F32 (ePAR_CH3_AFE_RES , 255 , "Ch3 AFE Res" , 0.0f , 5.0e3f , 0.0f , "ohm" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 measured AFE resistance (pull-up + series) value in ohm") /* ============================================================================================================================= */ @@ -148,35 +150,35 @@ PAR_ITEM_F32(ePAR_CH3_AFE_RES, 255, "Ch3 AFE Res", 0.0f, /* ============================================================================================================================= */ /* Channel 4 control */ -PAR_ITEM_U8 (ePAR_CH4_CTRL, 300, "Ch4 Control", 0U, 2U, 2U, NULL, ePAR_ACCESS_RW, false, "Channel 4 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!") -PAR_ITEM_U8 (ePAR_CH4_AFE_MEAS_EN, 301, "Ch4 AFE Measurement Control", 0U, 1U, 1U, NULL, ePAR_ACCESS_RW, true, "Channel 4 control AFE measurement usage for calculations: 0-Disable | 1-Enable") -PAR_ITEM_U8 (ePAR_CH4_TEST_MODE_EN, 302, "Ch4 Test Mode", 0U, 1U, 0U, NULL, ePAR_ACCESS_RW, false, "Channel 4 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!") -PAR_ITEM_U8 (ePAR_CH4_REF_SEL, 303, "Ch4 Reference Selection", 0U, 4U, 0U, NULL, ePAR_ACCESS_RW, true, "Channel 4 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset") -PAR_ITEM_F32(ePAR_CH4_REF_VAL, 304, "Ch4 Reference Value", -1E6f, 1E6f, 0.0f, NULL, ePAR_ACCESS_RW, true, "Channel 4 reference value based on control variable set") +PAR_ITEM_U8 (ePAR_CH4_CTRL , 300 , "Ch4 Control" , 0U , 2U , 2U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , false, "Channel 4 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!") +PAR_ITEM_U8 (ePAR_CH4_AFE_MEAS_EN , 301 , "Ch4 AFE Measurement Control", 0U , 1U , 1U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , true , "Channel 4 control AFE measurement usage for calculations: 0-Disable | 1-Enable") +PAR_ITEM_U8 (ePAR_CH4_TEST_MODE_EN, 302 , "Ch4 Test Mode" , 0U , 1U , 0U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , false, "Channel 4 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!") +PAR_ITEM_U8 (ePAR_CH4_REF_SEL , 303 , "Ch4 Reference Selection" , 0U , 4U , 0U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , true , "Channel 4 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset") +PAR_ITEM_F32 (ePAR_CH4_REF_VAL , 304 , "Ch4 Reference Value" , -1E6f , 1E6f , 0.0f , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , true , "Channel 4 reference value based on control variable set") /* Channel 4 status */ -PAR_ITEM_U8 (ePAR_CH4_STATUS, 310, "Ch4 Status", 0U, 3U, 3U, NULL, ePAR_ACCESS_RO, false, "Channel 4 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected") -PAR_ITEM_U8 (ePAR_CH4_FSM_STATE, 311, "Ch4 FSM State", 0U, 3U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 4 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal") +PAR_ITEM_U8 (ePAR_CH4_STATUS , 310 , "Ch4 Status" , 0U , 3U , 3U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected") +PAR_ITEM_U8 (ePAR_CH4_FSM_STATE , 311 , "Ch4 FSM State" , 0U , 3U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal") /* Channel 4 reference values */ -PAR_ITEM_F32(ePAR_CH4_TSIM, 320, "Ch4 Ref Temperature", -20.0f, 350.0f, 0.0f, "degC", ePAR_ACCESS_RO, false, "Channel 4 temperature in degree C") -PAR_ITEM_F32(ePAR_CH4_RSIM, 321, "Ch4 Ref Resistance", 920.0f, 2300.0f, 920.0f, "ohm", ePAR_ACCESS_RO, false, "Channel 4 resistance reference in ohm") -PAR_ITEM_F32(ePAR_CH4_VOUT, 322, "Ch4 Ref Vout", 0.85f, 1.58f, 0.85f, "V", ePAR_ACCESS_RO, false, "Channel 4 output voltage in V") -PAR_ITEM_F32(ePAR_CH4_ISINK, 323, "Ch4 Ref Isink", 658.5f, 950.0f, 658.5f, "uA", ePAR_ACCESS_RO, false, "Channel 4 sink current reference in uA") -PAR_ITEM_F32(ePAR_CH4_VSET, 324, "Ch4 Ref Vset", 1.7955f, 2.548f, 1.7955f, "V", ePAR_ACCESS_RO, false, "Channel 4 Vset (reference) voltage in V") -PAR_ITEM_F32(ePAR_CH4_VTH, 325, "Ch4 Ref Vth", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 4 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined.") -PAR_ITEM_F32(ePAR_CH4_VDAC_C, 326, "Ch4 Ref Vdac Coarse", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 4 Vdac coarse voltage in V") -PAR_ITEM_F32(ePAR_CH4_VDAC_F, 327, "Ch4 Ref Vdac Fine", 0.0f, 3.3f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 4 Vdac fine voltage in V") -PAR_ITEM_U16(ePAR_CH4_VDAC_F_RAW, 328, "Ch4 Ref Raw DAC Fine", 0U, 4095U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 4 raw fine DAC value") -PAR_ITEM_U16(ePAR_CH4_VDAC_C_RAW, 329, "Ch4 Ref Raw DAC Coarse", 0U, 4095U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 4 raw coarse DAC value") +PAR_ITEM_F32 (ePAR_CH4_TSIM , 320 , "Ch4 Ref Temperature" , -20.0f , 350.0f , 0.0f , "degC", ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 temperature in degree C") +PAR_ITEM_F32 (ePAR_CH4_RSIM , 321 , "Ch4 Ref Resistance" , 920.0f , 2300.0f , 920.0f , "ohm" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 resistance reference in ohm") +PAR_ITEM_F32 (ePAR_CH4_VOUT , 322 , "Ch4 Ref Vout" , 0.85f , 1.58f , 0.85f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 output voltage in V") +PAR_ITEM_F32 (ePAR_CH4_ISINK , 323 , "Ch4 Ref Isink" , 658.5f , 950.0f , 658.5f , "uA" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 sink current reference in uA") +PAR_ITEM_F32 (ePAR_CH4_VSET , 324 , "Ch4 Ref Vset" , 1.7955f, 2.548f , 1.7955f, "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 Vset (reference) voltage in V") +PAR_ITEM_F32 (ePAR_CH4_VTH , 325 , "Ch4 Ref Vth" , 0.0f , 3.3f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined.") +PAR_ITEM_F32 (ePAR_CH4_VDAC_C , 326 , "Ch4 Ref Vdac Coarse" , 0.0f , 3.3f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 Vdac coarse voltage in V") +PAR_ITEM_F32 (ePAR_CH4_VDAC_F , 327 , "Ch4 Ref Vdac Fine" , 0.0f , 3.3f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 Vdac fine voltage in V") +PAR_ITEM_U16 (ePAR_CH4_VDAC_F_RAW , 328 , "Ch4 Ref Raw DAC Fine" , 0U , 4095U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 raw fine DAC value") +PAR_ITEM_U16 (ePAR_CH4_VDAC_C_RAW , 329 , "Ch4 Ref Raw DAC Coarse" , 0U , 4095U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 raw coarse DAC value") /* Channel 4 measurements */ -PAR_ITEM_F32(ePAR_CH4_CUR, 360, "Ch4 Act Isink", 0.0f, 5000.0f, 0.0f, "uA", ePAR_ACCESS_RO, false, "Channel 4 actual current sink value in uA") -PAR_ITEM_F32(ePAR_CH4_VOL, 361, "Ch4 Act Vout", 0.0f, 10.0f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 4 actual output voltage value in V") -PAR_ITEM_U16(ePAR_CH4_CUR_RAW, 362, "Ch4 Raw Isink", 0U, 8191U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 4 raw ADC current value") -PAR_ITEM_U16(ePAR_CH4_VOL_RAW, 363, "Ch4 Raw Vout", 0U, 8191U, 0U, NULL, ePAR_ACCESS_RO, false, "Channel 4 raw ADC voltage value") -PAR_ITEM_F32(ePAR_CH4_AFE_VCC, 364, "Ch4 AFE Vcc", 0.0f, 10.0f, 0.0f, "V", ePAR_ACCESS_RO, false, "Channel 4 measured AFE Vcc voltage value in V") -PAR_ITEM_F32(ePAR_CH4_AFE_RES, 366, "Ch4 AFE Res", 0.0f, 5.0e3f, 0.0f, "ohm", ePAR_ACCESS_RO, false, "Channel 4 measured AFE resistance (pull-up + series) value in ohm") +PAR_ITEM_F32 (ePAR_CH4_CUR , 360 , "Ch4 Act Isink" , 0.0f , 5000.0f , 0.0f , "uA" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 actual current sink value in uA") +PAR_ITEM_F32 (ePAR_CH4_VOL , 361 , "Ch4 Act Vout" , 0.0f , 10.0f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 actual output voltage value in V") +PAR_ITEM_U16 (ePAR_CH4_CUR_RAW , 362 , "Ch4 Raw Isink" , 0U , 8191U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 raw ADC current value") +PAR_ITEM_U16 (ePAR_CH4_VOL_RAW , 363 , "Ch4 Raw Vout" , 0U , 8191U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 raw ADC voltage value") +PAR_ITEM_F32 (ePAR_CH4_AFE_VCC , 364 , "Ch4 AFE Vcc" , 0.0f , 10.0f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 measured AFE Vcc voltage value in V") +PAR_ITEM_F32 (ePAR_CH4_AFE_RES , 366 , "Ch4 AFE Res" , 0.0f , 5.0e3f , 0.0f , "ohm" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 measured AFE resistance (pull-up + series) value in ohm") /* ============================================================================================================================= */ @@ -184,6 +186,6 @@ PAR_ITEM_F32(ePAR_CH4_AFE_RES, 366, "Ch4 AFE Res", 0.0f, /* ============================================================================================================================= */ /* System status */ -PAR_ITEM_U32(ePAR_SYS_STATUS, 10000, "System status", 0U, UINT32_MAX, 0U, NULL, ePAR_ACCESS_RO, false, "0-None | For other look at sys_err.h") -PAR_ITEM_F32(ePAR_SYS_CPU_LOAD, 10010, "CPU load", 0.0f, 100.0f, 0.0f, "%", ePAR_ACCESS_RO, false, "Current CPU load in %") -PAR_ITEM_F32(ePAR_SYS_CPU_LOAD_MAX, 10018, "CPU Max. load", 0.0f, 100.0f, 0.0f, "%", ePAR_ACCESS_RO, false, "Maximum CPU load in %") \ No newline at end of file +PAR_ITEM_U32 (ePAR_SYS_STATUS , 10000, "System status" , 0U , UINT32_MAX, 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "0-None | For other look at sys_err.h") +PAR_ITEM_F32 (ePAR_SYS_CPU_LOAD , 10010, "CPU load" , 0.0f , 100.0f , 0.0f , "%" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Current CPU load in %") +PAR_ITEM_F32 (ePAR_SYS_CPU_LOAD_MAX, 10018, "CPU Max. load" , 0.0f , 100.0f , 0.0f , "%" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Maximum CPU load in %")