TWI (I2C) LCD kezelése

 

2020-05-30

Utoljára jó régen foglalkoztam az Arduinós kütyüimmel. Tavaly az I2C-s, AVR-es doksik szerint TWI portos LCD kijelzővel bajlódtam, de nem tettem pontot a munka végére. Ezt melegítettem fel, és szokásom szerint egy csomó zsákutca után egy egész takaros, kt_tlcd névre keresztelt objektumot tudok bemutatni nektek. Az I2C (alias TWI) soros port legnagyobb előnye, hogy két vezetéken keresztül elméletileg akár 127 egységet tudunk vezérelni. Minden esetre, lábtakarékos megoldás. A hátránya, hogy 100 kHz-es órajelen alapszik, ami adott esetben lehet, hogy lassú. Igaz, ezt a buszt a Philips anno TV IC-k közöti komunikációra fejlesztette ki. Alap esetben az I2C busz kezelése nem bonyolult, van egy Master, ami az Arduinónk AVR mikrovezérlője, és vannak a Slave egységek, amelyeknek különböző címük van. Ha utána olvasunk az I2C-nek, azért vannak olyan őrültségek, amikor több Master van egy buszon, de talán nem is csinál senki ilyet. Érdekes, hogy Master-t viszonylag könnyen le lehet programozni két vezeték rángatásával, a Slave keményebb dió. Az Arduinókban lévő AVR-ek tartalmaznak hardware-es TWI (I2C) portot, és annyira elterjedt megoldas, hogy az Arduinonak van ennek kezelésére egy Wire osztálya. Az alábbi képen látható, hogy egy Arduino Nano-val vezérlek egy I2C portos, háttérvilágításos LCD-t.

