Compare commits

..

No commits in common. "main" and "charge_control" have entirely different histories.

27 changed files with 180 additions and 1476 deletions

3
.gitignore vendored
View file

@ -4,6 +4,3 @@
# Vim swap files # Vim swap files
.*.sw? .*.sw?
# generated config HEX files
utils/*.hex

View file

@ -32,7 +32,7 @@ LDFLAGS+=--static \
-nostartfiles -Wl,--gc-sections \ -nostartfiles -Wl,--gc-sections \
-mthumb -mcpu=cortex-m0 -mthumb -mfloat-abi=soft -mthumb -mcpu=cortex-m0 -mthumb -mfloat-abi=soft
# the LD script (RAM-only does not work because the code is too large) # the LD script
#LDFLAGS+=-Tldscripts/lnsc-2420-$(BUILD).ld #LDFLAGS+=-Tldscripts/lnsc-2420-$(BUILD).ld
LDFLAGS+=-Tldscripts/lnsc-2420-release.ld LDFLAGS+=-Tldscripts/lnsc-2420-release.ld

View file

@ -7,7 +7,6 @@
MEMORY MEMORY
{ {
/*rom (rx) : ORIGIN = 0x08000000, LENGTH = 64K*/ /*rom (rx) : ORIGIN = 0x08000000, LENGTH = 64K*/
conf (r) : ORIGIN = 0x08007c00, LENGTH = 1K
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 4K ram (rwx) : ORIGIN = 0x20000000, LENGTH = 4K
} }
@ -97,10 +96,6 @@ SECTIONS
. = ALIGN(4); . = ALIGN(4);
end = .; end = .;
.conf : {
__conf_start = .;
} >conf
} }
PROVIDE(_stack = ORIGIN(ram) + LENGTH(ram)); PROVIDE(_stack = ORIGIN(ram) + LENGTH(ram));

View file

@ -3,18 +3,10 @@
/* Define memory regions. */ /* Define memory regions. */
MEMORY MEMORY
{ {
rom (rx) : ORIGIN = 0x08000000, LENGTH = 31K rom (rx) : ORIGIN = 0x08000000, LENGTH = 32K
conf (r) : ORIGIN = 0x08007c00, LENGTH = 1K
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 4K ram (rwx) : ORIGIN = 0x20000000, LENGTH = 4K
} }
/* Include the common ld script. */ /* Include the common ld script. */
INCLUDE cortex-m-generic.ld INCLUDE cortex-m-generic.ld
SECTIONS
{
.conf : {
__conf_start = .;
. = ALIGN(4);
} >conf
}

@ -1 +1 @@
Subproject commit c82c7406aa57948d1adabfdc51e8577ae37e99d4 Subproject commit 3b89fc5999874c49f6f5be65bbd5e75f7d77469c

View file

@ -1,35 +0,0 @@
#include <libopencm3/stm32/gpio.h>
#include "pinout.h"
#include "addon_io.h"
static const uint32_t OUTPUT_LIST[2] = {ADDON_ISO_IO_OUT1, ADDON_ISO_IO_OUT2};
void addon_io_init(void)
{
// Isolated outputs configuration: output, initially low = off
gpio_clear(ADDON_ISO_IO_PORT, ADDON_ISO_IO_OUT1 | ADDON_ISO_IO_OUT2);
gpio_mode_setup(ADDON_ISO_IO_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, ADDON_ISO_IO_OUT1 | ADDON_ISO_IO_OUT2);
// Isolated inputs configuration: input, no pull resistors
gpio_mode_setup(ADDON_ISO_IO_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, ADDON_ISO_IO_IN);
}
void addon_io_iso_out_on(uint8_t idx)
{
gpio_set(ADDON_ISO_IO_PORT, OUTPUT_LIST[idx]);
}
void addon_io_iso_out_off(uint8_t idx)
{
gpio_clear(ADDON_ISO_IO_PORT, OUTPUT_LIST[idx]);
}
bool addon_io_read_iso_in(void)
{
return gpio_get(ADDON_ISO_IO_PORT, ADDON_ISO_IO_IN) != 0;
}

View file

@ -1,14 +0,0 @@
#ifndef ADDON_IO_H
#define ADDON_IO_H
#include <stdint.h>
#include <stdbool.h>
void addon_io_init(void);
void addon_io_iso_out_on(uint8_t idx);
void addon_io_iso_out_off(uint8_t idx);
bool addon_io_read_iso_in(void);
#endif // ADDON_IO_H

View file

@ -1,284 +0,0 @@
#include <libopencm3/stm32/i2c.h>
#include <libopencm3/stm32/gpio.h>
#include "i2c_dma.h"
#include "pinout.h"
#include "bmp280_comp.h"
#include "bmp280.h"
#define BMP280_7BIT_ADDR 0x76
typedef enum {
READY,
SEND_REQUEST,
WAIT_MEASUREMENT_COMPLETE,
READ_RESULTS,
COMPENSATE_TEMPERATURE,
COMPENSATE_PRESSURE,
ERROR // set by the I²C callback in case of errors/timeout
} bmp280_readout_state_t;
static bmp280_readout_state_t m_readout_state;
// calibration value cache, shared with bmp280_comp.c
extern uint16_t dig_T1;
extern int16_t dig_T2;
extern int16_t dig_T3;
extern uint16_t dig_P1;
extern int16_t dig_P2;
extern int16_t dig_P3;
extern int16_t dig_P4;
extern int16_t dig_P5;
extern int16_t dig_P6;
extern int16_t dig_P7;
extern int16_t dig_P8;
extern int16_t dig_P9;
static fxp_t m_last_temperature = 0;
static fxp_t m_last_pressure = 0;
static bool m_measurements_valid = false;
static int32_t m_temp_raw;
static int32_t m_press_raw;
static void cb_i2c_dma(i2c_dma_evt_t evt, const uint8_t *rdata, size_t rlen);
bool bmp280_init(void)
{
m_measurements_valid = false;
// configure pins for I2C1
gpio_set_af(BMP280_I2C_PORT, GPIO_AF4, BMP280_I2C_SCL | BMP280_I2C_SDA);
gpio_mode_setup(BMP280_I2C_PORT, GPIO_MODE_AF, GPIO_PUPD_PULLUP, BMP280_I2C_SCL | BMP280_I2C_SDA);
// Set up I²C
i2c_reset(I2C1);
i2c_set_speed(I2C1, i2c_speed_sm_100k, 48);
i2c_peripheral_enable(I2C1);
// Set up the Chip
uint8_t wdata[2];
uint8_t rdata[32];
uint8_t wsize, rsize;
// check for chip presence by accessing the chip id register (not a proper read!)
i2c_set_7bit_address(I2C1, BMP280_7BIT_ADDR);
i2c_set_write_transfer_dir(I2C1);
i2c_set_bytes_to_transfer(I2C1, 1);
i2c_enable_autoend(I2C1);
i2c_send_start(I2C1);
uint32_t timeout = 1000000;
while(--timeout && !i2c_transmit_int_status(I2C1)); // wait for the address transfer to complete
i2c_send_data(I2C1, 0xD0);
if((timeout == 0) || i2c_nack(I2C1)) {
// something went wrong during communication
goto err_out;
}
// read the chip ID
wsize = 0;
wdata[wsize++] = 0xD0;
rsize = 1;
i2c_transfer7(I2C1, BMP280_7BIT_ADDR, wdata, wsize, rdata, rsize);
if(rdata[0] != 0x58) {
// unexpected chip id
goto err_out;
}
// read calibration data
wsize = 0;
wdata[wsize++] = 0x88; // first calibration data register of first block
rsize = 0xA1 + 1 - 0x88;
i2c_transfer7(I2C1, BMP280_7BIT_ADDR, wdata, wsize, rdata, rsize);
dig_T1 = (((uint16_t)rdata[ 1]) << 8) | rdata[ 0]; // 0x88, 0x89
dig_T2 = (((int16_t)(int8_t)rdata[ 3]) << 8) | rdata[ 2]; // 0x8A, 0x8B
dig_T3 = (((int16_t)(int8_t)rdata[ 5]) << 8) | rdata[ 4]; // 0x8C, 0x8D
dig_P1 = (((uint16_t)rdata[ 7]) << 8) | rdata[ 6]; // 0x8E, 0x8F
dig_P2 = (((int16_t)(int8_t)rdata[ 9]) << 8) | rdata[ 8]; // 0x90, 0x91
dig_P3 = (((int16_t)(int8_t)rdata[11]) << 8) | rdata[10]; // 0x92, 0x93
dig_P4 = (((int16_t)(int8_t)rdata[13]) << 8) | rdata[12]; // 0x94, 0x95
dig_P5 = (((int16_t)(int8_t)rdata[15]) << 8) | rdata[14]; // 0x96, 0x97
dig_P6 = (((int16_t)(int8_t)rdata[17]) << 8) | rdata[16]; // 0x98, 0x99
dig_P7 = (((int16_t)(int8_t)rdata[19]) << 8) | rdata[18]; // 0x9A, 0x9B
dig_P8 = (((int16_t)(int8_t)rdata[21]) << 8) | rdata[20]; // 0x9C, 0x9D
dig_P9 = (((int16_t)(int8_t)rdata[23]) << 8) | rdata[22]; // 0x9E, 0x9F
i2c_dma_init(cb_i2c_dma);
return true;
err_out:
return false;
}
static void trigger_read_status_register(void)
{
uint8_t wdata[1];
uint8_t wsize = 0, rsize;
wdata[wsize++] = 0xF3; // status register
rsize = 1;
i2c_dma_start_transfer7(I2C1, BMP280_7BIT_ADDR, wdata, wsize, rsize);
}
static void trigger_read_results(void)
{
uint8_t wdata[1];
uint8_t wsize = 0, rsize;
wdata[wsize++] = 0xF7;
rsize = 8;
i2c_dma_start_transfer7(I2C1, BMP280_7BIT_ADDR, wdata, wsize, rsize);
}
static void cb_i2c_dma(i2c_dma_evt_t evt, const uint8_t *rdata, size_t rlen)
{
(void)rlen;
switch(evt) {
case I2C_DMA_EVT_TRANSFER_COMPLETE:
switch(m_readout_state) {
case SEND_REQUEST:
m_readout_state = WAIT_MEASUREMENT_COMPLETE;
trigger_read_status_register();
break;
case WAIT_MEASUREMENT_COMPLETE:
if((rdata[0] & 0x09) != 0) {
// either "measuring" or "im_update" is active, so measurement is not complete yet.
// try again:
trigger_read_status_register();
} else {
m_readout_state = READ_RESULTS;
trigger_read_results();
}
break;
case READ_RESULTS:
m_press_raw =
((int32_t)rdata[0] << 12) // press_msb (0xF7)
| ((int32_t)rdata[1] << 4) // press_lsb (0xF8)
| ((int32_t)rdata[2] >> 4); // press_xlsb (0xF9)
m_temp_raw =
((int32_t)rdata[3] << 12) // temp_msb (0xFA)
| ((int32_t)rdata[4] << 4) // temp_lsb (0xFB)
| ((int32_t)rdata[5] >> 4); // temp_xlsb (0xFC)
m_readout_state = COMPENSATE_TEMPERATURE;
break;
default:
// should never happen
while(1);
break;
}
break;
case I2C_DMA_EVT_TIMEOUT:
case I2C_DMA_EVT_NAK:
m_readout_state = ERROR;
break;
}
}
bool bmp280_loop(void)
{
bool retval = false;
switch(m_readout_state) {
case READY:
// nothing to do in this state
break;
case SEND_REQUEST:
case WAIT_MEASUREMENT_COMPLETE:
case READ_RESULTS:
// handled in cb_i2c_dma exclusively
break;
case COMPENSATE_TEMPERATURE:
m_last_temperature = bmp280_comp_temperature(m_temp_raw);
m_readout_state = COMPENSATE_PRESSURE;
break;
case COMPENSATE_PRESSURE:
m_last_pressure = bmp280_comp_pressure(m_press_raw);
m_readout_state = READY;
m_measurements_valid = true;
retval = true; // measurement complete
break;
case ERROR:
m_readout_state = READY;
m_measurements_valid = false;
retval = true; // measurement cycle terminated
break;
}
if(m_readout_state != READY) {
i2c_dma_loop();
}
return retval;
}
bool bmp280_start_measurement(void)
{
uint8_t wdata[2];
uint8_t wsize = 0;
if(m_readout_state != READY) {
return false; // still busy
}
wdata[wsize++] = 0xF4; // measurement control register
wdata[wsize++] = (0x01 << 5) | (0x01 << 2) | 0x01; // pressure x1, temp x1, forced mode
i2c_dma_start_transfer7(I2C1, BMP280_7BIT_ADDR, wdata, wsize, 0);
m_readout_state = SEND_REQUEST;
return true;
}
bool bmp280_are_measurements_valid(void)
{
return m_measurements_valid;
}
fxp_t bmp280_get_temperature(void)
{
return m_last_temperature;
}
fxp_t bmp280_get_pressure(void)
{
return m_last_pressure;
}

