29.12.2015

Wyświetlacz LCD HD44780 na I2C

Wyświetlacze LCD zgodne z HD44780 niewielkim kosztem można przystosować do sterowania poprzez interfejs I2C. Uprości się wówczas sposób podłączania takiego wyświetlacza do układu. Zamiast sześciu linii GPIO wystarczy dwa przewody magistrali I2C plus zasilanie. Taki moduł można za niewielkie pieniądze zakupić i samemu przylutować do LCD lub od razu kupić wyświetlacz z wlutowanym adapterem. Adaptery przeważnie zbudowane są na układzie PCF8574. Najczęściej spotykane są takie adaptery jak na obrazku poniżej:

I2C LCD module
A tutaj adapter przylutowany do wyświetlacza LCD:

Uwaga! Inne adaptery (inaczej wyglądające) lub z innym  układem niż PCF8574T (na przykład z PCF8574TA) mogą mieć inaczej podłączone wyprowadzenia lub inny adres na magistrali I2C niż 0x27. Adapter do STM32F4Discovery (port dla NET MF 4.2) podłączamy następująco:

GND  -  GND
VCC  -  +5V
SDA  -  PB9
SCL  -  PB6

Teraz wystarczy tylko napisać nowy sterownik do biblioteki μLiquidCrystal i po kłopocie. Musimy zacząć od tego, jak jest podłączony układ PCF8574 do wyświetlacza. Dla adaptera takiego jak powyżej bity wartości wysłanej na interfejs I2C mają następujące znaczenie: 

                  (MSB) P7 P6 P5 P4 P3 P2 P1 P0 (LSB)
                        |  |  |  |   |  |  |  |
                   D7 __|  |  |  |   |  |  |  |__ RS (0 - cmd, 1 - data)
                   D6 _____|  |  |   |  |  |_____ RW (0 - write, 1 - read)
                   D5 ________|  |   |  |________ E  (falling edge)
                   D4 ___________|   |___________ Backlight (on/off)


Cała robota sprowadza się więc do napisania procedury, która będzie odpowiednio ustawiać poszczególne bity w wartości wysyłanej na interfejs I2C. Dodatkową komplikacją jest tryb 4 bitowej pracy wyświetlacza: najpierw trzeba wysłać 4 starsze bity wartości, a potem młodsze. Implementacja sterownika może wyglądać tak:

using MicroLiquidCrystal;
using Microsoft.SPOT.Hardware;

public class Pcf8574 : ILcdTransferProvider
{
    private readonly I2CDevice _device;
    private readonly I2CDevice.Configuration _config;
    private readonly I2CDevice.I2CTransaction[] _transactions;

    public bool FourBitMode
    {
        get { return true; }
    }

    public Pcf8574(I2CDevice device, ushort address = 0x27)
    {
        _device = device;
        _config = new I2CDevice.Configuration(address, 100);

        I2CDevice.I2CTransaction tran = I2CDevice.CreateWriteTransaction(new byte[1]);
        _transactions = new[] {tran};
    }

    public void Send(byte data, bool mode, bool backlight)
    {
        //Hi 4 bits
        int send = data & 0xF0;

        if (backlight)
            send |= 0x08; //P3

        if (mode)
            send |= 0x01; //P0

        Send((byte) (send | 0x04)); //E = 1
        Send((byte) send); //E = 0

        //Lo 4 bits + backlight + mode
        send = (data << 4) | (send & 0x0F);
        Send((byte) (send | 0x04)); //E = 1
        Send((byte) send); //E = 0
    }

    private void Send(byte data)
    {
        lock (_device)
        {
            _device.Config = _config;
            _transactions[0].Buffer[0] = data;
            _device.Execute(_transactions, 100);
        }
    }
}

Może wyjaśnienia troszkę wymaga konstruktor oraz druga procedura Send. W konstruktorze tworzymy i zapamiętujemy transakcję dla I2C tak, aby nie tworzyć jej za każdym razem. W procedurze Send przypisujemy bezpośrednio wartość do bufora tej transakcji. Dodatkowo w tej procedurze blokujemy I2CDevice i ponownie przypisujemy konfigurację. Pozwala to na współdzielenie magistrali I2C przez inne peryferia. Jeśli zawsze będziemy się trzymali takiego wzorca, żadne dodatkowe opakowania dla I2CDevice nie będą potrzebne. Poniżej przykład użycia:

using (I2CDevice device = new I2CDevice(null))
{
    Pcf8574 provider = new Pcf8574(device);

    Lcd lcd = new Lcd(provider);
    lcd.Begin(16, 2);

    lcd.Write("ABCDEFGHIJKLMNOP");
    lcd.SetCursorPosition(0, 1);
    lcd.Write("abcdefghijklmnop");
}