LinkedIn YouTube Facebook
Szukaj

Wstecz
Artykuły

[MBED] STM32 i SSD1306 – sterowanie dwukolorowym OLED [2]

 

promo

Przedstawiamy drugą część kursu obsługi wyświetlacza OLED za pomocą STM32. Pierwsza część artykułu jest dostępna pod adresem.

Wyświetlacz graficzny doskonale się nadaje do wyświetlania tekstu. W klasycznych wyświetlaczach alfanumerycznych czcionki mają jednakową wielkość zależną od wielkości wyświetlacza i mogą być wyświetlane w ustalonych wierszach. W wyświetlaczu graficznym można definiować czcionki o różnych rozmiarach zależnie od potrzeb i umieszczać je w dowolnym miejscu wyświetlacza – o ile się tam zmieszczą.

Przy adresowaniu Page Addressing Mode jest bardzo łatwo zdefiniować znaki o wysokości 8 pikseli, lub wielokrotności tej wartości: 16, 24, 32 itd. Najprościej definiować znaki np. 8×6 pikseli umieszczając w pamięci generatora znaków 6 kolejnych bajtów – łatwo to sobie wyobrazić patrząc na rysunek 7. W programowych bibliotekach obsługujących wyświetlacze graficzne stosowałem z powodzeniem tę metodę. Mam zdefiniowaną tablice generatora znaków i proste procedury wyświetlania znaku na podstawie jego kodu ASCII. Niestety przy tym wyświetlaczu znaki o wysokości 8 pikseli są praktycznie nieczytelne. Wynika to po prostu z małych wymiarów (przekątnej) matrycy. W naszym przypadku wartością graniczną jest wysokość 12 pikseli, a najlepiej gdyby znaki miały 16 i więcej pikseli.

Do rysowania znaków o dowolnej wielkości najlepiej się nadaje funkcja, która potrafi zapalić lub zgasić jeden piksel o dowolnych współrzędnych x, y niezależnych od trybu adresowania. Taka funkcja została pokazana na listingu 7.

List. 7. Rysowanie punktu o ustalonych współrzędnych

//**********************************************************
//"rysowanie” punktu w buforze RAM mikrokontrolera Hosta
//**********************************************************
void DrawPoint(unsigned char x,unsigned char y, unsigned char p)
{
  uint8_t chPos, chBx, chTemp = 0;
 
  if (x > 127 || y > 63) {
    return;
  }
  chPos = 7 - y / 8;
  chBx = y % 8;
  chTemp = 1 << (7 - chBx);
 
  if (p) {
    DispBuff[x][chPos] |= chTemp;
   
  } else {
    DispBuff[x][chPos] &= ~chTemp;
  }
}

Jak łatwo zauważyć funkcja void DrawPoint() z listingu 7 tylko modyfikuje zawartość dwuwymiarowego bufora DispBuff umieszczonego w pamięci RAM mikrokontrolera Hosta i nie zapisuje modyfikacji do sterownika SSD1306. Jak zobaczymy dalej wszystkie procedury wyświetlania modyfikują tylko ten bufor i aby zobaczyć efekt tych modyfikacji trzeba przepisać całą zawartość DispBuff do pamięci sterownika wywołując funkcję void RefreshRAM(void) pokazaną na listingu 5.

Mając do dyspozycji procedurę potrafiącą zapalać/gasić piksel o konkretnej współrzędnej możemy rysować proste, figury, okręgi, ale też rysować znaki alfanumeryczne o dowolnych wielkościach. Żeby to robić potrzebne są wzorce znaków umieszczone w tablicy zwanej tez generatorem znaków. Ręczne tworzenie wzorców znaków jest możliwe, ale to żmudne zajęcie. Istnieje szereg programów tworzących wzorce o zadanej wielkości znaku. W Internecie można też znaleźć gotowe tablice z wzorcami. Ja skorzystałem z gotowych tablic ze zdefiniowanymi znakami o wielkości 12x6pikseli i 16x8pikseli. Tablice są tak zbudowane, ze kod ASCII znaku adresuje grupę bajtów definiujących ten znak. To znacznie upraszcza procedury wyświetlające łańcuchy znaków (napisy). Na przykład tablica dla znaków o wysokości 12 i szerokości 6 pikseli jest dwuwymiarową tablicą const char c_chFont1206[95][12] i zawiera 95 wzorców znaków. Każdy element wzorca znaków ma 12bajtów i definiuje znak o szerokości 6 pikseli, ale wykorzystuje przestrzeń 12×8 pikseli – listing 8.

List. 8. Fragment tablicy generatora znaków 12×6 pikseli