View file

@ -1,18 +0,0 @@
#ifndef BMP280_H
#define BMP280_H
#include <stdbool.h>
#include <stdint.h>
#include <fxp.h>
bool bmp280_init(void);
bool bmp280_loop(void); // returns true when measurement cycle has terminated (successfully or with error)
bool bmp280_start_measurement(void);
bool bmp280_are_measurements_valid(void);
fxp_t bmp280_get_temperature(void);
fxp_t bmp280_get_pressure(void);
#endif // BMP280_H

View file

@ -1,77 +0,0 @@
/* BMP280 value compensation code, from the datasheet. */
#include "bmp280_comp.h"
// calibration value cache. Values are set externally in bmp280.c
uint16_t dig_T1;
int16_t dig_T2;
int16_t dig_T3;
uint16_t dig_P1;
int16_t dig_P2;
int16_t dig_P3;
int16_t dig_P4;
int16_t dig_P5;
int16_t dig_P6;
int16_t dig_P7;
int16_t dig_P8;
int16_t dig_P9;
typedef int32_t BMP280_S32_t;
typedef uint32_t BMP280_U32_t;
typedef int64_t BMP280_S64_t;
// Returns temperature in DegC, resolution is 0.01 DegC. Output value of “5123” equals 51.23 DegC.
// t_fine carries fine temperature as global value
BMP280_S32_t t_fine;
static BMP280_S32_t bmp280_compensate_T_int32(BMP280_S32_t adc_T)
{
BMP280_S32_t var1, var2, T;
var1 = ((((adc_T>>3) - ((BMP280_S32_t)dig_T1<<1))) * ((BMP280_S32_t)dig_T2)) >> 11;
var2 = (((((adc_T>>4) - ((BMP280_S32_t)dig_T1)) * ((adc_T>>4) - ((BMP280_S32_t)dig_T1))) >> 12) * ((BMP280_S32_t)dig_T3)) >> 14;
t_fine = var1 + var2;
T = (t_fine * 5 + 128) >> 8;
return T;
}
// Returns pressure in Pa as unsigned 32 bit integer in Q24.8 format (24 integer bits and 8 fractional bits).
// Output value of “24674867” represents 24674867/256 = 96386.2 Pa = 963.862 hPa
static BMP280_U32_t bmp280_compensate_P_int64(BMP280_S32_t adc_P)
{
BMP280_S64_t var1, var2, p;
var1 = ((BMP280_S64_t)t_fine) - 128000;
var2 = var1 * var1 * (BMP280_S64_t)dig_P6;
var2 = var2 + ((var1*(BMP280_S64_t)dig_P5)<<17);
var2 = var2 + (((BMP280_S64_t)dig_P4)<<35);
var1 = ((var1 * var1 * (BMP280_S64_t)dig_P3)>>8) + ((var1 * (BMP280_S64_t)dig_P2)<<12);
var1 = (((((BMP280_S64_t)1)<<47)+var1))*((BMP280_S64_t)dig_P1)>>33;
if (var1 == 0) {
return 0; // avoid exception caused by division by zero
}
p = 1048576-adc_P;
p = (((p<<31)-var2)*3125)/var1;
var1 = (((BMP280_S64_t)dig_P9) * (p>>13) * (p>>13)) >> 25;
var2 = (((BMP280_S64_t)dig_P8) * p) >> 19;
p = ((p + var1 + var2) >> 8) + (((BMP280_S64_t)dig_P7)<<4);
return (BMP280_U32_t)p;
}
fxp_t bmp280_comp_temperature(int32_t adc)
{
int32_t compensated = bmp280_compensate_T_int32(adc);
return ((fxp_t)compensated << POINTPOS) / 100L;
}
fxp_t bmp280_comp_pressure(int32_t adc)
{
uint32_t compensated = bmp280_compensate_P_int64(adc);
return (fxp_t)(((uint64_t)compensated << (POINTPOS - 8L)) / 100);
}

View file

@ -1,23 +0,0 @@
#ifndef BMP280_COMP_H
#define BMP280_COMP_H
#include <stdint.h>
#include <fxp.h>
/*!@brief Calculate temperature from BMP280 raw sensor value.
* @returns The temperature in °C.
*/
fxp_t bmp280_comp_temperature(int32_t adc);
/*!@brief Calculate relative humidity from BMP280 raw sensor value.
* @returns The relative humidity in %.
*/
fxp_t bmp280_comp_humidity(int32_t adc);
/*!@brief Calculate pressure from BMP280 raw sensor value.
* @returns The pressure in hPa.
*/
fxp_t bmp280_comp_pressure(int32_t adc);
#endif // BMP280_COMP_H

View file

@ -6,24 +6,10 @@
* than the actual voltage, you have to scale by 1.02 and therefore specify * than the actual voltage, you have to scale by 1.02 and therefore specify
* 1020 in this list. */ * 1020 in this list. */
/* Values for the device at the B26 tower */ #define CAL_FACTOR_U_BAT 1000
#define CAL_FACTOR_U_SOLAR 1002
#if 0 #define CAL_FACTOR_U_SW 1005
#define CAL_FACTOR_U_BAT 994
#define CAL_FACTOR_U_SOLAR 997
#define CAL_FACTOR_U_SW 996
#define CAL_FACTOR_I_SOLAR 1015 #define CAL_FACTOR_I_SOLAR 1015
#define CAL_FACTOR_I_LOAD 1000 #define CAL_FACTOR_I_LOAD 1000
#endif
/* Values for the development device */
#if 1
#define CAL_FACTOR_U_BAT 1012
#define CAL_FACTOR_U_SOLAR 1015
#define CAL_FACTOR_U_SW 1006
#define CAL_FACTOR_I_SOLAR 3980
#define CAL_FACTOR_I_LOAD 1000
#endif
#endif // CALIBRATION_H #endif // CALIBRATION_H

View file

