Average the measurements

The measurement module now provides averaged measurements. These are
used in some places where accuracy is more important than latency (for
example for the temperature limit, where noise triggered the limit too
early).
This commit is contained in:
Thomas Kolb 2021-06-18 23:37:16 +02:00
parent 7ca6104e80
commit 49bd9247e0
5 changed files with 144 additions and 10 deletions

View File

@ -84,7 +84,7 @@ static enum ChargeState control_solar_charging(
}
// temperature limit
if(meas->temperature > internal_temperature_limit) {
if(meas->avg_temperature > internal_temperature_limit) {
return CHARGE_HIGH_TEMPERATURE;
}
@ -92,7 +92,7 @@ static enum ChargeState control_solar_charging(
if((time_in_state > SLEEP_STATE_DELAY)
&& (current_switch_state == true)
&& (solar_switch_onoff_duration > SLEEP_SWITCH_DELAY)
&& (meas->i_solar < sleep_solar_current)) {
&& (meas->avg_i_solar < sleep_solar_current)) {
return CHARGE_SLEEP;
}
@ -232,9 +232,8 @@ static void load_fsm_update(uint64_t uptime_ms, struct MeasurementResult *meas)
power_switch_load_on();
}
if(meas->i_load > load_current_limit) {
// TODO: maybe only check this 10 ms after load is switched on
// to allow for inrush current?
if((meas->i_load > load_current_limit)
&& (discharge_time_in_state > LOAD_CURRENT_INRUSH_TIME)) {
discharge_state = DISCHARGE_OVERCURRENT;
}
@ -352,8 +351,10 @@ void charge_control_update(uint64_t uptime_ms, struct MeasurementResult *meas)
bool charge_control_is_idle(void)
{
return ((charge_state == CHARGE_SLEEP)
&& (discharge_state == DISCHARGE_VOLTAGE_LOW));
return (((charge_state == CHARGE_SLEEP)
|| (charge_state == CHARGE_HIGH_TEMPERATURE))
&& ((discharge_state == DISCHARGE_VOLTAGE_LOW)
|| (discharge_state == DISCHARGE_OVERCURRENT)));
}

View File

@ -7,7 +7,7 @@
#define U_BAT_REGULATION_CORRIDOR 100
/* Initial charge battery voltage threshold (in mV). */
#define U_BAT_INITIAL_FULL 28800 // stop charging if battery voltage reaches this threshold
#define U_BAT_INITIAL_FULL 28600 // stop charging if battery voltage reaches this threshold
/* Transition to floating voltage levels after this time (in ms). */
#define INITIAL_CHARGE_HOLD_TIME 3600000
@ -51,6 +51,10 @@
/* Current at which the overload protection triggers (in mA). */
#define LOAD_CURRENT_LIMIT_MA 10000
/* Inrush tolerance time (in ms). Overload protection is not enforced for this
* time after load power-on. */
#define LOAD_CURRENT_INRUSH_TIME 10
/* Minimum voltage that the charge pump must produce above U_bat before any
* power FET is switched on (in mV). */
#define MIN_CHARGE_PUMP_EXCESS_VOLTAGE 10000
@ -59,6 +63,27 @@
#define LOAD_ON_DELAY 10000
/* Measurement Averaging:
* Alpha is specified in units of 1/1000. 1000 means that only the latest
* value is relevant, 0 means that the measurement has no influence. The latter
* is useless.
*
* The formula to calculate the next averaged value avg from a measurement meas is:
* avg[k] = meas * (alpha/1000) + avg[k-1] * (1 - alpha/1000)
*
* For overload protection (battery voltage, load current), the latest values
* are always used.
* */
/* Averaging factor for load current. */
#define AVG_ALPHA_I_SOLAR 10
#define AVG_ALPHA_I_LOAD 10
#define AVG_ALPHA_U_BAT 100
#define AVG_ALPHA_U_SW 100
#define AVG_ALPHA_U_SOLAR 100
#define AVG_ALPHA_TEMP 10
/* Generic configuration */
/* Time (in ms) to stay active in idle state before entering deep sleep. */

View File

@ -77,8 +77,8 @@ static void update_leds(uint64_t uptime_ms, struct MeasurementResult *meas_data)
static uint64_t charge_pulse_until = 0;
static uint64_t discharge_pulse_until = 0;
charge_in_mAs = fxp_add(charge_in_mAs, meas_data->i_solar);
charge_out_mAs = fxp_add(charge_out_mAs, meas_data->i_load);
charge_in_mAs = fxp_add(charge_in_mAs, meas_data->avg_i_solar);
charge_out_mAs = fxp_add(charge_out_mAs, meas_data->avg_i_load);
if(charge_in_mAs > FXP_FROM_INT(1000)) {
led_chplex_on(LED_CHPLEX_IDX_CHARGE_PULSE);
@ -156,6 +156,38 @@ static void report_status(struct MeasurementResult *meas_data)
}
static void report_averaged(struct MeasurementResult *meas_data)
{
char number[FXP_STR_MAXLEN];
rs485_enqueue("AVGD:");
fxp_format(meas_data->avg_u_bat, number, 3);
rs485_enqueue(number);
rs485_enqueue(":");
fxp_format(meas_data->avg_u_solar, number, 3);
rs485_enqueue(number);
rs485_enqueue(":");
fxp_format(meas_data->avg_u_sw, number, 3);
rs485_enqueue(number);
rs485_enqueue(":");
fxp_format(meas_data->avg_i_solar, number, 3);
rs485_enqueue(number);
rs485_enqueue(":");
fxp_format(meas_data->avg_i_load, number, 3);
rs485_enqueue(number);
rs485_enqueue(":");
fxp_format(meas_data->avg_temperature, number, 2);
rs485_enqueue(number);
rs485_enqueue("\n");
}
static void low_power_mode(uint32_t duration_sec)
{
// stop the systick counter for reliable deep sleep
@ -230,6 +262,11 @@ int main(void)
report_status(&meas_data);
}
// Send the averaged measurement data from the last cycle.
if(timebase_ms % 500 == 20) {
report_averaged(&meas_data);
}
measurement_wait_for_completion();
measurement_finalize(&meas_data);

View File

@ -8,12 +8,27 @@
#include "measurement.h"
#include "calibration.h"
#include "config.h"
#define ADC_NUM_CHANNELS 6
static volatile int16_t adc_values[ADC_NUM_CHANNELS];
static fxp_t calibration_factors[ADC_NUM_CHANNELS-1]; // all except temperature; filled in measurement_init()
fxp_t avg_alpha_i_solar;
fxp_t avg_alpha_i_load;
fxp_t avg_alpha_u_bat;
fxp_t avg_alpha_u_sw;
fxp_t avg_alpha_u_solar;
fxp_t avg_alpha_temp;
fxp_t avg_alpha_i_solar_inv;
fxp_t avg_alpha_i_load_inv;
fxp_t avg_alpha_u_bat_inv;
fxp_t avg_alpha_u_sw_inv;
fxp_t avg_alpha_u_solar_inv;
fxp_t avg_alpha_temp_inv;
/* Temperature sensor calibration value address */
#define TEMP110_CAL_ADDR ((uint16_t*) ((uint32_t) 0x1FFFF7C2))
#define TEMP30_CAL_ADDR ((uint16_t*) ((uint32_t) 0x1FFFF7B8))
@ -89,6 +104,23 @@ void measurement_init(void)
calibration_factors[ANALOG_INPUT_I_LOAD] =
fxp_div(FXP_FROM_INT(CAL_FACTOR_I_LOAD), FXP_FROM_INT(1000));
// Convert and precalculate coefficients for exponential averaging
avg_alpha_i_solar = fxp_div(FXP_FROM_INT(AVG_ALPHA_I_SOLAR), FXP_FROM_INT(1000));
avg_alpha_i_load = fxp_div(FXP_FROM_INT(AVG_ALPHA_I_LOAD), FXP_FROM_INT(1000));
avg_alpha_u_bat = fxp_div(FXP_FROM_INT(AVG_ALPHA_U_BAT), FXP_FROM_INT(1000));
avg_alpha_u_sw = fxp_div(FXP_FROM_INT(AVG_ALPHA_U_SW), FXP_FROM_INT(1000));
avg_alpha_u_solar = fxp_div(FXP_FROM_INT(AVG_ALPHA_U_SOLAR), FXP_FROM_INT(1000));
avg_alpha_temp = fxp_div(FXP_FROM_INT(AVG_ALPHA_TEMP), FXP_FROM_INT(1000));
// Inverse (1 - alpha) exponential averaging coefficients
avg_alpha_i_solar_inv = fxp_sub(FXP_FROM_INT(1), avg_alpha_i_solar);
avg_alpha_i_load_inv = fxp_sub(FXP_FROM_INT(1), avg_alpha_i_load);
avg_alpha_u_bat_inv = fxp_sub(FXP_FROM_INT(1), avg_alpha_u_bat);
avg_alpha_u_sw_inv = fxp_sub(FXP_FROM_INT(1), avg_alpha_u_sw);
avg_alpha_u_solar_inv = fxp_sub(FXP_FROM_INT(1), avg_alpha_u_solar);
avg_alpha_temp_inv = fxp_sub(FXP_FROM_INT(1), avg_alpha_temp);
// Prepare the ADC
adc_power_off(ADC1);
// enable the temperature sensor
@ -173,4 +205,35 @@ void measurement_finalize(struct MeasurementResult *result)
calibration_factors[ANALOG_INPUT_I_LOAD]);
result->temperature = calc_temperature(adc_values[5]);
/* calculate exponentially averaged values */
result->avg_u_bat =
fxp_add(
fxp_mult(avg_alpha_u_bat, result->u_bat),
fxp_mult(avg_alpha_u_bat_inv, result->avg_u_bat));
result->avg_u_solar =
fxp_add(
fxp_mult(avg_alpha_u_solar, result->u_solar),
fxp_mult(avg_alpha_u_solar_inv, result->avg_u_solar));
result->avg_u_sw =
fxp_add(
fxp_mult(avg_alpha_u_sw, result->u_sw),
fxp_mult(avg_alpha_u_sw_inv, result->avg_u_sw));
result->avg_i_solar =
fxp_add(
fxp_mult(avg_alpha_i_solar, result->i_solar),
fxp_mult(avg_alpha_i_solar_inv, result->avg_i_solar));
result->avg_i_load =
fxp_add(
fxp_mult(avg_alpha_i_load, result->i_load),
fxp_mult(avg_alpha_i_load_inv, result->avg_i_load));
result->avg_temperature =
fxp_add(
fxp_mult(avg_alpha_temp, result->temperature),
fxp_mult(avg_alpha_temp_inv, result->avg_temperature));
}

View File

@ -11,6 +11,14 @@ struct MeasurementResult
fxp_t i_solar; // in Ampere
fxp_t i_load; // in Ampere
fxp_t temperature; // in degrees Celsius
// exponentially averaged versions of the above
fxp_t avg_u_bat;
fxp_t avg_u_solar;
fxp_t avg_u_sw;
fxp_t avg_i_solar;
fxp_t avg_i_load;
fxp_t avg_temperature;
};
void measurement_init(void);