1.06.2014

Wyświelacz LCD LM15SGFNZ07

Znalazłem jakiś stary telefon i wygrzebałem z niego wyświetlacz LCD. Zastanawiałem się czy da się to jakoś podłączyć do STM32F4Discovery i obsłużyć w .NET Micro Framework. Trochę poszukałem no i się da. Bardzo pomocne były informacje na tych stronach:
Wyświetlacz LM15SGFNZ07 zamontowany jest w następujących telefonach komórkowych: Siemens A60,  A65, C60, MC60, M55 i S55. Ważne jest, aby wyświetlacz miał zielone PCB. W tych telefonach był też montowany inny wyświetlacz - ze "złotym" PCB i ten opis takiego LCD nie dotyczy. Wyświetlacz nie powala rozdzielczością i kolorami. Może wyświetlić 4096 kolorów i ma rozmiar 101 x 80 pikseli. Komunikacja z wyświetlaczem odbywa się przez interfejs SPI. Ja użyłem SPI 2 (MSK=PB13, MISO=PB14, MOSI= PB15), ale nic nie stoi na przeszkodzie, aby użyć innego. Podłączenie do STM32F4Discovery poniżej.
LM15SGFNZ07
Piny na wyświetlaczu (od lewej do prawej) mają następujące znaczenie:
  • Pin 1 -> PE11: CS (chip select: Lo)
  • Pin 2 -> PE10: RESET (reset: Lo)
  • Pin 3 -> PE9  : RS (command: Hi, data: Lo)
  • Pin 4 -> PB13: SCLK (clock)
  • Pin 5 -> PB15: MOSI (data)
  • Pin 6              : VCC (+2.9V...+3.3V)
  • Pin 7              : GND
  • Pin 8              : LED1 backlight A
  • Pin 9              : LED1, LED2 K
  • Pin10             : LED2 backlight A
Programowa obsługa wyświetlacza sprowadza się do wysłania przez SPI odpowiednich instrukcji sterujących lub danych. Aby wyświetlić coś na ekranie musimy wysłać najpierw komendę informującą o oknie, które chcemy zapełnić, a następnie dane o poszczególnych pikselach. Jeden piksel może mieć 4096 kolorów. Poszczególne kolory składowe piksela (R, G, B) zakodowane są na 4 bitach. Wartość szesnastkowa koloru pojedynczego piksela ma następującą postać: 0x0RGB. Cała sztuka polega na tym, aby dane, które mają zapełnić okno, wysłać jak najszybciej przez interfejs SPI. W przeciwnym wypadku  wyświetlacz "będzie działał wolno". Najlepiej poszczególne piksele rysować najpierw w buforze, a następnie taki bufor wysłać za jednym razem. Na szczęście klasa SPI ma metodę do wysyłania wartości bufora typu ushort (0x0000..0xFFFF), która się świetnie tutaj nadaje: void Write(ushort[] writeBuffer).

Na początek konstruktor i dwie bardzo przydatne procedury:

public class Lm15Sgfnz07
{
    public const int Width = 101;
    public const int Height = 80;

    private readonly SPI _spi;
    private readonly OutputPort _reset;
    private readonly OutputPort _rs;

    public Lm15Sgfnz07(SPI.SPI_module spi, Cpu.Pin cs, Cpu.Pin reset, Cpu.Pin rs)
    {
        var spiCfg = new SPI.Configuration(cs, false, 0, 0, true, true, 5000, spi);
        _spi = new SPI(spiCfg);
        _reset = new OutputPort(reset, true);
        _rs = new OutputPort(rs, false);

        Initialize();
    }

    private void SendCommand(params byte[] values)
    {
        _rs.Write(true);
        _spi.Write(values);
    }

    private void SendData(params ushort[] values)
    {
        _rs.Write(false);
        _spi.Write(values);
    }
}
W konstruktorze została użyta procedura inicjalizacji wyświetlacza. Procedura resetuje wyświetlacz i wysyła (bliżej mi nieznane) sekwencje konfigurujące.

private void Initialize()
{
    _reset.Write(false);
    Thread.Sleep(10);
    _reset.Write(true);

    SendCommand(0xF4, 0x90, 0xB3, 0xA0, 0xD0,
                0xF0, 0xE2, 0xD4, 0x70, 0x66, 0xB2, 0xBA, 0xA1, 0xA3, 0xAB, 0x94, 0x95,
                0x95, 0x95, 0xF5, 0x90, 0xF1, 0x00, 0x10, 0x22, 0x30, 0x45, 0x50, 0x68,
                0x70, 0x8A, 0x90, 0xAC, 0xB0, 0xCE, 0xD0, 0xF2, 0x0F, 0x10, 0x20, 0x30,
                0x43, 0x50, 0x66, 0x70, 0x89, 0x90, 0xAB, 0xB0, 0xCD, 0xD0, 0xF3, 0x0E,
                0x10, 0x2F, 0x30, 0x40, 0x50, 0x64, 0x70, 0x87, 0x90, 0xAA, 0xB0, 0xCB,
                0xD0, 0xF4, 0x0D, 0x10, 0x2E, 0x30, 0x4F, 0x50, 0xF5, 0x91, 0xF1, 0x01,
                0x11, 0x22, 0x31, 0x43, 0x51, 0x64, 0x71, 0x86, 0x91, 0xA8, 0xB1, 0xCB,
                0xD1, 0xF2, 0x0F, 0x11, 0x21, 0x31, 0x42, 0x51, 0x63, 0x71, 0x85, 0x91,
                0xA6, 0xB1, 0xC8, 0xD1, 0xF3, 0x0B, 0x11, 0x2F, 0x31, 0x41, 0x51, 0x62,
                0x71, 0x83, 0x91, 0xA4, 0xB1, 0xC6, 0xD1, 0xF4, 0x08, 0x11, 0x2B, 0x31,
                0x4F, 0x51, 0x80, 0x94, 0xF5, 0xA2, 0xF4, 0x60, 0xF0, 0x40, 0x50, 0xC0,
                0xF4, 0x70);

    Thread.Sleep(10);

    SendCommand(0xF0, 0x81,
                0xF4, 0xB3, 0xA0,
                0xF0, 0x06, 0x10, 0x20, 0x30,
                0xF5, 0x0F, 0x1C, 0x2F, 0x34);
}
Teraz dwie bardzo ważne procedury: do regulacji kontrastu i do ustawienia okna dla danych. Doświadczalnie stwierdziłem, że najlepszą wartością dla kontrastu to 42.

