wtorek, 26 września 2017

[1] STM32L0 - Wyswietlacz E papier

W tym poście chciałbym opisać sposób obsługi wyświetlacza zamontowanego na płytce STM32L0 - Discovery. Ta rodzina mikrokontrolerów jak i pozostałe L-ki nie posiada wsparcia dla bibliotek standardowych, wobec czego zastosuje biblioteki HAL-a.
[Źródło: http://www.st.com/en/evaluation-tools/32l0538discovery.html]

Opis Układu


Jedną z największych zalet wyświetlacza tego typu jest utrzymywanie wprowadzonego napisy czy grafiki nawet po zaniku zasilania. Cały układ jest dodatkowo połączony z mikrokontrolerem STM32L0. Dzięki temu można uzyskać bardzo niskie pobory energi. Zwłaszcza jeśli chodzi o pracę na baterii.

Wyświetlacz GDE021A1 zamontowany w zestawie ma przekątną wynoszącą 2,04 cala przy rozdzielczości 172 x 72 piksele.

Aby wgrać nową grafikę należy ten ekran wyczyścić i dopiero przystąpić do wprowadzania nowego obrazka. Wgrywanie odbywa się poprzez ustawianie odpowiednich pikseli na wyświetlaczu.

Dokumentacja tego wyświetlacza może zostać pobrać pod tym linkiem.

Programowanie


Do programowania wykorzystam darmowe środowisko System Workbench Ac6. Projekt został wygenerowany w oparciu o środowisko CubeMx. Tutaj z pomocą przychodzą biblioteki BSP, które można zastosować do obsługi wyświetlacza. Są one dostępne w plikach pobieranych dla Cuba do wygenerowania projektu.

Wszystkie funkcje do obsługi wyświetlacza zostało zawartych w trzech plikach. Są nimi stm32l0538_discovery. Zawiera on procedurę inicjalizacji pinów i części sprzętowej. Plik stm32l0538_discovery_epd zawiera funkcje obsługujące czyszczenie ekranu, wypisywanie tekstu itp. Ostatnim plikiem jest gde021a1. Zawiera on główną część sprzętową czyli ustawianie rejestrów w wyświetlaczu itp.

Większość funkcji zwraca status po wywołaniu funkcji obsługującej dany układ. Do ich definicji służy następujący typ wyliczeniowy:

  1. typedef enum{
  2.   EPD_OK = 0,
  3.   EPD_ERROR = 1,
  4.   EPD_TIMEOUT = 2
  5. } EPD_StatusTypeDef;

Określa on czy dana operacja przebiegła poprawnie bądź czy wystąpił jakiś błąd.

Wyświetlacz umożliwia wyświetlenie kolorów z zakresu od 0x00 do 0xFF. Gdzie 0x00 jest czarny natomiast 0xFF oznacza kolor biały. Wszystkie kolory pomiędzy są odcieniami koloru szarego.

  1. #define EPD_COLOR_BLACK         0x00
  2. #define EPD_COLOR_DARKGRAY      0x55
  3. #define EPD_COLOR_LIGHTGRAY     0xAA
  4. #define EPD_COLOR_WHITE         0xFF

Inicjalizacja układu odbywa się poprzez wywołanie następującej funkcji:

  1. uint8_t BSP_EPD_Init(void)
  2. {
  3.   uint8_t status = EPD_ERROR;                       /* Operation status */
  4.   pFont = &Font16;                                  /* Set default font */
  5.   epd_drv = &gde021a1_drv;                          /* write module data into structure */
  6.   epd_drv->Init();                                  /* Init function */
  7.   BSP_EPD_Clear(EPD_COLOR_WHITE);                   /* Clear the EPD screen */
  8.   BSP_EPD_SetFont(&EPD_DEFAULT_FONT);               /* Initialize the font */
  9.   status = EPD_OK;
  10.   return status;
  11. }

Funkcja wykorzystuje dodatkowe procedury inicjalizacji odpowiedzialne za uruchomienie GPIO, SPI oraz jedną funkcję ustawiającą parametry wyświetlacza:

  1. /* configurate GPIO and SPI */
  2. void EPD_IO_Init(void)
  3. {
  4.   GPIO_InitTypeDef  GPIO_InitStruct;
  5.   /* EPD_CS_GPIO and EPD_DC_GPIO Periph clock enable */
  6.   EPD_CS_GPIO_CLK_ENABLE();
  7.   EPD_DC_GPIO_CLK_ENABLE();
  8.   EPD_RESET_GPIO_CLK_ENABLE();
  9.   EPD_BUSY_GPIO_CLK_ENABLE();
  10.   EPD_PWR_GPIO_CLK_ENABLE();
  11.   /* Configure EPD_CS_PIN pin: EPD Card CS pin */
  12.   GPIO_InitStruct.Pin = EPD_CS_PIN;
  13.   GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  14.   GPIO_InitStruct.Pull = GPIO_NOPULL;
  15.   GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
  16.   HAL_GPIO_Init(EPD_CS_GPIO_PORT, &GPIO_InitStruct);
  17.   /* Configure EPD_DC_PIN pin: EPD Card DC pin */
  18.   GPIO_InitStruct.Pin = EPD_DC_PIN;
  19.   HAL_GPIO_Init(EPD_DC_GPIO_PORT, &GPIO_InitStruct);
  20.   /* Configure EPD_RESET_PIN pin */
  21.   GPIO_InitStruct.Pin = EPD_RESET_PIN;
  22.   HAL_GPIO_Init(EPD_PWR_GPIO_PORT, &GPIO_InitStruct);
  23.   /* Configure EPD_RESET_PIN pin */
  24.   GPIO_InitStruct.Pin = EPD_PWR_PIN;
  25.   HAL_GPIO_Init(EPD_RESET_GPIO_PORT, &GPIO_InitStruct);
  26.   /* Configure EPD_BUSY_PIN pin */
  27.   GPIO_InitStruct.Pin = EPD_BUSY_PIN;
  28.   GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  29.   GPIO_InitStruct.Pull = GPIO_PULLDOWN;
  30.   HAL_GPIO_Init(EPD_BUSY_GPIO_PORT, &GPIO_InitStruct);
  31.   /* Enbale Display */
  32.   EPD_PWR_LOW();
  33.   /* Set or Reset the control line */
  34.   EPD_CS_LOW();
  35.   EPD_CS_HIGH();
  36.   /* EPD reset pin mamagement */
  37.   EPD_RESET_HIGH();
  38.   EPD_Delay(10);
  39.   /* SPI Configuration */
  40.   SPIx_Init();
  41. }
  42. /* Display configuration */
  43. void gde021a1_Init(void)
  44. {
  45.   uint8_t nb_bytes = 0;
  46.   /* Initialization of the GDE021A11 */
  47.   EPD_IO_Init();
  48.    
  49.   EPD_IO_WriteReg(EPD_REG_16);  /* Deep sleep mode disable */
  50.   EPD_IO_WriteData(0x00);
  51.   EPD_IO_WriteReg(EPD_REG_17);  /* Data Entry Mode Setting */
  52.   EPD_IO_WriteData(0x03);
  53.   EPD_IO_WriteReg(EPD_REG_68);  /* Set the RAM X start/end address */
  54.   EPD_IO_WriteData(0x00);       /* RAM X address start = 00h */
  55.   EPD_IO_WriteData(0x11);       /* RAM X adress end = 11h (17 * 4pixels by address = 72 pixels) */
  56.   EPD_IO_WriteReg(EPD_REG_69);  /* Set the RAM Y start/end address */
  57.   EPD_IO_WriteData(0x00);       /* RAM Y address start = 0 */
  58.   EPD_IO_WriteData(0xAB);       /* RAM Y adress end = 171 */
  59.   EPD_IO_WriteReg(EPD_REG_78);  /* Set RAM X Address counter */
  60.   EPD_IO_WriteData(0x00);
  61.   EPD_IO_WriteReg(EPD_REG_79);  /* Set RAM Y Address counter */
  62.   EPD_IO_WriteData(0x00);
  63.   EPD_IO_WriteReg(EPD_REG_240); /* Booster Set Internal Feedback Selection */
  64.   EPD_IO_WriteData(0x1F);
  65.   EPD_IO_WriteReg(EPD_REG_33);  /* Disable RAM bypass and set GS transition to GSA = GS0 and GSB = GS3 */
  66.   EPD_IO_WriteData(0x03);
  67.   EPD_IO_WriteReg(EPD_REG_44);  /* Write VCOMregister */
  68.   EPD_IO_WriteData(0xA0);
  69.   EPD_IO_WriteReg(EPD_REG_60);  /* Border waveform */
  70.   EPD_IO_WriteData(0x64);
  71.   EPD_IO_WriteReg(EPD_REG_50);  /* Write LUT register */
  72.   for (nb_bytes=0; nb_bytes<90; nb_bytes++)
  73.   {
  74.     EPD_IO_WriteData(WF_LUT[nb_bytes]);
  75.   }
  76. }

Czyszczenie wyświetlacza odbywa się poprzez wgranie na ekran koloru białego. W przypadku wyczyszczenia wyświetlacza kolorem biały istnieje duże prawdopodobieństwo, że będzie prześwitywać wcześniej wgrana grafika. W takim przypadku dobrze jest wyczyścić cały ekran najpierw kolorem czarnym po czym wgrać na niego kolor biały. Taki mechanizm działa znacząco poprawia czytelność lecz niestety wydłuża dwukrotnie czas odświeżania, który i tak jest dla e-papieru dosyć znaczący.

  1. void BSP_EPD_Clear(uint16_t Color)
  2. {
  3.   uint32_t index = 0;
  4.   epd_drv->SetDisplayWindow(0, 0, 171, 17);
  5.   for(index = 0; index < 3096; index++)
  6.   {
  7.       epd_drv->WritePixel(Color);
  8.   }
  9. }

Do wypisywania znaków bądź ciągów znaków używa się następujących funkcji:

  1. //---------------------------------------------------------------------------------------------------------
  2. void BSP_EPD_DisplayChar(uint16_t Xpos, uint16_t Ypos, uint8_t Ascii)
  3. {
  4.   Ascii -= 32;
  5.   DrawChar(Xpos, Ypos, &pFont->table[Ascii * ((pFont->Height) * (pFont->Width))]);
  6. }
  7. //---------------------------------------------------------------------------------------------------------
  8. void BSP_EPD_DisplayStringAt(uint16_t Xpos, uint16_t Ypos, uint8_t *Text, Text_AlignModeTypdef Mode)
  9. {
  10.   uint16_t refcolumn = 1, i = 0;
  11.   uint32_t size = 0, xsize = 0;
  12.   uint8_t  *ptr = Text;
  13.   /* Get the text size */
  14.   while (*ptr++) size ++ ;
  15.   /* Characters number per line */
  16.   xsize = (BSP_EPD_GetXSize()/pFont->Width);
  17.   switch (Mode)
  18.   {
  19.   case CENTER_MODE:
  20.     {
  21.       refcolumn = Xpos + ((xsize - size)* pFont->Width) / 2;
  22.       break;
  23.     }
  24.   case LEFT_MODE:
  25.     {
  26.       refcolumn = Xpos;
  27.       break;
  28.     }
  29.   case RIGHT_MODE:
  30.     {
  31.       refcolumn =  - Xpos + ((xsize - size)*pFont->Width);
  32.       break;
  33.     }    
  34.   default:
  35.     {
  36.       refcolumn = Xpos;
  37.       break;
  38.     }
  39.   }
  40.   /* Send the string character by character on EPD */
  41.   while ((*Text != 0) & (((BSP_EPD_GetXSize() - (i*pFont->Width)) & 0xFFFF) >= pFont->Width))
  42.   {
  43.     /* Display one character on EPD */
  44.     BSP_EPD_DisplayChar(refcolumn, Ypos, *Text);
  45.     /* Decrement the column position by 16 */
  46.     refcolumn += pFont->Width;
  47.     /* Point on the next character */
  48.     Text++;
  49.     i++;
  50.   }
  51. }
  52. //---------------------------------------------------------------------------------------------------------
  53. void BSP_EPD_DisplayStringAtLine(uint16_t Line, uint8_t *ptr)
  54. {
  55.   BSP_EPD_DisplayStringAt(0, LINE(Line), ptr, LEFT_MODE);
  56. }

Pierwsza funkcja od góry pozwala na wyświetlenie podanego znaku ascii w odpowiedniej pozycji. Druga daje możliwość wyświetlenia ciągu znaków w zadeklarowanej pozycji z podanym ustawieniem (wyrównaniem do lewej prawej bądź do środka). Ostatnia pozycja wyświetla informacje w podanej linii która jest liczona na podstawie wielkości czcionki.

Kolejna funkcja potrzebna do wyświetlacza jest jego odświeżania. Musi ona być wywołana za każdym razem gdy wprowadzane są zmiany na wyświetlaczu.

  1. void BSP_EPD_RefreshDisplay(void)
  2. {
  3.   epd_drv->RefreshDisplay();/* Refresh display sequence */
  4.   /* Poll on the BUSY signal and wait for the EPD to be ready */
  5.   while (HAL_GPIO_ReadPin(EPD_BUSY_GPIO_PORT, EPD_BUSY_PIN) != (uint16_t)RESET);
  6.   EPD_RESET_HIGH();         /*  EPD reset pin mamagement */
  7.   EPD_Delay(10);            /* Add a 10 ms Delay after EPD pin Reset */
  8. }

Następna funkcja pozwala na rysowanie pikseli na ekranie:

  1. void BSP_EPD_Pixel(uint16_t Xpos, uint16_t Ypos, uint8_t pixNumber)
  2. {
  3.         epd_drv->SetDisplayWindow(Xpos, Ypos, Xpos, Ypos);
  4.    
  5.         if(pixNumber == 1)      {   epd_drv->WritePixel(0xEF);  }
  6.         else if(pixNumber == 2) {   epd_drv->WritePixel(0x0F);  }
  7.         else if(pixNumber == 3) {   epd_drv->WritePixel(0x08);  }
  8.         else if(pixNumber == 4) {   epd_drv->WritePixel(0x00);  }
  9.         else
  10.         {
  11.                 epd_drv->WritePixel(pixNumber);
  12.         }
  13. }
  14. void gde021a1_SetDisplayWindow(uint16_t Xpos, uint16_t Ypos, uint16_t Width, uint16_t Height)
  15. {
  16.   /* Set Y position and the height */
  17.   EPD_IO_WriteReg(EPD_REG_68);
  18.   EPD_IO_WriteData(Ypos);
  19.   EPD_IO_WriteData(Height);
  20.    
  21.   /* Set X position and the width */
  22.   EPD_IO_WriteReg(EPD_REG_69);
  23.   EPD_IO_WriteData(Xpos);
  24.   EPD_IO_WriteData(Width);
  25.    
  26.   /* Set the height counter */
  27.   EPD_IO_WriteReg(EPD_REG_78);
  28.   EPD_IO_WriteData(Ypos);
  29.    
  30.   /* Set the width counter */
  31.   EPD_IO_WriteReg(EPD_REG_79);
  32.   EPD_IO_WriteData(Xpos);
  33. }
  34. void gde021a1_WritePixel(uint8_t HEX_Code)
  35. {
  36.   /* Prepare the register to write data on the RAM */
  37.   EPD_IO_WriteReg(EPD_REG_36);
  38.   /* Send the data to write */
  39.   EPD_IO_WriteData(HEX_Code);
  40. }

Odwołuje się ona do dwóch funkcji umieszczonych w bibliotece dla samego wyświetlacza.

Jako argument podaje się pozycje X oraz Y. X przyjmuje wartości z zakresu od 0 do 171, czyli tyle ile wynosi dłuższy bok wyświetlacza.  Drugi parametr przyjmuje wartości od 0 do 17. Pojedyncza wartość podana do tej zmiennej odpowiada czterem pikselom wprowadzonym do wyświetlacza. Ostatnia zmienna odnosi się do ilości pikseli jakie mają być wyświetlone na ekranie. Wprowadzenie wartości od 1 do 4 oznacza podaną ilość pikseli w kolejności. Jeśli chce się wyświetlać inną sekwencję to należy wprowadzić inną wartość do zmiennej.

Piksele grupowane są po dwie wartości w zmiennej 8 bitowej.

Ostatnia z funkcji daje możliwość wyrysowania obrazu na ekranie. Musi on być oczywiście w odpowiedniej rozdzielczości czyli 172x72. Do funkcji podaje się pozycje rozmiar oraz wskaźnik do tablicy z danymi:

  1. void BSP_EPD_DrawImage(uint16_t Xpos, uint16_t Ypos, uint16_t Xsize, uint16_t Ysize, uint8_t *pdata)
  2. {
  3.   /* Set display window */
  4.   epd_drv->SetDisplayWindow(Xpos, Ypos, (Xpos+Ysize-1), (Ypos+(Xsize/4)-1));
  5.   if(epd_drv->DrawImage != NULL)
  6.   {
  7.     epd_drv->DrawImage(Xpos, Ypos, Xsize, Ysize, pdata);
  8.   }
  9.   epd_drv->SetDisplayWindow(0, 0, BSP_EPD_GetXSize(), BSP_EPD_GetYSize());
  10. }

Obraz należy prze konwertować z bitmapy na hex a dokładnie na format xbm-x11. Służy do tego program np. XnView.

Co do samego wyświetlacza na pewno przy zastosowaniach energooszczędnych ma on dużo plusów, zwłaszcza jeśli chodzi o pobór prądu. Spokojnie da on sobie rade z wyświetlaniem informacji nie potrzebujących bardzo częstego odświeżania.

Bibliotekę do obsługi tego wyświetlacza można pobrać pod tym linkiem.