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.