This is file "LCD-lite.c".
//============================================================================== // Copyright (c) 2005-2010, Isaac Marino Bavaresco // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Neither the name of the author nor the // names of its contributors may be used to endorse or promote products // derived from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //============================================================================== // isaacbavaresco@yahoo.com.br //============================================================================== // Set TAB width to 4 characters //============================================================================== #include "LCDcfg-lite.h" //============================================================================== //============================================================================== //============================================================================== //============================================================================== //============================================================================== #if defined USE_READ_PIN && defined DETECT_FAILURE // Flag to indicate that the display is defective. unsigned char displayfailed = 0; #endif // defined USE_READ_PIN && defined DETECT_FAILURE //============================================================================== #define LCDWriteCmd(c) LCDWrite((c),0) #define LCDWriteData(c) LCDWrite((c),1) #define CMD_CLEAR_DISPLAY 0x01 #define CMD_SET_DDRAM_ADDRESS 0x80 //============================================================================== // Writes a character or command to the display. // Arguments: // c The character or command to be written // di Type of write: 0 = command, 1 = character static void LCDWrite( unsigned char c, unsigned char di ) { // Variable to save the interrupts state at function entry. DeclareIntSaveVar( Aux ); #if defined USE_READ_PIN unsigned char s; #if defined DETECT_FAILURE unsigned short n = NUMBER_OF_READS_TIMEOUT; // The display has failed previously... if( displayfailed ) // ... so we will not even try to access it return; #endif // defined DETECT_FAILURE #endif // defined USE_READ_PIN SaveInterruptsState( Aux ); #if defined USE_READ_PIN // Repeat while bit BUSY == 1 do { RestoreInterruptsState( Aux ); // At this point a pending interrupt may be serviced, so we don't // block the system for too long. On return, no assumption is // made about the state of the LCD interface pins, an interrupt // routine may change the value or direction of any pin, except // of the ENABLE pin. DisableInterrupts(); SetDataPortAsInputs(); // Set READ/!WRITE pin as read (1) SetRWAsRead(); // Set DATA/!INSTRUCTION pin as instruction (0) SetDIAsInstruction(); // Effectivate the data transfer. SetEAsEnabled(); // Wait for the minimum 'tHIGH'. Delay500ns(); // Read the status byte (only high nibble if in 4-bit mode). ReadDataPortValue( s ); // Low half of the trasfer cycle. SetEAsDisabled(); // Wait for the minimum 'tLOW'. Delay500ns(); #if defined USE_FOUR_BIT_INTERFACE // When using a 4-bit interface we need to do a dummy read // Effectivate the data transfer. SetEAsEnabled(); // Wait for the minimum 'tHIGH'. Delay500ns(); // Low half of the trasfer cycle. SetEAsDisabled(); // Wait for the minimum 'tLOW'. Delay500ns(); #endif // defined USE_FOUR_BIT_INTERFACE // Set READ/!WRITE pin as write (0) SetRWAsWrite(); } #if !defined DETECT_FAILURE // Repeat until the BUSY bit is zero while( s & 0x80 ); #else // !defined DETECT_FAILURE // Repeat until the BUSY bit is zero or until the maximum number of repetitions. while(( s & 0x80 ) && ( --n != 0u )); // We tried the maximum number of times... if( n == 0u ) // ... so we flag the display as failed displayfailed = 1; #endif // !defined DETECT_FAILURE #else // defined USE_READ_PIN DisableInterrupts(); #endif // defined USE_READ_PIN //-------------------------------------------------------------------------- // Now we may send the data. //-------------------------------------------------------------------------- // Set READ/!WRITE pin as write (0) SetRWAsWrite(); // Set DATA/!INSTRUCTION pin as data (1). SetDIValue( di ); // The data (only high nibble if in 4-bit mode) is output to the data port. SetDataPortValue( c ); SetDataPortAsOutputs(); // Effectivate the data transfer. SetEAsEnabled(); // Wait for the minimum 'tHIGH'. Delay500ns(); // Low half of the trasfer cycle. SetEAsDisabled(); // Wait for the minimum 'tLOW'. Delay500ns(); #if defined USE_FOUR_BIT_INTERFACE // The low nibble is output to the data port. SetDataPortValueLow( c ); // Effectivate the data transfer. SetEAsEnabled(); // Wait for the minimum 'tHIGH'. Delay500ns(); // Low half of the trasfer cycle. SetEAsDisabled(); // Wait for the minimum 'tLOW'. Delay500ns(); #endif // defined USE_FOUR_BIT_INTERFACE SetDataPortAsInputs(); RestoreInterruptsState( Aux ); #if !defined USE_READ_PIN // If we are not reading the busy flag, then we must wait at least for // the worst-case execution time before any other command ca be executed. // This write is a "clear screen" or a "home cursor" command... if( di == 0 && c <= 3 ) // ... that takes up to 1.52ms to execute. Delay1520us(); // For all other writes... else // ... the execution time is no more than 37us. Delay37us(); #endif // !defined USE_READ_PIN } //============================================================================== // Reads one data byte from the LCD. #if defined USE_READ_PIN static unsigned char LCDReadData( void ) { // Variable to save the interrupts state at function entry. DeclareIntSaveVar( Aux ); // Variable to temporarily hold the value read. unsigned char s; #if defined DETECT_FAILURE // Variable to count the number of busy flag reads before an error is signaled. unsigned short n = NUMBER_OF_READS_TIMEOUT; // If the display failed previously... if( displayfailed ) // ... we are not using it until some routine clears the error flag. return 0x00; #endif // defined DETECT_FAILURE SaveInterruptsState( Aux ); // Repeat while busy flag == 1 do { RestoreInterruptsState( Aux ); // At this point a pending interrupt may be serviced, so we don't // block the system for too long. On return, no assumption is // made about the state of the LCD interface pins, an interrupt // routine may change the value or direction of any pin, except // of the ENABLE pin. DisableInterrupts(); SetDataPortAsInputs(); // Set the READ/!WRITE pin as read (1) SetRWAsRead(); // Set DATA/!INSTRUCTION pin as instruction (0) SetDIAsInstruction(); // Effectivate the data transfer. SetEAsEnabled(); // Wait for the minimum 'tHIGH'. Delay500ns(); // Read the status byte (only high nibble if in 4-bit mode). ReadDataPortValue( s ); // Low half of the trasfer cycle. SetEAsDisabled(); // Wait for the minimum 'tLOW'. Delay500ns(); #if defined USE_FOUR_BIT_INTERFACE // When using a 4-bit interface we need to do a dummy read // Effectivate the data transfer. SetEAsEnabled(); // Wait for the minimum 'tHIGH'. Delay500ns(); // Low half of the trasfer cycle. SetEAsDisabled(); // Wait for the minimum 'tLOW'. Delay500ns(); #endif // defined USE_FOUR_BIT_INTERFACE // Set READ/!WRITE pin as write (0) SetRWAsWrite(); } // Test the LCD's BUSY flag. #if !defined DETECT_FAILURE while( s & 0x80 ); #else // !defined DETECT_FAILURE while(( s & 0x80 ) && ( --n != 0u )); if( n == 0u ) displayfailed = 1; #endif // !defined DETECT_FAILURE //-------------------------------------------------------------------------- // Now we can read the data. //-------------------------------------------------------------------------- // Set READ/!WRITE pin as read (1) SetRWAsRead(); // Set DATA/!INSTRUCTION pin as data (1). SetDIAsData(); // Effectivate the data transfer. SetEAsEnabled(); // Wait for the minimum 'tHIGH'. Delay500ns(); // Read the data byte (only high nibble if in 4-bit mode). ReadDataPortValue( s ); // Low half of the trasfer cycle. SetEAsDisabled(); // Wait for the minimum 'tLOW'. Delay500ns(); #if defined USE_FOUR_BIT_INTERFACE // Effectivate the data transfer. SetEAsEnabled(); // Wait for the minimum 'tHIGH'. Delay500ns(); // Read the data byte (low nibble only). ReadDataPortValueLow( s ); // Low half of the trasfer cycle. SetEAsDisabled(); // Wait for the minimum 'tLOW'. Delay500ns(); #endif // defined USE_FOUR_BIT_INTERFACE // Set READ/!WRITE pin as write (0) SetRWAsWrite(); RestoreInterruptsState( Aux ); // Return the read data. return s; } #endif // defined USE_READ_PIN //============================================================================== //============================================================================== //============================================================================== //============================================================================== // // // Hardware-independent part // // //============================================================================== //============================================================================== //============================================================================== //============================================================================== //============================================================================== // Current coordinates of the cursor. static unsigned char cursorx = 0, cursory = 0; // Number of columns and lines of the screen. static unsigned char maxx = INITIAL_MAXX, maxy = INITIAL_MAXY; //============================================================================== unsigned char getmaxx( void ) { return maxx; } //============================================================================== unsigned char getmaxy( void ) { return maxy; } //============================================================================== unsigned char getcursorx( void ) { return cursorx + 1; } //============================================================================== unsigned char getcursory( void ) { return cursory + 1; } //============================================================================== void clrscr( void ) { // interruptstate_t Aux; // // SaveInterruptsState( Aux ); // DisableInterrupts(); // Locate the cursor at the top-left corner of the screen cursorx = 0; cursory = 0; // Send the clear screen command to the LCD controller LCDWriteCmd( CMD_CLEAR_DISPLAY ); // RestoreInterruptsState( Aux ); } //============================================================================== void gotoxy( unsigned char x, unsigned char y ) { // interruptstate_t Aux; // // SaveInterruptsState( Aux ); // DisableInterrupts(); // If the coordinates are valid... if( x <= maxx && y <= maxy ) { // If the column is different than zero... if( x != 0u ) // ... change the current column to it. cursorx = x - 1; // If the line is different than zero... if( y != 0u ) // ... change the current line to it. cursory = y - 1; // We need to positon the hardware cursor to the right place. LCDWriteCmd( CMD_SET_DDRAM_ADDRESS | (( cursory << 6 ) & 0x40 ) | ( cursorx & 0x3f ) + ( cursory & 0x02 ? maxx : 0 )); } // RestoreInterruptsState( Aux ); } //============================================================================== // This routine's real name is defined in the file 'LCDcfg-lite.h'. // The name must be chosen to allow the library routines (printf, etc.) to link // to it. // NEW: The redefinable characters 0x01 to 0x06 may be printed directly, but // characters 0x00 and 0x07 conflict with special control codes. They may be // printed by using codes 0x0e and 0x0f, respectively. // This is the best possible solution, because we don't lose neither displayable // nor control characters (characters 8 to 15 are just a shadow of characters 0 // to 7). // Character 0 = '\0' (end of string) // Characters 1 to 6 = special redefinable characters 1 to 6. // Characters 7 to 13 = standard control characters. // Character 14 = printed as redefinable character zero. // Character 15 = printed as redefinable character 7. void LCD_PUTC( char c ) { // interruptstate_t Aux; // // SaveInterruptsState( Aux ); // DisableInterrupts(); switch( c ) { //---------------------------------------------------------------------- // '\a' = BELL case '\a': Beep(); // RestoreInterruptsState( Aux ); return /*0*/; //---------------------------------------------------------------------- // '\b' = BACKSPACE case '\b': // The cursor is not on the first column, ... if( cursorx > 0u ) // ... decrement the column. cursorx--; // The cursor is on the first column but it is not on the first line, ... else if( cursory > 0u ) { // ... position the cursor at the end of the previous line. cursory--; cursorx = maxx - 1; } // The LCD must be updated with the correct cursor coordinates. LCDWriteCmd( 0x80 | (( cursory << 6 ) & 0x40 ) | ( cursorx & 0x0f ) + ( cursory & 0x02 ? maxx : 0 )); // ... finished. // RestoreInterruptsState( Aux ); return /*0*/; //---------------------------------------------------------------------- // '\f' = FORMFEED case '\f': // Clear the screen. // Locate the cursor at the top-left corner of the screen cursorx = 0; cursory = 0; // Send the clear screen command to the LCD controller LCDWriteCmd( CMD_CLEAR_DISPLAY ); // RestoreInterruptsState( Aux ); return /*0*/; //---------------------------------------------------------------------- // '\n' = NEWLINE case '\n': // Return the cursor to the beginning of the line. cursorx = 0; // Fall-through to 'VTAB', to increment the line //---------------------------------------------------------------------- // '\v' = VTAB case '\v': // Increment the line number. cursory++; // Outside the 'switch' we will check if a wrap-around is needed. break; //---------------------------------------------------------------------- // '\r' = CR (carriage return) case '\r': // Return the cursor to the beginning of the line. cursorx = 0; // The LCD must be updated with the correct cursor coordinates. LCDWriteCmd( 0x80 | (( cursory << 6 ) & 0x40 ) | ( cursorx & 0x0f ) + ( cursory & 0x02 ? maxx : 0 )); // Fim // RestoreInterruptsState( Aux ); return /*0*/; //---------------------------------------------------------------------- // Aqui tratamos do caractere tabulação ('\t' = TAB) case '\t': { // We need a temporary variable. unsigned char Temp; // Calculate the column of the next tabulation. Temp = ( cursorx + TAB_WIDTH ) & ~( TAB_WIDTH - 1 ); // The cursor will be beyond the column after the last, ... if( Temp > maxx ) // ... restrict the cursor to the column after the last. Temp = maxx; // Calculate how many spaces must be inserted. Temp -= cursorx; // Advance the column to its final position. cursorx += Temp; // Print as many spaces as needed to reach the final position for( ; Temp; Temp-- ) LCDWriteData( ' ' ); // The cursor ended beyond the last column, ... if( cursorx >= maxx ) { // ... position the cursor to the beginning of the next line. cursorx = 0; cursory++; // Outside the 'switch' we will check if a wrap-around is needed. break; } // The cursor is yet on the same line, we may return right now. // RestoreInterruptsState( Aux ); return /*0*/; } //---------------------------------------------------------------------- // The character 14 (0x0e) will be translated to the redefinable // character 0 (zero). case 14: // Here we subtract 6, in the next case we subtract 8, resulting in zero. c -= 6; // Fall-through. //---------------------------------------------------------------------- // The character 15 (0x0f) will be translated to the redefinable // character 7. case 15: c -= 8; // Fall-through. //---------------------------------------------------------------------- // Here we cope with the printable characters. default: // Print the character. LCDWriteData( c ); // Increment the column. cursorx++; // The cursor ended beyond the last column, ... if( cursorx >= maxx ) { // ... position the cursor to the beginning of the next line. cursorx = 0; cursory++; // Outside the 'switch' we will check if a wrap-around is needed. break; } // The cursor is yet on the same line, we may return right now. // RestoreInterruptsState( Aux ); return /*0*/; } //-------------------------------------------------------------------------- // The cursor is beyond the last line, ... if( cursory >= maxy ) // ... position it on the last line... cursory = 0; // We must position the LCD cursor on the right place. LCDWriteCmd( 0x80 | (( cursory << 6 ) & 0x40 ) | ( cursorx & 0x0f ) + ( cursory & 0x02 ? maxx : 0 )); //-------------------------------------------------------------------------- // RestoreInterruptsState( Aux ); return /*0*/; } //============================================================================== void LCDControlCursor( unsigned char Mode ) { LCDWriteCmd(( Mode & 0x03 ) | 0x0c ); } //============================================================================== void LCDDefineChar( unsigned char Char, const unsigned char *Pattern ) { unsigned char c; LCDWriteCmd((( Char << 3 ) & 0x38 ) | 0x40 ); for( c = 8; c; c-- ) LCDWriteData( *Pattern++ ); LCDWriteCmd( 0x80 | (( cursory << 6 ) & 0x40 ) | ( cursorx & 0x3f ) + ( cursory & 0x02 ? maxx : 0 )); } //============================================================================== void LCDInit( void ) { // Initialize the LCD data interface. // The initial value of the LCD's ENABLE pin is 0 (disabled). SetEAsDisabled(); // The LCD's ENABLE pin is an output. SetEAsOutput(); // The initial value of the LCD's READ/!WRITE pin is 0 (write). SetRWAsWrite(); // The LCD's READ/!WRITE pin is an output. SetRWAsOutput(); // The initial value of the LCD's DATA/!INSTRUCTION pin is 0 (data). SetDIAsInstruction(); // The LCD's DATA/!INSTRUCTION pin is an output. SetDIAsOutput(); // Chip initialization, following data-sheet procedure. // Delay mandated by the data-sheet // "Wait for more than 15 ms after VCC rises to 4.5 V." // or // "Wait for more than 40 ms after VCC rises to 2.7 V." Delay15ms(); // Value to set the LCD interface to 8 bits, irrespective of the real board // interface length. SetDataPortValue( 0x30 ); SetDataPortAsOutputs(); // The command to change the interface to 8 bits must be issued 3 times. // Issue the command (first time). SetEAsEnabled(); // Wait for the minimum 'tHIGH'. Delay500ns(); // Low half of the trasfer cycle. SetEAsDisabled(); // Delay mandated by the data-sheet // "Wait for more than 4.1 ms." Delay4100us(); // Issue the command (second time). SetEAsEnabled(); // Wait for the minimum 'tHIGH'. Delay500ns(); // Low half of the trasfer cycle. SetEAsDisabled(); // Delay mandated by the data-sheet // "Wait for more than 100 µs." Delay100us(); // Issue the command (third time). SetEAsEnabled(); // Wait for the minimum 'tHIGH'. Delay500ns(); // Low half of the trasfer cycle. SetEAsDisabled(); #if !defined USE_READ_PIN // Wait for the execution time. Delay37us(); #endif // !defined USE_READ_PIN // Set the correct interface length #if defined USE_FOUR_BIT_INTERFACE // "Function set (Set interface to be 4 bits long.) Interface is 8 bits in length." LCDWriteCmd( 0x28 ); #else // defined USE_FOUR_BIT_INTERFACE // "Function set (Interface is 8 bits long.)" LCDWriteCmd( 0x38 ); #endif // defined USE_FOUR_BIT_INTERFACE // Turn display on LCDWriteCmd( 0x0c ); // Clear display LCDWriteCmd( CMD_CLEAR_DISPLAY ); } //==============================================================================