@ -2,33 +2,28 @@
#include <fxp.h> #include <fxp.h>
#include "bmp280.h"
#include "power_switch.h" #include "power_switch.h"
#include "measurement.h" #include "measurement.h"
#include "charge_pump.h" #include "charge_pump.h"
#include "rs485.h" #include "rs485.h"
#include "flash_config.h" #include "config.h"
#include "addon_io.h"
#include "charge_control.h" #include "charge_control.h"
static const char *CHARGE_STATE_TEXT[CHARGE_NUM_STATES] = { static const char *CHARGE_STATE_TEXT[] = {
"WAIT_CHARGEPUMP", "WAIT_CHARGEPUMP",
"INITIAL", "INITIAL",
"INITIAL_HOLD",
"TRANSITION", "TRANSITION",
"FLOAT", "FLOAT",
"SLEEP", "SLEEP",
"HIGH_INTERNAL_TEMPERATURE", "HIGH_TEMPERATURE"
"LOW_EXTERNAL_TEMPERATURE"
}; };
static const char *DISCHARGE_STATE_TEXT[DISCHARGE_NUM_STATES] = { static const char *DISCHARGE_STATE_TEXT[] = {
"WAIT_CHARGEPUMP", "WAIT_CHARGEPUMP",
"OK", "OK",
"VOLTAGE_LOW", "VOLTAGE_LOW",
"OVERCURRENT", "OVERCURRENT"
"OVERCURRENT_DELAY"
}; };
static enum ChargeState charge_state; static enum ChargeState charge_state;
@ -45,8 +40,6 @@ static fxp_t u_bat_regulation_corridor;
static fxp_t u_bat_initial_full; static fxp_t u_bat_initial_full;
static fxp_t u_bat_initial_low; static fxp_t u_bat_initial_low;
static fxp_t u_bat_initial_hold_cancel;
static fxp_t u_bat_float_full; static fxp_t u_bat_float_full;
static fxp_t u_bat_float_low; static fxp_t u_bat_float_low;
@ -56,19 +49,13 @@ static fxp_t u_bat_load_on;
static fxp_t u_bat_load_off; static fxp_t u_bat_load_off;
static fxp_t load_current_limit; static fxp_t load_current_limit;
static fxp_t load_current_limit_delay;
static fxp_t internal_temperature_limit; static fxp_t internal_temperature_limit;
static fxp_t internal_temperature_recovery; static fxp_t internal_temperature_recovery;
static fxp_t external_temperature_limit;
static fxp_t external_temperature_recovery;
static fxp_t sleep_solar_current; static fxp_t sleep_solar_current;
static fxp_t sleep_solar_excess_voltage; static fxp_t sleep_solar_excess_voltage;
static uint32_t overload_retry_time;
static enum ChargeState control_solar_charging( static enum ChargeState control_solar_charging(
fxp_t corridor_high, fxp_t corridor_high,
@ -96,21 +83,15 @@ static enum ChargeState control_solar_charging(
solar_switch_onoff_duration = 0; solar_switch_onoff_duration = 0;
} }
// internal temperature limit: prevent overheating of the power transistors. // temperature limit
if(meas->avg_temperature > internal_temperature_limit) { if(meas->avg_temperature > internal_temperature_limit) {
return CHARGE_HIGH_INTERNAL_TEMPERATURE; return CHARGE_HIGH_TEMPERATURE;
}
// external temperature limit: prevent charging the battery if temperature is too low.
if(bmp280_are_measurements_valid() &&
bmp280_get_temperature() < external_temperature_limit) {
return CHARGE_LOW_EXTERNAL_TEMPERATURE;
} }
// low-current limit (go to sleep at night) // low-current limit (go to sleep at night)
if((time_in_state > FLASH_CONFIG_SLEEP_STATE_DELAY) if((time_in_state > SLEEP_STATE_DELAY)
&& (current_switch_state == true) && (current_switch_state == true)
&& (solar_switch_onoff_duration > FLASH_CONFIG_SLEEP_SWITCH_DELAY) && (solar_switch_onoff_duration > SLEEP_SWITCH_DELAY)
&& (meas->avg_i_solar < sleep_solar_current)) { && (meas->avg_i_solar < sleep_solar_current)) {
return CHARGE_SLEEP; return CHARGE_SLEEP;
} }
@ -146,58 +127,28 @@ static void solar_fsm_update(uint64_t uptime_ms, struct MeasurementResult *meas)
charge_state, charge_state,
charge_time_in_state); charge_time_in_state);
// switch to hold state when high threshold is reached // time limit for initial charging
if(meas->u_bat >= u_bat_initial_full) { if(charge_time_in_state > INITIAL_CHARGE_HOLD_TIME) {
charge_state = CHARGE_INITIAL_HOLD;
}
break;
case CHARGE_INITIAL_HOLD:
charge_state = control_solar_charging(
u_bat_initial_full,
u_bat_initial_low,
uptime_ms,
meas,
charge_state,
charge_time_in_state);
// cancel charge hold if battery voltage is below threshold
if(meas->u_bat <= u_bat_initial_hold_cancel) {
charge_state = CHARGE_INITIAL;
}
// time limit for initial hold charging
if(charge_time_in_state > FLASH_CONFIG_INITIAL_CHARGE_HOLD_TIME) {
charge_state = CHARGE_TRANSITION; charge_state = CHARGE_TRANSITION;
} }
break; break;
case CHARGE_TRANSITION: case CHARGE_TRANSITION:
if(charge_time_in_state < FLASH_CONFIG_INITIAL_TO_FLOAT_TRANSITION_TIME) { // FIXME: dynamically adjust thresholds
// dynamically adjust thresholds
fxp_t u_bat_full =
fxp_add(u_bat_initial_full,
fxp_mult(
fxp_sub(u_bat_float_full, u_bat_initial_full),
fxp_div(charge_time_in_state, FLASH_CONFIG_INITIAL_TO_FLOAT_TRANSITION_TIME)));
fxp_t u_bat_low = fxp_sub(u_bat_full, u_bat_regulation_corridor);
charge_state = control_solar_charging( charge_state = control_solar_charging(
u_bat_full, u_bat_float_full,
u_bat_low, u_bat_float_low,
uptime_ms, uptime_ms,
meas, meas,
charge_state, charge_state,
charge_time_in_state); charge_time_in_state);
} else {
// time limit for transition to float charging reached // time limit for transition to float charging
if(charge_time_in_state > INITIAL_TO_FLOAT_TRANSITION_TIME) {
charge_state = CHARGE_FLOAT; charge_state = CHARGE_FLOAT;
break; break;
} }
break; break;
case CHARGE_FLOAT: case CHARGE_FLOAT:
@ -211,7 +162,13 @@ static void solar_fsm_update(uint64_t uptime_ms, struct MeasurementResult *meas)
// temperature limit // temperature limit
if(meas->temperature > internal_temperature_limit) { if(meas->temperature > internal_temperature_limit) {
charge_state = CHARGE_HIGH_INTERNAL_TEMPERATURE; charge_state = CHARGE_HIGH_TEMPERATURE;
break;
}
// low-current limit (go to sleep at night)
if(meas->i_solar < sleep_solar_current) {
charge_state = CHARGE_SLEEP;
break; break;
} }
break; break;
@ -233,7 +190,7 @@ static void solar_fsm_update(uint64_t uptime_ms, struct MeasurementResult *meas)
break; break;
case CHARGE_HIGH_INTERNAL_TEMPERATURE: case CHARGE_HIGH_TEMPERATURE:
if(charge_state_entered) { if(charge_state_entered) {
power_switch_solar_off(); power_switch_solar_off();
} }
@ -244,27 +201,6 @@ static void solar_fsm_update(uint64_t uptime_ms, struct MeasurementResult *meas)
} }
break; break;
case CHARGE_LOW_EXTERNAL_TEMPERATURE:
if(charge_state_entered) {
power_switch_solar_off();
// switch on the heater via the isolated I/O addon board
addon_io_iso_out_on(0);
}
// this state can only be entered if the BMP280 measurement is valid, so
// no need to check it again here.
if(bmp280_get_temperature() > external_temperature_recovery) {
charge_state = CHARGE_WAIT_CHARGEPUMP;
// switch the heater off again when this state is left
addon_io_iso_out_off(0);
break;
}
break;
default: default:
// unknown state // unknown state
break; break;
@ -297,24 +233,11 @@ static void load_fsm_update(uint64_t uptime_ms, struct MeasurementResult *meas)
} }
if((meas->i_load > load_current_limit) if((meas->i_load > load_current_limit)
&& (discharge_time_in_state > FLASH_CONFIG_LOAD_CURRENT_INRUSH_TIME)) { && (discharge_time_in_state > LOAD_CURRENT_INRUSH_TIME)) {
if(load_current_limit_delay == 0) {
// switch off immediately
power_switch_load_off();
discharge_state = DISCHARGE_OVERCURRENT; discharge_state = DISCHARGE_OVERCURRENT;
} else {
discharge_state = DISCHARGE_OVERCURRENT_DELAY;
}
} }
if((overload_retry_time != FLASH_CONFIG_OVERLOAD_RETRY_TIME) && if(meas->u_bat < u_bat_load_off) {
(discharge_time_in_state > 300000)) {
// overload did not trigger for 5 minutes, so we assume its stable and
// reset the retry time delay.
overload_retry_time = FLASH_CONFIG_OVERLOAD_RETRY_TIME;
}
if(meas->avg_u_bat < u_bat_load_off) {
discharge_state = DISCHARGE_VOLTAGE_LOW; discharge_state = DISCHARGE_VOLTAGE_LOW;
} }
break; break;
@ -326,38 +249,19 @@ static void load_fsm_update(uint64_t uptime_ms, struct MeasurementResult *meas)
} }
// Can only switch on again after a specific amount of time has passed // Can only switch on again after a specific amount of time has passed
if((meas->avg_u_bat > u_bat_load_on) if((meas->u_bat > u_bat_load_on)
&& (discharge_time_in_state > FLASH_CONFIG_LOAD_ON_DELAY)) { && (discharge_time_in_state > LOAD_ON_DELAY)) {
discharge_state = DISCHARGE_WAIT_CHARGEPUMP;
}
break;
case DISCHARGE_OVERCURRENT_DELAY:
if(meas->i_load < load_current_limit) {
// current recovered
discharge_state = DISCHARGE_OK; discharge_state = DISCHARGE_OK;
} else if(discharge_time_in_state >= FLASH_CONFIG_OVERLOAD_DELAY_TIME) {
// switch off immediately
power_switch_load_off();
discharge_state = DISCHARGE_OVERCURRENT;
} }
break; break;
case DISCHARGE_OVERCURRENT: case DISCHARGE_OVERCURRENT:
// Current limit reached // Battery voltage is too low, so keep the load switched off
if(discharge_state_entered) { if(discharge_state_entered) {
power_switch_load_off(); power_switch_load_off();
} }
// Overload recovery // no way out except reset
if(discharge_time_in_state >= overload_retry_time) {
// double the overload retry time for the next turn if it is less than 7 days
if(overload_retry_time < (7*24*3600*1000)) {
overload_retry_time *= 2;
}
discharge_state = DISCHARGE_WAIT_CHARGEPUMP;
}
break; break;
default: default:
@ -376,35 +280,28 @@ void charge_control_init(void)
discharge_state_entered = true; discharge_state_entered = true;
/* calculate thresholds */ /* calculate thresholds */
u_bat_regulation_corridor = fxp_div(FXP_FROM_INT(FLASH_CONFIG_U_BAT_REGULATION_CORRIDOR), u_bat_regulation_corridor = fxp_div(FXP_FROM_INT(U_BAT_REGULATION_CORRIDOR),
FXP_FROM_INT(1000)); FXP_FROM_INT(1000));
u_bat_initial_full = fxp_div(FXP_FROM_INT(FLASH_CONFIG_U_BAT_INITIAL_FULL), FXP_FROM_INT(1000)); u_bat_initial_full = fxp_div(FXP_FROM_INT(U_BAT_INITIAL_FULL), FXP_FROM_INT(1000));
u_bat_initial_low = fxp_sub(u_bat_initial_full, u_bat_regulation_corridor); u_bat_initial_low = fxp_sub(u_bat_initial_full, u_bat_regulation_corridor);
u_bat_initial_hold_cancel = fxp_div(FXP_FROM_INT(FLASH_CONFIG_U_BAT_INITIAL_HOLD_CANCEL), FXP_FROM_INT(1000)); u_bat_float_full = fxp_div(FXP_FROM_INT(U_BAT_FLOAT_FULL), FXP_FROM_INT(1000));
u_bat_float_full = fxp_div(FXP_FROM_INT(FLASH_CONFIG_U_BAT_FLOAT_FULL), FXP_FROM_INT(1000));
u_bat_float_low = fxp_sub(u_bat_float_full, u_bat_regulation_corridor); u_bat_float_low = fxp_sub(u_bat_float_full, u_bat_regulation_corridor);
min_charge_pump_excess_voltage = fxp_div(FXP_FROM_INT(FLASH_CONFIG_MIN_CHARGE_PUMP_EXCESS_VOLTAGE), min_charge_pump_excess_voltage = fxp_div(FXP_FROM_INT(MIN_CHARGE_PUMP_EXCESS_VOLTAGE),
FXP_FROM_INT(1000)); FXP_FROM_INT(1000));
u_bat_load_on = fxp_div(FXP_FROM_INT(FLASH_CONFIG_U_BAT_LOAD_ON), FXP_FROM_INT(1000)); u_bat_load_on = fxp_div(FXP_FROM_INT(U_BAT_LOAD_ON), FXP_FROM_INT(1000));
u_bat_load_off = fxp_div(FXP_FROM_INT(FLASH_CONFIG_U_BAT_LOAD_OFF), FXP_FROM_INT(1000)); u_bat_load_off = fxp_div(FXP_FROM_INT(U_BAT_LOAD_OFF), FXP_FROM_INT(1000));
load_current_limit = fxp_div(FXP_FROM_INT(FLASH_CONFIG_LOAD_CURRENT_LIMIT_MA), FXP_FROM_INT(1000)); load_current_limit = fxp_div(FXP_FROM_INT(LOAD_CURRENT_LIMIT_MA), FXP_FROM_INT(1000));
internal_temperature_limit = fxp_div(FXP_FROM_INT(FLASH_CONFIG_INTERNAL_TEMPERATURE_LIMIT), FXP_FROM_INT(10)); internal_temperature_limit = fxp_div(FXP_FROM_INT(INTERNAL_TEMPERATURE_LIMIT), FXP_FROM_INT(10));
internal_temperature_recovery = fxp_div(FXP_FROM_INT(FLASH_CONFIG_INTERNAL_TEMPERATURE_RECOVERY), FXP_FROM_INT(10)); internal_temperature_recovery = fxp_div(FXP_FROM_INT(INTERNAL_TEMPERATURE_RECOVERY), FXP_FROM_INT(10));
external_temperature_limit = fxp_div(FXP_FROM_INT(FLASH_CONFIG_EXTERNAL_TEMPERATURE_LIMIT), FXP_FROM_INT(10)); sleep_solar_current = fxp_div(FXP_FROM_INT(SLEEP_SOLAR_CURRENT), FXP_FROM_INT(1000));
external_temperature_recovery = fxp_div(FXP_FROM_INT(FLASH_CONFIG_EXTERNAL_TEMPERATURE_RECOVERY), FXP_FROM_INT(10)); sleep_solar_excess_voltage = fxp_div(FXP_FROM_INT(SLEEP_SOLAR_EXCESS_VOLTAGE), FXP_FROM_INT(1000));
sleep_solar_current = fxp_div(FXP_FROM_INT(FLASH_CONFIG_SLEEP_SOLAR_CURRENT), FXP_FROM_INT(1000));
sleep_solar_excess_voltage = fxp_div(FXP_FROM_INT(FLASH_CONFIG_SLEEP_SOLAR_EXCESS_VOLTAGE), FXP_FROM_INT(1000));
overload_retry_time = FLASH_CONFIG_OVERLOAD_RETRY_TIME;
} }
@ -455,8 +352,7 @@ void charge_control_update(uint64_t uptime_ms, struct MeasurementResult *meas)
bool charge_control_is_idle(void) bool charge_control_is_idle(void)
{ {
return (((charge_state == CHARGE_SLEEP) return (((charge_state == CHARGE_SLEEP)
|| (charge_state == CHARGE_HIGH_INTERNAL_TEMPERATURE) || (charge_state == CHARGE_HIGH_TEMPERATURE))
|| (charge_state == CHARGE_LOW_EXTERNAL_TEMPERATURE))
&& ((discharge_state == DISCHARGE_VOLTAGE_LOW) && ((discharge_state == DISCHARGE_VOLTAGE_LOW)
|| (discharge_state == DISCHARGE_OVERCURRENT))); || (discharge_state == DISCHARGE_OVERCURRENT)));
} }
@ -465,8 +361,7 @@ bool charge_control_is_idle(void)
bool charge_control_is_charge_blocked(void) bool charge_control_is_charge_blocked(void)
{ {
switch(charge_state) { switch(charge_state) {
case CHARGE_HIGH_INTERNAL_TEMPERATURE: case CHARGE_HIGH_TEMPERATURE:
case CHARGE_LOW_EXTERNAL_TEMPERATURE:
case CHARGE_WAIT_CHARGEPUMP: case CHARGE_WAIT_CHARGEPUMP:
return true; return true;

View file

@ -11,14 +11,10 @@ enum ChargeState
{ {
CHARGE_WAIT_CHARGEPUMP, CHARGE_WAIT_CHARGEPUMP,
CHARGE_INITIAL, CHARGE_INITIAL,
CHARGE_INITIAL_HOLD,
CHARGE_TRANSITION, CHARGE_TRANSITION,
CHARGE_FLOAT, CHARGE_FLOAT,
CHARGE_SLEEP, CHARGE_SLEEP,
CHARGE_HIGH_INTERNAL_TEMPERATURE, CHARGE_HIGH_TEMPERATURE
CHARGE_LOW_EXTERNAL_TEMPERATURE,
CHARGE_NUM_STATES
}; };
enum DischargeState enum DischargeState
@ -26,10 +22,7 @@ enum DischargeState
DISCHARGE_WAIT_CHARGEPUMP, DISCHARGE_WAIT_CHARGEPUMP,
DISCHARGE_OK, DISCHARGE_OK,
DISCHARGE_VOLTAGE_LOW, DISCHARGE_VOLTAGE_LOW,
DISCHARGE_OVERCURRENT, DISCHARGE_OVERCURRENT
DISCHARGE_OVERCURRENT_DELAY,
DISCHARGE_NUM_STATES
}; };
// Error flags // Error flags

View file

@ -1,7 +1,6 @@
#include <libopencm3/stm32/rcc.h> #include <libopencm3/stm32/rcc.h>
#include "clock.h" #include "clock.h"
#include "libopencm3/stm32/f0/rcc.h"
void init_clock(void) void init_clock(void)
{ {
@ -27,9 +26,6 @@ void init_clock(void)
// DMA1 is used for ADC data transfer // DMA1 is used for ADC data transfer
rcc_periph_clock_enable(RCC_DMA1); rcc_periph_clock_enable(RCC_DMA1);
// I2C1 is used for communication with the BMP280
rcc_periph_clock_enable(RCC_I2C1);
} }

95
src/config.h Normal file
View file

@ -0,0 +1,95 @@
#ifndef CONFIG_H
#define CONFIG_H
/* Thresholds for charging control */
/* Battery regulation corridor width (in mV). */
#define U_BAT_REGULATION_CORRIDOR 100
/* Initial charge battery voltage threshold (in mV). */
#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
/* Duration of the transistion from initial charging to float (in ms). */
#define INITIAL_TO_FLOAT_TRANSITION_TIME 600000
/* Float charge battery voltage threshold (in mV). */
#define U_BAT_FLOAT_FULL 27600 // stop charging if battery voltage reaches this threshold
/* Minimum voltage difference to U_bat that the solar panels must produce
* before charging is resumed after it was switched off (in mV). */
#define SLEEP_SOLAR_EXCESS_VOLTAGE 1000
/* Minimum charge current required before charging is stopped to save power at
* the charge pump (in mA). */
#define SLEEP_SOLAR_CURRENT 1
/* Delay between state change and sleep state check (in ms). */
#define SLEEP_STATE_DELAY 100
/* Delay between charging switch state change and sleep state check(in ms). */
#define SLEEP_SWITCH_DELAY 1000
/* Maximum allowed microcontroller temperature (in units of 0.1 °C). If this
* temperature is exceeded, charging is stopped. The load is kept on. Do not
* set this too high as the heat has to propagate from the power MOSFETs. */
#define INTERNAL_TEMPERATURE_LIMIT 500
/* Resume operation below this temperature (in units of 0.1 °C). */
#define INTERNAL_TEMPERATURE_RECOVERY 450
/* Thresholds for load control */
/* Voltage above which the load is turned on (in mV). */
#define U_BAT_LOAD_ON 27000
/* Voltage below which the load is turned off (in mV). */
#define U_BAT_LOAD_OFF 24000
/* 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
/* The minimum time the load must be off before it can be switched on again (in ms). */
#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. */
#define DEEPSLEEP_DELAY 1000
/* Deep sleep duration (in seconds). */
#define DEEPSLEEP_DURATION 10
#endif // CONFIG_H

View file

@ -1,7 +0,0 @@
#include "flash_config.h"
bool flash_config_is_present(void)
{
return (FLASH_CONFIG_CAL_FACTOR_U_BAT != 0xFFFF)
&& (FLASH_CONFIG_DEEPSLEEP_DURATION != 0xFFFFFFFF);
}

View file

@ -1,145 +0,0 @@
#ifndef FLASH_CONFIG_H
#define FLASH_CONFIG_H
#include <stdint.h>
#include <stdbool.h>
extern uint8_t __conf_start;
#define FLASH_CONFIG_BASE_PTR ((uint8_t*)(&__conf_start))
/***** Calibration factors *****/
/* Calibration factor 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 FLASH_CONFIG_CAL_FACTOR_U_BAT (*(uint16_t*)(FLASH_CONFIG_BASE_PTR + 0x0000))
#define FLASH_CONFIG_CAL_FACTOR_U_SOLAR (*(uint16_t*)(FLASH_CONFIG_BASE_PTR + 0x0002))
#define FLASH_CONFIG_CAL_FACTOR_U_SW (*(uint16_t*)(FLASH_CONFIG_BASE_PTR + 0x0004))
#define FLASH_CONFIG_CAL_FACTOR_I_SOLAR (*(uint16_t*)(FLASH_CONFIG_BASE_PTR + 0x0006))
#define FLASH_CONFIG_CAL_FACTOR_I_LOAD (*(uint16_t*)(FLASH_CONFIG_BASE_PTR + 0x0008))
/***** General configuration *****/
/* Battery regulation corridor width (in mV). */
#define FLASH_CONFIG_U_BAT_REGULATION_CORRIDOR (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x0100))
/* Initial charge battery voltage threshold (in mV). */
#define FLASH_CONFIG_U_BAT_INITIAL_FULL (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x0104))
/* Cancel initial charge voltage hold below this battery voltage (in mV). */
#define FLASH_CONFIG_U_BAT_INITIAL_HOLD_CANCEL (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x0108))
/* Transition to floating voltage levels after this time (in ms). */
#define FLASH_CONFIG_INITIAL_CHARGE_HOLD_TIME (*(uint32_t*)(FLASH_CONFIG_BASE_PTR + 0x010C))
/* Duration of the transistion from initial charging to float (in ms). */
#define FLASH_CONFIG_INITIAL_TO_FLOAT_TRANSITION_TIME (*(uint32_t*)(FLASH_CONFIG_BASE_PTR + 0x0110))
/* Float charge battery voltage threshold (in mV). */
#define FLASH_CONFIG_U_BAT_FLOAT_FULL (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x0114))
/* Minimum voltage difference to U_bat that the solar panels must produce
* before charging is resumed after it was switched off (in mV). */
#define FLASH_CONFIG_SLEEP_SOLAR_EXCESS_VOLTAGE (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x0118))
/* Minimum charge current required before charging is stopped to save power at
* the charge pump (in mA). */
#define FLASH_CONFIG_SLEEP_SOLAR_CURRENT (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x011C))
/* Delay between state change and sleep state check (in ms). */
#define FLASH_CONFIG_SLEEP_STATE_DELAY (*(uint32_t*)(FLASH_CONFIG_BASE_PTR + 0x0120))
/* Delay between charging switch state change and sleep state check(in ms). */
#define FLASH_CONFIG_SLEEP_SWITCH_DELAY (*(uint32_t*)(FLASH_CONFIG_BASE_PTR + 0x0124))
/* Maximum allowed microcontroller temperature (in units of 0.1 °C). If this
* temperature is exceeded, charging is stopped. The load is kept on. Do not
* set this too high as the heat has to propagate from the power MOSFETs. */
#define FLASH_CONFIG_INTERNAL_TEMPERATURE_LIMIT (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x0128))
/* Resume operation below this temperature (in units of 0.1 °C). */
#define FLASH_CONFIG_INTERNAL_TEMPERATURE_RECOVERY (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x012C))
/* Minimum allowed external (BMP280) temperature (in units of 0.1 °C). If the
* temperature falls below this value, charging is stopped to prevent damage to
* the battery. */
#define FLASH_CONFIG_EXTERNAL_TEMPERATURE_LIMIT (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x0130))
/* Resume operation above this temperature (in units of 0.1 °C). */
#define FLASH_CONFIG_EXTERNAL_TEMPERATURE_RECOVERY (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x0134))
/* Thresholds for load control */
/* Voltage above which the load is turned on (in mV). */
#define FLASH_CONFIG_U_BAT_LOAD_ON (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x0138))
/* Voltage below which the load is turned off (in mV). */
#define FLASH_CONFIG_U_BAT_LOAD_OFF (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x013c))
/* Current at which the overload protection triggers (in mA). */
#define FLASH_CONFIG_LOAD_CURRENT_LIMIT_MA (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x0140))
/* Inrush tolerance time (in ms). Overload protection is not enforced for this
* time after load power-on. */
#define FLASH_CONFIG_LOAD_CURRENT_INRUSH_TIME (*(uint32_t*)(FLASH_CONFIG_BASE_PTR + 0x0144))
/* Inrush tolerance time (in ms). Overload protection is not enforced for this
* time after load power-on. */
#define FLASH_CONFIG_LOAD_CURRENT_INRUSH_TIME (*(uint32_t*)(FLASH_CONFIG_BASE_PTR + 0x0144))
/* Minimum voltage that the charge pump must produce above U_bat before any
* power FET is switched on (in mV). */
#define FLASH_CONFIG_MIN_CHARGE_PUMP_EXCESS_VOLTAGE (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x0148))
/* The minimum time the load must be off before it can be switched on again (in ms). */
#define FLASH_CONFIG_LOAD_ON_DELAY (*(uint32_t*)(FLASH_CONFIG_BASE_PTR + 0x014c))
/* Overload delay time (in ms). If load current is too high for this duration,
* load is switched permanently off. */
#define FLASH_CONFIG_OVERLOAD_DELAY_TIME (*(uint32_t*)(FLASH_CONFIG_BASE_PTR + 0x0170))
/* Overload retry time (in ms). Load is switched on again after this time if
* overload condition has triggered. */
#define FLASH_CONFIG_OVERLOAD_RETRY_TIME (*(uint32_t*)(FLASH_CONFIG_BASE_PTR + 0x0174))
/* 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 FLASH_CONFIG_AVG_ALPHA_I_SOLAR (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x0150))
#define FLASH_CONFIG_AVG_ALPHA_I_LOAD (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x0154))
#define FLASH_CONFIG_AVG_ALPHA_U_BAT (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x0158))
#define FLASH_CONFIG_AVG_ALPHA_U_SW (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x015c))
#define FLASH_CONFIG_AVG_ALPHA_U_SOLAR (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x0160))
#define FLASH_CONFIG_AVG_ALPHA_TEMP (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x0164))
/* Generic configuration */
/* Time (in ms) to stay active in idle state before entering deep sleep. */
#define FLASH_CONFIG_DEEPSLEEP_DELAY (*(uint32_t*)(FLASH_CONFIG_BASE_PTR + 0x0168))
/* Deep sleep duration (in seconds). */
#define FLASH_CONFIG_DEEPSLEEP_DURATION (*(uint32_t*)(FLASH_CONFIG_BASE_PTR + 0x016c))
/* FIXME: next free memory location: 0x178. Update when adding a value! */
/* Functions */
bool flash_config_is_present(void);
#endif // FLASH_CONFIG_H

View file

@ -1,171 +0,0 @@
#include <string.h>
#include <libopencm3/stm32/i2c.h>
#include <libopencm3/stm32/dma.h>
#include "i2c_dma.h"
#define DMA_CHANNEL_TX DMA_CHANNEL2
#define DMA_CHANNEL_RX DMA_CHANNEL3
#define TIMEOUT_COUNTER_VALUE 100 // milliseconds
static uint8_t m_wdata[2];
static uint8_t m_wlen;
static uint8_t m_rdata[32];
static uint8_t m_rlen;
static uint8_t m_addr;
static uint32_t m_i2c;
static uint32_t m_timeout_counter;
typedef enum {
IDLE,
SEND_DATA,
RECEIVE_DATA
} i2c_dma_state_t;
static i2c_dma_state_t m_state;
static i2c_dma_callback_t m_callback;
static void setup_txdma(void)
{
dma_channel_reset(DMA1, DMA_CHANNEL_TX);
dma_set_priority(DMA1, DMA_CHANNEL_TX, DMA_CCR_PL_LOW);
dma_set_memory_size(DMA1, DMA_CHANNEL_TX, DMA_CCR_MSIZE_8BIT);
dma_set_peripheral_size(DMA1, DMA_CHANNEL_TX, DMA_CCR_PSIZE_8BIT);
dma_enable_memory_increment_mode(DMA1, DMA_CHANNEL_TX);
dma_set_read_from_memory(DMA1, DMA_CHANNEL_TX);
dma_set_peripheral_address(DMA1, DMA_CHANNEL_TX, (uint32_t) &I2C_TXDR(m_i2c));
// send m_wlen bytes from m_wdata
dma_set_memory_address(DMA1, DMA_CHANNEL_TX, (uint32_t)m_wdata);
dma_set_number_of_data(DMA1, DMA_CHANNEL_TX, m_wlen);
dma_enable_channel(DMA1, DMA_CHANNEL_TX);
i2c_enable_txdma(m_i2c);
}
static void setup_rxdma(void)
{
dma_channel_reset(DMA1, DMA_CHANNEL_RX);
dma_set_priority(DMA1, DMA_CHANNEL_RX, DMA_CCR_PL_LOW);
dma_set_memory_size(DMA1, DMA_CHANNEL_RX, DMA_CCR_MSIZE_8BIT);
dma_set_peripheral_size(DMA1, DMA_CHANNEL_RX, DMA_CCR_PSIZE_8BIT);
dma_enable_memory_increment_mode(DMA1, DMA_CHANNEL_RX);
dma_set_read_from_peripheral(DMA1, DMA_CHANNEL_RX);
dma_set_peripheral_address(DMA1, DMA_CHANNEL_RX, (uint32_t) &I2C_RXDR(m_i2c));
// receive m_rlen bytes and store them in m_rdata
dma_set_memory_address(DMA1, DMA_CHANNEL_RX, (uint32_t)m_rdata);
dma_set_number_of_data(DMA1, DMA_CHANNEL_RX, m_rlen);
dma_enable_channel(DMA1, DMA_CHANNEL_RX);
i2c_enable_rxdma(m_i2c);
}
void i2c_dma_init(i2c_dma_callback_t callback)
{
m_callback = callback;
m_state = IDLE;
}
void i2c_dma_loop(void)
{
switch(m_state) {
case IDLE:
// nothing to do
return;
case SEND_DATA:
//if(i2c_transfer_complete(m_i2c)) { // does not work for some reason :(
if(dma_get_interrupt_flag(DMA1, DMA_CHANNEL_TX, DMA_TCIF)) {
dma_clear_interrupt_flags(DMA1, DMA_CHANNEL_TX, DMA_TCIF);
if(m_rlen) {
m_state = RECEIVE_DATA;
i2c_set_7bit_address(m_i2c, m_addr);
i2c_set_read_transfer_dir(m_i2c);
i2c_set_bytes_to_transfer(m_i2c, m_rlen);
i2c_send_start(m_i2c);
i2c_enable_autoend(m_i2c); // important to do it after start() for a proper repeated start!
setup_rxdma();
} else {
// no RX requested => we are done here
m_state = IDLE;
m_callback(I2C_DMA_EVT_TRANSFER_COMPLETE, NULL, 0);
}
}
if(I2C_ISR(m_i2c) & I2C_ISR_NACKF) {
// there should be no NACK during the transfer => abort
I2C_ICR(m_i2c) |= I2C_ICR_NACKCF; // clear the interrupt flag
m_state = IDLE;
m_callback(I2C_DMA_EVT_NAK, NULL, 0);
}
break;
case RECEIVE_DATA:
//if(i2c_transfer_complete(m_i2c)) { // does not work for some reason :(
if(dma_get_interrupt_flag(DMA1, DMA_CHANNEL_RX, DMA_TCIF)) {
dma_clear_interrupt_flags(DMA1, DMA_CHANNEL_RX, DMA_TCIF);
m_state = IDLE;
m_callback(I2C_DMA_EVT_TRANSFER_COMPLETE, m_rdata, m_rlen);
}
if(I2C_ISR(m_i2c) & I2C_ISR_NACKF) {
// there should be no NACK during the transfer => abort
I2C_ICR(m_i2c) |= I2C_ICR_NACKCF; // clear the interrupt flag
m_state = IDLE;
m_callback(I2C_DMA_EVT_NAK, NULL, 0);
}
break;
}
m_timeout_counter--;
if(m_timeout_counter == 0) {
m_callback(I2C_DMA_EVT_TIMEOUT, NULL, 0);
}
}
void i2c_dma_start_transfer7(uint32_t i2c, uint8_t addr, const uint8_t *w, size_t wn, size_t rn)
{
m_wlen = wn;
m_rlen = rn;
memcpy(m_wdata, w, wn);
m_state = SEND_DATA;
m_i2c = i2c;
m_addr = addr;
i2c_set_7bit_address(i2c, addr);
i2c_set_write_transfer_dir(i2c);
i2c_set_bytes_to_transfer(i2c, wn);
if(rn) {
i2c_disable_autoend(i2c); // prepare repeated start
} else {
i2c_enable_autoend(i2c); // stop condition after write transfer
}
m_timeout_counter = TIMEOUT_COUNTER_VALUE;
i2c_send_start(i2c); // start the transfer. The rest is handled by the DMA.
setup_txdma();
}

View file

@ -1,22 +0,0 @@
#ifndef I2C_DMA_H
#define I2C_DMA_H
#include <stdint.h>
#include <stddef.h>
typedef enum
{
I2C_DMA_EVT_TRANSFER_COMPLETE,
I2C_DMA_EVT_NAK,
I2C_DMA_EVT_TIMEOUT,
} i2c_dma_evt_t;
typedef void (*i2c_dma_callback_t)(i2c_dma_evt_t evt, const uint8_t *rdata, size_t rlen);
void i2c_dma_init(i2c_dma_callback_t callback);
void i2c_dma_loop(void);
void i2c_dma_start_transfer7(uint32_t i2c, uint8_t addr, const uint8_t *w, size_t wn, size_t rn);
#endif // I2C_DMA_H

View file

@ -3,8 +3,6 @@
#include <libopencmsis/core_cm3.h> #include <libopencmsis/core_cm3.h>
#include <string.h>
#include <fxp.h> #include <fxp.h>
#include <fxp_basic.h> #include <fxp_basic.h>
@ -16,19 +14,12 @@
#include "power_switch.h" #include "power_switch.h"
#include "measurement.h" #include "measurement.h"
#include "deepsleep.h" #include "deepsleep.h"
#include "bmp280.h"
#include "addon_io.h"
#include "pinout.h" #include "pinout.h"
#include "flash_config.h" #include "config.h"
volatile int wait_frame = 1; volatile int wait_frame = 1;
static enum {
BMP280_NOT_PRESENT,
BMP280_IDLE,
BMP280_MEASURING,
} bmp280_state;
/* Set up systick to fire freq times per second */ /* Set up systick to fire freq times per second */
static void init_systick(int freq) static void init_systick(int freq)
@ -43,16 +34,6 @@ static void init_systick(int freq)
} }
static void config_err_blink_code(uint64_t timebase_ms)
{
if(timebase_ms % 500 < 250) {
led_chplex_mask(0x3F); // all on
} else {
led_chplex_mask(0x00); // all off
}
}
static bool ledtest(uint64_t timebase_ms) static bool ledtest(uint64_t timebase_ms)
{ {
if(timebase_ms == 0) { if(timebase_ms == 0) {
@ -171,24 +152,6 @@ static void report_status(struct MeasurementResult *meas_data)
fxp_format(meas_data->temperature, number, 2); fxp_format(meas_data->temperature, number, 2);
rs485_enqueue(number); rs485_enqueue(number);
rs485_enqueue(":");
rs485_enqueue(charge_control_is_charge_blocked() ? "1" : "0");
rs485_enqueue(":");
rs485_enqueue(charge_control_is_discharge_blocked() ? "1" : "0");
if(bmp280_state != BMP280_NOT_PRESENT
&& bmp280_are_measurements_valid()) {
rs485_enqueue(":");
fxp_format(bmp280_get_temperature(), number, 2);
rs485_enqueue(number);
rs485_enqueue(":");
fxp_format(bmp280_get_pressure(), number, 2);
rs485_enqueue(number);
}
rs485_enqueue("\n"); rs485_enqueue("\n");
} }
@ -221,25 +184,6 @@ static void report_averaged(struct MeasurementResult *meas_data)
fxp_format(meas_data->avg_temperature, number, 2); fxp_format(meas_data->avg_temperature, number, 2);
rs485_enqueue(number); rs485_enqueue(number);
rs485_enqueue(":");
rs485_enqueue(charge_control_is_charge_blocked() ? "1" : "0");
rs485_enqueue(":");
rs485_enqueue(charge_control_is_discharge_blocked() ? "1" : "0");
if(bmp280_state != BMP280_NOT_PRESENT
&& bmp280_are_measurements_valid()) {
rs485_enqueue(":");
fxp_format(bmp280_get_temperature(), number, 2);
rs485_enqueue(number);
rs485_enqueue(":");
fxp_format(bmp280_get_pressure(), number, 2);
rs485_enqueue(number);
}
rs485_enqueue("\n"); rs485_enqueue("\n");
} }
@ -265,29 +209,6 @@ static void low_power_mode(uint32_t duration_sec)
} }
static void handle_bmp280(uint64_t timebase_ms)
{
switch(bmp280_state) {
case BMP280_NOT_PRESENT:
// do nothing
break;
case BMP280_IDLE:
if(timebase_ms % 1000 == 137) {
bmp280_start_measurement();
bmp280_state = BMP280_MEASURING;
}
break;
case BMP280_MEASURING:
if(timebase_ms % 10 == 0 && bmp280_loop()) {
bmp280_state = BMP280_IDLE;
}
break;
}
}
int main(void) int main(void)
{ {
//uint32_t cpuload = 0; //uint32_t cpuload = 0;
@ -300,12 +221,9 @@ int main(void)
struct MeasurementResult meas_data; struct MeasurementResult meas_data;
memset(&meas_data, 0, sizeof(meas_data));
init_clock(); init_clock();
init_rtc(); init_rtc();
addon_io_init();
rs485_init(); rs485_init();
charge_pump_init(); charge_pump_init();
power_switch_init(); power_switch_init();
@ -315,12 +233,6 @@ int main(void)
led_chplex_init(); led_chplex_init();
led_chplex_on(LED_CHPLEX_IDX_SOLAR_ON); led_chplex_on(LED_CHPLEX_IDX_SOLAR_ON);
if(bmp280_init()) {
bmp280_state = BMP280_IDLE;
} else {
bmp280_state = BMP280_NOT_PRESENT;
}
init_systick(1000); init_systick(1000);
rs485_enqueue("LNSC-2420 v" VERSION " initialized.\n"); rs485_enqueue("LNSC-2420 v" VERSION " initialized.\n");
@ -332,14 +244,9 @@ int main(void)
ledtest_done = ledtest(timebase_ms); ledtest_done = ledtest(timebase_ms);
led_chplex_periodic(); led_chplex_periodic();
} else if(!startup_done) { } else if(!startup_done) {
if(flash_config_is_present()) {
charge_pump_start(); charge_pump_start();
startup_done = true; startup_done = true;
} else {
config_err_blink_code(timebase_ms);
led_chplex_periodic();
}
} else { } else {
measurement_start(); measurement_start();
@ -360,9 +267,6 @@ int main(void)
report_averaged(&meas_data); report_averaged(&meas_data);
} }
// update BMP280
handle_bmp280(timebase_ms);
measurement_wait_for_completion(); measurement_wait_for_completion();
measurement_finalize(&meas_data); measurement_finalize(&meas_data);
@ -371,26 +275,21 @@ int main(void)
charge_control_update(timebase_ms, &meas_data); charge_control_update(timebase_ms, &meas_data);
// deep sleep control // deep sleep control
if(bmp280_state != BMP280_MEASURING) { // general blockers
if(charge_control_is_idle()) { if(charge_control_is_idle()) {
if(!charge_control_was_idle) { if(!charge_control_was_idle) {
charge_control_was_idle = true; charge_control_was_idle = true;
charge_control_idle_since = timebase_ms; charge_control_idle_since = timebase_ms;
} else { } else {
// charge control already idle // charge control already idle
if((timebase_ms - charge_control_idle_since) > FLASH_CONFIG_DEEPSLEEP_DELAY) { if((timebase_ms - charge_control_idle_since) > DEEPSLEEP_DELAY) {
low_power_mode(FLASH_CONFIG_DEEPSLEEP_DURATION); low_power_mode(DEEPSLEEP_DURATION);
charge_control_was_idle = false; charge_control_was_idle = false;
// correct the time base after deep sleep
timebase_ms += FLASH_CONFIG_DEEPSLEEP_DURATION * 1000;
} }
} }
} else { } else {
charge_control_was_idle = false; charge_control_was_idle = false;
} }
} }
}
timebase_ms++; timebase_ms++;

