Gestire display LCD connesso al bus di espansione

List

Questa pagina fa parte del Manuale Programmazione IEC 61131-3. Vai all indice.

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.

Display LCD connesso a SlimLIne

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]
Was this article helpful?