Está en la página 1de 8

Serial Communications: The Inter-Integrated Circuit & Low Voltage Detect Interrupts

Andrew J Johnson 3018836 Andrew Saunders 3213187 EE4341 Lab 3 TA-Devin Persaud 2008-03-05

Abstract: Often battery powered devices lose power unexpected, and unless the device can deal with these brown outs, important data may be lost. For this purpose, low-voltage detect is a feature included on many microcontrollers. This Lab is designed to explore serial communication with an EEPROM device, timer interrupts, low voltage interrupts, and LCD display. These modules are all used together to create a timer device. This device should increment a counter displayed on the LCD each second. The past three values of the timer should be stored in the EEPROM so that if the power to the microcontroller is lost (unplugged), the previous values can be restored on power up. Introduction: Using a Microchip PIC18F4520 microcontroller, a 24LC256 EEPROM, and a HD44780 LCD display all connected to a PICDem 2 Plus Prototyping board, a simple timer device can be created that will store timer values even if the power supply is disconnected. The microcontroller will keep track of a counter that increments once every second (using the Timer 2 module) and displays this counter on the LCD display. Specifics on displaying characters on the HD44780 LCD display were explored in a previous lab. In the event that the power is disrupted, the microcontroller should send the current (and previous three) value of the counter serially to the 24LC256 EEPROM for powerless storage. When power is sent back to the microcontroller, the three values should be read from the EEPROM and the counter should then resume from where it was before the device lost power. Body: This lab requires extensive use of the HD44780 LCD display that has been used in previous labs. For the purpose of this lab, it will just be assumed that all the code for getting the LCD display to work is used and there are commands available to control the display which are: WriteDataXLCD(char); WriteCmdXLCD(SHIFT_CUR_LEFT); //displays ASCII chars //shifts the cursor one to the left

