Compare commits

..

5 commits

Author SHA1 Message Date
Thomas Kolb e6891aed78 Initialize the addon board 2023-09-16 22:48:41 +02:00
Thomas Kolb 19735ee550 overload: exponential backoff for retry
Whenever overload is detected, the time that must pass before the load is
turned on again is doubled. If the load was on for 5 minutes, the retry time is
reset to the configured value.
2023-06-18 16:43:41 +02:00
Thomas Kolb e8dff1f017 Correct timebase_ms after deep sleep
During deep sleep, the millisecond counter is stopped. To ensure correct delay
times, we add the sleep duration to the counter after wakeup.
2023-06-18 16:18:44 +02:00
Thomas Kolb bd08440584 charge_control: delay overload trigger by a configurable time 2023-06-18 16:18:13 +02:00
Thomas Kolb 2cedcb712a Implement heater control
This change controls a GPIO on the expansion connector depending on the
surrounding temperature. If external temperature is too low for
charging, the GPIO is set high, else it is set low.

In combination with the new LNSC-2420-Addon board this allows to switch
on a high-power heater connected directly to the solar panels whenever
the temperature is too low to charge the battery. Once the temperature
becomes high enough again, the heater is switched off and the battery is
charged instead.
2023-04-22 20:02:16 +02:00
9 changed files with 139 additions and 4 deletions

35
src/addon_io.c Normal file
View file

@ -0,0 +1,35 @@
#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;
}

14
src/addon_io.h Normal file
View file

@ -0,0 +1,14 @@
#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

@ -8,6 +8,7 @@
#include "charge_pump.h" #include "charge_pump.h"
#include "rs485.h" #include "rs485.h"
#include "flash_config.h" #include "flash_config.h"
#include "addon_io.h"
#include "charge_control.h" #include "charge_control.h"
@ -26,7 +27,8 @@ static const char *DISCHARGE_STATE_TEXT[DISCHARGE_NUM_STATES] = {
"WAIT_CHARGEPUMP", "WAIT_CHARGEPUMP",
"OK", "OK",
"VOLTAGE_LOW", "VOLTAGE_LOW",
"OVERCURRENT" "OVERCURRENT",
"OVERCURRENT_DELAY"
}; };
static enum ChargeState charge_state; static enum ChargeState charge_state;
@ -54,6 +56,7 @@ 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;
@ -64,6 +67,8 @@ 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,
@ -243,12 +248,18 @@ static void solar_fsm_update(uint64_t uptime_ms, struct MeasurementResult *meas)
case CHARGE_LOW_EXTERNAL_TEMPERATURE: case CHARGE_LOW_EXTERNAL_TEMPERATURE:
if(charge_state_entered) { if(charge_state_entered) {
power_switch_solar_off(); 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 // this state can only be entered if the BMP280 measurement is valid, so
// no need to check it again here. // no need to check it again here.
if(bmp280_get_temperature() > external_temperature_recovery) { if(bmp280_get_temperature() > external_temperature_recovery) {
charge_state = CHARGE_WAIT_CHARGEPUMP; charge_state = CHARGE_WAIT_CHARGEPUMP;
// switch the heater off again when this state is left
addon_io_iso_out_off(0);
break; break;
} }
break; break;
@ -287,7 +298,20 @@ 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 > FLASH_CONFIG_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) &&
(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) { if(meas->avg_u_bat < u_bat_load_off) {
@ -304,17 +328,36 @@ 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->avg_u_bat > u_bat_load_on)
&& (discharge_time_in_state > FLASH_CONFIG_LOAD_ON_DELAY)) { && (discharge_time_in_state > FLASH_CONFIG_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:
// Battery voltage is too low, so keep the load switched off // Current limit reached
if(discharge_state_entered) { if(discharge_state_entered) {
power_switch_load_off(); power_switch_load_off();
} }
// no way out except reset // Overload recovery
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:
@ -360,6 +403,8 @@ void charge_control_init(void)
sleep_solar_current = fxp_div(FXP_FROM_INT(FLASH_CONFIG_SLEEP_SOLAR_CURRENT), 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)); 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;
} }

View file