View file

@ -7,7 +7,8 @@
#include "pinout.h" #include "pinout.h"
#include "measurement.h" #include "measurement.h"
#include "flash_config.h" #include "calibration.h"
#include "config.h"
#define ADC_NUM_CHANNELS 6 #define ADC_NUM_CHANNELS 6
static volatile int16_t adc_values[ADC_NUM_CHANNELS]; static volatile int16_t adc_values[ADC_NUM_CHANNELS];
@ -54,7 +55,7 @@ static fxp_t calc_temperature(uint16_t adc_val)
static fxp_t adc_val_to_pin_voltage(uint16_t adc_val) static fxp_t adc_val_to_pin_voltage(uint16_t adc_val)
{ {
return fxp_div( return fxp_div(
fxp_mult(FXP_FROM_INT((int32_t)adc_val), fxp_div(FXP_FROM_INT(33), FXP_FROM_INT(10))), fxp_mult(FXP_FROM_INT(adc_val), fxp_div(FXP_FROM_INT(33), FXP_FROM_INT(10))),
FXP_FROM_INT(4096)); FXP_FROM_INT(4096));
} }
@ -93,23 +94,23 @@ void measurement_init(void)
// Convert calibration factors to fixed-point numbers for direct use // Convert calibration factors to fixed-point numbers for direct use
calibration_factors[ANALOG_INPUT_U_BAT] = calibration_factors[ANALOG_INPUT_U_BAT] =
fxp_div(FXP_FROM_INT(FLASH_CONFIG_CAL_FACTOR_U_BAT), FXP_FROM_INT(1000)); fxp_div(FXP_FROM_INT(CAL_FACTOR_U_BAT), FXP_FROM_INT(1000));
calibration_factors[ANALOG_INPUT_U_SOLAR] = calibration_factors[ANALOG_INPUT_U_SOLAR] =
fxp_div(FXP_FROM_INT(FLASH_CONFIG_CAL_FACTOR_U_SOLAR), FXP_FROM_INT(1000)); fxp_div(FXP_FROM_INT(CAL_FACTOR_U_SOLAR), FXP_FROM_INT(1000));
calibration_factors[ANALOG_INPUT_U_SW] = calibration_factors[ANALOG_INPUT_U_SW] =
fxp_div(FXP_FROM_INT(FLASH_CONFIG_CAL_FACTOR_U_SW), FXP_FROM_INT(1000)); fxp_div(FXP_FROM_INT(CAL_FACTOR_U_SW), FXP_FROM_INT(1000));
calibration_factors[ANALOG_INPUT_I_SOLAR] = calibration_factors[ANALOG_INPUT_I_SOLAR] =
fxp_div(FXP_FROM_INT(FLASH_CONFIG_CAL_FACTOR_I_SOLAR), FXP_FROM_INT(1000)); fxp_div(FXP_FROM_INT(CAL_FACTOR_I_SOLAR), FXP_FROM_INT(1000));
calibration_factors[ANALOG_INPUT_I_LOAD] = calibration_factors[ANALOG_INPUT_I_LOAD] =
fxp_div(FXP_FROM_INT(FLASH_CONFIG_CAL_FACTOR_I_LOAD), FXP_FROM_INT(1000)); fxp_div(FXP_FROM_INT(CAL_FACTOR_I_LOAD), FXP_FROM_INT(1000));
// Convert and precalculate coefficients for exponential averaging // Convert and precalculate coefficients for exponential averaging
avg_alpha_i_solar = fxp_div(FXP_FROM_INT(FLASH_CONFIG_AVG_ALPHA_I_SOLAR), FXP_FROM_INT(1000)); 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(FLASH_CONFIG_AVG_ALPHA_I_LOAD), 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(FLASH_CONFIG_AVG_ALPHA_U_BAT), 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(FLASH_CONFIG_AVG_ALPHA_U_SW), 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(FLASH_CONFIG_AVG_ALPHA_U_SOLAR), 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(FLASH_CONFIG_AVG_ALPHA_TEMP), 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 // Inverse (1 - alpha) exponential averaging coefficients
avg_alpha_i_solar_inv = fxp_sub(FXP_FROM_INT(1), avg_alpha_i_solar); avg_alpha_i_solar_inv = fxp_sub(FXP_FROM_INT(1), avg_alpha_i_solar);

