Tinkering Arduino

eine kleine Welt der Elektronik.

Sliding Countdown Timer Mit 8x8 LED Matrix Modul

Ein Kommentar von Alex in einem etwas älteren Beitrag hat mich dazu bewogen einen Arduino und ein 8x8 Matrix Modul herauszuholen und einen Countdown zu programmieren. Das ist ja nicht weiter schwer, nur sollten die Zahlen nicht einfach dargestellt werden, sondern mit einem Rolleffekt gewechselt werden.

Da der Countdown-Zähler zweistellige Zahlen anzeigen können sollte, konnte ich nicht einfach den Font aus dem alten Projekt nehmen. Daher habe ich gestern schnell ein kleinen Font Generator für das 8x8 LED Matrix Display geschrieben. Damit kann man recht schnell geeignet Bit-Maskern für die unterschiedlichen Ziffern erstellen.

Um genug Platz zwischen den Ziffern zu haben, habe ich mich für eine 8x3 Matrix entscheiden. Man könnte auch mit einer 6x3 Matrix arbeiten, dann müssen wir aber noch ein paar mehr logische Verschiebungen auf Bitebene druchführen, was an dieser Stelle nur dazu führt, dass die Funktion noch schwieriger zu lesen ist. Das Font-File font.c sieht dann in etwa so aus:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <avr/pgmspace.h>

const unsigned char font8_3[10][3] PROGMEM =
{
  { 0x7e, 0x42, 0x7e }, // 0
  { 0x44, 0x7e, 0x40 }, // 1
  { 0x7a, 0x4a, 0x4e }, // 2
  { 0x4a, 0x4a, 0x7e }, // 3
  { 0x0e, 0x08, 0x7f }, // 4
  { 0x4e, 0x4a, 0x7a }, // 5
  { 0x7e, 0x4a, 0x7a }, // 6
  { 0x02, 0x02, 0x7e }, // 7
  { 0x7e, 0x4a, 0x7e }, // 8
  { 0x4e, 0x4a, 0x7e }, // 9
};

Im folgenden möchte ich nur die zwei wichtigsten Funktionen hier erklären. Beginnen wir mir der Hauptschleife:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void loop() {
  unsigned int wait = 5000;

  unsigned int count_down = 20;

  unsigned long time_before, time_after;

  unsigned int red = 0;
  unsigned int green = 0;
  unsigned int blue = 255;

  unsigned int countdown_delay = 50;

  delay(wait);

  for (count_down; count_down > 0; count_down--) {
    time_before = millis();
    DispSlideInt(count_down, red, green, blue, countdown_delay);
    time_after = millis();
    delay( 1000 - (time_after-time_before) );
  }

  delay(wait);
}

An sich ist hier nicht viel zu erklären. In Zeile 4 ist es möglich, den Startwert (in Sekunden) für den Countdown festzulegen. Weiterhin lassen sich Farbwerte für die Ausgabe festlegen.

In Zeile 12 definiert man indirekt die Geschwindigkeit für das wechseln der Zahlen. Eigentlich legt aber der Wert in Millisekunden fest, wie lang gewartet werden soll, bevor die Zahl weiter “rotiert” wird.

Interessant ist sind die Zeilen 16-21. Da in diesem Projekt keine RTC (Echtzeituhr) verwendet wird, und nicht genau klar ist, wie lang das Rotieren der Ziffern benötigt, wird vor dem Aufruf der Funktion DispSlideInt(…) die Zeit genommen und nach dem Aufruf der Funktion. Dann kann daraus berechnet werden, wie lang für eine volle Sekunde zu warten ist. Ich gehe hier von einem idealen System aus, für den diese Operationen (hole die Zeit und Berechne die Differenz) keine Zeit anfällt.

In Zeile 19 wird die Funktion zur Darstellung der Zeit aufgerufen.

Bevor ich den Quellcode durchgehe möchte ich gern noch etwas zur Theorie einbringen. Der erstellte “Font” besteht aus mehreren Zeilen. Für jede Ziffer haben wir eine Zeile mit drei Hexadezimalzahlen definiert. Jede dieser drei Hexadezimalzahlen repräsentiert dabei eine Spalte mit acht Punkten.

