//============================================================================== // // PIC C software to control T6963 based LCD display // My display is 128 x 64 but you should be able to adapt to other sizes // by changing the defines. // // Bought the display and had to do something with it. Also trying to learn // C. So this was a 'C' learning experience! // // Processor used was 16F874 @ 10.0 Mhz. Should work on 877 -- new .H file. // Display used was a surplus MGLS-12864T. // // Version 0.001 Support not guaranteed. But, if I find any // serious bug's I will repost. // // Written by Art Prewitt N4PT artprewitt@earthlink.net // Feb. 15, 2002 // // // Direct comments/bug's to artprewitt@earthlink.net // // Based on information from Steve Lawther // http://ourwrold.compuserve.com/homepages/steve_lawther/t6963c.pdf // Also John Beale ... LPT port control of T6963 // // No reads from T6963 have been implemented // Ran out of ROM space in the 16F874... Need to buy a 877 // // Problems: // // Not guaranteed to be bug free!! But works on my hardware. // // Still not happy with line drawing routine, lines are // not smooth enough. // // Drawing routines are pixel by pixel... Could try a buffer then // send a number of bytes in auto mode. // // Compiler: // // Compiled using WIZ-C V8.0 Forest Electronic Developments compiler // Pic Call stack.... Local Optimize bytes 8 // Compiles to 3925 program words. // // I have combined my T6963.H file into this file for posting // split out if you choose. // // The Page Test and Display test routines should demo most of the // functions that have been implemented. // // // I am not an C programmer so you Guru's can condense // the code. Maybe make a WIZ-C element. // // // Enjoy ! // //============================================================================== #include #include #include //============================================================================== // // LCD Signal FS RST C/D ~CE ~RD ~WR // // PORTB BIT 6 5 4 3 2 1 //============================================================================== // LCD Data lines to PORTD PORTD.0 = DB0 etc. // // // if you need schematic send me an email //============================================================================== #define WR PB.B1 #define RD PB.B2 #define CE PB.B3 #define CD PB.B4 #define RST PB.B5 #define FS PB.B6 #define Ldata PORTD // lcd data DB0..DB7 #define HI 1 #define LO 0 #define DRAW 1 #define ERASE 0 //============================================================================== // // This is really T6963.H // //============================================================================== #define EightbyEight 0 // fonts #define SixbyEight 1 #define STA0STA1 0x03 // Status masks #define STA3 0x08 // // Macros // #define HiByte(X) (X >> 8 ) #define LoByte(X) (X & 0xFF ) //============================================================================== // // T6963 Commands & modifiers // // //============================================================================== #define PointerSet 0x20 // modifiers #define CursorPointerSet 0x01 #define OffsetRegisterSet 0x02 #define AddressPointerSet 0x04 #define ControlWordSet 0x40 // Modifiers #define TextHomeAddress 0x0 #define TextAreaSet 0x1 #define GraphicHomeAddress 0x02 #define GraphicAreaSet 0x03 #define ModeSet 0x80 // modifiers #define CGROMMode 0x0 #define CGRAMMode 0x8 #define ORMode 0x0 #define EXORMode 0x1 #define ANDMode 0x3 #define Textonly 0x4 #define DisplayModes 0x90 // modifiers #define GraphicsOff 0x0 #define GraphicsOn 0x8 #define TextOff 0x0 #define TextOn 0x4 #define CursorOff 0x0 #define CursorOn 0x2 #define CursorBlinkOff 0x0 #define CursorBlinkOn 0x1 #define CursorPattern 0xA0 // modifiers 0 to 7 Number lines for cursor 000 = bottom 111 = 8lines #define BottomLine 0x0 #define DataAuto 0xB0 // Modifiers #define DataAutoWrite 0x0 #define DataAutoRead 0x1 #define AutoReset 0x2 #define DataRdWr 0xC0 // Modifiers #define AddressPointerup/down 0x0 #define AddressPointerunchanged 0x4 #define AddressPointerup 0x0 #define AddressPointerdown 0x2 #define DataWrite 0x0 #define DataRead 0x1 #define BitSetReset 0xF0 // Modifiers or in bit number 0 to 7 #define BitReset 0x0 #define BitSet 0x8 //============================================================================== // // Display Constants // //============================================================================== #define MemorySize 0x2000 // 8k could be 4k #define GraphBase 0x0400 // base address of graphics memory #define TextBase 0x0000 // base address of text memory #define CGRamBase 0x1C00 // Requires offset = 3 #define OFFSET 0x03 // CG rom 1800h - 1FFFh // char80h Starts at 1C00h // 1800h + 80h*8 = 1C00 // These chars are at 0x80h // They are in a 6x8 field so work at both fonts // 80h is an up arrow // 81h is a square with a dot in the center // char MyChars[] = {0x00,0x04,0x0E,0x15,0x04,0x04,0x04,0x00, 0x00,0x3E,0x22,0x2A,0x22,0x3E,0x00,0x00}; #define NumMyChars 2 // change if chars added #define DisplayX 128 #define DisplayY 64 #define ROWS ((DisplayY / 8) - 1) #define XMAX (DisplayX -1) // limits of (x,y) LCD graphics drawing #define XMIN 0 #define YMAX (DisplayY - 1) #define YMIN 0 //============================================================================== // // End of H file // //============================================================================== #asmline __CONFIG 0x3F3A ; // Set 16F874 Fuses //============================================================================== // // Program Globals // //============================================================================== BYTE FontSize ; unsigned int BytesPerRow ; BYTE Paged = 0 ; int NumPages = 0 ; // Used in Displaytest & Pagetest int i ; int xx ; int yy ; BYTE bb ; // Used in lcd_line int t , distance ; // these were moved here to debug the line drawing routine int xerr , yerr ; // Not happy with line drawing routine int Dx,Dy ; // Still working on it.. Maybe float's ? int incx,incy ; //============================================================================== // // Function Proto's // //============================================================================== void ProcInit(); void lcd_init(); void DisplayTest(); void PageTest(); void StatSTA01(void); void StatSTA3(void); void DataOut(BYTE b); void CmdOut(BYTE b); void lcd_clear_graph(); void lcd_clear_text(); void lcd_char(char b); void lcd_char_raw(char b); void lcd_print(char *string); void lcd_print_page( char *string, int page); void lcd_pixel(int X, int Y, BYTE SetReset); void lcd_xy(int X, int Y); void lcd_xy_page(int X, int Y , int page); void lcd_changeFont(char NewFont); void SetAutoMode(); void AutoOut(BYTE B); void ResetAutoMode(); void lcd_clear_textline(BYTE Line); void lcd_cursorXY(BYTE X, BYTE Y); void DataOut2(int b); void lcd_plot( int X,int Y, BYTE SetReset); void lcd_box(int X1, int Y1, int X2, int Y2, BYTE SetReset); void lcd_line(int X1,int Y1, int X2, int Y2, BYTE SetReset); void AutoZero(unsigned int Terminate); void load_CGRAM(char *p, int NumBytes); void lcd_changepage(int page); void lcd_setup_page( BYTE Pages); //============================================================================== // // End Proto's // //============================================================================== void main() { ProcInit(); lcd_init(); PageTest() ; // Run once at start while(1) { DisplayTest(); if ( FS == EightbyEight) lcd_changeFont(SixbyEight); else lcd_changeFont(EightbyEight); } } //============================================================================= // // // Start of Test program // // //============================================================================= void PageTest() { if (FS == SixbyEight) lcd_changeFont(EightbyEight); lcd_setup_page(3); lcd_clear_text(); CmdOut(DisplayModes | GraphicsOff | TextOn | CursorOff | CursorBlinkOff); // Page 1 lcd_xy(0,0); lcd_print(" Page Test" ); lcd_xy(0,1) ; lcd_print("This is page 1"); lcd_xy(0,2); lcd_print("0123456789012345"); // Page 2 lcd_xy_page(0,0,2); // write to page 2... not displayed yet lcd_print("This is page 2"); lcd_xy_page(0,1,2); lcd_print("Page 2 line 2"); lcd_xy_page(0,3,2); lcd_print("0123456789012345"); // Page 3 // write to page 3... not displayed yet lcd_xy_page(0,0,3); lcd_print("This is page 3"); lcd_xy_page(0,1,3); lcd_print("Page 3 line 2"); lcd_xy_page(0,4,3); lcd_print("0123456789012345"); Wait(5000); lcd_changepage(2); // display page 2 Wait(5000); lcd_changepage(3); // display page 3 Wait(5000); lcd_changepage(1); lcd_xy(0,0); lcd_print(" Shift Pages"); // shift left through pages Wait(4000); for(i=0;i<=32;i++) { DataOut2(TextBase+i) ; // To start of page 3 CmdOut(ControlWordSet | TextHomeAddress); Wait(300); } Wait(5000); DataOut2(TextBase); CmdOut(ControlWordSet | TextHomeAddress); lcd_clear_text(); lcd_xy(0,7); lcd_print(" End Page Demo"); Wait(5000); CmdOut(DisplayModes | GraphicsOff | TextOff | CursorOff | CursorBlinkOff); lcd_changeFont(EightbyEight); // quick & dirty exit from page mode } void DisplayTest() { lcd_clear_graph(); lcd_clear_text(); CmdOut(DisplayModes | GraphicsOff | TextOn | CursorOff | CursorBlinkOff); lcd_xy(0,0); lcd_print("Hello World"); lcd_xy(0, 1); if ( FS == EightbyEight) { lcd_print("0123456789012345"); lcd_xy(0, 2); lcd_print("8 X 8 Font"); } else { lcd_print("012345678901234567890"; lcd_xy(0, 2); lcd_print("6 X 8 Font"); } lcd_xy(0, 3); lcd_print("Line 4"); lcd_xy(0, 4); lcd_print("Line 5"); lcd_xy(0,5); lcd_print("Line 6"); lcd_xy(0,6); lcd_print("Line 7"); lcd_xy(0, 7); lcd_print("More to come"); Wait(7000); lcd_clear_textline(3); lcd_clear_textline(4); lcd_xy(0,3); lcd_print("lcd_char "); lcd_xy(0,4); lcd_char('A'); lcd_char('B'); Wait(4000); lcd_clear_textline(0); lcd_print(" Erase lines "); for(i=1;i<7;i++) { lcd_clear_textline(i); Wait(200); } Wait(2000); lcd_clear_text(); lcd_cursorXY(0,4); lcd_xy(0,0); lcd_print("CURSOR ON"); CmdOut(DisplayModes | GraphicsOff | TextOn |CursorOn | CursorBlinkOff); Wait(2000); lcd_clear_textline(0); lcd_xy(0,0); lcd_print("Cursor Blink"); CmdOut(DisplayModes | GraphicsOff | TextOn | CursorOn | CursorBlinkOn); Wait(2000); lcd_clear_textline(0); lcd_xy(0,0); lcd_print(" Move Cursor "); lcd_cursorXY(0,0); CmdOut(CursorPattern | 7); CmdOut(DisplayModes | GraphicsOff | TextOn |CursorOn | CursorBlinkOff); for(i=0;i<=15;i++) { lcd_cursorXY(i,0); Wait(200); } for(i=1;i<=4;i++) { lcd_cursorXY(15,i); Wait(200); } Wait(1000); lcd_cursorXY(7,3); lcd_xy(0,1); lcd_print(" Cursor Size "); for(i=0;i<=7;i++) { CmdOut(CursorPattern | (BYTE)i ); Wait(200); } Wait(1000); CmdOut(DisplayModes | GraphicsOff | TextOn | CursorOff | CursorBlinkOff); lcd_clear_text(); lcd_xy(0,0); lcd_print("Font Test"); Wait(1000); lcd_xy(0,0); if ( FS == EightbyEight) { SetAutoMode(); for(xx=0;xx<=0x7F;xx++) AutoOut(xx); ResetAutoMode(); } else { // only 21 chars in 22 char field in 6x8 SetAutoMode(); bb =21 ; for(xx=0;xx<=0x7f;xx++) { if ( xx == bb) { bb += 21 ; AutoOut(0); // 0 to char 22 } AutoOut(xx); } ResetAutoMode(); } Wait(4000); lcd_clear_text(); lcd_xy(0,0); lcd_print("My Chars"); lcd_print(" "); lcd_char_raw(0x80); // user chars direct lcd_char_raw(0x81); lcd_xy(7,3); lcd_char(0x80 + 0x20); // add 20h to user char lcd_char(0x81 + 0x20); Wait(4000); lcd_clear_text(); lcd_xy(0,0); lcd_print(" Graphic Test "); CmdOut(DisplayModes | GraphicsOn | TextOn | CursorOff); lcd_line(XMAX/2,0,XMAX/2,YMAX,DRAW); // draw axis lcd_line(0,YMAX/2,XMAX,YMAX/2,DRAW); Wait(2000); CmdOut(DisplayModes | GraphicsOn | TextOff | CursorOff); Wait(3000); lcd_line(XMAX/2,0,XMAX/2,YMAX,ERASE); // erase axis lcd_line(0,YMAX/2,XMAX,YMAX/2,ERASE); Wait(1000); lcd_box(10,10,50,50,DRAW); // draw box Wait(1000); lcd_box(10,10,50,50,ERASE); // erase box Wait(1000); lcd_line(XMAX/2,YMAX/2,0,0,DRAW); // crisscross lcd_line(XMAX/2,YMAX/2,0,YMAX,DRAW); lcd_line(XMAX/2,YMAX/2,XMAX,YMAX,DRAW); lcd_line(XMAX/2,YMAX/2,XMAX,0,DRAW); Wait(4000); lcd_clear_graph(); for(i=0;i<=XMAX;i+=10) // pinwheel lcd_line(XMAX/2,YMAX/2,i,YMAX,DRAW); for(i = YMAX;i>=0;i-=10) lcd_line(XMAX/2,YMAX/2,XMAX,i,DRAW); for(i = XMAX ;i>=0;i-=10) lcd_line(XMAX/2,YMAX/2,i,0,DRAW); for(i=0;i<=YMAX;i+=10) lcd_line(XMAX/2,YMAX/2,0,i,DRAW); Wait(4000); lcd_clear_graph(); for(i=5;i<60;i+=10) // shrinking boxes lcd_box(i,i/2,126-i,63-i/2,DRAW); Wait(4000); } //==================================================================================== // // // End of Test // //==================================================================================== void ProcInit() { T1CON = 1 ; ADCON0 = 0; // Kill the pesky A/D ADCON1 = 7 ; // All Digital i/o PORTB = 0x1F; // FS low all else high TRISB = 0; // output PORTD = 0; TRISD = 0; // output PORTA = 0 ; TRISA = 0x3F ; CE = HI; // All control lines HI RD = HI; WR = HI; CD = HI; RST = LO; // Reset LCD Wait(1); // Delay 1 ms for reset could be shorter RST = HI; // Release Reset Wait(1); // unnecessary FS = EightbyEight ; // Set font 8x8 if ( FS == EightbyEight) // Set Font Size { FontSize = 8 ; BytesPerRow = 16 ; } else { FontSize = 6 ; BytesPerRow = 22 ; // only 21 chars per line but need // extra byte for last 2 bits in // graph mode } } //============================================================================== // // // Initalize Display // // //============================================================================== void lcd_init() { CmdOut(DisplayModes | GraphicsOff | TextOff | CursorOff ); DataOut2(GraphBase); // Graphic Base Address CmdOut(ControlWordSet | GraphicHomeAddress); DataOut2(BytesPerRow); // Graphic Area CmdOut(ControlWordSet | GraphicAreaSet); DataOut2(TextBase); // Text Base Address CmdOut(ControlWordSet | TextHomeAddress); Paged = 0 ; // page mode off DataOut2(BytesPerRow); // Text Area CmdOut(ControlWordSet | TextAreaSet); DataOut2(OFFSET); // User CGRAM CmdOut(PointerSet | OffsetRegisterSet); load_CGRAM(MyChars,(NumMyChars*8)); CmdOut(ModeSet | ORMode | CGROMMode); // mode set CmdOut(CursorPattern | 3); // cursor is 8 lines high lcd_clear_graph(); lcd_clear_text(); lcd_cursorXY(0,0); // Move Cursor to (x,y) } //============================================================================== // // // MidLevel Routines // //============================================================================== //============================================================================== // // Clear Graph Display ... Writes 0's (blanks) to graphic ram // Uses Auto Mode //============================================================================== void lcd_clear_graph() { DataOut2(GraphBase); CmdOut(PointerSet | AddressPointerSet); AutoZero( BytesPerRow * DisplayY ); } //============================================================================== // // Clear Text Display .... Writes Zeros( blanks) to Text Ram // Uses Auto Mode // //============================================================================== void lcd_clear_text() { DataOut2(TextBase); CmdOut(PointerSet | AddressPointerSet); AutoZero( BytesPerRow * ( DisplayY /8 )); } //============================================================================== // // Clear the Text at specified line & set addr ptr to start of line // //============================================================================== void lcd_clear_textline(BYTE Line) { lcd_xy(0,Line); AutoZero(BytesPerRow); lcd_xy(0,Line); } //============================================================================== // // Write char b to display... Use lcd_xy to set location // Also ROM generator is offset. Space (20h) is at 00 // So subtrace 0x20 from character // //============================================================================== void lcd_char(char b) { DataOut(b - 0x20); CmdOut(DataRdWr); } // write raw data to screen for use with user defined chars void lcd_char_raw(char b) { DataOut(b); CmdOut(DataRdWr); } //============================================================================== // // Print String Uses Auto Mode // Use lcd_xy to set location // subtract 0x20 hex from char //============================================================================== void lcd_print(char *string) { char *p = string ; SetAutoMode(); while (*p) AutoOut(*p++ - 0x20); ResetAutoMode(); } //============================================================================== // // Set or Reset the Pixel at X,Y ; SetReset = DRAW or ERASE // //============================================================================== void lcd_pixel(int X, int Y, BYTE SetReset) { BYTE Bit = (FontSize-1) - (X % FontSize); DataOut2(GraphBase + (Y * BytesPerRow) + (X / FontSize)); CmdOut(PointerSet | AddressPointerSet); if ( SetReset ) CmdOut(BitSetReset | BitSet | Bit); else CmdOut(BitSetReset | BitReset | Bit ); } //============================================================================== // // Set Text Ram address to X,Y // //============================================================================== void lcd_xy(int X, int Y) { DataOut2(TextBase + (Y * BytesPerRow) + X); CmdOut(PointerSet | AddressPointerSet); } //============================================================================== // // PAGE DEMO STUFF // Page version of lcd_xy .. This is only good for 8x8 font // 1 < page < x // I did NOT work all the features for paged display // Just enough to learn how to do it. //============================================================================== void lcd_xy_page(int X, int Y , int page) { if ( Paged) { DataOut2(TextBase + ( ( Y * BytesPerRow) + X ) + (page-1) * 16); CmdOut(PointerSet | AddressPointerSet); } } // // Change pages... Move TextHome address to Base + page offset // void lcd_changepage(int page) { if ( (Paged) && ( page <= NumPages) ) { DataOut2(TextBase + ((page-1) * 16)); // Text Base Address + page offset CmdOut(ControlWordSet | TextHomeAddress); } } // // Setup pages. Assumes 16 bytes/page. // Careful not to overwrite Graphic area // void lcd_setup_page( BYTE Pages) { Paged = 1 ; NumPages = Pages ; BytesPerRow = 16 * NumPages ; // only good for 8x8 font DataOut2(TextBase); // Text Base Address CmdOut(ControlWordSet | TextHomeAddress); DataOut2(BytesPerRow); // Text Area CmdOut(ControlWordSet | TextAreaSet); } //============================================================================== // // Position cursor at column, row // No value checking is done .. watch out in 6x8 mode //============================================================================== void lcd_cursorXY(BYTE X, BYTE Y) { DataOut(X); DataOut(Y); CmdOut(PointerSet | CursorPointerSet); } //============================================================================== // // Change Font either 8x8 or 6x8 // //============================================================================== void lcd_changeFont(char NewFont) { if ( NewFont == EightbyEight) { FS = EightbyEight ; FontSize = 8 ; BytesPerRow = 16; lcd_init(); } else if ( NewFont == SixbyEight) { FS = SixbyEight ; FontSize = 6 ; BytesPerRow = 22 ; lcd_init(); } } //============================================================================== // // Load User CGRAM Note ADP is changed // User must point to correct text or graph address before next write // Should really put the user chars in eeprom -- next time. // //============================================================================== void load_CGRAM(char *p, int NumBytes) { DataOut2(CGRamBase); CmdOut(PointerSet | AddressPointerSet ); SetAutoMode(); while(NumBytes--) AutoOut(*p++); ResetAutoMode(); } //============================================================================== // // Primitive plot routines flips the Y axis // 0,0 located at lower left corner // //============================================================================== void lcd_plot( int X,int Y, BYTE SetReset) { BYTE Bit = (BYTE) ((FontSize -1 ) - ( X % FontSize)) ; // bit in byte DataOut2(GraphBase + (YMAX - Y) * BytesPerRow + ( X / FontSize )); CmdOut(PointerSet | AddressPointerSet); if ( SetReset ) CmdOut(BitSetReset | BitSet | Bit); else CmdOut(BitSetReset | BitReset | Bit); } // // Draw a box lower left corner.. upper right corner // void lcd_box(int X1, int Y1, int X2, int Y2, BYTE SetReset) { lcd_line(X1,Y1,X1,Y2, SetReset); lcd_line(X1,Y2,X2,Y2, SetReset); lcd_line(X2,Y2,X2,Y1, SetReset); lcd_line(X2,Y1,X1,Y1, SetReset); } // // Line drawing routine using Bersenham's Algorithm // Could be better! // void lcd_line(int X1,int Y1, int X2, int Y2, BYTE SetReset) { // int t , distance ; made global for debug // int xerr , yerr = 0 ; // int Dx,Dy ; // int incx,incy ; // int XO,YO ; // Bersenham's algorithm Dx = X2 - X1 ; Dy = Y2 - Y1 ; if ( Dx > 0 ) incx = 1 ; else if (Dx == 0 ) incx = 0 ; else incx = -1 ; if ( Dy > 0 ) incy = 1 ; else if ( Dy == 0 ) incy = 0 ; else incy = -1 ; Dy = fabs(Dy); Dx = fabs(Dx); if ( Dx > Dy ) distance = Dx ; else distance = Dy ; yerr = 0; xerr = 0; for(t=0;t<=distance+1;t++) { lcd_plot(X1,Y1,SetReset); xerr += Dx ; yerr += Dy ; if ( xerr > distance ) { xerr -= distance ; X1 += incx ; if ( X1 < 0 ) X1 = 0 ; if ( X1 > XMAX ) X1 = XMAX ; } if(yerr > distance ) { yerr -= distance ; Y1 += incy; if ( Y1 < 0 ) Y1 = 0 ; if ( Y1 > YMAX ) Y1 = YMAX ; } } } //============================================================================== // // Send Terminate number of Zeros ( same as 20h) in Auto Mode. Used for // clear ram routines // //============================================================================== void AutoZero( unsigned int Terminate ) { SetAutoMode(); while(Terminate--) AutoOut(0); ResetAutoMode() ; } //============================================================================== // // Low Level Control Routines // // // //============================================================================== // // Read status STA0 and STA1 return when not busy // It will get stuck here if signals are bad // void StatSTA01() { BYTE lcd_status; TRISD = 0xFF; // Input Mode do { CE = LO; RD = LO; lcd_status = Ldata; CE = HI; RD = HI; } while ((lcd_status & STA0STA1) != STA0STA1); TRISD = 0; // output mode } // // Read status of STA3 for auto mode return when not busy // Can get stuck here !! void StatSTA3() { BYTE lcd_status; TRISD = 0xFF; // Input Mode do { CE = LO; RD = LO; lcd_status = Ldata; // read LCD status byte CE = HI; RD = HI; } while ((lcd_status & STA3) != STA3); TRISD = 0; // output mode } // // Check STA1 & STA2 and send data byte when not busy // void DataOut(BYTE b) { StatSTA01(); CD = LO; Ldata = b; CE = LO; // low for > 80 ns WR = LO ; CE = HI; WR = HI; CD = HI ; } // // Send 2 data bytes // void DataOut2(int b) { DataOut(LoByte(b)); DataOut(HiByte(b)); } // // check STA1 & STA2 and send command when not busy // void CmdOut(BYTE b) { StatSTA01(); Ldata = b; CE = LO; WR = LO; // > 80 ns CE = HI; WR = HI; } // // Set Auto Mode // void SetAutoMode() { CmdOut(DataAuto | DataAutoWrite); } // // Check STA3 Send byte Auto Mode when not busy // void AutoOut(BYTE b) { StatSTA3(); CD = LO; Ldata = b; CE = LO; // low for > 80 ns WR = LO ; CE = HI; WR = HI; CD = HI ; } // // Check STA3 Exit Auto Mode when not busy // Same as CmdOut but checks STA3 first // void ResetAutoMode() { StatSTA3(); Ldata = DataAuto | AutoReset; CE = LO; WR = LO; // > 80 ns CE = HI; WR = HI; }