Forum: Compiler & IDEs Wie kann ich den Inhalt eines Arrays in einen port schreiben?


von Oliver F. (ollif)


Lesenswert?

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:
1
volatile unsigned char gates [] = {LOW,HIGH,LOW,LOW,HIGH,LOW,LOW,LOW};
2
3
unsigned char voice = 0;
4
5
for (voice = 0; voice < 8 ;voice++)
6
{
7
8
        if( gates[voice] == HIGH)
9
        {
10
                PORTB |= (1<< voice);
11
        }
12
13
        else
14
        {
15
                PORTB &= ~ (1<< voice);
16
        }
17
18
}

geht das irgendwie besser/schneller.

Die Schleife habe ich sowieso

Vielen Dank für eure Hilfe.


Grüße

Oliver

von Oliver S. (oliverso)


Lesenswert?

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.

von Rolf Magnus (Gast)


Lesenswert?

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_t tmp = 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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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
  unsigned char voice, 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
  unsigned char byte = 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.

: Bearbeitet durch Moderator
von Oliver F. (ollif)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

Die nächste Frage ist, ob
1
volatile unsigned char gates [] = {LOW,HIGH,LOW,LOW,HIGH,LOW,LOW,LOW};

unbedingt so aussehen muss.

Warum nicht einfach ein
1
volatile unsigned char gates = 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.

: Bearbeitet durch User
von Oliver F. (ollif)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Oliver F. (ollif)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

: Bearbeitet durch User
von Oliver F. (ollif)


Lesenswert?

Hallo,

ich habe jetzt mal mit dem Timer angefangen. Bekomme auf PB7 ohne Teiler 
aber max 31.250 kHz erzeugt.

Hier mein Programm Code:
1
#define UBRR_VAL ((F_CPU+BAUD*8)/(BAUD*16)-1)   // Rounding
2
3
4
5
void init_USART(void)
6
{
7
  UCSR3B |= 0x08;          //UART TX (Transmit enable)
8
  UCSR3B |= 0x10;          //UART RX (Receive enable)
9
  UCSR3B |= 0x80;          //RX Interrupt enable
10
  UCSR3C |= 0x00;          //Mode Async 8N1 (8 Databits, No Parity, 1 Stopbit)
11
  UCSR3C |= 0x04;
12
  UCSR3C |= 0x02;  
13
  UCSR3A = 0x00;          
14
                    
15
  UBRR3H = 0;          //Highbyte is 0
16
  UBRR3L = UBRR_VAL;        //Lowbyte 
17
}
18
19
20
int main(int argc, char **argv) {
21
22
DDRB = (1 << PB0) | (1 << PB1) | (1 << PB2) | (1 << PB3) | (1 << PB4) | (1 << PB5) | (1 << PB6) | (1 << PB7);  
23
24
.....
25
26
  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

von Stefan E. (sternst)


Lesenswert?

Oliver F. schrieb:
> Die Frequenz sollte doch 16MHz / 8 = 2 Mhz betragen.

Nö
16MHz / 256 = 62500 Interrupts/Sek = 31250 Hz am Ausgang

von Oliver F. (ollif)


Lesenswert?

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

von Stefan E. (sternst)


Lesenswert?

Oliver F. schrieb:
> Wie kann ich aber höhere Frequenzen
> erzeugen?

CTC-Modus

von Karl H. (kbuchegg)


Lesenswert?

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.

von Oliver F. (ollif)


Angehängte Dateien:

Lesenswert?

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

von Oliver F. (ollif)


Angehängte Dateien:

Lesenswert?

Hallo,

ich glaube ich sitze heute schon zu lang am Computer.

Ich bekomme nur mit folgendem Code ein CLK Signal und alle 8 Takte eine 
Reset Signal:
1
ISR( TIMER0_OVF_vect )       // Overflow Interrupt Vector
2
{
3
  
4
  voice++;
5
  if (voice == 16)
6
  {
7
    PORTC |=  (1<<PC5);  //Reset High
8
    voice =0;
9
    
10
  }
11
  else
12
  {
13
    PORTC &= ~ (1<<PC5); //Reset LOW
14
  }
15
  
16
  PORTC ^= (1<<PC4); //CLK
17
  
18
}

mit
1
if (voice == 8)

will es nicht funktionieren.

Was mache ich denn jetzt wieder falsch??

Im Anhang ein Screen mit dem Taktsignal.


Gruß
Oliver

von Karl H. (kbuchegg)


Lesenswert?

Oliver F. schrieb:

>
1
> if (voice == 8)
2
> 
3
>
>
> 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.
1
#define RESET  PC5
2
#define CLK    PC4
3
4
ISR( TIMER0_OVF_vect )       // Overflow Interrupt Vector
5
{
6
  
7
  voice++;
8
  if (voice == 8)
9
  {
10
    PORTC |=  (1<<RESET);  //Reset High
11
    voice =0;
12
    PORTC &= ~ (1<<RESET); //Reset LOW
13
  }
14
  
15
  PORTC |= (1<<CLK);
16
  PORTC &= ~(1<<CLK);
17
}

von Karl H. (kbuchegg)


Lesenswert?

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.

: Bearbeitet durch User
von Oliver F. (ollif)


Angehängte Dateien:

Lesenswert?

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

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.