Forum: Compiler & IDEs Zweidimensionales Array im EEPROM eines ATMEGA168A auslagern (nur lesen)


von Björn T. (bht)


Lesenswert?

Hallo an alle!

Ich bin ja begeistert von dieser Seite und habe mich in den letzten 
Wochen recht gut in die ATMEGA8 bzw. 168 Programmierung eingearbeitet 
(µC.net sei Dank). Jetzt habe ich aber ein kleines oder großes 
Verständnisproblem.

Ich möchte mit Hilfe eines ATMEGA168A RGB-LED-Matrizen (je 16x32x3 LEDs) 
ansteuern. Für eine Matrix kann ich ein Array mit 32x16 Werten als 
Framebuffer im RAM speichern (erst mal nur einfarbig ohne PWM). 
Vergrößern kann ich das Array aber nicht, da mir dann der RAM nicht mehr 
ausreicht.

Wie kann ich nun "einfach" das Array in das EEPROM verbannen? Es sollen 
die Daten auch nur vom Hauptprogramm gelesen werden.

Ich programmiere in C und dem AtmelStudio 6.1 (mit DIAMEX USB 
ISP-Programmer).

Den Abschnitt unter 
"http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Programmspeicher_.28Flash.29"; 
kann ich nicht ganz nachvollziehen.

Habe daraus aber folgendes eingebunden:
const uint8_t framebuffer[][32*N] PROGMEM = {{ 0,1,0,1,... },{ 
0,1,0,1,... },usw...};

N ist via #define als 2 festgelegt...
Damit kann ich das Array jetzt zwar speichern (keine RAM Overflow 
Meldung mehr) aber ich weiß nicht so recht wie ich die Daten wieder 
auslesen kann?

Die LED-Matrizen zeigen jedenfalls nichts definiertes mehr an...

1.) Wie kann ich den Speicherplatzbedarf des Programms abfragen?
2.) Wo wurde jetzt was gespeichert - Kontrolle.
3.) Was muss ich beim Auslesen des Arrays beachten? - extern etc. ???
4.) Gibt es event. eine Möglichkeit die "0" und "1" Werte platzsparender 
als mit uint8_t zu speichern?

Vielen Dank schon mal für eure Hilfe!
bht

von Laurenz K. (gimmebottles)


Lesenswert?

Du schreibst EEPROM, wenn ich aber PROGMEM lese denke ich eher dass du 
den Flash meinst. Das macht auch mehr Sinn, weil das EEPROM nur eine 
verhältnismäßig kleine Anzahl von Lese-/Schreibvorgängen übersteht.

Mit "const ... PROGMEM" sagst du dem Linker, dass er die Variable bzw. 
Array im Programmspeicher, also dem Flash ablegen soll. Dieser ist nur 
lesbar während der Programmausführung, deswegen auch das const.

Zu deinen Fragen:

1.) Führe mal "avr-size <deinprogram>.elf aus. Das gibt dir die Größen 
der verschiedenen Sections an. text ist Programmcode, also im Flash. 
data und bss kommen ins RAM.

2.) avr-nm <deinprogram>.elf gibt dir die Speicheradressen an, an denen 
deine Variablen und Funktionen landen. Ein Blick in die Datenblätter 
sagt dir bei welchen Adressen RAM und FLash liegen.

3.) Auf ein Array im PROGMEM kannst du nicht einfach mit array[n] 
zugreigen, sondern musst einen Umweg über pgm_read_byte(...) gehen. 
Statt "a = array[n]" heißt es dann "a = pgm_read_byte(&array[n])".

4.) Ja, den gibt es :D Wenn du nur 0en und 1en brauchst, kannst du ja 8 
davon in ein einziges byte packen. Am besten schreibst du dir zwei 
Funktionen, mit denen du die einzelnen Bits komfortabel lesen und 
schreiben kannst. Stichwort dafür sind Bitoperationen.

von Mike (Gast)


Lesenswert?

Björn T. schrieb:
> 4.) Gibt es event. eine Möglichkeit die "0" und "1" Werte platzsparender
> als mit uint8_t zu speichern?

Guck mal in deinem C-Grundlagen Buch nach "bit field"

von Lukas (Gast)


Lesenswert?

Bitfelder sind oft (hier auch) nicht der gewünschte Ansatz.

Der TO bräuchte eher ein Bit-Array. Da es sowas in C nicht gibt muss man 
es sich über Bitmanipulation und Makros nachbauen.

von Björn T. (bht)


Lesenswert?

Danke, Punkt (3) -> Erfolg!!! :-)

Der Aufruf des Arrays hat jetzt mit:
1
    if( pgm_read_byte(&image[zeilen_count][spalten_count] ) == 1)
