/******* PV.c ****************** * * Qwik&Low Performance Verification * * STARTUP: * - display SSN on QwikBug console * - blink LED for half a second * - test segments of LCD display * CONTINUOUSLY: * - use Timer1 and the crystal oscillator to display seconds counter on middle of LCD * & blink LED every second * - display temperature on left of LCD and update every second * - display POT value on right of LCD * - use RPG to scroll through segments of LCD displaying for two seconds * - increment/decrement number displayed on console for each CW/CCW RPG increment * - keep LED lit when pushbutton pressed * Current draw = 20 uA without LED & LCD * * Developers: Chris Bruhn & Peter Ralston * ******* Program hierarchy ***** * * main * Initial * InitTX * DS2401 * PresenceTest * SendByte * ReadByte * SaveByte * SendSSNSTRING * LCDTest * Display * Timer1Check * ASCII4 * Temperature * ASCII4 * ReadPot * RPGHandle * Display * Pushbutton * * LoPriISR * * HiPriISR * ******************************* */ #include // Define PIC18LF4321 registers and bits /******************************* * Configuration selections ******************************* */ #pragma config OSC = INTIO1 // Use internal osc, RA6=Fosc/4, RA7=I/O #pragma config PWRT = ON // Enable power-up delay #pragma config LVP = OFF // Disable low-voltage programming #pragma config WDT = OFF // Disable watchdog timer initially #pragma config WDTPS = 4 // 16 millisecond WDT timeout period, nominal #pragma config MCLRE = ON // Enable master clear pin #pragma config PBADEN = DIG // PORTB<4:0> = digital #pragma config CCP2MX = RB3 // Connect CCP2 internally to RB3 pin #pragma config BOR = SOFT // Brown-out reset controlled by software #pragma config BORV = 3 // Brown-out voltage set for 2.0V, nominal #pragma config LPT1OSC = OFF // Deselect low-power Timer1 oscillator /******************************* * Global variables ******************************* */ unsigned int DELAY; // Counter used by delay macro signed int BIGNUM; // Sixteen-bit parameter for ASCII conversion unsigned char THOUSANDS, HUNDREDS, TENS, ONES; // ASCII coding of digits unsigned char BYTETOSEND; // Used by SendByte unsigned char SSNBYTE; // Used to store byte read from DS2401 unsigned char TEMPCNT; // Counter for reading temperature unsigned char i, j; // Indexing unsigned long VALUE; // Thirty-two bit value of temperature for // calc. unsigned char TMR1L_OLD; // To wait for Timer1 to change unsigned char SEC; // Used for counting/displaying seconds with // Timer1 unsigned char POT; // Value of potentiometer unsigned char OLDPOT; // Used in ReadPot for storing old value unsigned char RPG; // Value of RPG unsigned char RPGFLAG; // Flag that RPG value has changed unsigned char LCDSTR; // Sets which string to use unsigned char LCDFLAG; // Flag to display LCD string /******************************* * Variable strings ******************************* */ char LCDTEST[] = { 16, 16, 16, 16, 16, 16, 16, 16, 16 }; // Nine-character test display string. char LCDSTRING[] = " "; // Nine-character normal display string char SSNSTRING[] = " "; // DS2401's 64-bit serial number /******************************* * Constant array in program memory ******************************* */ const char rom FormHex[] = "0123456789ABCDEF"; /******************************* * Function prototypes ******************************* */ void Initial(void); void InitTX(void); void HiPriISR(void); void LoPriISR(void); void DS2401(void); char PresenceTest(void); void SendByte(void); void ReadByte(void); void SaveByte(void); void SendSSNSTRING(void); void LCDTest(void); void Timer1Check(void); void Temperature(void); void RPGHandle(void); void ReadPot(void); void Pushbutton(void); void Display(void); void ASCII4(void); /******************************* * Macros ******************************* */ #define Delay(x) DELAY = x; while(--DELAY){ Nop(); Nop(); } #define TXascii(in) TXREG = in; while(!TXSTAbits.TRMT) #define OpenDrainHigh TRISDbits.TRISD2 = 1 #define OpenDrainLow PORTDbits.RD2 = 0; TRISDbits.TRISD2 = 0 /******************************* * Interrupt vectors ******************************* */ // For high priority interrupts: #pragma code high_vector=0x08 void interrupt_at_high_vector(void) { _asm GOTO HiPriISR _endasm} #pragma code #pragma interrupt HiPriISR // For low priority interrupts: #pragma code low_vector=0x18 void interrupt_at_low_vector(void) { _asm GOTO LoPriISR _endasm} #pragma code #pragma interruptlow LoPriISR /////// Main program ////////////////////////////////////////////////////////// /******************************* * main ******************************* */ void main() { Initial(); // Initialize everything DS2401(); // Read and display SSN on PC LCDTest(); // Initial Test of LCD Segments T1CONbits.TMR1ON = 1; // Start Timer1 counting WDTCONbits.SWDTEN = 1; // Enable watchdog timer while (1) { Timer1Check(); // Timer1 counts/displays seconds and blinks LED Temperature(); // Read/Display Temperature every second ReadPot(); // Read/Display POT value when changed RPGHandle(); // Scroll through LCD segments in response to // RPG turns if (LCDFLAG) // Display to LCD if flag set { Display(); LCDFLAG = 0; } Pushbutton(); // Turn on LED while pushbutton is pressed Sleep(); // Sleep, letting watchdog timer wake up chip Nop(); } } /******************************* * Initial * * This function performs all initializations of variables and registers. ******************************* */ void Initial() { OSCCON = 0b01100010; // Use Fosc = 4 MHz (Fcpu = 1 MHz) SSPSTAT = 0b00000000; // Set up SPI for output to LCD SSPCON1 = 0b00110000; ADCON1 = 0b00001011; // RA0,RA1,RA2,RA3 pins analog; others // digital TRISA = 0b00001111; // Set I/O for PORTA TRISB = 0b01000100; // Set I/O for PORTB TRISC = 0b10000000; // Set I/O for PORTC TRISD = 0b10000000; // Set I/O for PORTD TRISE = 0b00000010; // Set I/O for PORTE PORTA = 0; // Set initial state for all outputs low PORTB = 0; PORTC = 0; PORTD = 0b00100000; // except RD5 that drives LCD interrupt PORTE = 0; SSPBUF = ' '; // Send a blank to initialize state of LCD Delay(50000); // Pause for half a second RCONbits.SBOREN = 0; // Now disable brown-out reset TEMPCNT = 60; // Convert temperature immediately InitTX(); // Initialize UART T1CON = 0b01001110; // Timer1 configuration TMR1H = 0xFF; // Set Timer1 to roll over when enabled TMR1L = 0xFF; PIE1bits.TMR1IE = 0; // Timer1 interrupts disabled PIR1bits.TMR1IF = 0; // Clear overflow flag SEC = 0; // Next second to display for Timer1 POT = 1; RPG = 16; // Set out of range so first turn would be 0 // or 15 depending on direction RPGFLAG = 0; PORTEbits.RE2 = 1; // Power up the pullup for the RPG's // interrupt Delay(10); // Pause for 100 us INTCON2bits.INTEDG2 = !PORTBbits.RB2; // Select initial interrupt // edge INTCON3bits.INT2IE = 1; // Enable INT2 interrupt source INTCON3bits.INT2IP = 1; // Use INT2 for high-priority interrupts INTCON3bits.INT2IF = 0; // Clear interrupt flag LCDSTR = 0; // Use LCDTEST for display initially T3CON = 0b00000111; // Timer3 configuration (used for LCDTEST // display timing) PIE2bits.TMR3IE = 1; // Enable local interrupt source IPR2bits.TMR3IP = 0; // Timer3 set for low-priority interrupts PIR2bits.TMR3IF = 0; // Clear interrupt flag T3CONbits.TMR3ON = 0; // Timer3 starts up off RCONbits.IPEN = 1; // Enable high/low priority interrupt feature INTCONbits.GIEL = 1; // Global low-priority interrupt enable INTCONbits.GIEH = 1; // Enable both high interrupts LCDFLAG = 0; } /******************************* * InitTX * * This function initializes the UART for its TX output function. ******************************* */ void InitTX() { RCSTA = 0b10010000; // Enable UART TXSTA = 0b00100000; // Enable TX SPBRG = 12; // Set baud rate BAUDCON = 0b00111000; // Invert TX output } /******************************* * HiPriISR * * Reads RPG ******************************* */ void HiPriISR() { PORTEbits.RE0 = 1; // Power pullup resistor to read direction INTCON3bits.INT2IF = 0; // Reset interrupt flag if (PORTEbits.RE1 == INTCON2bits.INTEDG2) // Direction? { if (RPG >= 15) // RPG max value to 15 { RPG = 0; } else { ++RPG; } } else { if (RPG == 0) // RPG min value to 0 { RPG = 15; } else { --RPG; } } PORTEbits.RE0 = 0; // Power down pullup resistor INTCON2bits.INTEDG2 ^= 1; // Toggle edge sensitivity RPGFLAG = 1; } /******************************* * LoPriISR * * Timeout for displaying LCDTEST triggered by Timer3. * Sets flag for using LCDSTRING for display and turns Timer3 off ******************************* */ void LoPriISR() { LCDSTR = 1; // Use LCDSTRING for display T3CONbits.TMR3ON = 0; // Turn Timer3 off PIR2bits.TMR3IF = 0; // Clear interrupt flag } /******************************* * DS2401 (Serial Number) * * This function displays on the PC the serial number given by the DS2401. * If it is not present, turn on red LED and halt. ******************************* */ void DS2401() { PORTDbits.RD3 = 1; // Apply power to SSN part OpenDrainLow; Delay(50); // Master Reset with 500 us negative pulse OpenDrainHigh; if (!PresenceTest()) // Presence Test { PORTDbits.RD4 = 1; // Turn on LED Sleep(); // and be done Nop(); } else { BYTETOSEND = 0x33; // Send "Read ROM" command to get back SendByte(); // serial number for (j = 16; j > 0; j -= 2) // Read and save 8 bytes { ReadByte(); SaveByte(); } SendSSNSTRING(); } OpenDrainHigh; // Leave I/O pin as an input PORTDbits.RD3 = 1; // Leave SSN part empowered } /******************************* * PresenceTest * * This function returns a 1 if DS2401 is present and a 0 otherwise. ******************************* */ char PresenceTest() { Delay(10); // After 100 us if (!PORTDbits.RD2) // check for Presence { while (!PORTDbits.RD2) ; // Wait for line to be released return 1; } return 0; } /******************************* * SendByte * * This function sends BYTETOSEND to DS2401. Tslot = 85 us for ONE or for ZERO. ******************************* */ void SendByte() { for (i = 0; i <= 7; i++) // Send 8 bits of command { if (BYTETOSEND & 1) // Test bit 0 { OpenDrainLow; OpenDrainHigh; // Send ONE Delay(6); } else { OpenDrainLow; Delay(6); // Send ZERO OpenDrainHigh; } BYTETOSEND >>= 1; // Move on to next bit } } /******************************* * ReadByte * * This function reads in the 64-bit serial number a byte at a time. * Tslot = 70 us for ONE or for ZERO. * DS2401 holds line low for zero for 35 us. ******************************* */ void ReadByte() { SSNBYTE = 0; // initialize SSNBYTE for (i = 0; i <= 7; i++) // Read 8 bits from DS2401 { SSNBYTE >>= 1; // move on to next bit OpenDrainLow; OpenDrainHigh; Delay(1); // Sample 13 us after 'clocking' if (PORTDbits.RD2) { SSNBYTE += 0b10000000; // Copy bit into SSNBYTE } Delay(3); } } /******************************* * SaveByte * * This function converts the binary value in SSNBYTE into two * ASCII-coded digits in SSNSTRING. ******************************* */ void SaveByte() { SSNSTRING[j - 2] = FormHex[SSNBYTE >> 4]; // Save upper nibble SSNSTRING[j - 1] = FormHex[SSNBYTE & 0x0F]; // Save lower nibble } /******************************* * SendSSNSTRING * * This function sends SSNSTRING to the PC. ******************************* */ void SendSSNSTRING() { for (i = 0; i < 16; i++) { TXascii(SSNSTRING[i]); // send all bytes until null terminator } TXascii('\r'); TXascii('\n'); } /******************************* * LCDTest * * Display all segments on for two seconds, off for one second, * eights for two seconds and all other segments for two seconds ******************************* */ void LCDTest() { PORTDbits.RD4 = 1; // Initially blink LED and wait half Delay(50000); // a second before starting test PORTDbits.RD4 = 0; Display(); // Display all segments for 1 second Delay(50000); Delay(50000); LCDTEST[0] = 'O'; // Display all letter Os for 1 second LCDTEST[1] = 'O'; LCDTEST[2] = 'O'; LCDTEST[3] = 'O'; LCDTEST[4] = 'O'; LCDTEST[5] = 'O'; LCDTEST[6] = 'O'; LCDTEST[7] = 'O'; LCDTEST[8] = 'O'; Display(); Delay(50000); Delay(50000); LCDTEST[0] = 17; // Display all other segments for 1 second LCDTEST[1] = 17; LCDTEST[2] = 17; LCDTEST[3] = 17; LCDTEST[4] = 17; LCDTEST[5] = 17; LCDTEST[6] = 17; LCDTEST[7] = 17; LCDTEST[8] = 17; Display(); Delay(50000); Delay(50000); LCDSTR = 1; // Use LCDSTRING to display nothing for 1 // second Display(); Delay(50000); Delay(50000); } /******************************* * Timer1Check * * Uses Timer1 to increment display at position 3 and 4 of the LCD * and blink LED every second ******************************* */ void Timer1Check() { PORTDbits.RD4 = 0; // Turn off LED if (PIR1bits.TMR1IF == 1) { INTCONbits.GIEH = 0; // Suspend interrupts --- critical region TMR1L_OLD = TMR1L; // Wait for Timer1 to change while (TMR1L_OLD == TMR1L); TMR1H += 0x80; // Next interrupt one second after last one PIR1bits.TMR1IF = 0; // Clear interrupt flag INTCONbits.GIEH = 1; // Reenable interrupts if (++SEC == 100) { SEC = 0; } BIGNUM = SEC; // Get characters for next second's display ASCII4(); LCDSTRING[3] = TENS; // Setup for displaying new seconds on LCD LCDSTRING[4] = ONES; LCDFLAG = 1; PORTDbits.RD4 = 1; // Turn on LED for 16 ms every second } } /******************************* * Temperature * * Update temperature display every second using Figure 9-9 algorithm. ******************************* */ void Temperature() { if (++TEMPCNT == 63) // Increment counter and return if not 31 { TEMPCNT = 0; // Reset TEMPCNT PORTDbits.RD6 = 1; // Power up temperature sensor ADCON1 = 0b00011011; // Use VREF+ ADCON2 = 0b00001001; // Left-justify result Delay(2); // Wait 20 usec ADCON0 = 0x07; // Power up ADC; select temperature; convert while (ADCON0bits.GO_DONE) ; // Wait for completion of conversion PORTDbits.RD6 = 0; // Power down temperature sensor ADCON0bits.ADON = 0; // Power down ADC VALUE = ADRES; // Copy to 32-bit VALUE to avoid overflow VALUE *= 2121; // These 5 lines add 216 bytes and 111 us VALUE >>= 8; VALUE >>= 8; VALUE += 159; BIGNUM = VALUE; // Copy result to 16-bit BIGNUM ASCII4(); // Break out digits LCDSTRING[0] = HUNDREDS; LCDSTRING[1] = TENS; LCDSTRING[2] = '?'; // Degree symbol PORTBbits.RB1 = 0; LCDFLAG = 1; } } /******************************* * ReadPot * * Reads the pot and displays as a hex value at positions 6 and 7 on the LCD ******************************* */ void ReadPot() { OLDPOT = POT; // Hold old value for comparing if changed ADCON1 = 0x0b; // Initialize ADC for Q&L's four analog // inputs ADCON2 = 0x09; // ADC clock with Fosc = 4 MHz; left justify // result PORTAbits.RA7 = 1; // Power up potentiometer (Figure 3-2) ADCON0 = 0x03; // Power up ADC; select pot input; start // conversion while (ADCON0bits.GO_DONE) ; // Wait for completion of conversion PORTAbits.RA7 = 0; // Power down potentiometer ADCON0bits.ADON = 0; // Power down ADC POT = ADRESH; // Read value from high byte if (POT != OLDPOT) // Update LCDSTRING with new value if pot has // changed { LCDSTRING[6] = FormHex[POT >> 4]; LCDSTRING[7] = FormHex[POT & 0x0F]; LCDFLAG = 1; } } /******************************* * RPGHandle * * Scrolls through all segments of the LCD in response to the RPG ******************************* */ void RPGHandle() { if (RPGFLAG) // Check if RPG has changed { RPGFLAG = 0; TXascii(FormHex[RPG >> 4]); // Display hex value of RPG on PC TXascii(FormHex[RPG & 0x0F]); TXascii('\r'); TXascii('\n'); LCDSTR = 0; LCDTEST[0] = RPG; // Display test segment LCDTEST[1] = RPG; LCDTEST[2] = RPG; LCDTEST[3] = RPG; LCDTEST[4] = RPG; LCDTEST[5] = RPG; LCDTEST[6] = RPG; LCDTEST[7] = RPG; LCDTEST[8] = RPG; LCDFLAG = 1; T3CONbits.TMR3ON = 0; // Reset Timer3 to timeout after 2 seconds TMR3L = 0; TMR3H = 0; T3CONbits.TMR3ON = 1; } } /******************************* * Pushbutton * * This function overrides the role of the BlinkAlive function and turns on * the LED for the duration of a pushbutton press. ******************************* */ void Pushbutton() { PORTEbits.RE0 = 1; // Power up the pushbutton Nop(); // Delay one microsecond before checking it if (!PORTDbits.RD7) // If pressed turn on LED for one sleep cycle { PORTDbits.RD4 = 1; } PORTEbits.RE0 = 0; // Power down the pushbutton } /******************************* * Display() * * This function sends LCDSTRING or LCDTEST to the LCD. ******************************* */ void Display() { PORTDbits.RD5 = 0; // Wake up LCD display for (i = 0; i <= 8; i++) { PIR1bits.SSPIF = 0; // Clear SPI flag if (LCDSTR) // Send characters from appropriate string { SSPBUF = LCDSTRING[i]; } else { SSPBUF = LCDTEST[i]; } while (!PIR1bits.SSPIF) ; // Wait for transmission to complete } PORTDbits.RD5 = 1; // Return RB5 high, ready for next string } /******************************* * ASCII4 * * This function converts the unsigned int parameter passed to it * in BIGNUM, that ranges between 0 and 9999, to four ASCII-coded digits * by performing successive subtractions. * Simplified by Chad Kersey. Takes up to 353 cycles. ******************************* */ void ASCII4() { ONES = TENS = HUNDREDS = THOUSANDS = '0'; // Initialize to ASCII zeroes while (BIGNUM >= 1000) { THOUSANDS++; BIGNUM -= 1000; } // Form THOUSANDS while (BIGNUM >= 100) { HUNDREDS++; BIGNUM -= 100; } // Form HUNDREDS while (BIGNUM >= 10) { TENS++; BIGNUM -= 10; } // Form TENS ONES += BIGNUM; // Form ONES }