2014-08-14 18:58:25 +01:00
// *************************************************************************************************************
// MightyBoost control sample sketch
// *************************************************************************************************************
2018-11-07 16:30:00 +00:00
// Copyright (2015) Felix Rusu, http://lowpowerlab.com
2015-03-10 14:02:19 +00:00
// http://lowpowerlab.com/mightyboost
// MightyBoost is a smart backup PSU controllable by Moteino, and this sketch is a sample control sketch to run
// MightyBoost in this mode.
// Be sure to check back for code updates and patches
// *************************************************************************************************************
// This sketch will provide control over the essential features of MightyBoost:
// - provide switched 5V power to a sensitive load like RaspberryPi which should not lose power instantly
// - Control the "5V*" output via Moteino+PowerButton (momentary tactile)
// - Monitor input supply and switch to battery backup when external power is lost
// - Monitor battery voltage and issue a shutdown/reboot signal when battery runs low
// This sketch may be extended to include integration with other LowPowerLab automation products
2014-08-14 18:58:25 +01:00
// *************************************************************************************************************
// License
// *************************************************************************************************************
// This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU General
// Public License as published by the Free Software
// Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General
// Public License along with this program; if not, write
// to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// Licence can be viewed at
// http://www.fsf.org/licenses/gpl.txt
//
// Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code
// *************************************************************************************************************
2018-11-07 16:30:00 +00:00
# include <LowPower.h> //get library from: https://github.com/lowpowerlab/lowpower
2014-08-14 18:58:25 +01:00
# define LED 5 // LED pin, should be analog for fading effect (PWM)
# define BUTTON 3 // Power button pin
2015-03-10 14:02:19 +00:00
# define SIG_SHUTOFF 6 // Signal to Pi to ask for a shutdown
# define SIG_BOOTOK A0 // Signal from Pi that it's OK to cutoff power
2014-11-08 23:57:51 +00:00
// !!NOTE!! Originally this was D7 but it was moved to A0 at least temporarily.
// On MightyBoost R1 you need to connect D7 and A0 with a jumper wire.
// The explanation for this is given here: http://lowpowerlab.com/mightyboost/#source
2014-08-14 18:58:25 +01:00
# define OUTPUT_5V 4 // HIGH on this pin will switch the "5V*" output ON
# define BATTERYSENSE A7 // Sense VBAT_COND signal (when powered externally should read ~3.25v/3.3v (1000-1023), when external power is cutoff it should start reading around 2.85v/3.3v * 1023 ~= 880 (ratio given by 10k+4.7K divider from VBAT_COND = 1.47 multiplier)
// hence the actual input voltage = analogRead(A7) * 0.00322 (3.3v/1024) * 1.47 (10k+4.7k voltage divider ratio)
// when plugged in this should be 4.80v, nothing to worry about
// when on battery power this should decrease from 4.15v (fully charged Lipoly) to 3.3v (discharged Lipoly)
// trigger a shutdown to the target device once voltage is around 3.4v to allow 30sec safe shutdown
2015-03-10 14:02:19 +00:00
# define LOWBATTERYTHRESHOLD 3.5 // a shutdown will be triggered to the target device when battery voltage drops below this (Volts)
2014-08-14 18:58:25 +01:00
2015-03-10 14:02:19 +00:00
# define RESETHOLDTIME 500 // Button must be hold this many mseconds before a reset is issued (should be much less than SHUTDOWNHOLDTIME)
# define SHUTDOWNHOLDTIME 2000 // Button must be hold this many mseconds before a shutdown sequence is started (should be much less than ForcedShutoffDelay)
# define ShutoffTriggerDelay 6000 // will start checking the SIG_BOOTOK line after this long
# define RecycleTime 50000 // window of time in which SIG_BOOTOK is expected to go HIGH
2014-08-14 18:58:25 +01:00
// should be at least 3000 more than Min
// if nothing happens after this window, if button is
// still pressed, force cutoff power, otherwise switch back to normal ON state
2015-03-10 14:02:19 +00:00
# define RESETPULSETIME 500 // When reset is issued, the SHUTOFF signal is held HIGH this many ms
# define ForcedShutoffDelay 7500 // when SIG_BOOTOK==0 (PI in unknown state): if button is held
// for this long, force shutdown (this should be less than RecycleTime)
# define ShutdownFinalDelay 4500 // after shutdown signal is received, delay for this long
2014-08-14 18:58:25 +01:00
// to allow all PI LEDs to stop activity (pulse LED faster)
2015-03-10 14:02:19 +00:00
# define PRINTPERIOD 10000
2014-08-14 18:58:25 +01:00
int lastValidReading = 1 ;
unsigned long lastValidReadingTime = 0 ;
2015-03-10 14:02:19 +00:00
unsigned long NOW = 0 ;
2014-08-14 18:58:25 +01:00
int PowerState = 0 ;
long lastPeriod = - 1 ;
float systemVoltage = 5 ;
void setup ( ) {
Serial . begin ( 115200 ) ;
pinMode ( BUTTON , INPUT_PULLUP ) ;
2015-03-10 14:02:19 +00:00
pinMode ( SIG_BOOTOK , INPUT ) ;
pinMode ( SIG_SHUTOFF , OUTPUT ) ;
2014-08-14 18:58:25 +01:00
pinMode ( LED , OUTPUT ) ;
pinMode ( OUTPUT_5V , OUTPUT ) ;
pinMode ( A7 , INPUT ) ;
2015-03-10 14:02:19 +00:00
digitalWrite ( SIG_SHUTOFF , LOW ) ; //added after sudden shutdown quirks, DO NOT REMOVE!
2014-08-14 18:58:25 +01:00
digitalWrite ( OUTPUT_5V , LOW ) ; //added after sudden shutdown quirks, DO NOT REMOVE!
}
void loop ( ) {
2018-11-07 16:30:00 +00:00
//reduce MCU ON current by ~10mA
LowPower . idle ( SLEEP_30MS , ADC_OFF , TIMER2_OFF , TIMER1_OFF , TIMER0_ON , SPI_OFF , USART0_OFF , TWI_OFF ) ;
2014-08-14 18:58:25 +01:00
int reading = digitalRead ( BUTTON ) ;
2015-03-10 14:02:19 +00:00
NOW = millis ( ) ;
digitalWrite ( SIG_SHUTOFF , LOW ) ; //added after sudden shutdown quirks, DO NOT REMOVE!
2014-08-14 18:58:25 +01:00
boolean batteryLow = systemVoltage < LOWBATTERYTHRESHOLD ;
2015-03-10 14:02:19 +00:00
if ( batteryLow | | reading ! = lastValidReading & & NOW - lastValidReadingTime > 200 )
{
2014-08-14 18:58:25 +01:00
lastValidReading = reading ;
2015-03-10 14:02:19 +00:00
lastValidReadingTime = NOW ;
2014-08-14 18:58:25 +01:00
if ( batteryLow | | reading = = 0 )
{
2015-03-10 14:02:19 +00:00
//make sure the button is held down for at least 'RESETHOLDTIME' before taking action (this is to avoid accidental button presses and consequently Pi shutdowns)
NOW = millis ( ) ;
while ( ! batteryLow & & ( PowerState = = 1 & & millis ( ) - NOW < RESETHOLDTIME ) ) { delay ( 10 ) ; if ( digitalRead ( BUTTON ) ! = 0 ) return ; }
//RESETHOLDTIME is satisfied, now check if button still held until SHUTDOWNHOLDTIME is satisfied
analogWrite ( LED , 128 ) ; //dim the LED to show something's going on
while ( ! batteryLow & & ( PowerState = = 1 & & millis ( ) - NOW < SHUTDOWNHOLDTIME ) )
{
if ( digitalRead ( BUTTON ) ! = 0 )
{
if ( BOOTOK ( ) ) //SIG_BOOTOK is HIGH so Pi is running the shutdowncheck.sh script, ready to intercept the RESET PULSE
{
digitalWrite ( SIG_SHUTOFF , HIGH ) ;
delay ( RESETPULSETIME ) ;
digitalWrite ( SIG_SHUTOFF , LOW ) ;
NOW = millis ( ) ;
boolean recycleDetected = false ;
while ( millis ( ) - NOW < RecycleTime ) //blink LED while waiting for BOOTOK to go high
{
//blink 3 times and pause
digitalWrite ( LED , LOW ) ;
delay ( 100 ) ;
digitalWrite ( LED , HIGH ) ;
delay ( 100 ) ;
digitalWrite ( LED , LOW ) ;
delay ( 100 ) ;
digitalWrite ( LED , HIGH ) ;
delay ( 100 ) ;
digitalWrite ( LED , LOW ) ;
delay ( 100 ) ;
digitalWrite ( LED , HIGH ) ;
delay ( 500 ) ;
if ( ! BOOTOK ( ) ) recycleDetected = true ;
else if ( BOOTOK ( ) & & recycleDetected )
return ;
}
return ; //reboot pulse sent but it appears a reboot failed; exit all checks
}
else return ; //ignore everything else (button was held for RESETHOLDTIME, but SIG_BOOTOK was LOW)
}
}
//SIG_BOOTOK must be HIGH when Pi is ON. During boot, this will take a while to happen (till it executes the "shutdowncheck" script)
2014-08-14 18:58:25 +01:00
//so I dont want to cutoff power before it had a chance to fully boot up
2015-03-10 14:02:19 +00:00
if ( batteryLow | | ( PowerState = = 1 & & BOOTOK ( ) ) )
2014-08-14 18:58:25 +01:00
{
// signal Pi to shutdown
2015-03-10 14:02:19 +00:00
digitalWrite ( SIG_SHUTOFF , HIGH ) ;
2014-08-14 18:58:25 +01:00
//now wait for the Pi to signal back
2015-03-10 14:02:19 +00:00
NOW = millis ( ) ;
2014-08-14 18:58:25 +01:00
float in , out ;
boolean forceShutdown = true ;
2015-03-10 14:02:19 +00:00
while ( millis ( ) - NOW < RecycleTime )
2014-08-14 18:58:25 +01:00
{
if ( in > 6.283 ) in = 0 ;
in + = .00628 ;
out = sin ( in ) * 127.5 + 127.5 ;
analogWrite ( LED , out ) ;
delayMicroseconds ( 1500 ) ;
2015-03-10 14:02:19 +00:00
//account for force-shutdown action (if button held for ForcedShutoffDelay, then force shutdown regardless)
if ( millis ( ) - NOW < = ( ForcedShutoffDelay - SHUTDOWNHOLDTIME ) & & digitalRead ( BUTTON ) ! = 0 )
2014-08-14 18:58:25 +01:00
forceShutdown = false ;
2015-03-10 14:02:19 +00:00
if ( millis ( ) - NOW > = ( ForcedShutoffDelay - SHUTDOWNHOLDTIME ) & & forceShutdown )
2014-08-14 18:58:25 +01:00
{
PowerState = 0 ;
digitalWrite ( LED , PowerState ) ; //turn off LED to indicate power is being cutoff
2015-03-10 14:02:19 +00:00
digitalWrite ( OUTPUT_5V , PowerState ) ;
2014-08-14 18:58:25 +01:00
break ;
}
2015-03-10 14:02:19 +00:00
if ( millis ( ) - NOW > ShutoffTriggerDelay )
2014-08-14 18:58:25 +01:00
{
// Pi signaling OK to turn off
2015-03-10 14:02:19 +00:00
if ( ! BOOTOK ( ) )
2014-08-14 18:58:25 +01:00
{
PowerState = 0 ;
digitalWrite ( LED , PowerState ) ; //turn off LED to indicate power is being cutoff
2015-03-10 14:02:19 +00:00
NOW = millis ( ) ;
while ( millis ( ) - NOW < ShutdownFinalDelay )
2014-08-14 18:58:25 +01:00
{
if ( in > 6.283 ) in = 0 ;
in + = .00628 ;
out = sin ( in ) * 127.5 + 127.5 ;
analogWrite ( LED , out ) ;
delayMicroseconds ( 300 ) ;
}
2015-03-10 14:02:19 +00:00
digitalWrite ( OUTPUT_5V , PowerState ) ;
2014-08-14 18:58:25 +01:00
break ;
}
}
}
// last chance: if power still on but button still pressed, force cutoff power
if ( PowerState = = 1 & & digitalRead ( BUTTON ) = = 0 )
{
PowerState = 0 ;
digitalWrite ( OUTPUT_5V , PowerState ) ;
}
2015-03-10 14:02:19 +00:00
digitalWrite ( SIG_SHUTOFF , LOW ) ;
2014-08-14 18:58:25 +01:00
}
2015-03-10 14:02:19 +00:00
else if ( PowerState = = 1 & & ! BOOTOK ( ) )
2014-08-14 18:58:25 +01:00
{
2015-03-10 14:02:19 +00:00
NOW = millis ( ) ;
unsigned long NOW2 = millis ( ) ;
int analogstep = 255 / ( ( ForcedShutoffDelay - SHUTDOWNHOLDTIME ) / 100 ) ; //every 500ms decrease LED intensity
2014-08-14 18:58:25 +01:00
while ( digitalRead ( BUTTON ) = = 0 )
{
2015-03-10 14:02:19 +00:00
if ( millis ( ) - NOW2 > 100 )
2014-08-14 18:58:25 +01:00
{
2015-03-10 14:02:19 +00:00
analogWrite ( LED , 255 - ( ( millis ( ) - NOW ) / 100 ) * analogstep ) ;
NOW2 = millis ( ) ;
2014-08-14 18:58:25 +01:00
}
2015-03-10 14:02:19 +00:00
if ( millis ( ) - NOW > ForcedShutoffDelay - SHUTDOWNHOLDTIME )
2014-08-14 18:58:25 +01:00
{
//TODO: add blinking here to signal final shutdown delay
PowerState = 0 ;
digitalWrite ( OUTPUT_5V , PowerState ) ;
break ;
}
}
}
else if ( PowerState = = 0 )
{
PowerState = 1 ;
digitalWrite ( OUTPUT_5V , PowerState ) ; //digitalWrite(LED, PowerState);
}
}
2015-03-10 14:02:19 +00:00
2014-08-14 18:58:25 +01:00
digitalWrite ( LED , PowerState ) ;
}
2015-03-10 14:02:19 +00:00
2014-08-14 18:58:25 +01:00
int currPeriod = millis ( ) / PRINTPERIOD ;
if ( currPeriod ! = lastPeriod )
{
lastPeriod = currPeriod ;
Serial . print ( " VIN: " ) ;
systemVoltage = analogRead ( BATTERYSENSE ) * 0.00322 * 1.47 ;
Serial . print ( systemVoltage ) ;
if ( systemVoltage > 4.3 )
Serial . println ( " (plugged in) " ) ;
else Serial . println ( " (running from battery!) " ) ;
}
2015-03-10 14:02:19 +00:00
}
boolean BOOTOK ( ) {
return analogRead ( SIG_BOOTOK ) > 800 ;
2014-11-08 23:57:51 +00:00
}