/* xDSL Modem Saver Thanks to The Wandering Engineer for hooking me up with PCINT's on the attiny's http://thewanderingengineer.com/2014/08/11/pin-change-interrupts-on-attiny85/ pinout attiny84: 01 + Vcc Vcc 02 D0 Motor 0 m[0-3] 03 D1 Motor 1 m[0-3] 04 R reset n/a reset 05 D2 Motor 2 m[0-3] 06 D3 Motor 3 m[0-3] 07 D4 Indicator LED LED 08 D5 Main Power Relay /NO MainPowerRelay 09 D6 Battery Charge Relay /NO BatteryChargeRelay 10 D7 Battery disconnect Relay /NC BatteryDisconnectRelay 11 A2 Battery DC Coltage Sense BatterySense 12 A1 Mains DC Voltage Sense MainsSense 13 D10 Strike detect and home switch trigger on Interrupt. StrikeSense 14 - Gnd gnd Operating modes almost a statemachine: sort states so all strike detect modes are above or below somthing 0 boot | POST check, go runstate 20 when mains is ok and connected 1 normal | monitor voltages, check for strike detects, add mAh as time goes by set a timer for mAh on a threshold, strike detect goto runstate 10 2 charge + normal ?? | return to mode 1 when done charging, do not add mAh when charging 10 Strike detect or mains fail | connect battery, disconnect mains, stop charging, home dolly routine, reconnect mains and charge if battery drops, goto runstate 100 20 Mains check and reconnect | connect mains, verify mains, if mains go low goto runstate 99, connect line, when connect done goto runstate 1 99 wait for good mains | connect mains every now and then, still listen for strikes, when good goto 20 100 Wait for 1 hour after strike detect | disconnect mains, wait 1 hour, monitor battery voltage connect mains if needed and charge for 30 sec intervals, strikecount, goto runstate 20 //stuff to monitor in current state: 0 check for good mains 1 strikes or battery low or battery high or mains high or mains low or charge intervals 2 strikes, battery low, battery high, mains high, mains low, charge timeout, 10 check for dolly homed goto 100 20 wait for mains, wait for dollyconnect, then go 1 99 wait for good mains, start dollyhome, goto 20 100 wait for an hour goto 99 stat table: stateID Wait for Action Next state 0 good mains 99 1 fubar power or strikes --- ---use a state change routine */ #include "avr/interrupt.h" //pinout static const int Motor[] = {0,1,2,3}; // output digital motor pins static const int LED = 4; // output digital LED pin static const int MainPowerRelay = 5; // output digital MainPowerRelay pin LOW=Open static const int BatteryChargeRelay = 6; // output digital BatteryChargeRelay pin LOW=Open static const int BatteryDisconnectRelay = 7; // output digital BatteryDisconnectRelay pin LOW=Close (battery connected) static const int BatterySense = A1; // input analog BatterySense pin 12 static const int MainsSense = A2; // input analog MainsSense pin 11 static const int StrikeSense = 10; // input digital pin change interrupt StrikeSense pin //other static values static const int mainhigh = 11000; // main high cutoff voltage mV static const int mainlow = 7000; // main low cutoff voltage mV static const int batthigh = 6200; // batt charge cutoff voltage static const int battlow = 5000; // batt start charge threshold static const int Mains = 0; static const int Battery = 1; static const int Vcc = 2; //main,batt,vcc static const int sHigh[] = {12000,6200,5500}; //sHigh[Mains] static const int sLow[] = {5000, 4000,2800}; //correct main sense low after debug static const float sMultiplier[] = {4124,2855,1000}; //needs calculation //static const int battchargeinterval=3600; static const int battchargemAh=83; //battery chargerate static const int battdischargemAh=110; //dischargerate when disconnected int badbackupbattery=false; //bad battery flag static const int disconnecttime = 2; // 60 time in minutes to keep line off after strike detect //correct this to about one hour int reconnecttimer=0; // time in minutes used for reconnect timer static const int connectsteps = 5000; // steps after home to connect line static const int homesteps = 300; // steps from switch home detect to home posistion static const int hometries = 3; // times to home before satisfied set higher?? //normal variables int runstate = 0; // runstate, only count strikes in certain runstates, (1, 2, 8, 20) strike detects -> runstate 8 int oldrunstate = 254; // off value to force runstate change on boot int currentstep = 0; // one of the current 8 steps int stepdir = true; // step direction volatile int steps = 0; // steps remaining volatile int hometimes = 0; // times to home dolly int loopcount; // reduce some routines from being executed every loop; //power check timers int cM0; int pM0; int i0=5; //clock used for battery charging int cM1; int pM1; int i1=60000; int pMinutes; int pHours; long pDays; //int pWeeks; //int pMonths; //int pYears; //voltage monitoring variables int vbatt; // average value battery mV int vmains; // average value mains rectified mV int vVcc; // average value Vcc mV //stepper motor statics and variables //motor geometry static const int stepsrevolution = 400; // steps/revolution using halfsteps static const int stepdelaymicros = 1400; // microseconds to wait after each step, minimum 1500, 2000 should work ok for slow static const int C0[] = {1,1,0,0,0,0,0,1}; // coil 0 halfstep sequence static const int C1[] = {0,1,1,1,0,0,0,0}; // coil 1 halfstep sequence static const int C2[] = {0,0,0,1,1,1,0,0}; // coil 2 halfstep sequence static const int C3[] = {0,0,0,0,0,1,1,1}; // coil 3 halfstep sequence //variables used in ISR volatile int strikecount = 0; // strikecount volatile int strikedetected = false; // strikedetected flag volatile int dollyhomesense = false; // if true, any pin 13 detect is counted as dolly home microswitch. volatile int homeswitchtriggered = false; // indicates home switch is trigggered //volatile int reconnecttimer = 0; // last strike countdown variable long pSoC; // some kind of state of charge ish thingy void setup() { for(int i=0; i<=3; i++) //motor outputs set { pinMode(Motor[i], OUTPUT); // Output all motor pins digitalWrite(Motor[i],LOW); // Turn off motor inverted, high is coil on } pinMode(LED, OUTPUT); // LED - Light Emitting Diode digitalWrite(LED,HIGH); // LED on for boot pinMode(MainPowerRelay, OUTPUT); // NO - Normally Open //digitalWrite(MainPowerRelay,HIGH); // We want power on during post. // state changes will take care of this pinMode(BatteryChargeRelay, OUTPUT); // NO - Normally Open //digitalWrite(BatteryChargeRelay,LOW); // No charging during boot // state changes will take care of this pinMode(BatteryDisconnectRelay, OUTPUT); // NC - Normally Closed //digitalWrite(BatteryDisconnectRelay,LOW); // Battery connected, relay is NC // state changes will take care of this pinMode(BatterySense, INPUT); // battery voltage sense pinMode(MainsSense, INPUT); // main dc voltage sense // use internal attiny84 circuitry to sense Vcc pinMode(StrikeSense,INPUT); // this is the PCINT0 pin @ portA/PCINT0, see http://42bots.com/tutorials/programming-attiny84-attiny44-with-arduino-uno/ // and https://c2.staticflickr.com/8/7054/6897568559_599ecce4d4.jpg digitalWrite(StrikeSense,HIGH); // pullup strikesense GIMSK = 0b00010000; // turns on pin change interrupts on PCMSK0 PCMSK0 = 0b00000001; // turn on interrupts on pins PCINT7:0 ->> PA0 -> pin 13 sei(); // enables interrupts } void newstate(int newstate) { runstate=newstate; oldrunstate=newstate; switch (runstate) { case 0: setrelay(MainPowerRelay,HIGH); setrelay(BatteryChargeRelay,LOW); setrelay(BatteryDisconnectRelay,LOW); //delay(1000); // let caps charge break; case 1: setrelay(MainPowerRelay,HIGH); setrelay(BatteryDisconnectRelay,HIGH); setrelay(BatteryChargeRelay,LOW); //dollyhomesense=false; break; case 2: setrelay(MainPowerRelay,HIGH); setrelay(BatteryChargeRelay,HIGH); setrelay(BatteryDisconnectRelay,HIGH); break; case 10: setrelay(BatteryDisconnectRelay,LOW); delay(200); setrelay(MainPowerRelay,LOW); setrelay(BatteryChargeRelay,LOW); //strikedetected=false; delay(800); //wait for strikedetector to settle //reconnecttimer=disconnecttime; //steps=5500; //stepdir=false; hometimes=hometries; break; case 20: setrelay(MainPowerRelay,HIGH); //hometimes=3; break; case 99: setrelay(MainPowerRelay,HIGH); setrelay(BatteryChargeRelay,LOW); break; case 100: setrelay(MainPowerRelay,LOW); setrelay(BatteryChargeRelay,LOW); break; default: runstate = 0; } } void loop() { //loopcount++; //if (loopcount>100) {loopcount=0;} //probably retering this if (runstate != oldrunstate) { //monitor for changes in runstate newstate(runstate); oldrunstate=runstate; } switch (runstate) { //if conditions are met, change to new state case 0: if (sLow[Mains] < vmains && vmains < sHigh[Mains] && sLow[Battery] < vbatt && vbatt < sHigh[Battery] ) { //if mains and battery == OK runstate=99; newstate(runstate); } break; case 1: if (vmains < sLow[Mains] || vmains > sHigh[Mains]) { //connect battery if mains die runstate=10; newstate(runstate); } //add battery low voltage detection, and bad batterybank detection, warn to LED // load test batterys every few weeks or so? do some season detection math based on recent disconnects? break; case 2: if (vmains < sLow[Mains] || vmains > sHigh[Mains]) { runstate=10; newstate(runstate); } //also look for 20mv dip? if (vbatt > sHigh[Battery]) { //if batt high return to runstate 1 runstate=1; // newstate(runstate); } //add battery charge control here... too break; case 10: // disconnect if (vbatt < sLow[Battery] || vVcc < sLow[Vcc]) { //reconnect mains if batteries or Vcc are low setrelay(MainPowerRelay,HIGH); //mark batterybank as bad! if (vbatt < sLow[Battery]) { badbackupbattery=true; } } if ( steps == 0 && hometimes==0 ) { //if dolly homed ok runstate=100; newstate(runstate); } break; case 20: //reconnecting if (sLow[Mains] < vmains && vmains < sHigh[Mains] && steps == 0 && hometimes == 0 ) { //if mains == OK && dolly homed ok delay(200); stepdir=true; steps=connectsteps; runstate=1; newstate(runstate); } break; case 99: // waitfor mains and dollyhome //if (sLow[Mains] < vmains && vmains < sHigh[Mains] && steps == 0 && hometimes == 0) //if mains == OK if (sLow[Mains] < vmains && vmains < sHigh[Mains] && steps == 0 && hometimes == 0 && badbackupbattery==false) { //if mains == OK hometimes=1; runstate=20; newstate(runstate); } break; case 100: if (vbatt < sLow[Battery] || vVcc < sLow[Vcc]) { //reconnect mains if batteries or Vcc are low setrelay(MainPowerRelay,HIGH); setrelay(BatteryChargeRelay,HIGH); if (vbatt < sLow[Battery]) { badbackupbattery=true; } } if (reconnecttimer < 1) { runstate=99; newstate(runstate); } //sleep here? // add stuff for reconnecttime // need to do more testing with sleep and interrupts //test code //delay(15000); //runstate=99; //newstate(runstate); //test end break; } //homing if (hometimes > 0) { if (homeswitchtriggered==true && stepdir==false) { // if homeswitch is hit hometimes--; steps=homesteps; stepdir=true; //dollyhomesense=false; delay(250); } if (stepdir==true && steps < 1) { //if home position hit and there is more homing to do steps=(homesteps+connectsteps)*1.1; stepdir=false; dollyhomesense=true; homeswitchtriggered=false; delay(100); } } if (steps > 0) { //if dolly has pending steps dostep(); } // Get snapshot of time unsigned long cM0 = millis(); // How much time has passed, accounting for rollover with subtraction! if ((unsigned long)(cM0 - pM0) >= i0) { // check power vmains = (vmains + VoltageSense(Mains)) / 2; vbatt = (vbatt + VoltageSense(Battery)) / 2; vVcc = (vVcc + VoltageSense(Vcc)) / 2; // Use the snapshot to set track time until next event pM0 = cM0; } //battery maintenanceclock and disconnecttime downcounter // Get snapshot of time unsigned long cM1 = millis(); // How much time has passed, accounting for rollover with subtraction! if ((unsigned long)(cM1 - pM1) >= i1) { //runs once every minute // It's time to do something! if (reconnecttimer > 0) { // reconnect timer countdown reconnecttimer--; } if (vmains < sLow[Mains]) { //add mAh to the chargepool if main power is disconnected pSoC=pSoC+(battdischargemAh/60); //add 110mAh for one minutes if power is off } if (runstate == 2) { pSoC=pSoC-(battchargemAh/60); //remove 83mAh/60 from chargepool every minute } if (runstate == 2 && pSoC <= 0) { runstate=1; newstate(runstate); } // Use the snapshot to set track time until next event pM1 = cM1; pMinutes++; if (pMinutes >= 60) { //one hour have passed pMinutes=0; pHours++; if ( runstate == 1 && pSoC > 23) { // charge mode if battery needs charging, runstate=2; newstate(runstate); } } if (pHours >= 24) { //one day have passed pHours=0; pDays++; //add mAh to the chargepool pSoC=pSoC+6; //mAh/day discharge value } } //strike detected if (strikedetected==true) { // all states listen for strikes! reconnecttimer=disconnecttime; strikedetected=false; if (runstate != 100) { //All but runstate 100 changes state runstate=10; newstate(runstate); }else { //if runstate 100 make sure mains is disconnected setrelay(MainPowerRelay,LOW); setrelay(BatteryChargeRelay,LOW); } } } void setrelay(int relay, int state) { digitalWrite(relay, state); } int VoltageSense(int sense){ //sample measurements int sample, mV; switch (sense) { case 0: sample = analogRead(MainsSense); break; case 1: sample = analogRead(BatterySense); break; case 2: mV = readVcc(); //Vcc in milliVolts break; } if (sense<=1) { mV = sample * (5.0 / 1023.0) * sMultiplier[sense]; } return mV; } void dostep() { delayMicroseconds(stepdelaymicros); if (steps>0) { if (stepdir==true) { currentstep++; if (currentstep > 7){currentstep = 0;} setrelay(Motor[0],C0[currentstep]); setrelay(Motor[1],C1[currentstep]); setrelay(Motor[2],C2[currentstep]); setrelay(Motor[3],C3[currentstep]); } else { currentstep--; if (currentstep < 0){currentstep = 7;} setrelay(Motor[0],C0[currentstep]); setrelay(Motor[1],C1[currentstep]); setrelay(Motor[2],C2[currentstep]); setrelay(Motor[3],C3[currentstep]); } steps--; } if (steps<1) { delay(50); dollyhomesense=false; homeswitchtriggered=false; setrelay(Motor[0],LOW); setrelay(Motor[1],LOW); setrelay(Motor[2],LOW); setrelay(Motor[3],LOW); } } long readVcc() { // Read 1.1V reference against AVcc // set the reference to Vcc and the measurement to the internal 1.1V reference #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) ADMUX = _BV(MUX5) | _BV(MUX0); #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) ADMUX = _BV(MUX3) | _BV(MUX2); #else ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); #endif delay(2); // Wait for Vref to settle ADCSRA |= _BV(ADSC); // Start conversion while (bit_is_set(ADCSRA,ADSC)); // measuring uint8_t low = ADCL; // must read ADCL first - it then locks ADCH uint8_t high = ADCH; // unlocks both long result = (high<<8) | low; result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000 return result; // Vcc in millivolts } ISR(PCINT0_vect) //interrupt handler { if (dollyhomesense==false){ strikedetected=true; strikecount++; } else{ homeswitchtriggered=true; } } /* void dollymove() { //steps need to be done if (stepdir==false && homeswitchtriggered==false) { //move if homeswitch is not triggered dostep(); } if (stepdir==true) { //move towards connection dostep(); } }*/ /*void dollyhome() { stepdir=false; steps=homesteps+connectsteps*1.1; if (digitalRead(StrikeSense)==HIGH) { homeswitchtriggered==false; } else { stepdir=true; steps=homesteps; } } */ /* //TEST AREA if (steps==0) { steps=5000; delay(1000); dollyhomesense=false; stepdir=!stepdir; if (stepdir==false){ //should be towards home dollyhomesense=true; steps=10000; homeswitchtriggered=false; } else { dollyhomesense=false; } } else { //steps need to be done if (stepdir==false && homeswitchtriggered==false) { dollyhomesense=true; dostep(); } if (stepdir==true) { dostep(); } if (stepdir==false && homeswitchtriggered==true) { stepdir=!stepdir; steps=300; delay(500); dollyhomesense=false; } } // END TEST AREA */ /* //do this less often? if (loopcount=33) { vmains = (vmains + VoltageSense(Mains)) / 2; vbatt = (vbatt + VoltageSense(Battery)) / 2; vVcc = (vVcc + VoltageSense(Vcc)) / 2; } */