/* SW-COMPUTER Factory Control Program for 2N Lion Converted from Cheetah-2N !!!! NOT DOWNWARD COMPATABLE with non 2N UNITS !!!! WWW.EXTREME-FIRE.COM Target controller = ATmega1284P-20AU microcontroller from ATMEL. Clock speed = 1MHZ with Internal Oscillator. Devolopment Platform = AVR Studio 4 Version 4.19 build 623. C Compiler = AVR ToolChain 3.3.0.710 GCC Optimization -Os Programmer = AVRISP mkII Hardware Schematic = SW-Lion-2N Hardware Layout = SW-Lion-2N Software Architecture = SW-Computer Internet Forum = http://forums.airsoftmechanics.com ME = sales@extreme-fire.com :D This program is fully released to the Public Domain by Terry Fritz 2007 / 2008. */ /* History 001 - Initial release Nov. 23, 2007 Terry Fritz 002 - Next generation Nov. 25, 2007 Terry Fritz 002.40 - First real beta release. 002.41 - Fixed Vibration timing problems, set peak current to 250 amps. Dec. 26, 2007 9:24AM 002.42 - Added Temperature sensing. Dec. 26, 2007 1:10PM 002.43 - Removed high battery voltage speed reduction. High battery voltage to 17.0V. Dec. 27, 2007 11:16PM Timing rechecked Dec 28, 2007 2:19PM 002.44 - Added event logging, LiPO monitor support, PWM and BurstTimeFactor low limits. Dec. 30, 2007 10:00AM 002.45 - Added LiPO Monitoring. Redid Port control. Jan 1, 2008 8:51AM 002.46 - Speeded up programing vibrations and Burst to Full auto time. Check LiPO alert 3 times. Jan. 7, 2008 4:30PM 002.50 - Released 002.60 - Tweaked LiPO Function. Lowered trip points from 3000mV to 1000mV. Changed installed detection from 2000mV to 750mV. Feb. 15, 2008 11:10PM Added FiringCounter (was unused MotorRunningLoopCounter). Added battery voltage reset after 300 trigger cycles and DeadTime. Fixed self test 6 limits. 002.70 - Fixed LiPO limits per WolfDragon. Mar. 14, 2008 4:28AM 002.80 - Set temp limit to 55C. Set Max battery voltage to 16V. Added maximum Current and Temperature logging. Added Semi-only, Auto Roll, Soft Start, and hard battery settings to definitions to make special programming easy. Changed RDSon to 2818 uOhms. Rollover counter adjusted to 162 from 167 counts. Fixed high to low battery voltage lockout problem - Thanks Jawz :) Brake time changed to 75mS. April 5, 2008 4:00PM 003.00 - Rolling to 3.00 rev now :) Adding over temp lockout! - Thanks Jawz!! Limited event log to 250 events. Recalibrated temperature. March 8, 2008 8:57PM 003.10 - Added "Secret Mode" programming for custom chip functions through trigger pulls: SemiOnly Soft and hard locks. 3-Round to full auto rollover enable / disable. Soft start enable / disable. Hard battery voltage limits. April 11, 2008 6:46AM 003.20 - Added low battery voltage failure warning. Fixed low voltage program mode problem. April 30, 2008 10:52PM Known issues: To use pin 1 (AUX1) 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. Set BrownOut voltage to 4.3V in fuses. Version 3.10 runs it low on memeory. Secret Mode programming is sacrificial. Future changes: None */ /* -- Trigger Master II specific programming -------------------------------------------- Done * Defaults to Burst Mode * Wider timing adj so 2 round burst possible * Replaced eventlog() ... "improved diagnostics" * Vibe confirmation in trigger pulls for confirmation (limited to actual real values for gunmode) * Burst complete even if trigger early release * Disable of AB (6 vibes: 0-no change 1-AB on 2-AB Off) * side effect: Reset now at 7 vibes * Semi Lock (enter this mode, no exit.) Pull trigger 10 times in mode selection = Semi LOCK * Burst only (no rollover to auto) - Mode '4' * Sniper/DM mode (Mode '5') added - semi-only mode with a 1 second shot delay 20080904 - Changed gun default if mode = 0xFF in eeprom to "ThreeRound" not "Normal" - Implemented wider range of "decrease burst time" and faster decrease (-2 not --) to allow for 2 round bursts if desired. Lower is by 4% at a time, higher is by 2% at a time. Max 20% slower and max 50% longer - Added ErrorLog() function and supporting DEFs to replace EventLog(). - Replaced EventLog() with a new function 20080904 - Sacrificed SecretMode() for program space - Altering firing loop so that bursts always complete (if more than 1 shot is made by time of letoff) - Added disable/enable of Active Braking in config mode - Implemented semi-lock mode (10 pulls in gun mode config = semi LOCK) - Burstonly mode done (Mode 4) - Sniper/DM mode with 1 second shot delay (but no delay for first 5 learning shots) ** This mode is COMPATIBLE with semi lock! (While in semi-lock, can select mode 5 to turn on ** delay. To turn off, just select any other mode. Lock will keep in semi but delay is now off.) - Hijacked the version eeprom byte to be Version 1.0 - Fixed bug in trigger pull detection for active braking enable/disable - Version 1.0 20080911 - Added ability for manual adjustment (via Burst time inc/dec in programming mode) of single shot time (i.e. 1-round bursts) for semi-locked units on fullauto-only guns. These guns have no means of auto-detecting shot timing due to lack of semi-auto mechanism. - Therefore added code which uses BurstTimeFactor to apply anywhere between -13 to +17 directly to (runtime) SingleShotTime. This is only done if: 1) Unit is semi-locked, and 2) BurstTimeFactor has been changed via user programming. (#2 is to ensure no functionality change for semi-locked units on semi-capable AEGs -- which have no reason to ever modify the burst time. And if they do, a factory reset will fix it.) - Fixed bug: A SEMI-LOCKED unit plugged directly into a fullauto-only gun will now "just work". This was not the case with previous versions. If a semi-only unit was installed into a gun that only fired fullauto, it would use the default BurstTime and never would override it for semi-only operation. Fixed in ReadEEPROM(). - Version 1.1 -- Trigger Master III changes, etc ---------------------------------------------------- 20081211 - New RDSon (value from Terry) for IRF1324 FET of 1280 (was 2818 uOhms) - DrainCurrentPeakLimit from 250(Amps) to 500(Amps) - Version 2.0 * This is the version used on the Trigger Master Mark III first production run 20090127 - Version C.1 Custom code for Anthony Surkon: Mode for SAFE-BURST-FULL (Mode 6) First 5 shots after powerup will be in semi only for timing sensing Cannot update timing after powerup Prototyping with a MkII, final on a MkIII 20090202 - Version C.2 Improved SafeBurstFull so that first 5 shots are still in semi (always ceasefire at disconnect of trigger in semi) but added code to re-introduce continual improvement/autosensing of bursttime based on the ShotCounter number detected at trigger disconnect time during bursts. (Added SavedShotCounter) It works by: 1. At the completion of a burst, call SetBurstTime() with the saved ShotCounter value before going to CeaseFire. 2. In CeaseFire - the normal location of SetBurstTime() - do not SetBurstTime if the gun is in SafeSemiFull mode. KNOWN ISSUE/LIMITATION: * Burst length is on a "Best Effort" basis. Since the piston is never stopped right at trigger disconnect time (in semi) after shot #5, it is not possible to guarantee the piston position and therefore firing cycle completion. This is exacerbated by the fact that the piston ceasefire position is indeterminate when firing in fullauto. SUMMARY (overrides any conflicting comments, not that there should be any): 1. SafeBurstFull (mode 6) works 2. Assuming the best practise of first 5 shots in semi-auto after powerup is done: The first 5 shots in this mode are always a single, semi shot. (This is to set the piston position to a known position.) 3. By the fifth trigger pull in semi, autosensing is complete and bursts should be 3 long. 4. A limitation of this mode is that burst timing is best-effort only. The piston is not stopped at trigger-disconnect time in semi, so shooting in semi doesn't 'reset' the piston. Also the piston will be in an indefinable position after shooting in full since it depends on when the shooter lets go of the trigger. It is possible and unavoidable that some minor drift in burst timing occurs in this mode. If it occurs, it can be reset if needed with a power cycle. - Text for label (Known Issues/Limitations): "It is possible for the duration of bursts to experience minor "drift" in a prolonged firing session, especially if fullauto is used frequently. When fullauto Is used the end-position of the piston depends on when the shooter releases the trigger. It could be half-way through a firing cycle when you stop. If you then fire a burst, that half-done firing cycle can lead to a minor timing error (e.g. a 3-and-a-half shot burst) This condition is just a mechanical gearbox issue. It does not affect the Trigger Master electronics, and any drift issue will be cleared at next powerup (when piston position is reset by the first shots in semi.)" - This is a candidate for production if it tests OK. - Code is getting a little... awkward. Might be time for a complete rewrite based on my other work. - 99.9% program full, by the way. 20090217 - Working on version 2.1 (Release Candidate) - Mode 6 (SAFE-BURST-FULL) above is considered a "bonus" mode only with no guaranteed timing accuracy. - ThreshHold for burst completion changed from 15% to 25%. 15% is too close to the "normal" variance for SingleShotTime of +/-10% Problem report: With much "quick triggering" the value of SingleShotTime (which is constantly updated as long as it is +/-10% of previous value) can become too small (successive -10%) and therefore cause burst fire on semi-auto, due to the "Complete burst if trigger released early" functionality. Another thing that can exacerbate (or be an alternate cause of) the problem is the firing cycle being left in a semi-fired state due to early trigger release or firing on auto. I.E. Firing cycle 50% done + next shot is on semi = detected SingleShotTime is SingleShotTime + 50% Fix attempt by cleaning up Burst Completion code and making more robust: 1) Altered threshold for burst completion from 15% to 25% (minor issue, does not solve problem) 2) make it static (set 'BurstCompletionThreshold' at startup and not updated, i.e. it is not a function of SingleShotTime and its later updates!) Cannot lead to burst complete on semi as long as the first timing shots are done properly. - Turns out that is not enough. Burst completion is more trouble than it is worth due to being fundamentally unable to cope with a lot of early-trigger-releasing which is an inevitable result of quick semi-auto trigger action. Therefore, disabling Burst Completion code. - Code is intact but voided by 'DisableBurstCompletion = 1'. - Sacrified FactoryDiagnostics() which I never use (honestly I thought I had toasted it before.) - Added EventLog(6) for Factory Reset Initiated - Added a vibe signal to end of delay for Sniper/DM mode - Advanced config option 6 is now 'Shot delay end vibe signal on/off' (default on) It used to be Active Braking enable/disable. 1 Pull = ON, 2 Pulls = OFF, 0 = No change - Fixed a bug where gunfunctions should only be processed if the trigger was pulled at all for option 1 (Firing Mode Selection) in programming mode. Skip that entirely if the trigger was not touched (otherwise it [re]sets some things it should not.) - V2.1, 96.1% 20090223 - Burst Completion has been successfully made more robust, but it still should be default off. - Added "Burst Completion On/Off" Default OFF. Programming option 7 - FACTORY RESET moved to "8" - Version 2.2, 99.2% 20090407 - Testing optimized code by Mike @ 3ONE Airsoft, dubbing it version 3.0 - Increase max voltage from 16.0V to 18.0V (defined in mV) - New programming method is: Powerup Short pulse Pull trigger once (before long pulse) Gun answer: 3 quick pulses (Programming mode entered) a. Pull trigger X times where X is option # b. Gun answer: X pulses confirmation of option # c. Pull trigger X times for option setting (feedback short pulse felt each trigger pull) d. Gun answer: 3 quick pulses e. Goto a) or do nothing for done Gun answer: Long pulse (programming mode exited) For best results, remove and re-connect power to reboot. (Some small gotchas seem to be present) - Note: FACTORY RESET = Enter option 8, gun answers with 8 pulses, hold trigger until long pulse, then reboot (gun will do nothing else after factory reset). 20090521 - Added function GetAux0Voltage() for future use, especially with Virtual Magazines. Useful reference data: Allegro sensor on +5V, GND, and Vout to AUX0 = ~2.5V when idle and ~0.2V or ~4.7V when a magnet is present, depending on sensor face and pole. (DK# 620-1024-ND) 0.1uF on power leads. Due to pullups, open connection to AUX0 (or AUX1) is read as ~5V. LiPo board is detected by measuring under 300mV on AUX0 and AUX1. Thus is is possible to auto-detect whether the sensor is plugged in. Simply check AUX0 voltage, if ~2.5V (assumes no mag inserted) then sensor plugged in and OK. * Virtual Magazines plug-and-play sensor functionality: At powerup, check whether magazine sensor is plugged in (look for ~2.5V on Aux0). Only check for LiPo monitor present later (as normal) if virtual magazine sensor is not present. In main loop, check sensor status: If it registers HIGH (~4.7V) or LOW (~0.2V) 3 times in a row, then vibe. Otherwise nothing. Global 'MagSensorInstalled' (default 0, set to '1' if magazine sensor detected.) Global 'MagPresent' is kept updated. Non-zero = mag present. - Added code to prevent firing if magazine is not present (only if magazine sensor is installed). Currently if using mag sensor mag must be NOT inserted at powerup to activate mag sensor functions. - Added event log event '7' for 'Magazine Sensor Unit Detected' - Version # changed to 3.1 - Test mode for Magazine Sensor Unit added as option #9. Once entered, is endless loop. Vibe while sensor sees magnet. Simple! Aid for install/test. If no magazine sensor unit was detected at powerup, vibes twice per second regularly forever as an error. - Added event code '8' for 'Sniper Mode Lock-in Dongle Detected' - SNIPER MODE LOCK-IN: #define SNIPER_MODE_LOCKIN_TIME 22600 (# of roughly 2-second increments) = ~12 hours 'RunTimeCounter' and 'RunTimeCounterOverruns' added to track each overrun (about 2 seconds) 'SniperModeLockIn' added (default 0). In ReadEEPROM() it's set to the contents of EEPROM byte 0x27 (if it's anything other than 0xFF.) If dongle (consisting of short from AUX0 to GND) is present at powerup, then Sniper/DM mode is enforced and cannot be left until the lock-in time is up. (About 12-14 hours, very roughly) 20090522 - Added code to lock-in Sniper mode even if dongle is not present at startup (but was in the past and time is not up) - Added code to actually activate sniper mode at dongle detection and set EEPROM byte 0x27 to '1' - Pre-emptively fixed a bug where sniper/dm mode doesn't actually implement the shot delay until a reboot if selected in configuration/programming mode. (runtime "ShotDelay" wasn't populated) 20090523 - Created GetAux1Voltage() - Added code to honor sniper mode lock-in byte and prevent changing gun mode. In ProgramEEPROM() firing mode is simply overridden to Sniper/DM if the user attempted any change to the gun's firing mode at all. (Once the time is up for sniper mode lock-in, the mode may be changed.) - Changed Sniper Mode lock-in detection threshold to be less than 300mV. It tested as 136mV in a test, even though it was shorted to ground. Need to make sure it is not confused with mag inserted at startup with correct pole when mag sensor installed (mag sensor puts ~2.5V idle, ~0.2V or ~4.7V on AUX0) The "low" result COULD look like "Sniper Mode Lock-in dongle present" which is AUX0 to GND, which isn't actually measured as 0V. - Also changed sniper mode dongle detect to check Aux1 voltage (should be HIGH instead of 'don't care') - Added EventLog events 9 and 10: Mag sensor not present or disabled (mag in), or sniper dongle not present (respectively). 20090525 - Changed EventLog event #0 from "Factory Test Done" to "Power On Initiated" - Implementing virtual magazine capacity programmability (multiples of 5) EEPROM location 0x28. It is read into "virtual_magazine_capacity" at startup by readeeprom(). Option #10 sets virtual magazine capacity. Each trigger pull = +5 rounds (start at 0). Default mag capacity is 30 rounds. Highest mag capacity is 100 (20 trigger pulls.) - Added code to manage reloads (reload to mag_capacity or +1 if still a round in the chamber) at mag insertion. - Added code to manage forcing 1 second deadtime when mag is removed. (No "popping" the mag to reload) - Added code to count shots in semi-auto - Added code to not fire if no rounds in magazine (or no mag) and mag sensor installed - 93.4% so far. Candidate for cleaning is detection of magazine sensor (doesn't need 3x read) 20090526 - Fixed a bug in semi-auto round counting. Code now deducts 1 round every time the motor is turned on due to a trigger pull. Additional rounds due to bursts or fullauto will be added later in loop. - Implemented burst counting for safe-burst-full mode (first sync shots are counted as 1 each) - Implemented 'burstcompletion_flag' flag value to tell when SafeBurstFull was in Burst or Full so we can apply proper shot counting, and when we are completing bursts in other modes. - Fixed a bug with shotcounter being reset furing fullauto for round counting, it does mess things up even when round counting is disabled. - Bugfixing round counting for mag mod. - Bug fix for mag sensor where only one pole but not the other registered as a mag change. - Addressed issue where changing fire modes in some cases had odd side effects related to burst times, etc. - After some fixes, round counting seems to work for everything except semi-only. Only get 3 shots (sometimes 2) before a 5 round mag is "empty". - Simple fix: wrap a "if not semi-only mode" around the Burst decrement code, because semi-only works by way of a 1-shot "BurstTime". Which gets counted as a "burst", which causes -2 rounds... - Round counting appears fixed and accurate now! - Changed Sniper Mode Lock-in dongle detection to be AUX0 to GND (don't forget weak pullup effect) and AUX1 at about 2.5V (voltage divider of 10k and 10k between +5 and GND). NOTE: Will not work until reset disable fuse is engaged to allow AUX1 to be an input. - 95.1% 20090527 - First shot 100% motor speed override feature is logically and conceptually actually a "user-programmed motor speed DOWNGRADE applied to fullauto only after first shot" feature. In other words, for all other intents and purposes the motor speed is overridden to 100% when this feature is active, it's just fullauto which is downgraded to the user-specified speed after the first shot. (Bursts and first shots of full are full-speed.) a. When user enables this option, a flag is turned on. b. While flag is active, motor speed is overridden in MotorON() to 100%. c. While flag is active, when entering fullauto downshift motor from 100% to the current programmed motorspeed. - "Semi and Bursts always at 100% speed" Implementing 100% override feature. EEPROM byte 0x29. 'FastFirstShots' runtime variable created (default 0). 'UserMotorSpeed' for saved value created. - 97.6% 20090529 - Cleaning up comments, etc. - Version 3.1 now. 97.7% use. Summary of changes since version 2.2: o Virtual Magazine Sensor (Allegro Hall Effect sensor) is automatically detected at startup by detecting ~2.5V on AUX0. o Sensor detected AND magazine inserted at startup = disable magazine sensor and virtual magazine functionality entirely. o Sensor detected AND *no* magazine inserted at startup = virtual magazine functionality enabled. o Gun will not fire if no magazine inserted. o Virtual magazines enabled means a magazine change is required every "X" rounds fired (default 30). o Purpose: Two (or more) hicaps can simulate and endless supply of "realcaps" by simply swapping between them for mag changes. o Mode "9" in advanced configuration is a neverending loop intended to assist installation of the sensor. The motor is vibrated whenever a suitable magnet is detected to be in proximity of the sensor. This is so the user can literally "feel out" optimal sensor placement and magnet suitability during installation. o Mode "9" vibrates regularly every 0.5s as an error if magazine sensor not detected. o Sensor design: Vin to +5V on AUX. GND to GND on AUX. Vout to AUX0. 0.1uF between Vin and GND. o Mode "10" in advanced configuration allows changing the capacity of virtual magazines in increments of 5 (per trigger pull). Maximum is 100 rounds. Minimum is 5 rounds. Default is 30. Zero trigger pulls disables virtual mags (but keeps sensor working and no mag = no shoot). o Mode "11" enables/disables "Fast First Shots": 1 Trigger Pull = ON, 2 = OFF. o "Fast First Shots" feature: when this is ON, semi-auto shots, bursts, and the first round of fullauto are always at motor speed 100% for fastest rate-of-fire and best trigger response. (It is intended to work along with reducing motor speed - so that fullauto rate-of-fire can be reduced to something more realistic/less wasteful without making bursts, single shots, or trigger response in general sluggish.) o Mode "12" (20090929, version 3.6 and later) enable/disable "LiPO Mode": 0=no change, 1=off, 2-4=number of cells in the li-po. This option overrides normal low batt calculation and implements voltage cutoff. These are pre-set voltages based on the indicated # of cells in the lipo at startup (2,3,4 cell packs supported). If a WolfDragon Li-PO monitor board is installed, it overrides this option. o "Sniper Mode Lock-In" feature causes the gun to enter "Sniper/DM Mode" (semi-only with shot delay) AND - this sounds a little complicated - will not allow the firing mode to be changed for at least 12 hours. o When the "Sniper Mode Lock-In" dongle is detected in the AUX port at startup (AUX0 at GND, AUX1 at ~2.5V) the gun enters the sniper firing mode and a lock flag is set in EEPROM. o While this lock flag is set, the gun will refuse to change firing modes. o This flag is only cleared if the gun has been powered up continuously for approximately 12 hours. Once that time is up, the EEPROM flag is cleared and the gun will allow firing mode changes. o Therefore "start up once" with the dongle = 12 hour lock-in. Removing the dongle and rebooting will only reset the 12 hour timer. o Sniper Lock-In Dongle design: AUX0 to GND, Two 10k resistor voltage divider (+5 and GND on AUX) to AUX1. 20090618 - Changed semi-lock trigger pulls needed to be 20 (not 10) for less chance of accidentally triggering. - 97.7% - Version 3.2 20090818 - Startup "long" vibration happens if any plug-and-play device detected and enabled on AUX at startup. (was just for LiPO daughterboard) - Removed triple-read for magsensor board to a single read only to save a wee bit of space. - Version 3.3 20090909 - "FastFirstShots" is referred to as "Smart RoF Drop-down" in documentation. - Fixed minor bug with double-long delay when LiPo board detected (didn't remove original) 20090910 - Added ability to disable virtual magazine (but keep the sensor activated and no-shoot while no mag) by setting the 'virtual magazine capacity' to 0 trigger pulls. NOTE THIS IS A DEPARTURE FROM NORMAL SETTING OF NO ACTION = NO CHANGE IN CONFIGURATION OPTIONS. - Added 'VirtualMagNeverEmpties' variable to make the above happen. - Version 3.4 (Untested) - 98.4% - Fixed small bug causing virtual mags to be disabled when programming mode was entered for any reason. Fixed by adding a flag value 'virt_mag_disable_flag' to the processing of the option. - 99.0% - Improved fullauto round counting for virtual magazine functionality. When shot counter > (singleshottime-12) then decrements rounds in virtual mag. (Note this was worse with bug above.) 20090911 - Bugfix for SetBurstTime() setting totally wrong values for stored SingleShotTime when ShotCounter is a "bad" value (such as from a FA burst). Fixed by a wrapper that prevents SingleShotTime being set to ShotCounter on golden shot (#3) if it's >250 (max singleshottime stored value is 254) AND moved the updating of the SingleShotTime for shots >3 into the preceding if() statement logic. It was out of it before, so the +/-10% was only performed on shots >3. Shots not >3 were updated without checking the +/-10% range (and could be using the 'bad' values such as described earlier). - Further improvements on virtual magazine FA shot counting. Mostly accurate, FA short bursts more accurate than long FA strings but semi and 3-bursts are accurate (that is unchanged). 20090923 - Adding some comments around battery monitoring and low voltage warning as prep for new functionality. - Fiddled the battery limits reset code. It kicks in only once, after about 300 triggers (i.e. after the battery has "settled" is the idea in the original code, I guess.) I removed a condition I had put in earlier since it never matters. - Changed the "hard battery voltage" condition for the battery reset limits. I'm pretty sure it's a typo, and that if the hard limits are enabled it should NOT reset battery limits (as it's written in: if ((BatteryLimitReset == 0) && (FiringCounter > 300) && (HardBatteryVoltage != 0)) it expressly only allows battery limit reset if HardBatteryVoltage is enabled. - 98.5% use 20090924 - Version 3.5 - Began changing "HardBatteryVoltage" (settable in EEPROM) to "LiPOModeOption" option. - Added code to support programming "LiPOModeOption" in EEPROM. Removed some logical references to HardBatteryVoltage that were unused anyway (HardBatteryVoltage was not used.) - Option 12 is "LiPO Mode". 1 pull = on, 2 = off, none = no change. - While "LiPOModeOption" (0x23) is ever only 0 (or 0xFF) or 1 in EEPROM (corresponding to disabled or enabled, respectively) during runtime it can change and may be used as a flag value. In other words it should be checked whether 0 or non-zero only. - Need to wrap functionality around a check of 'if (LiPOInstalled == 0)' because an actual LiPO monitor installed should override "LiPO Mode". - 98.8% - Added code to detect LiPO (2 or 3 cell) at startup by checking voltage, then overriding low batt trip point. (THREE_CELL_LIPO_LOW and TWO_CELL_LIPO_LOW) Also disables runtime of LiPOMode if neither 2 or 3 cell was detected. 20090928 - Added 'BatteryVoltageCutoff' which is default 7.0V (minimum voltage for electronics to work.) - Added code to respect that voltage (refuse to fire if voltage too low.) - Rewrote fire handling to cutoff firing if droop warning happens AND battery is not above cutoff level. - Removed the Battery Limit reset code entirely. Originally written it only kicked in when Hard battery limits were used which was possibly a typo and was never used anyway. Neat idea but we have gotten along just fine without it forever so far, and the program space was needed. - 99.6% - Fixed small bug with detection of LiPOs with LiPOModeOption, added eventlog() events around it. - 99.9% - Fixed some bugs with LiPOMode selection in ProgramEEPROM(). - 'InitialBatteryVoltage' is stored in EEPROM during ProgramEEPROM() and read at startup by ReadEEPROM(). Problem is that it's only re-done at startup if never programmed... Can this interfere with LiPOMode detection? ** NOT if LiPOModeOption is like 'register the current lipo.' Change battery pack voltages, reprogram. - Fixed bug with LiPO Mode battery detection - was using 1100 and 1300 (1.1V and 1.3V) not 11000 and 13000 (11.1V and 13.0V) - Added code fo 4 cell LiPOs too, ditched some eventlogs. - 100.0% - Ready for testing. 20090929 - Well that didn't last long. Turns out Lithium-Ion cells can have different cathode/anode combinations which cause different cell nominal voltages even though they are still "Lithium Polymer" batteries. Example: New MadBull batteries that use LiFE-Po4 chemistry and are nominal 3.3V per cell (labelled 9.6V and 12.8V packs). Still 3.0V min per cell. - Need to change the feature to no longer auto-detect based on voltage level. Instead the feature should ask the user how many cells are in their Li-Ion (any type) pack. Maintain same cutoff and droop warning levels. User should not switch different pack types (different cell numbers) without altering the setting accordingly. Keep in mind that "Li-PO mode" simply tailors the low battery warning to the cell chemistry and applies a cutoff. The low battery will last longer or shorter depending on the exact cell chemistry and nominal voltage. - Changed LiPO cutoffs and droop tripwires at startup to use cell #s based on 'LiPOModeOption'. 'LiPOModeOption' should be 0, 2, 3, 4 corresponding to cell # or 0 for disabled. - Re-added battery limit reset code with some ugly crap. - Changed LiPO mode (#12) in advanced config to accept 0-4 trigger pulls. 0=no change. 1=disable. 2-4 = number of lipo cells (used to configure droop and cutoff voltages at startup). - Moved LiPOModeOption level setting code to after programeeprom() and readeeprom() so that it's processed properly after a user might turn it on in advanced configuration. - v3.6 - 100.0% 20091019 - Li-PO mode: apparently individual cell voltages can drift by up to 0.1V? Confirmed field behaviour. Increased the cutoff level from (example) 9.1V to 9.2V for a 3 cell pack in Li-PO mode and the rest accordingly. (Assume one cell @ 3.0 and rest at 3.1) - v3.7 20091021 - Made voltage cutoff (but not low batt) in Li-PO Mode "sticky". - Removed Burst completion on/off code to make room. (Option #7) - v3.8 - 99.4% 20100419 - Quick and dirty change to use 'shotdelayendvibeenable' (default OFF) as 'activebrakingdisable' instead. - Left everything completely the same. Just change the code to ignore shot vibe, and to disable AB if it's nonzero. - 98.9% - 1 warning (unused var 'counter2' -- was for shotdelayendvibeenable code) - v3.9 20100921 - Fixed error detecting Sniper Lock-in (was missing mV=GetAux1Voltage before check) - v4.0 20101103 - Mark IV (2N power board) version. - List of changes: Change port control definitions for positive Brake Logic: from #define Neutral 0x29 #define Brake 0x28 #define Motor 0x2B to #define Neutral 0x28 #define Brake 0x29 #define Motor 0x2A Changed temp limit to 70C Changed low voltage limit to 6.5V - v5.0 (99.0%) LIMITATIONS/KNOWN ISSUES: Virtual Magazine Functionality: - Actual BBs are not detected. - A physically empty magazine is not detected. - Bursts always complete (if 2 shots left in the virtual magazine and the user fires a burst, 3 shots will still be done.) - Bursts are assumed to be 3 rounds long (user might override burst timing to get 2 or 4 round bursts but this is not detected.) - Round counting in semi-auto is very accurate but round counting in fullauto is only an estimate (but a decent one.) - In summary: Using this feature to simulate a large amount of "realcaps" with a couple hi-cap magazines to swap between for mag changes = works great! Using this feature with _actual_ 30-round magazines to make the gun stop firing when the magazine is empty = not so great. - A voltage divider and mechanical switch can replace the hall effect sensor. - Virtual magazine FA counting does not take into effect RoF Dropdown effect. RoF Dropdown users will have FA appear to count shots too quickly (while 3-bursts and semi are counted properly) - LiPO mode low voltage and cell cutoff for 2-cell (typ. 7.4V lipos) are based around minimum voltage of 7V for our unit, not cell chemistry. - If using LiPO mode, do not switch battery pack types (i.e. different # of cells) without also resetting (or disabling) the LiPO mode option. - 'InitialBatteryVoltage' is stored in EEPROM during ProgramEEPROM() and read at startup by ReadEEPROM(). Possible problem is that it's only re-done at startup if never programmed... - So therefore: change battery voltage packs, go into progamming mode (and reset just to be sure) then 5x semi shots to time. POTENTIAL ISSUES: 1. If Wolfdragon LiPO Monitor board is plugged in, it must be in a "battery OK state" to be detected. (i.e. AUX0 and AUX1 are both <300mV) This is code's original function. But if the monitor is something else (possibilities include AUX1>500mV = DEAD, AUX0>500mV = WEAK) then the monitor is not detected and it must be ensured that there is no possibility of conflict with other plug-and-play device states. LIST OF PLUG-AND-PLAY Device states (i.e. valid states possible at startup on AUX port to ensure no conflicts) --- AUX0 AUX1 Device and State ~5V ~5V Nothing connected (weak pullups pull-up to 5V) <300mV <300mV Wolfdragon LiPO monitor board present, also Battery OK signal (checked at startup) >500mV *Wolfdragon LiPO monitor Battery DEAD signal (possible startup state) >500mV *Wolfdragon LiPO monitor Battery WEAK signal (possible startup state) (* = Should not occur, users are directed to get "all clear" on monitor before powerup MOSFET unit) 2200-2750mV Magazine sensor installed and idle (no mag inserted) Checked at startup <500mV Magazine present (sensor and pole sensed combination A) >4500mV Magazine present (sensor and pole sensed combination B) <300mV 2100-2900mV Sniper Mode Lock-in dongle present TODO 20090708 x Improve shot counting in fullauto for virtual magazine (100 seemed way too long for actually 100) DONE, 30 and 40 round "mags" tested basically OK o Allow for some programmatic way to disable the virtual magazine (so the sensor is just ON=shoot OFF=noshoot) ** This can alternately be done with a switch in series with trigger wires 20090910 - 0 trigger pulls is disable, 1 is 5 rounds, etc etc. x Don't disable by start with magazine inserted --- no good for mounting on feed cover like M240/M249 which start that way ** UPDATE 20090909 actually this is unavoidable since the output isn't right (isn't ~2.5V) if the magazine sensor is installed and a mag is present. 20090922 o Auto-select "hard" voltage cutoff if in user-selected li-po mode. Also modify low warning.tc. Normal fire then vibe (errorcode 16) for normal droop calculation Add no fire, vibe only if under hard cutoff voltage. Allow an actual LiPo monitor board to override this functionality Note that existing code and stuff only measures battery voltage when not under load. Compensate by making "dead" be slightly higher than 3.0V per cell. 20091006 - Should ignore rapid succession semi-auto shots for purposes of re-evaluating single shot time. If the last was was too close to the previous then skip re-calc altogether (the +/- 10% thing) 20091019 - When Li-PO mode is enabled perhaps there should be some outward sign of it? - Maybe 'low' but certainly Cutoff levels for Li-PO mode should "lockout". - Ditch the burst completion mode code. It's problematic. NEXT VERSION * User-configurable low-batt and cutoff modes (like Li-PO but user set) * User-configurable pre-cocking (need care here) 18 Hints to reduce code size 1. Compile with full size optimization. 2. Use local variables whenever possible. 3. Use the smallest applicable data type. Use unsigned if applicable. 4. If a non-local variable is only referenced within one function, it should be declared static. 5. Collect non-local data in structures whenever natural. This increases the possibility of indirect addressing without pointer reload. 6. Use pointers with offset or declare structures to access memory mapped I/O. 7. Use for(;;) { } for eternal loops. 8. Use do { } while(expression) if applicable. 9. Use descending loop counters and pre-decrement if applicable. 10. Access I/O memory directly (i.e., do not use pointers). 11. Declare main as C_task if not called from anywhere in the program. 12. Use macros instead of functions for tasks that generates less than 2-3 lines assembly code. 13. Reduce the size of the Interrupt Vector segment (INTVEC) to what is actually needed by the application. Alternatively, concatenate all the CODE segments into one declaration and it will be done automatically. 14. Code reuse is intra-modular. Collect several functions in one module (i.e., in one file) to increase code reuse factor. 15. In some cases, full speed optimization results in lower code size than full size optimization. Compile on a module by module basis to investigate what gives the best result. 16. Optimize C_startup to not initialize unused segments (i.e., IDATA0 or IDATA1 if all variables are tiny or small). 17. If possible, avoid calling functions from inside the interrupt routine. 18. Use the smallest possible memory model. 20111102 - Terry blindly changed Don's program to work on the SW-Lion-2N :D --------------------------------------------------------------------------------------- */ /* Include libraries */ #include /* For all the odd ATTINY stuff */ #define Aux0 0 #define Aux1 1 #define Aux2 2 #define Drain 5 #define Trigger 6 #define Ground 10 /* Variable definitions */ #define VersionNumber 0x60 /* Software version number programed to EEPROM */ #define HighBatteryVoltage 18000 /* In mV */ #define LowBatteryVoltage 6500 /* In mV */ #define TriggerFiringVoltagePercent 80 /* Perent */ #define BatteryVoltageDroopWarningPercent 85 /* Percent */ #define TriggerHoldVoltage 3000 /* In mV */ #define TrigTrip 500 /* In mV */ #define DrainCurrentPeakLimit 500 /* In Amps */ #define RDSon 1280 /* In uOhms */ #define TemperatureLimit 70 /* In Celcius */ #define BrakeTime 75 /* mS */ #define Normal 1 /* These are GunModes made easy to read */ #define ThreeRound 2 #define ThreeRoundAuto 2 #define SemiOnly 3 /* Semi Only gun function */ // DonP Added 20080904 #define BurstOnly 4 // ThreeRound but with AutoRoll (roll over to auto) off #define SniperDM 5 // Semi only, with shot delay // DonP Added 20090127 #define SafeBurstFull 6 // SAFE-BURST-FULL, first 5 shots in semi exempt for timing. // Old for old board design //#define Neutral 0x29 /* Port control values Modified for LiPO */ //#define Brake 0x28 //#define Motor 0x2B // New for 2N power board #define Neutral 0b00000000 /* Port control values (PBx) Modified for LiPO PnP */ #define Brake 0b00010000 #define Motor 0b00001000 // DonP Defines // For EventLog #define ERRLOG_MEMSTART 0x41 // Beginning of cyclic error log #define ERRLOG_MEMEND 0xAF // End of cyclic error log #define ERRLOG_BOOKMARK_LOCN 0x40 // Always contains last location written to // For shot delay (non-zero shot delay for Sniper/DM mode) #define SNIPER_DM_MODE_SHOT_DELAY 100 // 1 second (This number * 10 = Delay in milliseconds) // For Sniper/DM mode lock-in #define SNIPER_MODE_LOCKIN_TIME 19000 // Number of "RunTimeCounter" overruns needed before Sniper mode lock-in is erased.(Each is very roughly 2 seconds) //Empirically tested: 1183 = 45mins, 22600 = 14.3hrs, 1577 = 1hr, 19000 = ~12hr min // NOTE about the following (DonP 20090929) // Li-PO cells can have different cathode/anode chemistries which lead to different cell voltages. // Example: Typical Li-PO is nominal 3.7V per cell but LiFE-Po4 (new ones from MadBull ~Fall 2009) // are 3.3V per cell and advertized (possibly incorrectly) as 9.6V and 12.8V. // ** However they are all Lithium-Ion cells and cutoff voltages are the same (3.0V min). // So keep the cutoff and low battery warning tripwire voltage the same for all cell numbers regardless // of specific cell types in the batteries. This will still work but low battery will last longer // or shorter depending on the specific battery type used is all. // // DonP 20091019 - Increased slightly 3 and 4 cell li-po cutoffs. #define TWO_CELL_LIPO_LOW 7100 // "low batt" in mV for typ. 7.4V (2 cell) lipo #define TWO_CELL_LIPO_DEAD 7000 // "dead batt" cutoff in mV for typ. 7.4V (2 cell) lipo // Note that low and dead for 2-cell based around our minimum voltage for the unit rather than // cell chemistry. (Since we can't run reliably below 7V) #define THREE_CELL_LIPO_LOW 9600 // "low batt" in mV for typ. 11.1V (3 cell) lipo #define THREE_CELL_LIPO_DEAD 9200 // "dead batt" cutoff for 11.1V (3 cell) lipo + buffer // #define FOUR_CELL_LIPO_LOW 12800 // Low batt in mV for typ. 14.4V (4 cell) lipo #define FOUR_CELL_LIPO_DEAD 12300 // An extra .1V per (#cells-1) as safety /* Function prototypes */ void MotorOFF(void); /* Turns the motor off */ void MotorON(void); /* Turns the motor on */ void BrakeOFF(void); /* Turns the Brake off */ void BrakeON(void); /* Turns the Brake 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(unsigned char Sensor, unsigned char ADCRange); /* Gets the Voltage (in mV) from the chosen source (Drain, Trigger, Aux) */ void Vibrate(void); /* Vibrates the gun's motor for signaling 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 */ // Sacrificed FactoryDiagnostics (unused) // void FactoryDiagnostics(void); /* Factory test */ unsigned char LiPOStatus(void); /* LiPO battery check */ // void Diagnostic(long DiagTime); /* Diagnostic test routein */ // Sacrificed SecretMode() // void SecretMode (void); /* Secret Programming Mode */ void Vibrate1 (unsigned char); /* Multiple Vibrations */ // void EventLog(char); /* Event logging */ void EventLog(char eventcode); // DonP Replacement function long GetAux0Voltage(void); // DonP 20090521 - returns in mV long GetAux1Voltage(void); /* Initialize variables */ /* long is a 4 byte integer */ unsigned char SemiOnlyLock = 0; /* Semi-Only lock flag 0=off (default) 1=on 2=permant on*/ unsigned char AutoRoll = 1; /* 3-Round auto rollover flag 0=off 1=on (default) */ unsigned char SoftStartEnable = 1; /* Soft Start Enable 1=default */ // DonP 20090924 //long HardBatteryVoltage = 0; /* Hard Battery Voltage Setting in mV. 0=off*/ unsigned char LiPOModeOption = 0; // 0=off/disabled (default, normal) 1=enabled. // This is only ever 0 (or 0xFF) or 1 coming out of // ReadEEPROM() and in EEPROM. It might be modified in // runtime to track overriding battery limits, etc. // Therefore: // 0 = disabled. Nonzero = Enabled/possibly in progress long InitialBatteryVoltage = 0; /* mV */ long BurstTimeFactor = 80; /* The single to three round burst ratio DEFAULT if EEPROM = FF*/ /* 100 = 3.00 X the single shot time. 2X+100% */ long BurstTimeFactorPercent = 0; /* Real BurstTimeFactorPercent */ unsigned char MotorSpeed = 254; /* 0 to 254 PWM value */ unsigned char UserMotorSpeed = 254; // DonP 20090527 - so we don't have to re-read EEPROM to get original value if the override option is used long SingleShotTime = 70; /* Default loops for single shot */ long BurstTime = 205; /* Default motor on time for a burst */ long SingleShotTimeOld = 0; /* Old value of single shot time */ unsigned char ErrorCode = 0; /* The Errors are each given a unique number */ long DrainCurrent = 0; /* The current through the drive FET in Amps */ long VoltageOffset0 = 0; /* A/D 5.0V REF offest error */ long VoltageOffset1 = 0; /* A/D 2.56V REF offest error */ long VoltageOffset2 = 0; /* A/D 1.1V REF offest error */ unsigned char TriggerHold = 0; /* Hold further shotting until trigger is released */ unsigned char GunFunction = ThreeRound; /* Operating mode DEFAULT if EEPROM = FF */ /* 1 = Normal 2 = ThreeRound 3 = Semi Only*/ // 4 = BurstOnly, 5 = Sniper/DM, 6 = SafeBurstFull long TimerCounter = 0; /* Timer counter */ long SBTCounter = 0; /* Burst time function counter */ unsigned char LiPOInstalled = 0; /* LiPO Installed flag */ unsigned char TemperatureMAX = 0; /* Maximum temperature seen */ unsigned char CurrentMAX = 0; /* Maxiumum current seen / 10 */ // DonP 20090217 - Turned it into this instead unsigned char ShotDelayEndVibeEnable = 0; //0x24 EEPROM address unsigned char ActiveBrakingDisable = 0; // Same as above! Quick and dirty. Set this value @ brake time to copy 'ShotDelayEndVibeEnable' // DonP 20080904 - For Sniper/DM mode (semi-only mode with shot delay) unsigned char ShotDelay = 0; // 0x25 EEPROM address (0 or FF in eeprom = disabled) // DonP 20090217 - For burst completion threshold. Calculated at SetBurstTime(). unsigned long BurstCompletionThreshold = 88; // Default value is default SingleShotTime (70) // + 25% = 88 unsigned int DisableBurstCompletion = 1; // 0x26 EEPROM address (FF in eeprom = disabled) // DonP 20090521 - For virtual magazine functionality unsigned char MagSensorInstalled = 0; // Whether a magazine sensor was detected unsigned char MagPresent = 0; // Aka Sensor status: 0=IDLE, 1=LOW, 2=HIGH (1 or 2 is mag present) // DonP 20090521 - For sniper/dm mode lock-in feature unsigned char SniperModeLockIn = 0; // Set to 1 if dongle detected at startup, or EEPROM lockin byte is set. // DonP 20090525 - Virtual Magazine capacity global, contains value of 0x28 as a multiple of 5 unsigned char virtual_magazine_capacity = 30; // Default 30 rounds. Changed by ReadEEPROM() at startup. // DonP 20090527 - For overriding first shot/burst to be 100% motor speed unsigned char FastFirstShots = 0; // DonP 20090910 - To enable disabling virtual mag (but keep sensor functionality) unsigned char VirtualMagNeverEmpties = 0; int main(void) { /* MAIN POWER UP */ long count1 = 0; /* Error loop counter */ long counter2 = 0; /* Counter for Vibrate function */ long count = 0; /* local counter */ long TriggerVoltage = 0; /* The voltage on the trigger input in mV */ long DrainVoltage = 0; /* The voltage on the MOSFET drains mV */ unsigned long FiringCounter = 0; /* Counts firing cycles*/ unsigned char GunMode = Normal; /* 1=trigger control (Normal) 2=burst (ThreeRoundAuto) */ long ShotCounter = 0; /* Shot count inside a burst */ signed long FullAutoLoopCounter = 0; // DonP 20090526 - shotcounter for fullauto round counting long SavedShotCounter = 0; // DonP 20090202 - for SafeBurstFull long ModeCounter = 0; /* A main loop counter for the threeround auto fuction */ unsigned char DroopWarningFlag = 0; /* Droop warning counter */ unsigned char DroopWarningFlag1 = 0; /* Hard Droop warning counter */ unsigned char LiPO = 0; /* LiPO battery status */ // DonP 20090928 - removed since unused after a change to make program space (it is not really LiPO installed flag anyway) unsigned char BatteryLimitReset = 0; /* LiPO Installed flag */ long DeadTimeCounter = 0; /* Counter used to find battery rest time */ long RunningTemp = 0; /* CPU temperature */ long BatteryVoltageDroopWarning = 5500;/* mV */ // DonP 20090928 - added to make voltage cutoff work long BatteryVoltageCutoff = 7050; // Default is 7.05V (7.0 is minimum for electronics to work) long TriggerFiringVoltage = 6000; /* mV */ unsigned char OverTempLockout = 0; /* Over temperature lockout flag */ unsigned char flag = 0; long mV = 0; // DonP 20090521 - variable for holding mV read from AUX ports. unsigned char mag_sensor_reading = 0; // DonP 20090521 - 0=nothing, 1=low, 2=high unsigned char prev_mag_sensor_reading = 0; // DonP 20090521 - used to keep track of sensor values so software filtering can be done unsigned char consecutive_reads = 0; // DonP 20090521 - For abovementioned filtering unsigned int RunTimeCounterOverruns = 0;// DonP 20090521 - To track how long we've been powered on unsigned int RunTimeCounter = 0; // DonP 20090521 - a "DeadTimeCounter" we can play with and reset at will signed int virtual_magazine_rounds_remaining = 0; // DonP 20090525 unsigned char burstcompletion_flag = 0; // DonP 20090526 unsigned char StickyBatteryCutoff = 0; // DonP 20091021 - For making LiPO Mode battery cutoff "sticky" /* 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) > TrigTrip) {ErrorCode = 4;} // EventLog(0); // Power up initiated /* Get EEPROM data */ ReadEEPROM(); // DonP 20080911 - Code to allow for manual adjustment of single shots in semi-locked mode // (See log for details) // if (EEPROM_read(0x20) == 0x02) // If we are SEMI-LOCKED { if( EEPROM_read(0x02) != 0xFF) // If unit has modified BurstTimeFactor, modify SingleShotTime { // This doesn't work well: Took it out // Apply BurstTimeFactor% + 20% to [the runtime value of] SingleShotTime // (BurstTimeFactor default is 80%) Actual value could be from 20-250 before we +20. /* SingleShotTime = (((BurstTimeFactor+20)*SingleShotTime)/100); */ // Replaced it with this below. (Mis)use bursttimefactor to modify runtime singleshottime. if( BurstTimeFactor >= 80) { flag = (BurstTimeFactor - 79); // 1-171 SingleShotTime = SingleShotTime + (flag/10); // +1 to +17 } if( BurstTimeFactor <= 79) { flag = (BurstTimeFactor-19); // 1-60 SingleShotTime = (SingleShotTime-(66-flag))/5; // -13 to -1 } } BurstTime = SingleShotTime-2; // This is the default semi-locking operation from SetBurstTime() // // This needs to be done even if we didn't mess with BurstTimeFactor above, because default // bursttime is about 3 shots worth. If we never do a SetBurstTime() then a fullauto-only gun // will never set BurstTime to be SingleShotTime; so we have to ensure it here if we're // semi-locked. } // // Adjusting BurstTime in programming mode therefore modifies the shot time for Semi-Locked // Trigger Masters in fullauto only guns (which are not capable of self-adjustment which requires // semi-auto gearbox operation.) We only do this if we're semi-locked AND the BurstTimeFactor has // been altered via user programming. // // End DonP for time adjusting of single shots work for semi-locked units with modified bursttimefactors. /* Energize drive circuits */ EnergizeFETs(); /* Setup AUX ports */ SetAUXPorts(); /* Check drain voltage */ DrainVoltage = (GetVoltage(Drain,0)); if (DrainVoltage < LowBatteryVoltage) {ErrorCode = 2;} if (DrainVoltage > HighBatteryVoltage) {EventLog(1); ErrorCode = 3;} // DonP 20090929 - Added in with gotos to save program space, to re-add battery reset redoreset: /* Set Battery Limits */ if (InitialBatteryVoltage == 0) {InitialBatteryVoltage = (GetVoltage(Drain,0));} // DonP 20090924 - Removed as part of repurposing this variable //if (HardBatteryVoltage != 0) {InitialBatteryVoltage = HardBatteryVoltage;} TriggerFiringVoltage = (InitialBatteryVoltage * TriggerFiringVoltagePercent) / 100; BatteryVoltageDroopWarning = (InitialBatteryVoltage * BatteryVoltageDroopWarningPercent) / 100; // DonP 20090929 - Added in with gotos to save program space, to re-add battery reset if(BatteryLimitReset==1) goto didreset; // DonP Note: This function sets 'LiPOInstalled=1' only if the monitor is present at startup // AND the battery is OK at startup (AUX0 and AUX1 = <300mV). Note this ONLY applies to the // WolfDragon li-po plug and play board! It has nothing to do with LiPOModeOption! LiPO = LiPOStatus(); // DonP 20090928 - not needed for production really, registers via extra-long startup vibe like for // all add-ons. // if( LiPOInstalled == 1 ) // DonP 20090522 - Added to register whether the Monitor was detected. // EventLog(11); //else // EventLog(12); // NOT detected (or present with a state other than 'BATT OK') // DonP 20090521 - Sense the magazine sensor if plugged into the AUX port (sensor Vout to Aux0) // DonP 20090818 - Used to be read 3x now just one to save a little program space. mV = GetAux0Voltage(); if( (mV > 2100) && (mV < 2900) ) { // Mag sensor is installed and active. MagSensorInstalled = 1; //EventLog(7); // Record "magazine sensor unit detected" //LiPOInstalled = 0; // Make sure this is 0, since the LiPO monitor board and this sensor // are mutually exclusive. Probably redundant since the LiPOStatus() // function SHOULD leave this at 0, but explicit for now. } //else { EventLog(9); } // Mag sensor not detected, or mag installed (so disable) // End DonP - Done auto-detect magazine sensor unit in AUX port. // DonP 20090526 - Sense whether the "Sniper Mode Lock-in" dongle is attached at powerup. // Dongle is a short between GND and AUX0 and a 50/50 voltage divider on AUX1. // If the gun powers up with this dongle installed, it will enter SNIPER/DM mode and // will refuse to change firing modes for approximately 12 hours. (~18k RunTimeCounter" overruns) // // SniperModeLockIn is also set to contents of lock byte in EEPROM 0x27 if it's non-0xFF. // (Set during ReadEEPROM() - in other words enforced if dongle not present now but was before and // hasn't expired yet.) mV = GetAux1Voltage(); if( (GetAux0Voltage()<300) && ((mV > 2100)&&(mV < 2900)) ) // Detect the dongle. (Less than 300mV on AUX0 (shorted to GND) and a voltage divider on AUX1 = dongle installed.) { // A short to AUX0 empirically tested as 136mV in a test done. So we pad it out. // // Pseudocode: Lock into Sniper/DM mode via setting some EEPROM byte which is only erased // after about 18-20k "RunTimeCounter" overruns. SniperModeLockIn = 1; // This is for the benefit of runtime calculations. EEPROM_write(0x27, 0x01); // Sniper mode lock-in byte engaged in EEPROM. //EventLog(8); // Actually move the gun to Sniper/DM mode (mode 5) // Use the same code and massaging needed when using programming mode. GunFunction = 3; // Sniper/DM mode forced (is semi-only with a shot delay) EEPROM_write(0x25, SNIPER_DM_MODE_SHOT_DELAY); // 'X' * 10ms = shot delay ShotDelay = EEPROM_read(0x25); // Set the runtime delay value (put EEPROM value into effect) EEPROM_write (0x01, (char)GunFunction); // Store gun function } //else { EventLog(10); } // Sniper Mode Lock-in dongle not present // // End DonP - detect Sniper Mode Lock-in dongle /* Vibrate status to motor */ count1 = ErrorCode; do { Timer(500); Vibrate(); } while (--count1); /*Factory Test */ //FactoryDiagnostics(); // DonP 20090217 - Removed (save program space) /* 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 18 Overheating */ /* Wait for trigger pull to enter programming mode (2 seconds) */ for (count=0; count < 1185; count++) { if (GetVoltage(Trigger,0) > LowBatteryVoltage) { // DonP added EventLog(5); // Event code for 'programming mode entered' ProgramEEPROM(); // ReadEEPROM() overrides 'BurstTime' if we boot in semi-auto which is causes // 1 shot bursts when going from SEMI-ONLY to HYBRID, unless a reboot. Semi-only // to SafeBurstFull works. Semi-only to SafeSemiBurst results in 1 shot bursts. // The gun can get "locked out" from making new timing measurements if this happens wrong. // (Burst time is ~= singleshottime which can prevent detecting semi at startup) // SafeBurstFull: Works because it specially overrides 'bursts' for the first 5 shots // and doesn't apply 'bursttime' at all, then sets SetBurstTime() with a good value. // The other modes don't get that far since they aren't recognized as semi-shots. // // Fixed this mainly by re-doing ReadEEPROM() after we do programming mode. ReadEEPROM(); } } /* Semi-Only Locked here From SemiOnlyLock definition */ if (SemiOnlyLock != 0) {GunFunction = 3;} // DonP 20090929 - Rewritten to use cell #s (not autodetection) // if LiPOModeOption is nonzero (and LiPO board not installed): // Set appropriate battery Droop and Cutoff levels according to the number of cells // in the battery pack (user specified in advanced configuration.) // // LiPOModeOption is 0,2,3,4 (disabled, 1cell, 2cell, 3cell, 4cell) if( (LiPOModeOption!=0) && (LiPOInstalled==0)) { switch( LiPOModeOption ) { /* case 1: LiPOModeOption = 0; // This should never happen, but if invalid # then disable it. //EventLog(15); brake; */ case 2: BatteryVoltageDroopWarning = TWO_CELL_LIPO_LOW; BatteryVoltageCutoff = TWO_CELL_LIPO_DEAD; //EventLog(13); break; case 3: BatteryVoltageDroopWarning = THREE_CELL_LIPO_LOW; BatteryVoltageCutoff = THREE_CELL_LIPO_DEAD; //EventLog(14); break; case 4: BatteryVoltageDroopWarning = FOUR_CELL_LIPO_LOW; BatteryVoltageCutoff = FOUR_CELL_LIPO_DEAD; //EventLog(16); break; default: LiPOModeOption = 0; // This should never happen, but if invalid # then disable it. //EventLog(15); break; } } /* MAIN SCANNING LOOP */ Vibrate();Vibrate();Vibrate();Vibrate(); // Extra long "long" powerup pulse means "LI-PO monitor detected and working" // 20090818 - Actually has turned into "plug-and-play device detected and enabled". // We give extra long startup pulse for anything detected on the AUX port. // Original: // if (LiPOInstalled != 0) {Vibrate();Vibrate();Vibrate();Vibrate();} // New: if( (LiPOInstalled!=0) || (MagSensorInstalled==1) || (SniperModeLockIn==1) ) {Vibrate();Vibrate();Vibrate();Vibrate();} MainLoop: ModeCounter++; DeadTimeCounter++; RunTimeCounter++; /* Set motor to off no matter what is going on */ MotorOFF(); // DonP 20090521 (IF magazine sensor was detected at startup...) // Monitor whether a magazine is inserted (technically, whether the magazine sensor has had // a magnetic field applied to it - either pole counts as long as it registers.) // // Update global 'MagPresent' once (and while) we have at least 3+ identical results in a row. // Also sets 'virtual_magazine_rounds_remaining' according to 'virtual_magazine_capacity', and // takes into account +1 in the chamber if mag change was done before last one was empty. // // (Don't actually have any evidence to suggest the sensor needs this filtering, but it's here anyway.) // if( MagSensorInstalled == 1 ) { prev_mag_sensor_reading = mag_sensor_reading; mV = GetAux0Voltage(); if( (mV > 2100) && (mV < 2900) ) mag_sensor_reading = 0; // 0=idle if( (mV < 1000) || (mV > 4000)) mag_sensor_reading = 1; // LOW (~0.2V) or HIGH (~4.7V) reading from sensor if( mag_sensor_reading == prev_mag_sensor_reading ) consecutive_reads++; else consecutive_reads=0; if( consecutive_reads >= 3 ) { // DonP 20090525 // Reload the mag if we now have a "MAG IN" state but the last known good state was "Mag OUT". if( (MagPresent==0) && (mag_sensor_reading==1) ) { if( (virtual_magazine_rounds_remaining>0) ) virtual_magazine_rounds_remaining = (virtual_magazine_capacity+1); // +1 in the chamber else virtual_magazine_rounds_remaining = virtual_magazine_capacity; } // DonP 20090525 // Force 1 second "dead time" when a magazine is removed. This prevents just popping the // same mag out-in to reload. As soon as it's "out" nothing is looked at again for 1 second. // therefore mag changes cause a minimum of 1 second no-fire time. if( (MagPresent==1) && (mag_sensor_reading==0) ) { Timer(1000); } // Update the main global variable for the mag sensor status, we are solidly in this state. MagPresent = mag_sensor_reading; } // If virtual magazines capacity has been disabled (VirtualMagNeverEmpties==1) then // effect that by forcing a "reload" of the virtual magazine here every time we are here. // (Note that bursts and semi-auto still decrement the virtual magazine rounds remaining, but // fullauto does not due to a wrapper that checks 'VirtualMagNeverEmpties' first. All other // situations are handled by just "reloading" the virtual magazine here every loop completion. // In this way it's a little clunky but saves program space and will NEVER empty the mag. if( VirtualMagNeverEmpties == 1 ) { virtual_magazine_rounds_remaining = 100; } // Some random, high number } // End mag sensor monitor maintenance /* Check trigger voltage */ TriggerVoltage = GetVoltage(Trigger,0); // DonP NOTE: Appears to handle the case of trigger pulled *but* under TriggerFiringVoltage while // battery voltage is still above what's considered "low (min)" (i.e. something that should not // normally happen). Causes immediate "low batt" warning. if ((TriggerVoltage < TriggerFiringVoltage) && (TriggerVoltage > LowBatteryVoltage)) {DroopWarningFlag1++; if (DroopWarningFlag1 > 5) {Vibrate(); Timer(1000); DroopWarningFlag1 = 0;} } // DonP NOTE: This is the normal "What happens when the trigger is (freshly) pulled" code. // Normally (no lipomonitor installed) if the Trigger is pulled and battery voltage is under the // droop warning amount (measured before turning the motor on, by the way) 3 or more times then // set errorcode to 16. // // That causes a low battery warning after the firing action is done. This will continue to happen // as long as the battery voltage continues to be under "BatteryVoltageDroopWarning". if ((TriggerVoltage >= TriggerFiringVoltage) && (TriggerHold == 0)) { if (LiPOInstalled == 0) // Remember this var refers strictly to the LiPO monitor daughterboard { // DonP 20090928 - here is the original code: // /* Check trigger voltage incase the battery is dying. Check three times to stop false warnings if (TriggerVoltage < BatteryVoltageDroopWarning) {DroopWarningFlag++;} else {DroopWarningFlag = 0; goto Fire;} if (DroopWarningFlag > 2) {ErrorCode = 16;} goto Fire; */ // // New version is the same except that it also includes a cutoff of power IF voltage level // dips below 'BatteryVoltageCutoff' (default 7.0V) which is overridden to an appropriate level // if LiPOModeOption was enabled (!=0) and LiPO type (2 or 3 cell; 7.4 or 11.1) was detected // at startup. // // For normal batteries it works exactly the same, except that the minimum voltage cutoff is // 7.0V. // Check trigger voltage against low battery threshold to detect dying battery. // Must happen 3 times in a row to turn on the low battery warning. // If the voltage level is ALSO below "BatteryVoltageCutoff" then instead enforce a cutoff // which is vibe only, no shoot first. // Cutoff is "sticky" in that we are not allowed to shoot again after hitting it. Low batt // is not. (Battery resting can raise voltage levels) if (TriggerVoltage < BatteryVoltageDroopWarning) {DroopWarningFlag++;} else {DroopWarningFlag = 0; goto Fire;} if (StickyBatteryCutoff==1) { ErrorCode=16; goto CeaseFire; } if (DroopWarningFlag > 2) { ErrorCode = 16; // Are we above the cutoff? Then go ahead and fire first before vibing. Otherwise // do not fire and vibe instead (and set the cutoff flag to prevent future firing). if( TriggerVoltage > BatteryVoltageCutoff ) { goto Fire; } else { StickyBatteryCutoff = 1; // "Lock" us into not firing due to cutoff. goto CeaseFire; } } else { goto Fire; } } else // Otherwise if LiPO daughterboard is installed then just do what it says { LiPO = LiPOStatus(); if (LiPO == 1) {DroopWarningFlag = 0; goto Fire;} // Batt OK DroopWarningFlag++; if ((LiPO == 3) && (DroopWarningFlag > 2)) {(ErrorCode = 16); goto CeaseFire;} // Batt dead (only vibe) if ((LiPO == 2) && (DroopWarningFlag > 2)) {ErrorCode = 16; goto Fire;} // Batt weak (fire, then vibe) goto Fire; } } if (TriggerVoltage < TrigTrip) {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 > 162) && (TriggerHold == 1) && (GunFunction != 3) && (AutoRoll == 1)) {GunMode = ThreeRoundAuto; goto Fire;} // DonP - 20090521; to track very roughly the total runtime if( RunTimeCounter > 1000 ) { RunTimeCounter=0; RunTimeCounterOverruns++; // Each overrun is about 2 seconds of idle runtime. } // DonP 20090521; disable the Sniper Mode Lockin if time is up and it's on if( (SniperModeLockIn==1) && (RunTimeCounterOverruns > SNIPER_MODE_LOCKIN_TIME) ) { // Do the unlock of Sniper mode EEPROM_write(0x27, 0xFF); // Persistent EEPROM lock cleared SniperModeLockIn = 0; // Runtime value updated // Gun may now have firing mode changed. } /* Reset Battery Limits after 300 triggers*/ // DonP 20090928 - Removed entirely. Commented out (now) unused 'BatteryLimitReset'. // We need the program space and according to the original code which only allowed this if // HardBatteryVoltage was nonzero (probably a typo) this actually NEVER got used in the past. // We have gotten by just fine without it so far so I'm axing it since the program space is needed. /* // DonP 20090217 - And DeadTimeCounter > 1000, which is empirically determined to be about 2 seconds. // 20090923 - removed the deadtimecounter part, not needed. // // DonP Note 20090923: this appears to reset battery limits if HardBatteryVoltage is enabled (!=0). // DroopWarning is recalculated as part of all this to be 85% of what battery is now (when recalculated). // Do we really want that? Was: "if ((BatteryLimitReset == 0) && (FiringCounter > 300) && (HardBatteryVoltage != 0))" // // ** Decided we don't, changed it to only reset battery limits if HardBatteryVoltage is disabled (==0). // // What this functionality does is reset battery limits ONCE only after about 300 triggers. // The idea being after the battery has "settled". // DonP 20090924 - Changed 'HardBatteryVoltage' to 'LiPOModeOption' (just a rename in the next line of code) // LiPOModeOption=0 is disabled/off (normal battery operation) if ((BatteryLimitReset == 0) && (FiringCounter > 300) && (LiPOModeOption == 0)) {InitialBatteryVoltage = (GetVoltage(Drain,0)); TriggerFiringVoltage = (InitialBatteryVoltage * TriggerFiringVoltagePercent) / 100; BatteryVoltageDroopWarning = (InitialBatteryVoltage * BatteryVoltageDroopWarningPercent) / 100; EventLog(4); BatteryLimitReset = 1; } */ // DonP 20090929 - Added in with gotos to save program space. if ((BatteryLimitReset == 0) && (FiringCounter > 300) && (LiPOModeOption == 0)) { EventLog(4); BatteryLimitReset = 1; InitialBatteryVoltage=0; goto redoreset; } didreset: /* Return to top and loop again */ goto MainLoop; /* FIRING LOOP */ /* If we are here, the trigger was detected as pulled while gun was otherwise idle. */ Fire: // DonP 20090521 - Do not fire if Mag Sensor is installed, was detected, AND there is no mag. // OR if there is a mag installed but the virtual magazine is "empty". // If any of that's true we ignore the trigger pull completely and wait till it's released. if( ((MagSensorInstalled==1) && (MagPresent==0) ) || ( (MagSensorInstalled==1) && (virtual_magazine_rounds_remaining<=0) ) ) { while( (TriggerVoltage >= TriggerHoldVoltage) ) // Wait till trigger released TriggerVoltage = GetVoltage(Trigger,1); goto MainLoop; } // End DonP if (OverTempLockout == 1) {goto CeaseFire;} /* MOTOR IS ON!!!!! */ MotorON(); Timer(1); /* A delay to avoid the motor start up electrical noise burst */ ShotCounter = 0; FullAutoLoopCounter = (0-SingleShotTime); // This counter is to count how long the gun has been firing in fullauto to help estimate shots. It is '-SingleShotTime' to cover for the initial decrement of rounds remaining in the firing loop (see below). SavedShotCounter = 0; // DonP 20090202 for SafeSemiFull FiringCounter++; /* increment firing loop counter */ // DonP 20090526 - Every trigger pull that results in motor on is at least 1 shot counted. // Bursts and Fullauto additional shots are tacked on later during the firing loop. // Note: This is the ONLY decrement that should ever apply to semi-only modes. if(MagSensorInstalled==1) { virtual_magazine_rounds_remaining--; } ShotDetect: /* Main firing loop. */ ShotCounter++; FullAutoLoopCounter++; TriggerVoltage = GetVoltage(Trigger,1); /* Get trigger voltage */ if (TriggerVoltage < TriggerHoldVoltage) /* Is trigger off now? */ { // DonP 20090217 - Updated Burst fire completion // Keep firing despite trigger release if ALL the following applies: // 0. Burst completion option is not disabled. // 1. Trigger is off (we are here, so it is) // 2. Current time spent firing (i.e. ShotCounter) is more than SingleShotTime + 25% // 3. But current time spent firing (i.e. ShotCounter) is also LESS THAN BurstTime // (In other words, trigger is down 25%+ longer than 1 shot, but less than a full burst) // 4. And we are in a qualifying fire mode (N.B. SafeBurstFull mode bursts are handled separately) // THEN complete the burst by going to ShotDetect instead of CeaseFire. // // In practice we don't dynamically determine SingleShotTime + 25% since that opens the door // to "completing" bursts when we're in semi-auto if the SingleShotTime gets eventually munged up // as a result of repeated quick-triggering (causing improper stacked -10% updates to // SingleShotTime) or something. // // So instead we calculate 'BurstCompletionThreshold' (default value = 'BurstTime' at powerup) // once inside SetBurstTime() [at 'Golden Shot'] and never touch it again. That way, worst // case scenario for screwed-up too-small SingleShotTime is no different than usual... BurstTime // is erroneous (and not 'fires[completes] burst in semi'). Burst completion will always require // more time than can be delivered while in semi-auto mode. // // NEWS: Burst completion was more trouble than expected. Original got screwed up too easily with // lots of quick semi-auto action which causes 2 things: // 1) Early trigger releases, could lead to -10% SingleShotTime drift // 2) Early releases leave piston in semi-fired state, therefore next trigger pull // is in fact 100% + ??% of SingleShotTime causing the next cycle to be off (which // can screw up SingleShotTime sensing as well as triggering of Burst Completion. // // However it's more a function of the user's trigger habits, etc so it has been added as an // option. I believe it is sacrificial - the users most likely to want burst completion (because // they let off the trigger early) are the ones most likely to screw it up as a result. // // Besides, real firearms cease firing in the middle of a burst if the trigger is released anyway. // if( DisableBurstCompletion == 0 ) { if( BurstCompletionThreshold < BurstTime ) // Enable check and Sanity check { // Complete bursts in SAFE-SEMI-BURST/FULL as well as SAFE-SEMI-BURST if( (ShotCounter>BurstCompletionThreshold) && (ShotCounter(SingleShotTime)) && (FastFirstShots==1) ) OCR0B = (char)UserMotorSpeed; // Restore user-programmed motor speed immediately // DonP 2009010 UPDATED - Count shots in fullauto (estimated with some laughable junk math) // Basically: Each time that time spent in fullauto is > (some singleshottime factor) then // rounds fired = +1 //if( (ShotCounter > (SingleShotTime+(SingleShotTime/2)) ) ) //if( (MagSensorInstalled==1) && (FullAutoLoopCounter>(SingleShotTime+10)) ) if( (MagSensorInstalled==1) && (FullAutoLoopCounter>(SingleShotTime-8)) ) { // 20090910 - Wrapped a 'if VirtualMagNeverEmpties==0' around the decrement then // ceasefire if empty just to totally ensure the magazine never ever empties no // matter how long the fullauto lasts. if( VirtualMagNeverEmpties==0 ) { virtual_magazine_rounds_remaining--; if(virtual_magazine_rounds_remaining<=0) { goto CeaseFire; } } FullAutoLoopCounter=0; // This is a 'ShotCounter' but just for fullauto mode. } goto ShotDetect; /* Keep cycling in auto modes (track those variables to tell how, it's not pretty.) */ } if (ShotCounter > BurstTime) { // DonP 20090526 - To count the other 2 rounds in a burst prior to stopping for the // burst/full delay. Tack on some code so we *never* hit this during semi-auto // firing modes (which otherwise can happen since bursttime is the singleshottime in semi modes.) // Decrement is only 2 because we start the firing action off with a decrement. if((MagSensorInstalled==1)&&(GunFunction!=3)) { virtual_magazine_rounds_remaining-=2; } TriggerHold = 1; goto CeaseFire; /* Three round time delay wait */ } goto ShotDetect; /* FIRING HALT */ /* Ways to get to CEASEFIRE code: 1. We entered FIRE while in an overtemperature lockout condition. 2. Trigger down and firing, but burst time is up in hybrid mode (we note that trigger is still held) 3. Trigger is open, but we are in SafeBurstFull and completed the burst (while in semi). 4. We released the trigger while gun was firing in AUTO. 5. Trigger is open and released (such as by cutoff lever in semi) before bursttime was up but no burstcompletion was applied. */ CeaseFire: /* The motor ON and OFF functions have a 1mS anti cross conduction safety delay built in */ /* Turn motor to off */ MotorOFF(); /* Turn Brake to on */ // if( ActiveBrakingEnable == 1 ) // DonP 20080904 - Skip Active Braking if disabled // { // ActiveBrakingDisable = ShotDelayEndVibeEnable ActiveBrakingDisable = ShotDelayEndVibeEnable; // default is 0 (off) if( ActiveBrakingDisable == 0 ) // If not disabled, then brake { BrakeON(); } // } /* Leave the Brake on for some time in mS */ Timer(BrakeTime); /* Turn Brake to off */ BrakeOFF(); // DonP 20080904 - To effect Sniper/DM mode shot delay // Delay an amount of time equal to (ShotDelay x 10) milliseconds. if((ShotDelay > 0) && (FiringCounter>=5)) // Only delay if past the 'sync' shots { Timer(ShotDelay*10); // DonP 20090217 - Added a quick vibe to signal end of delay, if enabled /* if( ShotDelayEndVibeEnable == 1 ) { for (counter2 = 5; counter2 > 0; --counter2) { PORTB = Motor; FastTimer(20); // Wait 500uS for motor on pulse PORTB = Neutral; Timer(25); } } */ } /* If in semi mode, set burst time */ // DonP 20090202 - Added "And not in SafeBurstFull mode" // This way we never set burst time with a 'ShotCounter' that might be invalid due to // overriding in semi mode. We instead have entered SetBurstTime() within the override of semi // in the actual shot loop right before CeaseFire with the proper value. Not the cleanest code. // if ((TriggerHold == 0) && (GunFunction!=SafeBurstFull)) { SetBurstTime(ShotCounter); } /* Check unit temperature */ //Not used on ATMEGA1284P but kept code for timing stabiity RunningTemp = GetVoltage(Ground,2); // Null value now RunningTemp = (RunningTemp - 298) * 100 / 109; if (RunningTemp >= TemperatureLimit) {EventLog(3); OverTempLockout = 1;} if (RunningTemp <= (TemperatureLimit - 5)) {OverTempLockout = 0;} if (OverTempLockout == 1) {ErrorCode = 18;} ModeCounter = 0; DeadTimeCounter = 0; /*Maximum Temperature */ if (RunningTemp > TemperatureMAX) { TemperatureMAX = RunningTemp; EEPROM_write(0x10, (char)TemperatureMAX); } /*Maximum Current */ if (DrainCurrent / 10 > CurrentMAX) { CurrentMAX = DrainCurrent / 10; EEPROM_write(0x11, (char)CurrentMAX); } /* 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 18 - Overheating - Vibrate motor three times */ Timer(100); if (ErrorCode == 16) {Vibrate1(1);} if (ErrorCode == 17) {Vibrate1(2);} if (ErrorCode == 18) {Vibrate1(3);} 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) { long counter6 = 0; /* Motor softstart counter */ long RampSpeed = 0; /* MotorRampSpeed variable */ PORTB = Neutral; /* Be REALLY(!!) sure Brake is off */ Timer(1); /* Wait 1mS for anti-cross conduction */ PORTB = Motor; /* motor is on for peak current test */ Timer(1); /* Wait to stabalize */ /* Check drain current for over peak current limit */ DrainCurrent = GetVoltage(Drain,1) * 1000 / RDSon; PORTB = Neutral; /* motor is off after test */ if (DrainCurrent > DrainCurrentPeakLimit) {PORTB = Neutral; EventLog(2); ErrorCode = 17; return;} /* Shutdown if overcurrent */ MotorPWM(1); // Override to 100% motor speed if option enabled (it is downgraded to proper user value // of 'UserMotorSpeed' during fullauto fire). if( FastFirstShots==1 ) { MotorSpeed=254; } /* Soft Start */ if (SoftStartEnable == 1) { for (counter6 = 0; counter6 < 51; counter6++) { RampSpeed = MotorSpeed / 2 + (MotorSpeed * counter6) / 100; OCR0A = (char)RampSpeed; } } else { RampSpeed = MotorSpeed / 2 + (MotorSpeed * counter6) / 100; OCR0A = (char)MotorSpeed; } } void BrakeOFF(void) {PORTB = Neutral;} /* The Brake signal is negative logic */ void BrakeON(void) { PORTB = Neutral; /* Be REALLY(!!) sure motor is off */ Timer(1); /* Wait 1mS for anti-cross conduction */ PORTB = Brake; /* The Brake signal is negative logic */ } void Timer(long Time) /* Timer(100) = 100mS */ { long TimeFactor = 100; /* Adjust for 1mS step. Magically, the calibration factor = 100 */ Time = Time * TimeFactor; for (TimerCounter = 0; TimerCounter <= Time; TimerCounter++) {} } void FastTimer(long Time) /* Timer(50) = 672uS */ { long TimeFactorFast = 1; /* A sub mS timer counter */ Time = Time * TimeFactorFast; for (TimerCounter = 0; TimerCounter <= Time; TimerCounter++) {} } void EnergizeFETs(void) { /* Energize brake circuit */ /* Brake is on PB4 */ /* 2N changed this! */ PORTB = 0x00; /* Set pullups off */ DDRB = 0b00010000; /* Set port PB4 for output */ PORTB = Neutral; /* Set port B pin 0 to 0 */ /* Wait 1000mS for capacitive Brake circuit to stabilize (very important!!) - Not used now though */ Timer(1000); /* Energize motor circuit */ /* Motor is on PB3 */ DDRB = 0b00011000; /* Set port PB4 and PB3 for output */ PORTB = Neutral; /* Set ports PB4 and PB3 to 0 */ /* Wait 1mS */ Timer(1); } void SetAUXPorts(void) { /* Set AUX ports */ /* Turn on pullups for Aux0 and Aux1. Set motor off and Brake off */ /* Pullups are set in the Nuetral, Brake, and Motor definitions. */ DDRA = 0b00000000; // PORTB = Neutral; } //A/D Convertor Control long GetVoltage(unsigned char Sensor, unsigned char ADCRange) { ADCSRA = 0b10000011; // Setup A/D 125kHz clock long Voltage = 0; /* A/D convertor returned voltage in mV */ /* Select input Source */ if (ADCRange == 0) /* 0-5V or 0-20V range */ { if (Sensor == Aux0) {ADMUX = 0b01000100;} if (Sensor == Aux1) {ADMUX = 0b01000101;} if (Sensor == Aux2) {ADMUX = 0b01000110;} if (Sensor == Drain) {ADMUX = 0b01000010;} if (Sensor == Trigger) {ADMUX = 0b01000011;} if (Sensor == Ground) {ADMUX = 0b01011111;} } if (ADCRange == 1) /* 0-2.56V or 0-10.24V range */ { if (Sensor == Aux0) {ADMUX = 0b11000100;} if (Sensor == Aux1) {ADMUX = 0b11000101;} if (Sensor == Aux2) {ADMUX = 0b11000110;} if (Sensor == Drain) {ADMUX = 0b11000010;} if (Sensor == Trigger) {ADMUX = 0b11000011;} if (Sensor == Ground) {ADMUX = 0b11011111;} } if (ADCRange == 2) /* 0-1.1V or 0-4.4V range */ { if (Sensor == Aux0) {ADMUX = 0b10000100;} if (Sensor == Aux1) {ADMUX = 0b10000101;} if (Sensor == Aux2) {ADMUX = 0b10000110;} if (Sensor == Drain) {ADMUX = 0b10000010;} if (Sensor == Trigger) {ADMUX = 0b10000011;} if (Sensor == Ground) {ADMUX = 0b10011111;} } /* Start A/D conversion */ ADCSRA = ADCSRA | 0b01000000; //Start A/D conversion (bit 6 is set) while (ADCSRA & 0b01000000) {;} // Test if done or wait Voltage = ADCW; /* This word is the 10 bit A/D result */ if ((Sensor == Drain) || (Sensor == Trigger)) // Rdivider = 1/4 { if (ADCRange == 0) // 20.0V { Voltage = ((Voltage * 20000) / 1023); /* Convert A/D full range (1023) to 20000mV or 20.0V */ } if (ADCRange == 1) // 10.23V { Voltage = ((Voltage * 10240) / 1023); /* Convert A/D full range (1023) to 10240mV or 10.24V */ } if (ADCRange == 2) // 4.4V { Voltage = ((Voltage * 4400) / 1023); /* Convert A/D full range (1023) to 4400mV or 4.400V */ } } else // No voltage divider { if (ADCRange == 0) // 5.0V { Voltage = ((Voltage * 5000) / 1023); /* Convert A/D full range (1023) to 5000mV or 5.0V */ } if (ADCRange == 1) // 2.56V { Voltage = ((Voltage * 2560) / 1023); /* Convert A/D full range (1023) to 2560mV or 2.56V */ } if (ADCRange == 2) // 1.1V { Voltage = ((Voltage * 1100) / 1023); /* Convert A/D full range (1023) to 1100mV or 1.100V */ } } /* Fix for Lion's R7 hardware resistor */ if (Sensor == Trigger) {Voltage = Voltage * 5 / 4;} 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 */ /* 1.1V REF Range = 1.1V */ VoltageOffset2 = 0; /* Clear old value */ VoltageOffset2 = GetVoltage (Ground, 2); /* Run the converter with grounded input */ VoltageOffset2 = ADCW; /* Get the raw A/D converter output word */ } void Vibrate(void) { long counter2 = 10; /* Counter for Vibrate function */ /* Vibrate for 1/2 second */ BrakeOFF(); Timer(1); do { PORTB = Motor; FastTimer(20); /* Wait 500uS for motor on pulse */ PORTB = Neutral; Timer(25); } while(--counter2); } void Vibrate1(unsigned char VibCount) /* Vibrate multiple times */ { do { Vibrate(); Timer(250); } while (--VibCount); } void Vibrate2(unsigned char VibCount) /* Vibrate multiple times fast*/ { do { Vibrate(); Timer(50); } while (--VibCount); } /* 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< LowBatteryVoltage)) && (TriggerHold == 0)) { ModeSelectFlag++; if (ModeSelectFlag > 12) {ModeSelectFlag=12;} // This is the max # of pulls recognized count = 0; TriggerHold = 1; } } if (ModeSelectFlag>0) { if (ModeSelectFlag > 1) {Vibrate1(ModeSelectFlag-1);} Vibrate(); if (ModeSelectFlag==1) { // MODE 1 *START* GunFunctionFlag = 0; for (count=0; count < 600; count++) { if (GetVoltage(Trigger,0) < TrigTrip) {TriggerHold = 0;} /* Trigger hold is a debounce for the trigger switch */ if (((GetVoltage(Trigger,0) > LowBatteryVoltage)) && (TriggerHold == 0)) { // DonP 20080904 - Added for feedback (but only if within legal range of 1-6, or 10 exactly) // DonP 20090618 - Changed from 10 to 20 for semi-locking if( (GunFunctionFlag < 6) || (GunFunctionFlag==19) ) // '19' is about to be 20 { Vibrate(); } GunFunctionFlag++; // Inc this anyway.. we limit it to 6 (or allow it to be 20) below GunFunction = GunFunctionFlag; // Limit mode to 6 max, (or allow to be 20 exactly) if ( (GunFunctionFlag > 6) && (GunFunctionFlag != 20) ) { GunFunction = 6; } count = 0; TriggerHold = 1; } } } // MODE 1 *END* if (ModeSelectFlag==2) { // MODE 2 *START* /* Pull Trigger now to go to decrease BurstTimeFactor by number of trigger pulls. Limit = 20*/ for (count=0; count < 600; count++) { if (GetVoltage(Trigger,0) < TrigTrip) {TriggerHold = 0;} /* Tigger hold is a debounce for the trigger switch */ if (((GetVoltage(Trigger,0) > LowBatteryVoltage) && (TriggerHold == 0) && (BurstTimeFactor > 0))) { if( BurstTimeFactor > 20 ) // DonP 20080911 - To stop vibing when it "bottoms out" { Vibrate(); // DonP 20080904 - For feedback BurstTimeFactor-=2; // DonP 20080904 - Changed to be -2 instead of -- } count = 0; TriggerHold = 1; } } } // MODE 2 *END* if (ModeSelectFlag==3) { // MODE 3 *START* /* Pull Trigger now to go to increase BurstTimeFactor by number of trigger pulls. Limit = 250 */ for (count=0; count < 600; count++) { if (GetVoltage(Trigger,0) < TrigTrip) {TriggerHold = 0;} /* TiggerHold is a debounce for the trigger switch */ if (((GetVoltage(Trigger,0) > LowBatteryVoltage) && (TriggerHold == 0) && (BurstTimeFactor < 250))) { if( BurstTimeFactor < 250 ) // DonP 20080911 - To stop vibing when it "tops out" { Vibrate(); // DonP 20080904 - For feedback BurstTimeFactor++; } count = 0; TriggerHold = 1; } } } // MODE 3 *END* if (ModeSelectFlag==4) { // MODE 4 *START* /* Pull Trigger now to go to decrease MotorSpeed by number of trigger pulls. Limit = 52*/ for (count=0; count < 600; count++) { if (GetVoltage(Trigger,0) < TrigTrip) {TriggerHold = 0;} /* Tigger hold is a debounce for the trigger switch */ if (((GetVoltage(Trigger,0) > LowBatteryVoltage) && (TriggerHold == 0) && (MotorSpeed > 0))) { if(MotorSpeed > 52) // DonP 20080911 - To stop vibing when it "bottoms out" { Vibrate(); // DonP 20080904 - For feedback MotorSpeed = (MotorSpeed * 9) / 10; } count = 0; SBTCounter = 0; TriggerHold = 1; } } } // MODE 4 *END* if (ModeSelectFlag==5) { // MODE 5 *START* /* Pull Trigger now to go to increase MotorSpeed by number of trigger pulls. Limit = 254 */ for (count=0; count < 600; count++) { if (GetVoltage(Trigger,0) < TrigTrip) {TriggerHold = 0;} /* TiggerHold is a debounce for the trigger switch */ if (((GetVoltage(Trigger,0) > LowBatteryVoltage) && (TriggerHold == 0) && (MotorSpeed < 255))) { if (MotorSpeed < 232) { Vibrate(); // DonP 20080904 - For feedback MotorSpeed = (MotorSpeed * 11) / 10; count = 0; SBTCounter = 0; TriggerHold = 1; } else { MotorSpeed = 254;count = 0; SBTCounter = 0; TriggerHold = 1; } } } } // MODE 5 *END* if (ModeSelectFlag==6) { // MODE 6 *START* // Pull Trigger now to select ShotDelayEndVibe On/Off when in Sniper/DM mode // No pull = No change. 1 = ShotDelayEndVibe On, 2 = ShotDelayEndVibe Off ShotDelayEndVibeEnable=0; for (count=0; count < 600; count++) { if (GetVoltage(Trigger,0) < TrigTrip) {TriggerHold = 0;} /* TiggerHold is a debounce for the trigger switch */ if (( (GetVoltage(Trigger,0) > LowBatteryVoltage) && (TriggerHold == 0) )) { if (ShotDelayEndVibeEnable < 2) { Vibrate(); // DonP 20080904 - For feedback ShotDelayEndVibeEnable++; count=0; TriggerHold=1; } else { count=0; TriggerHold=1; } } } } // MODE 6 *END* if (ModeSelectFlag==7) { // MODE 7 *START* // Pull Trigger now to select BurstCompletion On/Off (when in burst/full or burst-only mode) // No pull = No change. 1 = BurstCompletion On, 2 = BurstCompletion Off (Default) DisableBurstCompletion=0; for (count=0; count < 600; count++) { if (GetVoltage(Trigger,0) < TrigTrip) {TriggerHold = 0;} /* TiggerHold is a debounce for the trigger switch */ if (( (GetVoltage(Trigger,0) > LowBatteryVoltage) && (TriggerHold == 0) )) { if (DisableBurstCompletion < 2) { Vibrate(); DisableBurstCompletion++; count=0; TriggerHold=1; } else { count=0; TriggerHold=1; } } } } // MODE 7 *END* // MODE 8 IS AT END if (ModeSelectFlag==9) { // MODE 9 *START* // // Gun has entered TEST MODE for the Magazine Sensor Unit (if installed) // It will remain here and never exit. Other options changed before this in the // same programming session will not be saved. // // Gun will vibrate while the Magazine Sensor Unit (hall effect sensor IC with Vout // on AUX0) has detected a magnet within acceptable proximity. // // Gun will be "silent" when no magnet is sensed. // // Gun will immediately and forever pulse once per 0.5 seconds as an error signal // if the Magazine Sensor Unit was not detected at powerup. // if( MagSensorInstalled == 0 ) { Timer(2000); for(;;) { Vibrate(); Timer(500); } } for(;;) { count = GetAux0Voltage(); // Risky to commandeer this variable but since this is // a dead-end function we have no risk of fubar'ing // something that depends on it. if( (count > 2200) && (count < 2750) ) Timer(50); // Nothing (idle) if( (count < 500) ) Vibrate(); // 1=LOW (~0.2V) reading from sensor (mag present) if( (count > 4500) ) Vibrate(); // 2=HIGH (~4.7V) reading from sensor (mag present) } } // MODE 9 *END* if (ModeSelectFlag==10) { // MODE 10 *START* virt_mag_disable_flag = 1; // Begin by marking it 'disabled' if we entered this option for (count=0; count < 600; count++) { if (GetVoltage(Trigger,0) < TrigTrip) {TriggerHold = 0;} /* TiggerHold is a debounce for the trigger switch */ if ( ((GetVoltage(Trigger,0) > LowBatteryVoltage) && (TriggerHold == 0) )) { // NOTE: This is the ONLY option where '0' (no action) is something other // than 'No Change'. Zero trigger pulls disables virtual magazines (but leaves // the sensor functional if installed.) virt_mag_disable_flag = 0; // A trigger pull of any kind disables the 'disable' flag (clunky, I know!) // Max 100 total (20 trigger pulls) if (virtual_magazine_capacity_multiplier < 20) { Vibrate(); // DonP 20080904 - For feedback virtual_magazine_capacity_multiplier++; } count = 0; TriggerHold = 1; } } } // MODE 10 *END* if (ModeSelectFlag==11) { // MODE 11 *START* // Trigger is pulled to set "First Shot/Burst Speed override to 100%" on/off // No pull = no change, 1 pull = on, 2 pulls = off (default) for (count=0; count < 600; count++) { if (GetVoltage(Trigger,0) < TrigTrip) {TriggerHold = 0;} /* TiggerHold is a debounce for the trigger switch */ if ( ((GetVoltage(Trigger,0) > LowBatteryVoltage) && (TriggerHold == 0) )) { if (FastFirstShotsChange < 2) // max 2 pulls { Vibrate(); // For feedback FastFirstShotsChange++; } count = 0; TriggerHold = 1; } } } // MODE 11 *END* // DonP 20090924 - LiPOModeOption setting if (ModeSelectFlag==12) { // Mode 12 *START* // Trigger is pulled to turn LiPOModeOption on or off // No pull = no change, 1 pull = OFF (set to 0), 2-4 pulls = # of cells (store the number) for (count=0; count < 600; count++) { if (GetVoltage(Trigger,0) < TrigTrip) {TriggerHold = 0;} /* TiggerHold is a debounce for the trigger switch */ if ( ((GetVoltage(Trigger,0) > LowBatteryVoltage) && (TriggerHold == 0) )) { if (LiPOModeOptionChange < 4) // max 4 pulls { Vibrate(); // For feedback LiPOModeOptionChange++; } count = 0; TriggerHold = 1; } } } // Mode 12 *END* /* Store EEPROM data */ // Here we do any housekeeping or other pre-fiddling of stuff that is needed prior to writing // the values out into EEPROM. // DonP 20090217 if( GunFunctionFlag != 0 ) // To fix a bug where re-entering pgm mode turns existing sniper/dm mode { // into just semi-only. We should only process this if GunFunctionFlag is // nonzero (meaning we touched the trigger at all for option 1.) // DonP 20090523 - First of all, if SniperModeLockIn is active then prevent any // attempt to change firing mode by overriding to "5" (Sniper/DM mode) if( SniperModeLockIn == 1 ) {GunFunction = 5;} // DonP 20080904 - Process SEMI LOCK (if entered) if( GunFunction == 20 ) // DonP 20090618 - Changed from 10 to 20 { EEPROM_write(0x20, 0x02); // PERMANENTLY program EEPROM byte for gun into SEMI LOCKED mode. GunFunction = 3; // Put gun into runtime SEMI mode (before we commit it to EEPROM below) } // DonP 20080904 - Process BURST ONLY mode (if selected) // Burst only mode is really just burst mode (mode 2) with the AutoRoll EEPROM config OFF if( GunFunction == 4 ) { EEPROM_write(0x21, 0x00); // AutoRoll config byte = autoroll OFF AutoRoll = 0; // Runtime autoroll OFF GunFunction = 2; // Override to Burstmode } else { // Ensure autoroll is (back) ON if we're NOT processing a selected MODE 4 (Burstonly) if (EEPROM_read(0x21) != 0xFF) // If not already at default { EEPROM_write(0x21, 0xFF); // AutoRoll reset (default on) AutoRoll = 1; } } // DonP 20080904 - Process SNIPER/DM mode if selected // Sniper/DM mode is really just Semi-only mode with a shot delay set to non-zero if( GunFunction == 5 ) { EEPROM_write(0x25, SNIPER_DM_MODE_SHOT_DELAY); // 'X' * 10ms = the dead-time delay after each firing event ShotDelay = EEPROM_read(0x25); // This is the run-time value, normally populated by ReadEEPROM() // We must do it here since we don't necessarily reboot after programming. GunFunction = 3; // Semi-only mode } else { // Ensure NO shot delay if we're in any other mode if (EEPROM_read(0x25) != 0xFF) // If not already at default { EEPROM_write(0x25, 0xFF); // Shot delay config byte to default (off) ShotDelay = 0; // Runtime shot delay to 0. } } } /* Gun Function */ EEPROM_write (0x01, (char)GunFunction); /* Burst time factor */ if (BurstTimeFactor < 20) {BurstTimeFactor = 20;} // DonP 20080904 - Changed '50' to '20' if (BurstTimeFactor >250) {BurstTimeFactor = 250;} EEPROM_write(0x02, (char)BurstTimeFactor); /* MotorSpeed */ if (MotorSpeed < 52) {MotorSpeed = 52;} if (MotorSpeed >254) {MotorSpeed = 254;} EEPROM_write(0x04, (char)MotorSpeed); InitialBatteryVoltage = (GetVoltage(Drain,0)); //BatteryVoltageEEPROM = (char)(InitialBatteryVoltage / 100); // DonP 20090928 - removed the above and put directly in below instead of 2 separate steps for // a little optimizing. The variable BatteryVoltageEEPROM was never used anywhere else. EEPROM_write(0x05, (char)(InitialBatteryVoltage / 100)); // DonP 20080904 - for ShotDelayEndVibeEnable if( ShotDelayEndVibeEnable==1 ) { EEPROM_write(0x24, 0x01); ShotDelayEndVibeEnable=1; // On } if( ShotDelayEndVibeEnable==2 ) { EEPROM_write(0x24, 0x00); ShotDelayEndVibeEnable=0; // Off } // End DonP for ShotDelayEndVibe // DonP 20091021 - Removed ability to change (enable) Burstcompletion to make room for // improved LiPO mode cutoff handling. /* // DonP 20090223 - DisableBurstCompletion if( DisableBurstCompletion==1 ) // Burstcompletion On (one trigger pull) { EEPROM_write(0x26, 0x00); DisableBurstCompletion = 0; } if( DisableBurstCompletion==2 ) // Burstcompletion Off (two trigger pulls) { EEPROM_write(0x26, 0x01); DisableBurstCompletion = 1; } // End DonP for DisableBurstCompletion */ // Sniper lock-in byte (0x27) does not need to be written here if(virt_mag_disable_flag == 1) { // NOTE AGAIN: This is the ONLY option where no input does NOT mean "no change". // No input (virtual_magazine_capacity_multiplier == 0) means disable! // The appropriate settings are in ReadEEPROM() for disabling, etc. // 20090910 - DonP EEPROM_write(0x28, (char)0x00); } // DonP 20090525 - Write the multiplier to modify virtual magazine capacity (multiples of 5) else if( virtual_magazine_capacity_multiplier > 0) { EEPROM_write(0x28, (char)virtual_magazine_capacity_multiplier); virtual_magazine_capacity = (virtual_magazine_capacity_multiplier * 5); } // DonP 20090527 - To enable/disable/unchange FastFirstShots feature if( FastFirstShotsChange > 0 ) { if ( FastFirstShotsChange == 1 ) // Enable { EEPROM_write(0x29, 0x01); } else //if ( FastFirstShotsChange == 2 ) // Disable. /* Saves a bit of program space cause we're using a 'change' flag. */ { EEPROM_write(0x29, 0x00); } } // DonP 20090924 - To enable/disable/unchange LiPOModeOption feature // No pull = no change, 1 pull = OFF (set to 0), 2-4 pulls = # of cells (store the number) if( LiPOModeOptionChange > 0 ) { if ( LiPOModeOptionChange == 1 ) // Disable { EEPROM_write(0x23, 0x00); } else { EEPROM_write(0x23, (char)LiPOModeOptionChange); // 2-4 (representing # of cells) } } if (ModeSelectFlag==8) { // MODE 8 *START* /* Pull and hold Trigger now to go to Master Reset */ counter5 = 0; for (count=0; count < 600; count++) { while (GetVoltage(Trigger,0) > LowBatteryVoltage) { counter5++; if (counter5 > 1500) { EventLog(6); // Factory reset performed code EEPROM_write(0x00,0xFF); EEPROM_write(0x01,0xFF); EEPROM_write(0x02,0xFF); EEPROM_write(0x03,0xFF); EEPROM_write(0x04,0xFF); EEPROM_write(0x05,0xFF); EEPROM_write(0x24,0xFF); // DonP 20080904 - For ShotDelayEndVibeEnable reset EEPROM_write(0x21,0xFF); // DonP 20080904 - To reset AUTOROLL to default (on) EEPROM_write(0x25,0xFF); // DonP 20080904 - To reset shot delay. EEPROM_write(0x26,0xFF); // DonP 20090223 - To reset DisableBurstCompletion // We do not factory reset 0x27 (Sniper Mode Lock-in) EEPROM_write(0x28,0xFF); // DonP 20090525 - Reset virtual mag capacity EEPROM_write(0x29,0xFF); // DonP 20090527 - Reset FastFirstShots Vibrate();Vibrate();Vibrate();Vibrate();Vibrate();Vibrate();Vibrate();Vibrate();Vibrate();Vibrate(); for(;;){} } } } } // MODE 8 *END* } } while (ModeSelectFlag != 0); } void ReadEEPROM(void) { /* Note - Erased EEPROM = FF not 00 */ /* 0x00 = Version Number 0x01 = GunFunction 0x02 = BurstTimeFactor 0x03 = SingleShotTime 0x04 = Motor Speed 0x05 = Initial Battery Voltage / 100 0x10 = Maximum Temperature 0x11 = Maximum Current / 10 0x20 = SemiOnlyLock 0x21 = AutoRoll 0x22 = SoftStartEnable // Used to be '0x23 = HardBatteryVoltage / 100' (DonP 20090924) 0x23 = LiPOModeOption 0x24 = ShotDelayEndVibeEnable // DonP 20090217 0x25 = ShotDelay // DonP 20080904 (delay after each firing action, used for Sniper/DM mode) 0x26 = BurstCompletion // DonP 20090223 0x27 = Sniper Mode Locked In // DonP 20090522 0x28 = Virtual Magazine Capacity (multiples of 5, 0=disabled) // DonP 20090525 0x29 = Motor speed downgrade enable after first shot/burst // DonP 20090527 */ /* 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; BurstTimeFactorPercent = 2 * BurstTimeFactor + 100; BurstTime = (BurstTimeFactorPercent * 117 * SingleShotTime) / 120 / 100 + (37 * (BurstTimeFactorPercent - 100) / 120); //if (GunFunction == 3) {BurstTime = SingleShotTime;} /* Semi only mode */ } if (GunFunction == 3) {BurstTime = SingleShotTime;} /* Semi only mode */ // // BUGFIX - DonP 20080911 // This line used to live above. The original has been commented out and it has been copied here. // If SingleShotTime has never been updated (i.e. SetBurstTime() has never run because the gun // has never fired in semi-auto) then the line overriding the burst time in Semi-Auto only mode // (or in Semi-Lock) will never actually run. // // Result: Trigger pulls on a fresh install on a gun only in full-auto will fire default // bursttime value 3-round bursts on every trigger pull. ///////////////////////////////////////////////////////////////////////////////////////////// /* Motor Speed */ MotorSpeed = EEPROM_read(0x04); /* 0x05 = Initial Battery Voltage / 100 */ if (EEPROM_read(0x05) != 0xFF) {InitialBatteryVoltage = EEPROM_read(0x05) * 100;} /* Maximum Temperature */ if (EEPROM_read(0x10) != 0xFF) {TemperatureMAX = EEPROM_read(0x10);} /* Maximum Current */ if (EEPROM_read(0x11) != 0xFF) {CurrentMAX = EEPROM_read(0x11);} // DonP 20090217 if (EEPROM_read(0x24) != 0xFF) {ShotDelayEndVibeEnable = EEPROM_read(0x24);} // DonP 20080904 if (EEPROM_read(0x25) != 0xFF) {ShotDelay = EEPROM_read(0x25);} // This number x 10ms = delay // DonP 20080923 if (EEPROM_read(0x26) != 0xFF) {DisableBurstCompletion = EEPROM_read(0x26);} /* Lock Flags */ if (EEPROM_read(0x20) != 0xFF) {SemiOnlyLock = EEPROM_read(0x20);} if (EEPROM_read(0x21) != 0xFF) {AutoRoll = EEPROM_read(0x21);} if (EEPROM_read(0x22) != 0xFF) {SoftStartEnable = EEPROM_read(0x22);} // DonP 20090924 - used to be HardBatteryVoltage and set to contents of 0x23 * 100 if (EEPROM_read(0x23) != 0xFF) {LiPOModeOption = EEPROM_read(0x23);} // DonP 20090522 if (EEPROM_read(0x27) != 0xFF) {SniperModeLockIn = EEPROM_read(0x27);} // DonP 20090525 if (EEPROM_read(0x28) == 0x00) { VirtualMagNeverEmpties = 1; virtual_magazine_capacity = 100; } else if (EEPROM_read(0x28) != 0xFF) {virtual_magazine_capacity = (EEPROM_read(0x28) * 5); } // default is 30 // DonP 20090527 if (EEPROM_read(0x29) != 0xFF) {FastFirstShots = EEPROM_read(0x29); UserMotorSpeed=MotorSpeed; } } void SetBurstTime(long ShotCounter) { SBTCounter++; /* Shot Three after gun is powered up is the golden shot */ // // DonP: That means that after shot #3, values will only be updated if they fall between +/10% of // it. // // 20090910 - This value (shot #3) can be bad, for example if in Normal mode and it leads to // a FA burst shotcounter being used as the SingleShotTime. Should ignore the [new] value if it's // 254 or more... (Since 0xFE is the maximum that can be written to EEPROM as a char) // // ORIGINAL: if (SBTCounter == 3) if( SBTCounter == 3 ) { // 20090911 - 250 is really a high number, but this wrapper prevents using terrible out of spec values such as from a FA burst if( ShotCounter < 250 ) { SingleShotTime = ShotCounter; } // else SingleShotTime = unchanged // DonP 20090217 - Set 'BurstCompletionThreshold' once at this time to SingleShotTime + 25% BurstCompletionThreshold = (SingleShotTime*125/100); } /* Do not update (top up after golden shot) if ShotCounter is < 90% or > 110% of SingleShotTime (early user trigger release) */ if (SBTCounter > 3) { if ((ShotCounter > (SingleShotTime * 11) / 10) || (ShotCounter < (SingleShotTime * 9) / 10)) {return;} else {SingleShotTime = ((SingleShotTime * 5) + ShotCounter) / (5 + 1);} // 20090911 - Moved this into the >3 if() - it was outside it } /* SingleShotTime = 1.17 x ShotCounter + 37 in mS. BurstTime = 1.20 x ShotCounter + 37 in mS. BurstTime = BurstTimeFactor x 1.17 / 1.20 x SingleShotTime + 37 / 1.20 x (BurstTimeFactor - 1) */ BurstTimeFactorPercent = 2 * BurstTimeFactor + 100; BurstTime = (BurstTimeFactorPercent * 117 * SingleShotTime) / 120 / 100 + (37 * (BurstTimeFactorPercent - 100) / 120); 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;} if (SingleShotTime != SingleShotTimeOld) { EEPROM_write(0x03, (char)SingleShotTime); SingleShotTimeOld = SingleShotTime; } /* Semi only mode */ if (SBTCounter < 3) {BurstTime = 2 * SingleShotTime;} else { if (GunFunction == 3) {BurstTime = SingleShotTime - 2;} } } void MotorPWM(char PWMduty) // For ATMEGA1284P /* Controlling OC0A which is pin 43 PB3 to the motor drive */ { BrakeOFF(); //Turn off brake if (PWMduty != 0) {TCCR0A = 0b10000011;} // Start Motor else {TCCR0A = 0b00000011;} // Stop Motor OCR0A = (char)PWMduty; // This is the PWM comparator test value TCCR0B = 0b10000001; // 3.906kHz (1MHz / 256) } /* DonP 20090217 - Removed to save program space void FactoryDiagnostics(void) // Factory test { // Test for Trigger Key voltage TestValue = GetVoltage(Trigger,0); if ((TestValue < 4392) || (TestValue > 5368)) {return;} EventLog(0); // Turn on data ports for output - Only reset when battery disconnected DDRB = 0x2B; PORTB = 0x01; TestResult: TestCode = 1; PORTB = 0x01; // Turn FETs off Timer(2000); TestValue = GetVoltage(Trigger,0); // Trigger sense test if ((TestValue < 4734) || (TestValue > 5025)) {TestCode = 2;} TestValue = GetVoltage(Trigger,1); // Trigger sense test if ((TestValue < 4734) || (TestValue > 5025)) {TestCode = 3;} TestValue = GetVoltage(Drain,1); // Drain sense test if ((TestValue < 4734) || (TestValue > 5025)) {TestCode = 4;} PORTB = 0x03; // Turn Q1 Drive FET on Timer(1); TestValue = GetVoltage(Drain,1); // Q1 test if ((TestValue < 0) || (TestValue > 10)) {TestCode = 5;} PORTB = 0x01; // Turn Q1 Drive FET off Timer(1); PORTB = 0x00; // Turn Q2 Brake FET on Timer(1); TestValue = GetVoltage(Drain,1); // Q2 test if ((TestValue < 9701) || (TestValue > 10301)) {TestCode = 6;} PORTB = 0x01; // Turn Q2 Brake FET off Timer(1); // Blink Error Code to AUX5 LED for (TestCounter = 0; TestCounter < TestCode; TestCounter++) { PORTB = 0x21; // LED on Timer(500); // Wait 500mS PORTB = 0x01; // LED off Timer(500); // Wait 500mS } if (TestCode != 1) { // Output bad value visually in mS Timer(1500); PORTB = 0x21; // LED on Timer(TestValue); PORTB = 0x01; // LED off Timer(2000); } // //1 = Normal //2 = 20V Trigger Voltage //3 = 10V Trigger Voltage //4 = Drain open circuit voltage //5 = Q1 FET drive //6 = Q2 FET drive // // Store Lock flags to EEPROM EEPROM_write(0x20, (char)SemiOnlyLock); EEPROM_write(0x21, (char)AutoRoll); EEPROM_write(0x22, (char)SoftStartEnable); EEPROM_write(0x23, (char)HardBatteryVoltage / 100); goto TestResult; // Keep Flashing Code } */ /* DonP -- see below void EventLog(char Event) // 0 = Factory Test Performed 0x40 // 1 = High Battery Voltage 0x41 // 2 = Peak Current Limit 0x42 // 3 = Over Temperature Limit 0x43 // 4 = Battery Voltage Reset 0x44 { Event = Event + 64; EventOld = EEPROM_read((char)(Event)); EventOld++; if (EventOld > 250) {EventOld = 250;} EEPROM_write((char)Event,(char)EventOld); return; } */ // DonP 20080904 - ErrorLog() to replace above EventLog() // Logs errors between two EEPROM locations in a cyclic fashion. void EventLog(char eventcode) { // Self-maintaining // // Errors are logged between ERRLOG_MEMSTART and ERRLOG_MEMEND. // Last location written is always contained in ERRLOG_BOOKMARK_LOCN // // Read ERRLOG_BOOKMARK_LOCN, then write to that +1, and write the new address to // ERRLOG_BOOKMARK_LOCN. // // If ERRLOG_LOCN ever becomes higher than ERRLOG_MEMEND, then make it ERRLOG_MEMSTART. // -------------------- // Error Code Meanings: // // 0 = Unit Power-up initiated // 1 = High Battery Voltage // 2 = Peak Current Limit // 3 = Over Temperature Limit // 4 = Battery Voltage Reset (This # and lower are from Terry's original) // 5 = Programming mode entered // 6 = Factory reset initiated // 7 = Magazine Sensor Unit Detected // 8 = Sniper Mode Lock-in Dongle Detected // 9 = Magazine Sensor not detected at startup (or disabled due to magazine present) // 0A = 10 Sniper Mode Lock-in Dongle not Detected at startup // 0B = 11 Wolfdragon LiPO monitor board with Batt OK detected at startup // 0C = 12 Wolfdragon LiPO monitor board not detected at startup // 0D = 13 LiPO mode detected 7.4V lipo // 0E = 14 LiPO mode detected 11.1V lipo // 0F = 15 LiPO mode did not detect a lipo // 1F = 16 LiPO mode detected 14.4V lipo unsigned char ErrorLogAddress = 0xff; // Current error log address // Read last location to which we wrote a log entry. ErrorLogAddress = EEPROM_read( ERRLOG_BOOKMARK_LOCN ); if( ErrorLogAddress == 0xFF ) // Fresh from programming! ErrorLogAddress = (ERRLOG_MEMSTART-1); // -1 because we inc by 1 in a moment. // Inc by 1 ErrorLogAddress++; // If it's now past the end of our log memory, loop it around to the beginning. if( ErrorLogAddress > ERRLOG_MEMEND ) ErrorLogAddress = ERRLOG_MEMSTART; // Write out our entry (obliterating the existing one, which is also the oldest one.) EEPROM_write(ErrorLogAddress, (char)eventcode); // Write out the location of the last entry written. EEPROM_write(ERRLOG_BOOKMARK_LOCN, (char)ErrorLogAddress); return; } // End DonP unsigned char LiPOStatus(void) /* 0 = No Monitor Present 1 = LiPO Battery OK 2 = LiPO Battery Weak 3 = LiPO Battery Dead */ { long Aux0Data = 0; /* Aux port 0 data variable */ long Aux1Data = 0; /* Aux port 1 data variable */ PORTB = Neutral; /* Be sure pullups are on */ Timer(1); /* Aux voltages are /4 since there are no voltage dividers */ /* Voltages are im mV */ Aux0Data = GetVoltage(Aux0,0) / 4; Aux1Data = GetVoltage(Aux1,0) / 4; // DonP Note: LiPOInstalled is default '0' and is only ever set to '1' here at startup. // This function is only ever re-entered if that happened (at startup.) if ((Aux0Data < 300) && (Aux1Data < 300)) {LiPOInstalled = 1; return 1;} if (Aux1Data > 500) {return 3;} if (Aux0Data > 500) {return 2;} return 1; } long GetAux0Voltage(void) { long mV = 0; // PORTB = Neutral; // Be sure pullups are on // Timer(1); // Aux voltages are /4 since there are no voltage dividers on the ports // Voltages are im mV // Aux1Data = GetVoltage(Aux1,0) / 4; mV = GetVoltage(Aux0,0) / 4; // Since pullups are active, 5V or so is open circuit // 2.5V (between 2.0 and 3.0 for best reliability) is Allegro sensor present with output on AUX0 // Allegro sensor will be around 0.2V or 4.7V depending on polarity of nearby magnet. // Below 2.0V is something else .. see lipo function above: Aux0 and Aux1 below 300mV is monitor // present. return mV; } long GetAux1Voltage(void) // This function always returns 5V read when reset disable fuse is not done { long mV = 0; // PORTB = Neutral; // Be sure pullups are on // Timer(1); // Aux voltages are /4 since there are no voltage dividers on the ports // Voltages are im mV // Aux1Data = GetVoltage(Aux1,0) / 4; mV = GetVoltage(Aux1,0) / 4; // Since pullups are active, 5V or so is open circuit // 2.5V (between 2.0 and 3.0 for best reliability) is Allegro sensor present with output on AUX0 // Allegro sensor will be around 0.2V or 4.7V depending on polarity of nearby magnet. // Below 2.0V is something else .. see lipo function above: Aux0 and Aux1 below 300mV is monitor // present. return mV; } //void Diagnostic(long DiagTime) /* program test output to led. Only for development use */ /* { DDRB = 0x2B; PORTB = 0x21; // LED on Timer(RunningTemp); // Wait 500mS PORTB = 0x01; // LED off Timer(500); // Wait 500mS SetAUXPorts(); } */ /* void SecretMode(void) { // Semi Lock Vibrate(); if (EEPROM_read(0x20) != 0x02) {EEPROM_write(0x20, 0x00);} count6 = 0; for (count=0; count < 600; count++) { if (GetVoltage(Trigger,0) < TrigTrip) {TriggerHold = 0;} if (((GetVoltage(Trigger,0) > LowBatteryVoltage)) && (TriggerHold == 0)) { if (EEPROM_read(0x20) == 0x00) {EEPROM_write(0x20, 0x01);} count6++; if (count6 == 10) {EEPROM_write(0x20, 0x02);} count = 0; TriggerHold = 1; } } // AutoRoll Vibrate1(1);Vibrate(); EEPROM_write(0x21, 0x01); for (count=0; count < 600; count++) { if (GetVoltage(Trigger,0) < TrigTrip) {TriggerHold = 0;} if (((GetVoltage(Trigger,0) > LowBatteryVoltage)) && (TriggerHold == 0)) { EEPROM_write(0x21, 0x00); count = 0; TriggerHold = 1; } } // Soft Start Vibrate1(2);Vibrate(); EEPROM_write(0x22, 0x01); for (count=0; count < 600; count++) { if (GetVoltage(Trigger,0) < TrigTrip) {TriggerHold = 0;} if (((GetVoltage(Trigger,0) > LowBatteryVoltage)) && (TriggerHold == 0)) { EEPROM_write(0x22, 0x00); count = 0; TriggerHold = 1; } } // HardBatteryVoltage Vibrate1(3);Vibrate(); EEPROM_write(0x23, 0x00); count6 = 0; for (count=0; count < 600; count++) { if (GetVoltage(Trigger,0) < TrigTrip) {TriggerHold = 0;} if (((GetVoltage(Trigger,0) > LowBatteryVoltage)) && (TriggerHold == 0)) { EEPROM_write(0x23, (char)(70 + 5 * count6)); count6++; count = 0; TriggerHold = 1; } } } */ /* End of program */