In order to display three digit numbers easily (one byte can be as high as 255, which requires 3 digits), a function for converting bytes to decimal and writing them was needed: void displayXLCD(unsigned char num) { unsigned char hundreds,tens,ones; if(num<10) { hundreds=0; tens=0; ones=num; } else if(output_count<100) { hundreds=0; ones=num%10; tens=(num-ones)/10; } else { ones=num%10; tens=((num-ones)%100)/10; hundreds=(num-(num%100))/100; } WriteDataXLCD(hundreds+48); //convert hundreds to ASCII WriteDataXLCD(tens+48); //convert tens to ASCII WriteDataXLCD(ones+48); //convert ones to ASCII }

The general format of the LCD will be to have the previous three values of the counter at low voltage detects (LVDs) displayed and the last value updated in real time. This can be seen in the figure below:

Figure 1: format of LCD display With LCD functions in place, the first step in creating the desired device is to get the timer to increment count every second and display it on the LCD. The count will be incremented using a TMR2 interrupt flag triggered low priority interrupt service routine. TMR2 is only one byte and it increments every instruction cycle. Therefore, with a FOSC of 4MHz (determined by integrated oscillator) and a prescaler and postscaler of 16, each time TMR reaches its maximum value of 255, 65.28 milliseconds have passed. In the ISR there can be a counter that counts 20 of these interrupts so when the interrupt counter reaches 20, the output counter can be incremented, for it will be close (close enough for the purposes of this lab) to one second. The actual time is: 1sec cycles instructions 4 16 prescale 16 postscale 255 20 int errupts 1.3056 sec 4000000cycles instruction int errupt TMR2 is set up with the following code: TMR2 = 0; T2CON = 0b011110111; IPR1bits.TMR2IP = 0; PIE1bits.TMR2IE = 1; //initialize TMR2 to start from scratch //Pre16 and Post16, enabled //TMR2 to PR2 Match High Interrupt Priority //enable interrupt

Next the value of the output counter should increment every twenty interrupts. This is done with a secondary counter in the low priority interrupt service routine triggered by TMR2 being full: void low_isr(void) { PIR1bits.TMR2IF = 0; //clear interrupt flag TMR2 = 0; //reset TMR2 to zero if(timer_count == 20) //after 20 interrupts, ~1 second has passed { timer_count = 0; //reset the subcount WriteCmdXLCD(SHIFT_CUR_LEFT); WriteCmdXLCD(SHIFT_CUR_LEFT); WriteCmdXLCD(SHIFT_CUR_LEFT); //go back three digits displayXLCD(output_count); //output incremented count output_count++; //increment for next time } timer_count++; //increment subcount } Now that the microcontroller is counting seconds and displaying them, code must be set up for reading and writing to the external EEPROM for secure storage without power. The 24LC256 is initialized with the following code: DDRCbits.RC3 = 1; DDRCbits.RC4 = 1;

SSPSTAT = 0x80; SSPCON1 = 0x28; SSPADD = 0x09; SSPCON2 = 0x00; There is a built in library for the 24LC256 EEPROM, so the reading and writing is not difficult. As long as we have #include <i2c.h>, we are free to use the predefined read and write functions to serially interface to the EEPROM. They are shown here, but very small because they are long:
unsigned char HDByteWriteI2C(unsigned char ControlByte, unsigned char HighAdd, unsigned char LowAdd, unsigned char data ) { IdleI2C(); // ensure module is idle StartI2C(); // initiate START condition while ( SSPCON2bits.SEN ); // wait until start condition is over WriteI2C( ControlByte ); // write 1 byte - R/W bit should be 0 IdleI2C(); // ensure module is idle WriteI2C( HighAdd ); // write address byte to EEPROM IdleI2C(); // ensure module is idle WriteI2C( LowAdd ); // write address byte to EEPROM IdleI2C(); // ensure module is idle WriteI2C ( data ); // Write data byte to EEPROM IdleI2C(); // ensure module is idle StopI2C(); // send STOP condition while ( SSPCON2bits.PEN ); // wait until stop condition is over while (EEAckPolling(ControlByte)); //Wait for write cycle to complete return ( 0 ); // return with no error } unsigned char HDByteReadI2C( unsigned char ControlByte, unsigned char HighAdd, unsigned char LowAdd, unsigned char *data, unsigned char length ) { IdleI2C(); // ensure module is idle StartI2C(); // initiate START condition while ( SSPCON2bits.SEN ); // wait until start condition is over WriteI2C( ControlByte ); // write 1 byte IdleI2C(); // ensure module is idle WriteI2C( HighAdd ); // WRITE word address to EEPROM IdleI2C(); // ensure module is idle while ( SSPCON2bits.RSEN ); // wait until re-start condition is over WriteI2C( LowAdd ); // WRITE word address to EEPROM IdleI2C(); // ensure module is idle RestartI2C(); // generate I2C bus restart condition while ( SSPCON2bits.RSEN ); // wait until re-start condition is over WriteI2C( ControlByte | 0x01 ); // WRITE 1 byte - R/W bit should be 1 for read IdleI2C(); // ensure module is idle getsI2C( data, length ); // read in multiple bytes NotAckI2C(); // send not ACK condition while ( SSPCON2bits.ACKEN ); // wait until ACK sequence is over StopI2C(); // send STOP condition while ( SSPCON2bits.PEN ); // wait until stop condition is over return ( 0 ); // return with no error

For explanation purposes, assume there is already data stored on the EEPROM about the previous counter values. Our code reads this data in the initialization so that when the microcontroller is booted after a power loss, it will read the previous values and begin displaying on the LCD: void init(void) { //Restore LCD with old values and current value HDByteReadI2C(0xA0, 0x00,0x00, &stored_data, 0x03); output_count = stored_data[0]; displayXLCD(stored_data[3]); WriteDataXLCD(' '); displayXLCD(stored_data[2]); WriteDataXLCD(' '); displayXLCD(stored_data[1]); WriteDataXLCD(' '); displayXLCD(stored_data[0]); //3 values ago //space //2 values ago //space //last value of count at LVD //space //value of count at LVD

the only thing left to do is the low voltage detect interrupt service routine. The timer used the low priority ISR, because the power outage is more urgent, and it needs to use the high priority ISR. The initialization for the low voltage detect interrupt requires a bit of setup, mostly with HLVDCON. The bits for this register are shown here, as documented in the PIC18F4520 datasheet:
bit 7 VDIRMAG: Voltage Direction Magnitude Select bit 1 = Event occurs when voltage equals or exceeds trip point (HLVDL3:HLDVL0) 0 = Event occurs when voltage equals or falls below trip point (HLVDL3:HLVDL0) bit 5 IRVST: Internal Reference Voltage Stable Flag bit 1 = Indicates that the voltage detect logic will generate the interrupt flag at the specified voltage range 0 = Indicates that the voltage detect logic will not generate the interrupt flag at the specified voltage range and the HLVD interrupt should not be enabled bit 4 HLVDEN: High/Low-Voltage Detect Power Enable bit 1 = HLVD enabled 0 = HLVD disabled bit 3-0 HLVDL3:HLVDL0: Voltage Detection Limit bits(1)

so the setup can be done with the following segment of code found in init(): HLVDCON = 0b00011110; while(!LVDCONbits.IRVST); PIR2bits.HLVDIF = 0; IPR2bits.HLVDIP = 1; PIE2bits.HLVDIE = 1; //LVD, enable, flag at 15/16ths of VDD //wait for LVD logic to be ready //set interrupt flag to low //make LVD a high priority ISR //enable LVD interrupts

Now the ISR for the low voltage detect is coded so as to store your present and past count values for powerless storage. When the board loses power there is about 20 milliseconds with which the values can be stored, and that should be enough time to run through this ISR: #pragma interrupt high_isr void high_isr(void) { HDByteWriteI2C(0xA0, 0x00,0x00,output_count); HDByteWriteI2C(0xA0, 0x00,0x01,stored_data[0]); HDByteWriteI2C(0xA0, 0x00,0x02,stored_data[1]); HDByteWriteI2C(0xA0, 0x00,0x03,stored_data[2]); } Then at last the main function can be coded, and it is extremely trivial because everything is handled in interrupts: void main(void) { init(); while(1); } Conclusion: When the PICDEM boards power is unplugged, the LCD dies and all the lights fade, but when it is plugged back in the values appear on the LCD correctly and the counter continues to count from where it was as if the device had never lost power. This could be very useful for things like alarm clock, which always have to be reset in power outages, or medical equipment or any place where sensitive data might be lost in a power outage.

Code (for reference):


#include #include #include #include #include "Z:\4341lab3\p18f4520.h" "Z:\4341lab3\xlcd.h" "Z:\4341lab3\AN991.h" "Z:\4341lab3\i2c.h" <delays.h>

void low_isr(void); void high_isr(void); void init(void); void initDataToXLCD(void); void DelayFor18TCY(void); void DelayPORXLCD(void); void DelayXLCD(void); void displayXLCD(unsigned char); unsigned char HDByteWriteI2C( unsigned char ControlByte, unsigned char HighAdd, unsigned char LowAdd, unsigned char data ); unsigned char HDByteReadI2C( unsigned char ControlByte, unsigned char HighAdd, unsigned char LowAdd, unsigned char *data, unsigned char length ); unsigned char timer_count=0; unsigned char output_count=0; unsigned char stored_data[4]; #pragma code high_priority=0x08 void high_priority_vector(void) { _asm GOTO high_isr _endasm } #pragma code #pragma code low_priority=0x18 void low_priority_vector(void) { _asm GOTO low_isr _endasm } #pragma code #pragma interrupt high_isr void high_isr(void) { PIR2bits.LVDIF=0; HDByteWriteI2C(0xA0, HDByteWriteI2C(0xA0, HDByteWriteI2C(0xA0, HDByteWriteI2C(0xA0, }

0x00,0x00,output_count); 0x00,0x01,stored_data[0]); 0x00,0x02,stored_data[1]); 0x00,0x03,stored_data[2]);

