7.06.2014

Sterownik HAL dla LCD LM15SGFNZ07

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