2
    PORTD |= (1<<RT1);
3
    else
4
    PORTD &= ~(1<<RT1);

funktioniert! Allerdings bekomme ich bereits mit zwei LED-Matrizen ein 
Geschwindigkeitsproblem, die Bildwiederholrate würde dann auf 48,5 Hz 
sinken. Unschön für die Augen, dabei läuft der ATMEGA168 schon 
übertaktet mit einem 22MHz Quarz. Die Sache mit dem Unterprogramm (z.B. 
Binärwandlung) würde das CLK-Signal weiter verlangsamen...

Laurenz K. schrieb:
> 1.) Führe mal "avr-size <deinprogram>.elf aus. Das gibt dir die Größen
> der verschiedenen Sections an. text ist Programmcode, also im Flash.
> data und bss kommen ins RAM.
>
> 2.) avr-nm <deinprogram>.elf gibt dir die Speicheradressen an, an denen
> deine Variablen und Funktionen landen. Ein Blick in die Datenblätter
> sagt dir bei welchen Adressen RAM und FLash liegen.

Wie kann ich die o.g. Befehle mit dem Atmel Studio 6.1 ausführen?
Ich wechsel unten im Programm auf den Reiter "Command Window", dann 
kommt folgendes:
1
>avr-size ATMEGA168A_RGBLED.elf
2
Command "avr" is not valid.
1
>avr-data ATMEGA168A_RGBLED.elf
2
Command "avr" is not valid.
1
>avr-text ATMEGA168A_RGBLED.elf
2
Command "avr" is not valid.

Stimmt, PROGMEM klingt nicht nach EEPROM sondern (sinnigerweise) nach 
Programmspeicher (also FLASH?!), aber da steht doch auch das 
Hauptprogramm drin. Warum kann ich das große Array dann jetzt 
kompilieren und vorher ohne  PROGMEM nicht?

Also beim 168A ist jetzt:
- der Flashspeicher 16 kByte,
- das EEPROM 512 Byte groß

Bestimmt blicke ich da etwas besser durch, wenn ich die o.g. Befehle 
aufrufen kann!

LG
bht

von Laurenz K. (gimmebottles)


Lesenswert?

Dass du Atmel Studio verwendest habe ich glatt überlesen, ich dachte im 
GCC Unterforum direkt an den GCC, deswegen werden dir die Befehle nicht 
viel helfen. Mit AtmelStudio habe ich nie gearbeitet.

Björn T. schrieb:
> Stimmt, PROGMEM klingt nicht nach EEPROM sondern (sinnigerweise) nach
> Programmspeicher (also FLASH?!), aber da steht doch auch das
> Hauptprogramm drin. Warum kann ich das große Array dann jetzt
> kompilieren und vorher ohne  PROGMEM nicht?

Alle Daten, deren Wert du schon im Code vorgibst, müssen ja in den 
Flash, da das RAM bei STromverlust ja geleert wird. Wenn du also 
"uint8_t array[] = {1,1,1,0,0,1,0};" definierst, landen diese Daten auch 
im FLash, werden aber ganz zu beginn des Programms ins RAM kopiert, der 
Mikrocontroller greift dann nur noch aufs RAM zu.
Mit PROGMEM bleiben die Daten im Flash und werden direkt daraus gelesen. 
Hat dann den Nachteil dass die "Read-Only" sind.
Dein Programm lässt sich mit PROGMEM kompilieren, weil es jetzt viel 
weniger RAM braucht als ohne PROGMEM.

von Falk B. (falk)


Lesenswert?

@ Björn T. (bht)

>Der Aufruf des Arrays hat jetzt mit:

>    if( pgm_read_byte(&image[zeilen_count][spalten_count] ) == 1)
>    PORTD |= (1<<RT1);
>    else
>    PORTD &= ~(1<<RT1);

Naja, besser als nix.

>funktioniert! Allerdings bekomme ich bereits mit zwei LED-Matrizen ein
>Geschwindigkeitsproblem, die Bildwiederholrate würde dann auf 48,5 Hz
>sinken. Unschön für die Augen, dabei läuft der ATMEGA168 schon
>übertaktet mit einem 22MHz Quarz. Die Sache mit dem Unterprogramm (z.B.
>Binärwandlung) würde das CLK-Signal weiter verlangsamen...

Das Zauberwort lautet Bitmanipulation.

Und für eine 32x16 RGB Matrix braucht man nur 32x16x3 Bit = 1536 BIT = 
192 Byte. Die hat dein AVR locker als RAM. Und wenn man es sinnvoll 
strukturiert, ist sowohl der Zugriff schnell als auch platzsparend.

uint16_t frambuffer[32][3];

