Hallo Community,
Ich bin recht neu mit Microcontrollern und hab mal eine Frage an die
Cracks.
Ich hab eine alte LED-Matrixanzeige die ich mit einem ATMega644
ansteuern möchte. Die Anzeige hat ein schieberegister von 120Bit (DATA
und CLK) und 7 Eingänge um die entsprechende Zeile zu aktivieren.
Meine Funktion funktioniert, ist aber sehr rechenintensiv um sie durch
einen Interupt zu starten. Die Funktion muss um eine anzeige zu bekommen
durchgehend ausgeführt werden.
Meine Frage ist nun: Kann man die Funktion so optimieren, das es per
Interupt ausführ bar wird ?
hier der Code:
Hi,
Ach so, vergessen !? .. Das Board zu beschreiben.
Ich nutze derzeit als Basis das Board von Ulrich Radig ETH_M32_EX
Die UART ist mit der RS232 belegt..
Ich hab an dem ATMega644 die PORTs C und D zu Verfügung da diese schön
auf einem Connector geführt sind.
An PC0 - PC6 hängen die Zeilen , an PD2 - CLK, PD3 - DATA.
Joerg
@jogy (Gast)
>Meine Funktion funktioniert, ist aber sehr rechenintensiv um sie durch>einen Interupt zu starten.
Wer sagt das? So schlecht ist dein Code nicht. Hast du mal simuliert,
wie lange der für eine Zeile braucht? Was du machst ist Soft-SPI, und
das ist auf einem AVR schon eingermassen schnell.
> Die Funktion muss um eine anzeige zu bekommen> durchgehend ausgeführt werden.
Klar, siehe LED-Matrix. Dazu nutzt man aber einen Timer.
>Meine Frage ist nun: Kann man die Funktion so optimieren, das es per>Interupt ausführ bar wird ?
Ist sie schon. Aber man kann eine handvoll Sachen noch besser machen.
>// Clk-Impuls für Schieberegister erzeugen>void mtx_clk_impuls()
Hier sollte man die Funktion direkt in den Code schreiben, damit spart
man sich die Zeit für den Funktionsaufruf. Oder die Funktion mit INLINE
deklarieren, dann macht das der Compiler.
>void mtx_send_data_p() {> uint8_t i1; // Pointer auf Zeile> uint8_t i2; // Pointer auf Byte pro Zeile (Rückwärts zähler)> uint8_t i3; // Pointer auf Bit> uint8_t i4; // Pointer auf erstes Byte der Zeile> char c1; // Byte zu bearbeiten
Deine Variablennamen sind nichtssagend. Heutzutage nimmt man
aussagekräftige Namen, siehe
http://www.mikrocontroller.net/articles/Strukturierte_Programmierung_auf_Mikrocontrollern#Formatierung_des_Quelltextes
Ausserdem sind das keine Pointer sondern einfache Zähler.
> i4 = 0; // mit der ersten Zeile beginnen> c1 = matrix_buffer[(i4+(i2-1))]; // Byte aus dem Buffer holen
Kann man einfacher machen.
c1 = matrix_buffer[i4]; // Byte aus dem Buffer holen
i4--;
>mtx_steuer_port |= (1<<mtx_data);// wenn Bit0=1 dann Data-Leitung auf 1
Hier könnte viel Rechenzeit sinnlos verbraten werden, wenn mtx_data eine
Variable ist. Das sollte ein define sein, denn dann kann es der Compiler
bei eingeschalteter Optimierung berechnen und statt einer echten
Schiebeoperation zur Laufzeit eine Konstante einsetzen. DAS ist wichtig!
> Meine Funktion funktioniert, ist aber sehr rechenintensiv um sie> durch einen Interupt zu starten.
Ähm. die Funktion bleibt aber konzeptionell sowieso nicht so, wie sie
jetzt ist.
Deine jetzige Funktion macht einen Durchgang durch die komplette Matrix.
Und genau das ist nicht das was passieren soll/muss/darf, wenn du
interruptgetrieben multiplext.
Ein
1
for( i = 0; i < 8; i++ )
2
{
3
Gib Zeile i aus
4
5
_delay_us( 2200 );
6
}
ersetzt sich im Interrupt Fall durch
1
ISR( ... )
2
{
3
static uint8_t i;
4
5
i++;
6
if( i == 8 )
7
i = 0;
8
9
Gib Zeile i aus
10
}
Bei jédem USR Aufruf wird reihum die nächste Zeile ausgegeben. Die
notwendige Wartezeit wird dadurch realisiert, dass die ISR fertig ist
und anderem Code die Chance gibt auch was zu tun. Während der µC NICHT
in der ISR ist, leuchten die LED einer Zeile. Und es braucht dann
logischerweise 8 ISR Aufrufe, bis alle 8 Zeilen der Matrix einmal
geleuchtet haben.
und so schlecht ist dann dein Originalcode für "Gib Zeile i aus" dann
auch wieder nicht, dass der jetzt massiv zuschlagen würde. (Falk hat ja
schon ein paar Änderungen vorgeschlagen)
Deine Hauptänderung ist der Ersatz von _delay_us durch eine vernünftige
Interrupt Steuerung!
Das ist zunächst mal deine Hauptänderung! Und wenn das dann immer noch
zu langsam ist (oder du ein wenig ISR Zeit sparen willst), dann
optimierst du den Rest.
Hallo,
um im embedded Bereich die Funktionen zu optimieren, sollte man das
generierte Assembler Listing anschauen und einigermaßen verstehen
können. So wie es der Falk schon gesagt hat kann man da dann einiges
Erkennen und optimieren.
Als Anfänger, der sowieso mit „C“ arbeiten will, ist es eventuell
einfacher mit einem 32 Bit Kontroller zu programmieren. Dort stößt man
generell nicht so schnell an Ressourcen – Grenzen, durch die man erst
optimieren muss.
Mfg
DerDan
Erstmal : Wow ...
Danke an alle für die vielen Hinweise. Mit soviel Feedback hatte ich
jetzt nicht gerechnet.
Nochmal kurz zu mir, ich bin absolut neu in sachen Microcontroller. Ich
habe in den 80'ern ein wenig mit Z80 und 65xx Prozessoren gearbeitet
(Lehre), habe Programierkenntnisse in C und Assembler, wobei ich sehr
sehr lange nichts mehr gemacht habe.
Mein Favorit von den Antworten ist, per Interupt nur eine Zeile anzeigen
zu lassen.
Ich werde alle Vorschläge mal Testen und mein Ergebnis hier natürlich
Posten (bzw. weiter Fragen ..)
MfG
Joerg
> Meine Frage ist nun: Kann man die Funktion so optimieren,> das es per Interupt ausführ bar wird ?
Natürlich, der delay muss da raus, ein delay in einer Interrupt-Funktion
ist wahnsinnig.
Der Interrupt muss also 7 mal so schnell kommen.
Dann zeigt die Funktion jeweils eine Zeile an
a) uint8_t i1; // Pointer auf Zeile
b) for(i1=0;i1<7;i1++) { // Schleife 7 Zeilen
c) _delay_us(2200);
ersetze durch
a) static uint8_t i1=0; // Pointer auf Zeile
b) {
c) i1=(i1+1)%7;
So,
Ich hab mir jetzt den Optimierten Code von Falk Brunner (Danke nochmal)
genommen und mit der Idee von Karl Heinz Buchegger (ebenfalls danke)
kombiniert.
Also ich zeige pro Interupauslösung nur eine Zeile an, das "Delay" hab
ich durch die vorbelgung des Timer Counters (nutze Timer0) gelöst.
Bestimmt kann man einiges im Code noch eleganter lösen, aber so klappt
es erstmal. Zusätzlich kann ich noch die Helligkeit regeln in dem ich
später den Timercounter variabel ändere (hoffe ich zumindest).
Folgendes ist dabei herausgekommen (diesmal mit den define's, ich hoffe
ich hab keine vergessen):
1
// Pinbelegung für die Matrix, an verwendete Pins anpassen
2
3
#define mtx_zeilen_port PORTC // Port für Matrix Zeilen Ansteuerung
4
#define mtx_zeilen_ddr DDRC // Port-Richtung
5
#define mtx_z1 PC0
6
#define mtx_z2 PC1
7
#define mtx_z3 PC2
8
#define mtx_z4 PC3
9
#define mtx_z5 PC4
10
#define mtx_z6 PC5
11
#define mtx_z7 PC6
12
#define mtx_steuer_port PORTD // Port für Steuerung
13
#define mtx_steuer_ddr DDRD // Port-Richtung
14
#define mtx_clk PD2 // Clk-Leitung an Schieberegister 74HCT164
15
#define mtx_data PD3 // Data-Leitung an Schieberegister
@ jogy (Gast)
>ich durch die vorbelgung des Timer Counters (nutze Timer0) gelöst.
Bestimmt kann man einiges im Code noch eleganter lösen, aber so klappt
>es erstmal. Zusätzlich kann ich noch die Helligkeit regeln in dem ich>später den Timercounter variabel ändere (hoffe ich zumindest).
Das klingt nach einem Bug. Denn die Helligkeit der Anzeige ist von der
Multiplexfrequenz in erster Linie UNABHÄNGIG! Wenn es dennoch eine
Abhängikeit gitb, deutet das auf langsame Treiber oder so hin.
>Folgendes ist dabei herausgekommen (diesmal mit den define's, ich hoffe>ich hab keine vergessen):
Defines schreibt man meisten komplett groß, um sie als solche zu
erkennen. Ist eine allgemeine Vereinbarung.
> #define mtx_z1 PC0> #define mtx_z2 PC1> #define mtx_z3 PC2> #define mtx_z4 PC3> #define mtx_z5 PC4> #define mtx_z6 PC5> #define mtx_z7 PC6
Ist im Code gar nicht genutzt, wozu also die defines? Das sind reine
Textersetzungen, welche aber den Code deutlich lesbarer und leichter
änderbar machen.
> #define TCCR_MTX TCCR0B // Timer0> #define TIMSK_MTX TIMSK0 // Timer0
Sowas ist unüblich.
> #define MTX_COUNTER 0x7F // Timer Counter vorbelegung
Für sowas gibt es den CTC Modus. Ausserdem sind hier Dezimalzahlen
deutlich besser verständlich.
>ISR (TIMER0_OVF_vect) {> static uint8_t zeile;> static uint8_t index;
OK.
> static uint8_t cnt_byte;> static uint8_t cnt_bit;> static uint8_t data; // Byte zu bearbeiten
Das static ist überflüssig und ggf. kontraproduktiv. Diese Variablen
werden bei jedem Aufruf neu initialisiert, die muss man sich
zwischendurch nicht merken.
> if( !index ) { // Variable unbelegt, dann mit 14 Vorbelegen
Variablen sind nicht unbelegt, sie haben immer einen Wert.
> index = 14;> }
Diese Steuerung machst du besser über die Variable Zeile, der Rest
ergibt sich daraus.
> if( !zeile) { // Variable unbelegt, dann mit 0 vorbelegen> zeile = 0;> }> if( zeile == 8 ) { // Zähler steht auf 8.te Zeile, dann Zähler auf 0> zeile = 0;> }
Eben hier kann man auch index rücksetzen.
> TCNT0 = MTX_COUNTER; // Counter vorbelegung vom Timer neu setzen.
CTC Modus ist hier einfacher und zeitgemäß.
Geht aber vorerst auch so.
Hast du mal simuliert, wie lange deine alte und deine neue Routine
brauchen?
MFG
Falk
if(!zeile){// Variable unbelegt, dann mit 0 vorbelegen
6
zeile=0;
7
}
8
if(zeile==8){// Zähler steht auf 8.te Zeile, dann Zähler auf 0
9
zeile=0;
10
}
11
12
...
13
14
zeile++;// nächste Zeile für Anzeige
15
16
...
17
}
kannst du auch DEUTLICH einfacher schreiben.
Variablen sind nie 'unbelegt'. Sie haben IMMER irgendeinen Wert, denn
die Bits sind ja da und sind entweder 0 oder 1. Die erscheinen ja nicht
einfach irgendwie magisch im µC wenn du das erste mal einen Wert
zuweist.
Was du geschrieben hast, ist eine Variante von
if( zeile == 0 )
zeile = 0;
und das ist augenscheinlich ziemlich unsinnig. Es bewirkt zwar nichts,
es macht aber auch nichts Sinnvolles.
Und auch static Variablen kann man initialisieren, so wie alles andere
auch.
1
staticuint8_tzeile=0;
2
3
....
4
5
6
...
7
8
zeile++;// nächste Zeile für Anzeige
9
if(zeile==8)
10
zeile=0;
11
...
12
}
dann hast du das korrekte Hochzählen mit 'Überlaufbehandlung' bei 8 an
einer Stelle beisammen. Und die Initialisierung sorgt dafür, dass
'zeile', wenn es zur Welt kommt, auf jeden Fall eine 0 enthält.
Nachdem du den Clock Impuls abgesetzt hast (den Pin also wieder auf 0
hast), macht ja der Aufrufer noch Zeugs. D.h. du brauchst du nicht mehr
zuwarten.
Danke für die Hinweise auf meinen "Code" , naja, bitte um nachsicht ,
ich lerne noch..
Falk Brunner schrieb:> Das klingt nach einem Bug. Denn die Helligkeit der Anzeige ist von der> Multiplexfrequenz in erster Linie UNABHÄNGIG! Wenn es dennoch eine> Abhängikeit gitb, deutet das auf langsame Treiber oder so hin.>>ich durch die vorbelgung des Timer Counters (nutze Timer0) gelöst.>>Bestimmt kann man einiges im Code noch eleganter lösen, aber so klappt>>>es erstmal. Zusätzlich kann ich noch die Helligkeit regeln in dem ich>>später den Timercounter variabel ändere (hoffe ich zumindest).>> Das klingt nach einem Bug. Denn die Helligkeit der Anzeige ist von der> Multiplexfrequenz in erster Linie UNABHÄNGIG! Wenn es dennoch eine> Abhängikeit gitb, deutet das auf langsame Treiber oder so hin.
Bug, ja eher nicht.. Denn folgendes Passiert doch (jetzt):
Zeile abschalten (LED'aus). 120Bits in das Display schieben, Zeile
einschalten (LED's an). Nun mit Eurer hilfe Interuptgesteuert.
Die Zeile in der Anzeige wird also nur während der Pausen zwischen den
Interuptauslösungen angezeigt.
Demnach , je schneller die Interruptreoutine aufgerufen wird, haben die
LED's der Anzeige weniger Zeit zu ihre vollen Leuchtstärke zu finden.
Die LED-Matrix die ich hier habe ist aus den 90'er Jahren und ich will
die alte Prozessorplatine zu der es keinerlei doku gibt, ersetzen.
Das habe ich durch Zufall gefunden und hat mir das Reverse Engeneering
erspart :
http://circuitcellar.com/contests/nxpmbeddesignchallenge/winners/DEs/Abstract_3835-Kalk_DE.pdf
Die Verbesserungsvorschläge , auch mit der GROß und kleinschreibung,
werde ich noch ändern. Das mit den unbelegten Variablen, kommt
wahrscheinlich aus meiner Windows VB ecke im Kopf...
Und zum Counter: über den CTC-Modus bin ich auch gestolpert. Wußte aber
nicht wie ich den aktiviere. Werde ich mal ergoogeln.
Grüsse
jogy schrieb:> Demnach , je schneller die Interruptreoutine aufgerufen wird, haben die> LED's der Anzeige weniger Zeit zu ihre vollen Leuchtstärke zu finden.
Ähm.
Das ist ein kleiner Irrtum.
Du schaltest die LED ein. Die brennen sofort mit voller Helligkeit.
Wenn du die Helligkeit variieren willst, dann musst du das Verhältnis
von Ein zu Aus variieren.
Aber nur weil du schneller blinkst (und was anderes ist Multiplexen
nicht) werden LED nicht heller.
@ jogy (Gast)
>Zeile abschalten (LED'aus). 120Bits in das Display schieben, Zeile>einschalten (LED's an). Nun mit Eurer hilfe Interuptgesteuert.
Jo.
>Die Zeile in der Anzeige wird also nur während der Pausen zwischen den>Interuptauslösungen angezeigt.
Das ist die allermeiste Zeit. Simulier mal wie lange dein ISR dauert und
dann denk mal nach wie oft sie aufgerufen wird. Siehe LED-Matrix,
dort ist das Thema Multiplexing noch mal beschrieben.
>Demnach , je schneller die Interruptreoutine aufgerufen wird, haben die>LED's der Anzeige weniger Zeit zu ihre vollen Leuchtstärke zu finden.
Alles relativ, nicht erst seit Einstein. EINE Zeile wird immer 1/7 der
Zeit eines vollen Durchlaufs für alle Zeilen angezeigt. Ob das nunr
7x1ms oder 7x10ms ist, ist erstmal egal.
>Und zum Counter: über den CTC-Modus bin ich auch gestolpert. Wußte aber>nicht wie ich den aktiviere. Werde ich mal ergoogeln.
Es reicht, das Datenblatt des AVRs zu lesen. Oder das hier.
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial/Die_Timer_und_Z%C3%A4hler_des_AVR