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
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.
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"
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.
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
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.
@ 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;
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_tframebuffer[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_tvsync[][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.
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:
@ 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.
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
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
case0:
3
PORTB=0b00001110;// CBA ----111-
4
break;
5
6
case1:
7
PORTB=0b00000000;// CBA ----000-
8
break;
9
10
case2:
11
PORTB=0b00000010;// CBA ----001-
12
break;
13
14
case3:
15
PORTB=0b00000100;// CBA ----010-
16
break;
17
18
case4:
19
PORTB=0b00000110;// CBA ----011-
20
break;
21
22
case5:
23
PORTB=0b00001000;// CBA ----100-
24
break;
25
26
case6:
27
PORTB=0b00001010;// CBA ----101-
28
break;
29
30
case7:
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.
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.