/* ks0108.cpp - Arduino library support for ks0108 and compatable graphic LCDs Copyright (c)2008 Michael Margolis All right reserved mailto:memargolis@hotmail.com?subject=KS0108_Library The high level functions of this library are based on version 1.1 of ks0108 graphics routines written and copyright by Fabian Maximilian Thiele. His sitelink is dead but you can obtain a copy of his original work here: http://www.scienceprog.com/wp-content/uploads/2007/07/glcd_ks0108.zip Code changes include conversion to an Arduino C++ library, rewriting the low level routines to read busy status flag and support a wider range of displays, adding more flexibility in port addressing and improvements in I/O speed. The interface has been made more Arduino friendly and some convenience functions added. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Version: 1.0 - May 8 2008 - first release Version: 1.1 - Nov 7 2008 - restructured low level code to adapt to panel speed - moved chip and panel configuration into seperate header files - added fixed width system font Version: 2 - May 26 2009 - second release - added support for Mega and Sanguino, improved panel speed tolerance, added bitmap support */ #include #include #include #include #define ksSOURCE #include "ks0108.h" #define EN_DELAY() asm volatile( "ldi r24, %0 \n\t" "subi r24, 0x1 \n\t" "and r24, r24 \n\t" "brne .-6 \n\t" ::"M" (EN_DELAY_VALUE) : "r24" ) // the (EN_DELAY_VALUE) argument for the above delay is in ks-0108_panel.h //#define GLCD_DEBUG // uncomment this if you want to slow down drawing to see how pixels are set void ks0108::ClearPage(uint8_t page, uint8_t color){ for(uint8_t x=0; x < DISPLAY_WIDTH; x++){ GotoXY(x, page * 8); this->WriteData(color); } } void ks0108::ClearScreen(uint8_t color){ uint8_t page; for( page = 0; page < 8; page++){ GotoXY(0, page * 8); ClearPage(page, color); } } void ks0108::DrawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t color) { uint8_t length, i, y, yAlt, xTmp, yTmp; int16_t m; // // vertical line // if(x1 == x2) { // x1|y1 must be the upper point if(y1 > y2) { yTmp = y1; y1 = y2; y2 = yTmp; } this->DrawVertLine(x1, y1, y2-y1, color); // // horizontal line // } else if(y1 == y2) { // x1|y1 must be the left point if(x1 > x2) { xTmp = x1; x1 = x2; x2 = xTmp; } this->DrawHoriLine(x1, y1, x2-x1, color); // // angled line :) // } else { // angle >= 45° if((y2-y1) >= (x2-x1) || (y1-y2) >= (x2-x1)) { // x1 must be smaller than x2 if(x1 > x2) { xTmp = x1; yTmp = y1; x1 = x2; y1 = y2; x2 = xTmp; y2 = yTmp; } length = x2-x1; // not really the length :) m = ((y2-y1)*200)/length; yAlt = y1; for(i=0; i<=length; i++) { y = ((m*i)/200)+y1; if((m*i)%200 >= 100) y++; else if((m*i)%200 <= -100) y--; this->DrawLine(x1+i, yAlt, x1+i, y, color); if(length <= (y2-y1) && y1 < y2) yAlt = y+1; else if(length <= (y1-y2) && y1 > y2) yAlt = y-1; else yAlt = y; } // angle < 45° } else { // y1 must be smaller than y2 if(y1 > y2) { xTmp = x1; yTmp = y1; x1 = x2; y1 = y2; x2 = xTmp; y2 = yTmp; } length = y2-y1; m = ((x2-x1)*200)/length; yAlt = x1; for(i=0; i<=length; i++) { y = ((m*i)/200)+x1; if((m*i)%200 >= 100) y++; else if((m*i)%200 <= -100) y--; this->DrawLine(yAlt, y1+i, y, y1+i, color); if(length <= (x2-x1) && x1 < x2) yAlt = y+1; else if(length <= (x1-x2) && x1 > x2) yAlt = y-1; else yAlt = y; } } } } void ks0108::DrawRect(uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint8_t color) { DrawHoriLine(x, y, width, color); // top DrawHoriLine(x, y+height, width, color); // bottom DrawVertLine(x, y, height, color); // left DrawVertLine(x+width, y, height, color); // right } void ks0108::DrawRoundRect(uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint8_t radius, uint8_t color) { int16_t tSwitch, x1 = 0, y1 = radius; tSwitch = 3 - 2 * radius; while (x1 <= y1) { this->SetDot(x+radius - x1, y+radius - y1, color); this->SetDot(x+radius - y1, y+radius - x1, color); this->SetDot(x+width-radius + x1, y+radius - y1, color); this->SetDot(x+width-radius + y1, y+radius - x1, color); this->SetDot(x+width-radius + x1, y+height-radius + y1, color); this->SetDot(x+width-radius + y1, y+height-radius + x1, color); this->SetDot(x+radius - x1, y+height-radius + y1, color); this->SetDot(x+radius - y1, y+height-radius + x1, color); if (tSwitch < 0) { tSwitch += (4 * x1 + 6); } else { tSwitch += (4 * (x1 - y1) + 10); y1--; } x1++; } this->DrawHoriLine(x+radius, y, width-(2*radius), color); // top this->DrawHoriLine(x+radius, y+height, width-(2*radius), color); // bottom this->DrawVertLine(x, y+radius, height-(2*radius), color); // left this->DrawVertLine(x+width, y+radius, height-(2*radius), color); // right } /* * Hardware-Functions */ void ks0108::FillRect(uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint8_t color) { uint8_t mask, pageOffset, h, i, data; height++; pageOffset = y%8; y -= pageOffset; mask = 0xFF; if(height < 8-pageOffset) { mask >>= (8-height); h = height; } else { h = 8-pageOffset; } mask <<= pageOffset; this->GotoXY(x, y); for(i=0; i<=width; i++) { data = this->ReadData(); if(color == BLACK) { data |= mask; } else { data &= ~mask; } this->WriteData(data); } while(h+8 <= height) { h += 8; y += 8; this->GotoXY(x, y); for(i=0; i<=width; i++) { this->WriteData(color); } } if(h < height) { mask = ~(0xFF << (height-h)); this->GotoXY(x, y+8); for(i=0; i<=width; i++) { data = this->ReadData(); if(color == BLACK) { data |= mask; } else { data &= ~mask; } this->WriteData(data); } } } void ks0108::InvertRect(uint8_t x, uint8_t y, uint8_t width, uint8_t height) { uint8_t mask, pageOffset, h, i, data, tmpData; height++; pageOffset = y%8; y -= pageOffset; mask = 0xFF; if(height < 8-pageOffset) { mask >>= (8-height); h = height; } else { h = 8-pageOffset; } mask <<= pageOffset; this->GotoXY(x, y); for(i=0; i<=width; i++) { data = this->ReadData(); tmpData = ~data; data = (tmpData & mask) | (data & ~mask); this->WriteData(data); } while(h+8 <= height) { h += 8; y += 8; this->GotoXY(x, y); for(i=0; i<=width; i++) { data = this->ReadData(); this->WriteData(~data); } } if(h < height) { mask = ~(0xFF << (height-h)); this->GotoXY(x, y+8); for(i=0; i<=width; i++) { data = this->ReadData(); tmpData = ~data; data = (tmpData & mask) | (data & ~mask); this->WriteData(data); } } } void ks0108::SetInverted(boolean invert) { // changed type to boolean if(this->Inverted != invert) { this->InvertRect(0,0,DISPLAY_WIDTH-1,DISPLAY_HEIGHT-1); this->Inverted = invert; } } void ks0108::SetDot(uint8_t x, uint8_t y, uint8_t color) { uint8_t data; this->GotoXY(x, y-y%8); // read data from display memory data = this->ReadData(); if(color == BLACK) { data |= 0x01 << (y%8); // set dot } else { data &= ~(0x01 << (y%8)); // clear dot } this->WriteData(data); // write data back to display } // // Font Functions // uint8_t ReadPgmData(const uint8_t* ptr) { // note this is a static function return pgm_read_byte(ptr); } void ks0108::SelectFont(const uint8_t* font,uint8_t color, FontCallback callback) { this->Font = font; this->FontRead = callback; this->FontColor = color; } void ks0108::PrintNumber(long n){ byte buf[10]; // prints up to 10 digits byte i=0; if(n==0) PutChar('0'); else{ if(n < 0){ PutChar('-'); n = -n; } while(n>0 && i <= 10){ buf[i++] = n % 10; // n % base n /= 10; // n/= base } for(; i >0; i--) this->PutChar((char) (buf[i-1] < 10 ? '0' + buf[i-1] : 'A' + buf[i-1] - 10)); } } int ks0108::PutChar(char c) { uint8_t width = 0; uint8_t height = this->FontRead(this->Font+FONT_HEIGHT); uint8_t bytes = (height+7)/8; uint8_t firstChar = this->FontRead(this->Font+FONT_FIRST_CHAR); uint8_t charCount = this->FontRead(this->Font+FONT_CHAR_COUNT); uint16_t index = 0; uint8_t x = this->Coord.x, y = this->Coord.y; if(c < firstChar || c >= (firstChar+charCount)) { return 1; } c-= firstChar; if( this->FontRead(this->Font+FONT_LENGTH) == 0 && this->FontRead(this->Font+FONT_LENGTH+1) == 0) { // zero length is flag indicating fixed width font (array does not contain width data entries) width = this->FontRead(this->Font+FONT_FIXED_WIDTH); index = c*bytes*width+FONT_WIDTH_TABLE; } else{ // variable width font, read width data, to get the index for(uint8_t i=0; iFontRead(this->Font+FONT_WIDTH_TABLE+i); } index = index*bytes+charCount+FONT_WIDTH_TABLE; width = this->FontRead(this->Font+FONT_WIDTH_TABLE+c); } // last but not least, draw the character for(uint8_t i=0; iFontRead(this->Font+index+page+j); if(height > 8 && height < (i+1)*8) { data >>= (i+1)*8-height; } if(this->FontColor == BLACK) { this->WriteData(data); } else { this->WriteData(~data); } } // 1px gap between chars if(this->FontColor == BLACK) { this->WriteData(0x00); } else { this->WriteData(0xFF); } this->GotoXY(x, this->Coord.y+8); } this->GotoXY(x+width+1, y); return 0; } void ks0108::Puts(const char* str) { int x = this->Coord.x; while(*str != 0) { if(*str == '\n') { this->GotoXY(x, this->Coord.y+this->FontRead(this->Font+FONT_HEIGHT)); } else { this->PutChar(*str); } str++; } } void ks0108::Puts_P(PGM_P str) { int x = this->Coord.x; while(pgm_read_byte(str) != 0) { if(pgm_read_byte(str) == '\n') { this->GotoXY(x, this->Coord.y+this->FontRead(this->Font+FONT_HEIGHT)); } else { this->PutChar(pgm_read_byte(str)); } str++; } } uint8_t ks0108::CharWidth(char c) { uint8_t width = 0; uint8_t firstChar = this->FontRead(this->Font+FONT_FIRST_CHAR); uint8_t charCount = this->FontRead(this->Font+FONT_CHAR_COUNT); // read width data if(c >= firstChar && c < (firstChar+charCount)) { c -= firstChar; width = this->FontRead(this->Font+FONT_WIDTH_TABLE+c)+1; } return width; } uint16_t ks0108::StringWidth(char* str) { uint16_t width = 0; while(*str != 0) { width += this->CharWidth(*str++); } return width; } uint16_t ks0108::StringWidth_P(PGM_P str) { uint16_t width = 0; while(pgm_read_byte(str) != 0) { width += this->CharWidth(pgm_read_byte(str++)); } return width; } void ks0108::CursorTo( uint8_t x, uint8_t y){ // 0 based coordinates for fixed width fonts (i.e. systemFont5x7) GotoXY( x * (this->FontRead(this->Font+FONT_FIXED_WIDTH)+1), y * (this->FontRead(this->Font+FONT_HEIGHT)+1) ) ; } void ks0108::GotoXY(uint8_t x, uint8_t y) { uint8_t chip, cmd; if( (x > DISPLAY_WIDTH-1) || (y > DISPLAY_HEIGHT-1) ) // exit if coordinates are not legal return; this->Coord.x = x; // save new coordinates this->Coord.y = y; if(y/8 != this->Coord.page) { this->Coord.page = y/8; cmd = LCD_SET_PAGE | this->Coord.page; // set y address on all chips for(chip=0; chip < DISPLAY_WIDTH/CHIP_WIDTH; chip++){ this->WriteCommand(cmd, chip); } } chip = this->Coord.x/CHIP_WIDTH; x = x % CHIP_WIDTH; cmd = LCD_SET_ADD | x; this->WriteCommand(cmd, chip); // set x address on active chip } void ks0108::Init(boolean invert) { pinMode(D_I,OUTPUT); pinMode(R_W,OUTPUT); pinMode(EN,OUTPUT); pinMode(CSEL1,OUTPUT); pinMode(CSEL2,OUTPUT); delay(10); fastWriteLow(D_I); fastWriteLow(R_W); fastWriteLow(EN); this->Coord.x = 0; this->Coord.y = 0; this->Coord.page = 0; this->Inverted = invert; for(uint8_t chip=0; chip < DISPLAY_WIDTH/CHIP_WIDTH; chip++){ delay(10); this->WriteCommand(LCD_ON, chip); // power on this->WriteCommand(LCD_DISP_START, chip); // display start line = 0 } delay(50); this->ClearScreen(invert ? BLACK : WHITE); // display clear this->GotoXY(0,0); } __inline__ void ks0108::SelectChip(uint8_t chip) { //static uint8_t prevchip; if(chipSelect[chip] & 1) fastWriteHigh(CSEL1); else fastWriteLow(CSEL1); if(chipSelect[chip] & 2) fastWriteHigh(CSEL2); else fastWriteLow(CSEL2); } void ks0108::WaitReady( uint8_t chip){ // wait until LCD busy bit goes to zero SelectChip(chip); lcdDataDir(0x00); fastWriteLow(D_I); fastWriteHigh(R_W); fastWriteHigh(EN); EN_DELAY(); while(LCD_DATA_IN_HIGH & LCD_BUSY_FLAG) ; fastWriteLow(EN); } __inline__ void ks0108::Enable(void) { EN_DELAY(); fastWriteHigh(EN); // EN high level width min 450 ns EN_DELAY(); fastWriteLow(EN); //EN_DELAY(); // some displays may need this delay at the end of the enable pulse } uint8_t ks0108::DoReadData(uint8_t first) { uint8_t data, chip; chip = this->Coord.x/CHIP_WIDTH; this->WaitReady(chip); if(first){ if(this->Coord.x % CHIP_WIDTH == 0 && chip > 0){ // todo , remove this test and call GotoXY always? this->GotoXY(this->Coord.x, this->Coord.y); this->WaitReady(chip); } } fastWriteHigh(D_I); // D/I = 1 fastWriteHigh(R_W); // R/W = 1 fastWriteHigh(EN); // EN high level width: min. 450ns EN_DELAY(); #ifdef LCD_DATA_NIBBLES data = (LCD_DATA_IN_LOW & 0x0F) | (LCD_DATA_IN_HIGH & 0xF0); #else data = LCD_DATA_IN_LOW; // low and high nibbles on same port so read all 8 bits at once #endif fastWriteLow(EN); if(first == 0) this->GotoXY(this->Coord.x, this->Coord.y); if(this->Inverted) data = ~data; return data; } inline uint8_t ks0108::ReadData(void) { this->DoReadData(1); // dummy read return this->DoReadData(0); // "real" read this->Coord.x++; } void ks0108::WriteCommand(uint8_t cmd, uint8_t chip) { if(this->Coord.x % CHIP_WIDTH == 0 && chip > 0){ // todo , ignore address 0??? EN_DELAY(); } this->WaitReady(chip); fastWriteLow(D_I); // D/I = 0 fastWriteLow(R_W); // R/W = 0 lcdDataDir(0xFF); EN_DELAY(); lcdDataOut(cmd); this->Enable(); // enable EN_DELAY(); EN_DELAY(); lcdDataOut(0x00); } void ks0108::WriteData(uint8_t data) { uint8_t displayData, yOffset, chip; //showHex("wrData",data); //showXY("wr", this->Coord.x,this->Coord.y); #ifdef LCD_CMD_PORT uint8_t cmdPort; #endif #ifdef GLCD_DEBUG volatile uint16_t i; for(i=0; i<5000; i++); #endif if(this->Coord.x >= DISPLAY_WIDTH) return; chip = this->Coord.x/CHIP_WIDTH; this->WaitReady(chip); if(this->Coord.x % CHIP_WIDTH == 0 && chip > 0){ // todo , ignore address 0??? this->GotoXY(this->Coord.x, this->Coord.y); } fastWriteHigh(D_I); // D/I = 1 fastWriteLow(R_W); // R/W = 0 lcdDataDir(0xFF); // data port is output yOffset = this->Coord.y%8; if(yOffset != 0) { // first page #ifdef LCD_CMD_PORT cmdPort = LCD_CMD_PORT; // save command port #endif displayData = this->ReadData(); #ifdef LCD_CMD_PORT LCD_CMD_PORT = cmdPort; // restore command port #else fastWriteHigh(D_I); // D/I = 1 fastWriteLow(R_W); // R/W = 0 SelectChip(chip); #endif lcdDataDir(0xFF); // data port is output displayData |= data << yOffset; if(this->Inverted) displayData = ~displayData; lcdDataOut( displayData); // write data this->Enable(); // enable // second page this->GotoXY(this->Coord.x, this->Coord.y+8); displayData = this->ReadData(); #ifdef LCD_CMD_PORT LCD_CMD_PORT = cmdPort; // restore command port #else fastWriteHigh(D_I); // D/I = 1 fastWriteLow(R_W); // R/W = 0 SelectChip(chip); #endif lcdDataDir(0xFF); // data port is output displayData |= data >> (8-yOffset); if(this->Inverted) displayData = ~displayData; lcdDataOut(displayData); // write data this->Enable(); // enable this->GotoXY(this->Coord.x+1, this->Coord.y-8); }else { // just this code gets executed if the write is on a single page if(this->Inverted) data = ~data; EN_DELAY(); lcdDataOut(data); // write data this->Enable(); // enable this->Coord.x++; //showXY("WrData",this->Coord.x, this->Coord.y); } } void ks0108::DrawBitmap(const uint8_t * bitmap, uint8_t x, uint8_t y, uint8_t color){ uint8_t width, height; uint8_t i, j; width = ReadPgmData(bitmap++); height = ReadPgmData(bitmap++); for(j = 0; j < height / 8; j++) { this->GotoXY(x, y + (j*8) ); for(i = 0; i < width; i++) { uint8_t displayData = ReadPgmData(bitmap++); if(color == BLACK) this->WriteData(displayData); else this->WriteData(~displayData); } } } // class wrapper ks0108::ks0108(){ this->Inverted=0; } // Make one instance for the user ks0108 GLCD = ks0108();