From 49bd9247e022988f51946c1f0fe53aa8f3c54163 Mon Sep 17 00:00:00 2001 From: Thomas Kolb Date: Fri, 18 Jun 2021 23:37:16 +0200 Subject: [PATCH] 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). --- src/charge_control.c | 15 ++++++----- src/config.h | 27 ++++++++++++++++++- src/main.c | 41 ++++++++++++++++++++++++++-- src/measurement.c | 63 ++++++++++++++++++++++++++++++++++++++++++++ src/measurement.h | 8 ++++++ 5 files changed, 144 insertions(+), 10 deletions(-) diff --git a/src/charge_control.c b/src/charge_control.c index 516223a..5f1d6a0 100644 --- a/src/charge_control.c +++ b/src/charge_control.c @@ -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))); } diff --git a/src/config.h b/src/config.h index 8ac166b..7676c5b 100644 --- a/src/config.h +++ b/src/config.h @@ -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. */ diff --git a/src/main.c b/src/main.c index 4e8812d..580e7a5 100644 --- a/src/main.c +++ b/src/main.c @@ -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); diff --git a/src/measurement.c b/src/measurement.c index dd112fe..713e8cb 100644 --- a/src/measurement.c +++ b/src/measurement.c @@ -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)); } diff --git a/src/measurement.h b/src/measurement.h index 1999266..602f15b 100644 --- a/src/measurement.h +++ b/src/measurement.h @@ -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);