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:
- http://www.drzasiek.strefa.pl/m55.html
- http://nis-embedded.blogspot.com/2014/01/stm32-rtc-lcd.html
- http://www.elektor.fr/080320
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.
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:
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); } }
Pełny kot: DemoLM15SGFNZ07Managed.
Brak komentarzy:
Prześlij komentarz