public void Contrast(byte contrast)
{
    SendCommand(0xF4,
               (byte) (0xB0 | (contrast >> 4)),
               (byte) (0xA0 | (contrast & 0x0F)));
}

private void ViewPort(int x1 = 0, int y1 = 0, int x2 = Width-1, int y2 = Height-1)
{
    x1 <<= 1;
    x1 += 6;
    x2 <<= 1;
    x2 += 7;

    SendCommand(0xf0,
               (byte) (0x00 | (x1 & 0x0f)),
               (byte) (0x10 | (x1 >> 4)),
               (byte) (0x20 | (y1 & 0x0f)),
               (byte) (0x30 | (y1 >> 4)),
                0xf5,
               (byte) (0x00 | (x2 & 0x0f)),
               (byte) (0x10 | (x2 >> 4)),
               (byte) (0x20 | (y2 & 0x0f)),
               (byte) (0x30 | (y2 >> 4)));
}
Teraz kilka przykładów użycia wyżej wymienionych procedur w funkcjach rysujących. Procedura SetPixel jest nieefektywna i należy jej używać tylko w celach testowych.

public void SetPixel(ushort color, int x, int y)
{
    ViewPort(x, y, x, y);
    SendData(color);
}

public void DrawImage(ushort[] image, int x, int y, int width, int height)
{
    ViewPort(x, y, x + width - 1, y + height - 1);
    SendData(image);
}

public void FillRectangle(ushort color, int x, int y, int width, int height)
{
    var buffer = new ushort[width * height];
    for (int i = 0; i < buffer.Length; i++)
        buffer[i] = color;

    DrawImage(buffer, x, y, width, height);
}

public void Clear(ushort color)
{
    FillRectangle(color, 0, 0, Width, Height);
}

Biblioteka została uzupełniona jeszcze o następujące procedury rysujące, których nie ma sensu tu wklejać:
  • Line(ushort color, int x1, int x2, int y1, int y2) - nieefektywana, używa SetPixel
  • Rectangle(ushort color, int x, int y, int width, int height, int thickness) - tak jak wyżej
  • Text(string text, int x, int y, ushort foreColor, ushort backColor, ILm15Sgfnz07Font font)
  • int MeasureTextWidth(string text, ILm15Sgfnz07Font font)
  • static ushort[] PpmToImage(byte[] ppmContent, out int width, out int height, out int depth)
Niektóre procedury używają nieefektywnego rysowania pojedynczych pikseli. Dlaczego? Bo nie chciało mi się tego optymalizować. Ale o tym w następnych wpisach. Statyczna procedura PpmToImage konwertuje dane obrazu PPM na format procedury DrawImage. Obraz PPM można wcześniej przygotować np. w programie Paint.Net (potrzebny jest plugin do zapisu plików PPM) i umieścić w zasobach aplikacji. Do rysowania napisów potrzebne są definicje fontów. Jeden (domyślny) załączyłem w przykładzie. Inne fonty można wygenerować programem MikroElektronika GLCD Font Creator.

No dobra. Poniżej przykład jak tego wszystkiego użyć:
public class Program
{
    public static void Main()
    {
        const Cpu.Pin csPin = Stm32F4Discovery.FreePins.PE11;
        const Cpu.Pin resetPin = Stm32F4Discovery.FreePins.PE10;
        const Cpu.Pin rsPin = Stm32F4Discovery.FreePins.PE9;

        const ushort black = 0x0000;
        const ushort white = 0x0FFF;
        const ushort red = 0x0F00;
        const ushort blue = 0x000F;
        const ushort green = 0x00F0;
        const ushort yellow = 0x0FF0;

        var lcd = new Lm15Sgfnz07(SPI.SPI_module.SPI2, csPin, resetPin, rsPin);
        lcd.Contrast(43);
        lcd.Clear(white);

        lcd.FillRectangle(red, 10, 10, Lm15Sgfnz07.Width - (2*10), 20);
        lcd.Text(".NET MF STM32F4", 6, 35, black, white);

        lcd.Rectangle(blue, 5, 5, Lm15Sgfnz07.Width - (2*5), Lm15Sgfnz07.Height - (2*5));

        lcd.Line(0x888, 0, Lm15Sgfnz07.Width, 0, Lm15Sgfnz07.Height);
        lcd.Line(0x888, Lm15Sgfnz07.Width, 0, 0, Lm15Sgfnz07.Height);

        byte[] res = Resources.GetBytes(Resources.BinaryResources.smile);

        int width, height, depth;
        ushort[] img = Lm15Sgfnz07.PpmToImage(res, out width, out height, out depth);
        lcd.DrawImage(img, (Lm15Sgfnz07.Width - width)/2, 50, width, height);

        Thread.Sleep(Timeout.Infinite);
    }
}
LM15SGFNZ07 STM32F4Discovery


Pełny kot: DemoLM15SGFNZ07Managed.

Brak komentarzy:

Prześlij komentarz