// vi:ts=4 // --------------------------------------------------------------------------- // hd44780_NTCU165ECPB.h - hd44780_NTCU165ECPB i/o subclass for hd44780 library // Copyright (c) 2016-2020 Bill Perry // // The API functionality provided by this library class is compatible // with the API functionality of the Arduino LiquidCrystal library. // // --------------------------------------------------------------------------- // // This file is part of the hd44780 library // // hd44780_NTCU165ECPB is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation version 3 of the License. // // hd44780_NTCU165ECPB 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. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with hd44780_NTCU165ECPB. If not, see . // // --------------------------------------------------------------------------- // // It implements hd44780 library subclass i/o methods to control a // Noritake CU165ECBP-T2J display. // The Noritake CU165ECBP-T2J display is a 16x1 line VFD display. // It is not a hd44780 display. So while this device is supported by hd44780, // it will not be capable of supporting all the hd44780 capabilities. // The subclass will do its best to emulatate hd44780 functions and map // them to equivalent functionality when possible but certain capabilities // will not be possible. // // Datasheet can be found here: // http://www.blog.zapro.dk/wp-content/CU165.pdf // Full datasheet/release notes: // http://www.blog.zapro.dk/wp-content/CU165ECPB_T2J_specification.pdf // // The device uses SPI for communication. // Pinout: // 1 Vcc // 2 GND // 3 /CS (SS) // 4 CLK (SCK) // 5 DATA (MOSI) // // --------------------------------------------------------------------------- // history // // 2017.12.23 bperrybap - added support LCD API 1.0 init() // 2016.08.22 bperrybap - initial creation // // @author Bill Perry - bperrybap@opensource.billsworld.billandterrie.com // // --------------------------------------------------------------------------- #ifndef hd44780_NTCU165ECPB_h #define hd44780_NTCU165ECPB_h class hd44780_NTCU165ECPB : public hd44780 { public: // ==================== // === constructors === // ==================== // no parameters: use h/w spi signals // Note: This doesn't work for Leonardo since SS is not available and only drives an LED hd44780_NTCU165ECPB() : hd44780(16,1), _cs(SS), _clk(SCK), _data(MOSI) { } // supply alternate chip/slave select // will use h/w spi with alternate chip select hd44780_NTCU165ECPB(int cs) : hd44780(16,1), _cs(cs), _clk(SCK), _data(MOSI) { } // supply pins for chip select, clock, and data hd44780_NTCU165ECPB(int cs, int clock, int data) : hd44780(16,1), _cs(cs), _clk(clock), _data(data) { } private: // ==================== // === private data === // ==================== // Arduino Pin information const uint8_t _cs; // arduino pint for slave select (chip select) const uint8_t _clk; // arduino pin for clock const uint8_t _data; // arduino pin for master out slave in (data) // other internal variables uint8_t _cgmode; // set when in cgramaddr mode uint8_t _udf[5]; // udf buffer area uint8_t _cgindx; // index into cgdata (will be 0 to 7) uint8_t _cgchar; // custom character (0 to 7, this board goes to f) // device commands static const uint8_t CMD_SETDISPLAYLEN = 0x00; // lower 3 bits sets total digits/cols // to display // 0 is 9 and 7 means all 16 digits/cols static const uint8_t CMD_SETBRIGHTNESS = 0x08; // lower 3 bits sets brigtness // 0 is 1/16 and 7 is 14/16 max static const uint8_t CMD_SETDIGSCAN160 = 0xF6; // set digit scan time to 160us static const uint8_t CMD_SETDIGSCAN320 = 0xF7; // set digit scan time to 320us static const uint8_t CMD_SETDIGPOINTER = 0xE0; // set column, lower 4 bits sets column static const uint8_t CMD_NOAUTOINCREMENT = 0xF0; // no auto advance to next column static const uint8_t CMD_AUTOINCREMENT = 0xF1; // auto advance to next column static const uint8_t CMD_DISPLAYOFF = 0xF0; // all digits/dots off static const uint8_t CMD_DISPLAYON = 0xF1; // normal display mode static const uint8_t CMD_DISPLAYALLON = 0xF3; // all digits, all dots on static const uint8_t CMD_STOREUDF = 0xFC; // begin user defined font (custom char) // There are 16 custom chars, they are // accessed/printed using 0x90-0x9F // they are programmed using locations 0-F // Each character glyph is 5 bytes. // Each byte is vertical 7 pixels with the // MSB at the top and the LSB dropped. // The first byte is the left most column of // the glyph. // full command data is 7 bytes: // 0xFC, 5 bytes of data // start of custom user defined font (UDF) characters // There are 16 UDF characters that start here static const uint8_t UDF_BASE = 0x90; // ================================================== // === hd44780 i/o subclass virtual i/o functions === // ================================================== // // Note: // It is not possible to read from the device. // so ioread(), and & iostatus() // will not be defined and will use the defaults in // hd44780 which will return an apropriate error status // ioinit() - Returns non zero if initialization failed. int ioinit() { int status = 0; // setup SPI signals digitalWrite(_cs, HIGH); pinMode(_cs, OUTPUT); pinMode(_clk, OUTPUT); digitalWrite(_clk, LOW); pinMode(_data, OUTPUT); // check to see if SPI has beginTransaction() and endTransaction() // this was added in IDE 1.06 and so if using an IDE older than that, // H/W spi will not be used // This requires that be included prior to this header #if (defined(SPI_HAS_TRANSACTION) && !defined(ARDUINO_ARCH_ESP32)) || \ (defined(ARDUINO_ARCH_ESP32) && defined(_SPI_H_INCLUDED) && defined(SPI_HAS_TRANSACTION)) // check to see if h/w spi can be used if((_data == MOSI) && (_clk == SCK)) SPI.begin(); #endif // maybe look at cols to see how many should be turned on? devicewrite(CMD_SETDISPLAYLEN|0x7); // show all 16 characters devicewrite(CMD_SETDIGSCAN320); // set digit scan time to 320us devicewrite(CMD_AUTOINCREMENT); // set auto advance to next column on writes // for now turn on display and "backlight" // clear and set to "home" address position devicewrite(CMD_DISPLAYON); devicewrite(CMD_SETBRIGHTNESS | 0x7); // max brightness device_clear(); return ( status ); } // // iowrite() - send either a command or data byte to lcd // returns zero on success, non zero on failure // // Things are a bit tricky here since this is not a hd44780 device. // The code must look at the commands and emulate them or drop them // int iowrite(hd44780::iotype type, uint8_t value) { /* * waitReady() is not called as this module does not need any execution time * delays even when using a 4mHz h/w clock. */ /* * 4 bit commands are part of low level hd44780 initalization. * since this is not a hd44780 display, just throw them away * and say they "worked" */ if(type == hd44780::HD44780_IOcmd4bit) return(0); if(type == hd44780::HD44780_IOcmd) { return(hd44780cmd(value)); } if(_cgmode) { // in cgram mode so have to process custom character data. // hd44780 font data is stored horizontally in 8 bytes // with each byte being 1 row of dots // with upper 3 bits of each byte ignored. // first byte of the 8 bytes is upper row of dots. // Noritake UDF format is vertical columns of 5 bytes with // the LSB of each byte ignored as it only has 5x7 dots. // the first byte is the right most column of 7 dots of the font. // // Because of this mismatch, the bits & bytes have to be flipped // around and stored in the 5 UDF bytes until the full 8 hd44780 font // bytes come in. Once all 8 hd44780 font bytes are in, // the 5 byte UDF font definition can be pushed to the device. for(int i = 0; i < 5; i++) { if( (value & (1<< i))) { _udf[4-i] |= (1 << (7- _cgindx)); } else { _udf[4-i] &= ~(1 << (7-_cgindx)); } } _cgindx++; if(_cgindx > 7) // is this the last of the 8 bytes? { // 0xFC, 5 bytes of data devicewrite(CMD_STOREUDF); devicewrite(_cgchar); for(int i = 0; i < 5; i++) { devicewrite(_udf[i]); } // now send it again for the 8-f version // to emulate the hd44780 custom chars // that duplicate 0-7 at 8-f devicewrite(CMD_STOREUDF); devicewrite(_cgchar+8); for(int i = 0; i < 5; i++) { devicewrite(_udf[i]); } // note, cgmode is still in effect. // the UDF/font byte index is reset back to 0, // and the cgchar location is bumped. // this is to allow for the case when the code may // be attempting to program more than a single character at once. _cgindx = 0; _cgchar++; _cgchar &= 0xf; } return(0); } // upper 32 bytes are for commands so drop chars that are in the 0xE0 to 0xFF range if(value >= 0xE0) { return(0); } // check for hd44780 custom font characters, // The hd44780 character code must be mapped to the UDF characters // which start at a different location. if(value < 16) { value += UDF_BASE; } devicewrite(value); return(0); } // iosetBacklight() - set backlight brightness // Since this display does not use a backlight but can control pixel intensity // use the backlight brightness to set the pixel intensity. // // Note: // The dimmer value on the CU165 is 3 bit value // other than the 000 value it looks like the value is mapped to a 4 bit // value by adding a 0 msb. // 000 is 1/16 // 001 is 2/16 // 010 is 4/16 // 011 is 6/16 // 100 is 8/16 // 101 is 10/16 // 110 is 12/16 // 111 is 14/16 // // Becauswe of this, an API dimvalue of 0 does not turn off the display pixels // so the code must check for that and explicitly turn off the display if the // API dimvalue is zero. int iosetBacklight(uint8_t dimvalue) { if(dimvalue) { // the dim value is 0-255 so devide by 32 to get a value 0-7 devicewrite(CMD_SETBRIGHTNESS | (dimvalue/32)); devicewrite(CMD_DISPLAYON); } else { devicewrite(CMD_DISPLAYOFF); } return(RV_ENOERR); } // ================================ // === internal class functions === // ================================ // devicwrite() - send a byte to the device void devicewrite(uint8_t value) { // use h/w spi if we can // note: checking for SPI support (SPI.h included) is messy since // ESP32 core always defines SPI_HAS_TRANSACTION even when SPI.h is not included #if (defined(SPI_HAS_TRANSACTION) && !defined(ARDUINO_ARCH_ESP32)) || \ (defined(ARDUINO_ARCH_ESP32) && defined(_SPI_H_INCLUDED) && defined(SPI_HAS_TRANSACTION)) if((_data == MOSI) && (_clk == SCK)) { // NOTE: spec says 2Mhz is max, 4Mhz seems to work. SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0)); digitalWrite(_cs,LOW); // select device SPI.transfer(value); delayMicroseconds(1); // must delay at least 130ns before de-selecting digitalWrite(_cs,HIGH); // deselect device SPI.endTransaction(); } else #endif { digitalWrite(_cs,LOW); // select device myshiftOut(_data, _clk, MSBFIRST, value); delayMicroseconds(1); // must delay at least 130ns before de-selecting digitalWrite(_cs,HIGH); // deselect device } } // can't use standard shiftOut() on esp32 because it is too fast for display // void myshiftOut(uint8_t dataPin, uint8_t clockPin, uint8_t bitOrder, uint8_t val) { uint8_t i; for (i = 0; i < 8; i++) { if (bitOrder == LSBFIRST) { digitalWrite(dataPin, val & 1); val >>= 1; } else { digitalWrite(dataPin, (val & 128) != 0); val <<= 1; } // insert delay on super fast processors to slow down clock // and allow a bit of time for slew rate rise when using 3v processor with 5v logic #if (F_CPU > 40000000) delayMicroseconds(1); // min pulse width is 230ns #endif digitalWrite(clockPin, HIGH); #if (F_CPU > 40000000) // insert delay on super fast processors to slow down clock delayMicroseconds(1); // min pulse width is 230ns #endif digitalWrite(clockPin, LOW); } } // hd44780cmd() - emulate hd44780 commands best we can // for now, silently drop unsupported commands int hd44780cmd(uint8_t value) { // detect the hd44780 command if(value & HD44780_SETDDRAMADDR) { _cgmode = 0; // back to ddram mode devicewrite(CMD_SETDIGPOINTER | (value & 0xf)); } else if(value & HD44780_SETCGRAMADDR) { _cgmode = 1; // enter cgram mode _cgindx = 0; _cgchar = (value >> 3) & 0x7; // for now limmit to 8 custom like hd44780 } else if(value & HD44780_FUNCTIONSET) { // not supported } else if(value & HD44780_CURDISPSHIFT) { // not supported } else if(value & HD44780_DISPLAYCONTROL) { if(value & HD44780_DISPLAYON) devicewrite(CMD_DISPLAYON); else devicewrite(CMD_DISPLAYOFF); } else if(value & HD44780_ENTRYMODESET) { // not supported } else if(value & HD44780_RETURNHOME) { _cgmode = 0; // back to ddram mode devicewrite(CMD_SETDIGPOINTER); } else if(value & HD44780_CLEARDISPLAY) { _cgmode = 0; // back to ddram mode device_clear(); } return(0); } void device_clear() { devicewrite(CMD_SETDIGPOINTER); for(uint8_t col = 0; col < 16; col++) { devicewrite(' '); } devicewrite(CMD_SETDIGPOINTER); } }; // end of class definition #endif