/* * Arduino library to drive an LCD display that uses an ks0073 controller compatible with the HD44780 * Based on the structure of the standard LiquidCrystal.cpp routines for the Hitachi HD44780 chip * The commands seem to be a subset of the HD44780, so this is usable as a replacement * for the LiquidCrystal library, but compatibility hasn't been extensively tested. * * I haven't yet discovered exactly which controller chip they contain * They have a 10 pin, 1mm spaced, PCB flex cable, connected as follows: * 1 LED A Backlight+ * 2 LED A Backlight+ * 3 SID Serial Data Input * 4 LED K Backlight- * 5 SClk Serial clock * 6 Vss Ground * 7 SOD Serial Data Output * 8 Vdd Logic +5V * 9 CS Data register select * 10 VCI Voltage converter input for LCD * * Thanks to the authors of the standard LiquidCrystal.cpp HD44780 library. * Author Tony Wills * Licensed CC-BY-SA * * 20220823 version 1.01 * see http://www.arduino.scorchingbay.nz (or an archive.org copy) for more information */ #include "LiquidCrystal_ks0073.h" #include #include #include #if defined(ARDUINO) && ARDUINO >= 100 #include "Arduino.h" #else #include "WProgram.h" #endif LiquidCrystal_ks0073::LiquidCrystal_ks0073(uint8_t sclk, uint8_t mosi, uint8_t miso, uint8_t ss) { init(sclk, mosi, miso, ss); } LiquidCrystal_ks0073::LiquidCrystal_ks0073(uint8_t sclk, uint8_t mosi, uint8_t ss) { init(sclk, mosi, 255, ss); } void LiquidCrystal_ks0073::init(uint8_t sclk, uint8_t mosi, uint8_t miso, uint8_t ss) { _sclk_pin = sclk; _mosi_pin = mosi; _miso_pin = miso; _ss_pin = ss; digitalWrite(_sclk_pin, HIGH); pinMode(_sclk_pin, OUTPUT); digitalWrite(_mosi_pin, HIGH); pinMode(_mosi_pin, OUTPUT); digitalWrite(_ss_pin, HIGH); pinMode(_ss_pin, OUTPUT); // not supporting miso pin yet, but for future options if (_miso_pin != 255) { pinMode(_miso_pin, INPUT); } } void LiquidCrystal_ks0073::begin(uint8_t cols, uint8_t lines ) { _displayfunction = LCD_8BITMODE | LCD_1LINE | LCD_NRMREG | LCD_NORMAL ; if (lines > 1) { _displayfunction |= LCD_2LINE ; } _numlines = lines; _currline = 0; // set 8bit mode, number of lines command(LCD_FUNCTIONSET | _displayfunction); // turn the display on with no cursor or blinking default _displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF; display(); // clear it off clear(); // Initialize to default text direction (for romance languages) _displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT; // set the entry mode command(LCD_ENTRYMODESET | _displaymode); } /********** high level commands, for the user! */ void LiquidCrystal_ks0073::clear() { command(LCD_CLEARDISPLAY); // clear display, set cursor position to zero delay(2); // this command takes a long time! } void LiquidCrystal_ks0073::home() { command(LCD_RETURNHOME); // set cursor position to zero delay(2); // this command takes a long time! } void LiquidCrystal_ks0073::setCursor(uint8_t col, uint8_t row) { int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 }; if ( row > _numlines ) { row = _numlines-1; // we count rows starting with 0 } command(LCD_SETDDRAMADDR | (col + row_offsets[row])); } // Turn the display on/off (quickly) void LiquidCrystal_ks0073::noDisplay() { _displaycontrol &= ~LCD_DISPLAYON; command(LCD_DISPLAYCONTROL | _displaycontrol); } void LiquidCrystal_ks0073::display() { _displaycontrol |= LCD_DISPLAYON; command(LCD_DISPLAYCONTROL | _displaycontrol); } // Turns the underline cursor on/off void LiquidCrystal_ks0073::noCursor() { _displaycontrol &= ~LCD_CURSORON; command(LCD_DISPLAYCONTROL | _displaycontrol); } void LiquidCrystal_ks0073::cursor() { _displaycontrol |= LCD_CURSORON; command(LCD_DISPLAYCONTROL | _displaycontrol); } // Turn on and off the blinking cursor void LiquidCrystal_ks0073::noBlink() { _displaycontrol &= ~LCD_BLINKON; command(LCD_DISPLAYCONTROL | _displaycontrol); } void LiquidCrystal_ks0073::blink() { _displaycontrol |= LCD_BLINKON; command(LCD_DISPLAYCONTROL | _displaycontrol); } // Turn on and off the reverse video // This only works when the IE pin on the ks0073 chip is wired to "high" (selects instruction set 1) // but the vlgem1021 LCD has it wired "low" void LiquidCrystal_ks0073::noReverse() { _displayfunction &= ~LCD_REVERSE; command(LCD_FUNCTIONSET | _displayfunction); } void LiquidCrystal_ks0073::reverse() { _displayfunction |= LCD_REVERSE; command(LCD_FUNCTIONSET | _displayfunction); } // These commands scroll the display without changing the RAM // There is also a dot shift/scroll operation on the ks0073, // that uses the "Set Scroll Quantity" command to shift the display // by one dot. But with the slowness of LCD transitions, // doesn't really give a smooth scroll. So not implimented here. void LiquidCrystal_ks0073::scrollDisplayLeft(void) { command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT); } void LiquidCrystal_ks0073::scrollDisplayRight(void) { command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT); } // This is for text that flows Left to Right void LiquidCrystal_ks0073::leftToRight(void) { _displaymode |= LCD_ENTRYLEFT; command(LCD_ENTRYMODESET | _displaymode); } // This is for text that flows Right to Left void LiquidCrystal_ks0073::rightToLeft(void) { _displaymode &= ~LCD_ENTRYLEFT; command(LCD_ENTRYMODESET | _displaymode); } // This will 'right justify' text from the cursor void LiquidCrystal_ks0073::autoscroll(void) { _displaymode |= LCD_ENTRYSHIFTINCREMENT; command(LCD_ENTRYMODESET | _displaymode); } // This will 'left justify' text from the cursor void LiquidCrystal_ks0073::noAutoscroll(void) { _displaymode &= ~LCD_ENTRYSHIFTINCREMENT; command(LCD_ENTRYMODESET | _displaymode); } // Allows us to fill the first 8 CGRAM locations // with custom characters void LiquidCrystal_ks0073::createChar(uint8_t location, uint8_t charmap[]) { location &= 0x07; // we only have 8 locations 0-7 sequenceBegin(BGN_COMMAND); send(LCD_SETCGRAMADDR | (location << 3)); sequenceBegin(BGN_WRITE); for (uint8_t i=0; i<8; i++) { send(charmap[i]); } sequenceEnd(); setCursor(0,0); // return to writing data ram } /*********** mid level commands, for sending data/cmds */ // used for singe byte commands inline void LiquidCrystal_ks0073::command(uint8_t value) { sequenceBegin(BGN_COMMAND); send(value); sequenceEnd(); } // used by print library to send characters to display inline size_t LiquidCrystal_ks0073::write(uint8_t value) { sequenceBegin(BGN_WRITE); // send(LCD_DISPLAYCONTROL|LCD_DISPLAYON); send(value); sequenceEnd(); return 1; // assume sucess } /************ low level data pushing commands **********/ // send either command or data as part of a sequence of one or more bytes void LiquidCrystal_ks0073::send(uint8_t value) { write8bits(value); } // select LCD to begin writing command and possibly data // bit bang the data out, LSB first void LiquidCrystal_ks0073::sequenceBegin(uint8_t value) { digitalWrite(_ss_pin, LOW); for (int i = 0; i < 8; i++) { digitalWrite(_mosi_pin, !!((value >> i) & 0x01)); digitalWrite(_sclk_pin, LOW); delayMicroseconds(CLK_PER); digitalWrite(_sclk_pin, HIGH); delayMicroseconds(CLK_PER); } } // deselect LCD, writing done void LiquidCrystal_ks0073::sequenceEnd(void) { digitalWrite(_ss_pin, HIGH); delayMicroseconds(80); // time to process command/data } // bit bang the data out, LSB first // KS0073 needs serial data broken into two 4bit nibbles, padded out to 8 bits each void LiquidCrystal_ks0073::write8bits(uint8_t value) { uint8_t nibble = value & 0x0F; for (int i = 0; i < 8; i++) { digitalWrite(_mosi_pin, !!((nibble >> i) & 0x01)); digitalWrite(_sclk_pin, LOW); delayMicroseconds(CLK_PER); digitalWrite(_sclk_pin, HIGH); delayMicroseconds(CLK_PER); } nibble = (value >> 4); for (int i = 0; i < 8; i++) { digitalWrite(_mosi_pin, !!((nibble >> i) & 0x01)); digitalWrite(_sclk_pin, LOW); delayMicroseconds(CLK_PER); digitalWrite(_sclk_pin, HIGH); delayMicroseconds(CLK_PER); } digitalWrite(_mosi_pin,HIGH); }