Vise tekst og grafikk på displayer

Vise tekst og grafikk på displayer

I tidligere bygg har vi brukt utviklingsmiljøets Seriell overvåker flittig. Den er utmerket for feilsøking, men ikke særlig egnet for ferdige prosjekter. I dette kapittelet skal vi derfor se nærmere på displayer og hvordan vi kan vise tekst og bilder på dem. 

Alfanumerisk LCD-display

En displaytype som ofte brukes i Arduino-sammenheng er alfanumeriske LCD-displayer. Det er LCD-skjermer som er laget for å vise bestemte tegn (bokstaver, sifre og spesialtegn). Takket være biblioteket LiquidCrystal kan vi raskt oppnå resultater.

Alfanumeriske LCD-displayer med to og fire tegnlinjer.

Vi har valgt å ikke bruke alfanumeriske displayer i noen av bokens prosjekter. Slike skjermer er riktignok enkle å bruke, men de ser litt umoderne ut og kan ikke håndtere grafikk. Hvis du vil vite mer om alfanumeriske LCD-displayer, anbefaler vi Arduinos offisielle referansesider som omhandler LiquidCrystal

OLED-display

Nå for tiden koster ikke OLED-displayer en formue (noe de gjorde før). De er betydelig bedre enn alfanumeriske LCD-displayer siden pikslene lyser selv. OLED-displayer krever derfor ingen bakgrunnsbelysning og gir et dypt sortnivå.

I bokens medfølgende komponentpakke er det et monokromatisk OLED-display med SSD1306-kontrolleren. Det har en oppløsning på 128 x 64 som kan brukes til å vise både bilder og tekst. Du kan også velge hvilken skrift teksten skal vises med.

OLED-display med SSD1306-kontroller

Tilkoblinger for OLED-displayer

Det er to populære måter å koble OLED-displayer til utviklingskort på: I²C (Inter-Integrated Circuit) og SPI (Serial Peripheral Interface). Vi kommer til å bruke I²C i denne boken, fordi I²C-protokollen har mange fordeler over SPI. Til forskjell fra SPI sikrer I²C at dataene kommer frem. I²C trenger dessuten bare to ledere for å sende data (mens SPI trenger fire). Tilkoblingene for I²C heter SCL (Serial Clock Line) og SDA (Serial Data Line).

Arduino Uno har to dedikerte pinner for å koble til I²C-enheter (SCL- og SDA-stiften). De er ikke merket på alle versjoner av utviklingskortet, men de sitter øverst i pinneraden med GPIO-pinnene. A4 og A5 kan også brukes som I²C-kontakter.

I eksempelet i dette kapittelet vil vi koble vårt SSD1306-baserte OLED-display til utviklingskortet. Det gjør vi med følgende kobling. 

Illustrasjon laget med komponenter fra Fritzing (fritzing.org). CC BY-SA 3.0

Adressere piksler i displayet

Displayet i eksempelet vårt består av 128 piksler horisontalt og 64 piksler vertikalt. For å kunne tenne ønskede piksler må vi først og fremst kunne adressere dem. Pikselen øverst til venstre kalles (0, 0), og pikselen nederst til høyre kalles (127, 63).

Her er en oversikt over hvor ulike piksler befinner seg i koordinatsystemet.  

Tenne piksler

I følgende eksempel vil vi tenne to spesifikke piksler og tegne tre streker. Vi begynner derfor skissen med å importere biblioteket U8glib. I motsetning til Servo-biblioteket er ikke U8glib forhåndsinstallert i utviklingsmiljøet (se om nødvendig installasjonsveiledningen i Biblioteker). Vi lager deretter et nytt SSD1306-display og kaller det oled. Vi angir at det har målene 128 x 64 og at det er koblet til via I²C.

Del 1/3 av DrawPixels.ino

Vi lager en egen funksjon ved navn draw. I den skriver vi hvilke piksler vi vil tenne ved hjelp av funksjonen drawPixels samt X- og Y-koordinatene som argument. Vi vil også tegne linjer, noe vi gjør med funksjonen drawLine og et par koordinater. Funksjonens argument skrives slik:

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

I skissen bruker vi funksjonen på denne måten:

Del 2/3 av DrawPixels.ino

Avslutningsvis kaller vi opp draw-funksjonen fra en do-løkke i loop-funksjonen.

Del 3/3 av DrawPixels.ino

Skrive tekst

Siden vi kan adressere hver eneste lille piksel på skjermen er det ikke noe problem å skrive tekst i varierende størrelse og skrift. Vi kan i stor grad ta utgangspunkt i koden som vi allerede har skrevet, men vi må definere hvordan bokstavene våre skal se ut.

Hvis vi vil at teksten skal skrives med Times i størrelse 12, legger vi inn følgende kode i teksten.

