Compare commits

..

18 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
Thomas Kolb 1153da6fc2 config_b26: adjust minimum voltage to 24.5V 2022-11-13 15:05:33 +01:00
Thomas Kolb a140b3ade6 Do not send BMP280 data if invalid 2022-11-13 14:35:34 +01:00
Thomas Kolb 5090b7de4a Try to make BMP280 communication more resilient
If communication times out or a NAK is received, this is now recognized
and sensor values are flagged as invalid. The communication then
restarts on the next regular cycle. This is still to be tested.
2022-11-09 20:47:13 +01:00
Thomas Kolb 4d4cc41b46 fix: actually use the new calibration factors in flash 2022-11-09 20:45:34 +01:00
Thomas Kolb 430b7c73d0 i2c_dma: removed unused include 2022-10-15 21:49:01 +02:00
Thomas Kolb 1cbe0ce410 Prevent deep sleep during BMP280 communication 2022-10-15 21:48:15 +02:00
Thomas Kolb 5c6497fb82 charge_control: block charging if external temperature is too low 2022-10-15 21:44:27 +02:00
Thomas Kolb d7b8c90cb4 Add new temperature thresholds to flash config 2022-10-15 21:42:46 +02:00
Thomas Kolb c807acec11 bmp280: add function to check for valid measurements 2022-10-15 21:41:50 +02:00
Thomas Kolb 07dd91ecc2 bmp280: read out via DMA in the background
This removes any busy waiting on the I²C from the main loop.
Initialization is still done in a blocking way.
2022-10-15 20:49:31 +02:00
Thomas Kolb 055186180f Merge branch 'main' into bmp280 2022-10-15 16:43:36 +02:00
Thomas Kolb 3dcf412a02 BMP280: Crude workaround for detecting chip presence 2022-10-08 21:49:28 +02:00
Thomas Kolb 2f0f6a01f2 Initial BMP280 integration
This version uses only blocking i2c calls, and long transfers will
probably cause frame loss. Also, if no BMP280 is present, the firmware
will not start up and hang in an endless loop instead.
2022-10-08 20:50:08 +02:00
19 changed files with 930 additions and 63 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

284
src/bmp280.c Normal file
View file

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

18
src/bmp280.h Normal file
View file

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

77
src/bmp280_comp.c Normal file
View file