View file

@ -43,21 +43,4 @@
#define RS485_TX_PIN GPIO6 #define RS485_TX_PIN GPIO6
#define RS485_RX_PIN GPIO7 #define RS485_RX_PIN GPIO7
/*** Expansion connector signals ***/
/* BMP280 I²C */
#define BMP280_I2C_PORT GPIOA
#define BMP280_I2C_SCL GPIO9
#define BMP280_I2C_SDA GPIO10
/* Isolated inputs and outputs on I/O addon board */
#define ADDON_ISO_IO_PORT GPIOA
#define ADDON_ISO_IO_OUT1 GPIO5
#define ADDON_ISO_IO_OUT2 GPIO6
#define ADDON_ISO_IO_IN GPIO7
#endif // PINOUT_H #endif // PINOUT_H

View file

@ -1,125 +0,0 @@
# Values for the device at the B26 tower
calibration:
# Calibration factor 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.
CAL_FACTOR_U_BAT: 994
CAL_FACTOR_U_SOLAR: 997
CAL_FACTOR_U_SW: 996
CAL_FACTOR_I_SOLAR: 1015
CAL_FACTOR_I_LOAD: 1000
config:
# Thresholds for charging control
# Battery regulation corridor width (in mV).
U_BAT_REGULATION_CORRIDOR: 100
# Initial charge battery voltage threshold (in mV).
# stop charging if battery voltage reaches this threshold
U_BAT_INITIAL_FULL: 28600
# Cancel initial charge voltage hold below this battery voltage (in mV).
U_BAT_INITIAL_HOLD_CANCEL: 27000
# Transition to floating voltage levels after this time (in ms).
INITIAL_CHARGE_HOLD_TIME: 1800000
# Duration of the transistion from initial charging to float (in ms).
INITIAL_TO_FLOAT_TRANSITION_TIME: 600000
# Float charge battery voltage threshold (in mV).
# stop charging if battery voltage reaches this threshold
U_BAT_FLOAT_FULL: 27600
# Minimum voltage difference to U_bat that the solar panels must produce
# before charging is resumed after it was switched off (in mV).
SLEEP_SOLAR_EXCESS_VOLTAGE: 1000
# Minimum charge current required before charging is stopped to save power at
# the charge pump (in mA).
SLEEP_SOLAR_CURRENT: 1
# Delay between state change and sleep state check (in ms).
SLEEP_STATE_DELAY: 60000
# Delay between charging switch state change and sleep state check(in ms).
SLEEP_SWITCH_DELAY: 1000
# Maximum allowed microcontroller temperature (in units of 0.1 °C). If this
# temperature is exceeded, charging is stopped. The load is kept on. Do not
# set this too high as the heat has to propagate from the power MOSFETs.
INTERNAL_TEMPERATURE_LIMIT: 500
# Resume operation below this temperature (in units of 0.1 °C).
INTERNAL_TEMPERATURE_RECOVERY: 450
# Minimum allowed external (BMP280) temperature (in units of 0.1 °C). If the
# temperature falls below this value, charging is stopped to prevent damage to
# the battery.
EXTERNAL_TEMPERATURE_LIMIT: -30
# Resume operation above this temperature (in units of 0.1 °C).
EXTERNAL_TEMPERATURE_RECOVERY: 0
# Thresholds for load control
# Voltage above which the load is turned on (in mV).
U_BAT_LOAD_ON: 27000
# Voltage below which the load is turned off (in mV).
U_BAT_LOAD_OFF: 24500
# Current at which the overload protection triggers (in mA).
LOAD_CURRENT_LIMIT_MA: 10000
# Inrush tolerance time (in ms). Overload protection is not enforced for this
# time after load power-on.
LOAD_CURRENT_INRUSH_TIME: 10
# Overload delay time (in ms). If load current is too high for this duration,
# load is switched permanently off.
FLASH_CONFIG_OVERLOAD_DELAY_TIME: 10
# Overload retry time (in ms). Load is switched on again after this time if
# overload condition has triggered. If overload immediately triggers again,
# this time is doubled.
FLASH_CONFIG_OVERLOAD_RETRY_TIME: 10000
# Minimum voltage that the charge pump must produce above U_bat before any
# power FET is switched on (in mV).
MIN_CHARGE_PUMP_EXCESS_VOLTAGE: 10000
# The minimum time the load must be off before it can be switched on again (in ms).
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.
AVG_ALPHA_I_SOLAR: 10
AVG_ALPHA_I_LOAD: 10
AVG_ALPHA_U_BAT: 100
AVG_ALPHA_U_SW: 100
AVG_ALPHA_U_SOLAR: 100
AVG_ALPHA_TEMP: 10
# Generic configuration
# Time (in ms) to stay active in idle state before entering deep sleep.
DEEPSLEEP_DELAY: 1000
# Deep sleep duration (in seconds).
DEEPSLEEP_DURATION: 10

