/* SW-COMPUTER Factory Control Program VERSION 002.2 December 8,2007 Terry Fritz WWW.EXTREME-FIRE.COM Target controller = ATTINY85-20PU microcontroller from ATMEL. Clock speed = 8 MHz. Development platform = AVR Studio 4 Version 4.13 Service Pack 1 Build 557. C compiler = WinAVR 20070525 Programer = AVR Dragon Hardware Schematic = SW-COMPUTER 2.2 and earlier noting port changes. Hardware Layout = SW-COMPUTER 2.2 and earlier noting port changes. This program is fully released to the Public Domain by Terry Fritz 2007. History 001 - Initial release Nov. 23, 2007 Terry Fritz 002 - Next generation Nov. 25, 2007 Terry Fritz 002.2 - Near final release. Dec. 10, 2007 9:17PM Known issues: To use pin 1 (AUX) the Reset Disable Fuse has to be set and the Debug Wire Enable Fuse has to be clear. ( RSTDISBL = 1 DWEN = 0 ) They use pin 1 by defalt and will keep the pin high if not fixed. Future changes: None */ /* Include libraries */ #include /* For all the odd ATTINY stuff */ /* Define ATTINY85 ports (PBx) to the gun controls */ /* Note that these port numbers or NOT the physical pin numbers! */ #define Drain 2 /* FET drain voltage port */ #define Aux0 3 /* AUX 0 I/O port */ #define Trigger 4 /* Trigger voltage port */ #define Aux1 5 /* AUX 0 I/O port */ #define Ground 0 /* Internal A/D ground used for calibration */ /* Variable definitions */ #define VersionNumber 0x22 /* Software version number programed to EEPROM */ #define HighBatteryVoltage 16000 /* In mV */ #define LowBatteryVoltage 7000 /* In mV */ #define TriggerFiringVoltagePercent 80 /* Perent */ #define BatteryVoltageDroopWarningPercent 85 /* Percent */ #define TriggerHoldVoltage 5200 /* In mV */ /* Must be 4500 or greater to drive main fet to 200A */ #define DrainCurrentPeakLimit 200 /* In Amps */ #define RDSon 2870 /* In uOhms */ #define BreakTime 100 /* mS */ #define VibrateFrequency 40 /* Hz */ #define Normal 1 /* These are GunModes made easy to read */ #define ThreeRound 2 #define ThreeRoundAuto 2 /* Function prototypes */ void MotorOFF(void); /* Turns the motor off */ void MotorON(void); /* Turns the motor on */ void BreakOFF(void); /* Turns the break off */ void BreakON(void); /* Turns the break on */ void EnergizeFETs(void); /* Enables the FETs in a controled way */ void Timer(long Time); /* A mS timer Timer(123) = 123mS */ void FastTimer(long Time); /* An unclaibrated ~15uS fast timer */ long GetVoltage(long Sensor,long ADCRange); /* Gets the Voltage (in mV) from the chosen source (Drain, Trigger, Aux) */ void Vibrate(void); /* Vibrates the gun's motor for signalling the user */ void CalibrateAtoD(void); /* Calibrates A/D convertor */ void EEPROM_write(unsigned char ucAdress, unsigned char ucData); /* Write to EEPROM */ unsigned char EEPROM_read(unsigned char ucAddress); /* Read from EEPROM */ void ProgramEEPROM(void); /* User programming of the EEPROM */ void ReadEEPROM(void); /* Get stored EEPROM data */ void SetBurstTime(long ShotCounter); /* Set burst time function */ void MotorPWM(char PWMduty); /* Motor PWM function */ void SetAUXPorts(void); /* Setup AUX ports */ /* Initialize variables */ /* long is a 4 byte integer */ long BatteryVoltageDroopWarning = 5500;/* mV */ long TriggerFiringVoltage = 6000; /* mV */ long InitialBatteryVoltage = 7000; /* mV */ long BurstTimeFactor = 85; /* The single to three round burst ratio DEFAULT if EEPROM = FF*/ /* 100 = 2.00 X the single shot time. (2X Percent) */ char MotorSpeed = 100; /* 0 to 255 PWM value */ long Mx = 116; /* Burst fire slope in 1/100ths */ long Bx = 0; /* Burst fire offest */ long VibrateTime = 500; /* Time the vibrator is on in mS*/ long TimeFactor = 100; /* Adjust for 1mS step. Magically, the callibration factor = 100 */ long BurstTime = 20; /* Motor on time for a burst */ long TimeFactorFast = 1; /* A sub mS timer counter */ long ErrorCode = 0; /* The Errors are each given a unique number */ long TriggerVoltage = 0; /* The voltage on the trigger input in mV */ long DrainCurrent = 0; /* The current through the drive FET in Amps */ long DrainVoltage = 0; /* The voltage on the MOSFET drains mV */ long counter2 = 0; /* Counter for Vibrate function */ long Time = 0; /* Variable for Timer function */ long counter = 0; /* Counter for Timer function */ long Sensor = 0; /* Input pin number for ADC */ long Voltage = 0; /* A/D convertor returned voltage in mV */ long count = 0; /* local counter */ long VoltageOffset0 = 0; /* A/D 5.0V REF offest error */ long VoltageOffset1 = 0; /* A/D 2.56V REF offest error */ long MotorRunningLoopCounter = 0; /* Counts loops while firing */ long GunMode = Normal; /* 1=trigger control (Normal) 2=burst (ThreeRoundAuto) */ long ShotCounter = 0; /* Shot count inside a burst */ long TriggerHold = 0; /* Hold further shotting until trigger is released */ long ModeCounter = 0; /* A main loop counter for the threeround auto fuction */ long GunFunction = Normal; /* Operating mode DEFAULT if EEPROM = FF */ /* 1 = Normal 2 = ThreeRound */ unsigned char ucAddress = 0x00; /* For EEPROM functions */ unsigned char ucData = 0x00; /* For EEPROM functions */ long ADCRange = 0; /* 0 = 5.0V REF 1 = 2.56V REF */ long count1 = 0; /* Error loop counter */ long counter3 = 0; /* Timer counter */ long counter4 = 0; /* FastTimer counter */ long SBTCounter = 0; /* Burst time function counter */ long SingleShotTime = 20; /* Loops for single shot */ char PWMduty = 0x00; /* Motor PWM control variable */ long counter5 = 0; /* Master Reset time counter */ long GunFunctionFlag = 0; /* Counter used in mode programing */ int main(void) { /* MAIN POWER UP */ /* Wait 2 seconds for hardware to stabilize */ Timer(2000); ErrorCode = 1; GunMode = Normal; /* Setup A/D parameters */ CalibrateAtoD(); /* Check trigger voltage */ if (GetVoltage(Trigger,0) > 500) {ErrorCode = 4;} /* Get EEPROM data */ ReadEEPROM(); /* Energize drive circuits */ EnergizeFETs(); /* Setup AUX ports */ SetAUXPorts(); /* Check drain voltage */ DrainVoltage = (GetVoltage(Drain,0)); if (DrainVoltage < LowBatteryVoltage) {ErrorCode = 2;} if (DrainVoltage > HighBatteryVoltage) {ErrorCode = 3;} /* Set Battery Limits */ InitialBatteryVoltage = DrainVoltage; TriggerFiringVoltage = (InitialBatteryVoltage * TriggerFiringVoltagePercent) / 100; BatteryVoltageDroopWarning = (InitialBatteryVoltage * BatteryVoltageDroopWarningPercent) / 100; /* Vibrate status to motor */ for (count1 = 0; count1 < ErrorCode; count1++) { Timer(500); Vibrate(); } /* if the battery voltage is not safe, force a shutdown */ Halt: if (ErrorCode > 1) {goto Halt;} /* Error Codes 1 Normal operation 2 Battery voltage too low 3 Battery voltage too high 4 Trigger voltage too high 16 Low battery 17 High motor peak current */ /* Wait for trigger pull to enter programming mode (2 seconds) */ for (count=0; count < 729; count++) { if (GetVoltage(Trigger,0) > TriggerFiringVoltage) {ProgramEEPROM();} } /* MAIN SCANNING LOOP */ Vibrate();Vibrate(); MainLoop: ModeCounter++; /* Set motor to off no matter what is going on */ MotorOFF(); /* Check trigger voltage */ TriggerVoltage = GetVoltage(Trigger,0); if ((TriggerVoltage > TriggerFiringVoltage) && (TriggerHold == 0)) { /* Check trigger voltage incase the battery is dying */ if (TriggerVoltage < BatteryVoltageDroopWarning) {ErrorCode = 16;} goto Fire; } if (TriggerVoltage < 500) {TriggerHold = 0; GunMode = Normal; ModeCounter = 0;} /* Goto full auto in three round burst mode if trigger is held down 1/2 second longer */ if ((ModeCounter > 156) && (TriggerHold == 1)) {GunMode = ThreeRoundAuto; goto Fire;} /* Return to top and loop again */ goto MainLoop; /* FIRING LOOP */ Fire: /* MOTOR IS ON!!!!! */ MotorON(); Timer(1); /* A delay to avoid the motor start up electrical noise burst */ ShotCounter = 0; MotorRunningLoopCounter++; /* increment loop counter */ ShotDetect: /* 2.22mS per loop. */ //PORTB = 0x03; //PORTB = 0x21; ShotCounter++; TriggerVoltage = GetVoltage(Trigger,1); /* Get trigger voltage */ if ((TriggerVoltage < TriggerHoldVoltage)) /* Is trigger off now?*/ { if ((GunFunction == Normal) || (GunMode == ThreeRoundAuto)) {GunMode = Normal; goto CeaseFire;} /* normally just CeaseFire */ if (TriggerHold == 0) {goto CeaseFire;} } if ((GunMode == ThreeRoundAuto) || (GunFunction == Normal)) {goto ShotDetect;} /* Keep cycling in auto modes */ if (ShotCounter > BurstTime) {TriggerHold = 1; goto CeaseFire;} /* Three round time delay wait */ goto ShotDetect; /* FIRING HALT */ CeaseFire: /* The motor ON and OFF functions have a 1mS anti cross conduction safety delay built in */ /* Turn motor to off */ MotorOFF(); /* Turn break to on */ BreakON(); /* Leave the break on for some time in mS */ Timer(BreakTime); /* Turn break to off */ BreakOFF(); if (TriggerHold ==0) {SetBurstTime(ShotCounter);} ModeCounter = 0; /* If an error occured report it */ if (ErrorCode == 0) {goto MainLoop;} /* 16 - Low battery - Vibrate motor once after firing 17 - High motor peak current - Vibrate motor twice */ Timer(100); if (ErrorCode == 16) {Vibrate();} if (ErrorCode == 17) {Vibrate(); Timer(500); Vibrate();} ErrorCode = 0; goto MainLoop; } /* End of main */ /* FUNCTIONS */ /* For safety in that "nothing can go wrong", the motor drive ports are controlled directly */ void MotorOFF(void) { MotorPWM(0); } void MotorON(void) { PORTB = 0x01; /* Be REALLY(!!) sure break is off */ Timer(1); /* Wait 1mS for anti-cross conduction */ PORTB = 0x03; /* motor is on for peak current test */ Timer(1); /* Wait to stabalize */ /* Check drain current for over peak current limit */ DrainCurrent = GetVoltage(Drain,1); PORTB = 0x01; /* motor is off after test */ if ((DrainCurrent * 1000 / RDSon) > DrainCurrentPeakLimit) {PORTB = 0x01; ErrorCode = 17; return;} /* Shutdown if overcurrent */ MotorPWM(MotorSpeed); } void BreakOFF(void) {PORTB = 0x01;} /* The break signal is negative logic */ void BreakON(void) { PORTB = 0x01; /* Be REALLY(!!) sure motor is off */ Timer(1); /* Wait 1mS for anti-cross conduction */ PORTB = 0x00; /* The break signal is negative logic */ } void Timer(long Time) /* Timer(100) = 100mS */ {Time = Time * TimeFactor; for (counter3 = 0; counter3 <= Time; counter3++) {};} void FastTimer(long Time) /* Timer(50) = 672uS */ {Time = Time * TimeFactorFast; for (counter4 = 0; counter4 <= Time; counter4++) {};} void EnergizeFETs(void) { /* Energize break circuit */ /* Break is on PB0 */ PORTB = 0x00; /* Set pullups off */ DDRB = 0x01; /* Set port B pin 0 for output */ PORTB = 0x01; /* Set port B pin 0 to 1 and all others to 0 */ /* Wait 1000mS for capacitive break circuit to stabilize (very important!!) */ Timer(1000); /* Energize motor circuit */ /* Motor is on PB1 */ DDRB = 0x03; /* Set port B pins 0 and 1 for output */ PORTB = 0x01; /* Set port B pin 0 to 1 and pin 1 to 0 */ /* Wait 1mS */ Timer(1); } void SetAUXPorts(void) { /* Set AUX ports */ /* Turn on DB5 for diagnostics */ DDRB = 0x23; PORTB = 0x01; } long GetVoltage(long Sensor, long ADCRange) { /* Select input Source */ if (ADCRange == 0) { if (Sensor == 5) {ADMUX = 0x00;} if (Sensor == 2) {ADMUX = 0x01;} if (Sensor == 4) {ADMUX = 0x02;} if (Sensor == 3) {ADMUX = 0x03;} if (Sensor == 0) {ADMUX = 0x0D;} /* internal ground for calibration */ } if (ADCRange == 1) { if (Sensor == 5) {ADMUX = 0x90;} if (Sensor == 2) {ADMUX = 0x91;} if (Sensor == 4) {ADMUX = 0x92;} if (Sensor == 3) {ADMUX = 0x93;} if (Sensor == 0) {ADMUX = 0x9D;} /* internal ground for calibration */ } /* Start A/D conversion */ ADCSRA = 0b11000110; /* Start A/D conversion (bit 6 is set) */ /* Test if done (bit 6 goes clear, but is set when A/D is complete) */ do {} while (bit_is_set(ADCSRA,6)); /* Get voltage Vref = 5000mV Rdivider = 1/4 Full range is 20V */ Voltage = ADCW; /* This word is the 10 bit A/D result */ if (ADCRange == 0) { Voltage = Voltage - VoltageOffset0; /* A/D Offset error adjustment */ if (Voltage < 0) {Voltage = 0;} Voltage = ((Voltage * 20) / 102) * 100; /* Convert A/D full range (1023) to 19980mV or 19.98V */ } if (ADCRange == 1) { Voltage = Voltage - VoltageOffset1; /* A/D Offset error adjustment */ if (Voltage < 0) {Voltage = 0;} Voltage = Voltage * 10; /* Convert A/D full range (1023) to 10230mV or 10.23V */ } return Voltage; } void CalibrateAtoD(void) { /* 5.0V REF Range = 20V */ VoltageOffset0 = 0; /* Clear old value */ VoltageOffset0 = GetVoltage (Ground, 0); /* Run the converter with grounded input */ VoltageOffset0 = ADCW; /* 2.56V REF Range = 10.23V */ VoltageOffset1 = 0; /* Clear old value */ VoltageOffset1 = GetVoltage (Ground, 1); /* Run the converter with grounded input */ VoltageOffset1 = ADCW; /* Get the raw A/D converter output word */ } void Vibrate(void) { /* Vibrate for 1/2 second */ BreakOFF(); Timer(1); for (counter2 = 0; counter2 < (VibrateFrequency * VibrateTime / 1000); counter2++) { PORTB = 0x03; FastTimer(20); /* Wait 500uS for motor on pulse */ PORTB = 0x01; Timer(1000 / VibrateFrequency); } } /* EEPROM stuff copied directly from ATMEL data sheet */ void EEPROM_write(unsigned char ucAddress, unsigned char ucData) { /* Wait for completion of previous write */ while (EECR & (1< TriggerFiringVoltage)) && (TriggerHold == 0)) { GunFunctionFlag++; GunFunction = GunFunctionFlag; if (GunFunction > 2) {GunFunction = 2;} /* limit is mode 2 */ count = 0; TriggerHold = 1; } } Vibrate();Timer(250);Vibrate(); /* Pull Trigger now to go to decrease BurstTimeFactor by number of trigger pulls. Limit = 0*/ for (count=0; count < 500; count++) { if (GetVoltage(Trigger,0) < 500) {TriggerHold = 0;} /* Tigger hold is a debounce for the trigger switch */ if (((GetVoltage(Trigger,0) > TriggerFiringVoltage) && (TriggerHold == 0) && (BurstTimeFactor > 0))) {BurstTimeFactor--; count = 0; TriggerHold = 1;} } Vibrate();Timer(250);Vibrate();Timer(250);Vibrate(); /* Pull Trigger now to go to increase BurstTimeFactor by number of trigger pulls. Limit = 250 */ for (count=0; count < 381; count++) { if (GetVoltage(Trigger,0) < 500) {TriggerHold = 0;} /* TiggerHold is a debounce for the trigger switch */ if (((GetVoltage(Trigger,0) > TriggerFiringVoltage) && (TriggerHold == 0) && (BurstTimeFactor < 250))) {BurstTimeFactor++; count = 0; TriggerHold = 1;} } Vibrate();Timer(250);Vibrate();Timer(250);Vibrate();Timer(250);Vibrate(); /* Pull Trigger now to go to decrease MotorSpeed by number of trigger pulls. Limit = 0*/ for (count=0; count < 500; count++) { if (GetVoltage(Trigger,0) < 500) {TriggerHold = 0;} /* Tigger hold is a debounce for the trigger switch */ if (((GetVoltage(Trigger,0) > TriggerFiringVoltage) && (TriggerHold == 0) && (MotorSpeed > 0))) {MotorSpeed = (MotorSpeed * 9) / 10; count = 0; SBTCounter = 0; TriggerHold = 1;} } Vibrate();Timer(250);Vibrate();Timer(250);Vibrate();Timer(250);Vibrate();Timer(250);Vibrate(); /* Pull Trigger now to go to increase MotorSpeed by number of trigger pulls. Limit = 255 */ for (count=0; count < 500; count++) { if (GetVoltage(Trigger,0) < 500) {TriggerHold = 0;} /* TiggerHold is a debounce for the trigger switch */ if (((GetVoltage(Trigger,0) > TriggerFiringVoltage) && (TriggerHold == 0) && (MotorSpeed < 255))) { if (MotorSpeed < 232) {MotorSpeed = (MotorSpeed * 11) / 10; count = 0; SBTCounter = 0; TriggerHold = 1;} else {MotorSpeed = 254;count = 0; SBTCounter = 0; TriggerHold = 1;} } } /* Store EEPROM data */ /* Gun Function */ if (GunFunction == Normal) {EEPROM_write(0x01, 0x01);} if (GunFunction == ThreeRound) {EEPROM_write(0x01, 0x02);} /* Burst time factor */ if (BurstTimeFactor < 1) {BurstTimeFactor = 1;} if (BurstTimeFactor >250) {BurstTimeFactor = 250;} EEPROM_write(0x02, (char)BurstTimeFactor); /* MotorSpeed */ if (MotorSpeed < 1) {MotorSpeed = 1;} if (MotorSpeed >254) {MotorSpeed = 254;} EEPROM_write(0x04, (char)MotorSpeed); Vibrate();Timer(250);Vibrate();Timer(250);Vibrate();Timer(250);Vibrate();Timer(250);Vibrate();Timer(250);Vibrate(); /* Pull Trigger now to go to Master Reset */ counter5 = 0; for (count=0; count < 500; count++) { while (GetVoltage(Trigger,0) > TriggerFiringVoltage) { counter5++; if (counter5 > 1500) { EEPROM_write(0x00,0xFF); EEPROM_write(0x01,0xFF); EEPROM_write(0x02,0xFF); EEPROM_write(0x03,0xFF); EEPROM_write(0x04,0xFF); Vibrate();Vibrate();Vibrate();Vibrate();Vibrate();Vibrate();Vibrate();Vibrate();Vibrate();Vibrate(); Halt1: goto Halt1; } } } } void ReadEEPROM(void) { /* Note - Erased EEPROM = FF not 00 */ /* 0x00 = Version Number 0x01 = GunFunction 0x02 = BurstTimeFactor 0x03 = SingleShotTime 0x04 = Motor Speed */ /* Version */ EEPROM_write(0x00, (char)VersionNumber); /* Gun Function */ if (EEPROM_read(0x01) != 0xFF) {GunFunction = EEPROM_read(0x01);} /* Burst time factor */ if (EEPROM_read(0x02) != 0xFF) {BurstTimeFactor = EEPROM_read(0x02);} /* Single Shot Time */ if (EEPROM_read(0x03) != 0xFF) { SingleShotTime = EEPROM_read(0x03); SBTCounter = 0; BurstTime = ((((2 * BurstTimeFactor) + 100) * SingleShotTime) * Mx / 10000) + Bx; } /* Motor Speed */ if (EEPROM_read(0x04) != 0xFF) {MotorSpeed = EEPROM_read(0x04);} } void SetBurstTime(long ShotCounter) { SBTCounter++; /* Do not update if ShotCounter is < 80% or > 120% of SingleShotTime (early user trigger release) */ if (SBTCounter > 25) { if ((ShotCounter > (SingleShotTime * 12) / 10) || (ShotCounter < (SingleShotTime * 8) / 10)) {return;} } else {SingleShotTime = ((SingleShotTime * 5) + ShotCounter) / (5 + 1);} BurstTime = ((((2 * BurstTimeFactor) + 100) * SingleShotTime) * Mx / 10000) + Bx; if (BurstTime < SingleShotTime) {BurstTime = SingleShotTime;} /* Be sure a BurstTime error will not lock out the gun */ /* Store SingleShotTime now */ if (SingleShotTime < 0) {SingleShotTime = 1;} if (SingleShotTime >254) {SingleShotTime = 254;} EEPROM_write(0x03, (char)SingleShotTime); } void MotorPWM(char PWMduty) /* Controlling OC0B which is pin 6 to the motor drive */ { if (PWMduty == 0) {TCCR0A = 0x03; GTCCR = 0x80; PORTB = 0x01; return;} /* Shutdown */ GTCCR = 0x81; /* HALT, SYNC, and RESET the PWM counter */ TCCR0A = 0x23; /* Enable PWMcontrol of motor and set function */ TCCR0B = 0x01; /* Divide by 8 for ~500hZ */ TCNT0 = (char)PWMduty; /* Zero the counter */ OCR0B = (char)PWMduty; /* This is the PWM comparator test value */ TIMSK = 0x08; /* Be sure the harware interupts are off but compare OS0B */ GTCCR = 0x00; /* Start the PWM */ } /* End of program */