In C und C++ gibt es wie in anderen Programmiersprachen die logische Verschiebung die mit dem Operator ‘<<’ bzw ‘>>’ erfolgt. Mit diesen Operationen kann man also die Bits für die einzelnen Spalten verschieben, was sich auf dem LED Matrix Modul so auswirkt, dass die Zahlen nach oben oder unten verschoben werden. Verschiebt man die drei Spalten der Zahl 5 in 8 Schritten um jeweils 1 Bit, wird im Display die 5 langsam (nach oben bzw. unten) herausgeschoben.

Die binärer Repräsentation der Ziffern 5 und 4 sehen wie folgt aus:

1
2
3
const unsigned char font8_3[1][3] PROGMEM = {
  { 0b01001110, 0b01001010, 0b01111010 } // 5
};
1
2
3
const unsigned char font8_3[1][3] PROGMEM = {
  { 0b00001110, 0b00001000, 0b01111110 } // 4
};

Um nun die 5 in eine 4 auf dem Display zu überführen kann man nun die Bitmuster der jeweiligen Spalte für die Ziffer 5 und Ziffer 4 hintereinander hängen und dann nur die ersten 8 Bits betrachten. Im Beispiel für die erste Spalte sieht das dann in etwa so aus:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
  unsigned char five = 0b01001110;
  unsigned char four = 0b00001110;

  unsigned char chr;
  unsigned short bitmuster;

  bitmuster = ( four << 8 | five ); //0000111001001110
  chr = bitmuster; //01001110

  bitmuster = bitmuster >> 1; //0000011100100111
  chr = bitmuster; //00100111

  bitmuster = bitmuster >> 1; //0000001110010011
  chr = bitmuster; //10010011

  bitmuster = bitmuster >> 1; //0000000111001001
  chr = bitmuster; //11001001

  bitmuster = bitmuster >> 1; //0000000011100100
  chr = bitmuster; //11100100

  bitmuster = bitmuster >> 1; //0000000001110010
  chr = bitmuster; //01110010

  bitmuster = bitmuster >> 1; //0000000000111001
  chr = bitmuster; //00111001

  bitmuster = bitmuster >> 1; //0000000000011100
  chr = bitmuster; //00011100

  bitmuster = bitmuster >> 1; //0000000000001110
  chr = bitmuster; //00001110

Die Variable bitmuster ist vom Typ short und kann somit zwei Bytes aufnehmen. Um nun die Variable bitmuster zu setzen gibt es die Möglichkeit, das eine Byte um 8 Bit zu verschieben und dann mit dem logischen ODER die beiden zu verknüpfen.

Durch die darauffolgende Operation wird das Byte mit den niederstwertigen Bits von bitmuster an chr zugewiesen, da chr nur ein Byte aufnehmen kann. Die folgenden Zeilen verschieben die bitmuster jeweils um ein Bit. Die Kommentare zeigen den aktuellen Inhalt der Variablen. Ich hoffe, in diesem Beispiel wird deutlich, wie die Verschiebung der Bits erfolgt, und das wir so unseren gewünschten Effekt erreichen können.

Jetzt möchte ich noch ein paar Dinge zur Funktion DispSlideInt(…) erläutern. Die Funktion habe ein wenig umgestellt, damit die Erklärung anhand der Zeilennummern einfacher wird.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
void DispSlideInt(unsigned int number,unsigned char R,unsigned char G,unsigned char B, unsigned int countdown_delay) {
  unsigned char i,j,Page_Write,temp;
  unsigned char chrtemp[8] = {0};

  int msb = number / 10 % 10;
  int lsb = number % 10;
  int msb_next = (number - 1) / 10 % 10;
  int lsb_next = (number - 1)  % 10;

  unsigned short col0 = pgm_read_byte(&(font8_3[msb_next][0])) << 8 | pgm_read_byte(&(font8_3[msb][0]));
  unsigned short col1 = pgm_read_byte(&(font8_3[msb_next][1])) << 8 | pgm_read_byte(&(font8_3[msb][1]));
  unsigned short col2 = pgm_read_byte(&(font8_3[msb_next][2])) << 8 | pgm_read_byte(&(font8_3[msb][2]));

  unsigned short col5 = pgm_read_byte(&(font8_3[lsb_next][0])) << 8 | pgm_read_byte(&(font8_3[lsb][0]));
  unsigned short col6 = pgm_read_byte(&(font8_3[lsb_next][1])) << 8 | pgm_read_byte(&(font8_3[lsb][1]));
  unsigned short col7 = pgm_read_byte(&(font8_3[lsb_next][2])) << 8 | pgm_read_byte(&(font8_3[lsb][2]));

  chrtemp[3] = 0x00;
  chrtemp[4] = 0x00;

  for (int bias = 0; bias < 9; bias++) {
    if(Page_Index == 0)
      Page_Write = 1;
    if(Page_Index == 1)
      Page_Write = 0;

    if (msb == msb_next) {
      chrtemp[0] = col0;
      chrtemp[1] = col1;
      chrtemp[2] = col2;
    } else {
      chrtemp[0] = ( col0 ) >> bias;
      chrtemp[1] = ( col1 ) >> bias;
      chrtemp[2] = ( col2 ) >> bias;
    }

    chrtemp[5] = ( col5 ) >> bias;
    chrtemp[6] = ( col6 ) >> bias;
    chrtemp[7] = ( col7 ) >> bias;

    for(i = 0;i < 8;i++) {
      temp = chrtemp[i];
      for(j = 0;j < 8;j++) {
        if(temp & 0x80) {
          dots[Page_Write][j][i][0] = R;
          dots[Page_Write][j][i][1] = G;
          dots[Page_Write][j][i][2] = B;
        } else {
          dots[Page_Write][j][i][0] = 0;
          dots[Page_Write][j][i][1] = 0;
          dots[Page_Write][j][i][2] = 0;
        }
        temp = temp << 1;
      }
    }
    Page_Index = Page_Write;

    delay(countdown_delay);
  }
}

