Visa text och grafik på displayer

Visa text och grafik på displayer

I tidigare byggen har vi använt utvecklingsmiljöns Serial monitor flitigt. Den är utmärkt för felsökning, men är inte lämplig att använda i färdiga projekt. I det här kapitlet ska vi därför se närmare på displayer samt hur vi kan visa text och bilder på dem. 

Alfanumerisk LCD-display

En displaytyp som används ofta i Arduino-sammanhang är alfanumeriska LCD-­displayer. Det är LCD-skärmar som är gjorda för att skriva specifika tecken (bokstäver, siffror och specialtecken). Tack vare biblioteket LiquidCrystal går det snabbt att åstadkomma resultat. 

Alfanumeriska LCD-displayer med två respektive fyra teckenrader.

Vi har valt att inte använda alfanumeriska displayer i något av bokens projekt. Sådana skärmar är visserligen lätta att använda, men de ser lite omoderna ut och kan inte hantera grafik. För den som vill veta mer om alfanumeriska LCD-displayer rekommenderar vi Arduinos officiella referenssidor som behandlar LiquidCrystal. 

OLED-display

Numera kostar inte OLED-displayer en halv förmögenhet (vilket de gjorde förr i tiden). De är betydligt bättre än alfanumeriska LCD-displayer då pixlarna lyser själva. OLED-displayer kräver därför ingen bakgrundsbelysning och de ger en djup svärta. 

I bokens tillhörande komponentkit finns en monokrom OLED-display med SSD1306-kontrollern. Den har en upplösning på 128 x 64 som kan användas för att visa både bilder och text. Det går till och med att välja vilket typsnitt som texten ska skrivas med.

OLED-display med SSD1306-kontroller

Anslutningar för OLED-displayer

Det finns två populära sätt att ansluta OLED-displayer till utvecklingskort: I²C (Inter-Integrated Circuit) och SPI (Serial Peripheral Interface). Vi kommer att använda I²C i denna bok, då I²C-protokollet har många fördelar över SPI. Till skillnad från SPI säkerställer I²C att datan kommer fram. I²C behöver dessutom enbart två ledare för att skicka data (medan SPI behöver fyra). Anslutningarna för I²C heter SCL (Serial Clock Line) och SDA (Serial Data Line).

Arduino Uno har två dedikerade stift för att ansluta I²C-enheter (SCL- och SDA-stiften). De är inte utmärkta på alla versioner av utvecklingskortet, men de sitter längst upp i stiftraden med GPIO-stiften. Det går även att använda A4 och A5 som I²C-kontakter. 

I detta kapitels exempel vill vi ansluta vår SSD1306-baserade OLED-display till utvecklingskortet. Det gör vi med följande koppling. 

Illustration gjord med komponenter från Fritzing (fritzing.org). CC BY-SA 3.0

Adressera pixlar i displayen

Displayen i vårt exempel består av 128 pixlar på bredden och 64 pixlar på höjden. För att tända önskade pixlar måste vi först och främst kunna adressera dem. Pixeln längst upp till vänster kallas (0, 0) och pixeln längst ned till höger kallas (127, 63). 

Här följer en översikt över var olika pixlar befinner i sig i koordinatsystemet. 

Tänd pixlar

I följande exempel vill vi tända två specifika pixlar och dra tre streck. Vi inleder därför vår sketch med att importera biblioteket U8glib. Till skillnad från Servo-biblioteket är inte U8glib förinstallerat i utvecklingsmiljön (se vid behov installationsanvisningar i Bibliotek). Vi skapar sedan en ny SSD1306-display vid namn oled. Vi anger att den har dimensionerna 128 x 64 och att den är ansluten via I²C. 

Del 1/3 av DrawPixels.ino

Vi skapar en egen funktion vid namn draw. I den skriver vi vilka pixlar vi vill tända med hjälp av funktionen drawPixels samt X- och Y-koordinaterna som argument. Vi vill även rita linjer, vilket vi gör med funktionen drawLine och ett par koordinater. Funktionens argument skrivs på följande vis.

drawLine(start-X, start-Y, slut-X, slut-y);

I sketchen använder vi funktionen på detta vis. 

Del 2/3 av DrawPixels.ino

Avslutningsvis anropar vi draw-funktionen från en do-loop i loop-funktionen.

Del 3/3 av DrawPixels.ino

Skriv text

Eftersom vi kan adressera varenda liten pixel på skärmen är det inga problem att skriva text i varierande storlekar och typsnitt. Vi kan i stor utsträckning utgå från koden som vi redan har skrivit, men vi måste definiera hur våra bokstäver ska se ut. 

Om vi vill att texten ska skrivas i Times storlek 12 för vi in följande kod i sketchen. 

oled.setFont(u8g_font_timB12);

Vi vill att bokstäverna ska skrivas med Helvetica i storlek 10 och skriver därför följande kod.

oled.setFont(u8g_font_helvB10);

Siffran som står efter bokstaven B anger teckenstorleken. En översikt över tillgängliga typsnitt finns här.

Vi vill skriva två rader längst upp till vänster på skärmen. U8glib bygger bokstäverna nerifrån och upp utifrån en koordinat,  så vi ska inte ange (0, 0) som startkoordinat. Eftersom tecknen är tio pixlar höga ska vi istället ange (0, 10). 

