Make measurement usable

- Run ADC Calibration on startup (without calibration, there is a huge
  offset error, making the current sensing unusable)
- Added calibration factors which allow to compensate for inaccuracies
  in the circuitry (example: 1% tolerance transistors)
- Send measured values via RS485
This commit is contained in:
Thomas Kolb 2021-06-06 12:17:38 +02:00
parent 2ca6c41260
commit 46d3e16104
3 changed files with 118 additions and 25 deletions

15
src/calibration.h Normal file
View file

@ -0,0 +1,15 @@
#ifndef CALIBRATION_H
#define CALIBRATION_H
/* All values defined here are divided by 1000 to determine the actual scaling
* factor. Therefore, if the voltage determined by the firmware is 2% lower
* than the actual voltage, you have to scale by 1.02 and therefore specify
* 1020 in this list. */
#define CAL_FACTOR_U_BAT 1000
#define CAL_FACTOR_U_SOLAR 1002
#define CAL_FACTOR_U_SW 1005
#define CAL_FACTOR_I_SOLAR 1015
#define CAL_FACTOR_I_LOAD 1000
#endif // CALIBRATION_H

View file

@ -18,19 +18,11 @@
#include "rs485.h"
#include "charge_pump.h"
#include "power_switch.h"
#include "measurement.h"
volatile int wait_frame = 1;
static void init_gpio(void)
{
// Set up UART TX on PB6 for debugging
/*gpio_mode_setup(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO6);
gpio_set_af(GPIOB, GPIO_AF0, GPIO6);*/
}
static void init_clock(void)
{
/* Set STM32 to 48 MHz. */
@ -49,6 +41,12 @@ static void init_clock(void)
// USART1 is used for RS485
rcc_periph_clock_enable(RCC_USART1);
// ADC1 for analog measuremnts
rcc_periph_clock_enable(RCC_ADC1);
// DMA1 is used for ADC data transfer
rcc_periph_clock_enable(RCC_DMA1);
}
@ -100,6 +98,38 @@ static bool ledtest(uint64_t timebase_ms)
}
static void report_status(struct MeasurementResult *meas_data)
{
char number[FXP_STR_MAXLEN];
rs485_enqueue("MEAS:");
fxp_format(meas_data->u_bat, number, 3);
rs485_enqueue(number);
rs485_enqueue(":");
fxp_format(meas_data->u_solar, number, 3);
rs485_enqueue(number);
rs485_enqueue(":");
fxp_format(meas_data->u_sw, number, 3);
rs485_enqueue(number);
rs485_enqueue(":");
fxp_format(meas_data->i_solar, number, 3);
rs485_enqueue(number);
rs485_enqueue(":");
fxp_format(meas_data->i_load, number, 3);
rs485_enqueue(number);
rs485_enqueue(":");
fxp_format(meas_data->temperature, number, 2);
rs485_enqueue(number);
rs485_enqueue("\n");
}
int main(void)
{
//uint32_t cpuload = 0;
@ -109,12 +139,14 @@ int main(void)
uint8_t switchtest = 0; // FIXME: delme: just for testing
struct MeasurementResult meas_data;
init_clock();
init_gpio();
rs485_init();
charge_pump_init();
power_switch_init();
measurement_init();
led_chplex_init();
led_chplex_on(LED_CHPLEX_IDX_SOLAR_ON);
@ -128,18 +160,25 @@ int main(void)
if(!ledtest_done) {
ledtest_done = ledtest(timebase_ms);
led_chplex_periodic();
} else if(!startup_done) {
charge_pump_start();
power_switch_load_on(); // FIXME: just for testing!
startup_done = true;
} else {
measurement_start();
// measurement takes some time, so do other things before waiting for
// completion. This is a good place for tasks that are not critical in
// latency, such as updating the LEDs, sending the state over RS485 etc.
if(timebase_ms % 500 == 0) {
led_chplex_toggle(LED_CHPLEX_IDX_DISCHARGE_PULSE);
}
// FIXME: just for testing
if(timebase_ms % 3000 == 0) {
if(timebase_ms % 10000 == 0) {
switchtest++;
if(switchtest & 0x01) {
@ -158,10 +197,24 @@ int main(void)
led_chplex_off(LED_CHPLEX_IDX_LOAD_ON);
}
}
}
led_chplex_periodic();
// Send the status data from the last cycle.
if(timebase_ms % 1000 == 0) {
report_status(&meas_data);
}
measurement_wait_for_completion();
if(timebase_ms % 1000 == 100) {
measurement_finalize(&meas_data);
}
// Check the protections directly after the measurement finishes. This
// ensures fast reaction time.
// TODO: check_protections(&meas_data);
}
timebase_ms++;
while(wait_frame) {

View file

@ -3,13 +3,16 @@
#include <fxp.h>
#include "libopencm3/stm32/f0/adc.h"
#include "pinout.h"
#include "measurement.h"
#include "calibration.h"
#define ADC_NUM_CHANNELS 6
volatile int16_t adc_values[ADC_NUM_CHANNELS];
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()
/* Temperature sensor calibration value address */
#define TEMP110_CAL_ADDR ((uint16_t*) ((uint32_t) 0x1FFFF7C2))
@ -39,22 +42,24 @@ static fxp_t adc_val_to_pin_voltage(uint16_t adc_val)
}
static fxp_t convert_voltage_divider(uint16_t adc_val, fxp_t r1, fxp_t r2)
static fxp_t convert_voltage_divider(uint16_t adc_val, fxp_t r1, fxp_t r2, fxp_t cal)
{
fxp_t pin_voltage = adc_val_to_pin_voltage(adc_val);
return fxp_mult(pin_voltage, fxp_div(fxp_add(r1, r2), r2));
fxp_t meas_voltage = fxp_mult(pin_voltage, fxp_div(fxp_add(r1, r2), r2));
return fxp_mult(meas_voltage, cal);
}
static fxp_t convert_ina139(uint16_t adc_val, fxp_t rshunt, fxp_t vgain)
static fxp_t convert_ina139(uint16_t adc_val, fxp_t rshunt, fxp_t vgain, fxp_t cal)
{
fxp_t pin_voltage = adc_val_to_pin_voltage(adc_val);
fxp_t shunt_voltage = fxp_div(pin_voltage, vgain);
fxp_t current = fxp_div(shunt_voltage, rshunt);
return current;
return fxp_mult(current, cal);
}
@ -69,6 +74,18 @@ void measurement_init(void)
16 // Temperature sensor
};
// Convert calibration factors to fixed-point numbers for direct use
calibration_factors[ANALOG_INPUT_U_BAT] =
fxp_div(FXP_FROM_INT(CAL_FACTOR_U_BAT), FXP_FROM_INT(1000));
calibration_factors[ANALOG_INPUT_U_SOLAR] =
fxp_div(FXP_FROM_INT(CAL_FACTOR_U_SOLAR), FXP_FROM_INT(1000));
calibration_factors[ANALOG_INPUT_U_SW] =
fxp_div(FXP_FROM_INT(CAL_FACTOR_U_SW), FXP_FROM_INT(1000));
calibration_factors[ANALOG_INPUT_I_SOLAR] =
fxp_div(FXP_FROM_INT(CAL_FACTOR_I_SOLAR), FXP_FROM_INT(1000));
calibration_factors[ANALOG_INPUT_I_LOAD] =
fxp_div(FXP_FROM_INT(CAL_FACTOR_I_LOAD), FXP_FROM_INT(1000));
adc_power_off(ADC1);
// enable the temperature sensor
@ -76,6 +93,7 @@ void measurement_init(void)
// configure ADC
//adc_enable_scan_mode(ADC1);
adc_set_clk_source(ADC1, ADC_CLKSOURCE_PCLK_DIV4); // -> 12 MHz @ 48 MHz ABP
adc_set_single_conversion_mode(ADC1);
adc_set_resolution(ADC1, ADC_RESOLUTION_12BIT);
adc_set_sample_time_on_all_channels(ADC1, ADC_SMPR_SMP_239DOT5);
@ -83,6 +101,8 @@ void measurement_init(void)
adc_set_right_aligned(ADC1);
adc_set_regular_sequence(ADC1, ADC_NUM_CHANNELS, channels);
adc_calibrate(ADC1);
// configure DMA for ADC
//nvic_enable_irq(NVIC_DMA1_STREAM5_IRQ);
@ -126,23 +146,28 @@ void measurement_finalize(struct MeasurementResult *result)
{
result->u_bat = convert_voltage_divider(adc_values[ANALOG_INPUT_U_BAT],
FXP_FROM_INT(220),
FXP_FROM_INT(22));
FXP_FROM_INT(22),
calibration_factors[ANALOG_INPUT_U_BAT]);
result->u_solar = convert_voltage_divider(adc_values[ANALOG_INPUT_U_SOLAR],
FXP_FROM_INT(330),
FXP_FROM_INT(22));
FXP_FROM_INT(22),
calibration_factors[ANALOG_INPUT_U_SOLAR]);
result->u_sw = convert_voltage_divider(adc_values[ANALOG_INPUT_U_SW],
FXP_FROM_INT(1000),
FXP_FROM_INT(47));
FXP_FROM_INT(47),
calibration_factors[ANALOG_INPUT_U_SW]);
result->i_solar = convert_ina139(adc_values[ANALOG_INPUT_I_SOLAR],
fxp_div(FXP_FROM_INT(2), FXP_FROM_INT(1000)),
FXP_FROM_INT(56));
FXP_FROM_INT(56),
calibration_factors[ANALOG_INPUT_I_SOLAR]);
result->i_load = convert_ina139(adc_values[ANALOG_INPUT_I_LOAD],
fxp_div(FXP_FROM_INT(5), FXP_FROM_INT(1000)),
FXP_FROM_INT(56));
FXP_FROM_INT(56),
calibration_factors[ANALOG_INPUT_I_LOAD]);
result->temperature = calc_temperature(adc_values[5]);
}