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.
For more details about storage into NVM look at the General Embedded C Library Manual document.
- 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.
When combined with the following modules, the Device Parameters module significantly enhances the capabilities of embedded applications:
-
- Enables communication between the embedded application and a PC.
- Allows real-time configuration and diagnostics via the CLI interface.
-
- Ensures that configured settings are stored persistently.
-
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.
- Together, these modules allow:
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.
In case of using NVM module PAR_CFG_NVM_EN = 1, then NVM module must pe part of project. NVM module must take following path:
"root/middleware/nvm/nvm/src/nvm.h"
Parameter module utilize C11 _Atomic and _Generic features, therefore make sure your compiler supports C11 primitives.
- 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.
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"
| 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) |
| 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) |
| 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 | uint8_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) |
| 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) |
| 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) |
| 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) |
Put all user code between sections: USER CODE BEGIN & USER CODE END!
1. Copy template files to root directory of module. 2. List names of all wanted parameters inside par_cfg.h file
/**
* 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
{
// USER CODE START...
ePAR_TEST_U8 = 0,
ePAR_TEST_I8,
ePAR_TEST_U16,
ePAR_TEST_I16,
ePAR_TEST_U32,
ePAR_TEST_I32,
ePAR_TEST_F32,
// USER CODE END...
ePAR_NUM_OF
} par_num_t;3. Change parameter configuration table inside par_cfg.c file. It is recommended to use designated initializers.
/**
* 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
// ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
[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" },
// ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// USER CODE END...
};4. Set-up all configurations options inside par_cfg.h file
| 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. |
5. Call par_init() function
// 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)!
6. Setting/Getting parameter value
// 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:
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 ); 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.
// --- 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
// Store all paramters to NVM
if ( ePAR_OK != par_save_all())
{
// Storing to NVM error...
// Further actions here...
}8. On-change callback usage
////////////////////////////////////////////////////////////////////////////////
/**
* 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
////////////////////////////////////////////////////////////////////////////////
/**
* 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...
}
}