Poprzednio był opis jak wyświetlacz LM15SGFNZ07 podłączyć do STM32F4Discovery i obsługiwać z kodu zarządzanego w C#. Były tam procedury, których nie przerobiłem na rysowanie w buforze z jednego prostego powodu - to był tylko test czy wszystko będzie działać. Moim celem było napisanie drivera obsługującego ten wyświetlacz z poziomu CLR. Ale po kolei.
W Microsoft.SPOT.Graphics jest klasa Bitmap, która jest kontenerem do przechowywania informacji o obrazie. Obiekt zawiera informacje o poszczególnych pikselach obrazu oraz posiada metody do wykonywania operacji na nich (np. DrawLine, DrawText etc.). Metoda Flush pozwala wysłać taką bitmapę na ekran wyświetlacza. Jest tylko jeden problem - wyświetlacz musi mieć sterownik wkompilowany w CLR. W katalogu MicroframeworkPK\DeviceCode\Drivers\Display umieszczone są drivery do niektórych wyświetlaczy. Katalog stubs zawiera zaślepkę dla takiego sterownika. Celem jest uzupełnienie funkcji w pliku Display_stubs_functions.cpp, tak aby obsługiwać nasz wyświetlacz.
Nie jestem programistą C++ i nie znam się na programowaniu w tym języku więc mój kod może być (czytaj jest) mocno nieoptymalny, niezgodny z zasadami pisania w C++ i nie należy się na nim wzorować. Moim celem było zbudowanie działającego drivera jak najszybciej i jak najmniejszym kosztem. Implementację drivera podzieliłem na kilka etapów. Dzięki temu, że miałem poprawnie działający kod w C# mogłem sprawdzić poprawność działania kolejnych funkcji. Najpierw w driverze napisałem procedurę inicjalizacji. W kodzie C# sprawdziłem czy dam radę coś wyświetlić. Jeśli to się powiodło sukcesywnie uzupełniałem kolejne elementy. Najpierw jednak trzeba przygotować katalogi, pliki i TinyCLR.proj.
W katalogu Solutions\Discovery4\DeviceCode zakładamy podkatalog Display, a w nim kolejny podkatalog LM15SGFNZ07. To jest nasz katalog bazowy. Do niego kopiujemy z DeviceCode\Drivers\Display\stubs pliki Display_stubs_functions.cpp i dotNetMF.proj. Nazwę pliku cpp zmieniamy na LM15SGFNZ07_functions.cpp.
Modyfikujemy następujące elementy w dotNetMF.proj:
<AssemblyName>LM15SGFNZ07</AssemblyName> <ProjectGuid>{D34AB75E-29B0-4408-AE73-F8741FC6D19F}</ProjectGuid> <Description>LM15SGFNZ07 display driver</Description> <LibraryFile>LM15SGFNZ07.$(LIB_EXT)</LibraryFile> <ProjectPath>$(SPOCLIENT)\Solutions\Discovery4\DeviceCode\Display\LM15SGFNZ07\dotNetMF.proj</ProjectPath> <ManifestFile>LM15SGFNZ07.$(LIB_EXT).manifest</ManifestFile> <IsStub>False</IsStub> <Directory>Solutions\Discovery4\DeviceCode\Display\LM15SGFNZ07</Directory> <ItemGroup> <Compile Include="LM15SGFNZ07_functions.cpp" /> </ItemGroup>
Teraz trzeba zmodyfikować Solutions\Discovery4\TinyCLR\TinyCLR.proj, tak aby dodać biblioteki graficzne i nasz nowy sterownik.
Zamieniamy element
<Import Project="$(SPOCLIENT)\Framework\Features\Core.featureproj" />na
<Import Project="$(SPOCLIENT)\Framework\Features\TinyCore.featureproj" /> <Import Project="$(SPOCLIENT)\Framework\Features\Graphics.featureproj" />
Teraz musimy odnaleźć element
<ItemGroup> <PlatformIndependentLibs Include="Graphics_stub.$(LIB_EXT)" /> <RequiredProjects Include="$(SPOCLIENT)\CLR\Graphics\dotNetMF_stub.proj" /> </ItemGroup>
i zamienić na:
<ItemGroup> <PlatformIndependentLibs Include="Graphics.$(LIB_EXT)" /> <RequiredProjects Include="$(SPOCLIENT)\CLR\Graphics\dotNetMF.proj" /> </ItemGroup> <ItemGroup> <DriverLibs Include="graphics_pal.$(LIB_EXT)" /> <RequiredProjects Include="$(SPOCLIENT)\DeviceCode\PAL\Graphics\dotNetMF.proj" /> </ItemGroup> <ItemGroup> <PlatformIndependentLibs Include="SPOT_Graphics.$(LIB_EXT)" /> <RequiredProjects Include="$(SPOCLIENT)\CLR\Libraries\SPOT_Graphics\dotNetMF.proj" /> </ItemGroup> <ItemGroup> <PlatformIndependentLibs Include="Graphics_Bmp.$(LIB_EXT)" /> <RequiredProjects Include="$(SPOCLIENT)\CLR\Graphics\BMP\dotNetMF.proj" /> </ItemGroup> <ItemGroup> <PlatformIndependentLibs Include="Graphics_Gif.$(LIB_EXT)" /> <RequiredProjects Include="$(SPOCLIENT)\CLR\Graphics\GIF\dotNetMF.proj" /> </ItemGroup> <ItemGroup> <PlatformIndependentLibs Include="Graphics_Jpeg.$(LIB_EXT)" /> <RequiredProjects Include="$(SPOCLIENT)\CLR\Graphics\Jpeg\dotNetMF.proj" /> </ItemGroup> <ItemGroup> <DriverLibs Include="LM15SGFNZ07.$(LIB_EXT)" /> <RequiredProjects Include="$(SPOCLIENT)\Solutions\Discovery4\DeviceCode\Display\LM15SGFNZ07\dotNetMF.proj" /> </ItemGroup>
Jak można się domyślić właśnie dodaliśmy obsługę BMP, GIF i JPG. Pozwoli to umieszczać takie formaty w zasobach programu i ładować je do bitmap. Jeśli jakieś formaty są niepotrzebne to trzeba zamienić na zaślepki.
Powyższe operacje generowania gotowego katalogu sterownika oraz modyfikacji projektu TinyCLR można również wykonać z programu SolutionWizard. Ja jednak chciałem pokazać, że ręczna modyfikacja tych plików też jest możliwa.
Teraz trzeba tylko uzupełnić funkcje w LM15SGFNZ07_functions.cpp. Minimum co musimy dodać to funkcje: LCD_GetWidth, LCD_GetHeight i LCD_BitBltEx. Ponieważ LCD LM15SGFNZ07 musi być wcześniej zainicjowany musimy jeszcze uzupełnić procedurę LCD_Initialize.
Na początku pliku dodajemy definicję użytych pinów oraz konfigurację portu SPI:
#include "tinyhal.h" #define PE9 (4*16 + 9) #define PE10 (4*16 + 10) #define PE11 (4*16 + 11) #define CS_PIN PE11 #define RESET_PIN PE10 #define RS_PIN PE9 SPI_CONFIGURATION spiCfg = { CS_PIN, // Chip select FALSE, // Chip Select polarity TRUE, // MSK_IDLE TRUE, // MSK_SAMPLE_EDGE FALSE, // 16-bit mode 5000, // SPI Clock Rate KHz 0, // CS setup time us 0, // CS hold time us 1, // SPI Module (SPI2) { GPIO_PIN_NONE, // SPI BusyPin FALSE, // SPI BusyPinActiveState } };
Następnie funkcje pomocnicze:
void Lm15Sgfnz07_SendCommand(UINT8* data, INT32 count) { CPU_GPIO_SetPinState(RS_PIN, TRUE); CPU_SPI_nWrite8_nRead8(spiCfg, data, count, NULL, 0, 0); } void Lm15Sgfnz07_SendData(UINT8* data, INT32 count) { CPU_GPIO_SetPinState(RS_PIN, FALSE); CPU_SPI_nWrite8_nRead8(spiCfg, data, count, NULL, 0, 0); } void Lm15Sgfnz07_SendData16(UINT16* data, INT32 count) { CPU_GPIO_SetPinState(RS_PIN, FALSE); spiCfg.MD_16bits = TRUE; CPU_SPI_nWrite16_nRead16(spiCfg, data, count, NULL, 0, 0); spiCfg.MD_16bits = FALSE; } void Lm15Sgfnz07_Window(UINT8 x1, UINT8 y1, UINT8 x2, UINT8 y2) { x1 <<= 1; x1 += 6; x2 <<= 1; x2 += 7; UINT8 data[10]; data[0] = 0xf0; data[1] = 0x00 | (x1 & 0x0f); data[2] = 0x10 | (x1 >> 4); data[3] = 0x20 | (y1 & 0x0f); data[4] = 0x30 | (y1 >> 4); data[5] = 0xf5; data[6] = 0x00 | (x2 & 0x0f); data[7] = 0x10 | (x2 >> 4); data[8] = 0x20 | (y2 & 0x0f); data[9] = 0x30 | (y2 >> 4); Lm15Sgfnz07_SendCommand(data, 10); } void Lm15Sgfnz07_Contrast(UINT8 contrast) { UINT8 buf[3]; buf[0] = 0xf4; buf[1] = 0xb0 | (contrast >> 4); buf[2] = 0xa0 | (contrast & 0x0f); Lm15Sgfnz07_SendCommand(buf, 3); }
Uzupełniamy funkcje Width i Height:
INT32 LCD_GetWidth() { NATIVE_PROFILE_HAL_DRIVERS_DISPLAY(); return 101; } INT32 LCD_GetHeight() { NATIVE_PROFILE_HAL_DRIVERS_DISPLAY(); return 80; }
Teraz nadszedł czas na procedurę inicjalizacji. Jest to ten sam kot co w bibliotece C# przeniesiony do C++:
BOOL LCD_Initialize() { NATIVE_PROFILE_HAL_DRIVERS_DISPLAY(); CPU_GPIO_EnableOutputPin(CS_PIN, TRUE); CPU_GPIO_EnableOutputPin(RESET_PIN, TRUE); CPU_GPIO_EnableOutputPin(RS_PIN, FALSE); CPU_GPIO_SetPinState(RESET_PIN, FALSE); HAL_Time_Sleep_MicroSeconds_InterruptEnabled(10*1000);//10ms CPU_GPIO_SetPinState(RESET_PIN, TRUE); HAL_Time_Sleep_MicroSeconds_InterruptEnabled(10*1000); UINT8 initData1[139] = {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}; Lm15Sgfnz07_SendCommand(initData1, 139); HAL_Time_Sleep_MicroSeconds_InterruptEnabled(10*1000); UINT8 initData2[15] = {0xF0, 0x81, 0xF4, 0xB3, 0xA0, 0xF0, 0x06, 0x10, 0x20, 0x30, 0xF5, 0x0F, 0x1C, 0x2F, 0x34}; Lm15Sgfnz07_SendCommand(initData2, 15); HAL_Time_Sleep_MicroSeconds_InterruptEnabled(10*1000); Lm15Sgfnz07_Contrast(42); return TRUE; }
No i najważniejsza procedura rysująca. Tutaj potrzebne jest małe wyjaśnienie. Do procedury trafiają dane UINT32 data[]. Jest to tablica poszczególnych pikseli bitmapy w formacie RGB 5:6:5. Przy czym jedna komórka tablicy (data[n]) zawiera informacje o dwóch, kolejnych pikselach. W little endian (tak jak u nas) 16 młodszych bitów (maska 0x0000FFFF) zawiera informacje o pierwszym (z pary) pikselu, a starsze 16 bitów (0xFFFF0000) o drugim pikselu. Cała logika sprowadza się do wyłuskania kolejnych pikseli RGB 5:6:5 i skonwertowanie ich na RGB 4:4:4.
void LCD_BitBltEx( int x, int y, int width, int height, UINT32 data[] ) { NATIVE_PROFILE_HAL_DRIVERS_DISPLAY(); const int size = width * height; UINT16* buf = (UINT16*)private_malloc(size*2); int widthInWords = Graphics_GetWidthInWords(width); for(int py = 0; py < height; py++) { for(int px = 0; px < width; px++) { UINT32 shift = (px % 2) * 16; UINT32 mask = 0x0000FFFF << shift; UINT16 pixel = (data[py*widthInWords + px/2] & mask) >> shift; //from RGB 5:6:5 //to RGB 0:4:4:4 pixel = ((pixel >> 4) & 0x0F00) | ((pixel >> 3) & 0x00F0) | ((pixel >> 1) & 0x000F); buf[py*width + px] = pixel; } } Lm15Sgfnz07_Window(x, y, x + width - 1, y + height - 1); Lm15Sgfnz07_SendData16(buf, size); private_free(buf); }
No i tyle. Teraz wystarczy skompilować solucję i wgrać do STM32F4Discovery. W ten sposób uzyskujemy dostęp do funkcji graficznych .NET Micro Framework. Jako demo może posłużyć lekko zmodyfikowany przykład z katalogu MicroFrameworkPK\Product\Sample\Test\LCD
Kot dema razem z driverem (katalog Discovery4): DemoLM15SGFNZ07Driver
Brak komentarzy:
Prześlij komentarz