oled.setFont(u8g_font_timB12);

Vi vil at bokstavene skal skrives med Helvetica i størrelse 10 og legger derfor inn følgende kode:

oled.setFont(u8g_font_helvB10);

Sifrene som står etter bokstaven B, angir tegnstørrelsen. Du finner en oversikt over tilgjengelige skrifter her.

Vi vil skrive to linjer øverst til venstre på skjermen. U8glib bygger bokstavene nedenfra og opp ut fra en koordinat, så vi skal ikke angi (0, 0) som startkoordinat. Siden tegnene har en høyde på ti piksler skal vi i stedet angi (0, 10).

Neste linje med tekst vil vi at skal gå ut fra koordinatene (0, 23). Da blir det lite mellomrom mellom de to linjene, og bokstaver som går under basislinjen (se liten y), havner ikke oppå bokstavene på den andre linjen.

DrawText.ino

Formatere tekst

Av og til ser det bedre ut hvis teksten er midtstilt (horisontalt) i stedet for venstrejustert. Dessverre finnes det ikke noen måte å midtstille den på automatisk, men det går an å regne ut hvor teksten skal begynne å bli skrevet for at den skal havne i midten.

I følgende eksempel skal vi skrive et ordtak i midten av displayet. Vi begynner med å lagre ordspråkets to tekstlinjer i hvert sitt globale char-array.

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

I draw-funksjonen begynner vi med å velge skrifttype og størrelse. Deretter regner vi ut hvor mange piksler høy en linje er, og vi tar hensyn til at enkelte bokstaver går under basislinjen.

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

Midtstillelsen av linjene løser vi med å subtrahere den aktuelle strenglengden fra den maksimale linjelengden og dividere differansen på to.

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

Til slutt skriver vi den første linjen (line1) på koordinaten som plasserer den midtstilt to linjehøyder ned. Vi gjør det samme med den andre linjen (line2).

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

Den ferdige skissen ser slik ut:

DrawCenterText.ino

Animere

Grafen vi tegnet hadde vært morsommere hvis den beveget seg. Men noen få kodelinjer kan vi få den til å bli tegnet gradvis.

Det er viktig å huske at hele displayet skal oppdateres hver gang noe endres. Vi kan altså ikke fylle på grafen gradvis, men vi må lage en serie med bilder der grafen blir litt lenger for hvert bilde. For å holde rede på hvor lang grafen er for øyeblikket, lager vi en global variabel ved navn n.

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

I loop-funksjonen lager vi en while-løkke som regner ut n (forlenger grafen) så lenge n er mindre enn 30. Hvis n er over 30, blir koden satt på pause i fem sekunder, og deretter blir n endret til 0 (slik at grafen kan tegnes 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-funksjonen endrer vi argumentene for drawLine. Hver gang drawLine kalles opp, skal funksjonen lage en linje som går fra (12, 60) til (12 + n * 4, 60 - n).

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

KJØRINGStartkoordinatSlutTkoordinat
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 ferdige koden ser slik ut:

DrawAnimation.ino

Tegne bilder

Siden vi kan adressere enkeltstående piksler, kan vi tegne monokromatiske bilder på OLED-displayet. Det er ikke så praktisk å manuelt angi alle piksler som skal tennes. Heldigvis kan vi omforme bilder til koordinater ved hjelp av gratisprogrammet Gimp, som finnes til både Windows og Mac OS (kan lastes ned fra www.gimp.org).

I dette eksempelet skal vi tegne Kjell & Company-logoen på displayet. Vi begynner med å lage et nytt bilde i Gimp med samme størrelse som displayet (128 x 64 piksler), og limer inn logoen.

Deretter velger vi Export as (på Fil-menyen) og eksporterer bildet som et X bitmap-bilde. Vi kaller filen logo.xbm

Det eksporterte bildet kan vi åpne i et valgfritt tekstredigeringsprogram (f.eks. Notisblokk). Vi kopierer teksten og limer den inn over alle funksjoner i en ny skisse. Da ser vi at "bildet" egentlig er spesifikasjoner for hvor bredt og høyt bildet er, og et langt char-array med tegn som bygger opp bildet.

Vi må endre en liten ting i følgende linje:

static unsigned char logo_bits[] = {

For at bildet skal tegnes korrekt endrer vi den linjen til: 

static unsigned char logo_bits[] U8G_PROGMEM = {

Vi endrer også define-variablene til int-konstanter og skriver en draw-funksjon som tegner bildet med metoden drawXBMP. Som argument angir vi koordinatene, bildebredden, bildehøyden og bildedataene. Den ferdige skissen ser slik ut: 

DrawLogo.ino

Sist endret: 2017-09-08