Hallo,
Ich mache gerade meine ersten Schritte in der C Programmierung eines
ATMEL 2560 (Arduino Mega 2560 Board).
Das ganze mit GCC und dem Atmel Studio.
Hier zu meinem Problem:
Ich habe ein Array mit 8 bit Werten.
Wie kann ich den Inhalt des arrays in einen Ausgangsport schreiben?
Hier mein Ansatz:
An der Stelle geht es nicht schneller.
Die Frage wäre, ob du das Char-Array überhaupt brauchst, oder ob es
nicht möglich ist, gleich einen uint8_t zu verwenden.
Oliver F. schrieb:> geht das irgendwie besser/schneller.
Du kannst zumindest erstmal alle Bits sammeln und dann am Stück an den
Port schreiben, statt jedes einzeln. Das ist schneller und hat den
Vorteil, daß alle Bits gleichzeitig anliegen und nicht Stück für Stück
eintrudeln. Außerdem sind variable Shiftweiten auf dem AVR ineffizient.
Die kann man hier vermeiden.
Wenn du, wie du sagst, die Schleife auf jeden Fall brauchst, würde ich
diese ungefähr so machen:
1
uint8_ttmp=0;
2
for(voice=0;voice<8;voice++)
3
{
4
tmp>>=1;
5
if(gates[voice]==HIGH)
6
tmp|=(1<<7);
7
}
8
PORTB=tmp;
Da werden die Bits eins nach dem anderen in die tmp-Variable geschoben
und dann am Schluß alles ausgegeben.
Wenn es wirklich auf die Geschwindigkeit ankommt, würde ich die
symbolischen Konstanten Low und High durch 0 und 1 ersetzen und
folgenden Code verwenden:
1
unsignedcharvoice,byte;
2
3
for(voice=8;voice;voice--)
4
{
5
byte<<=1;
6
byte|=gates[voice-1];
7
}
8
PORTB=byte;
Das liefert zumindest mit dem GCC 4.8.2 sehr kompakten und schnellen
Assemblercode.
Nicht ganz so kompakt, dafür aber etwa dreimal so schnell wird die
Sache, wenn man die Schleife abwicklt:
1
unsignedcharbyte=0;
2
3
byte|=gates[7];
4
byte<<=1;
5
byte|=gates[6];
6
byte<<=1;
7
byte|=gates[5];
8
byte<<=1;
9
byte|=gates[4];
10
byte<<=1;
11
byte|=gates[3];
12
byte<<=1;
13
byte|=gates[2];
14
byte<<=1;
15
byte|=gates[1];
16
byte<<=1;
17
byte|=gates[0];
18
19
PORTB=byte;
Den gleichen Code generiert der Compiler auch mit der Option -O3, nur
dass er dann überall die Geschwindigkeit auf Kosten der Programmgröße
optimiert, auch dort, wo man das nicht möchte.
Spiel doch einfach ein wenig mit den unterschiedlichen Varianten herum
und schau dir jeweils den generierten Assemblercode und/oder die im
Simulator angezeigten Taktzyklen an. Dann bekommst du bald ein gewisses
Gespür dafür, mit welchen Konstrukten der Compiler bzw. der AVR gut
umgehen kann, und mit welchen weniger gut.
Einen wichtigen Punkt hat Rolf schon genannt: Da der AVR im Gegensatz zu
den meisten "größeren" Prozessoren keinen Barrel-Shifter hat, sollte man
Ausdrücke der Form
1
ausdruck<<variabler_ausdruck
– wo immer möglich – vermeiden.
Gerade in Schleifen lässt sich der Code oft so umstellen, dass nur noch
Shift-Operationen um 1 Bit erforderlich sind:
1
ausdruck<<1
Diese Shifts kann der AVR direkt in einem Taktzyklus ausführen, während
der Compiler bei variabler Shiftweite i.Allg. eine zeitaufwendige
Schleife generiert.
Hallo,
Vielen Dank für die Antworten.
Habe das erstmal das Verfahren von Rolf verwendet, was schonmal
schneller ist als meine Variante. Die Portausgabe ist aber nur eine
Schwachstelle in meinem Programm.
Gruß
Oliver
unbedingt so aussehen muss.
Warum nicht einfach ein
1
volatileunsignedchargates=0b01001000;
und dann ganz einfach mittel
1
PORTB=gates;
ausgeben.
Wenn gates nicht konstant ist, und danach sieht alles aus, muss man eben
beim Setzen bzw. Löschen eines Gates die entsprechenden
Bitmanipulationen machen.
Anstelle von
1
gates[gateNr]=HIGH;
dann eben
1
gates|=(1<<gateNr);
bzw. die entsprechende Bitoperation beim Löschen.
Letzten Endes läuft es dann auf die Fragestellung hinaus: was geschieht
öfter. Wird öfter ausgegeben, oder ändern sich die Werte öfter?
Je nachdem verlegt man die Bitoperation dann in den Teil, der weniger
oft ausgeführt wird. Um die Bitoperation an sich kommt man nicht rum.
Aber man muss sie ja nicht zb. 30000 mal in der Sekunde in einer ISR
machen, wenn sich die Gate-Belegung nur alle paar Minuten mal ändert.
Für eine oft ausgeführte ISR richtet man die Dinge im Vorfeld schon so
her, dass die ISR möglichst wenig Arbeit hat.
Hallo Karl Heinz,
vielen Dank für deine Antworten werde mal versuchen das Array durch eine
unsigned char Variable zu ersetzen.
Ich habe in diesem Thread mal meinen source Code hochgeladen.
Beitrag "MIDI to Voltage mit diskretem DAC"
Gruß
Oliver
So ganz klar ist mir noch nicht, warum du da ständig die Gates
ansteuerst. Ein DAC behält ja seine Ausgangsspannung sowieso bei, bis er
neue Daten bekommt. D.h. die Gate bzw. Ausgangsspannungsinformation muss
nur geändert werden, wenn per MIDI neue Informationen reinkommen. Was
bringt es dir, wenn du da laufend die DAC mit immer denselben Werten
versorgst?
Aber selbst wenn:
Mein Bauch sagt: der ganze Ansatz ist ein wenig seltsam. Zeitsteuerungen
bedingen praktisch immer einen Timer. Auch Multiplexing fällt da
drunter.
FAQ: Timer
Das Scannen des Keyboards wird da auch keine Ausnahme machen.
Die UART hingegen kann auch schon mal ein wenig warten, bis der nächste
Multiplexschritt fertig abgearbeitet ist. Das dauert ja nicht besonders
lange
1
uint8_t voiceNr;
2
ISR( ... ) // Timer ISR
3
{
4
voiceNr++;
5
6
if( voiceNr == 8 )
7
{
8
Resetpuls generieren
9
Gate Information rausgeben
10
}
11
12
voice[voiceNr] auf den Port legen
13
Clock nachschieben
14
}
so in etwa, wobei die Details dir überlassen bleiben.
Übernimmt deine externe Hardware mit jeweils der steigenden UND der
fallenden Flanke? Wenn nicht würde ich gegen eine XOR Operation
zugunsten von dezidiertem 1-Setzen bzw. 0-Löschen plädieren.
Karl Heinz schrieb:> So ganz klar ist mir noch nicht, warum du da ständig die Gates> ansteuerst. Ein DAC behält ja seine Ausgangsspannung sowieso bei, bis er> neue Daten bekommt. D.h. die Gate bzw. Ausgangsspannungsinformation muss> nur geändert werden, wenn per MIDI neue Informationen reinkommen. Was> bringt es dir, wenn du da laufend die DAC mit immer denselben Werten> versorgst?
Hallo Karl Heinz,
Die Gate Signale gehen auf High wenn eine Taste gerdrückt wird un geht
wieder auf low wenn die Taste losgelassen wird. Das ganze mit maximal 8
angeschlagen Tasten(8-stimmig polyphon).
Die Erzeugung der Steuerspannung wird gemultiplexed dazu kommt der
diskret aufgebaute DA Wandler zum Einsatz.
Ich weiß das es integrierte Wandler mit SPI/i2C gibt es gehört hier zum
Gesamtkonzept des Synthesizers.
In der Tat sieht es komisch aus den DAC immer mit den gleichen Werten zu
versorgen. Hier kommte aber später eine polyphone Portamento Funktion
hinzu.
Das mit dem Timer werde ich mir aber mal anschauen.
Gruß
Oliver
Oliver F. schrieb:> Die Erzeugung der Steuerspannung wird gemultiplexed dazu> kommt der diskret aufgebaute DA Wandler zum Einsatz.
Ah, ok.
Das hab ich im Code nicht gesehen, dass es nur einen DAC gibt.
Das wesentliche Gleis, auf dem der Zug jetzt äbfahren sollte, heißt
sowieso "Multiplexing mittels Timer". Diese Schleifensache ist sowieso
keine saubere Lösung.
TIMSK0|=(1<<TOIE0);// den Overflow Interrupt des Timers freigeben
27
28
TCCR0B=(1<<CS00);// kein Vorteiler , jetzt zählt der Timer bereits
29
30
sei();
31
....
32
}
33
34
35
36
ISR(TIMER0_OVF_vect)// Overflow Interrupt Vector
37
{
38
39
PORTB^=(1<<PB7);// Pin 13 toggeln
40
//}
41
}
Kann das mit der initialisierung der USART zusammenhängen?
Die Frequenz sollte doch 16MHz / 8 = 2 Mhz betragen.
Was mache ich hier falsch?
Zur Info: Atmel 2560 auf Arduino Mega2560 Board.
Gruß
Oliver
Stefan Ernst schrieb:> Nö> 16MHz / 256 = 62500 Interrupts/Sek = 31250 Hz am Ausgang
Hallo,
Danke dann ist das ja klar. Wie kann ich aber höhere Frequenzen
erzeugen?
Ich benötige CLK Signal von 100Khz
Wie kann ich das erreichen?
Gruß
Oliver
Oliver F. schrieb:> Stefan Ernst schrieb:>> Nö>> 16MHz / 256 = 62500 Interrupts/Sek = 31250 Hz am Ausgang>> Hallo,>> Danke dann ist das ja klar. Wie kann ich aber höhere Frequenzen> erzeugen?>> Ich benötige CLK Signal von 100Khz
Jetzt bin ich noch mehr verwirrt.
Ich dachte das CLK Signal brauchst du um den Multiplexer anzusteuern,
der die Spannung des einen DAC auf 8 Ausgänge aufteilt.
Wozu brauchst du da 100kHz Multiplexfrequenz? Bist du sicher, dass dein
DAC da noch mitkommt und die Kapazitäten keinen Streich spielen?
Butter bei den Fischen. Wie schnell verändert sich die Spannung
wirklich? Jetzt real gesehen und nicht was du dir (aus welchem Grund
auch immer) wünscht.
Karl Heinz schrieb:> Wozu brauchst du da 100kHz Multiplexfrequenz? Bist du sicher, dass dein> DAC da noch mitkommt und die Kapazitäten keinen Streich spielen?>> Butter bei den Fischen. Wie schnell verändert sich die Spannung> wirklich? Jetzt real gesehen und nicht was du dir (aus welchem Grund> auch immer) wünscht.
Hallo Karl Heinz,
Das ist eine gute Frage. MIDI läuft ja mit rund 31 kHz
Ein NoteOn Befehl hat 3Byte dann braucht die Verarbeitung ca:
24Bit / 31250Bit/s = 0,000768s
Dann wäre meine Clock Rate:
1/0,0007s = 1428 Hz
Dann habe ich mich bei meiner ersten Berechnung aber ganz schön
verhauen.
Oder habe ich hier einen Denkfehler?
Hier der grobe Ablauf des Programms:
Erste Taste gedrückt interrupt auslösen
Im interrupt:
Array gates an Position [0] auf High setzten.
Array voices an Position [0] auf High setzten , markiert die Stimme
als belegt
Array oct an Position[0] auf den Spannungswert des Oktavbereich
setzten
Array oct an Position [0] auf den Spannungswert der Taste setzten.
In der Main Schleife
Durchlaufen der Arrays
Spannung der Octave ausgeben Bits OctA- OctC setzten Datenübernahme
mit Oct CS
Spannung der Note ausgeben bits NoteA- NoteC setzten Datenübernahme
mit Note CS
CLK taktet den nachgeschalteten Multiplexer -> speichert die
Steuerspannung in S/H Gliedern
Ausgabe der Gate Werte
ende Schleife.
Dann kann ich ja jatzt anfangen mit dem Timer ein stabiles Clk Signal zu
generieren.
Vielen Dank für die Erleuchtung
Gruß
Oliver
>> will es nicht funktionieren.
Logisch
>> Was mache ich denn jetzt wieder falsch??
Das hier
> PORTC ^= (1<<PC4); //CLK
toggelt den Ausgangspins. Du brauchst also 2 ISR AUfrufe um 1 Clock-Puls
zu generieren.
Es würde mich sehr wundern, wenn dein Multiplexer-IC so lahm wäre und
mit dem Tempo des µC nicht mitkommt.
Oliver F. schrieb:> Erste Taste gedrückt interrupt auslösen
Taste?
Dann kannst du deine Anforderungen gleich nochmal runterschrauben.
Selbst mit gutem Willen und absoluter Konzentration wird es kaum einem
Menschen gelingen, eine Taste in 1 Sekunde mehr als 10 bis 15 mal zu
drücken und wieder loszulassen. Sollen es 20 mal sein - auch gut. Aber
sicher nicht 1000 mal. Das schafft noch nicht mal Batman.
Mit deinen 31kHz aus dem ursprünglichen ISR Ansatz bist du also soweit,
dass bei einer gedrückten Taste der Ausgang für einen Menschen praktisch
instantan und sofort auf Spannung geht. Alles was in den kleinen
einstelligen Millisekundenbereich geht, ist für uns Menschen de fakto
'sofort'. Was im Umkehrschluss gerechnet bedeutet, dass eine ISR
Frequenz ab ca. 8kHz aufwärts mehr als dicke reichen wird.
Hallo Karl Heinz,
nochmal vielen Dank.
Habe dein Programm jetzt um die Ausgabe der Gates erweitert und bekomme
bei 31250Hz und einer Stimmenanzahl von 4, Anzahl habe ich zu
Testzwecken Reduziert da mein LA nur 8 Ports hat, auf eine Latenz
zwischen Midi In Signal und Ausgabe des Gate Signals von ca. 0,2ms Da
kann man wohl von Latenzfrei sprechen...
Im Screenshot kann man die 3 durch die USART ausgelösten Interrupts und
das Nachfolgende Gate Signal sehen. Die Übertragung des 3 Byte MIDI
Befehls dauert länger als die Generierung des Gate Signals.
Im nächsten Schritt werde ich mich um die Erzeugung der Signale für den
DAC kümmern.
Gruß
Oliver