@ -0,0 +1,77 @@
/* 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);
}

23
src/bmp280_comp.h Normal file
View file

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

@ -2,29 +2,33 @@
#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 "flash_config.h"
#include "addon_io.h"
#include "charge_control.h" #include "charge_control.h"
static const char *CHARGE_STATE_TEXT[] = { static const char *CHARGE_STATE_TEXT[CHARGE_NUM_STATES] = {
"WAIT_CHARGEPUMP", "WAIT_CHARGEPUMP",
"INITIAL", "INITIAL",
"INITIAL_HOLD", "INITIAL_HOLD",
"TRANSITION", "TRANSITION",
"FLOAT", "FLOAT",
"SLEEP", "SLEEP",
"HIGH_TEMPERATURE" "HIGH_INTERNAL_TEMPERATURE",
"LOW_EXTERNAL_TEMPERATURE"
}; };
static const char *DISCHARGE_STATE_TEXT[] = { 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;
@ -52,13 +56,19 @@ 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,
@ -86,9 +96,15 @@ static enum ChargeState control_solar_charging(
solar_switch_onoff_duration = 0; solar_switch_onoff_duration = 0;
} }
// temperature limit // internal temperature limit: prevent overheating of the power transistors.
if(meas->avg_temperature > internal_temperature_limit) { if(meas->avg_temperature > internal_temperature_limit) {
return CHARGE_HIGH_TEMPERATURE; return CHARGE_HIGH_INTERNAL_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)
@ -195,7 +211,7 @@ 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_TEMPERATURE; charge_state = CHARGE_HIGH_INTERNAL_TEMPERATURE;
break; break;
} }
break; break;
@ -217,7 +233,7 @@ static void solar_fsm_update(uint64_t uptime_ms, struct MeasurementResult *meas)
break; break;
case CHARGE_HIGH_TEMPERATURE: case CHARGE_HIGH_INTERNAL_TEMPERATURE:
if(charge_state_entered) { if(charge_state_entered) {
power_switch_solar_off(); power_switch_solar_off();
} }
@ -228,6 +244,27 @@ 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;
@ -261,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)) {
discharge_state = DISCHARGE_OVERCURRENT; if(load_current_limit_delay == 0) {
// switch off immediately
power_switch_load_off();
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) {
@ -278,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:
@ -329,8 +398,13 @@ void charge_control_init(void)
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(FLASH_CONFIG_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(FLASH_CONFIG_INTERNAL_TEMPERATURE_RECOVERY), FXP_FROM_INT(10));
external_temperature_limit = fxp_div(FXP_FROM_INT(FLASH_CONFIG_EXTERNAL_TEMPERATURE_LIMIT), FXP_FROM_INT(10));
external_temperature_recovery = fxp_div(FXP_FROM_INT(FLASH_CONFIG_EXTERNAL_TEMPERATURE_RECOVERY), FXP_FROM_INT(10));
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;
} }
@ -381,7 +455,8 @@ 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_TEMPERATURE)) || (charge_state == CHARGE_HIGH_INTERNAL_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)));
} }
@ -390,7 +465,8 @@ 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_TEMPERATURE: case CHARGE_HIGH_INTERNAL_TEMPERATURE:
case CHARGE_LOW_EXTERNAL_TEMPERATURE:
case CHARGE_WAIT_CHARGEPUMP: case CHARGE_WAIT_CHARGEPUMP:
return true; return true;

View file

@ -15,7 +15,10 @@ enum ChargeState
CHARGE_TRANSITION, CHARGE_TRANSITION,
CHARGE_FLOAT, CHARGE_FLOAT,
CHARGE_SLEEP, CHARGE_SLEEP,
CHARGE_HIGH_TEMPERATURE CHARGE_HIGH_INTERNAL_TEMPERATURE,
CHARGE_LOW_EXTERNAL_TEMPERATURE,
CHARGE_NUM_STATES
}; };
enum DischargeState enum DischargeState
@ -23,7 +26,10 @@ 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,6 +1,7 @@
#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)
{ {
@ -26,6 +27,9 @@ 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);
} }

View file

@ -3,5 +3,5 @@
bool flash_config_is_present(void) bool flash_config_is_present(void)
{ {
return (FLASH_CONFIG_CAL_FACTOR_U_BAT != 0xFFFF) return (FLASH_CONFIG_CAL_FACTOR_U_BAT != 0xFFFF)
&& (FLASH_CONFIG_U_BAT_REGULATION_CORRIDOR != -1); && (FLASH_CONFIG_DEEPSLEEP_DURATION != 0xFFFFFFFF);
} }

View file

@ -63,27 +63,47 @@ extern uint8_t __conf_start;
/* Resume operation below this temperature (in units of 0.1 °C). */ /* Resume operation below this temperature (in units of 0.1 °C). */
#define FLASH_CONFIG_INTERNAL_TEMPERATURE_RECOVERY (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x012C)) #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 */ /* Thresholds for load control */
/* Voltage above which the load is turned on (in mV). */ /* Voltage above which the load is turned on (in mV). */
#define FLASH_CONFIG_U_BAT_LOAD_ON (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x0130)) #define FLASH_CONFIG_U_BAT_LOAD_ON (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x0138))
/* Voltage below which the load is turned off (in mV). */ /* Voltage below which the load is turned off (in mV). */
#define FLASH_CONFIG_U_BAT_LOAD_OFF (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x0134)) #define FLASH_CONFIG_U_BAT_LOAD_OFF (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x013c))
/* Current at which the overload protection triggers (in mA). */ /* Current at which the overload protection triggers (in mA). */
#define FLASH_CONFIG_LOAD_CURRENT_LIMIT_MA (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x0138)) #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 /* Inrush tolerance time (in ms). Overload protection is not enforced for this
* time after load power-on. */ * time after load power-on. */
#define FLASH_CONFIG_LOAD_CURRENT_INRUSH_TIME (*(uint32_t*)(FLASH_CONFIG_BASE_PTR + 0x013C)) #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 + 0x0140)) #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). */ /* 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 + 0x0144)) #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:
@ -99,21 +119,23 @@ extern uint8_t __conf_start;
* */ * */
/* Averaging factor for load current. */ /* Averaging factor for load current. */
#define FLASH_CONFIG_AVG_ALPHA_I_SOLAR (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x0148)) #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 + 0x014C)) #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 + 0x0150)) #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 + 0x0154)) #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 + 0x0158)) #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 + 0x015C)) #define FLASH_CONFIG_AVG_ALPHA_TEMP (*(int32_t*)(FLASH_CONFIG_BASE_PTR + 0x0164))
/* Generic configuration */ /* Generic configuration */
/* Time (in ms) to stay active in idle state before entering deep sleep. */ /* Time (in ms) to stay active in idle state before entering deep sleep. */
#define FLASH_CONFIG_DEEPSLEEP_DELAY (*(uint32_t*)(FLASH_CONFIG_BASE_PTR + 0x0160)) #define FLASH_CONFIG_DEEPSLEEP_DELAY (*(uint32_t*)(FLASH_CONFIG_BASE_PTR + 0x0168))
/* Deep sleep duration (in seconds). */ /* Deep sleep duration (in seconds). */
#define FLASH_CONFIG_DEEPSLEEP_DURATION (*(uint32_t*)(FLASH_CONFIG_BASE_PTR + 0x0164)) #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 */

