diff --git a/src/calibration.h b/src/calibration.h new file mode 100644 index 0000000..6fdd9cd --- /dev/null +++ b/src/calibration.h @@ -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 diff --git a/src/main.c b/src/main.c index d635928..7b5d2bc 100644 --- a/src/main.c +++ b/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++; diff --git a/src/measurement.c b/src/measurement.c index 4543162..be963e0 100644 --- a/src/measurement.c +++ b/src/measurement.c @@ -3,13 +3,16 @@ #include +#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]); }