const char c_chFont1206[95][12] = {
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",0*/
{0x00,0x00,0x00,0x00,0x3F,0x40,0x00,0x00,0x00,0x00,0x00,0x00},/*"!",1*/
{0x00,0x00,0x30,0x00,0x40,0x00,0x30,0x00,0x40,0x00,0x00,0x00},/*""",2*/
{0x09,0x00,0x0B,0xC0,0x3D,0x00,0x0B,0xC0,0x3D,0x00,0x09,0x00},/*"#",3*/
{0x18,0xC0,0x24,0x40,0x7F,0xE0,0x22,0x40,0x31,0x80,0x00,0x00},/*"$",4*/
{0x18,0x00,0x24,0xC0,0x1B,0x00,0x0D,0x80,0x32,0x40,0x01,0x80},/*"%",5*/
{0x03,0x80,0x1C,0x40,0x27,0x40,0x1C,0x80,0x07,0x40,0x00,0x40},/*"&",6*/
{0x10,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"'",7*/
{0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x80,0x20,0x40,0x40,0x20},/*"(",8*/
{0x00,0x00,0x40,0x20,0x20,0x40,0x1F,0x80,0x00,0x00,0x00,0x00},/*")",9*/
{0x09,0x00,0x06,0x00,0x1F,0x80,0x06,0x00,0x09,0x00,0x00,0x00},/*"*",10*/
………
}

Dla znaków 16×8 pikseli jest zdefiniowana druga tablica const uint8_t c_chFont1608[95][16].

Mając tablice generatora znaków i procedurę DrawPoint() potrafiącą „zapalać i gasić” bit w buforze pamięci mikrokontrolera odpowiadający zawartości pamięci RAM sterownika, a tym samym odpowiadający pikselowi na matrycy OLED można napisać procedurę „rysującą” znak w pamięci RAM mikrokontrolera. Pamiętamy, że aby ten znak się wyświetlił trzeba przepisać zawartość DispBuff do pamięci RAM wywołując funkcję void RefreshRAM().

Na listingu 9 pokazano procedurę void DisplayChar() z argumentami:

  • x i y – współrzędne początku znaku na ekranie,
  • Chr – kod ASCII wyświetlanego znaku,
  • size – wielkość znaku 12, lub 16 pikseli,
  • mode= 1 wyświetlanie normalne, mode=0 wyświetlanie w negatywie.

Zależnie od wartości argumentu size bajty wzorca są pobierane z tablicy c_chFont1206[95][12] lub z tablicy c_chFont1608[95][16]. Kiedy argument mode jest wyzerowany to dodatkowo wartość pobranego bajtu jest negowana. Potem jest analizowany każdy bit bajtu wzorca i zależnie od jego wartości DrawPoint() zapisuje do bufora DispBuff odpowiednią wartość.

List. 9. Wyświetlenie jednego znaku

//******************************************************************************
//wyświetlenie jednego znaku
//argumenty:
//x,y - współrzędne na ekranie
//Chr - kod ASCII znaku
//size - rozmiar 12, lub 16
//mode=1 znak wyświetlany normalnie, mode=0 znak wyświetlany w negatywie
//*******************************************************************************
void DisplayChar(uint8_t x, uint8_t y, uint8_t Chr, uint8_t size, uint8_t mode)
{    
  uint8_t i, j;
  uint8_t chTemp, chYpos0 = y;
 
  Chr =Chr - ' ';          
  for (i = 0; i < size; i ++) {  
    if (size == 12) {
      if (mode) {
        chTemp = c_chFont1206[Chr][i];
      } else {
        chTemp = ~c_chFont1206[Chr][i];
      }
    } else {
      if (mode) {
        chTemp = c_chFont1608[Chr][i];
      } else {
        chTemp = ~c_chFont1608[Chr][i];
      }
    }
   
    for (j = 0; j < 8; j ++) {
      if (chTemp & 0x80) {
        DrawPoint(x, y, 1);
      } else {
        DrawPoint(x, y, 0);
      }
      chTemp <<= 1;
      y ++;
     
      if ((y - chYpos0) == size) {
        y = chYpos0;
        x ++;
        break;
      }
    } 
  }

}

Mając procedurę wyświetlania jednego znaku o dowolnych współrzędnych można napisać procedurę wyświetlającą łańcuch znaków od określonej pozycji.

Na listingu 10 pokazano procedurę void DispTxt() z argumentami:

  • x i y – współrzędne początku znaku na ekranie,
  • *txt – wskaźnik na początek bufora z łańcuchem znaków,
  • size – wielkość znaku 12 lub 16 pikseli,
  • mode= 1 wyświetlanie normalne, mode=0 wyświetlanie w negatywie.

List. 10. Procedura wyświetlania ciągów znaków

//*********************************************************************************
//wyświetlenie łańcucha znaków - napisu
//argumenty
//x,y - współrzędne na ekranie
//*txt - wskaźnik na początek bufora zwierającego łańcuch znaków ASCII do wyświetlenia
//size - wysokość znaków 12, lub 16 pikseli
//mode=1 znaki wyświetlane normalnie, mode=0 znaki wyświetlane w negatywie
//*********************************************************************************
void DispTxt(uint8_t x, uint8_t y, const uint8_t *txt, uint8_t size, uint8_t mode)
{
  while (*txt != '\0') {    
    if (x > (SSD1306_WIDTH - size / 2)) {
      x = 0;
      y += size;
      if (y > (SSD1306_HEIGHT - size)) {
        y = x = 0;
        DisplayCls(0x00);
      }
    }
   
    DisplayChar(x, y, *txt, size, mode);
    x += size / 2;
    txt ++;
  }
}
Absolwent Wydziału Elektroniki Politechniki Wrocławskiej, współpracownik miesięcznika Elektronika Praktyczna, autor książek o mikrokontrolerach Microchip i wyświetlaczach graficznych, wydanych nakładem Wydawnictwa BTC. Zawodowo zajmuje się projektowaniem zaawansowanych systemów mikroprocesorowych.