Nästa rad med text vill vi att utgår från koordinaten (0, 23). Då blir det lite mellanrum mellan de två raderna och bokstäver som går under baslinjen (se lilla y) hamnar inte ovanpå bokstäverna på den undre raden. 

DrawText.ino

Formatera text

Ibland är det snyggast om texten är centrerad i stället för vänsterjusterad. Tyvärr finns det inget automatiskt sätt att centrera den, men det går att räkna ut var texten ska börja skrivas ut för att den ska hamna i mitten. 

I följande exempel vill vi skriva ett ordspråk i mitten av displayen. Vi börjar med att spara ordspråkets två textrader i varsin global char-array.

// Store text in char arrays
char line1[] = "Do to do,";
char line2[] = "not to try!";

I draw-funktionen börjar vi med att välja typsnitt och storlek. Sedan räknar vi ut hur många pixlar hög en rad är med hänsyn tagen till att vissa bokstäver går under baslinjen.

 // Set font to Helvetica size 10
  oled.setFont(u8g_font_helvB10);
  
  // Calculate line height
  int lineHeight = oled.getFontAscent() - oled.getFontDescent();  

Centreringen av raderna löser vi genom att subtrahera den aktuella stränglängden från den maximala radlängden och dividera differensen med två. 

 // Calculate starting position
  int line1x = (oled.getWidth() - oled.getStrWidth(line1)) / 2;
  int line2x = (oled.getWidth() - oled.getStrWidth(line2)) / 2;

Slutligen skriver vi ut första raden (line1) på koordinaten som sätter den centrerat två linjehöjder ned. Vi gör samma sak med andra raden (line2).

 // Print text lines
  oled.drawStr(line1x, lineHeight * 2, line1);
  oled.drawStr(line2x, lineHeight * 3, line2);

Den färdiga sketchen ser ut på följande vis.

DrawCenterText.ino

Animera

Grafen som vi har ritat hade varit betydligt roligare om den rörde sig. Med några få kodrader kan vi få den att ritas upp successivt. 

Det är viktigt att tänka på att hela displayen ska uppdateras varje gång något ändras. Vi kan alltså inte fylla i grafen successivt, utan vi måste skapa en serie av bildrutor där grafen har blivit lite längre för varje bildruta. För att hålla koll på hur lång grafen är för tillfället skapar vi en global variabel vid namn n

// Declare and initialise variable for length of line
int n = 0;

I loop-funktionen skapar vi en while-loop som räknar upp n (förlänger grafen) så länge n är mindre än 30. Om n är över 30 pausar koden i fem sekunder och ändrar sedan n till  0 (så att grafen kan ritas på nytt). 

void loop(void) {

  // Refresh picture
  oled.firstPage();  
  do {
    draw();
  } while(oled.nextPage());

  // Continue drawing line until n<30
  if(n < 30) {
    n++;
  }
  
  // Wait 5000 ms and then remove line
  else {
    delay(5000);
    n = 0;
  }
}

I draw-funktionen ändrar vi argumenten för drawLine. Varje gång drawLine anropas ska funktionen skapa en linje som går från (12,  60) till (12 + n * 4,  60 - n). 

 oled.drawLine(12, 60, 12 + n * 4, 60 - n);

KörningStartkoordinatSlutkoordinat
0 12, 60 12, 60
1 12, 60 16, 59
2 12, 60 20, 58
3 12, 60 24, 57
4 12, 60 28, 56

Den färdiga koden ser ut på följande vis.

DrawAnimation.ino

Rita bilder

Eftersom vi kan adressera enskilda pixlar kan vi rita monokroma bilder på OLED-displayen. Det är inte så praktiskt att manuellt ange alla pixlar som ska tändas. Som tur är kan vi omvandla bilder till koordinater med hjälp av gratisprogrammet Gimp som finns till både Windows och Mac OS (finns att ladda ned från www.gimp.org). 

I detta exempel vill vi rita Kjell & Company-logotypen på displayen. Vi börjar med att skapa en ny bild i Gimp med samma storlek som displayen (128 x 64 pixlar) och klistrar in logotypen. 

Sedan väljer vi Exportera som (på Arkiv-menyn) och exporterar bilden som en X bitmap-bild. Vi kallar filen logo.xbm

Den exporterade bilden kan vi öppna i valfri textredigerare (t.ex. Anteckningar). Vi kopierar texten och klistrar in den ovanför alla funktioner i en ny sketch. Då ser vi att "bilden" egentligen är specifikationer för hur bred och hög bilden är samt en lång char-array med tecken som bygger upp bilden.

Vi måste ändra en detalj i följande rad.

static unsigned char logo_bits[] = {

För att bilden ska tolkas rätt uppdaterar vi den till följande rad. 

static unsigned char logo_bits[] U8G_PROGMEM = {

Vi  byter också define-variablerna till int-konstanter, och skriver en draw-funktion som ritar bilden med metoden drawXBMP. Som argument anger vi koordinaterna, bild­bredden, bildhöjden och bilddatan. Den färdiga sketchen ser ut på följande vis. 

DrawLogo.ino

Senast ändrad: 2017-08-02