171
src/i2c_dma.c Normal file
View file

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

22
src/i2c_dma.h Normal file
View file

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

@ -16,12 +16,19 @@
#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 "flash_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)
@ -170,6 +177,18 @@ static void report_status(struct MeasurementResult *meas_data)
rs485_enqueue(":"); rs485_enqueue(":");
rs485_enqueue(charge_control_is_discharge_blocked() ? "1" : "0"); 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");
} }
@ -208,6 +227,19 @@ static void report_averaged(struct MeasurementResult *meas_data)
rs485_enqueue(":"); rs485_enqueue(":");
rs485_enqueue(charge_control_is_discharge_blocked() ? "1" : "0"); 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");
} }
@ -233,6 +265,29 @@ 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;
@ -250,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();
@ -259,6 +315,12 @@ 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");
@ -298,6 +360,9 @@ 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);
@ -306,19 +371,24 @@ int main(void)
charge_control_update(timebase_ms, &meas_data); charge_control_update(timebase_ms, &meas_data);
// deep sleep control // deep sleep control
if(charge_control_is_idle()) { if(bmp280_state != BMP280_MEASURING) { // general blockers
if(!charge_control_was_idle) { if(charge_control_is_idle()) {
charge_control_was_idle = true; if(!charge_control_was_idle) {
charge_control_idle_since = timebase_ms; charge_control_was_idle = true;
} else { charge_control_idle_since = timebase_ms;
// charge control already idle } else {
if((timebase_ms - charge_control_idle_since) > FLASH_CONFIG_DEEPSLEEP_DELAY) { // charge control already idle
low_power_mode(FLASH_CONFIG_DEEPSLEEP_DURATION); if((timebase_ms - charge_control_idle_since) > FLASH_CONFIG_DEEPSLEEP_DELAY) {
charge_control_was_idle = false; low_power_mode(FLASH_CONFIG_DEEPSLEEP_DURATION);
charge_control_was_idle = false;
// correct the time base after deep sleep
timebase_ms += FLASH_CONFIG_DEEPSLEEP_DURATION * 1000;
}
} }
} else {
charge_control_was_idle = false;
} }
} else {
charge_control_was_idle = false;
} }
} }

View file