@ -27,6 +27,7 @@ enum DischargeState
DISCHARGE_OK, DISCHARGE_OK,
DISCHARGE_VOLTAGE_LOW, DISCHARGE_VOLTAGE_LOW,
DISCHARGE_OVERCURRENT, DISCHARGE_OVERCURRENT,
DISCHARGE_OVERCURRENT_DELAY,
DISCHARGE_NUM_STATES DISCHARGE_NUM_STATES
}; };

View file

@ -86,6 +86,10 @@ extern uint8_t __conf_start;
* time after load power-on. */ * time after load power-on. */
#define FLASH_CONFIG_LOAD_CURRENT_INRUSH_TIME (*(uint32_t*)(FLASH_CONFIG_BASE_PTR + 0x0144)) #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 /* Minimum voltage that the charge pump must produce above U_bat before any
* power FET is switched on (in mV). */ * power FET is switched on (in mV). */
#define FLASH_CONFIG_MIN_CHARGE_PUMP_EXCESS_VOLTAGE (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x0148)) #define FLASH_CONFIG_MIN_CHARGE_PUMP_EXCESS_VOLTAGE (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x0148))
@ -93,6 +97,14 @@ extern uint8_t __conf_start;
/* The minimum time the load must be off before it can be switched on again (in ms). */ /* 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)) #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: /* Measurement Averaging:
* Alpha is specified in units of 1/1000. 1000 means that only the latest * Alpha is specified in units of 1/1000. 1000 means that only the latest
@ -123,6 +135,8 @@ extern uint8_t __conf_start;
/* Deep sleep duration (in seconds). */ /* Deep sleep duration (in seconds). */
#define FLASH_CONFIG_DEEPSLEEP_DURATION (*(uint32_t*)(FLASH_CONFIG_BASE_PTR + 0x016c)) #define FLASH_CONFIG_DEEPSLEEP_DURATION (*(uint32_t*)(FLASH_CONFIG_BASE_PTR + 0x016c))
/* FIXME: next free memory location: 0x178. Update when adding a value! */
/* Functions */ /* Functions */

View file

@ -17,6 +17,7 @@
#include "measurement.h" #include "measurement.h"
#include "deepsleep.h" #include "deepsleep.h"
#include "bmp280.h" #include "bmp280.h"
#include "addon_io.h"
#include "pinout.h" #include "pinout.h"
#include "flash_config.h" #include "flash_config.h"
@ -304,6 +305,7 @@ int main(void)
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();
@ -379,6 +381,9 @@ int main(void)
if((timebase_ms - charge_control_idle_since) > FLASH_CONFIG_DEEPSLEEP_DELAY) { if((timebase_ms - charge_control_idle_since) > FLASH_CONFIG_DEEPSLEEP_DELAY) {
low_power_mode(FLASH_CONFIG_DEEPSLEEP_DURATION); low_power_mode(FLASH_CONFIG_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 {

View file

@ -43,6 +43,8 @@
#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 */ /* BMP280 I²C */
#define BMP280_I2C_PORT GPIOA #define BMP280_I2C_PORT GPIOA
@ -50,4 +52,12 @@
#define BMP280_I2C_SCL GPIO9 #define BMP280_I2C_SCL GPIO9
#define BMP280_I2C_SDA GPIO10 #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

@ -79,6 +79,15 @@ config:
# time after load power-on. # time after load power-on.
LOAD_CURRENT_INRUSH_TIME: 10 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 # Minimum voltage that the charge pump must produce above U_bat before any
# power FET is switched on (in mV). # power FET is switched on (in mV).
MIN_CHARGE_PUMP_EXCESS_VOLTAGE: 10000 MIN_CHARGE_PUMP_EXCESS_VOLTAGE: 10000

View file

@ -45,6 +45,8 @@ CONFIG_KEY_TO_OFFSET = {
"AVG_ALPHA_TEMP": 0x0164, "AVG_ALPHA_TEMP": 0x0164,
"DEEPSLEEP_DELAY": 0x0168, "DEEPSLEEP_DELAY": 0x0168,
"DEEPSLEEP_DURATION": 0x016c, "DEEPSLEEP_DURATION": 0x016c,
"FLASH_CONFIG_OVERLOAD_DELAY_TIME": 0x0170,
"FLASH_CONFIG_OVERLOAD_RETRY_TIME": 0x0174
} }