Lo scopo di questo articolo è quello di dimostrare come utilizzando i ns sistemi SlimLine (Anche in versione OEM) sia possibile ingegnerizzare sistemi custom abbinando moduli commerciali dotati di interfaccia I2C. Su Internet troverete una infinità di moduli per le più svariate necessità a costi relativamente bassi, molti di questi prodotti vengono pubblicizzati per interfacciamento con Arduino o Raspberry.
Utilizzando la funzione SysI2CWrRd è possibile collegare al bus di espansione dei ns sistemi SlimLine qualsiasi dispositivo con interfaccia I2C, SlimLine provvede anche a generare i 5Vdc di alimentazione del dispositivo. Vediamo come collegando all’interfaccia parallela del modulo LCD QC2004A (Basato su controller Hitachi HD44780U) una scheda interfaccia I2C sia possibile gestire un display da 4 righe da 20 caratteri.
Function block “I2CLCDDisplay”
Questo blocco funzione gestisce una scheda I2C di interfaccia display che si basa sull’integrato PCF8574P, un’espansione di I/O a 8 bit su bus I2C. L’alimentazione è da 3 a 5V e l’indirizzo I2C di default è 16#27. Sulla scheda troviamo
- Un indicatore LED.
- Un potenziometro per il controllo del contrasto.
- Un ponticello per l’abilitazione al comando della retroilluminazione.
- Ponticelli per la configurazione indirizzo sul bus I2C.
La gestione è suddivisa in sequenze non potendo acquisire il segnale di display occupato, viene effettuata una temporizzazione tra le varie sequenze. La scheda interfccia gestisce solo 4 bit del bus quindi tutti i comandi sono suddivisi su due nibbles da 4 bits ciascuno.
Per il passaggio dei dati da visualizzare sul display si utilizza il buffer DData dichiarato come matrice da 4 righe da 21 bytes. Si è scelta la dichiarazione a matrice prchè nell’utilizzo permette di suddividere il testo nelle 4 righe del display potendole gestire indipendentemente. E’ stata dichiarata una lunghezza di 21 bytes (Mentre il display ha solo 20 colonne) per lasciare lo spazio al dato 16#00 di terminazione stringa.
LogicLab (Mdp238, I2CLCDDisplay)
FUNCTION_BLOCK I2CLCDDisplay
VAR
i : UDINT; (* Auxiliary counter *)
Rs : BOOL; (* RS command *)
CaseNr : USINT; (* Case gestione *)
CaseBk : USINT; (* Case back *)
DDIDx : USINT; (* Display data index *)
OData : BYTE; (* Output data (8Bit) *)
DBuffer : ARRAY[0..3] OF BYTE; (* Display buffer *)
TimeBf : UDINT; (* Time buffer (uS) *)
END_VAR
VAR_INPUT
Enable : BOOL; (* FB enable *)
Backlight : BOOL; (* Backlight command *)
I2CAddress : USINT; (* LCD I2C address *)
DData : ARRAY[0..3, 0..20] OF BYTE; (* Display data *)
END_VAR
VAR_OUTPUT
Fault : BOOL; (* FB fault *)
ErrorNr : UDINT; (* Error number *)
END_VAR
// *****************************************************************************
// FUNCTION BLOCK "I2CLCDDisplay"
// *****************************************************************************
// Questo blocco funzione esegue la gestione del controller display SainSmart
// IIC/I2C/TWI 1602 Serial LCD Module Display For Arduino UNO MEGA R3.
// Il controller utilizza un PCF8574 "Remote 8-bit I/O expander for I2C-bus" per
// gestire i segnali di controllo di un display con interfaccia parallela tipo
// QC2004A Systronix basato sul controller HD44780U della Hitachi.
// -----------------------------------------------------------------------------
// Gli 8 ports di uscita del I/O expander sono connessi al display nel modo.
//
// Pin4 P0 -> Pin 4 Rs Display
// Pin5 P1 -> Pin 5 R/W Display
// Pin6 P2 -> Pin 6 En Display
// Pin7 P3 -> Comando backlight
// Pin9 P4 -> Pin 11 D4 Display
// Pin 10 P5 -> Pin 12 D5 Display
// Pin 11 P6 -> Pin 13 D6 Display
// Pin 12 P7 -> Pin 14 D7 Display
// -----------------------------------------------------------------------------
// -------------------------------------------------------------------------
// INIZIALIZZAZIONI
// -------------------------------------------------------------------------
// Eseguo reset bit di one shot.
IF (Fault) THEN Fault:=FALSE; CaseNr:=0; END_IF;
// Gestione errore esecuzione.
IF (ErrorNr <> 0) THEN ErrorNr:=0; Fault:=TRUE; RETURN; END_IF;
// -------------------------------------------------------------------------
// GESTIONE ABILITAZIONE
// -------------------------------------------------------------------------
// Gestione abilitazione blocco funzione.
IF NOT(Enable) THEN CaseNr:=0; RETURN; END_IF;
// -------------------------------------------------------------------------
// TEMPORIZZAZIONE TRA CASES PROGRAMMA
// -------------------------------------------------------------------------
// Siccome non è possibile leggere dal display il segnale di busy, viene
// eseguita una temporizzazione tra i vari cases pari al tempo necessario
// al display per eseguire il comando più lento (Clear display 760 uS).
IF ((SysGetSysTime(TRUE)-TimeBf) < 1000) THEN RETURN; END_IF;
TimeBf:=SysGetSysTime(TRUE); //Time buffer (uS)
// -------------------------------------------------------------------------
// GESTIONE CASE PROGRAMMA
// -------------------------------------------------------------------------
// Eseguo gestione case LCD.
CASE (CaseNr) OF
// ---------------------------------------------------------------------
// INIZIALIZZAZIONE DISPLAY
// ---------------------------------------------------------------------
// In questi cases, viene eseguita la sequenza di reset del display.
// In tutti questa cases occorre eseguire l'interfacciamento a 4 bit,
// al termine della fase sarà possibile accedere al display ad 8 bit
// eseguendo due accessi consecutivi a 4 bit.
// ---------------------------------------------------------------------
// Eseguo comando "Default function set".
0, 1, 2:
IF NOT(SysI2CWrRd(I2CAddress, 3, ADR('$30$34$30'), 0, 0)) THEN ErrorNr:=10; RETURN; END_IF;
CaseNr:=CaseNr+1; //Case gestione
// ---------------------------------------------------------------------
// Eseguo set interfaccia a 4 bit. Dopo questo comando è possibile
// inviare comandi ad 8 bit eseguendo due accessi consecutivi a 4 bit.
3:
IF NOT(SysI2CWrRd(I2CAddress, 3, ADR('$20$24$20'), 0, 0)) THEN ErrorNr:=20; RETURN; END_IF;
CaseNr:=10; //Case gestione
// ---------------------------------------------------------------------
// INIZIALIZZAZIONE DISPLAY
// ---------------------------------------------------------------------
// Eseguo comando "Function set DL, N, F", 8 bits, 2 lines, 5*7 dots
10:
Rs:=FALSE; //RS command
OData:=16#28; //Output data (8Bit)
CaseBk:=CaseNr+1; //Case back
CaseNr:=200; //Uscita dato 8 bit
// ---------------------------------------------------------------------
// Eseguo comando "Display on/off control".
11:
Rs:=FALSE; //RS command
OData:=16#04; //Output data (8Bit)
CaseBk:=CaseNr+1; //Case back
CaseNr:=200; //Uscita dato 8 bit
// ---------------------------------------------------------------------
// Eseguo comando "Clear display".
12:
Rs:=FALSE; //RS command
OData:=16#01; //Output data (8Bit)
CaseBk:=CaseNr+1; //Case back
CaseNr:=200; //Uscita dato 8 bit
// ---------------------------------------------------------------------
// Eseguo comando "Cursor and display shift".
13:
Rs:=FALSE; //RS command
OData:=16#06; //Output data (8Bit)
CaseBk:=CaseNr+1; //Case back
CaseNr:=200; //Uscita dato 8 bit
// ---------------------------------------------------------------------
// Eseguo comando "Display on/off control".
14:
Rs:=FALSE; //RS command
OData:=16#0C; //Output data (8Bit)
CaseBk:=100; //Case back
CaseNr:=200; //Uscita dato 8 bit
// ---------------------------------------------------------------------
// SCRITTURA DATI SU DISPLAY
// ---------------------------------------------------------------------
// ----------------------------------------------------------[1a Riga]--
// Eseguo comando "Set DDRAM address".
100:
DDIDx:=0; //Display data index
Rs:=FALSE; //RS command
OData:=16#80; //Output data (8Bit)
CaseBk:=CaseNr+1; //Case back
CaseNr:=200; //Uscita dato 8 bit
// ---------------------------------------------------------------------
101:
OData:=DData[0, DDIDx]; //Output data (8Bit)
DDIDx:=DDIDx+1; //Display data index
IF (DDIDx >= 20) THEN CaseNr:=110; RETURN; END_IF;
Rs:=TRUE; //RS command
CaseBk:=CaseNr; //Case back
CaseNr:=200; //Uscita dato 8 bit
// ----------------------------------------------------------[2a Riga]--
// Eseguo comando "Set DDRAM address".
110:
DDIDx:=0; //Display data index
Rs:=FALSE; //RS command
OData:=16#C0; //Output data (8Bit)
CaseBk:=CaseNr+1; //Case back
CaseNr:=200; //Uscita dato 8 bit
// ---------------------------------------------------------------------
111:
OData:=DData[1, DDIDx]; //Output data (8Bit)
DDIDx:=DDIDx+1; //Display data index
IF (DDIDx >= 20) THEN CaseNr:=120; RETURN; END_IF;
Rs:=TRUE; //RS command
CaseBk:=CaseNr; //Case back
CaseNr:=200; //Uscita dato 8 bit
// ----------------------------------------------------------[3a Riga]--
// Eseguo comando "Set DDRAM address".
120:
DDIDx:=0; //Display data index
Rs:=FALSE; //RS command
OData:=16#94; //Output data (8Bit)
CaseBk:=CaseNr+1; //Case back
CaseNr:=200; //Uscita dato 8 bit
// ---------------------------------------------------------------------
121:
OData:=DData[2, DDIDx]; //Output data (8Bit)
DDIDx:=DDIDx+1; //Display data index
IF (DDIDx >= 20) THEN CaseNr:=130; RETURN; END_IF;
Rs:=TRUE; //RS command
CaseBk:=CaseNr; //Case back
CaseNr:=200; //Uscita dato 8 bit
// ----------------------------------------------------------[4a Riga]--
// Eseguo comando "Set DDRAM address".
130:
DDIDx:=0; //Display data index
Rs:=FALSE; //RS command
OData:=16#D4; //Output data (8Bit)
CaseBk:=CaseNr+1; //Case back
CaseNr:=200; //Uscita dato 8 bit
// ---------------------------------------------------------------------
131:
OData:=DData[3, DDIDx]; //Output data (8Bit)
DDIDx:=DDIDx+1; //Display data index
IF (DDIDx >= 20) THEN CaseNr:=100; RETURN; END_IF;
Rs:=TRUE; //RS command
CaseBk:=CaseNr; //Case back
CaseNr:=200; //Uscita dato 8 bit
// =====================================================================
// ESEGUO USCITA DATO 8 BIT SU DISPLAY
// =====================================================================
// ---------------------------------------------------------------------
// GESTIONE USCITA MS NIBBLE
// ---------------------------------------------------------------------
// Gestisco segnali di comando verso display.
200:
DBuffer[0]:=OData AND 16#F0;
IF (Rs) THEN DBuffer[0]:=DBuffer[0] OR 16#01; END_IF; //Rs:=TRUE
IF (Backlight) THEN DBuffer[0]:=DBuffer[0] OR 16#08; END_IF; //Backlight:=TRUE
// Copio output buffer in tutti i bytes di uscita dato display.
DBuffer[1]:=DBuffer[0] OR 16#04; //En:=TRUE
DBuffer[2]:=DBuffer[0];
IF NOT (SysI2CWrRd(I2CAddress, 3, ADR(DBuffer), 0, 0)) THEN ErrorNr:=100; RETURN; END_IF;
CaseNr:=CaseNr+1; //Case gestione
// ---------------------------------------------------------------------
// GESTIONE USCITA LS NIBBLE
// ---------------------------------------------------------------------
// Gestisco segnali di comando verso display.
DBuffer[0]:=(OData*16) AND 16#F0; //Output buffer
IF (Rs) THEN DBuffer[0]:=DBuffer[0] OR 16#01; END_IF; //Rs:=TRUE
IF (Backlight) THEN DBuffer[0]:=DBuffer[0] OR 16#08; END_IF; //Backlight:=TRUE
// Copio output buffer in tutti i bytes di uscita dato display.
DBuffer[1]:=DBuffer[0] OR 16#04; //En:=TRUE
DBuffer[2]:=DBuffer[0]; //Output buffer
IF NOT (SysI2CWrRd(I2CAddress, 3, ADR(DBuffer), 0, 0)) THEN ErrorNr:=101; RETURN; END_IF;
CaseNr:=CaseBk; //Case gestione
ELSE
CaseNr:=0; //Case gestione
END_CASE;
// [End of file]
Programma di esempio
In questo programma viene istanziato il FB di gestione display, e viene gestita la visualizzazione dei messaggi. Alla attivazione è visualizzato un messaggio di benvenuto, poi dopo una temporizzazione si visualizza il messsaggio con data/ora.
LogicLab (Mdp238, ST_I2CLCDDisplay)
PROGRAM ST_I2CLCDDisplay
VAR
i : UDINT; (* Auxiliary counter *)
PageNr : USINT; (* Page selector *)
TimeBf : UDINT; (* Time buffer (uS) *)
DTime : SysETimeToDate; (* Conversione in Data/Ora *)
LCD : I2CLCDDisplay; (* LCD management FB *)
END_VAR
// *****************************************************************************
// PROGRAM "ST_I2CLCDDisplay"
// *****************************************************************************
// An example how to use the FB I2CLCDDisplay. It manages a 4*20 LCD display.
// -----------------------------------------------------------------------------
// -------------------------------------------------------------------------
// INITIALIZATION
// -------------------------------------------------------------------------
// Program initializations.
IF (SysFirstLoop) THEN
LCD.I2CAddress:=16#3F; //LCD I2C address
TimeBf:=SysGetSysTime(TRUE); //Time buffer (uS)
END_IF;
// -------------------------------------------------------------------------
// GESTIONE DISPLAY LCD
// -------------------------------------------------------------------------
// Gestione blocco funzione display.
LCD.Backlight:=TRUE; //Backlight command
LCD(Enable:=TRUE); //Gestione FB
// -------------------------------------------------------------------------
// MESSAGE DISPLAY
// -------------------------------------------------------------------------
// By using the "PageNr" variable some messages are displayed.
CASE (PageNr) OF
// ---------------------------------------------------------------------
// WELCOME SCREEN
// ---------------------------------------------------------------------
// At system power up a welcome screen is displayed for 2 seconds.
// +--------------------+
// |I2CLCDDisplay |
// |Hello! |
// | |
// | |
// +--------------------+
0:
i:=Sysmemmove(ADR(LCD.DData[0, 0]), ADR('I2CLCDDisplay '), 20);
i:=Sysmemmove(ADR(LCD.DData[1, 0]), ADR('Hello! '), 20);
i:=Sysmemmove(ADR(LCD.DData[2, 0]), ADR(' '), 20);
i:=Sysmemmove(ADR(LCD.DData[3, 0]), ADR(' '), 20);
IF ((SysGetSysTime(TRUE)-TimeBf) > 2000000) THEN PageNr:=1; END_IF;
// ---------------------------------------------------------------------
// DATE/TIME MESSAGE
// ---------------------------------------------------------------------
// The date and time is displayed.
// +--------------------+
// |SlimLine |
// |Data display |
// |Date:xx/xx/xxxx |
// |Time:xx:xx:xx |
// +--------------------+
1:
DTime(EpochTime:=SysDateTime); //Convert Epoch time
i:=SysVsnprintf(ADR(LCD.DData[0, 0]), 20, ADR('%s'), STRING_TYPE, ADR('SlimLine '));
i:=SysVsnprintf(ADR(LCD.DData[1, 0]), 20, ADR('%s'), STRING_TYPE, ADR('Data display '));
i:=SysVsnprintf(ADR(LCD.DData[2, 0]), 20, ADR('Date:%02d/'), USINT_TYPE, ADR(DTime.Day));
i:=SysCVsnprintf(ADR(LCD.DData[2, 0]), 20, ADR('%02d/'), USINT_TYPE, ADR(DTime.Month));
i:=SysCVsnprintf(ADR(LCD.DData[2, 0]), 20, ADR('%04d '), UINT_TYPE, ADR(DTime.Year));
i:=SysVsnprintf(ADR(LCD.DData[3, 0]), 20, ADR('Time:%02d:'), USINT_TYPE, ADR(DTime.Hour));
i:=SysCVsnprintf(ADR(LCD.DData[3, 0]), 20, ADR('%02d:'), USINT_TYPE, ADR(DTime.Minute));
i:=SysCVsnprintf(ADR(LCD.DData[3, 0]), 20, ADR('%02d '), USINT_TYPE, ADR(DTime.Second));
END_CASE;
// [End of file]