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:
parent
2ca6c41260
commit
46d3e16104
15
src/calibration.h
Normal file
15
src/calibration.h
Normal 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
|
79
src/main.c
79
src/main.c
|
@ -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,9 +197,23 @@ int main(void)
|
|||
led_chplex_off(LED_CHPLEX_IDX_LOAD_ON);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
led_chplex_periodic();
|
||||
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++;
|
||||
|
||||
|
|
|
@ -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(330),
|
||||
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(1000),
|
||||
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]);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue