Arduino Lys-avis

Fra Kommunikation-IT Holstebro HTX
Skift til: navigering, søgning
Dot Matrix modul

Grundlæggende forudsætninger

Modulet der anvendes til lysavisen er baseret på en MAX7219 driver, der kan styres af 3 serielle signaler.

Man kunne vælge at basere det på en færdigskrevet bibliotek[1], som ville gøre det enkelt at gå til LED-Matrix-modulet. I stedet er det baseret på software skrevet fra bunden til at styre modulet med. Den grunlæggende software er beskrevet på Arduino Dot-Matrix.

Modulet er købt ved aliexpress.com[2].

Hardware opkobling

For at kunne styre hver enkelt modul, så kan man koble CLK, +5V og GND igennem alle de moduler man ønsker.

Til gengæld skal databenet føres direkte til alle moduler, da det er et andet signal der kommer ud af modulet på D-OUT. Dette gøres ved at fordele signalet via et fumlebræt som vist

For at kunne skille modulerne ad, så får de hver sin CS fra Arduinoen.

Dette kan skitseres som følger:

Opstilling med Arduino koblet til en række Dot-matrix-moduler som en lysavis.
Opstilling med Arduino koblet til en række Dot-matrix-moduler som en lysavis.

I praksis ser opkoblingen af 6 moduler ud som følger:

Opstilling med Arduino koblet til 6 Dot-matrix-moduler som en lysavis.
Praktisk opstilling med Arduino koblet til 6 Dot-matrix-moduler som en lysavis.

Ideen bag softwaren til lys-avisen

For at man kan få lysavisen til at "løbe" hen over alle modulerne, så er man nødt til at genskrive alt indholdet med et fast interval - i dette tilfælde sat til 100 ms.

For kunne gøre lysavisen rimelig universel, så er der lagt et helt karaktersæt ind, så man kan oversætte de givne karakterer til en visning på Dot-matrix displayene.

Starten af lysavisen er at alle moduler er blanke, og når den viste tekst er udskrevet hel, så sættes alle moduler igen blanke inden der sættes den samme tekst op igen.

Som softwaren er konstrueret nu, så starter lysavisen med en konstant tekst efter reset, men man kan via den serielle monitor indlæse en ny tekststreng der vil blive vist. Denne tekststreng må højst være 128 karakterer lang. En ide til en udvidelse kunne være at man gemmer den indlæste tekst i EEPROM, så det er den sidst indskrevne tekst den vågner op med.

Opbygningen af softwaren

Hele softwaren ligger i en ZIP-fil, sammen med de andre eksempler på kode til Dot-Matrix. Lysavisen beskrevet her hedder Lys-Avis.

Som en start defineres alle de hardware-mæssige forudsætninger for Lys-avisen, hvilke ben der anvendes, størrelsen på modulerne størrelsen på de karakterer der defineres, hvor mange karakterer der er defineret, max antal karakterer der kan vises i Lys-avisen og opdaterings-tiden (hvor hurtigt den ruller).

const byte antalBlok = 6;    // Antallet af blokke der skal vise noget
const byte charWidth = 6;    // Karakter-bredden i punkter
const byte blokWidth = 8;    // Blok-bredden i punkter
const int din = 2;           // Data in ben, fordeles til alle moduler
const int cs [antalBlok] {3, 5, 6, 7, 8, 9}; // CS-ben, et til hvert modul
const int clk = 4;           // Clock-ben - til første modul, linkes videre
const byte antalCh = 102;    // Antallet i charSet
const byte maxStr = 128;     // Max antal viste karakterer, må ikke være over 255
const int updateTime = 100;  // Antal ms mellen hver opdatering

Karakter-tabellen

Karaktererne opbygges i en matrix der er 5 dots bred og 8 dots høj, hvor de to nederste dots reserveres til de karakterer der går "under linjen", så for at repræsentere en dot skal der angives en bit høj, og en bit lav, hvis der ikke er en dot, og da karakteren netop er 8 bit høj, så passer det fint med en byte, så definitionen af en karakter kan gemmes i 5 bytes (bredden af en karakter). Dette passer også så fornemt med at man tænder 8 dots af gangen ved at kommunikere en byte over, så for at vise en karakter skal der blot overføres 5 byte plus en byte der er 0 til afstanden mellem karaktererne.