View file

@ -1,116 +0,0 @@
# Values for the development device
calibration:
# Calibration factor 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.
CAL_FACTOR_U_BAT: 1012
CAL_FACTOR_U_SOLAR: 1015
CAL_FACTOR_U_SW: 1006
CAL_FACTOR_I_SOLAR: 3980
CAL_FACTOR_I_LOAD: 1000
config:
# Thresholds for charging control
# Battery regulation corridor width (in mV).
U_BAT_REGULATION_CORRIDOR: 100
# Initial charge battery voltage threshold (in mV).
# stop charging if battery voltage reaches this threshold
U_BAT_INITIAL_FULL: 28600
# Cancel initial charge voltage hold below this battery voltage (in mV).
U_BAT_INITIAL_HOLD_CANCEL: 27000
# Transition to floating voltage levels after this time (in ms).
INITIAL_CHARGE_HOLD_TIME: 1800000
# Duration of the transistion from initial charging to float (in ms).
INITIAL_TO_FLOAT_TRANSITION_TIME: 600000
# Float charge battery voltage threshold (in mV).
# stop charging if battery voltage reaches this threshold
U_BAT_FLOAT_FULL: 27600
# Minimum voltage difference to U_bat that the solar panels must produce
# before charging is resumed after it was switched off (in mV).
SLEEP_SOLAR_EXCESS_VOLTAGE: 1000
# Minimum charge current required before charging is stopped to save power at
# the charge pump (in mA).
SLEEP_SOLAR_CURRENT: 1
# Delay between state change and sleep state check (in ms).
SLEEP_STATE_DELAY: 60000
# Delay between charging switch state change and sleep state check(in ms).
SLEEP_SWITCH_DELAY: 1000
# Maximum allowed microcontroller temperature (in units of 0.1 °C). If this
# temperature is exceeded, charging is stopped. The load is kept on. Do not
# set this too high as the heat has to propagate from the power MOSFETs.
INTERNAL_TEMPERATURE_LIMIT: 500
# Resume operation below this temperature (in units of 0.1 °C).
INTERNAL_TEMPERATURE_RECOVERY: 450
# Minimum allowed external (BMP280) temperature (in units of 0.1 °C). If the
# temperature falls below this value, charging is stopped to prevent damage to
# the battery.
EXTERNAL_TEMPERATURE_LIMIT: 250
# Resume operation above this temperature (in units of 0.1 °C).
EXTERNAL_TEMPERATURE_RECOVERY: 270
# Thresholds for load control
# Voltage above which the load is turned on (in mV).
U_BAT_LOAD_ON: 27000
# Voltage below which the load is turned off (in mV).
U_BAT_LOAD_OFF: 24000
# Current at which the overload protection triggers (in mA).
LOAD_CURRENT_LIMIT_MA: 10000
# Inrush tolerance time (in ms). Overload protection is not enforced for this
# time after load power-on.
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).
MIN_CHARGE_PUMP_EXCESS_VOLTAGE: 10000
# The minimum time the load must be off before it can be switched on again (in ms).
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.
AVG_ALPHA_I_SOLAR: 10
AVG_ALPHA_I_LOAD: 10
AVG_ALPHA_U_BAT: 100
AVG_ALPHA_U_SW: 100
AVG_ALPHA_U_SOLAR: 100
AVG_ALPHA_TEMP: 10
# Generic configuration
# Time (in ms) to stay active in idle state before entering deep sleep.
DEEPSLEEP_DELAY: 1000
# Deep sleep duration (in seconds).
DEEPSLEEP_DURATION: 10

