Compare commits
32 commits
charge_con
...
main
Author | SHA1 | Date | |
---|---|---|---|
Thomas Kolb | e6891aed78 | ||
Thomas Kolb | 19735ee550 | ||
Thomas Kolb | e8dff1f017 | ||
Thomas Kolb | bd08440584 | ||
Thomas Kolb | 2cedcb712a | ||
Thomas Kolb | 1153da6fc2 | ||
Thomas Kolb | a140b3ade6 | ||
Thomas Kolb | 5090b7de4a | ||
Thomas Kolb | 4d4cc41b46 | ||
Thomas Kolb | 430b7c73d0 | ||
Thomas Kolb | 1cbe0ce410 | ||
Thomas Kolb | 5c6497fb82 | ||
Thomas Kolb | d7b8c90cb4 | ||
Thomas Kolb | c807acec11 | ||
Thomas Kolb | 07dd91ecc2 | ||
Thomas Kolb | 055186180f | ||
Thomas Kolb | 3dcf412a02 | ||
Thomas Kolb | 2f0f6a01f2 | ||
Thomas Kolb | 16619397b1 | ||
Thomas Kolb | 481cf6f9f4 | ||
Thomas Kolb | 4ebb231346 | ||
Thomas Kolb | 1e093978f3 | ||
Thomas Kolb | 5528259517 | ||
Thomas Kolb | 2275edb475 | ||
Thomas Kolb | 87ddba7af7 | ||
Thomas Kolb | 8dd5c8121f | ||
Thomas Kolb | 6920c548e9 | ||
Thomas Kolb | 6ff8918e62 | ||
Thomas Kolb | 716c158716 | ||
Thomas Kolb | 0339b521dd | ||
Thomas Kolb | d239a947cd | ||
Thomas Kolb | 79fe1113de |
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -4,3 +4,6 @@
|
||||||
|
|
||||||
# Vim swap files
|
# Vim swap files
|
||||||
.*.sw?
|
.*.sw?
|
||||||
|
|
||||||
|
# generated config HEX files
|
||||||
|
utils/*.hex
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -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
|
# the LD script (RAM-only does not work because the code is too large)
|
||||||
#LDFLAGS+=-Tldscripts/lnsc-2420-$(BUILD).ld
|
#LDFLAGS+=-Tldscripts/lnsc-2420-$(BUILD).ld
|
||||||
LDFLAGS+=-Tldscripts/lnsc-2420-release.ld
|
LDFLAGS+=-Tldscripts/lnsc-2420-release.ld
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +97,10 @@ SECTIONS
|
||||||
|
|
||||||
. = ALIGN(4);
|
. = ALIGN(4);
|
||||||
end = .;
|
end = .;
|
||||||
|
|
||||||
|
.conf : {
|
||||||
|
__conf_start = .;
|
||||||
|
} >conf
|
||||||
}
|
}
|
||||||
|
|
||||||
PROVIDE(_stack = ORIGIN(ram) + LENGTH(ram));
|
PROVIDE(_stack = ORIGIN(ram) + LENGTH(ram));
|
||||||
|
|
|
@ -3,10 +3,18 @@
|
||||||
/* Define memory regions. */
|
/* Define memory regions. */
|
||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
rom (rx) : ORIGIN = 0x08000000, LENGTH = 32K
|
rom (rx) : ORIGIN = 0x08000000, LENGTH = 31K
|
||||||
|
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 3b89fc5999874c49f6f5be65bbd5e75f7d77469c
|
Subproject commit c82c7406aa57948d1adabfdc51e8577ae37e99d4
|
35
src/addon_io.c
Normal file
35
src/addon_io.c
Normal 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
14
src/addon_io.h
Normal 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
284
src/bmp280.c
Normal 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
18
src/bmp280.h
Normal 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
77
src/bmp280_comp.c
Normal 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
23
src/bmp280_comp.h
Normal 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
|
|
@ -6,10 +6,24 @@
|
||||||
* 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. */
|
||||||
|
|
||||||
#define CAL_FACTOR_U_BAT 1000
|
/* Values for the device at the B26 tower */
|
||||||
#define CAL_FACTOR_U_SOLAR 1002
|
|
||||||
#define CAL_FACTOR_U_SW 1005
|
#if 0
|
||||||
|
#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
|
||||||
|
|
|
@ -2,28 +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 "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",
|
||||||
"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;
|
||||||
|
@ -40,6 +45,8 @@ 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;
|
||||||
|
|
||||||
|
@ -49,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,
|
||||||
|
@ -83,15 +96,21 @@ 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)
|
||||||
if((time_in_state > SLEEP_STATE_DELAY)
|
if((time_in_state > FLASH_CONFIG_SLEEP_STATE_DELAY)
|
||||||
&& (current_switch_state == true)
|
&& (current_switch_state == true)
|
||||||
&& (solar_switch_onoff_duration > SLEEP_SWITCH_DELAY)
|
&& (solar_switch_onoff_duration > FLASH_CONFIG_SLEEP_SWITCH_DELAY)
|
||||||
&& (meas->avg_i_solar < sleep_solar_current)) {
|
&& (meas->avg_i_solar < sleep_solar_current)) {
|
||||||
return CHARGE_SLEEP;
|
return CHARGE_SLEEP;
|
||||||
}
|
}
|
||||||
|
@ -127,28 +146,58 @@ static void solar_fsm_update(uint64_t uptime_ms, struct MeasurementResult *meas)
|
||||||
charge_state,
|
charge_state,
|
||||||
charge_time_in_state);
|
charge_time_in_state);
|
||||||
|
|
||||||
// time limit for initial charging
|
// switch to hold state when high threshold is reached
|
||||||
if(charge_time_in_state > INITIAL_CHARGE_HOLD_TIME) {
|
if(meas->u_bat >= u_bat_initial_full) {
|
||||||
|
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:
|
||||||
// FIXME: dynamically adjust thresholds
|
if(charge_time_in_state < FLASH_CONFIG_INITIAL_TO_FLOAT_TRANSITION_TIME) {
|
||||||
|
// 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_float_full,
|
u_bat_full,
|
||||||
u_bat_float_low,
|
u_bat_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
|
// time limit for transition to float charging reached
|
||||||
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:
|
||||||
|
@ -162,13 +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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// low-current limit (go to sleep at night)
|
|
||||||
if(meas->i_solar < sleep_solar_current) {
|
|
||||||
charge_state = CHARGE_SLEEP;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -190,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();
|
||||||
}
|
}
|
||||||
|
@ -201,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;
|
||||||
|
@ -233,11 +297,24 @@ 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 > 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(meas->u_bat < u_bat_load_off) {
|
if((overload_retry_time != FLASH_CONFIG_OVERLOAD_RETRY_TIME) &&
|
||||||
|
(discharge_time_in_state > 300000)) {
|
||||||
|
// overload did not trigger for 5 minutes, so we assume it’s 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;
|
||||||
|
@ -249,19 +326,38 @@ 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->u_bat > u_bat_load_on)
|
if((meas->avg_u_bat > u_bat_load_on)
|
||||||
&& (discharge_time_in_state > 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:
|
||||||
|
@ -280,28 +376,35 @@ 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(U_BAT_REGULATION_CORRIDOR),
|
u_bat_regulation_corridor = fxp_div(FXP_FROM_INT(FLASH_CONFIG_U_BAT_REGULATION_CORRIDOR),
|
||||||
FXP_FROM_INT(1000));
|
FXP_FROM_INT(1000));
|
||||||
|
|
||||||
u_bat_initial_full = fxp_div(FXP_FROM_INT(U_BAT_INITIAL_FULL), 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_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_float_full = fxp_div(FXP_FROM_INT(U_BAT_FLOAT_FULL), FXP_FROM_INT(1000));
|
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(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(MIN_CHARGE_PUMP_EXCESS_VOLTAGE),
|
min_charge_pump_excess_voltage = fxp_div(FXP_FROM_INT(FLASH_CONFIG_MIN_CHARGE_PUMP_EXCESS_VOLTAGE),
|
||||||
FXP_FROM_INT(1000));
|
FXP_FROM_INT(1000));
|
||||||
|
|
||||||
u_bat_load_on = fxp_div(FXP_FROM_INT(U_BAT_LOAD_ON), 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_off = fxp_div(FXP_FROM_INT(U_BAT_LOAD_OFF), FXP_FROM_INT(1000));
|
u_bat_load_off = fxp_div(FXP_FROM_INT(FLASH_CONFIG_U_BAT_LOAD_OFF), FXP_FROM_INT(1000));
|
||||||
|
|
||||||
load_current_limit = fxp_div(FXP_FROM_INT(LOAD_CURRENT_LIMIT_MA), FXP_FROM_INT(1000));
|
load_current_limit = fxp_div(FXP_FROM_INT(FLASH_CONFIG_LOAD_CURRENT_LIMIT_MA), FXP_FROM_INT(1000));
|
||||||
|
|
||||||
internal_temperature_limit = fxp_div(FXP_FROM_INT(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(INTERNAL_TEMPERATURE_RECOVERY), FXP_FROM_INT(10));
|
internal_temperature_recovery = fxp_div(FXP_FROM_INT(FLASH_CONFIG_INTERNAL_TEMPERATURE_RECOVERY), FXP_FROM_INT(10));
|
||||||
|
|
||||||
sleep_solar_current = fxp_div(FXP_FROM_INT(SLEEP_SOLAR_CURRENT), FXP_FROM_INT(1000));
|
external_temperature_limit = fxp_div(FXP_FROM_INT(FLASH_CONFIG_EXTERNAL_TEMPERATURE_LIMIT), FXP_FROM_INT(10));
|
||||||
sleep_solar_excess_voltage = fxp_div(FXP_FROM_INT(SLEEP_SOLAR_EXCESS_VOLTAGE), FXP_FROM_INT(1000));
|
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_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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -352,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)));
|
||||||
}
|
}
|
||||||
|
@ -361,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;
|
||||||
|
|
||||||
|
|
|
@ -11,10 +11,14 @@ 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_TEMPERATURE
|
CHARGE_HIGH_INTERNAL_TEMPERATURE,
|
||||||
|
CHARGE_LOW_EXTERNAL_TEMPERATURE,
|
||||||
|
|
||||||
|
CHARGE_NUM_STATES
|
||||||
};
|
};
|
||||||
|
|
||||||
enum DischargeState
|
enum DischargeState
|
||||||
|
@ -22,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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
95
src/config.h
95
src/config.h
|
@ -1,95 +0,0 @@
|
||||||
#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
|
|
7
src/flash_config.c
Normal file
7
src/flash_config.c
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#include "flash_config.h"
|
||||||
|
|
||||||
|
bool flash_config_is_present(void)
|
||||||
|
{
|
||||||
|
return (FLASH_CONFIG_CAL_FACTOR_U_BAT != 0xFFFF)
|
||||||
|
&& (FLASH_CONFIG_DEEPSLEEP_DURATION != 0xFFFFFFFF);
|
||||||
|
}
|
145
src/flash_config.h
Normal file
145
src/flash_config.h
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
#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
|
171
src/i2c_dma.c
Normal file
171
src/i2c_dma.c
Normal 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
22
src/i2c_dma.h
Normal 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
|
107
src/main.c
107
src/main.c
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
#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>
|
||||||
|
|
||||||
|
@ -14,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 "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)
|
||||||
|
@ -34,6 +43,16 @@ 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) {
|
||||||
|
@ -152,6 +171,24 @@ 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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,6 +221,25 @@ 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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,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;
|
||||||
|
@ -221,9 +300,12 @@ 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();
|
||||||
|
@ -233,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");
|
||||||
|
@ -244,9 +332,14 @@ 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();
|
||||||
|
|
||||||
|
@ -267,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);
|
||||||
|
|
||||||
|
@ -275,21 +371,26 @@ 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) > DEEPSLEEP_DELAY) {
|
if((timebase_ms - charge_control_idle_since) > FLASH_CONFIG_DEEPSLEEP_DELAY) {
|
||||||
low_power_mode(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 {
|
||||||
charge_control_was_idle = false;
|
charge_control_was_idle = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
timebase_ms++;
|
timebase_ms++;
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,7 @@
|
||||||
#include "pinout.h"
|
#include "pinout.h"
|
||||||
|
|
||||||
#include "measurement.h"
|
#include "measurement.h"
|
||||||
#include "calibration.h"
|
#include "flash_config.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];
|
||||||
|
@ -55,7 +54,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(adc_val), fxp_div(FXP_FROM_INT(33), FXP_FROM_INT(10))),
|
fxp_mult(FXP_FROM_INT((int32_t)adc_val), fxp_div(FXP_FROM_INT(33), FXP_FROM_INT(10))),
|
||||||
FXP_FROM_INT(4096));
|
FXP_FROM_INT(4096));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,23 +93,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(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(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));
|
||||||
avg_alpha_i_load = fxp_div(FXP_FROM_INT(AVG_ALPHA_I_LOAD), 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_u_bat = fxp_div(FXP_FROM_INT(AVG_ALPHA_U_BAT), 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_sw = fxp_div(FXP_FROM_INT(AVG_ALPHA_U_SW), 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_solar = fxp_div(FXP_FROM_INT(AVG_ALPHA_U_SOLAR), 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_temp = fxp_div(FXP_FROM_INT(AVG_ALPHA_TEMP), FXP_FROM_INT(1000));
|
avg_alpha_temp = fxp_div(FXP_FROM_INT(FLASH_CONFIG_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);
|
||||||
|
|
17
src/pinout.h
17
src/pinout.h
|
@ -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
|
||||||
|
|
125
utils/config_b26.yaml
Normal file
125
utils/config_b26.yaml
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
# 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
|
116
utils/config_dev.yaml
Normal file
116
utils/config_dev.yaml
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
# 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
|
73
utils/config_to_hex.py
Executable file
73
utils/config_to_hex.py
Executable file
|
@ -0,0 +1,73 @@
|
||||||
|
#!/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])
|
18
utils/upload_config_hex.sh
Executable file
18
utils/upload_config_hex.sh
Executable file
|
@ -0,0 +1,18 @@
|
||||||
|
#!/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
|
Loading…
Reference in a new issue