For at illustrere hvordan karaterene defineres vises her eksemplet for 3 karakterer A, B og C. I karaktertabellen er det valgt at bunden af karakteren er de mest betyden bits i byten.

Karaktererne startes med at blive tegnet op på et kvadreret papir, og ser ud som følger:

  *    ***     *** 
 * *   *  *   *   *
 * *   ****   *
*****  *   *  *
*   *  *   *  *   *
*   *  ****    ***


Ved at omdanne dette til 0'er og 1'er, så får man følgende mønster:

00100 11100 01110
01010 10010 10001
01010 11110 10000
11111 10001 10000
10001 10001 10001
10001 11110 01110
00000 00000 00000
00000 00000 00000

Hvis man for A's vedkommende tolker dette som binære tal læst lodret med mindst betydende bit øverst, så får man følgende 5 binære tal, der kan oversættes til Hexadicimale tal som vist her:
00111000 - 0x38
00001110 - 0x0E
00001001 - 0x09
00001110 - 0x0E
00111000 - 0x38

For at kunne kunne identificere den enkelte karakter i karaktertabellen, så anvendes ASCII-tabellen, der knytter et bestemt tal (0-255) til en bestemt karakter, hvor A, B og C har ASCII-numrene 65, 66 og 67. Ud fra dette kan man definere A, B og C som følger:

  {  65, 0x38, 0x0E, 0x09, 0x0E, 0x38 }, // A
  {  66, 0x3F, 0x25, 0x25, 0x26, 0x18 }, // B
  {  67, 0x1E, 0x21, 0x21, 0x21, 0x12 }, // C

Med lidt tålmodighed kan alle de 102 karakterer der er defineret opstilles i tabellen charSet:

