Sensor Recalibration
The generic sensor recalibration driver provides a reusable, state-driven interface for performing two-point linear calibration on analog or continuous sensors (such as APPS pedals or steering angle sensors). It handles sample averaging, noise deadzone offsets, validity checks, and optional non-volatile memory (NVM) storage with CRC32 verification.
The driver files are located at:
- repos/common/drivers/sensors/recalibration/generic_sensor_recal.c
- repos/common/drivers/sensors/recalibration/generic_sensor_recal.h
- Unit tests: repos/common/test/drivers/Sensors/test_generic_sensor_recal.c
State Machine (FSM)
The recalibration sequence operates as a finite state machine. A calibration pass starts by measuring the sensor's minimum position, waiting for user confirmation, measuring the maximum position, calculating scaling parameters, and storing/verifying them in persistent memory.
Recalibration States:
RECAL_STATE_IDLE: Default resting state. No calibration is active.RECAL_STATE_MEASURE_MIN_SAMPLING: Captures raw samples via the read callback up tosample_windowand averages them to establish the baseline minimum.RECAL_STATE_WAIT_MAX_CAPTURE: Halts and waits for the user to position the physical sensor at its maximum extent and triggerRecal_Capture().RECAL_STATE_MEASURE_MAX_SAMPLING: Captures raw samples up tosample_windowand averages them to establish the baseline maximum.RECAL_STATE_COMPUTE: Runs plausibility checks, applies offsets, and computes the floating-pointscaleandoffsetconstants.RECAL_STATE_STORE: Attempts to write the calibration payload to non-volatile memory via callbacks.RECAL_STATE_VERIFY: Reads back the NVM payload and performs a CRC checksum match to confirm write integrity.RECAL_STATE_COMPLETED: Calibration successfully concluded; new parameters are ready for sensor applications.RECAL_STATE_ERROR: Transitioned to when NVM storage fails or sensor limits fail plausibility validation.
Mathematical Model
The driver maps raw sensor voltages/ADC values to a normalized float range between
1. Adjusting Raw Limits with Deadzones
To prevent the calibrated output from flickering at
2. Parameter Calculation
Once limits are adjusted, the driver performs two sanity checks:
If these pass, it computes the scaling factors:
3. Normalization Formula
At runtime, the sensor driver converts any raw ADC input to a calibrated percentage using:
This maps
Struct Definitions
1. Recal_Config_t
Defines parameters for starting a calibration sequence.
typedef struct {
uint32_t sample_window; // Number of samples to average (default: 16)
uint16_t min_offset; // Baseline count added to measured minimum
uint16_t max_offset; // Baseline count subtracted from measured maximum
} Recal_Config_t;2. Recal_Callbacks_t
Provides interface functions to interact with the underlying hardware and storage layer.
typedef struct {
// Read raw ADC/sensor sample callback
uint32_t (*read_raw)(void *context);
// Persistent storage hooks (return 0 on success, negative on error)
int (*nvm_write)(const void *data, size_t len, void *context);
int (*nvm_read)(void *data, size_t len, void *context);
void *context; // User context pointer forwarded to callbacks
} Recal_Callbacks_t;3. Recal_Data_t
The compiled calibration structure stored persistently in flash.
typedef struct {
uint16_t version; // Layout version (defaults to 1)
float offset; // Calculated offset multiplier
float scale; // Calculated scale multiplier
uint32_t raw_min; // Final adjusted raw min limit
uint32_t raw_max; // Final adjusted raw max limit
uint32_t crc; // CRC32 checksum (calculated over all preceding fields)
} Recal_Data_t;API Function Reference
Recal_Instance_t *Recal_Init(Recal_Instance_t *inst, Recal_Callbacks_t const *cb, Recal_Config_t const *config)
Initializes the calibration instance with callbacks, config parameters, and sets the initial state to RECAL_STATE_IDLE.
int Recal_Start(Recal_Instance_t *inst)
Resets sample buffers and starts a new calibration process, moving the state to RECAL_STATE_MEASURE_MIN_SAMPLING. Returns RECAL_OK on success, or RECAL_ERR_INVALID_ARG if already calibrating.
int Recal_Capture(Recal_Instance_t *inst)
Signals the FSM to transition from RECAL_STATE_WAIT_MAX_CAPTURE to RECAL_STATE_MEASURE_MAX_SAMPLING (starts capturing high samples).
int Recal_Abort(Recal_Instance_t *inst)
Immediately aborts any active sequence and returns the state machine to RECAL_STATE_IDLE.
void Recal_Tick(Recal_Instance_t *inst, uint32_t dt_ms)
Updates the calibration step. Must be called periodically from a timer, main loop, or task runner. The parameter dt_ms passes elapsed time.
const Recal_Data_t *Recal_GetData(Recal_Instance_t *inst)
Returns a pointer to the computed calibration factors inside Recal_Data_t. Valid after entering RECAL_STATE_COMPLETED.
int Recal_SaveToNvm(Recal_Instance_t *inst)
Computes the CRC32 checksum over the current calibration struct fields and executes the nvm_write callback.
int Recal_LoadFromNvm(Recal_Instance_t *inst)
Invokes the nvm_read callback, computes the CRC32 checksum over the retrieved fields, and compares it with the read crc field. Returns 0 on validation success, or RECAL_ERR_NVM on error or CRC mismatch.
Usage Example
Below is a complete implementation integrating the generic driver with a mock sensor and simulated flash storage:
#include "generic_sensor_recal.h"
#include <stdio.h>
// Simulated flash page in RAM
static uint8_t mock_flash_page[128];
static uint32_t dummy_read_raw(void *context) {
uint32_t *hardware_adc = (uint32_t *)context;
return *hardware_adc;
}
static int dummy_nvm_write(const void *data, size_t len, void *context) {
if (len > sizeof(mock_flash_page)) return -1;
memcpy(mock_flash_page, data, len);
return 0;
}
static int dummy_nvm_read(void *data, size_t len, void *context) {
if (len > sizeof(mock_flash_page)) return -1;
memcpy(data, mock_flash_page, len);
return 0;
}
void execute_recalibration(void) {
uint32_t adc_sensor_reading = 200U; // Start at minimum sensor position
Recal_Instance_t inst;
Recal_Callbacks_t cb = {
.read_raw = dummy_read_raw,
.nvm_write = dummy_nvm_write,
.nvm_read = dummy_nvm_read,
.context = &adc_sensor_reading
};
Recal_Config_t cfg = {
.sample_window = 4U, // Sample 4 ticks for min and max
.min_offset = 10U,
.max_offset = 10U
};
// Initialize calibration instance
Recal_Init(&inst, &cb, &cfg);
// Step 1: Start Calibration (sampling min)
Recal_Start(&inst);
for (int i = 0; i < 4; i++) {
Recal_Tick(&inst, 10U);
}
// Now state is RECAL_STATE_WAIT_MAX_CAPTURE
// Step 2: Set raw to max position and capture
adc_sensor_reading = 3800U;
Recal_Capture(&inst);
// Step 3: Tick to sample max
for (int i = 0; i < 4; i++) {
Recal_Tick(&inst, 10U);
}
// Step 4: Tick to run COMPUTE and NVM STORE/VERIFY steps
Recal_Tick(&inst, 10U); // COMPUTE -> STORE
Recal_Tick(&inst, 10U); // STORE -> VERIFY
Recal_Tick(&inst, 10U); // VERIFY -> COMPLETED
if (Recal_GetState(&inst) == RECAL_STATE_COMPLETED) {
const Recal_Data_t *data = Recal_GetData(&inst);
printf("Calibration Complete! Min Raw: %lu, Max Raw: %lu\n", data->raw_min, data->raw_max);
printf("Computed Scale: %f, Offset: %f\n", data->scale, data->offset);
}
}