#pragma interruptlow low_isr void low_isr(void) { char STATUS_BACK = STATUS; char W_BACK = WREG; char BSR_BACK = BSR; PIR1bits.TMR2IF = 0; TMR2 = 0; if(timer_count == 20) { timer_count = 0; WriteCmdXLCD(SHIFT_CUR_LEFT); WriteCmdXLCD(SHIFT_CUR_LEFT); WriteCmdXLCD(SHIFT_CUR_LEFT); displayXLCD(output_count); output_count++; } timer_count++; BSR = BSR_BACK; WREG = W_BACK; STATUS = STATUS_BACK; } void main(void) { init(); while(1) }

void init(void) { unsigned char hundreds,tens,ones; unsigned char temp1,temp2,temp3; //LCD TRISD=0; PORTD=0; PORTDbits.RD7=1; OpenXLCD( FOUR_BIT & LINES_5X7 ); //I2C DDRCbits.RC3 = 1; DDRCbits.RC4 = 1; SSPSTAT = 0x80; SSPCON1 = 0x28; SSPADD = 0x09; SSPCON2 = 0x00; //Restore LCD with old values and current value HDByteReadI2C(0xA0, 0x00,0x00, &stored_data, 0x03); output_count = stored_data[0]; Delay10KTCYx(20); //Seems we have a problem writing too quickly after OPENXLCD

displayXLCD(stored_data[3]); WriteDataXLCD(' '); displayXLCD(stored_data[2]); WriteDataXLCD(' '); displayXLCD(stored_data[1]); WriteDataXLCD(' '); displayXLCD(stored_data[0]); //TMR2 TMR2 = 0; T2CON = 0b011110111; IPR1bits.TMR2IP = 0; PIE1bits.TMR2IE = 1; //LVD LVDCON = 0b00011110; while(!LVDCONbits.IRVST); PIR2bits.LVDIF = 0; IPR2bits.LVDIP = 1; PIE2bits.LVDIE = 1; //Interrupts RCONbits.IPEN = 1; INTCONbits.GIEH = 1; INTCONbits.GIEL = 1; } void DelayFor18TCY(void) { Delay10TCYx(1); Delay1TCY(); Delay1TCY(); Delay1TCY(); Delay1TCY(); Delay1TCY(); Delay1TCY(); Delay1TCY(); Delay1TCY(); } void DelayPORXLCD(void) //15ms { Delay10KTCYx(6); } void DelayXLCD(void) //5ms { Delay10KTCYx(6); } void displayXLCD(unsigned char num) { unsigned char hundreds,tens,ones; if(num<10) {

hundreds=0; tens=0; ones=num; } else if(output_count<100) { hundreds=0; ones=num%10; tens=(num-ones)/10; } else { ones=num%10; tens=((num-ones)%100)/10; hundreds=(num-(num%100))/100; } WriteDataXLCD(hundreds+48); WriteDataXLCD(tens+48); WriteDataXLCD(ones+48); } unsigned char HDByteWriteI2C(unsigned char ControlByte, unsigned char HighAdd, unsigned char LowAdd, unsigned char data ) { IdleI2C(); // ensure module is idle StartI2C(); // initiate START condition while ( SSPCON2bits.SEN ); // wait until start condition is over WriteI2C( ControlByte ); // write 1 byte - R/W bit should be 0 IdleI2C(); // ensure module is idle WriteI2C( HighAdd ); // write address byte to EEPROM IdleI2C(); // ensure module is idle WriteI2C( LowAdd ); // write address byte to EEPROM IdleI2C(); // ensure module is idle WriteI2C ( data ); // Write data byte to EEPROM IdleI2C(); // ensure module is idle StopI2C(); // send STOP condition while ( SSPCON2bits.PEN ); // wait until stop condition is over while (EEAckPolling(ControlByte)); //Wait for write cycle to complete return ( 0 ); // return with no error } unsigned char HDByteReadI2C( unsigned char ControlByte, unsigned char HighAdd, unsigned char LowAdd, unsigned char *data, unsigned char length ) { IdleI2C(); // ensure module is idle StartI2C(); // initiate START condition while ( SSPCON2bits.SEN ); // wait until start condition is over WriteI2C( ControlByte ); // write 1 byte IdleI2C(); // ensure module is idle WriteI2C( HighAdd ); // WRITE word address to EEPROM IdleI2C(); // ensure module is idle while ( SSPCON2bits.RSEN ); // wait until re-start condition is over WriteI2C( LowAdd ); // WRITE word address to EEPROM IdleI2C(); // ensure module is idle RestartI2C(); // generate I2C bus restart condition while ( SSPCON2bits.RSEN ); // wait until re-start condition is over WriteI2C( ControlByte | 0x01 ); // WRITE 1 byte - R/W bit should be 1 for read IdleI2C(); // ensure module is idle getsI2C( data, length ); // read in multiple bytes NotAckI2C(); // send not ACK condition while ( SSPCON2bits.ACKEN ); // wait until ACK sequence is over StopI2C(); // send STOP condition while ( SSPCON2bits.PEN ); // wait until stop condition is over return ( 0 ); // return with no error

También podría gustarte