/******************************************************************************/ /* */ /* PWRMGR.C */ /* */ /******************************************************************************/ /* This program is an example of a PIC-based Power Manager. The PIC communicates over its serial port to a client using a simple ascii HEX protocol. Commands issued over this serial link, allow the client to read sensors, turn on/off pins (for power management) and most interesting; set a sleep interval. Sleep intervals cause the PIC to sleep for some amount of time, at the end of which, the PIC wakes up and reawakens the client - bClientally, an alarm clock. The program has been chopped up - don't expect this code to compile as is!! I figure clients don't need their work trotted about too shamelessly. BUT - I assure you, this code actually performed in a system. */ /* Select the specific PIC version to compile to. */ #include <16c73a.h> /* Tell compiler the crystal frequency. This allows the 'delay_ms' to work as well as providing timing to the UART. */ #use delay(clock=3686400, RESTART_WDT) /* Watchdog Timer (WDT) is used. The on reset default for the WDT prescaler is the maximum of 1:256. So, we must clear the WDT counter within about 18ms. The program is written as a main loop, so this shouldn't be a problem if the WDT is pet within this main loop. Care must be taken that no code enters into a loop, however. We will setup the serial driver so that it pets the watchdog. */ /******************************************************************************/ /* I/O is as follows: */ /* Pin */ /* . Direction */ /* . . Name & Description */ /*----------------------------------------------------------------------------*/ /* Sorry! SNIP! */ /* Assign direction to A,B and C ports. Remember that, pins are by default an input, so we need to specify only the outputs. Don't bother to specify the special-purpose pins, like the UART C6 and C7 pins because the RS232 specifier does that. */ #use fixed_io(a_outputs=PIN_A5) #use fixed_io(b_outputs=PIN_B0, PIN_B1, PIN_B2, PIN_B3) #use fixed_io(c_outputs=PIN_C0, PIN_C2, PIN_C3, PIN_C4, PIN_C5, PIN_C6) /* Define some specific pin names to the physical pins */ #define PIN_MAINPOWER PIN_C3 #define PIN_ ._OFF PIN_C4 #define PIN_BATTERYOK PIN_A2 #define PIN_BATTERYOFF PIN_RA5 #define PIN_BATTERYON PIN_C2 #define PIN_POWERA PIN_B0 #define PIN_POWERB PIN_B1 #define PIN_POWERC PIN_B2 #define PIN_POWERD PIN_B3 #define PIN_POWERE PIN_C5 /* Specify the UART speed and pins. Also specify the RESTART_WDT flag so that the WDT doesn't go off while waiting for a character! */ #use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7, PARITY=N, RESTART_WDT) /******************************************************************************/ /* TIMEBASE */ /* */ /* See Microchip Application Note AN582. */ /* */ /* We'll use TIMER1 for our timebase. A 32KHz watch crystal will driver */ /* timer1. Timer1 is a 16-bit counter, which can generate an interrupt */ /* on overflow. Timer1 is special. It will work even when the rest */ /* of the PIC is asleep. This interrupt can also wake up the PIC. This */ /* is perfect for implementing a real-time clock. The power draw for */ /* the Timer1 when the rest of the PIC is aslepp, is about 15 uA. */ /* */ /* 32.786KHz goes first to a prescaler. We will set the prescaler to its */ /* maximum of 8. From there, it goes to the timer1 increment. See p10-66 */ /* So, the overflow should occur every: */ /* */ /* 1 / (32786/8)(2**16) = 16 seconds */ /* */ /* Therefore, *** 1 "Tick" is 16 seconds *** */ /* */ /* The interrupt routine is InterruptTimer1Overflow, which will */ /* increment the real-time counters. */ /* */ /******************************************************************************/ /* Real-time interval. These variables are set by the Set Alarm command and then countdown via the timer 1 overflow interrupt. Each counter is 8 bits. The nT0 counter is the Least Significant Byte. */ unsigned int nT0; /* least significant byte */ unsigned int nT1; unsigned int nT2; unsigned int nT3; /* most significant byte */ /* Use this tick counter for any other timeouts we may need. */ long int lTicksSinceBoot; /* Period check for battery. */ long int lLastTimeBatteryCheck; /* Check the battery every hour, which is 3600 seconds, or 225 ticks */ #define PERIODIC_BATTERY_CHECK 225 /* Serial Command Protocol In general, we use the 'xy' protocol.. Binary data is wrapped in a message starting with 'x' char and ending in 'y' char. All binary data is then encoded in a 2 byte hexadecimal. The last 2 binary bytes are a fletcher checksum. An exception to the hex rule is that if you wish to NOT use checksums, you may insert '*' characters instead (e.g. 4 ascii '*' characters). So, to send 4 bytes with the values: 0xA2 0x00 0x13 0xFF: xA20013FF****y The above message was 15 ASCII characters. The payload was the 4 bytes. This program includes a little state machine that'll handle the above. Once a message is captured, the final binary bytes are available in an array (char cMessage[6]) which individual commands can access. So, in specifying a message, I'll just concern myself with the binary. Commands Set Sleep Interval and Sleep Format: 0x01 t4 t3 t2 t1 Set the sleep interval countdown. t4 is the MSB. Use zeros in higher order bytes for shorter intervals. Units are in ticks, which are 16 seconds. For example, to sleep for 32 seconds: 0x01 0x00 0x00 0x00 0x02 Set Power Control Mask Format: 0x02 p Set the power control bits. P is encoded as follows: p = {x x x e d c b a} where x is a don't care bit and bits e,d,c,b,a are as follows: e = RC5 = ... d = RB3 = ... c = RB2 = ... b = RB1 = ... a = RB0 = ... Set Mask Format: 0x03 m Set the "mask" used to register Discrete Digital changes. In other words, we use the PORTB[7:4] bits in the change mode. PORTB changes may cause a sleeping PIC to wake up and wake up the client. These mask bits can mask out particular bits. The m bit is encoded as follows: m = {m4 m3 m2 m1 x x x x} Where x is don't care and m4 correspnds to the Digital Sense 4 bit, which physically goes to the PORTB[7] bit of the PIC. Likewise, m1 corresponds to the Digital Sense 1 bit, which physically goes to the PORTB[4] bit of the PIC. A '1' means that a change for that bit IS monitored. ** The power-up default mask value is 0xF0 which means all bits are monitored for changes. Read Analog Format: 0x04 p Where the p byte indicates which analog port to read, where 0x00 specifies the Analog Sense 1 port (channel #0 of the PIC A/D) and 0x01 specifies the Analog Sense 2 port (channel #1 of the PIC A/D) The PIC will respond with the binary message (wrapped in the 'xy' wrapper): 0x04 p v Where p is the port number echoed back and v is the 8-bit value read from the port. Read Discretes Format: 0x05 The current Digital Sense values are read and returned in the response: 0x05 v Where v is the bit vector as follows: v = {d4 d3 d2 d1 0 0 0 0} Where d4 is the value read from Digital Sense 4. Read Battery Format: 0x05 Read the voltage on the dedicated battery voltage analog input, which is connected to the PIC Analog input #3 (pin 5, AN3). The PIC reads the port and responds with: 0x05 v Where v is the 8-bit value. Read Status and Clear Format: 0x06 Read several flags. This is a destructive read. The PIC responds with: 0x06 flags Where flags is: flages = {d4 d3 d2 d1 0 0 p s} Where: d4 - discrete 4 has changed since last time this command was issued. d3 - discrete 3 " d2 - discrete 2 " d1 - discrete 1 " p - Cold Boot flag, 1=Cold Boot has occured since last time this command was executed. s - Sleep Interval expired, 1=Sleep interval has expired since last time this command was executed. Read All Data Format: 0x07 Read everything. This is intended as a convenient method to poll the PIC. The PIC responds with: 0x07 flags T3 T2 T1 T0 bat a1 a2 dig Where: flags = T3 = Sleep interval byte, MSB T2 = Sleep interval byte T1 = Sleep interval byte T0 = Sleep interval byte, LSB bat = Current battery voltage a1 = Current value of Analog Sense #1 a2 = Current value of Analog Sense #2 dig = Current value of discretes = {d4 d3 d2 d1 0 0 0 0} */ /**** Serial ****/ #define MAX_SERIAL_BYTES 10 char c; int nStateSerial; /* Serial state machine state variable */ int nBytes; /* Count of final binary bytes stored in cMessage. */ char cMessage[MAX_SERIAL_BYTES]; /* Buffer for incoming binary bytes after they are parsed. */ #define STATE_SERIAL_IDLE 0 #define STATE_SERIAL_HIGH 1 #define STATE_SERIAL_LOW 2 /* Message Handlers */ void ExecuteCommand (void); /* High-level dispatcher */ /* The following command codes are our published commands to be used by the Client client: 0x01 - Set Sleep Timer and Go To Sleep 0x02 - Set Power Control Mask 0x03 - Set Discrete Mask 0x04 - Read Analog Sense 0x05 - Read Discretes 0x06 - Read Battery Voltage 0x07 - Read and Clear State Flags 0x08 - Read All Data The remaining codes are for test: 0x11 - Read the tick counters 0x12 - Enter an infinite loop so we may see WDT go off. */ #define CMD_SETSLEEP 1 #define CMD_SETPOWER 2 #define CMD_SETMASK 3 #define CMD_READANALOG 4 #define CMD_READDISCRETES 5 #define CMD_READBATTERY 6 #define CMD_READFLAGS 7 #define CMD_READDATA 8 #define CMD_TEST1 0x11 #define CMD_TEST2 0x12 void ExecuteCommandSetAlarm (void); /* CMD = 1 */ void ExecuteCommandWriteDig (void); /* CMD = 2 */ void ExecuteCommandSetMask (void); /* CMD = 3 */ void ExecuteCommandReadADC (void); /* CMD = 4 */ void ExecuteCommandReadDig (void); /* CMD = 5 */ void ExecuteCommandReadBattery (void); /* CMD = 6 */ void ExecuteCommandReadStatus (void); /* CMD = 7 */ void ExecuteCommandReadData (void); /* CMD = 8 */ void ExecuteCommandTest1 (void); /* CMD = 0x11 */ void ExecuteCommandTest2 (void); /* CMD = 0x12 */ /* Set up bClient hardware modes here. */ void InitializePIC (void); short int bBatteryCheckEnabled; void CheckBattery (void); /* Functions that power up the client or put it to sleep. */ void WakeupClient (void); void PutClientToBed (void); /****************************************************************/ /* */ /* Interrupt Service Routines: */ /* 1. InterruptPortBChange */ /* 2. InterruptTimer1Overflow */ /* */ /****************************************************************/ /* PORTB Change Interrupt */ void InterruptPortBChange (void); /* ISR! */ /* Timer1 Overflow Interrupt */ void InterruptTimer1Overflow (void); /* ISR! */ /* Reset power up stuff */ short int bColdBoot; /* TRUE is PIC reset because of a power-up or reset */ /**** ADC ****/ int byADCPort; int byADCSample; int byLastADCPort; void ReadADC (void); /* PORTB Variables Our internal variables are all based on bit positions of actual PORTB bits. The PORTB discretes are PB7:4. */ /* The following is a special CCS compiler feature. */ #BYTE PORTB = 6 int nPortBChange; /* Ones indicate changes to PORTB since last read */ int nPortBBefore; /* Remember PORTB for when a change interrupt occurs. */ int nPortBMask; /* Enable/Mask bits we want port change detection on. */ /****************************************************************/ /* */ /* SLEEP */ /* */ /****************************************************************/ /* The following flag indicates whether an interval has been setup and the PIC should now go to sleep. */ short int bSleepRequested; short int bSleeping; /* The following flag indicates whether the time interval expired TRUE - Time interval expired, as detirmined by Timer1Overflow ISR. FALSE - Interval is not expired. */ short int bSleepIntervalExpired; /* for debug.. */ #BYTE T1CON = 0x10 #BYTE INTCON = 0x0B #BYTE PIE1 = 0x8C main () { /* Well, let's initialize hardware stuff. */ InitializePIC (); /* Delay just a little bit so that we get a stable UART to output with. */ delay_ms (1); printf ("\n\rPower Manager."); /* Did a WDT cause this? */ if (restart_cause() == WDT_TIMEOUT) { printf ("\n\rWDT Time Out!"); } if (restart_cause() == WDT_FROM_SLEEP) { printf ("\n\rWDT From Sleep!"); } bBatteryCheckEnabled = BATTERY_CHECK_ENABLED; /* Since we are here, we must have be reseting from a power-up or a reset. We need to note this in a flag so that client knows if a sleep interval, or whatever has been lost. Note that if PIC goes to sleep, that it wakes up and continues executing where it left off, or executes an ISR that woke it up and then continues where it ledft off. This means that this code right here only happens on power-up. So, initialize the PIC etc. and do whatever to the tag as if it is fresh out of the box. */ bColdBoot = TRUE; /* Initialize bClient variables */ nStateSerial = STATE_SERIAL_IDLE; lTicksSinceBoot = 0; /* We are not sleeping... */ bSleeping = FALSE; bSleepRequested = FALSE; /* Zero out any PORTB information. */ nPortBMask = 0xF0; /* Default will be that NONE of the PortB inputs are masked. */ nPortBChange = 0x00; nPortBBefore = PORTB; /* Zero out our long sleep interval counters. */ nT0 = 0; nT1 = 0; nT2 = 0; nT3 = 0; /* Enable individual interrupts. */ enable_interrupts (INT_TIMER1); enable_interrupts (RB_CHANGE); /* Clear any PORTB interrupt that might have been pending. */ input(PIN_B7); /* Allow interrupts in general */ enable_interrupts (GLOBAL); /* Start Main Loop. Our main loop is our bClient embedded processor infinite loop. There are two main processes in this loop. One is for the SLEEP logic, and the other is the state machine for the serial commands. When the PIC is awake, it will skip over this first process. Likewise, when the PIC is asleep, it hangs out in this sleep loop and will ignore serial input. */ for (;;) { /* Pet the watchdog!! */ restart_wdt(); /* SLEEP Machine */ if (bSleepRequested) { /* The spec says we should wait 1 second before actually powering down the client. */ delay_ms (1000); /* delay 1 second. */ /* PutClientToBed does whatever needs doing to power everything down. */ PutClientToBed (); /* Zero out the TIMER1 */ set_timer1 (0); /* Set and clear various flags. */ nPortBChange = 0x00; nPortBBefore = PORTB; bSleepIntervalExpired = FALSE; bSleeping = TRUE; /* OK. Here comes our actual sleep loop. */ /** Ohh! wait.. turn UART Off and set pin C6 low. (also remember that this asm mode assumes decimal ?!?!) ***/ #asm bcf 24,7 bcf 7,6 #endasm SleepLoop: /* Do this in assembler to make sure we get some NOPs in there. */ #asm sleep nop nop nop #endasm /* These are the conditions on which we wake up. The interrupt routines are the first to execute after waking up, and they need to set or not set these flags as appropriate. */ if (bSleepIntervalExpired) goto WakeUp; if (nPortBChange) goto WakeUp; restart_wdt(); goto SleepLoop; WakeUp: /* Turn UART back ON! */ #asm bsf 24,7 #endasm /* Turn off the sleeping flag. This will make sure that the next TIMER1 tick interrupt doesn't try to work on the sleep interval. */ bSleeping = FALSE; /* OK. We just woke up. Call this function to do whatever power control we're supposed to do. */ WakeupClient (); delay_ms (100); if (nPortBChange) { printf ("Awake from PORTB Change (%02X)", nPortBChange); } else if (bSleepIntervalExpired) { printf ("Awake from Timeout"); } else { printf ("Why did we wake up?"); } /* Clean up */ bSleepRequested = FALSE; } /******** State Machine for the serial protocol ************/ /* The serial protocol is defined in the client firmware. We are using the "xy" protocol. The protocol is as follows. Messages are framed with lower-case 'x' and 'y'. All characters are ASCII. The last 4 characters before the final 'y' are for checksum. We are opting to not implement error checking, and therefore use the special '*' placeholder. The data to be sent is binary data. Each byte of data is represented by a 2 hex ASCII characters. The state machine looks for the initial 'x'. It then starts gathering bytes using 2 states: STATE_SERIAL_HIGH and STATE_SERIAL_LOW, which packet the bytes. The binary data bytes are stuffed into the buffer. The 'y' character will end the message. Non hex digits other than '*' will be dropped. A maximum character count will also be checked and if exceeded, the state machine will be reset. */ switch (nStateSerial) { /* IDLE state where we look for the starting 'x' character. */ case STATE_SERIAL_IDLE: if (kbhit()) { c = getc(); if (c == 'x') { nStateSerial = STATE_SERIAL_HIGH; nBytes = 0; } } break; /* Expecting the High nibble hex character. Might get the final 'y' character if end of message. Ignore any '*'s */ case STATE_SERIAL_HIGH: if (kbhit()) { c = getc(); if ( (c >= '0') && (c <= '9')) { cMessage[nBytes] = (c - '0') << 4; nStateSerial = STATE_SERIAL_LOW; } else if ( (c >= 'A') && (c <= 'F')) { cMessage[nBytes] = (c - 'A' + 10) << 4; nStateSerial = STATE_SERIAL_LOW; } else if (c == '*') { nStateSerial = STATE_SERIAL_HIGH; } else if (c == 'y') { /* That's it! We got a command. */ /* Reset the serial protocol, first */ nStateSerial = STATE_SERIAL_IDLE; /* OK. Now do it. */ ExecuteCommand (); } else { printf ("ERROR!"); nStateSerial = STATE_SERIAL_IDLE; } } break; /* Expecting the Low nibble hex character. If we get it, then stuff byte into buffer, increment byte counter and return to looking for next High nibble. */ case STATE_SERIAL_LOW: if (kbhit()) { c = getc(); /* Do a quick check to see if too many bytes have been stuffed. If so, we need to reset state machine. */ if (nBytes > MAX_SERIAL_BYTES) { /* Whoa! Too many bytes! Reset */ nStateSerial = STATE_SERIAL_IDLE; } else if ( (c >= '0') && (c <= '9')) { cMessage[nBytes] |= ((c - '0') & 0x0F); nBytes++; nStateSerial = STATE_SERIAL_HIGH; } else if ( (c >= 'A') && (c <= 'F')) { cMessage[nBytes] |= ((c - 'A' + 10) & 0x0F); nBytes++; nStateSerial = STATE_SERIAL_HIGH; } else if (c == '*') { nStateSerial = STATE_SERIAL_LOW; } else if (c == 'y') { /* Shouldn't have received this! */ printf ("ERROR!"); nStateSerial = STATE_SERIAL_IDLE; } else { printf ("ERROR!"); nStateSerial = STATE_SERIAL_IDLE; } } break; } /* Check the battery. We only do this for battery applications. */ if (bBatteryCheckEnabled) { /* Battery check */ if ((lTicksSinceBoot - lLastTimeBatteryCheck) > PERIODIC_BATTERY_CHECK) { /* Do it. Check the battery. Look at the input that is connected to the external Comparator. If the battery is OK, then the input should be HIGH. If it is LOW, then the battery is below proper operating threshold. */ if (!input(PIN_BATTERYOK)) { /* WHOA! Battery is too low. We need to shut everything down. Then, lock up! */ printf ("BATTERY FAIL! GO TO DEEP SLEEP!!!"); /* That's it! Go to infinit sleep. Disable interrupts and then issue the final, deep, dark sleep command.. */ disable_interrupts (GLOBAL); sleep(); } lLastTimeBatteryCheck = lTicksSinceBoot; } } } } /* Call this once after reset. Set up hardware configuration. */ void InitializePIC () { /* Turn on Main Power via the latching relays */ output_high (PIN_ ._OFF); output_high (PIN_MAINPOWER); /* Power up the client and I guess some other bClient things. Don't power the RF, though. Let the client decide that. Power up the ... so that it can get a fix quicker? */ output_high (PIN_POWERA); /* Power up the ..... */ output_low (PIN_POWERB); /* NO Power to Analog, including DClient's PLL. This will be done on board... */ output_high (PIN_POWERC); /* Power up the Digital (including the Client) */ output_low (PIN_POWERE); /* NO Power to the ...? */ /* Setup TIMER1 as our real-time clock. Set up so that timer1 is a counter (not a timer), enable the oscillator circuit, run asynchronously, set prescaler to 8, and go ahead and enable it now. */ setup_timer_1 (T1_EXTERNAL | T1_CLK_OUT | T1_DIV_BY_8); set_timer1 (0); /* Set up the A/D port. We need a total of 3 analog inputs, so let's use the A/D condifuration 4. Use the internal clock, but use a lower divider. This will be slower but will perhaps be lower in power (?). Since the client is using a serial command to ask for this anyway, the speed is not critical. */ setup_port_a (RA0_RA1_RA3_ANALOG); setup_adc (ADC_CLOCK_DIV_32); set_adc_channel (0); /* Turn on the internal pull-ups for PORTB. This allows end-user to connect a contact closure to the discrete inputs. Note that this will INCREASE power consumption! */ port_b_pullups (TRUE); /* setup_counters (RTCC_INTERNAL, WDT_2304MS); */ } /* This function is called once a command is framed and is in the little buffer. Based on the command code, call more specific handlers. Command codes: 0x01 - Set Sleep Timer and Go To Sleep CMD_SETSLEEP 0x02 - Set Power Control Mask CMD_SETPOWER 0x03 - Set Discrete Mask CMD_SETMASK 0x04 - Read Analog Sense CMD_READANALOG 0x05 - Read Discretes CMD_READDISCRETES 0x06 - Read Battery Voltage CMD_READBATTERY 0x07 - Read and Clear State Flags CMD_READFLAGS 0x08 - Read All Data CMD_READDATA Tests: 0x11 - Read timer ticks CMD_TEST1 0x12 - Read All Data CMD_TEST2 */ void ExecuteCommand (void) { /* First byte after the SOM (which is not queued) is command character. */ switch (cMessage[0]) { case CMD_SETSLEEP: ExecuteCommandSetAlarm (); break; case CMD_SETPOWER: ExecuteCommandWriteDig (); break; case CMD_SETMASK: ExecuteCommandSetMask (); break; /* Read A/D Converter Command. */ case CMD_READANALOG: ExecuteCommandReadADC (); break; /* Read Discrete Digital Port Command. */ case CMD_READDISCRETES: ExecuteCommandReadDig (); break; case CMD_READBATTERY: ExecuteCommandReadBattery (); break; case CMD_READFLAGS: ExecuteCommandReadStatus (); break; case CMD_READDATA: ExecuteCommandReadData (); break; /* Remaining are just for testing. */ case CMD_TEST1: ExecuteCommandTest1 (); break; case CMD_TEST2: ExecuteCommandTest2 (); break; case 0x13: printf ("Regs: %02X %02X %02X", T1CON, INTCON, PIE1); break; default: printf ("?"); break; } } /***************************************************************************/ /* */ /* */ /* Specific Command Handlers */ /* */ /* */ /***************************************************************************/ /* Set Alarm and Go To Sleep CMD_SETSLEEP In this command handler, get the time arguments out of the message, copy them into our long interval counters, respond, set the flag indicating that an interval has been set up, and then return. Let the main loop actually initate and deal with the sleep loop. */ void ExecuteCommandSetAlarm (void) { nT3 = cMessage[1]; nT2 = cMessage[2]; nT1 = cMessage[3]; nT0 = cMessage[4]; /* No response */ /* OK. A SLEEP command has been received and the interval is all set up. Enter our sleep mode. When we go to sleep, we will still get interrupts from the PORTB change detector and the TIMER1 overflow interrupt. */ bSleepRequested = TRUE; } /* Set Power Control Bits (e.g. write digital) CMD_SETPOWER */ void ExecuteCommandWriteDig (void) { /* If the client sends all zeros, let's go ahead and turn off the main power. This is sort of like committing suicide, but we'll allow the client to do so. Sine there is no provision for waking the client back up, tag remains off until a cold boot! */ if (cMessage[1] == 0b00000000) { output_low (PIN_MAINPOWER); /* Power down the main power! */ output_low (PIN_ ._OFF); /** Ohh! wait.. turn UART Off and set pin C6 low. (also remember that this asm mode assumes decimal ?!?!) ***/ #asm bcf 24,7 bcf 7,6 #endasm /* No need to continue.*/ return; } // The 'e' output corresponds to PIN_C5 or ... if (cMessage[1] & 0b00000001) output_high (PIN_POWERE); else output_low (PIN_POWERE); // The 'd' output corresponds to PIN_B3 or 4.0VA_ON if (cMessage[1] & 0b00000010) output_high (PIN_POWERD); else output_low (PIN_POWERD); // The 'c' output corresponds to PIN_B2 if (cMessage[1] & 0b00000100) output_high (PIN_POWERC); else output_low (PIN_POWERC); // The 'b' output corresponds to PIN_B1 if (cMessage[1] & 0b00001000) { output_high (PIN_POWERB); } else { output_low (PIN_POWERB); } // The 'a' output corresponds to PIN_B0 if (cMessage[1] & 0b00010000) output_high (PIN_POWERA); else output_low (PIN_POWERA); /* No response to this command. */ } /* Set Discrete Mask CMD_SETMASK The mask is left justified so that it aligns with the PORTB bits easily. Mask = {m4 m3 m2 m1 x x x x} Where m4 is the mask for PIN_B7 which is assign to Digital Sense 4. When m4 = 1 then a change on this pin will regsiter as a change event and show up in the change bits and potentially awake from processor from sleep state. Note that *ANY* change on PORTB[7:4] will wake up the processor and cause an interrupt. The mask is for this program's detirmination of what a "change" is. */ void ExecuteCommandSetMask (void) { nPortBMask = cMessage[1]; } /* Read Analog Sense CMD_READANALOG Valid ports are 0 and 1. */ void ExecuteCommandReadADC (void) { byADCPort = cMessage[1]; ReadADC (); /* Now, respond (respond in HEX for testing) */ printf ("x%02X", CMD_READANALOG); printf ("%02X%02X****y", byADCPort, byADCSample); } /* Read Discretes CMD_READDISCRETES */ void ExecuteCommandReadDig (void) { int byDigitalSample; byDigitalSample = PORTB; byDigitalSample = byDigitalSample & 0xF0; printf ("x%02X%02X****y", CMD_READDISCRETES, byDigitalSample); } /* Read Battery CMD_READBATTERY The battery sense voltage is connected to RA3. */ void ExecuteCommandReadBattery (void) { byADCPort = 3; ReadADC (); /* Now, respond */ printf ("x%02X%02X****y", CMD_READBATTERY, byADCSample); } /* Read and Clear Status Flags CMD_READFLAGS The status byte format is: [ d4 d3 d2 d1 0 0 p s ] Where: d4 - discrete 4 has changed since last time this command was issued. d3 - discrete 3 " d2 - discrete 2 " d1 - discrete 1 " p - Cold Boot flag, 1=Cold Boot has occured since last time this command was executed. s - Sleep Interval expired, 1=Sleep interval has expired since last time this command was executed. */ void ExecuteCommandReadStatus (void) { int nStatusBits; /* Or in all the bits of this status vector */ nStatusBits = 0; /* Start with the PortB Change flags. */ nStatusBits = nPortBChange & 0xF0; /* Set the 'p' bit if bColdBoot is set. */ if (bColdBoot) bit_set (nStatusBits, 1); /* Set the 's' bit if bSleepIntervalExpired is true. */ if (bSleepIntervalExpired) bit_set (nStatusBits, 0); /* Respond */ printf ("x%02X%02X****y", CMD_READFLAGS, nStatusBits); /* Clear these flags. */ bColdBoot = FALSE; nPortBChange = 0; nPortBBefore = PORTB; bSleepIntervalExpired = FALSE; } /* Read All Data CMD_READDATA There are N data bytes as follows: Flags - Same format as the flags in Read Flags command T3 - MSB of Sleep Interval T2 - Sleep Interval T1 - Sleep Interval T0 - LSB of Sleep Interval Battery Voltate - Current battery voltage Analog Sense 1 - Current value of Analog Sense #1 Analog Sense 2 - Current value of Analog Sense #2 Discretes - Current value of discretes [ d4 d3 d2 d1 0 0 0 0 ] */ void ExecuteCommandReadData (void) { int byAnalog1, byAnalog2, byBatteryVoltage, byDiscretes; int byStatusBits; /* Or in all the bits of this status vector */ byStatusBits = 0; /* Start with the PortB Change flags. */ byStatusBits = nPortBChange & 0xF0; /* Set the 'p' bit if bColdBoot is set. */ if (bColdBoot) bit_set (byStatusBits, 1); /* Set the 's' bit if bSleepIntervalExpired is true. */ if (bSleepIntervalExpired) bit_set (byStatusBits, 0); /* Read Battery voltage */ byADCPort = 3; ReadADC (); byBatteryVoltage = byADCSample; /* Read Analog Sense #1 */ byADCPort = 0; ReadADC (); byAnalog1 = byADCSample; /* Read Analog Sense #2 */ byADCPort = 1; ReadADC (); byAnalog2 = byADCSample; /* Read discretes. */ byDiscretes = PORTB & 0xF0; printf ("x%02X", CMD_READDATA); printf ("%02X", byStatusBits); printf ("%02X", nT3); printf ("%02X", nT2); printf ("%02X", nT1); printf ("%02X", nT0); printf ("%02X", byBatteryVoltage); printf ("%02X", byAnalog1); printf ("%02X", byAnalog2); printf ("%02X", byDiscretes); printf ("****y"); /* Clear the flags since we read them. */ bColdBoot = FALSE; nPortBChange = 0; nPortBBefore = PORTB; bSleepIntervalExpired = FALSE; } /** TEST2 - Print out tick info **/ void ExecuteCommandTest1 (void) { long int t; /* This is our test command */ t = get_timer1(); printf ("S=(%02X %02X %02X %02X), ", nT3, nT2, nT1, nT0); printf ("Ticks=%lX, ", lTicksSinceBoot); printf ("T1=%lX", t); break; } /** TEST2 - Enter tight loop for WDT test **/ void ExecuteCommandTest2 (void) { /* This should do it... */ for (;;) {} } /*************** End of Specific Handlers ******************/ void ReadADC (void) { /* Don't change channels unless neccessary */ if (byLastADCPort != byADCPort) { byLastADCPort = byADCPort; set_adc_channel (byADCPort); /* Now, we are supposed to delay very briefly if the channel has been changed. 1ms is probably very, very conservative. Since this is all part of a 9600 baud command sequence, then 1ms is a small percentage of the transaction, so don't worry about it... */ delay_ms(1); } /* Get the value. */ byADCSample = read_adc(); } /* Client Sleep/Wakeup functions. Use these to put the client asleep, or to wake it up. This is mostly just a matter of outputing the right bits on the right outputs. */ void WakeupClient (void) { /* Start main power first.. */ output_high (PIN_ ._OFF); output_high (PIN_MAINPOWER); /* Power up the Client and I guess some other bClient things. Don't power the .., though. Let the Client decide that. Power up the ... so that it can get a fix quicker? */ output_high (PIN_POWERA); /* Power up the ..... */ output_low (PIN_POWERB); /* No Power up the Analog, including DClient PLL. */ output_high (PIN_POWERC); /* Power up the Digital (including the Client) */ output_low (PIN_POWERE); /* No Power to the ...? */ } void PutClientToBed (void) { printf ("\n\rPut Client to sleep.."); /* Remove power to the client and I guess all the other things. */ output_low (PIN_POWERA); output_low (PIN_POWERB); output_low (PIN_POWERC); output_low (PIN_POWERD); output_low (PIN_POWERE); output_low (PIN_ ._OFF); output_low (PIN_MAINPOWER); /* Power down the main power. Technically, we could have done this line all by itself... */ } /*************************************************************************/ /* */ /* Interrupt Service Routines */ /* */ /* Interrupts are: */ /* 1. Timer1 Overflow */ /* 2. Port B Change */ /* */ /* */ /*************************************************************************/ #INT_TIMER1 void InterruptTimer1Overflow (void) { /* Do we need to clear the interrupting condition? Maybe, by reading it? */ get_timer1(); /* In addition to the sleep interval, we need to always maintain a simple ticks counter. This can be used for timeouts and other functions that need to be done periodically. */ lTicksSinceBoot++; /* Just for testing... remove later. */ /* putc ('.');*/ /* Don't proceed if we are not asleep */ if (bSleeping) { /* Check if interval is through. */ if (nT3 == 0 && nT2 == 0 && nT1 == 0 && nT0 == 0) { bSleepIntervalExpired = TRUE; return; } /* Decrement counters, check for roll-overs, etc. Each byte is, obviously, 8-bits. When we decrement it, it should eventually wrap and go from 0x00 to 0xFF. Whenever a byte wraps, we should decrement the next higher byte. Once a byte does not wrap, exit out of the function. */ nT0--; if (nT0 == 0xFF) { /* Roll Over! */ nT1--; if (nT1 == 0xFF) { /* Roll Over! */ nT2--; if (nT2 == 0xFF) { /* Roll Over! */ nT3--; } } } } } /*** Interrupt Service Routine is called if any change on the ports ***/ #INT_RB void InterruptPortBChange (void) { /* Read the bits. Reading any one bit will clear the interrupt. The variable nPortBChange should have 1's for bits that have changed. nPortBBefore os the starting state for these bits. The ^ operator will return to us 1's for bits that are now different. We finally AND this with our MASK. */ nPortBChange = nPortBChange | (nPortBMask & ((PORTB & 0xF0) ^ nPortBBefore)); nPortBBefore = PORTB; /* Just set a flag. If we are sleeping, then the dream function will pick this up and wake up. */ /* printf ("PORTB!");*/ } /*** END OF PROGRAM ***/