@ -7,7 +7,6 @@
#include "pinout.h" #include "pinout.h"
#include "measurement.h" #include "measurement.h"
#include "calibration.h"
#include "flash_config.h" #include "flash_config.h"
#define ADC_NUM_CHANNELS 6 #define ADC_NUM_CHANNELS 6
@ -94,15 +93,15 @@ 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(CAL_FACTOR_U_BAT), FXP_FROM_INT(1000)); fxp_div(FXP_FROM_INT(FLASH_CONFIG_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(CAL_FACTOR_U_SOLAR), FXP_FROM_INT(1000)); fxp_div(FXP_FROM_INT(FLASH_CONFIG_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(CAL_FACTOR_U_SW), FXP_FROM_INT(1000)); fxp_div(FXP_FROM_INT(FLASH_CONFIG_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(CAL_FACTOR_I_SOLAR), FXP_FROM_INT(1000)); fxp_div(FXP_FROM_INT(FLASH_CONFIG_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(CAL_FACTOR_I_LOAD), FXP_FROM_INT(1000)); fxp_div(FXP_FROM_INT(FLASH_CONFIG_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(FLASH_CONFIG_AVG_ALPHA_I_SOLAR), FXP_FROM_INT(1000));

View file

@ -43,4 +43,21 @@
#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

@ -56,13 +56,21 @@ config:
# Resume operation below this temperature (in units of 0.1 °C). # Resume operation below this temperature (in units of 0.1 °C).
INTERNAL_TEMPERATURE_RECOVERY: 450 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 # Thresholds for load control
# Voltage above which the load is turned on (in mV). # Voltage above which the load is turned on (in mV).
U_BAT_LOAD_ON: 27000 U_BAT_LOAD_ON: 27000
# Voltage below which the load is turned off (in mV). # Voltage below which the load is turned off (in mV).
U_BAT_LOAD_OFF: 24000 U_BAT_LOAD_OFF: 24500
# Current at which the overload protection triggers (in mA). # Current at which the overload protection triggers (in mA).
LOAD_CURRENT_LIMIT_MA: 10000 LOAD_CURRENT_LIMIT_MA: 10000
@ -71,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

@ -56,6 +56,14 @@ config:
# Resume operation below this temperature (in units of 0.1 °C). # Resume operation below this temperature (in units of 0.1 °C).
INTERNAL_TEMPERATURE_RECOVERY: 450 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 # Thresholds for load control

View file

@ -29,20 +29,24 @@ CONFIG_KEY_TO_OFFSET = {
"SLEEP_SWITCH_DELAY": 0x0124, "SLEEP_SWITCH_DELAY": 0x0124,
"INTERNAL_TEMPERATURE_LIMIT": 0x0128, "INTERNAL_TEMPERATURE_LIMIT": 0x0128,
"INTERNAL_TEMPERATURE_RECOVERY": 0x012C, "INTERNAL_TEMPERATURE_RECOVERY": 0x012C,
"U_BAT_LOAD_ON": 0x0130, "EXTERNAL_TEMPERATURE_LIMIT": 0x0130,
"U_BAT_LOAD_OFF": 0x0134, "EXTERNAL_TEMPERATURE_RECOVERY": 0x0134,
"LOAD_CURRENT_LIMIT_MA": 0x0138, "U_BAT_LOAD_ON": 0x0138,
"LOAD_CURRENT_INRUSH_TIME": 0x013C, "U_BAT_LOAD_OFF": 0x013c,
"MIN_CHARGE_PUMP_EXCESS_VOLTAGE": 0x0140, "LOAD_CURRENT_LIMIT_MA": 0x0140,
"LOAD_ON_DELAY": 0x0144, "LOAD_CURRENT_INRUSH_TIME": 0x0144,
"AVG_ALPHA_I_SOLAR": 0x0148, "MIN_CHARGE_PUMP_EXCESS_VOLTAGE": 0x0148,
"AVG_ALPHA_I_LOAD": 0x014C, "LOAD_ON_DELAY": 0x014c,
"AVG_ALPHA_U_BAT": 0x0150, "AVG_ALPHA_I_SOLAR": 0x0150,
"AVG_ALPHA_U_SW": 0x0154, "AVG_ALPHA_I_LOAD": 0x0154,
"AVG_ALPHA_U_SOLAR": 0x0158, "AVG_ALPHA_U_BAT": 0x0158,
"AVG_ALPHA_TEMP": 0x015C, "AVG_ALPHA_U_SW": 0x015c,
"DEEPSLEEP_DELAY": 0x0160, "AVG_ALPHA_U_SOLAR": 0x0160,
"DEEPSLEEP_DURATION": 0x0164, "AVG_ALPHA_TEMP": 0x0164,
"DEEPSLEEP_DELAY": 0x0168,
"DEEPSLEEP_DURATION": 0x016c,
"FLASH_CONFIG_OVERLOAD_DELAY_TIME": 0x0170,
"FLASH_CONFIG_OVERLOAD_RETRY_TIME": 0x0174
} }
@ -63,7 +67,7 @@ if __name__ == "__main__":
for key, offset in CONFIG_KEY_TO_OFFSET.items(): for key, offset in CONFIG_KEY_TO_OFFSET.items():
value = config['config'][key] value = config['config'][key]
enc_bytes = struct.pack('<I', value) enc_bytes = struct.pack('<i', value)
output.puts(BASE_ADDR + offset, enc_bytes) output.puts(BASE_ADDR + offset, enc_bytes)
output.write_hex_file(sys.argv[2]) output.write_hex_file(sys.argv[2])