Erster Index ist die Spalte, zweiter die Farbe. Pro Eintrag werden 16 
Bit einer Zeile gespeichert. Der Zugriff erfolgt dann einmalig und kann 
direkt auf einen Port oder ins SPI kopiert werden.

PORTD = framebuffer[zeile][farbe];
PORTC = framebuffer[zeile][farbe]>>8;

von Björn T. (bht)


Lesenswert?

Ah ok, kaum lässt man dem Kompilerfenster etwas mehr Platz, sieht man 
auch was!
1
        Program Memory Usage   :  1064 bytes   13,0 % Full
2
        Data Memory Usage     :  26 bytes   2,5 % Full

Ja, Bitmanipulationen sind eine feine Sache, ich weiß aber nicht wie ich 
diese hier sinnvoll einsetzen kann. Ach sorry, zu erwähnen ist, dass auf 
den LED-Panels Schieberegister sind, die Takt für Takt die Werte von 
rot1 und 2, grün 1 und 2 sowie blau 1 und 2 erwarten also seriell, nicht 
parallel. Mit Output Enable wird dann nach N x 32 die komplette Zeile 
geschrieben.
1
uint16_t framebuffer[32][3];

also das hier ja, könnte (irgendwie) funktionieren (für 1xZeile, 
3xFarbe)...

aber
1
PORTD = framebuffer[zeile][farbe];
2
PORTC = framebuffer[zeile][farbe]>>8;

nicht, da die Ausgabe so parallel wäre...

Trotzdem denke ich, dass die ganzen anderen Steuerbefehle mit if-else 
Abfragen noch viel Optimierungspotenzial bieten, ich weiß im Moment aber 
leider noch nicht wie! :-/

Hier das Elend mit der Zeilenauswahl... Ganz nebenbei, wäre case hier 
eigentlich schneller in der Abarbeitung?
1
// Signale für Zeilenauswahl (A,B,C-Signale)
2
uint8_t vsync[][3]={  {1,1,1},
3
            {0,0,0},
4
            {1,0,0},
5
            {0,1,0},
6
            {1,1,0},
7
            {0,0,1},
8
            {1,0,1},
9
            {0,1,1}
10
            };
11
/*
12
...
13
*/
14
    // Zeilenauswahl
15
    if(vsync[zeilen_count][SIGNAL_A] == 1)
16
    PORTB |= (1<<A);
17
    else
18
    PORTB &= ~(1<<A);
19
    
20
    if(vsync[zeilen_count][SIGNAL_B] == 1)
21
    PORTB |= (1<<B);
22
    else
23
    PORTB &= ~(1<<B);
24
    
25
    if(vsync[zeilen_count][SIGNAL_C] == 1)
26
    PORTB |= (1<<C);
27
    else
28
    PORTB &= ~(1<<C);

Gibt es eine Möglichkeit "1" bzw "0" direkt auf die jeweiligen Portpins 
zu legen. PORTB |= (1<<A); z.B. ist ja Bitmanipulation, nur eben etwas 
umständlich finde ich.

von Laurenz K. (gimmebottles)


Lesenswert?

Björn T. schrieb:
> Gibt es eine Möglichkeit "1" bzw "0" direkt auf die jeweiligen Portpins
> zu legen. PORTB |= (1<<A); z.B. ist ja Bitmanipulation, nur eben etwas
> umständlich finde ich.

Nein, du kannst ein Register immer nur im ganzen schreiben. Du könntest 
aber alles erstmal in einen zwischenspeicher schreiben, und erst am Ende 
diesen Speicher ins Register schreiben, also so:
1
    uint8_t buf;
2
    buf = PORTB;
3
4
    if(vsync[zeilen_count][SIGNAL_A] == 1)
5
    buf |= (1<<A);
6
    else
7
    buf &= ~(1<<A);
8
    
9
    if(vsync[zeilen_count][SIGNAL_B] == 1)
10
    buf |= (1<<B);
11
    else
12
    buf &= ~(1<<B);
13
    
14
    if(vsync[zeilen_count][SIGNAL_C] == 1)
15
    buf |= (1<<C);
16
    else
17
    buf &= ~(1<<C);
18
19
    PORTB = buf;

Das dürfte deinen Code ein wenig beschleunigen.

von Falk B. (falk)


Lesenswert?

@ Björn T. (bht)

>Ja, Bitmanipulationen sind eine feine Sache, ich weiß aber nicht wie ich
>diese hier sinnvoll einsetzen kann.

Bitte? Du must den Wert von Bits abfragen und auf IOI-Port kopieren, 
ausserdem an einem anderen IO-Port einen Takt erzeugen. Verdammt viele 
Bitmanipulationen!