In den Zeilen 5,6,7 und 8 wird die übergebene Zahl in einzelne Ziffern zerlegt, so dass diese einzeln ausgeben und rotiert werden können.

In den Zeilen 10-12 und 14-16 werden dann die Spalten für die Ziffern aus dem Font-File übernommen. Ich habe im obigen Beispiel beschrieben, wie das zuweisen von zwei Bytes an ein Short funktioniert. Auch hier wird das eine Byte um 8 Bits verschoben.

In den Zeilen 18 und 19 werden die beiden mittleren Spalten gesetzt.

Im Folgenden betrachten wir die Schleife ab Zeile 21 die 9 mal wiederholt wird. Der erste Durchlauf dieser Schleife zeigt noch einmal den den alten Wert an, die folgenden 8 Durchläufe verschieben dann jeweils die zu verschiebenden Ziffern. Für die 10er Stelle, im folgenden kurz MSB genannt, gibt es eine Fallunterscheidung. Ist die alte Zahl gleich der neuen Zahl, soll hier nicht rotiert werden und die Zeilen 28-30 geben hier die Bytes der drei Spalten aus. Ist diese Stelle der auszugebenden Zahl nicht gleich, weil zur nächsten Dekade gewechselt wird, mit in den Zeilen 32-34 der andere Fall ausgeführt. Hier wird die Bitfolge gleich um mehrere Bits verschoben. Um wieviele Bits, hängt vom Schleifendurchlauf ab. Für die 1er Stelle, im foldenden LSB genannt, muss immer verschoben werden. Für die Ausgabe des LSB werden in den Zeilen 37-39 die drei Spalten dieser Ziffer verschoben. Auch hier hängt die Verschiebung direkt vom Schleifendruchlauf ab.

Die Zeile 41-56 sollen hier nicht näher erläutert werden. Diese sind für die Ausgabe verantwortlich.

In der letzten Zeile dieser Funktion wird noch eine kurze Pause eingelegt, bevor der nächste Schleifendurchlauf erfolgt.

Für das Beispielvideo habe ich die loop-Funktion noch ein wenig aufgepeppt, in dem die letzten drei Zahlen rot ausgegeben werden und am Ende noch kurz die “0 0” fünf mal blinkt.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void loop() {
  unsigned int wait = 100;

  int count_down = 25;

  unsigned long time_before, time_after;

  int red = 0;
  int green = 0;
  int blue = 255;

  delay(50 * wait);

  for (count_down; count_down > 0; count_down--) {
    time_before = millis();
    if (count_down == 4) {
      red = 255;
      blue = 0;
    }
    DispSlideInt(count_down, red, green, blue, 30);
    time_after = millis();
    delay( 1000 - (time_after-time_before) );
  }

  DispBlinkInt(count_down, red, green, blue, 100, 100, 5);

  delay(50 * wait);
}

Und das kleine Video:

Im GitHub-Account findet man das zu diesem Beitrag das dazugehörige Projekt Countdown_8x8_RGB_LED und dem entsprechenden Commit.

Comments