View file

@ -1,73 +0,0 @@
#!/usr/bin/env python3
import sys
import struct
import yaml
from intelhex import IntelHex
BASE_ADDR = 0x08007C00
CALIBRATION_KEY_TO_OFFSET = {
"CAL_FACTOR_U_BAT": 0x0000,
"CAL_FACTOR_U_SOLAR": 0x0002,
"CAL_FACTOR_U_SW": 0x0004,
"CAL_FACTOR_I_SOLAR": 0x0006,
"CAL_FACTOR_I_LOAD": 0x0008,
}
CONFIG_KEY_TO_OFFSET = {
"U_BAT_REGULATION_CORRIDOR": 0x0100,
"U_BAT_INITIAL_FULL": 0x0104,
"U_BAT_INITIAL_HOLD_CANCEL": 0x0108,
"INITIAL_CHARGE_HOLD_TIME": 0x010C,
"INITIAL_TO_FLOAT_TRANSITION_TIME": 0x0110,
"U_BAT_FLOAT_FULL": 0x0114,
"SLEEP_SOLAR_EXCESS_VOLTAGE": 0x0118,
"SLEEP_SOLAR_CURRENT": 0x011C,
"SLEEP_STATE_DELAY": 0x0120,
"SLEEP_SWITCH_DELAY": 0x0124,
"INTERNAL_TEMPERATURE_LIMIT": 0x0128,
"INTERNAL_TEMPERATURE_RECOVERY": 0x012C,
"EXTERNAL_TEMPERATURE_LIMIT": 0x0130,
"EXTERNAL_TEMPERATURE_RECOVERY": 0x0134,
"U_BAT_LOAD_ON": 0x0138,
"U_BAT_LOAD_OFF": 0x013c,
"LOAD_CURRENT_LIMIT_MA": 0x0140,
"LOAD_CURRENT_INRUSH_TIME": 0x0144,
"MIN_CHARGE_PUMP_EXCESS_VOLTAGE": 0x0148,
"LOAD_ON_DELAY": 0x014c,
"AVG_ALPHA_I_SOLAR": 0x0150,
"AVG_ALPHA_I_LOAD": 0x0154,
"AVG_ALPHA_U_BAT": 0x0158,
"AVG_ALPHA_U_SW": 0x015c,
"AVG_ALPHA_U_SOLAR": 0x0160,
"AVG_ALPHA_TEMP": 0x0164,
"DEEPSLEEP_DELAY": 0x0168,
"DEEPSLEEP_DURATION": 0x016c,
"FLASH_CONFIG_OVERLOAD_DELAY_TIME": 0x0170,
"FLASH_CONFIG_OVERLOAD_RETRY_TIME": 0x0174
}
if __name__ == "__main__":
output = IntelHex()
if len(sys.argv) < 3:
print(f"usage: {sys.argv[0]} <yaml-config> <output-hex>")
sys.exit(1)
with open(sys.argv[1], 'r') as configfile:
config = yaml.safe_load(configfile)
for key, offset in CALIBRATION_KEY_TO_OFFSET.items():
value = config['calibration'][key]
enc_bytes = struct.pack('<H', value)
output.puts(BASE_ADDR + offset, enc_bytes)
for key, offset in CONFIG_KEY_TO_OFFSET.items():
value = config['config'][key]
enc_bytes = struct.pack('<i', value)
output.puts(BASE_ADDR + offset, enc_bytes)
output.write_hex_file(sys.argv[2])

View file

@ -1,18 +0,0 @@
#!/bin/sh
set -euo pipefail
HEXFILE="$1"
if [ ! -f "$HEXFILE" ]; then
echo "$HEXFILE cannot be accessed."
exit 1
fi
JLinkExe -device STM32F030C8 -speed 1000 -if SWD <<EOF
connect
loadfile $HEXFILE
r
g
exit
EOF