/********************************************************************* * * Project: (if you don't mind... I'll withhold the actual * application.. Maybe you can guess..) * * This is a working piece of firmware used * in an industrial application. Your application * will be different, of course. This example may * help in a few ways though: * 1. I use the CCS compiler, and I use several * of that compilers nice extensions, such * as writing to the serial port, delays, * bit manipulation, etc. I found the * CCS compiler to be more than adequete * for these PIC control applications. * 2. The PIC communicates to an A/D chip, the * MAXIM 192. You can copy this function. * 3. Perhaps, this program help you size your * application - I show the % memory * utilization of the 16C63 I used. * 4. I suppose you might get embedded * programming ideas... I am a fan * of round-robin, state-machine programming.. * Really, this is a simple program but * the bread-n-butter of embedded control: * monitor some sensors, look at switches, * light some LEDs, apply a bunch of rules.. * * * * Description: * Runs on a PIC16C63, compiled with the CCS PCM compiler. * Uses the overflow interrupt from timer1 as a timebase. * Various debug messages are output on the serial port * at 4800,8N and can be viewed with typical terminal * program. Program uses a simple round-robin loop to * monitor and handle all events and actions. Code written * with 4MHz crystal in mind. * * PIC16C63 is programmed with PICSTART Plus programmer through * through the MPLAB program. Programmer options include: * Device = 16C63, Oscillator = XT, Watchdog = OFF, Brownout = ON, * Code Protect = OFF, Power Up Timer = OFF. * Normal procedure is to: * 1) Compile with PCM in a DOS window, * 2) In MPLAB, enable programmer and select above options, * 3) Select File | Import and choose the .HEX file * 4) Select Picstart Plus | Program/Verify and program part. * * The A/D chip used to sample analog values is the MAXIM MAX192. * * * Author: Tom Coonan * File: trash.c * Updated: 12/14/96 * Version: 1.1 * Revisions: * 12/10/96: - Added Self Test. * 12/14/96: - Changed DIR_RLY polarity. * - Changed CHARGE_MIN constant value from 839 to 800. * - Added rule to charging that turns charger on * when motor is running. * - Added bMotorOn flag to help with new charger rule. * * * Misc. Programming Notes: * 1) I use a prefix notation for variables based on their type: * * b - boolean, will be declared as byte (8 bits) * by - byte (8 bits) * n - integer, declared as long ints (16 bits) * * Most simple counters and state variables fit into 8 bits * and are therefore declared as byte. Only a few variables * need the longer 16-bit length. Note what the lengths of * types are for this compiler! * * 2) Chip resources used: 56% program rom, 29% data ram * *******************************************************************/ /* I/O Map Note: Currently, 'fix_io' statements are used, so we do not have to use TRIS statements. This is OK since pins are used as either inputs or outputs, but never change mode. Pin Equate I/O TRIS Bits ----------------------------------- A7 N/C INPUT 1 TRIS Word = F0 A6 N/C INPUT 1 A5 EXTENDED INPUT 1 A4 CYCLE_SW INPUT 1 A3 MOTOR_RLY OUTPUT 0 A2 RECYCLE_RLY OUTPUT 0 A1 DIR_RLY OUTPUT 0 A0 CHARGE_RLY OUTPUT 0 B7 CHARGE_LED OUTPUT 0 TRIS Word = 03 B6 SPA_LED OUTPUT 0 B5 SERVICE_LED OUTPUT 0 B4 CYCLE1_LED OUTPUT 0 B3 CYCLE2_LED OUTPUT 0 B2 CYCLE3_LED OUTPUT 0 B1 RETRACTED INPUT 1 B0 RECYCLE_SW INPUT 1 C7 SERIAL_RX INPUT 1 TRIS Word = AC C6 SERIAL_TX OUTPUT 0 C5 TESTJ1_7To8 INPUT 1 <--- Used to trigger Self Test C4 ADC_CLK OUTPUT 0 C3 ADC_DOUT INPUT 1 C2 ADC_SSTRB INPUT 1 C1 ADC_DIN OUTPUT 0 C0 ADC_CS OUTPUT 0 */ #include <16c63.h> #fuses XT,NOPROTECT,NOWDT /* Allows the 'delay_' functions to work correctly with our 4MHz crystal. */ #use delay(clock=4000000) /* Tell compiler our I/O. We never change any one pin's input/output. */ #use fixed_io(a_outputs=PIN_A3,PIN_A2,PIN_A1,PIN_A0) #use fixed_io(b_outputs=PIN_B7,PIN_B6,PIN_B5,PIN_B4,PIN_B3,PIN_B2) #use fixed_io(c_outputs=PIN_C6,PIN_C4,PIN_C1,PIN_C0) /* Used for debug purposes. */ #use rs232(baud=4800, xmit=PIN_C6, rcv=PIN_C7) // Bit equates for sensors INPUTs #define RETRACTED PIN_B1 #define EXTENDED PIN_A5 // Bit equates for Relays OUTPUTs #define CHARGE_RLY PIN_A0 #define DIR_RLY PIN_A1 #define MOTOR_RLY PIN_A3 #define RECYCLE_RLY PIN_A2 // Bit equates for LED OUTPUTs #define CHARGE_LED PIN_B7 #define SPA_LED PIN_B6 #define SERVICE_LED PIN_B5 // Bit equates for Switch INPUTs #define RECYCLE_SW PIN_B0 #define CYCLE_SW PIN_A4 #define CYCLE1_LED PIN_B4 #define CYCLE2_LED PIN_B3 #define CYCLE3_LED PIN_B2 // Bit equates for A/D chip I/O #define ADC_CS PIN_C0 #define ADC_CLK PIN_C4 #define ADC_DIN PIN_C1 #define ADC_DOUT PIN_C3 #define ADC_SSTRB PIN_C2 #define FALSE 0 #define TRUE 1 /* Voltage Equations Due to the limited nature of PIC arithmetic, the program logic uses the raw A/D sample values and does not use any floating point math. Only a few key variables are involved, therefore, all thresholds are precomputed below and placed in defines. 1) The A/D input accepts a 0.000V to 4.096V input which maps to a 10 bit unsigned binary ranging from 0x000 to 0x3FF (or, 0 to 1023). The following equation relates the A/D voltage input to the expected binary: sample = 1024*(vin/4.096) where sample is the value of the A/D number ultimately used in the program from the A/D (e.g. the value in nBatteryVoltage). 2) The Battery voltage is divided through a resistor network resulting in: vin = vbattery*0.2582 Therefore, the final equation is: sample = 1024*(vbattery*0.2582/4.096) 3) Here is a table of voltages and samples: Variable Voltage Sample CHARGE_MAX 14.2 916 CHARGE_MIN 13.0 839 BATTERY_PROTECT 9.5 613 SPA_VOLTAGE 13.6 878 */ /* What the values SHOULD be */ #define CHARGE_MAX 916 #define CHARGE_MIN 800 #define BATTERY_PROTECT 613 #define SPA_VOLTAGE 878 // Specify some hysterysis around the SPA voltage. // // Note: The A/D resolution is 16.0 Volts / 1024 = 15.6 mV #define SPA_HYSTERYSIS 7 #define MAX_PENDING_CYCLES 3 /* Called on power up. */ void LampTest (void); /* A/D driver */ long int nADSample; /* global where sample is assembled */ void GetAD (byte byChannel); long int nBatteryVoltage; /* 16-bit integer, need for 10-bit A/D sample */ long int nAvgBatteryVoltage; long int nBatteryVoltage_last1; long int nBatteryVoltage_last2; long int nBatteryVoltage_last3; /* States and flags */ byte bCharging; byte bEverCharged; /* Need to know if charger has ever been on */ byte byPendingCycles; byte bMotorPaused; /* if battery volts drop below protect, pause */ byte bMotorOn; byte bEnoughMotorJuice; /* Service Soon flag. This gets set whenever a forward motor action or a reverse motor action times out, or if the battery voltage ever drops below a battery protection voltage. */ byte bServiceSoon; /* Flag is set if a forward or reverse timout has *ever* occured. */ byte bTimeoutOccured; /* Flag is set if a battery protect voltage was ever reached. */ byte bBatteryProtect; /* Flag is set if battery voltage has ever risen about battery protect. This will help elliminate the service soon light coming on prematurely. */ byte bBatteryEverOK; /* Maximum number of cycles that can be queued up. Set after reading strap */ byte byMaxCycles; /* Primary state machine variable for the "cycle" procedure */ byte byCycleState; /* Cycle State Machine states for variable nCycleState */ #define STATE_CYCLE_INITIAL 0 #define STATE_CYCLE_INITIAL_WAITJUICE 1 #define STATE_CYCLE_IDLE 2 #define STATE_CYCLE_CHECKJUICE 3 #define STATE_CYCLE_WAITJUICE 4 #define STATE_CYCLE_FORWARD 5 #define STATE_CYCLE_REVERSE1 6 #define STATE_CYCLE_REVERSE2 7 #define STATE_CYCLE_DONE 8 /* Number of consequitive samples of switch needed to assert switch */ #define CYCLE_SWITCH_REQUIRED_SAMPLES 50 /* Cycle switch state machine variables */ byte byCycleSwitchState; byte bySample_CycleSwitch; #define STATE_CYCLE_SWITCH_IDLE 0 #define STATE_CYCLE_SWITCH_SAMPLE 1 #define STATE_CYCLE_SWITCH_WAIT_RELEASE 2 /* Timer Tick Counters (used with delay define constants) */ byte byTicks_Charger_Hysterysis; /* Charger can change once per second. */ /* The delays for forward and reverse are long, so need long ints. */ long int nTicks_Reverse; /* Reverse direction timeout (long one) */ long int nTicks_Forward; /* Forward direction timeout (long one) */ long int nTicks_Paused; /* Bookmark, when we pause motor */ byte byTicks_CycleSwitch; /* Debounce for cycle switch */ byte byTicks_Delay; /* A miscellanious delay */ byte byTicks_Display; /* Last time display (e.g. RS232) */ byte byTicks_Initial_Charge; /* Brief time that Initial Charge rule applies */ /**** Delays ****/ /* Our timing is based on the overflow of the timer1. First, here's how we detirmine how long a "tick" is: 4MHz crystal, divided by 4 (e.g. PIC always does this) is 1MHz. Timer 1 is a 16 bit counter. The Timer1 will be programmed to use a DIVIDE BY 4 prescaler (see the setup_timer_1 function call). Therefore, the number of interrupts per second is: 4,000,000 / 4 / 4 / 2^16 = 3.814... Hz Or, 1 tick is 262ms, or roughly 1/4 second Note: Look at the interrupt service routine to see how tick counters are done. Basically, they are little background down counters. */ /* Cycle switch debounce (sort of like debounce.. see the state machine) */ #define TICKS_CYCLESWITCH 2 /* Initial charging rule is only in effect briefly after power up. */ #define TICKS_INITIAL_CHARGE 4 /* Forward direction timeout (90 seconds) */ #define TICKS_FORWARD 360 /* Reverse direction timeout (90 seconds) */ #define TICKS_REVERSE 360 /* Lamptest time duration */ #define TICKS_LAMPTEST 2 /* Minimum time between any change in charger state */ #define TICKS_CHARGER_HYST 4 /* Delay after cycle is done before direction solenoid released */ #define TICKS_CYCLE_DONEDELAY 1 /* Time period after forward-reverse changeover when sensors ignored. */ #define TICKS_IGNORE_SENSORS 4 /* Sense if the Self Test is being requested. This is done through the RC5 input. If the switch on it is closed, this port will read 0 which means YES run the self-test. */ int SelfTestRequested (void); /* The self test used during manufacturing. */ void SelfTest (void); void SelfTestFail (void); /* Called if fail.. will blink LEDs forever. */ /* Update the nBatteryVoltage variable by sampling voltage with A/D */ void GetBatteryVoltage(void); /* Start Motor */ void StartMotor (void); /* Stop Motor */ void StopMotor (void); /* Forward (direction solenoid energized) */ void Forward (void); /* Reverse (direction solenoid de-energized) */ void Reverse (void); /* Return true if the retracted sensor is active */ int SenseRetracted (void); /* Return true if the extended over pressure sensor is active */ int SenseExtended (void); /* Sense if the multicycle option is enabled. This is done through the A/D port 1. */ int MultiCyclesEnabled (void); /* Sense if the initial retract option is enabled. This is done through the A/D port 4. */ int InitialRetractsEnabled (void); /* Main program and main loop */ main () { /* Note: all printf calls are sent out RS232 port at 4800 baud. */ printf ("TRASH COMPACTOR V0.0\r"); /* Initially turn all relays off. */ output_low (CHARGE_RLY); output_low (DIR_RLY); output_low (MOTOR_RLY); output_low (RECYCLE_RLY); /* Initialize interface to the A/D chip. */ output_low (ADC_CLK); output_high (ADC_CS); /* Initialize variables */ byTicks_Charger_Hysterysis = 0; nTicks_Reverse = 0; nTicks_Forward = 0; nTicks_Paused = 0; byTicks_CycleSwitch = 0; byTicks_Delay = 0; byTicks_Display = 0; byTicks_Initial_Charge = 0; bEverCharged = FALSE; byPendingCycles = 0; byCycleSwitchState = 0; byCycleState = 0; bServiceSoon = FALSE; bCharging = FALSE; bMotorPaused = FALSE; bEnoughMotorJuice = FALSE; bTimeoutOccured = FALSE; bBatteryProtect = FALSE; bBatteryEverOK = FALSE; bMotorOn = FALSE; /* Testing! */ printf ("Sensors: "); if (input(EXTENDED)) printf ("1"); else printf ("0"); if (input(RETRACTED)) printf ("1"); else printf ("0"); if (input(CYCLE_SW)) printf ("1"); else printf ("0"); if (InitialRetractsEnabled()) printf ("1"); else printf ("0"); if (MultiCyclesEnabled()) printf ("1"); else printf ("0"); if (SelfTestRequested()) printf ("1"); else printf ("0"); printf ("\r"); /* Check for Self Test signal. */ if (SelfTestRequested ()) { printf ("Self Test.\r"); SelfTest (); /* This function never returns..... */ } /* Check the multicycles strapping option (done via A/D channel #1) */ if (MultiCyclesEnabled()) byMaxCycles = MAX_PENDING_CYCLES; else byMaxCycles = 1; printf ("Max Cycles: %u\r", byMaxCycles); /* Enable Timer Interrupt */ setup_timer_1 (T1_INTERNAL | T1_DIV_BY_4); /* Enable Interrupts */ enable_interrupts (INT_TIMER1); enable_interrupts (GLOBAL); /* Blink some lights.. */ LampTest (); /*** Get an initial battery voltage sample */ GetBatteryVoltage(); printf ("Initial A/D: %4lX\r", nBatteryVoltage); nBatteryVoltage_last1 = nBatteryVoltage; nBatteryVoltage_last2 = nBatteryVoltage; nBatteryVoltage_last3 = nBatteryVoltage; /************* Main Loop **************/ /* Set a timer for when our initial charger rule applies. Once this time expires, the rule can't fire */ byTicks_Initial_Charge = TICKS_INITIAL_CHARGE; for (;;) { /******************************************************/ /* */ /* SAMPLE Battery Voltage with A/D */ /* */ /******************************************************/ /*** Get a raw battery voltage sample out of the A/D chip. */ GetBatteryVoltage(); /* Average last 4 samples */ nAvgBatteryVoltage = nBatteryVoltage + nBatteryVoltage_last1 + nBatteryVoltage_last2 + nBatteryVoltage_last3; /* Must divide by the 4 samples, which is just a right shift of 2 */ nAvgBatteryVoltage = nAvgBatteryVoltage >> 2; /* Shift samples down the sample chain */ nBatteryVoltage_last3 = nBatteryVoltage_last2; nBatteryVoltage_last2 = nBatteryVoltage_last1; nBatteryVoltage_last1 = nBatteryVoltage; /* TEST: Print out A/D values on the RS232 port. */ if (byTicks_Display == 0) { printf ("A/D: %4lXh (avg), %4lXh (inst)\r", nAvgBatteryVoltage, nBatteryVoltage); byTicks_Display = 4; /* Only update output once a second */ } /******************************************************/ /* */ /* UPDATE The CHARGING SYSTEM */ /* */ /******************************************************/ if (bCharging && (nAvgBatteryVoltage > CHARGE_MAX) && (byTicks_Charger_Hysterysis == 0)) { /* Shut down charger */ bCharging = FALSE; /* Set hysterysis ticker so we don't consider a change for 1 second */ byTicks_Charger_Hysterysis = TICKS_CHARGER_HYST; } else if (!bCharging && (nAvgBatteryVoltage < CHARGE_MIN) && (byTicks_Charger_Hysterysis == 0)) { /* Start charger up */ bCharging = TRUE; /* Set hysterysis ticker so we don't consider a change for 1 second */ byTicks_Charger_Hysterysis = TICKS_CHARGER_HYST; } /* New Rule: Charge if below CHARGE_MAX and Motor is on */ else if (bMotorOn && (nAvgBatteryVoltage < CHARGE_MAX) && (byTicks_Charger_Hysterysis == 0)) { /* Start charger up */ bCharging = TRUE; /* Set hysterysis ticker so we don't consider a change for 1 second */ byTicks_Charger_Hysterysis = TICKS_CHARGER_HYST; } /* Try the initial charging rule */ if (byTicks_Initial_Charge != 0) { if (!bEverCharged && (nAvgBatteryVoltage < CHARGE_MAX)) { bCharging = TRUE; } } /******************************************************/ /* */ /* CYCLE SWITCH State Machine */ /* */ /******************************************************/ switch (byCycleSwitchState) { case STATE_CYCLE_SWITCH_IDLE: if (input(CYCLE_SW) == 0 && !bTimeoutOccured) { /* Switch was pressed! */ /* Set switch ticks for debounce */ byTicks_CycleSwitch = TICKS_CYCLESWITCH; bySample_CycleSwitch = 0; byCycleSwitchState = STATE_CYCLE_SWITCH_SAMPLE; } break; case STATE_CYCLE_SWITCH_SAMPLE: // The counter, nSample_CycleSwitch, counts how many times // in a row we sample the switch pressed. We demand a certain // number of agreeing samples before we declare the switch // truly pressed. This will help reduse false cycles from noise. // // if (byTicks_CycleSwitch == 0) { // timeout.. must have been noise.. byCycleSwitchState = STATE_CYCLE_SWITCH_IDLE; } else if (bySample_CycleSwitch == CYCLE_SWITCH_REQUIRED_SAMPLES) { // OK. We got this far, so take the action if (byPendingCycles < byMaxCycles) { byPendingCycles++; } byCycleSwitchState = STATE_CYCLE_SWITCH_WAIT_RELEASE; } else if (input(CYCLE_SW) != 0) { // Whoa!! not low. Still unstable. Reset counter. bySample_CycleSwitch = 0; } else if (input(CYCLE_SW) == 0) { // Here is a valid low sample. Increment counter. bySample_CycleSwitch++; } break; case STATE_CYCLE_SWITCH_WAIT_RELEASE: if (byTicks_CycleSwitch == 0) { if (input(CYCLE_SW) == 1) { byCycleSwitchState = STATE_CYCLE_SWITCH_IDLE; } byTicks_CycleSwitch = TICKS_CYCLESWITCH; } break; default: byCycleSwitchState = STATE_CYCLE_SWITCH_IDLE; break; } /******************************************************/ /* */ /* Motor/RAM Cycle State Machine */ /* */ /******************************************************/ switch (byCycleState) { case STATE_CYCLE_INITIAL: // See if the option is enabled. If not, simply skip to IDLE state if (!InitialRetractsEnabled()) { byCycleState = STATE_CYCLE_IDLE; break; } /**** Initially, we demand that ram be completely retracted. ****/ if (SenseRetracted()) { // Already retracted.. Cool.. byCycleState = STATE_CYCLE_IDLE; break; } /* Not retracted! */ printf ("Initially unretracted...\r"); Reverse(); printf ("RV..\r"); byCycleState = STATE_CYCLE_INITIAL_WAITJUICE; break; case STATE_CYCLE_INITIAL_WAITJUICE: if (bEnoughMotorJuice) { // We have enough juice to start. We need to take a short cut // into the normal sequencing. printf ("Motor..\r"); bMotorPaused = FALSE; StartMotor(); nTicks_Reverse = TICKS_REVERSE; byCycleState = STATE_CYCLE_REVERSE2; } break; case STATE_CYCLE_IDLE: if (byPendingCycles != 0 && !bTimeoutOccured) { /* There's at least one cycle queued, so let's start. */ Forward(); printf ("FW..\r"); /* First, wait till enough juice */ byCycleState = STATE_CYCLE_CHECKJUICE; /* Also, add in a little delay just to let things settle */ byTicks_Delay = 1; } break; case STATE_CYCLE_CHECKJUICE: if (bEnoughMotorJuice && (byTicks_Delay == 0)) { /* Enough juice is available, so, let'd start going forward. Set a timer ticker also. */ StartMotor(); printf ("Motor..\r"); nTicks_Forward = TICKS_FORWARD; byCycleState = STATE_CYCLE_FORWARD; } else { if (!bCharging && (nAvgBatteryVoltage < CHARGE_MAX)) { bCharging = TRUE; } printf ("Wait juice..\r"); byCycleState = STATE_CYCLE_WAITJUICE; } break; case STATE_CYCLE_WAITJUICE: if (bEnoughMotorJuice) { printf ("Got juice..\r"); byCycleState = STATE_CYCLE_CHECKJUICE; } break; case STATE_CYCLE_FORWARD: /* Handle Pause case first thing.. */ if (bMotorPaused) { if (!bBatteryProtect && (byTicks_Delay == 0)) { /* Battery voltage OK again. Restart, and restore ticks */ printf ("Resume..\r"); bMotorPaused = FALSE; nTicks_Forward = nTicks_Paused; StartMotor(); } } /* Check for timeout or extended sensor */ else { if ((nTicks_Forward == 0) || SenseExtended()) { printf ("RV..\r"); Reverse(); nTicks_Reverse = TICKS_REVERSE; byTicks_Delay = TICKS_IGNORE_SENSORS; byCycleState = STATE_CYCLE_REVERSE1; /* If a timout ever occurs, light the service soon light! */ if (nTicks_Forward == 0) { printf ("Timeout!\r"); bTimeoutOccured = TRUE; byPendingCycles = 0; // Clear out all pending cycles byCycleState = STATE_CYCLE_IDLE; StopMotor(); } } else if (bBatteryProtect) { /* Battery voltage extremely low! Pause motor till enough juice is present. Save our tick counter, too. */ printf ("Pause..\r"); StopMotor(); bMotorPaused = TRUE; nTicks_Paused = nTicks_Forward; /* Also add a hysterysis factor to avoid chatter */ byTicks_Delay = 8; } } break; case STATE_CYCLE_REVERSE1: /* Initially after reversing, ignore sensors for a little bit */ if (byTicks_Delay == 0) { /* OK, that was long enough.. Go to primary reverse state. */ byCycleState = STATE_CYCLE_REVERSE2; } break; case STATE_CYCLE_REVERSE2: /* Handle Pause first thing.. */ if (bMotorPaused) { if (!bBatteryProtect && (byTicks_Delay == 0)) { /* Voltage is OK again. Restart, and restore ticks */ printf ("Resume..\r"); bMotorPaused = FALSE; nTicks_Reverse = nTicks_Paused; StartMotor(); } } /* Check for timeout or retracted sensor */ else { if ((nTicks_Reverse == 0) || SenseRetracted() || SenseExtended()) { StopMotor(); if (byPendingCycles != 0) byPendingCycles--; byCycleState = STATE_CYCLE_DONE; /* A slight delay is required after motor is shut down and when direction solenoid is de-energized. */ byTicks_Delay = TICKS_CYCLE_DONEDELAY; /* If a timout ever occurs, light the service soon light! */ if (nTicks_Reverse == 0) { printf ("Timeout!\r"); bTimeoutOccured = TRUE; byPendingCycles = 0; // Clear out all pending cycles byCycleState = STATE_CYCLE_IDLE; StopMotor(); } } else if (bBatteryProtect) { /* Battery voltage extremely low! Pause motor till enough juice is present. Save our tick counter, too. */ printf ("Pause..\r"); StopMotor(); bMotorPaused = TRUE; nTicks_Paused = nTicks_Reverse; /* Also add a hysterysis factor to avoid chatter */ byTicks_Delay = 8; } } break; case STATE_CYCLE_DONE: /* Wait till the final delay is done between motor start and direction. */ if (byTicks_Delay == 0) { printf ("Done Cycle..\r"); byCycleState = STATE_CYCLE_IDLE; } break; } /*****************************************************/ /* */ /* UPDATE Various Flags */ /* */ /*****************************************************/ /*** Set flag whether we have starting power available or nor. ****/ // Also, put some hysterysis in here.. // if (!bEnoughMotorJuice && (nAvgBatteryVoltage > (SPA_VOLTAGE))) { bEnoughMotorJuice = TRUE; } else if (bEnoughMotorJuice && (nAvgBatteryVoltage < (SPA_VOLTAGE - SPA_HYSTERYSIS))) { bEnoughMotorJuice = FALSE; } /* Check if battery voltage is less than the BATTERY_PROTECT */ if (nAvgBatteryVoltage < BATTERY_PROTECT) { bBatteryProtect = TRUE; } else { bBatteryProtect = FALSE; bBatteryEverOK = TRUE; // Set flag that battery has been OK at some point. } /* The service soon light comes on if ever a timeout occurs or if ever the battery protect threshhold was passed (once battery ever achieved a decent level). This state (and LED) once set stays on forever (until reset). */ if ((bBatteryProtect && bBatteryEverOK) || bTimeoutOccured) bServiceSoon = TRUE; /*****************************************************/ /* */ /* UPDATE LEDs and Relay I/O */ /* */ /*****************************************************/ /* Update Cycle LEDs */ switch (byPendingCycles) { case 0: output_low (CYCLE1_LED); output_low (CYCLE2_LED); output_low (CYCLE3_LED); break; case 1: output_high (CYCLE1_LED); output_low (CYCLE2_LED); output_low (CYCLE3_LED); break; case 2: output_high (CYCLE1_LED); output_high (CYCLE2_LED); output_low (CYCLE3_LED); break; case 3: output_high (CYCLE1_LED); output_high (CYCLE2_LED); output_high (CYCLE3_LED); break; } /* Update the SPA LED */ if (bEnoughMotorJuice) { output_high (SPA_LED); } else { output_low (SPA_LED); } /* Update the Service Soon LED */ if (bServiceSoon) { output_high (SERVICE_LED); } else { output_low (SERVICE_LED); } /* Control charger relay and charger LED */ if (bCharging) { bEverCharged = TRUE; output_high (CHARGE_RLY); output_high (CHARGE_LED); } else { output_low (CHARGE_RLY); output_low (CHARGE_LED); } } } /*********** End of the Main Loop **************/ /***********************************************/ /* */ /* Low-Level I/O Functions */ /* */ /***********************************************/ /* Called on power up to see if Self Test needs to be run. */ int SelfTestRequested (void) { if (!input(PIN_C5)) return TRUE; else return FALSE; } /* Start Motor */ void StartMotor (void) { bMotorOn = TRUE; output_high (MOTOR_RLY); } /* Stop Motor */ void StopMotor (void) { bMotorOn = FALSE; output_low (MOTOR_RLY); } /* Forward */ void Forward (void) { output_low (DIR_RLY); } /* Reverse */ void Reverse (void) { output_high (DIR_RLY); } /* Return true if the retracted sensor is active */ int SenseRetracted (void) { return input (RETRACTED); } /* Return true if the forward over pressure sensor is active */ int SenseExtended (void) { /* Sensor is asserted when grounded, or zero, so invert */ return input (EXTENDED); } /* Called on power up to give operator warm fuzzy that all is working. */ void LampTest (void) { /* Light all the LEDs */ output_high (CYCLE1_LED); output_high (CYCLE2_LED); output_high (CYCLE3_LED); output_high (SPA_LED); output_high (CHARGE_LED); output_high (SERVICE_LED); /* Delay for 1/4 second */ byTicks_Delay = TICKS_LAMPTEST; while (byTicks_Delay != 0); /* Extinguish all the LEDs */ output_low (CYCLE1_LED); output_low (CYCLE2_LED); output_low (CYCLE3_LED); output_low (SPA_LED); output_low (CHARGE_LED); output_low (SERVICE_LED); } /*****************************************************************************/ /* TIMER INTERRUPT */ /* */ /* Timer Interrupt occurs at every overflow of the free-running counter. */ /* We use this simply as a timebase. This interrupt service routine */ /* will decrement any ticks we are managing. Ticks are decremented */ /* down to zero and then they are not decremented further. Main thread */ /* routines read these tick counters and set them when they need a */ /* countdown. */ /*****************************************************************************/ #int_timer1 void TimerInterrupt (void) { /* Decrement every tick counter we are currently using. */ if (nTicks_Reverse > 0) nTicks_Reverse--; if (nTicks_Forward > 0) nTicks_Forward--; if (byTicks_CycleSwitch > 0) byTicks_CycleSwitch--; if (byTicks_Delay > 0) byTicks_Delay--; if (byTicks_Display > 0) byTicks_Display--; if (byTicks_Charger_Hysterysis > 0) byTicks_Charger_Hysterysis--; if (byTicks_Initial_Charge > 0) byTicks_Initial_Charge--; /* Interrupt remains enabled.. No need to do anything.. */ } /**************************************************************/ /* */ /* A/D Drivers */ /* */ /**************************************************************/ /* A/D Has up to 8 channels it can sample. Here is our current channel allocation: Channel# Usage 0 Battery Voltage 1 Multiple Cycles Enable (Low means disabled) 2 Initial Retraction Enable (Low means disabled) 3 Not Used. 4 Not Used. 5 Not Used. 6 Not Used. 7 Not Used. */ void GetBatteryVoltage (void) { /* Battery Voltage is wired to input channel #0 */ GetAD (0); /* Copy to the global variable */ nBatteryVoltage = nADSample; } int MultiCyclesEnabled (void) { /* Multicycle option strap is wired to input channel #1 */ GetAD (1); /* Copy to the global variable */ if (nADSample < 0x0100) { /* Low voltage, jumper is IN, therefore option is DISABLED */ return FALSE; } else { /* High voltage, jumper is OUT, therefore option is ENABLED */ return TRUE; } } int InitialRetractsEnabled (void) { /* Multicycle option strap is wired to input channel #4 */ GetAD (2); /* Copy to the global variable */ if (nADSample < 0x0100) { /* Low voltage, jumper is IN, therefore option is DISABLED */ return FALSE; } else { /* High voltage, jumper is OUT, therefore option is ENABLED */ return TRUE; } } /* Here's the basic driver to the MAXIM chip */ void GetAD (byte byChannel) { byte byControlWord; byte byBitCounter; /* Read the A/D chip. Control Byte is: 1ccc1111 Where ccc is the channel. Now, the channel encoding is a little screwy. It depends on whether it is in unipolar or differential mode. This circuit is wired up as unipolar and is encoded as follows: Channel ccc ------------- 0 000 1 100 2 001 3 101 4 010 5 110 6 011 7 111 The other bits control unipolar versus differential and other neat stuff not needed for this project. See the MAXIM data sheet for details. */ switch (byChannel) { case 0: byControlWord = 0b10001111; break; case 1: byControlWord = 0b11001111; break; case 2: byControlWord = 0b10011111; break; case 3: byControlWord = 0b11011111; break; case 4: byControlWord = 0b10101111; break; case 5: byControlWord = 0b11101111; break; case 6: byControlWord = 0b10111111; break; case 7: byControlWord = 0b11111111; break; default: printf ("Bad A/D Channel!\r"); return; break; } nADSample = 0; output_low (ADC_CS); /* Shift the control byte into A/D */ for (byBitCounter = 0; byBitCounter < 8; byBitCounter++) { if ((byControlWord & 0x80) == 0x80) { output_high (ADC_DIN); } else { output_low (ADC_DIN); } // Pulse clock output_high (ADC_CLK); output_low (ADC_CLK); byControlWord = byControlWord << 1; delay_us(50); } /* Shift out 10 bits of data from A/D */ for (byBitCounter = 0; byBitCounter < 10; byBitCounter++) { /* A/D outputs the next converted bit after the falling edge */ output_high (ADC_CLK); output_low (ADC_CLK); /* Get the bit */ shift_left(&nADSample,2,input(ADC_DOUT)); delay_us(50); } /* All done. Bring CS back HI */ output_high (ADC_CS); } /**************************************************************/ /* */ /* Self Test */ /* */ /* This Self Test is triggered by state of PIN_C5 on */ /* power up. We are using a special test jig for */ /* this test. This is done at manufacturing time. */ /* Look at first part of main() to see how self test */ /* is triggered. */ /* */ /* The goal of self test is to catch basic board-level */ /* problems, unprogrammed chips, etc. This is NOT a */ /* performance tester. We test that relays flip and the */ /* switches are connected. We also test LEDs and test that */ /* the A/D seems to react to changes in voltage. */ /* This will catch most basic manufacturing problems. */ /* */ /* See the additional testing procedure for exactly what */ /* steps to do for the test. */ /* */ /**************************************************************/ void SelfTest () { /* Phase 1. Energize each relay and see if switch detects a low. The test jig is wired to help us with this... If not, test fails. First, check all switch inputs which should initially all be high. */ if (!input(CYCLE_SW) || !input(RECYCLE_SW) || !input(RETRACTED) || !input (EXTENDED)) { printf ("*"); /* for the ambitious technician... */ SelfTestFail (); } output_high (MOTOR_RLY); delay_ms (1000); if (input(CYCLE_SW)) { printf ("a"); /* for the ambitious technician... */ SelfTestFail (); } output_high (RECYCLE_RLY); delay_ms (1000); if (input(RECYCLE_SW)) { printf ("b"); /* for the ambitious technician... */ SelfTestFail (); } output_high (CHARGE_RLY); delay_ms (1000); if (input(RETRACTED)) { printf ("c"); /* for the ambitious technician... */ SelfTestFail (); } output_high (DIR_RLY); delay_ms (1000); if (input(EXTENDED)) { printf ("d"); /* for the ambitious technician... */ SelfTestFail (); } /* Phase 2. Enter into a loop where we sample the A/D voltage and indicate on the LEDs that we are reading a voltage. We aren't concerned about an actual, accurate indication. We want to see all the LEDs change and we want to see that the voltage affects this process. */ printf ("!"); /* for the ambitious technician... */ for (;;) { delay_ms (255); output_low (CYCLE3_LED); output_low (CYCLE2_LED); output_low (CYCLE1_LED); output_low (SPA_LED); output_low (CHARGE_LED); output_low (SERVICE_LED); GetBatteryVoltage(); /* The test fixture test voltage ranges approx. between 1.77V and 3.22V. This corresponds to samples between 442 - 805. Also, to display a "bar graph" indicator of this voltage, order the LEDs as they physically are ordered on the board. */ if (nADSample > 600) { output_high (CYCLE3_LED); if (nADSample > 620) { output_high (CYCLE2_LED); if (nADSample > 640) { output_high (CYCLE1_LED); if (nADSample > 660) { output_high (SERVICE_LED); if (nADSample > 680) { output_high (SPA_LED); if (nADSample > 700) { output_high (CHARGE_LED); } } } } } } } } /* Need to do something to indicate to the human that test has failed... Just blink LEDs forever... */ void SelfTestFail () { for (;;) { /* Light all the LEDs */ output_high (CYCLE1_LED); output_high (CYCLE2_LED); output_high (CYCLE3_LED); output_high (SPA_LED); output_high (CHARGE_LED); output_high (SERVICE_LED); /* Delay for 1/4 second */ delay_ms (250); /* Extinguish all the LEDs */ output_low (CYCLE1_LED); output_low (CYCLE2_LED); output_low (CYCLE3_LED); output_low (SPA_LED); output_low (CHARGE_LED); output_low (SERVICE_LED); /* Delay for 1/4 second */ delay_ms (1000); } } /* END! */