> Ach sorry, zu erwähnen ist, dass auf
>den LED-Panels Schieberegister sind, die Takt für Takt die Werte von
>rot1 und 2, grün 1 und 2 sowie blau 1 und 2 erwarten also seriell, nicht
>parallel. Mit Output Enable wird dann nach N x 32 die komplette Zeile
>geschrieben.

Nennt sich Soft-SPI.

>Trotzdem denke ich, dass die ganzen anderen Steuerbefehle mit if-else
>Abfragen noch viel Optimierungspotenzial bieten, ich weiß im Moment aber
>leider noch nicht wie! :-/

Das ist wohl so.

>Hier das Elend mit der Zeilenauswahl... Ganz nebenbei, wäre case hier
>eigentlich schneller in der Abarbeitung?

>// Signale für Zeilenauswahl (A,B,C-Signale)

Was soll das sein? Im uC gibt es keine Signale, nur Variablen.
Und schau dir endlich mal Bitmanipulation an, das ist ja grausam!

>Gibt es eine Möglichkeit "1" bzw "0" direkt auf die jeweiligen Portpins
>zu legen. PORTB |= (1<<A); z.B. ist ja Bitmanipulation, nur eben etwas
>umständlich finde ich.

Ist aber der normale Weg. Da kommt am Ende EIN Assemblerbefehl raus, der 
gerade mal 2 Takte braucht. sbi/cbi.

von Oliver (Gast)


Lesenswert?

Laurenz K. schrieb:
> Du könntest
> aber alles erstmal in einen zwischenspeicher schreiben, und erst am Ende
> diesen Speicher ins Register schreiben, also so:
> ...
> Das dürfte deinen Code ein wenig beschleunigen.

Könnte es nicht, und macht es nicht. Die direkten Portzugriffe dauern 
alle nur einen Taktzyklus, schneller geht es einfach nicht.

Oliver

von Björn T. (bht)


Lesenswert?

@ Laurenz K:

Dein Vorschlag mit:
1
    uint8_t buf;
2
    buf = PORTB;
3
4
    if(vsync[zeilen_count][SIGNAL_A] == 1)
5
    buf |= (1<<A);
6
    else
7
    buf &= ~(1<<A);
8
    
9
    if(vsync[zeilen_count][SIGNAL_B] == 1)
10
    buf |= (1<<B);
11
    else
12
    buf &= ~(1<<B);
13
    
14
    if(vsync[zeilen_count][SIGNAL_C] == 1)
15
    buf |= (1<<C);
16
    else
17
    buf &= ~(1<<C);
18
19
    PORTB = buf;

hat so nicht funktioniert, habe die Zeilenauswahl jetzt wie folgt 
umgeschrieben und dieses Zeilen-Array ganz rausgeworfen. Jetzt werden 
gleich die drei richtigen Bit-Kombinationen auf den Port geschrieben. 
Die Abarbeitung erfolgt wesentlich schneller!
1
    switch(zeilen_count) {
2
      case 0:
3
      PORTB = 0b00001110;    // CBA ----111-
4
      break;
5
      
6
      case 1:
7
      PORTB = 0b00000000;    // CBA ----000-
8
      break;
9
      
10
      case 2:
11
      PORTB = 0b00000010;    // CBA ----001-
12
      break;
13
      
14
      case 3:
15
      PORTB = 0b00000100;    // CBA ----010-
16
      break;
17
      
18
      case 4:
19
      PORTB = 0b00000110;    // CBA ----011-
20
      break;
21
      
22
      case 5:
23
      PORTB = 0b00001000;    // CBA ----100-
24
      break;
25
      
26
      case 6:
27
      PORTB = 0b00001010;    // CBA ----101-
28
      break;
29
      
30
      case 7:
31
      PORTB = 0b00001100;    // CBA ----110-
32
      break;
33
    }

@ Falk Brunner

Falk Brunner schrieb:
> Was soll das sein? Im uC gibt es keine Signale, nur Variablen.
> Und schau dir endlich mal Bitmanipulation an, das ist ja grausam!

Alles was der µC an den Ports rausgibt um andere HW zu steuern sind für 
mich Signale, dann kann man die doch auch ruhig als solche bezeichnen?!

Wie könnte denn deiner Meinung nach eine bessere Lösung mit 
Bitmanipulation aussehen? Konnte dein Statement von oben noch nicht ganz 
nachvollziehen bzw. umsetzen.

von Karl H. (kbuchegg)


Lesenswert?

Björn T. schrieb:

> Jetzt werden
> gleich die drei richtigen Bit-Kombinationen auf den Port geschrieben.
> Die Abarbeitung erfolgt wesentlich schneller!

Ja.
Solange, bis du die restlichen 5 Bits am Port B ebenfalls für etwas 
benötigst. Dann ist die Kacke am dampfen.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.