mirror of
https://github.com/sjlongland/cluster-powerctl.git
synced 2025-09-14 04:23:15 +10:00
powerctl: Re-work controller logic.
This commit is contained in:
parent
dd0f371b7c
commit
03161f8d8e
118
README.md
118
README.md
@ -1,11 +1,12 @@
|
|||||||
Power controller for solar powered personal cloud
|
Power controller for solar powered personal cloud
|
||||||
=================================================
|
=================================================
|
||||||
|
|
||||||
This firmware is intended to control the charging of a battery bank that
|
This firmware is intended to control the charging of a battery bank that powers
|
||||||
powers a small computer cluster running a private cloud system. The
|
a small computer cluster running a private cloud system. The system has two
|
||||||
idea is to try to keep the battery within a small range of voltages,
|
charging sources: mains power and solar power.
|
||||||
charging from solar or mains based chargers as necessary to top the
|
|
||||||
battery back up.
|
The chargers are assumed to have a logic-level signal which, when pulled low,
|
||||||
|
shuts down the relevant charger.
|
||||||
|
|
||||||
Circuit description
|
Circuit description
|
||||||
-------------------
|
-------------------
|
||||||
@ -13,20 +14,22 @@ Circuit description
|
|||||||
An ATTiny24A microcontroller runs from a 5V rail derived from the
|
An ATTiny24A microcontroller runs from a 5V rail derived from the
|
||||||
battery. It uses its internal RC oscillator running at 1MHz.
|
battery. It uses its internal RC oscillator running at 1MHz.
|
||||||
|
|
||||||
Connected to PB0 and PB1 are the two MOSFETs that turn on the input
|
Connected to PB0 and PB1 are the two transistors that turn on the input
|
||||||
sources, one for solar (PB1), the other for a mains charger (PB0).
|
sources, one for solar (PB1), the other for a mains charger (PB0). These are
|
||||||
|
NPN bi-polar junction transistors. When the base is turned on via PB0 or PB1,
|
||||||
|
this allows current to flow from the collector to ground, effectively pulling
|
||||||
|
the relevant logic level output low.
|
||||||
|
|
||||||
The source pins of these connect to the battery positive input, so when
|
Connected to PB2 is a third NPN transistor which is wired to a P-channel
|
||||||
the FET is on, power may flow from that source to the battery.
|
MOSFET, such that turning the transistor on also turns on that MOSFET. This
|
||||||
|
is PWM-controlled to manage some cooling fans. The internal temperature
|
||||||
A third MOSFET connects to PB2, this is PWM-controlled to manage some
|
sensor is used to decide whether the fans should be on or off, and at what
|
||||||
cooling fans. The internal temperature sensor is used to decide whether
|
speed.
|
||||||
the fans should be on or off, and at what speed.
|
|
||||||
|
|
||||||
Connecting to each input, and to the battery, are separate voltage
|
Connecting to each input, and to the battery, are separate voltage
|
||||||
dividers, comprising of a 1.5kOhm and 100Ohm resistors. These divide
|
dividers, comprising of a 1.5kOhm and 100Ohm resistors. These divide
|
||||||
the input voltage by 16, and the divided voltage is fed into the ADC
|
the input voltage by 16, and the divided voltage is fed into the ADC
|
||||||
pins PA0 (mains), PA1 (solar) and PA2 (battery).
|
pins PA1 (solar) and PA2 (battery).
|
||||||
|
|
||||||
LEDs connect to PA7, PA6, PA5, PA4 and PA3 to 0v. ICSP is shared with the
|
LEDs connect to PA7, PA6, PA5, PA4 and PA3 to 0v. ICSP is shared with the
|
||||||
LEDs.
|
LEDs.
|
||||||
@ -34,18 +37,17 @@ LEDs.
|
|||||||
GPIOs
|
GPIOs
|
||||||
-----
|
-----
|
||||||
|
|
||||||
PB0: Mains MOSFET (active HIGH)
|
PB0: Mains enable (active LOW)
|
||||||
PB1: Solar MOSFET (active HIGH)
|
PB1: Solar enable (active LOW)
|
||||||
PB2: Fan MOSFET (active HIGH)
|
PB2: Fan MOSFET (active HIGH)
|
||||||
PB3: nRESET
|
PB3: nRESET
|
||||||
PA7: Temperature Low LED (active HIGH)
|
PA7: Temperature Low LED (active HIGH)
|
||||||
PA6: Temperature High LED (active HIGH) + ICSP MOSI
|
PA6: Temperature High LED (active HIGH) + ICSP MOSI
|
||||||
PA5: Battery Voltage High LED (active HIGH) + ICSP MISO
|
PA5: Mains Floating LED (active HIGH) + ICSP MISO
|
||||||
PA4: Warning LED (active high) + ICSP SCK + Debug Tx
|
PA4: Mains Charging LED (active high) + ICSP SCK + Debug Tx
|
||||||
PA3: Battery Voltage Good LED (active HIGH)
|
PA3: Battery Voltage Good LED (active HIGH)
|
||||||
PA2: Analogue Input: Battery voltage
|
PA2: Analogue Input: Battery voltage
|
||||||
PA1: Analogue Input: Solar voltage
|
PA1: Analogue Input: Solar voltage
|
||||||
PA0: Analogue Input: Mains voltage
|
|
||||||
|
|
||||||
Firmware description
|
Firmware description
|
||||||
--------------------
|
--------------------
|
||||||
@ -64,48 +66,60 @@ loop. If DEBUG is enabled, "INIT xx" will be displayed, where xx is the
|
|||||||
value of the MCUSR register.
|
value of the MCUSR register.
|
||||||
|
|
||||||
Two counters are decremented by the Timer 1 overflow interrupt.
|
Two counters are decremented by the Timer 1 overflow interrupt.
|
||||||
`led_timeout` causes the main loop to update the state of the LEDs when
|
`t_second` causes the main loop to decrement each one-second timer, `t_adc`
|
||||||
it hits zero, and `adc_timeout` triggers a new ADC capture run when it
|
causes the ADC state machine to advance one tick and checkt the state of the
|
||||||
reaches zero.
|
channels.
|
||||||
|
|
||||||
The three analogue inputs and temperature sensor are scanned when
|
The two analogue inputs and temperature sensor are scanned when
|
||||||
`adc_timeout` reaches zero, then the state analysed. The battery
|
`t_adc` reaches zero, then the state analysed.
|
||||||
voltage is compared to the previous reading to dissern if the battery is
|
|
||||||
charging, discharging or holding steady.
|
|
||||||
|
|
||||||
If this state has not changed, a battery state counter increments,
|
The charger is in one of four states:
|
||||||
otherwise it is reset.
|
* initialisation
|
||||||
|
* solar
|
||||||
|
* charging from mains
|
||||||
|
* floating on mains
|
||||||
|
|
||||||
The current state of the FETs is checked. Three states are valid:
|
On power up, we enter the initialisation state. In this state, we wait for
|
||||||
- Idle state: all FETs off
|
the first ADC readings to arrive before deciding on whether we run from mains
|
||||||
- Mains charge: MAINS FET turned on
|
power or solar. During this time, all power inputs are inhibited.
|
||||||
- Solar charge: SOLAR FET turned on
|
|
||||||
|
|
||||||
IF statements at this point compare the battery voltage to the
|
If either the battery or solar input are below minimum thresholds, the mains
|
||||||
thresholds, and decide whether to switch voltage or not.
|
charger is turned on and we enter the "charging from mains" state.
|
||||||
|
Otherwise the solar input is used and we enter the "solar" state.
|
||||||
|
|
||||||
|
In the "solar" state, we monitor the battery voltage. If it drops below the
|
||||||
|
minimum voltage, we switch to the "charging from mains" state.
|
||||||
|
|
||||||
|
In the "charging from mains" state, we monitor the battery charging progress.
|
||||||
|
Upon reaching the high voltage threshold, we switch to the "floating on mains"
|
||||||
|
state.
|
||||||
|
|
||||||
|
In the "floating on mains" state, we wait a minimum of one hour for the
|
||||||
|
mains charger to finish its cycle. If the battery drops below the
|
||||||
|
high-voltage threshold, we move back to the "charging from mains" state.
|
||||||
|
|
||||||
|
Once an hour has elapsed in the floating state, if the solar input is above
|
||||||
|
the minimum threshold, we turn off the mains charger and switch to the "solar"
|
||||||
|
state.
|
||||||
|
|
||||||
LED indications
|
LED indications
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
The LEDs have the following meanings:
|
The LEDs have the following meanings:
|
||||||
|
|
||||||
Warning LED (PA4; DEBUG undefined):
|
|
||||||
- Off: No warning condition
|
|
||||||
- Flashing: Battery below critical threshold or temperature above
|
|
||||||
maximum threshold
|
|
||||||
- On: Not used
|
|
||||||
|
|
||||||
When in DEBUG mode, this LED may flicker with serial activity, and will
|
|
||||||
remain ON when idle.
|
|
||||||
|
|
||||||
Temperature LEDs (PA6, PA7):
|
Temperature LEDs (PA6, PA7):
|
||||||
- PA7 On, PA6 Off: Temperature below minimum threshold
|
- PA7 Off, PA6 Off: Temperature below minimum threshold
|
||||||
- PA7 Flashing, PA6 Off: Temperature above minimum threshold
|
- PA7 On, PA6 Off: Temperature between thresholds
|
||||||
- PA7 Off, PA6 Flashing: Temperature above maximum threshold
|
- PA7 Off, PA6 On: Temperature above maximum threshold
|
||||||
- Other states: not used
|
|
||||||
|
|
||||||
Battery LEDs (PA5, PA3):
|
Battery State LED (PA3):
|
||||||
- PA3 Flashing, PA5 Off: Battery below low threshold
|
- PA3 Off: Battery is below minimum voltage
|
||||||
- PA3 Off, PA5 Flashing: Battery above high threshold
|
- PA3 On: Battery is above minimum voltage
|
||||||
- PA3 On, PA5 Off: Battery is in "good" range (between low and high)
|
|
||||||
- Other states: not used
|
Mains Charger State LEDs (PA4, PA5):
|
||||||
|
- PA4 Off, PA5 Off: Mains supply is off
|
||||||
|
- PA4 On, PA5 Off: Mains supply is charging the battery
|
||||||
|
- PA4 Off, PA5 On: Mains supply is floating the battery
|
||||||
|
|
||||||
|
Note that in debug mode, PA4 instead becomes the serial TX line, and so will
|
||||||
|
remain on in the idle state, and will flicker with serial activity.
|
||||||
|
8
board.h
8
board.h
@ -22,15 +22,15 @@
|
|||||||
/* LEDs */
|
/* LEDs */
|
||||||
#define LED_TEMP_LOW (1 << 7)
|
#define LED_TEMP_LOW (1 << 7)
|
||||||
#define LED_TEMP_HIGH (1 << 6)
|
#define LED_TEMP_HIGH (1 << 6)
|
||||||
#define LED_BATT_HIGH (1 << 5)
|
#define LED_BATT_FLT (1 << 5)
|
||||||
#define LED_WARNING (1 << 4)
|
#define LED_BATT_CHG (1 << 4)
|
||||||
#define LED_BATT_GOOD (1 << 3)
|
#define LED_BATT_GOOD (1 << 3)
|
||||||
#define LED_PORT PORTA
|
#define LED_PORT PORTA
|
||||||
#define LED_PORT_DDR_REG DDRA
|
#define LED_PORT_DDR_REG DDRA
|
||||||
#define LED_PORT_DDR_VAL ( LED_TEMP_LOW \
|
#define LED_PORT_DDR_VAL ( LED_TEMP_LOW \
|
||||||
| LED_TEMP_HIGH \
|
| LED_TEMP_HIGH \
|
||||||
| LED_BATT_HIGH \
|
| LED_BATT_FLT \
|
||||||
| LED_WARNING \
|
| LED_BATT_CHG \
|
||||||
| LED_BATT_GOOD )
|
| LED_BATT_GOOD )
|
||||||
|
|
||||||
/* MOSFETs */
|
/* MOSFETs */
|
||||||
|
428
powerctl.c
428
powerctl.c
@ -38,36 +38,22 @@
|
|||||||
)
|
)
|
||||||
|
|
||||||
/* --- Thresholds --- */
|
/* --- Thresholds --- */
|
||||||
#define V_CH_ADC ADC_READ(V_CH_MV)
|
|
||||||
#define V_H_ADC ADC_READ(V_H_MV)
|
#define V_H_ADC ADC_READ(V_H_MV)
|
||||||
#define V_L_ADC ADC_READ(V_L_MV)
|
#define V_L_ADC ADC_READ(V_L_MV)
|
||||||
#define V_CL_ADC ADC_READ(V_CL_MV)
|
#define V_SOL_MIN_ADC ADC_READ(V_SOL_MIN_MV)
|
||||||
#define V_DELTA_ADC ADC_READ(V_DELTA_MV)
|
|
||||||
|
|
||||||
/* --- Timeouts --- */
|
/* --- Timeouts --- */
|
||||||
#define T_LED_TICKS TIMER_TICKS(T_LED_MS)
|
|
||||||
#define T_ADC_TICKS TIMER_TICKS(T_ADC_MS)
|
#define T_ADC_TICKS TIMER_TICKS(T_ADC_MS)
|
||||||
|
|
||||||
#define STATE_DIS_CHECK (0) /*!< Check voltage in discharge state */
|
#define STATE_INIT (0) /*!< Initial start-up state */
|
||||||
#define STATE_DIS_WAIT (1) /*!< Wait in discharge state */
|
#define STATE_SOLAR (1) /*!< Running from solar */
|
||||||
#define STATE_CHG_CHECK (2) /*!< Check voltage in charging state */
|
#define STATE_MAINS_CHG (2) /*!< Charging from mains */
|
||||||
#define STATE_CHG_WAIT (3) /*!< Wait in charging state */
|
#define STATE_MAINS_FLT (3) /*!< Floating on mains */
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Charger state machine state. We have four states we can be in.
|
* Charger state machine state. We have four states we can be in.
|
||||||
*/
|
*/
|
||||||
static volatile uint8_t charger_state = STATE_DIS_CHECK;
|
static volatile uint8_t charger_state = STATE_INIT;
|
||||||
|
|
||||||
#define SRC_NONE (0) /*!< Turn off all chargers */
|
|
||||||
#define SRC_SOLAR (1) /*!< Turn on solar charger */
|
|
||||||
#define SRC_MAINS (2) /*!< Turn on mains charger */
|
|
||||||
#define SRC_ALT (3) /*!< Alternate to *other* source,
|
|
||||||
* valid for select_src only.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* Charging source.
|
|
||||||
*/
|
|
||||||
static volatile uint8_t charge_source = SRC_NONE;
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* For state machine, the last state of the ADC MUX so we know whether
|
* For state machine, the last state of the ADC MUX so we know whether
|
||||||
@ -76,16 +62,15 @@ static volatile uint8_t charge_source = SRC_NONE;
|
|||||||
*/
|
*/
|
||||||
static volatile uint8_t last_admux = 0;
|
static volatile uint8_t last_admux = 0;
|
||||||
|
|
||||||
/*!
|
|
||||||
* For state machine, determines what the battery was one sample ago so
|
|
||||||
* we know if it's charging, discharging, or remaining static. ADC units.
|
|
||||||
*/
|
|
||||||
static volatile uint16_t v_bl_adc = 0;
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Current reading of the battery voltage in ADC units.
|
* Current reading of the battery voltage in ADC units.
|
||||||
*/
|
*/
|
||||||
static volatile uint16_t v_bn_adc = 0;
|
static volatile uint16_t v_bat_adc = 0;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Current reading of the solar voltage in ADC units.
|
||||||
|
*/
|
||||||
|
static volatile uint16_t v_sol_adc = 0;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Current reading of the internal temperature sensor in ADC units.
|
* Current reading of the internal temperature sensor in ADC units.
|
||||||
@ -103,9 +88,9 @@ static volatile uint16_t t_second = 0;
|
|||||||
static volatile uint16_t t_adc = 0;
|
static volatile uint16_t t_adc = 0;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* How long before we change LED states?
|
* Float timeout
|
||||||
*/
|
*/
|
||||||
static volatile uint16_t t_led = 0;
|
static volatile uint16_t t_float = 0;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Fan kick-start timeout
|
* Fan kick-start timeout
|
||||||
@ -113,19 +98,9 @@ static volatile uint16_t t_led = 0;
|
|||||||
static volatile uint8_t t_fan = 0;
|
static volatile uint8_t t_fan = 0;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Charger timeout
|
* ADC readings taken?
|
||||||
*/
|
*/
|
||||||
static volatile uint8_t t_charger = T_LF_S;
|
static volatile uint8_t adc_checked = 0;
|
||||||
|
|
||||||
/*!
|
|
||||||
* Charger warning timeout
|
|
||||||
*/
|
|
||||||
static volatile uint8_t t_cwarn = 0;
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* Are we presently in a warning state?
|
|
||||||
*/
|
|
||||||
static volatile uint8_t charger_warning = 0;
|
|
||||||
|
|
||||||
/* Debug messages */
|
/* Debug messages */
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
@ -162,190 +137,99 @@ static inline void uart_tx_bool(const char* msg, uint8_t val) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Enter charger warning state. This indicates that the battery
|
* Switch to charging from mains power.
|
||||||
* *should* be charging, but isn't due to insufficient input current from
|
|
||||||
* the charger.
|
|
||||||
*/
|
*/
|
||||||
static inline void enter_warning() {
|
static void enter_mains_chg(void) {
|
||||||
if (charger_warning)
|
/* Enable mains power */
|
||||||
LED_PORT |= LED_WARNING;
|
|
||||||
else
|
|
||||||
charger_warning = 1;
|
|
||||||
|
|
||||||
/* Reset our timer */
|
|
||||||
t_cwarn = T_CWARN_S;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* Leave the charger warning state. This indicates the charger has left
|
|
||||||
* the charging state or the battery has begun charging.
|
|
||||||
*/
|
|
||||||
static inline void exit_warning() {
|
|
||||||
charger_warning = 0;
|
|
||||||
t_cwarn = 0;
|
|
||||||
LED_PORT &= ~LED_WARNING;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* Switch between chargers. This is does a "break-before-make" switchover
|
|
||||||
* of charging sources to switch from mains to solar, solar to mains, or to
|
|
||||||
* switch from charging to discharging mode. It expressly forbids turning
|
|
||||||
* both chargers on simultaneously.
|
|
||||||
*
|
|
||||||
* Added is the ability to just alternate between sources.
|
|
||||||
*/
|
|
||||||
static void select_src(uint8_t src) {
|
|
||||||
if (src == SRC_ALT) {
|
|
||||||
if (charge_source == SRC_SOLAR)
|
|
||||||
src = SRC_MAINS;
|
|
||||||
else
|
|
||||||
src = SRC_SOLAR;
|
|
||||||
}
|
|
||||||
#ifdef DEBUG
|
|
||||||
uart_tx_str(STR_SELECT_SRC);
|
|
||||||
#endif
|
|
||||||
switch(src) {
|
|
||||||
case SRC_SOLAR:
|
|
||||||
FET_PORT &= ~FET_MAINS;
|
FET_PORT &= ~FET_MAINS;
|
||||||
FET_PORT |= FET_SOLAR;
|
|
||||||
charge_source = SRC_SOLAR;
|
/* Indicate via LEDs */
|
||||||
#ifdef DEBUG
|
LED_PORT |= LED_BATT_CHG;
|
||||||
uart_tx_str(STR_SRC_SOLAR);
|
LED_PORT &= ~LED_BATT_FLT;
|
||||||
#endif
|
|
||||||
break;
|
/* Enter state */
|
||||||
case SRC_MAINS:
|
charger_state = STATE_MAINS_CHG;
|
||||||
FET_PORT &= ~FET_SOLAR;
|
|
||||||
FET_PORT |= FET_MAINS;
|
|
||||||
charge_source = SRC_MAINS;
|
|
||||||
#ifdef DEBUG
|
|
||||||
uart_tx_str(STR_SRC_MAINS);
|
|
||||||
#endif
|
|
||||||
break;
|
|
||||||
case SRC_NONE:
|
|
||||||
default:
|
|
||||||
FET_PORT &= ~FET_SRC_MASK;
|
|
||||||
charge_source = SRC_NONE;
|
|
||||||
#ifdef DEBUG
|
|
||||||
uart_tx_str(STR_SRC_NONE);
|
|
||||||
#endif
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
#ifdef DEBUG
|
|
||||||
uart_tx_str(STR_NL);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void discharge_check() {
|
/*!
|
||||||
/* Decide when we should do our next check */
|
* Switch to floating on mains power.
|
||||||
#ifdef DEBUG
|
|
||||||
uart_tx_str(STR_DIS); uart_tx_str(STR_CHK);
|
|
||||||
uart_tx_bool(STR_V_BN_GE_V_H, v_bn_adc >= V_H_ADC);
|
|
||||||
#endif
|
|
||||||
if (v_bn_adc >= V_H_ADC)
|
|
||||||
t_charger = T_LF_S;
|
|
||||||
else
|
|
||||||
t_charger = T_HF_S;
|
|
||||||
|
|
||||||
/* Snapshot the current battery voltage */
|
|
||||||
v_bl_adc = v_bn_adc;
|
|
||||||
|
|
||||||
/* Exit state */
|
|
||||||
#ifdef DEBUG
|
|
||||||
uart_tx_bool(STR_V_BN_GT_V_L, v_bn_adc > V_L_ADC);
|
|
||||||
#endif
|
|
||||||
if (v_bn_adc > V_L_ADC)
|
|
||||||
charger_state = STATE_DIS_WAIT;
|
|
||||||
else
|
|
||||||
charger_state = STATE_CHG_CHECK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void discharge_wait() {
|
|
||||||
#ifdef DEBUG
|
|
||||||
uart_tx_str(STR_DIS); uart_tx_str(STR_WAIT);
|
|
||||||
uart_tx_bool(STR_V_BN_LE_V_CL, v_bn_adc <= V_CL_ADC);
|
|
||||||
#endif
|
|
||||||
if (v_bn_adc <= V_CL_ADC)
|
|
||||||
/* Expire timer */
|
|
||||||
t_charger = 0;
|
|
||||||
|
|
||||||
/* Exit state if timer is expired */
|
|
||||||
#ifdef DEBUG
|
|
||||||
uart_tx_bool(STR_T_CHARGER, !t_charger);
|
|
||||||
#endif
|
|
||||||
if (!t_charger)
|
|
||||||
charger_state = STATE_DIS_CHECK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void charge_check() {
|
|
||||||
#ifdef DEBUG
|
|
||||||
uart_tx_str(STR_CHG); uart_tx_str(STR_CHK);
|
|
||||||
uart_tx_bool(STR_V_BN_LE_V_CL, v_bn_adc <= V_CL_ADC);
|
|
||||||
#endif
|
|
||||||
/* Still need to charge, when should we next check? */
|
|
||||||
if (v_bn_adc <= V_CL_ADC)
|
|
||||||
t_charger = T_HF_S;
|
|
||||||
else
|
|
||||||
t_charger = T_LF_S;
|
|
||||||
|
|
||||||
/* Critically high voltage check */
|
|
||||||
if (v_bn_adc >= V_CH_ADC) {
|
|
||||||
/* We must stop now! */
|
|
||||||
select_src(SRC_NONE);
|
|
||||||
charger_state = STATE_DIS_CHECK;
|
|
||||||
charger_warning = 0;
|
|
||||||
t_cwarn = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (charge_source == SRC_NONE) {
|
|
||||||
/* Not yet charging, switch to primary source */
|
|
||||||
select_src(SRC_SOLAR);
|
|
||||||
/* As we have just started charging, reset warning timer */
|
|
||||||
exit_warning();
|
|
||||||
} else if (v_bn_adc <= (v_bl_adc + V_DELTA_ADC)) {
|
|
||||||
/* Check for high voltage threshold, are we there yet? */
|
|
||||||
#ifdef DEBUG
|
|
||||||
uart_tx_bool(STR_V_BN_GE_V_H, v_bn_adc >= V_H_ADC);
|
|
||||||
#endif
|
|
||||||
if (v_bn_adc >= V_H_ADC) {
|
|
||||||
/* We are done now */
|
|
||||||
select_src(SRC_NONE);
|
|
||||||
exit_warning();
|
|
||||||
return;
|
|
||||||
} else if (!t_cwarn) {
|
|
||||||
if (charger_warning) {
|
|
||||||
/*
|
|
||||||
* Situation still not improving,
|
|
||||||
* switch sources.
|
|
||||||
*/
|
*/
|
||||||
select_src(SRC_ALT);
|
static void enter_mains_float(void) {
|
||||||
}
|
/* Reset timer */
|
||||||
/* Reset our warning timer */
|
t_float = T_FLOAT_S;
|
||||||
enter_warning();
|
|
||||||
}
|
/* Indicate via LEDs */
|
||||||
} else if (!t_cwarn) {
|
LED_PORT &= ~LED_BATT_CHG;
|
||||||
/* Things are improving, reset warning if set. */
|
LED_PORT |= LED_BATT_FLT;
|
||||||
exit_warning();
|
|
||||||
|
/* Enter state */
|
||||||
|
charger_state = STATE_MAINS_FLT;
|
||||||
}
|
}
|
||||||
|
|
||||||
v_bl_adc = v_bn_adc;
|
/*!
|
||||||
charger_state = STATE_CHG_WAIT;
|
* Switch to running on solar.
|
||||||
|
*/
|
||||||
|
static void enter_solar(void) {
|
||||||
|
/* Inhibit mains */
|
||||||
|
FET_PORT |= FET_MAINS;
|
||||||
|
|
||||||
|
/* Indicate via LEDs */
|
||||||
|
LED_PORT &= ~(LED_BATT_FLT | LED_BATT_CHG);
|
||||||
|
|
||||||
|
/* Enter state */
|
||||||
|
charger_state = STATE_SOLAR;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void charge_wait() {
|
/*!
|
||||||
#ifdef DEBUG
|
* Checks at start-up
|
||||||
uart_tx_str(STR_CHG); uart_tx_str(STR_WAIT);
|
*/
|
||||||
uart_tx_bool(STR_V_BN_GE_V_CH, v_bn_adc >= V_CH_ADC);
|
static void init_check(void) {
|
||||||
#endif
|
/* Wait until we have our first readings from the ADC */
|
||||||
if (v_bn_adc >= V_CH_ADC)
|
if (!adc_checked)
|
||||||
/* Expire timer */
|
return;
|
||||||
t_charger = 0;
|
|
||||||
|
|
||||||
#ifdef DEBUG
|
if ((v_bat_adc < V_L_ADC) || (v_sol_adc < V_SOL_MIN_ADC))
|
||||||
uart_tx_bool(STR_T_CHARGER, !t_charger);
|
/* Battery/solar is low, begin charging */
|
||||||
#endif
|
enter_mains_chg();
|
||||||
if (!t_charger)
|
else
|
||||||
charger_state = STATE_CHG_CHECK;
|
/* Run from solar */
|
||||||
|
enter_solar();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Checks whilst running on solar
|
||||||
|
*/
|
||||||
|
static void solar_check(void) {
|
||||||
|
if (v_bat_adc < V_L_ADC) {
|
||||||
|
/* Move to mains power */
|
||||||
|
enter_mains_chg();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Checks whilst charging from mains
|
||||||
|
*/
|
||||||
|
static void mains_chg_check(void) {
|
||||||
|
if (v_bat_adc >= V_H_ADC) {
|
||||||
|
/* We've reached the float voltage */
|
||||||
|
enter_mains_float();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Checks whilst floating on mains
|
||||||
|
*/
|
||||||
|
static void mains_float_check(void) {
|
||||||
|
if (v_bat_adc < V_H_ADC) {
|
||||||
|
/* We've regressed, go back to charging state! */
|
||||||
|
enter_mains_chg();
|
||||||
|
return;
|
||||||
|
} else if ((!t_float) && (v_sol_adc >= V_SOL_MIN_ADC)) {
|
||||||
|
/* Solar can take it from here */
|
||||||
|
enter_solar();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@ -358,7 +242,7 @@ int main(void) {
|
|||||||
|
|
||||||
/* Configure MOSFETs */
|
/* Configure MOSFETs */
|
||||||
FET_PORT_DDR_REG = FET_PORT_DDR_VAL;
|
FET_PORT_DDR_REG = FET_PORT_DDR_VAL;
|
||||||
FET_PORT = 0;
|
FET_PORT = FET_MAINS | FET_SOLAR;
|
||||||
|
|
||||||
/* Turn on ADC and timers */
|
/* Turn on ADC and timers */
|
||||||
PRR &= ~((1 << PRTIM0) | (1 << PRTIM1) | (1 << PRADC));
|
PRR &= ~((1 << PRTIM0) | (1 << PRTIM1) | (1 << PRADC));
|
||||||
@ -400,49 +284,8 @@ int main(void) {
|
|||||||
/* One second passed, tick down the 1-second timers. */
|
/* One second passed, tick down the 1-second timers. */
|
||||||
if (!t_second) {
|
if (!t_second) {
|
||||||
t_second = TIMER_FREQ;
|
t_second = TIMER_FREQ;
|
||||||
if (t_charger)
|
if (t_float)
|
||||||
t_charger--;
|
t_float--;
|
||||||
if (t_cwarn)
|
|
||||||
t_cwarn--;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (t_adc)
|
|
||||||
t_adc--;
|
|
||||||
|
|
||||||
if (!t_led) {
|
|
||||||
if (v_bn_adc <= V_CL_ADC) {
|
|
||||||
/* Battery is critically low */
|
|
||||||
LED_PORT &= ~LED_BATT_HIGH;
|
|
||||||
LED_PORT ^= LED_BATT_GOOD;
|
|
||||||
} else if (v_bn_adc <= V_L_ADC) {
|
|
||||||
/* Battery is low */
|
|
||||||
LED_PORT &= ~(LED_BATT_HIGH|LED_BATT_GOOD);
|
|
||||||
} else if (v_bn_adc <= V_H_ADC) {
|
|
||||||
/* Battery is in "good" range */
|
|
||||||
LED_PORT &= ~LED_BATT_HIGH;
|
|
||||||
LED_PORT |= LED_BATT_GOOD;
|
|
||||||
} else if (v_bn_adc <= V_CH_ADC) {
|
|
||||||
/* Battery is above "high" threshold */
|
|
||||||
LED_PORT |= LED_BATT_HIGH;
|
|
||||||
LED_PORT &= ~LED_BATT_GOOD;
|
|
||||||
} else {
|
|
||||||
/* Battery is critically high */
|
|
||||||
LED_PORT ^= LED_BATT_HIGH;
|
|
||||||
LED_PORT &= ~LED_BATT_GOOD;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (temp_adc < TEMP_MIN) {
|
|
||||||
LED_PORT |= LED_TEMP_LOW;
|
|
||||||
LED_PORT &= ~LED_TEMP_HIGH;
|
|
||||||
} else if (temp_adc < TEMP_MAX) {
|
|
||||||
LED_PORT ^= LED_TEMP_LOW;
|
|
||||||
LED_PORT &= ~LED_TEMP_HIGH;
|
|
||||||
} else {
|
|
||||||
LED_PORT &= ~LED_TEMP_LOW;
|
|
||||||
LED_PORT ^= LED_TEMP_HIGH;
|
|
||||||
}
|
|
||||||
|
|
||||||
t_led = T_LED_TICKS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!t_adc) {
|
if (!t_adc) {
|
||||||
@ -451,6 +294,28 @@ int main(void) {
|
|||||||
|
|
||||||
while(ADCSRA & (1 << ADEN));
|
while(ADCSRA & (1 << ADEN));
|
||||||
|
|
||||||
|
/* Temperature LED control */
|
||||||
|
if (temp_adc < TEMP_MIN) {
|
||||||
|
LED_PORT |= LED_TEMP_LOW;
|
||||||
|
LED_PORT &= ~LED_TEMP_HIGH;
|
||||||
|
} else if (temp_adc < TEMP_MAX) {
|
||||||
|
LED_PORT |= (LED_TEMP_LOW | LED_TEMP_HIGH);
|
||||||
|
} else {
|
||||||
|
LED_PORT &= ~LED_TEMP_LOW;
|
||||||
|
LED_PORT |= LED_TEMP_HIGH;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The "SOLAR" FET is no longer fitted, so this is more
|
||||||
|
* an indication of whether we consider solar to be
|
||||||
|
* "good enough". In short, it's just controlling the
|
||||||
|
* LED where the MOSFET was now.
|
||||||
|
*/
|
||||||
|
if (v_sol_adc < V_SOL_MIN_ADC)
|
||||||
|
FET_PORT |= FET_SOLAR;
|
||||||
|
else
|
||||||
|
FET_PORT &= ~FET_SOLAR;
|
||||||
|
|
||||||
/* Fan control */
|
/* Fan control */
|
||||||
if (t_fan) {
|
if (t_fan) {
|
||||||
/* Kick-start mode */
|
/* Kick-start mode */
|
||||||
@ -475,22 +340,29 @@ int main(void) {
|
|||||||
OCR0A = 0;
|
OCR0A = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Battery state LED control */
|
||||||
|
if (v_bat_adc <= V_L_ADC) {
|
||||||
|
LED_PORT &= ~LED_BATT_GOOD;
|
||||||
|
} else {
|
||||||
|
LED_PORT |= LED_BATT_GOOD;
|
||||||
|
}
|
||||||
|
|
||||||
/* Charger control */
|
/* Charger control */
|
||||||
switch (charger_state) {
|
switch (charger_state) {
|
||||||
case STATE_DIS_CHECK:
|
case STATE_INIT:
|
||||||
discharge_check();
|
init_check();
|
||||||
break;
|
break;
|
||||||
case STATE_DIS_WAIT:
|
case STATE_SOLAR:
|
||||||
discharge_wait();
|
solar_check();
|
||||||
break;
|
break;
|
||||||
case STATE_CHG_CHECK:
|
case STATE_MAINS_CHG:
|
||||||
charge_check();
|
mains_chg_check();
|
||||||
break;
|
break;
|
||||||
case STATE_CHG_WAIT:
|
case STATE_MAINS_FLT:
|
||||||
charge_wait();
|
mains_float_check();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
charger_state = STATE_DIS_CHECK;
|
charger_state = STATE_INIT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -505,8 +377,8 @@ ISR(TIM1_COMPA_vect) {
|
|||||||
if (t_second)
|
if (t_second)
|
||||||
t_second--;
|
t_second--;
|
||||||
|
|
||||||
if (t_led)
|
if (t_adc)
|
||||||
t_led--;
|
t_adc--;
|
||||||
}
|
}
|
||||||
|
|
||||||
ISR(ADC_vect) {
|
ISR(ADC_vect) {
|
||||||
@ -519,20 +391,14 @@ ISR(ADC_vect) {
|
|||||||
ADCSRA |= (1 << ADSC);
|
ADCSRA |= (1 << ADSC);
|
||||||
break;
|
break;
|
||||||
case ADC_MUX_BATT:
|
case ADC_MUX_BATT:
|
||||||
v_bn_adc = adc;
|
v_bat_adc = adc;
|
||||||
#if 0
|
|
||||||
/* Not being used for now */
|
|
||||||
ADMUX = ADC_MUX_SOLAR;
|
ADMUX = ADC_MUX_SOLAR;
|
||||||
ADCSRA |= (1 << ADSC);
|
ADCSRA |= (1 << ADSC);
|
||||||
break;
|
break;
|
||||||
case ADC_MUX_SOLAR:
|
case ADC_MUX_SOLAR:
|
||||||
adc_solar = adc;
|
v_sol_adc = adc;
|
||||||
ADMUX = ADC_MUX_MAINS;
|
/* Once we get here, we've done a full cycle */
|
||||||
ADCSRA |= (1 << ADSC);
|
adc_checked = 1;
|
||||||
break;
|
|
||||||
case ADC_MUX_MAINS:
|
|
||||||
adc_mains = adc;
|
|
||||||
#endif
|
|
||||||
default:
|
default:
|
||||||
ADMUX = ADC_MUX_TEMP;
|
ADMUX = ADC_MUX_TEMP;
|
||||||
ADCSRA &= ~(1 << ADEN);
|
ADCSRA &= ~(1 << ADEN);
|
||||||
|
@ -28,11 +28,6 @@
|
|||||||
*/
|
*/
|
||||||
#define T_ADC_MS (250)
|
#define T_ADC_MS (250)
|
||||||
|
|
||||||
/*!
|
|
||||||
* How long before we change LED states? Milliseconds.
|
|
||||||
*/
|
|
||||||
#define T_LED_MS (150)
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Temperature ranges and fan PWM settings
|
* Temperature ranges and fan PWM settings
|
||||||
*/
|
*/
|
||||||
@ -57,53 +52,29 @@
|
|||||||
|
|
||||||
/* --- Thresholds --- */
|
/* --- Thresholds --- */
|
||||||
|
|
||||||
/*!
|
|
||||||
* Critically high battery voltage. Exceeding this voltage could damage
|
|
||||||
* the battery or the equipment downstream of it.
|
|
||||||
*/
|
|
||||||
#define V_CH_MV (15500)
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* High battery voltage. If we reach this voltage and the charger
|
* High battery voltage. If we reach this voltage and the charger
|
||||||
* stops, just switch to discharge mode, consider the job done.
|
* stops, just switch to discharge mode, consider the job done.
|
||||||
*/
|
*/
|
||||||
#define V_H_MV (14000)
|
#define V_H_MV (14400)
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Low battery voltage. If the voltage dips to or below this level, we
|
* Low battery voltage. If the voltage dips to or below this level, we
|
||||||
* should turn the charger on.
|
* should turn the charger on.
|
||||||
*/
|
*/
|
||||||
#define V_L_MV (13800)
|
#define V_L_MV (12400)
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Critically low battery voltage. If we reach this level, we need to
|
* Solar minimum voltage. If the solar is below this threshold, we
|
||||||
* urgently turn the charger on and need to be ready to switch sources
|
* consider it too low to reliably charge the system.
|
||||||
* in a hurry if the chosen source isn't charging.
|
|
||||||
*/
|
*/
|
||||||
#define V_CL_MV (11800)
|
#define V_SOL_MIN_MV (18000)
|
||||||
|
|
||||||
/*!
|
|
||||||
* Battery minimum charge step. This is the amount of charge we expect
|
|
||||||
* the battery to have increased by before we consider flagging a
|
|
||||||
* warning.
|
|
||||||
*/
|
|
||||||
#define V_DELTA_MV (50)
|
|
||||||
|
|
||||||
/* --- Timeouts --- */
|
/* --- Timeouts --- */
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* High frequency polling period in seconds.
|
* How long do we remain on the mains charger after reaching V_H_MV?
|
||||||
*/
|
*/
|
||||||
#define T_HF_S (15)
|
#define T_FLOAT_S (3600)
|
||||||
|
|
||||||
/*!
|
|
||||||
* Low frequency polling period in seconds.
|
|
||||||
*/
|
|
||||||
#define T_LF_S (60)
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* How long before we consider a lack of voltage increase a warning?
|
|
||||||
*/
|
|
||||||
#define T_CWARN_S (10)
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user