/* ; ************************************************ ; * Software Defined Edge Finder * ; * Version 2.0 * ; This work is licensed under the Creative * ; Commons Attribution 4.0 International License. * ; To view a copy of this license, visit * ; http://creativecommons.org/licenses/by/4.0/ * ; or send a letter to Creative Commons, * ; PO Box 1866, Mountain View, CA 94042, USA. * ; * by R.G. Sparber * ; ************************************************ ; ; ============================================ ; H A R D W A R E I N F O R M A T I O N ; ============================================ ; ; ATTiny85 runs at 1 MHz ; ; physical pin logical pin name use ; 1 PB5 NotReset, ADC0,PCINT5 ; 2 PB3 ADC3 negative analog input ; 3 PB4 ADC2 positive analog input ; 4 ground ; 5 PB0 PB0 opto positive drive ; 6 PB1 PB1 opto negative drive ; 7 PB2 PB2 power control output ; 8 power ; ============================================ ; P O R T S A N D L O G I C A L P I N N A M E S ; ============================================ */ const int PowerControlPin = 2; const int TouchdownPositive = 0; const int TouchdownNegative = 1; const int SkinnyPrintPin = 2; //logical port that will be used as output /* ; ============================================ ; C O N S T A N T S ; ============================================ */ const long OneSecondMS = 1540;//in milliseconds const long TouchdownWindowTimerMS = 10000; const long HalfSecondMS = 770; const long TenSecondsMS = 15400; const long OneMinuteMS = 92400; //note used const unsigned int MinimumSwing = 100; //Vx swing is 0.05 mV times this number /* The 10 bit ADC (0 - 1023) uses a 1.1V reference so one count is (1.1V/1023) = 1.1 mV. Then there is a X20 amp in front of it so one LSB reflected to the input is 1.1 mV/20 = 0.05 mV per count. The test current is 50 mA so one count represents (0.05 mV/0.05 amps) = 1.1 milliohms. 20 counts is 22 milliohms. That is enough to detect touchdown but noise margin is small. */ const unsigned int MaximumTouchdown = 800; //need room above Touchdown to see Free /* ; ============================================ ; V A R I A B L E S ; ============================================ */ unsigned int Reading = 0; //put in due to debugger unsigned int Touchdown = 0;//ADC output at touchdown unsigned int Free = 0; //ADC output when free from reference surface unsigned int Threshold = 0; //touchdown threshold unsigned long OneMinuteTimerStartTime = 0; boolean LastTD = false; boolean TouchdownWindowTimerRunning = false; boolean TouchdownWindowTimerDone = false; unsigned long TouchdownWindowTimerStartTime = 0; unsigned long LowBatteryTimer = 0; unsigned long StartOfMinuteMS = millis(); unsigned int sample = 0; /*___________________________________________debugger ; ============================================ ; C O N S T A N T S ; ============================================ */ unsigned int InterByteTimeMicroS = 250; //time from the transmission of the last bit of one byte to the header of the next byte unsigned int TimeUnitMicroS = 1500; //a TimeUnit is actually about 0.2 milliseconds long than this value unsigned long A_MinuteMS = 60000; //used for overall power down /* ; ============================================ ; V A R I A B L E S ; ============================================ */ unsigned int Distance = 0; byte DotCount = 0; //used by Heartbeat() byte ByteBeingSent = 0; byte Stack[12]; //holds 10 or fewer bytes waiting to be transmitted plus the overflow symbol of FF FF byte LocalTimeUnitCounter = 0; byte SkinnyTransmitterCounter = 0; //used to shift stack down byte StackPointer = 0; //next available location in stack byte GreenDipRate = 0; boolean StackOverflowQ = false; boolean SendingStreamQ = false; byte ReadCycle = 0; byte FlashCycle = 0;//used to flash green LED to signal reading's proximity to Threshold. unsigned long start = 0; unsigned long LastTransmission = millis();//used by Heartbeat() byte TimeUnitCounter = 0; unsigned long TimeLastBitSentMicroS = micros(); //used to tell when to start sending next byte unsigned long TimeTimeUnitSentMicroS = micros() - 2*TimeUnitMicroS; //used to tell when to send next TimeUnit. Initialized to insure immediate transmission unsigned long TimeAllOfByteSentMicroS = micros(); // used to tell when to send next byte. I set it so first byte is sent without delay. //___________________________________________debugger /* ; ============================================ ; S E T U P ; ============================================ */ void setup(){ //direct writes to hardware registers to set up analog and ports PORTB = 0b00000000; //turns off all pull up resistors DDRB = 0b00000111; //sets both inputs and outputs except diagnostic port ADCSRA = 0b10000011; //enable ADC but do not run it yet ADCSRB = 0; //this is the default. I want Unipolar input, no input polarity reversal, free running ADC although I am running it single. DIDR0 = 0b00011000; //turn off unneeded digital inputs on analog pins //first do battery test ADMUX = 0b10010010; // set internal reference to 2.56 volts (bit 7 = 1, bit 6 = 0, bit 4 = 1), analog inputs to single ended (ADC2) with gain of x 1 (MUX[3:0] = 0010 (bits 3, 2, and 0 = 0, bit 1 = 1). PowerUp(); //keep power up, turn on LED to tell user to remove bridge. Start timer that turns circuit off in 1 minute if no touchdown found if (ReadVoltage() < 629 ){ //when the batteries get to 1.1V each, call for replacements. //do not proceed with edge finder function. Alternately flash red/green at 0.5 second intervals for 10 seconds and then turn off power. If auto powerdown inhibited, keep warning user LowBattery: LowBatteryTimer = millis(); while((millis() - LowBatteryTimer)< 10000){ //flash LEDs for 10 seconds GreenOffOptoAndRedOn(); delay(500); GreenOnOptoAndRedOff(); delay(500); } //then kill power PowerDown(); //due to filter cap, ATTiny85 stays on for about 2 seconds after power is removed goto LowBattery;//if power down has been inhibited, continue to warn user and do not proceed } //set up for Edge Finder ADMUX = 0b10000111; //set internal reference to 1.1 volts, analog inputs to differential with gain of x 20. Prescaller at 8. //pinMode(SkinnyPrintPin,OUTPUT); //assignment for debugger delay(OneSecondMS);//keep LED on long enough to be seen Touchdown = ReadMinimumVoltage(); //read voltage assuming user has bridged gap. Record minimum voltage seen in NumberOfCycles samples. Machine is not moving so readings should be clean; Touchdown is an unsigned int so can hold up to 65,535 while the DAC can output up to 1023. if (TouchdownTooLargeQ() == true){ SignalStartOverAndDie();//Flash LED 5 times and power down unless inhibited by user. Then we just keep trying to get user to fix problem. We do not proceed. } //only get here if Touchdown is small enough SignalReady(); //Signal the user to remove the bridge. This is done by turning off the LED and also keeping opto off. delay(OneSecondMS); //give user time to react and remove bridge before starting to read ReadVoltageWhenOffOfSurface: Free = ReadMaximumVoltage(); //read voltage assuming user has removed bridge gap. Record maximum voltage seen. Machine is not moving so readings should be free of noise. if(LargeEnoughDifferenceQ() == true){ CalculateThreshold(); }else{ SignalRetry(); //flicker of LED tell user to turn spindle a little GiveUpSoPowerDownQ();//power down if running more than 10 seconds unless power down has been inhibited by the user. Then continue to flicker LED. goto ReadVoltageWhenOffOfSurface; //user will see continuous flickering until Free is large enough or until 10 seconds have passed } } /* ; ============================================ ; L 0 0 P ; ============================================ */ void loop(){//scan input and report touchdown to CNCPC via opto closure AutoPowerDownAfterOneMinute(); //don't run longer than one minute if (TouchdownQ() == true){ SignalTouchdown();//tell user plus if this is the first touchdown, set LastTD to true in order to start TouchdownWindowTimer. }else{ SignalNoTouchdown(); } NormalPowerDownQ();//power down if running more than TouchdownWindowTimerMS after first touchdown detected TouchdownWindowTimer(); //timer is used by NormalPowerDownQ() } /* ; ============================================ ; S U B R O U T I N E S ; ============================================ */ void AutoPowerDownAfterOneMinute(){ if (millis() - StartOfMinuteMS > A_MinuteMS) PowerDown(); //don't run longer than one minute } boolean TouchdownTooLargeQ(){ if (Touchdown > MaximumTouchdown){ return true; //touchdown too large to be able to detect Free }else{ return false; } } void SignalStartOverAndDie(){ TopOfStartOver: int FlashCount=0; while(FlashCount<10){ GreenOnOptoAndRedOff(); delay(100); GreenOffOptoAndRedOn(); //turn on opto to prevent probe crash. User must notice that we are not at touchdown yet CNC12 says we touched plus red led was flashing to indicate problem. If auto power down is overridden, CNC12 will keep getting touchdown signal. delay(100); ++FlashCount; } PowerDown(); //turns off power to entire circuit //if power down overridden, keep signaling to start over goto TopOfStartOver; } void PowerUp(){ OneMinuteTimerStartTime = millis();//start normal power down timer digitalWrite(PowerControlPin,HIGH);//keep power on until done GreenOnOptoAndRedOff(); //ack power is on } unsigned int ReadMinimumVoltage(){ unsigned int ReadCycle = 0; unsigned int MaxNumberOfReadings = 2000; unsigned int LastReading = 1023;//start at maximum possible value unsigned int CurrentReading = 0; while (ReadCycle (LastReading)){ LastReading = CurrentReading; //current reading is larger so save it }else{ return CurrentReading; //Current reading the same as last or smaller so we are stable or falling. } ++ReadCycle; } return CurrentReading; //after MaxReadings readings we are still rising so just return current reading. } unsigned int ReadVoltage(){ //takes an average over NumberOfCycles unsigned int sum = 0; unsigned int result = 0; byte NumberOfCycles = 10; ReadCycle = 0; while(ReadCycle Threshold returns false byte HitCounter = 0; while(HitCounter < 2){ ReadADC(); //it outputs sample if (sample < Threshold){ ++HitCounter; }else{ return false; //a single reading > Threshold says no touchdown } } return true; } void SignalTouchdown(){ GreenOffOptoAndRedOn(); //user sees LED go out and CNCPC detect touchdown delay(200); //wait 200 ms to insure PC sees touchdown if (LastTD == false) LastTD = true; //signal that we have had a touchdown } void SignalNoTouchdown(){ GreenOnOptoAndRedOff();//user sees LED on and CNCPC waiting for touchdown } void GiveUpSoPowerDownQ(){ //if we have waited long enough to reach touchdown but failed, turn off power if ((millis() - OneMinuteTimerStartTime) > TenSecondsMS){ PowerDown(); //turns off power to entire circuit return; //if auto power down overridden, return to reporting touchdown state }else{ return; } } void NormalPowerDownQ(){ //CNC is doing touchdown, back away, touchdown sequence which will be done within 10 seconds of first touchdown so power down if (TouchdownWindowTimerDone == true){ //power down since we have waited long enough for second touchdown PowerDown(); //turns off power to entire circuit return; //if auto power down is overridden, return to reporting touchdown state } } void PowerDown(){ //due to large filter capacitor, it does take time for the device to lose power digitalWrite(PowerControlPin,LOW); //turns off power to entire circuit unless inhibited by user return;//if auto power down inhibited by user, we will still be running so return } void TouchdownWindowTimer(){ /* Start the timer by setting LastTD flag true. Timer sets the TouchdownWindowTimerRunning flag true until ten seconds are up. Then it changes flag to false and sets the TouchdownWindowTimerDone flag true. */ if (TouchdownWindowTimerDone == true)return;//only get here if auto shut down has been inhibited by user if((LastTD == true) && (TouchdownWindowTimerRunning == false)){ //just had first touchdown so start timer if it is not running or done TouchdownWindowTimerStartTime = millis(); TouchdownWindowTimerRunning = true; TouchdownWindowTimerDone = false; return; } if ((TouchdownWindowTimerRunning == true) && ((millis() - TouchdownWindowTimerStartTime) > TouchdownWindowTimerMS)){ //time is up TouchdownWindowTimerRunning = false; TouchdownWindowTimerDone = true; return; } } void SignalRetry(){ //flash LED 20 times to tell user to turn spindle a little. It looks like a flicker. int i = 1; while(i<21){ GreenOnOptoAndRedOff();//will be LED on and opto off delay(2*i);//causes ramp up in on time AllOff(); delay(55 - 2*i); ++i; } } boolean LargeEnoughDifferenceQ(){ if (Free > (Touchdown + MinimumSwing)){ return true; }else{ return false; } } void CalculateThreshold(){ Threshold = (Free +(19*Touchdown))/20; //5% above Touchdown } void GreenOffOptoAndRedOn(){ digitalWrite(TouchdownPositive,LOW); digitalWrite(TouchdownNegative,HIGH); } void GreenOnOptoAndRedOff(){ digitalWrite(TouchdownPositive,HIGH); digitalWrite(TouchdownNegative,LOW); } void AllOff(){ digitalWrite(TouchdownPositive,HIGH); digitalWrite(TouchdownNegative,HIGH); } void ReadADC(){ //a dip in physical pin 7 signals start of ADC read; used to measure cycle rate of Vx sampling while looking for Touchdown unsigned int lower = 0; unsigned int upper = 0; ADCSRA = 0b11000011;//ADC was enabled in setup. start conversion NotDone: if((ADCSRA & 0b00010000) == 0b00000000)goto NotDone;//wait until the ADC Conversion is done. Bit ADIF goes to 1 when done. It is cleared on next start. //read ADCL first and then ADCH lower = ADCL; //read ADCL first and then ADCH upper = ADCH; sample = (lower | (upper<<8));//then put them together. return; } /* ; ============================================ ; D I A G N O S T I C S U B R O U T I N E S ; ============================================ */ void SkinnyPrintByte(byte data){ if (StackOverflowQ == true)return; if (StackPointer > (sizeof(Stack) - 1)){ StackOverflowQ = true; return; } Stack[StackPointer] = data; //populate next available stack location ++StackPointer; //point to the new free stack location above the data just added } void SkinnyPrinter(){ Heartbeat(); MonitorStack(); SendFromStack(); SendStream(); } void Heartbeat(){ if(NoTransmissionsInTenSecondsQ == true){ SkinnyPrintByte(0xFE); } //a transmission is going out so no need for heartbeat } boolean NoTransmissionsInTenSecondsQ(){ if (TimeUnitCounter == 0){ //so no transmission now if((millis()-LastTransmission)< 10000){ return false; }else{ LastTransmission = millis(); return true; } } //otherwise, a transmission is going out now return false; } void MonitorStack(){ if (StackOverflowQ == true){ //flush stack and transmit 255 twice in a row StackPointer = 2; Stack[0]=0xFF; Stack[1] = 0xFF; Stack[2] = 0; //make backfill of stack 0 rather than random data StackOverflowQ = false; } } void SendFromStack(){ if (SendingStreamQ == true)return; if(StackPointer == 0)return; //no byte waiting to be sent if ((micros() - TimeAllOfByteSentMicroS) < InterByteTimeMicroS)return; PrepareToSendStream(Stack[0]); SkinnyTransmitterCounter = 0; //shift down stack and adjust StackPointer while(SkinnyTransmitterCounter < StackPointer){ Stack[SkinnyTransmitterCounter] = Stack[SkinnyTransmitterCounter+1]; ++SkinnyTransmitterCounter; } --StackPointer; return; } void PrepareToSendStream(byte data){ SendingStreamQ = true;//we are now Sending a Stream ByteBeingSent = data; //load supplied byte as the one about to be sent } void SendStream(){ if (SendingStreamQ == false)return; if ((micros() - TimeTimeUnitSentMicroS) < TimeUnitMicroS)return; if (TimeUnitCounter <7){ //header is 0 through 6 SendHeader(); }else{ //TimeUnitCounter should be 7 through 30 SendBits();//when done sending 8 bits it returns TimeUnitCounter to 0 and SendingStreamQ to false } if(SendingStreamQ == true){ //if sending stream, advance TimeUnitCounter but if done, do not advance it TimeTimeUnitSentMicroS = micros();//prepare to send next TimeUnit ++TimeUnitCounter;//prepare to send next TimeUnit } } void SendHeader(){ switch(TimeUnitCounter){ case 0: digitalWrite(SkinnyPrintPin,LOW); break; case 1: digitalWrite(SkinnyPrintPin,HIGH); break; case 2: digitalWrite(SkinnyPrintPin,LOW); break; case 3: digitalWrite(SkinnyPrintPin,HIGH); break; case 4: digitalWrite(SkinnyPrintPin,HIGH); break; case 5: digitalWrite(SkinnyPrintPin,HIGH); break; case 6: digitalWrite(SkinnyPrintPin,LOW); break; } } void SendBits(){ LocalTimeUnitCounter = (TimeUnitCounter - 7)%3; //this is the relative TimeUnit position within a bit if((1 & ByteBeingSent) == 0){ //this will extract the current LSB and if true we must send a logic 0 switch(LocalTimeUnitCounter){ case 0: digitalWrite(SkinnyPrintPin,HIGH); break; case 1: digitalWrite(SkinnyPrintPin,LOW); break; case 2: digitalWrite(SkinnyPrintPin,LOW); break; } }else{ //the LSB is a logic 1 switch(LocalTimeUnitCounter){ case 0: digitalWrite(SkinnyPrintPin,HIGH); break; case 1: digitalWrite(SkinnyPrintPin,HIGH); break; case 2: digitalWrite(SkinnyPrintPin,LOW); break; } } if (LocalTimeUnitCounter == 2)ByteBeingSent = ByteBeingSent>>1; //shift bits to the right one place so bit to the left of the previous LSB becomes the LSB if (TimeUnitCounter >29){ //should be 30 after last bit sent TimeUnitCounter=0;//prepare for next transmission SendingStreamQ = false; TimeAllOfByteSentMicroS = micros();//time stamp end of transmission } } void SkinnyPrintInteger(int data){ //debugger SkinnyPrintByte(highByte(data)); SkinnyPrintByte(lowByte(data)); } void Diagnostic(){ GreenOffOptoAndRedOn(); delay(50); GreenOnOptoAndRedOff(); delay(50); AllOff(); }