Talán a panel felső szegélyén látható feliratokból is sejthető, hogy ez egy Hitachi HD44780 vezérlő kompatibilis LCD. A hátuljára van forrasztva egy PCF8574 I2C port expanderen alapuló kis kártya. Ez csinál a TWI buszunkból párhuzamos 8 bites portot. A kártyán található egy trimmer poti, ezt be kell állítsuk, hogy az LCD-n látszódjon a kiírás. Van rajta A0/A1/A2 jumper, amikkel 0..7 címre állíthatjuk a kártyát, vagyis 8 db-ot tehetünk egy buszra. A panel csücskén még felfedezhető egy kapcsoló tranzisztor, ami a háttérvilágítást kapcsolja. A kilógó jumper-rel ez megszakítható. Hogy ennek mi értelme? Egyébként sincs sok értelme a dolognak, háttérvilágítás nélkül ez az LCD szinte olvashatatlan. Talán valami energiatakarékoskodásra lehet jó, bár akkor meg az ember másik kijelzőt választ. Hazai beszerzés felejtős, Kínából házhoz szállítva nem kerül 3 USD-be darabja, csak ki kell várni :(.

Sajnos megint belebonyolódtam, hogy az Interneten fellelhető Arduino library-k nem tetszettek. Komplikáltak, olyan kódok vannak bennük, amit sohasem fogok használni, és kicsit bele is akartam a PCF8574 használatába ásni magam, mert további terveim is vannak vele. Az alább közzétett program ezt írja ki az kijelzőnkre.

Ez tulajdonképpen egy fejlesztési program példány, amiben benne van a header, a library és a csillagos sorminta alatt maga a program. A magyar betük kezelése nincs benne. Nem emlékszem már pontosan (akit érdekel olvasson utána) , maga kt_tlcd osztály a print osztályból van származtatva, és a print-nek szüksége van egy write függvényre, amivel ki tudja pötyögni amit kell. Ha tanulmányozzuk a send(), a send4bit() és a write_8574() függvényeket, láthatjuk, ha 1 byte-ot el akarunk küldeni az LCD-nek, az azt jelenti, hogy 6x kell elküldenünk a PCF8574 címét és az adatot, plusz start/stop biteket, és mindezt 100 kHz-en, szóval így lesz ez lassú. Általában alapból nem kapcsolják fel a háttérvilágítást, de én igen. Ha nincs az LCD-nek háttérvilágítása, akkor úgysem számít, ha van, anélkül szinte olvashatatlan. Olyan rövid lett az egész a C++ tömörségének köszönhetően, hogy én is csodálkozom, biztos benne van minden? De igen, ennyi az egész.


/******************************************************************************
*   Author       -  Kiraly Tibor
*                   http://www.tkiraaly.hu
*   Date         -  2020.05.29.
*   Chip         -  Arduino, PCF8574, HD44780 LCD
*   Compiler     -  Arduino IDE 1.8.12
*
*   TWI (I2C) LCD kezelese, print()-tel irhato
*    
*      NANO        MEGA          PCF 8574          LCD
*    -------+    -------+      +----------+      +----------+
*           !           !      !          !      !          !
*       GND +--     GND +--  --+ GND  GND +------+ GND      !
*       VCC +--     VCC +--  --+ VCC  VCC +------+ VCC      !
*        A5 +--     D21 +--  --+ SCL   P0 +------+ RS       !
*        A4 +--     D20 +--  --+ SDA   P1 +------+ RW       !
*           !           !      !       P2 +------+ EN       !
*           !           !      !       P3 +------+ BL       !
*                            --+ A0    P4 +------+ D4       !
*                            --+ A1    P5 +------+ D5       !
*                            --+ A2    P6 +------+ D6       !
*                              !       P7 +------+ D7       !
*                              !          !      !          !
*                              +----------+      +----------+
* 
*   A0  A1  A2   HEX address
*    1   1   1   0x27 - default
*    0   1   1   0x26
*    1   0   1   0x25
*    0   0   1   0x24
*    1   1   0   0x23
*    0   1   0   0x22
*    1   0   0   0x21
*    0   0   0   0x20
*    
******************************************************************************/

#include "Arduino.h"
#include "Wire.h"
#include "Print.h" 


// commands for HD44780 LCD
#define LCD_CLEARDISPLAY          0x01
#define LCD_RETURNHOME            0x02
#define LCD_ENTRYMODESET          0x04
#define LCD_DISPLAYCONTROL        0x08
#define LCD_CURSORSHIFT           0x10
#define LCD_FUNCTIONSET           0x20
#define LCD_SETCGRAMADDR          0x40
#define LCD_SETDDRAMADDR          0x80

// for ENTRYMODESET
#define LCD_ENTRYRIGHT            0
#define LCD_ENTRYLEFT             0x02
#define LCD_ENTRYSHIFTINCREMENT   0x01
#define LCD_ENTRYSHIFTDECREMENT   0

// for DISPLAYCONTROL
#define LCD_DISPLAYON             0x04
#define LCD_DISPLAYOFF            0
#define LCD_CURSORON              0x02
#define LCD_CURSOROFF             0
#define LCD_BLINKON               0x01
#define LCD_BLINKOFF              0

// for CURSORSHIFT
#define LCD_DISPLAYMOVE           0x08
#define LCD_CURSORMOVE            0
#define LCD_MOVERIGHT             0x04
#define LCD_MOVELEFT              0

// for FUNCTIONSET
#define LCD_8BITMODE              0x10
#define LCD_4BITMODE              0
#define LCD_2LINE                 0x08
#define LCD_1LINE                 0
#define LCD_5x10DOTS              0x04
#define LCD_5x8DOTS               0

#define LCD_BACKLIGHT             B00001000      // Backlight bit/pin
#define LCD_EN                    B00000100      // Enable bit/pin
#define LCD_RW                    B00000010      // Read/Write bit
#define LCD_RS                    B00000001      // Register select bit


class kt_tlcd : public Print
{
   public:
   kt_tlcd( uint8_t, uint8_t, uint8_t);           // address, columns, rows
   void init();
   void cls() { send( LCD_CLEARDISPLAY); delay( 2);}
   void xy( uint8_t, uint8_t);                    // column, row
   void cursor_off() { send( LCD_DISPLAYCONTROL+ LCD_DISPLAYON);}
   void cursor_on() { send( LCD_DISPLAYCONTROL+ LCD_DISPLAYON+ LCD_CURSORON);}
   void cursor_blink() { send( LCD_DISPLAYCONTROL+ LCD_DISPLAYON+ LCD_CURSORON+ LCD_BLINKON);}
   void backlight_on() { _backlight= LCD_BACKLIGHT; write_8574( 0);}
   void backlight_off() { _backlight= 0; write_8574( 0);}
   virtual size_t write( uint8_t value) { send( value, LCD_RS); return 1;}            // for print
   void create_char( uint8_t, uint8_t[]);         // number, character map

   private:
   void send( uint8_t, uint8_t= 0);
   void send4bits( uint8_t);
   void write_8574( uint8_t);
   uint8_t _addr;
   uint8_t _cols;
   uint8_t _rows;
   uint8_t _backlight;
};


kt_tlcd::kt_tlcd( uint8_t a, uint8_t c, uint8_t r)
{
   _addr= a;
   _cols= c;
   _rows= r;
   _backlight= LCD_BACKLIGHT;
}


void kt_tlcd::init()
{
   Wire.begin();
   delay( 50); 
   write_8574( 0);	                          // reset expander
   delay( 10);                           
   send4bits( 0x03 << 4);                         // put the LCD into 4 bit mode
   delayMicroseconds( 4500);                      // wait min 4.1ms
   send4bits( 0x03 << 4);
   delayMicroseconds( 4500);                      // wait min 4.1ms
   send4bits( 0x03 << 4); 
   delayMicroseconds( 150);
   send4bits( 0x02 << 4); 
   if ( _rows > 1) send( LCD_FUNCTIONSET+ LCD_2LINE+ LCD_4BITMODE+ LCD_5x8DOTS);
   else            send( LCD_FUNCTIONSET+ LCD_1LINE+ LCD_4BITMODE+ LCD_5x8DOTS);
   send( LCD_DISPLAYCONTROL+ LCD_DISPLAYON+ LCD_CURSOROFF);
   cls();
   send( LCD_ENTRYMODESET+ LCD_ENTRYLEFT+ LCD_ENTRYSHIFTDECREMENT);
   send( LCD_RETURNHOME);                         // set cursor position to home
   delay( 2);
}


void kt_tlcd::xy( uint8_t col, uint8_t row)
{
   static int row_offsets[]= { 0x00, 0x40, 0x14, 0x54};
   if ( row > _rows) row= 0;
   send( LCD_SETDDRAMADDR+ row_offsets[ row]+ col);
}


void kt_tlcd::create_char( uint8_t location, uint8_t charmap[])
{
   send( LCD_SETCGRAMADDR+ ( ( location& 0x07) << 3));
   for( int i= 0; i < 8; i++) write( charmap[ i]);
}


void kt_tlcd::send( uint8_t value, uint8_t mode)
{
   send4bits(( value& 0xf0)| mode);                 // high nibble
   send4bits((( value << 4)& 0xf0)| mode);          // low nibble
}


void kt_tlcd::send4bits( uint8_t value)
{
   write_8574( value);
   write_8574( value| LCD_EN);                      // EN high
   delayMicroseconds( 1);                           // need > 450ns
   write_8574( value& ~LCD_EN);                     // EN low
   delayMicroseconds( 50);                          // need > 37us
} 


void kt_tlcd::write_8574( uint8_t value)
{                                        
   Wire.beginTransmission( _addr);
   Wire.write( value| _backlight);
   Wire.endTransmission();   
}

/******************************************************************************/


kt_tlcd lcd( 0x27, 16, 2);                          // addres, chars, rows


void setup()
{
   lcd.init();
   lcd.xy( 0, 0);
   lcd.print( "Hello, World!");
   lcd.xy( 0, 1);
   lcd.print( "kt_tlcd");
}


void loop()
{
}

Ennyi az egész, itt a vége, fuss el véle.