// Hele karaktertabellen, der definerer visningen af karakter-numrene
// Første ciffer et karakteren ASCII nummer
// De 5 følgende er de enkelte punkter defineret som HEX-tal
// Alle karakterer er 5x8 punkter, efterfulgt af et punkts mellemrum
const byte charSet [antalCh][6] {
  {   0, 0x55, 0xAA, 0x55, 0xAA, 0x55 }, // Ikke eksisterende
  {  32, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Space
  {  33, 0x00, 0x00, 0x2F, 0x00, 0x00 }, // !
  {  34, 0x00, 0x06, 0x00, 0x06, 0x00 }, // "
  {  35, 0x14, 0x3E, 0x14, 0x3E, 0x14 }, // #
  {  36, 0x24, 0x2A, 0x7F, 0x2A, 0x12 }, // $
  {  37, 0x22, 0x10, 0x08, 0x04, 0x22 }, // %
  {  38, 0x14, 0x2A, 0x2C, 0x10, 0x28 }, // &
  {  39, 0x00, 0x00, 0x06, 0x00, 0x00 }, // '
  {  40, 0x00, 0x00, 0x3E, 0x41, 0x00 }, // (
  {  41, 0x00, 0x41, 0x3E, 0x00, 0x00 }, // )
  {  42, 0x24, 0x18, 0x0E, 0x18, 0x24 }, // *
  {  43, 0x08, 0x08, 0x3E, 0x08, 0x08 }, // +
  {  44, 0x00, 0x60, 0x20, 0x00, 0x00 }, // ,
  {  45, 0x00, 0x08, 0x08, 0x08, 0x00 }, // -
  {  46, 0x00, 0x30, 0x30, 0x00, 0x00 }, // .
  {  47, 0x20, 0x10, 0x08, 0x04, 0x02 }, // /
  {  48, 0x1E, 0x21, 0x21, 0x1E, 0x00 }, // 0
  {  49, 0x00, 0x22, 0x3F, 0x20, 0x00 }, // 1
  {  50, 0x22, 0x31, 0x29, 0x26, 0x00 }, // 2
  {  51, 0x12, 0x21, 0x2D, 0x12, 0x00 }, // 3
  {  52, 0x0F, 0x08, 0x08, 0x3F, 0x00 }, // 4
  {  53, 0x17, 0x25, 0x25, 0x19, 0x00 }, // 5
  {  54, 0x1E, 0x25, 0x25, 0x18, 0x00 }, // 6
  {  55, 0x21, 0x11, 0x09, 0x07, 0x00 }, // 7
  {  56, 0x1A, 0x25, 0x25, 0x1A, 0x00 }, // 8
  {  57, 0x06, 0x29, 0x29, 0x1E, 0x00 }, // 9
  {  58, 0x00, 0x00, 0x24, 0x00, 0x00 }, // :
  {  59, 0x00, 0x40, 0x24, 0x00, 0x00 }, // ;
  {  60, 0x08, 0x14, 0x22, 0x41, 0x00 }, // <
  {  61, 0x00, 0x14, 0x14, 0x14, 0x00 }, // =
  {  62, 0x41, 0x22, 0x14, 0x08, 0x00 }, // >
  {  63, 0x02, 0x01, 0xB1, 0x09, 0x06 }, // ?
  {  64, 0x3E, 0x49, 0x55, 0x5D, 0x4E }, // @
  {  65, 0x38, 0x0E, 0x09, 0x0E, 0x38 }, // A
  {  66, 0x3F, 0x25, 0x25, 0x26, 0x18 }, // B
  {  67, 0x1E, 0x21, 0x21, 0x21, 0x12 }, // C
  {  68, 0x3F, 0x21, 0x21, 0x21, 0x1E }, // D
  {  69, 0x3F, 0x25, 0x25, 0x25, 0x21 }, // E
  {  70, 0x3F, 0x05, 0x05, 0x05, 0x01 }, // F
  {  71, 0x1E, 0x21, 0x21, 0x29, 0x1A }, // G
  {  72, 0x3F, 0x04, 0x04, 0x04, 0x3F }, // H
  {  73, 0x00, 0x21, 0x3F, 0x21, 0x00 }, // I
  {  74, 0x11, 0x21, 0x21, 0x21, 0x1F }, // J
  {  75, 0x3F, 0x08, 0x0C, 0x12, 0x21 }, // K
  {  76, 0x3F, 0x20, 0x20, 0x20, 0x20 }, // L
  {  77, 0x3F, 0x02, 0x0C, 0x02, 0x3F }, // M
  {  78, 0x3F, 0x06, 0x08, 0x10, 0x3F }, // N
  {  79, 0x1E, 0x21, 0x21, 0x21, 0x1E }, // O
  {  80, 0x3F, 0x09, 0x09, 0x09, 0x06 }, // P
  {  81, 0x1E, 0x21, 0x21, 0x11, 0x2E }, // Q
  {  82, 0x3F, 0x05, 0x0D, 0x15, 0x22 }, // R
  {  83, 0x12, 0x25, 0x29, 0x29, 0x12 }, // S
  {  84, 0x01, 0x01, 0x3F, 0x01, 0x01 }, // T
  {  85, 0x1F, 0x20, 0x20, 0x20, 0x1F }, // U
  {  86, 0x07, 0x18, 0x20, 0x18, 0x07 }, // V
  {  87, 0x07, 0x38, 0x06, 0x38, 0x07 }, // W
  {  88, 0x23, 0x14, 0x08, 0x14, 0x23 }, // X
  {  89, 0x03, 0x04, 0x38, 0x04, 0x03 }, // Y
  {  90, 0x31, 0x29, 0x2D, 0x25, 0x23 }, // Z
  {  91, 0x00, 0x7F, 0x41, 0x41, 0x00 }, // [
  {  92, 0x02, 0x04, 0x08, 0x10, 0x20 }, // \
  {  93, 0x00, 0x41, 0x41, 0x7F, 0x00 }, // ]
  {  94, 0x00, 0x02, 0x01, 0x02, 0x00 }, // ^
  {  95, 0x40, 0x40, 0x40, 0x40, 0x40 }, // _
  {  96, 0x00, 0x01, 0x02, 0x00, 0x00 }, // `
  { 198, 0x38, 0x06, 0x1D, 0x25, 0x21 }, // Æ
  { 216, 0x2E, 0x11, 0x2D, 0x22, 0x1D }, // Ø
  { 197, 0x30, 0x1C, 0x15, 0x1C, 0x30 }, // Å
  {  97, 0x18, 0x24, 0x24, 0x1C, 0x20 }, // a
  {  98, 0x20, 0x1F, 0x24, 0x24, 0x18 }, // b
  {  99, 0x18, 0x24, 0x24, 0x24, 0x00 }, // c
  { 100, 0x18, 0x24, 0x24, 0x1F, 0x20 }, // d
  { 101, 0x1C, 0x2A, 0x2A, 0x2C, 0x00 }, // e
  { 102, 0x00, 0x04, 0x3F, 0x05, 0x00 }, // f
  { 103, 0x18, 0xA4, 0xA4, 0xA4, 0x78 }, // g
  { 104, 0x3F, 0x04, 0x04, 0x38, 0x00 }, // h
  { 105, 0x00, 0x00, 0x3A, 0x00, 0x00 }, // i
  { 106, 0x00, 0x80, 0x80, 0x7A, 0x00 }, // j
  { 107, 0x3F, 0x10, 0x18, 0x24, 0x00 }, // k
  { 108, 0x00, 0x1F, 0x20, 0x00, 0x00 }, // l
  { 109, 0x38, 0x04, 0x18, 0x04, 0x38 }, // m
  { 110, 0x3C, 0x08, 0x04, 0x38, 0x00 }, // n
  { 111, 0x18, 0x24, 0x24, 0x18, 0x00 }, // o
  { 112, 0xF8, 0x24, 0x24, 0x18, 0x00 }, // p
  { 113, 0x18, 0x24, 0x24, 0xF8, 0x00 }, // q
  { 114, 0x04, 0x38, 0x04, 0x08, 0x00 }, // r
  { 115, 0x24, 0x2A, 0x2A, 0x12, 0x00 }, // s
  { 116, 0x00, 0x02, 0x1F, 0x22, 0x00 }, // t
  { 117, 0x1C, 0x20, 0x20, 0x1C, 0x20 }, // u
  { 118, 0x04, 0x18, 0x20, 0x18, 0x04 }, // v
  { 119, 0x0C, 0x30, 0x18, 0x30, 0x0C }, // w
  { 120, 0x22, 0x14, 0x08, 0x14, 0x22 }, // x
  { 121, 0x1C, 0xA0, 0xA0, 0x7C, 0x00 }, // y
  { 122, 0x24, 0x34, 0x2C, 0x24, 0x00 }, // z
  { 123, 0x00, 0x08, 0x36, 0x41, 0x00 }, // {
  { 124, 0x00, 0x00, 0x7F, 0x00, 0x00 }, // |
  { 125, 0x00, 0x41, 0x36, 0x08, 0x00 }, // }
  { 126, 0x02, 0x01, 0x02, 0x04, 0x02 }, // ~
  { 230, 0x1C, 0x22, 0x1C, 0x2A, 0x2C }, // æ
  { 248, 0x2C, 0x12, 0x2A, 0x24, 0x1A }, // ø
  { 229, 0x18, 0x24, 0x25, 0x1C, 0x20 } // å
};

Initialisering af programmet

Initialiseringen af programmet sker som det er standard i setup().

Den serielle port sættes op, så man kan kommunikere via den serielle port og lægge nye tekster ind.

De ben der skal anvendes til at kommunikere med modulerne skal sættes op til output og sættes til det rigtige niveau. Da der er flere moduler skal alle CS-benene sættes op, og i samme loop initialiseres registrene i blokkene når benene er sat op og visningen i displayet slettes, så der er klart til at lys-avisen kan vise tekst.

Til slut skrives en velkomst-besked på den serielle port, der indikerer at man kan skrive en ny tekst ind i den serielle monitor.

void setup() {
  Serial.begin(9600);
  // Sæt de serielle udgange:
  pinMode (din, OUTPUT);
  pinMode (clk, OUTPUT);
  // Sæt start-niveauerne:
  digitalWrite(clk, LOW);
  for (int n = 0; n < antalBlok; n++) {
    pinMode (cs[n], OUTPUT);
    digitalWrite(cs[n], HIGH);
    // Initialiser alle blokke
    disp(n, 0x0B, 0x07);  // Scan Limit - Display alle bit
    disp(n, 0x09, 0x00);  // Decode Mode - Ingen dekodning
    disp(n, 0x0C, 0x01);  // Shutdown - Normal operation
    disp(n, 0x0F, 0x00);  // Display Test - Slået fra
    disp(n, 0x0A, 0x01);  // Intensity - low level
    // slet visning i alle blokke
    for (int i = 1; i < 9; i++) {
      disp(n, i, 0);
    }
  }
  Serial.print("Skriv tekst ind her - husk at sl");
  Serial.write(229);  // å
  Serial.println(" CR LF til");
  Serial.println("-------------------------------------------");
}

Kommunikation med modulerne

Rutinerne til at clocke data ind i modulerne med stammer fra Dot-matrix testen, hvor den første rutine til at sende en byte med er præcist den samme, mens rutinen disp er modificeret lidt, så den også angiver hvilket modul der skal kommunikeres med, det er parameteren blok der angiver det. De to andre parametre adr og data er den adresse der skal skrives til (hvilken kolonne af dots det er) og data er det indhold der skal skrives i den kolonne.

// Funktion der clocker en byte ud til displayet
void sendByte(byte data) {
  for (int n = 0; n < 8; n++) {
    digitalWrite(din, data & 0x80);
    data <<= 1;
    digitalWrite(clk, HIGH);
    digitalWrite(clk, LOW);
  }
}

// Funktion der vælger blokken og clocker adresse og data til blokken
void disp(byte blok, byte adr, byte data) {
  digitalWrite(cs[blok], LOW);
  sendByte(adr);
  sendByte(data);
  digitalWrite(cs[blok], HIGH);
}

Variabler i programmet

Fordi en del af variablerne skal gemmes fra gang til gang at loop() kaldes, så er de erklæret globalt, og for at holde variablerne samlet er de alle erklæret her.

De 4 første variabler har med modtagelsen af en streng fra den serielle port at gøre.

De resterende variabler har med styringen af udskriften af den gemte streng at gøre, hvor strengen som standard sættes til "Lysavis lavet ved Holstebro HTX - 2015", så der kommer en fornuftig visning når programmet vågner. Til slut er der en test-variabel.

byte ch;             // Karakter modtaget serielt
boolean OK;          // Er katakteren fundet
char modtStreng [maxStr];  // Streng til modtagelse
byte modtPtr = 0;    // Pointer til næste karakter der skal modtages

int ptr = 0;         // Pointer der angiver hvor visningen er kommet til
char streng [maxStr] = "Lysavis lavet ved Holstebro HTX - 2015";
byte strAntal = 38;  // Visnings-streng og antallet i den
int strPtr;          // Punkt-pointer ind i den viste streng
long time = 0;       // Sidste visnings-tidspunkt
byte data;           // Byte der skal clockes ud til displayet
byte blok;           // Angivelsen af blokken der skal skrives til
byte row;            // Angivelsen af hvilken række i karakteren
byte strNr;          // Angivelsen af hvilken karater i strengen der vises

byte timeCnt = 0;    // Testvariabel til visning af udlæsningstid

Modtagelse af karakterer til en streng

Hovedsoftwaren bliver afviklet i loop(), hvor den kaldes med forskelligt interval, alt efter hvor lang tid koden tager i loopet. Dette er koden forsøgt skrevet til, så man kommer igennem hurtigst muligt, hvis der ikke skal udføres noget.

Den første del af koden i loop() som er vist herunder sørger for at modtage fra den serielle port, hvis der kommer noget. Denne kode tager højde for at der kan være længere interval mellem den bliver kaldt (op til 17 ms viser det sig i en teste foretager længere nede med 6 moduler tilkoblet).

Der kigges først på om der er karakterer tilgængelige, og hvis der ikke er det, så vil der ikke ske mere i koden. Er der karakterer tilgængelige, så loopes der indtil man har tømt bufferen (max 64 karakterer i Serial-modulet). Hver enkelt karakter der modtages echoes tilbage for brugervenlighedens skyld.

Hvis det er en almindelig karakter, så lagres den i en buffer (et array) og pointeren sættes klar til næste karakter, hvis man ikke kommer ud over bufferens grænse (128 karakterer), ellers tabes karakteren, da den alligevel ikke kan håndteres.

For at afslutte sendingen skal Serial Monitor sættes op til at sende CR som linje afslutning. Dette detekteres der på her (en LF ignoreres for en sikkerheds skyld) og når der modtages en CR, så overføres hele den modtagne streng til visnings-strengen, antallet registreres og der gøres klar til en ny modtagelse (pointeren nulstilles).

void loop() {
  while (Serial.available() > 0) {  // Modtages der nye karkterer
    // Læs en karakter
    ch = Serial.read();
    Serial.write(ch);
    /* Til visning af karakter nummer - udkommenteret
    Serial.print(" ");
    Serial.println(ch);
    */
    if (ch == 13) {   // Ved linje slut - CR - gemmes den
      for (int n = 0; n < modtPtr; n++) {
        streng[n] = modtStreng[n];
      }
      // Antallet i linjen registreres og der gøres klar til modtagelse af ny linje
      strAntal = modtPtr;
      modtPtr = 0;
    } else if (ch == 10) { // Ignorer LF
    } else {  // Gem den modtagne karakter
      modtStreng[modtPtr] = ch;
      if (modtPtr < maxStr) {
        modtPtr++;
      }
    }
  }

Visning af tekststengen i Dot-Matrix modulerne

Denne del af koden aktiveres hver gang funktionen millis() er blevet updateTime større end sidste opdatering. Dette gør at koden her bliver afviklet 10 gange i sekundet (med updateTime på 100, hvarende til 100ms).

Koden baserer sig på 2 pointere der begge bevæger sig i kolonner, og altså har 6 trin for hver karakter. Den første pointer ptr løber hen gennem 6 gange hele strengens længde plus det antal dots der er i alle blokke (det giver et tomt ophold fra slut tekst til ny start af samme tekst på netop alle blokke). Den næste poniter strPtr løber for hver visning et antal igennem svarende til det antal dots der er i alle blokke. Tricket er at den starter på den anden pointer minus antal dots i blokkene, hvilket gør at den kan starte med en negativ værdi.

I gennemløbet af alle dots tjekkes om strPtr er negativ, hvor der så vises blank (det etablerer mellemrummet mellem start og slut). Er strPtr positiv beregnes hvilken række i karakteren der skal vises. Hvis det er den 6. så er det mellemrummet mellem karaktererne, så vises der 0 så der bliver et mellemrum. Er det inde i karakteren, så tjekkes der om den aktuelle karakter ligger inden for strengen, og hvis den gør det, så ledes karatertabellen igennem for at finde oversættelsen af den ønskede karakter. Findes den ikke sættes visningen til en dummy-karakter for at indikere at karakteren ikke kan vises.

Når opslaget til karakteren er fundet, så hentes den ønskede række frem fra karaktertabellen og rækken clockes ud med de beskrevne rutiner, og der sættes frem til næste række i modulerne.

Når alle rækker i modulerne er vist, så sættes pointeren ptr en frem, så visningen rykker sig en frem ved hver visning.

  if (time + updateTime < millis()) { // Opdater 10 gange i sekundet  
    time = millis();
    // Begræns visningen til strenges antal punkter + bredden af blokkene
    if (ptr > antalBlok * blokWidth + strAntal * charWidth) {
      ptr = 0;
    }
    // Find hvor strengens start er
    strPtr = ptr - antalBlok * blokWidth;
    // Løb hen gennem alle punkter i blokkene
    for (int n = 0; n < antalBlok * blokWidth; n++) {
      if (strPtr < 0) {  // Vis tomt mellem start og slut af streng
        data = 0;
      } else {   // Find den række der skal clockes ud
        row = strPtr % charWidth;
        if (row == (charWidth - 1)) {  // Tomt mellemrum mellem karaktererne
          data = 0;
        } else {  // Find den aktuelle række
          row++;
          strNr = strPtr / charWidth;
          if (strNr >= strAntal) {
            data = 0;
          } else {
            OK = false;
            for (int i = 0; i < antalCh; i++) { // Led efter den aktuelle karakter i karaktersættet
              if (byte(streng[strNr]) == charSet[i][0]) {
                data = i;
                OK = true;
                break;
              }
            }
            if (! OK) {
              data = 0;  // Sæt fejlvisning hvis den ikke er fundet
            }
            data = charSet[data][row];  // Hent det der skal clockes ud
          }
        }
      }
      // Clock den aktuelle byte ud (8 punkter lodret)
      blok = antalBlok - n / blokWidth - 1;
      disp(blok, n % blokWidth + 1, data);
      strPtr++;
    }
    // ch = streng[strNr];
    ptr++;

Test af afviklingstiden

Den sidste del af koden der her er kommenteret ud er taget med for at kunne få en ide om hvor lang tid koden tager om at gennemløbe en hel udscanning af alle moduler.

Ved en test med 6 moduler viste tiderne skift fra 12 ms hvor der ikke var tekst der skulle vises op til 17 ms hvor det var udelukkende karakterer sidst i karaktertabellen (kræver søgning i tabellen hver gang). Dette betyder at softwaren kan håndtere op til omkring 36 moduler før den får tidsproblemer ved en opdatering på 100 ms, mens man max kan håndtere ca. 18 moduler med en opdatering på 50 ms.

Det er ikke afprøvet hvad der sker hvis man bryder grænserne for opdatering, men det ville sikkert kunne give problemer med den serielle kommunikation, fx at man ville kunne miste karakterer i det modtagne.

    // Analyse af hvor lang tid optegningen tage - resultat 12 - 17 ms
    // afhængigt af hvor i karaktertabellen det hentes
    /* udkommenteret
    timeCnt++;
    if (timeCnt == 10) {
      timeCnt = 0;
      Serial.println(millis() - time);
    } */
  }
}

Demonstration af funktionen

Herunder demonstreres hvordan lys-avisen fungerer i en lille video.

Direkte link

Alternativ kodning med et bibliotek

Når man nu har et bibliotek til modulet, så kan det være en god ide at finde ud af hvilke fordele og ulemper der er ved at anvende biblioteket, når vi nu har fundet ud af hvordan modulet grundlæggende virker.

Hele softwaren ligger i en ZIP-fil, sammen med de andre eksempler på kode til Dot-Matrix. Lysavisen med den nye biblioteks-kode som er beskrevet her hedder Lys-Avis2.

Hardware med biblioteket

Ved lidt søgning på nettet (specielt Youtube) kan man finde ud af at modulet lægger op til at man kan daisy-chaine alle signalerne i modulerne (det er også det der virker mest logisk), således at det kun er det første modul der forbindes til arduinoen (3 udgange, GND og +5V). Næste modul forbindes så med alle 5 signaler til de 5 udgange, osv.

Dette giver en opstilling som vist her:

Lys-avis Hardware lavet med biblioteksmodulet
Lys-avis Hardware lavet med biblioteksmodulet

Biblioteks-software

Selve biblioteket[1] skal selvfølgelig installeres i Arduinos IDE som et af Arduinos Biblioteker. Biblioteket hedder LedControl, men kan også søges frem ved at søge i bibliotekerne på MAX7219.

For at få kontakt til biblioteket skal det includes og biblioteket skal initialiseres med de 3 pins det er forbundet til, som vist i koden herunder:

#include <LedControl.h>

const byte antalBlok = 6;    // Antallet af blokke der skal vise noget
/*
 første pin (2) er forbundet til DataIn (DIN)
 anden pin (4) er forbundet til CLK 
 tredje pin (3) er forbundet til LOAD (CS) 
*/
LedControl lc=LedControl(2, 4, 3, antalBlok);

Herefter kan vi henvende os til objektet lc med de metoder der ligger i biblioteks-softwaren.

Man kan finde en nærmere omtale af biblioteket[3] på Arduinos playground.

Initialisering af modulerne

Initialiseringen kommer til at se noget anderledes ud, da modulerne kontaktes med nogle lidt anderledes metoder, specielt når man skal have fat i special-registrene, så der har de eksempler der ligger i LedControl biblioteket være gode at "låne" fra.

setup() kommer til at se ud som følger:

void setup() {
  Serial.begin(9600);
  for (int n = 0; n < antalBlok; n++) {
    // Initialiser alle blokke
    /* MAX72XX er i power-saving mode ved start, derfor skal den ud af shutdown */
    lc.shutdown(n,false);
    /* Set brightness til et lavt niveau, så Arduinoen kan forsyne */
    lc.setIntensity(n,1);
    /* Slet indholdet i displayet */
    lc.clearDisplay(n);
  } 
  Serial.print("Skriv tekst ind her - husk at sl");
  Serial.write(229);  // å
  Serial.println(" CR LF til");
  Serial.println("-------------------------------------------");
}

Initialiseringen af den serielle kommunikation er ikke anderledes.

Kommunikation med seriel og moduler

Der er ikke ændret på kommunikationen med den serielle monitor, så det er fuldstændigt den samme kode.

Kommunikationen med modulerne ligger nu i bibliotekerne, så de to rutiner der var før kan nu slettes.

Alle variabler til at kommunikere serielt med og til udlæsningen af karakterer er stort set de samme, så der er ikke rettet noget i dem.

Udlæsning til modulerne

For at udlæse til modulerne er det stort set den samme kode der anvendes, da der hver gang lysavisen skal rykkes skal analyseres hvilke karakterer der skal sendes, hvor de er i karaktertabellen og hvilke data der skal kommunikeres til modulerne.

Den eneste ændring der er foretaget i den gamle kode er der hvor den byte der skal clockes ud til modulerne sendes. Dette bliver nu gjort med metoden setRow som vist i følgende kode:

      // Clock den aktuelle byte ud (8 punkter lodret)
      blok = antalBlok - n / blokWidth - 1;
      lc.setRow(blok, n % blokWidth, data);
      strPtr++;

Test af udlæsningstiden

Som det til dels var forventet, så er det langsommere at læse data ud gennem alle moduler.

Ved en test med 6 moduler blev der målt en udlæsningstid på 83 til 87 ms, hvilket gør at med 100 ms opdateringstid, der kan der ikke føjes flere moduler på, hvis det eller skal kunne lade sig gøre at modtage noget fra den serielle port, så denne udlæsningsform begrænser altså tiden en hel del.

Software med EEPROM funktion

Som beskrevet i starten kan det være smart at man gemmer teksten i en EEPROM, så man kan få Arduinoen til at huske den sidst indtastede tekst, selvom strømmen forsvinder.

Hele softwaren ligger i en ZIP-fil, sammen med de andre eksempler på kode til Dot-Matrix. Lysavisen med EEPROM-kode som er beskrevet her hedder Lys-Avis3.

Rettelser i Setup()

For at man kan se om der ligger en tekst i EEPROM, så læses antallet af karakterer i den første adresse i EEPROM. Hvis dette tal ligger over 0 og under længden af den maksimale streng, så læses alle de angivne karakterer fra EEPROM.

Dette gøres i koden her, som et placeret i setup():

  strAntal = EEPROM.read(0);  // Læs antallet af gemte karakterer
  if (strAntal > 0 && strAntal < maxStr) {  // Hvis der ligger en standard streng i EEPROM  
    for (int n = 0; n < strAntal; n++) {
      ch = EEPROM.read(n+1);
      streng[n] = char(ch);
    }
  } else {
    strAntal = 38;  // Ellers brug default
  }

Hvis ikke der ses en streng i EEPROM, så sættes strengen til sin standard værdi.

Lagring af en ny streng

Hvis der indtastes en ny streng, så skiftes den eksisterende visning ud med den nye modtagne streng.

På dette tidspunkt gemmes den nye strang og antallet af karakterer så også i EEPROM. Dette fører til følgende kode:

    if (ch == 13) {   // Ved linje slut - CR - gemmes den
      EEPROM.write(0, modtPtr);  // Gem antallet af karakterer i EEPROM
      for (int n = 0; n < modtPtr; n++) {
        streng[n] = modtStreng[n];
        EEPROM.write(n+1, streng[n]);  // Gem strengens karakter i EEPROM
      }
      // Antallet i linjen registreres og der gøres klar til modtagelse af ny linje
      strAntal = modtPtr;
      modtPtr = 0;
    }

Test af lagring i EEPROM

Det at der ikke læses noget tilfældigt i EEPROM, hvis teksten er tom er testet i første hug, derefter kunne EEPROM ikke slettes (andet end hvis der blev lagt andet indhold i af et andet program), mem det ser ud til at fungere på en frisk Arduino, så standard-teksten kommer frfem.

Efter at der er lagt en ny tekst ind i EEPROM vil det være denne tekst den starter med efter enhver reset (testet med Serial Monitor start, Reset-knap og power off). Programmerer med koden ned igen bliver EEPROM'en ikke overskrevet, så der kommer den sidst gemte tekst fra EEPROM'en frem igen.

Lagring i EEPROM må siges at fungere.

Referencer

  1. 1,0 1,1 Henvisning til GitHUB LED-matrix Bibliotek
  2. Købsside ved aliexpress.com til modulet
  3. Arduino Playgraound omtale an ledControl