Forum: Mikrocontroller und Digitale Elektronik ATmega644 Timing Probleme


von Niclas (Gast)


Lesenswert?

Hallo,

ich versuche eine UART ähnliche Verbindung zu implementieren. Nun habe 
ich jedoch Probleme beim Empfangen von Daten.

Im Grunde ist es ganz einfach. 1 Bit hat die Länge von einer etu. Eine 
etu hat die Länge von 1/4 * 372/Frequenz in Sekunden. Beim Empfangen 
soll erst mittels eines Pin Change Interrupts das Startbit empfangen 
werden. Dann soll bis zur Mitte des ersten Bits gewartet werden, dann 
das erste Bit gelesen werden und dann bis zur Mitte des zweiten Bits 
gewartet werden usw..

Folgenden Code habe ich bisher:

Funktion zum Empfangen eines Bytes:
1
uint8_t receiveByte(int etu)
2
{
3
  uint8_t receivedByte = 0x00;    
4
  volatile int8_t parityCounter = 0;
5
  startBitFlag = 0;        
6
  PCICR |= (1 << PCIE1);
7
  PCMSK1 |= (1 << PCINT14);
8
  while(startBitFlag != 1);                                    //wait for startbit
9
  startBitFlag = 0;        
10
  PCICR = 0x00;                                                //disable Pin Change Interrupt
11
  PCMSK1 = 0x00;
12
  receiveLock = 1;      
13
  startTimer2(x);                                              //warte bis zur Mitte des 1. Bits (PROBLEM)
14
  while(receiveLock);                                          //Abwarten bis das erste data bit ankommt
15
  receivedByte |= (receivedBit << 0);
16
  startTimer2(y);                                              //warte bis zur Mitte des 2. Bits (PROBLEM)
17
  if (receivedBit == 1)
18
    parityCounter++;
19
  receiveLock = 1;
20
    for (int i = 1; i < 8; i++)
21
    {
22
          while(receiveLock);
23
          receivedByte |= (receivedBit << i);
24
          if(receivedBit == 1)
25
            parityCounter++;
26
          receiveLock = 1;
27
    }
28
  while(receiveLock);            
29
  stopTimer2(); 
30
  if(receivedBit)              
31
  {        
32
    parityCounter++;
33
  }
34
  if (parityCounter % 2)
35
  {
36
    uart0_putstring("Error 1: Parity Check failed.\n");
37
  }
38
  receiveLock = 1;
39
  return receivedByte;
40
}

8-bit Timer:
1
//Timer für receive
2
void startTimer2(uint8_t countArg)
3
{
4
  TCNT2 = 0;          
5
  TCCR2A |= (1<<WGM21);    
6
  TCCR2B |= (1<<CS21);    
7
  OCR2A = countArg;      
8
  TIMSK2 |= (1<<OCIE2A);    
9
}
10
11
void stopTimer2(void)
12
{
13
  TCCR0A &= ~(1 << WGM01);
14
  TCCR2B = 0x00;
15
  TIMSK2 = 0x00;
16
}

Interrupts:
1
//Pin Change Interrupt für PCINT14
2
ISR(PCINT1_vect)
3
{
4
  cli();                  
5
  if( ( PINB & (1 << IO) ) == 0 )      //Wenn 0 anliegt...
6
    startBitFlag = 1;
7
  sei();                  
8
}
9
10
//Interrupt für ETUTimer2
11
ISR(TIMER2_COMPA_vect)
12
{
13
  cli();
14
  if( ( PINB & ( 1 << IO ) ) == 0 )
15
    receivedBit = 0;
16
  else
17
    receivedBit = 1;
18
    
19
  receiveLock = 0;
20
  sei();
21
}

Das Problem ist, dass ich nicht weiß welche Werte ich jeweils für x und 
y einsetzen muss. Ich habe schon versucht mit Atmel Studio die Takte zu 
zählen, um eine Vorstellung davon zu bekommen, wie lange die einzelnen 
Aufrufe dauern. Ich habe viel hin und her gerechnet und probiert. Leider 
haben meine Versuche nicht zum Erfolg geführt. Das ganze läuft auf einem 
ATmega644.

Das Ziel ist es die Parameter x und y des startTimer2 durch von etu 
abhängige Parameter zu ersetzen.

Bin gespannt auf eure Antworten.

Niclas

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Niclas schrieb:
> receivedByte |= (receivedBit << 0);
Ähm, was tut diese Zeile?

> receivedByte |= (receivedBit << i);
Dieser Code ist auf einem AVR extrem ungünstig, weil der nämlich keinen 
Barrelshifter hat. Wesentlich effizienter wäre es, eine Bitmaske zu 
schieben und die dann zu verodern:
1
  uint8_t receiveMask = 0x01;    
2
  :
3
  :
4
  while(receiveLock);
5
  if (receviedBit) {
6
    receivedByte |= receiveMask;
7
    receiveMask <<= 1; // <<<< Maske schieben
8
    parityCounter++;
9
  }
10
  startTimer2(y);
11
  receiveLock = 1;
12
    for (int i = 1; i < 8; i++)
13
    {
14
          while(receiveLock);
15
          if (receviedBit) {
16
            receivedByte |= receiveMask;
17
            receiveMask <<= 1; // <<<< Maske schieben
18
            parityCounter++; 
19
          }
20
          receiveLock = 1;
Denn das ergibt statt realen 127 Schiebevorgängen nur noch 8...

> Eine etu hat die Länge von 1/4 * 372/Frequenz in Sekunden.
Was ergibt das in Echtzeit? Eher 1us oder eher 1ms?

> Das Problem ist, dass ich nicht weiß welche Werte ich jeweils für x und
> y einsetzen muss.
Ich kann dein Problem ganz einfach auf die Hälfte reduzieren: du 
brauchst nur den Wert von Y. Denn es gilt ja X=Y/2.

Und wenn du weißt, wie lang ein "etu" tatsächlich ist und welche 
Quarzfrequenz du hast, dann kannst du ausrechnen, wie oft der 
Timerinterrupt kommen muss und die Timerregister danach berechnen. Da 
muss eigentlich nichts ausprobiert werden...

BTW: angesichts der sehr kreativen englischen Schreibweisen von 
"Empfang" möchte ich anmerken, dass der Compiler problemlos auch 
deutsche Zeichenketten interpretieren kann...  ;-)

: Bearbeitet durch Moderator
von Peter D. (peda)


Lesenswert?

Niclas schrieb:
> ich versuche eine UART ähnliche Verbindung zu implementieren.

Wenn es keine UART ist, sondern nur "ähnlich", dann erklär erstmal das 
Timing. Niemand hat Lust, das vermutliche Protokoll aus einem nicht 
funktionierenden Code reverse zu engineeren.

Niclas schrieb:
> Eine
> etu hat die Länge von 1/4 * 372/Frequenz in Sekunden.

Ich hab keine Ahnung, was etu oder 372 bedeuten soll.
Versuchs mal in Baud, Hertz oder µs anzugeben. Dann kriegt man eine 
Vorstellung, wieviel CPU-Zyklen ein Bit lang ist.

Wenn die Baudrate konstant ist, kann Dir der Compiler prima alle 
weiteren Konstanten bereits zur Compilezeit ausrechen. Du mußt nur die 
Formeln hinschreiben (vorzugsweise als Macro).

Hier mal ne SW-UART als Beispiel:
Beitrag "Software UART mit FIFO"

von Niclas (Gast)


Angehängte Dateien:

Lesenswert?

Hi,

erstmal vielen Dank für die ausführlichen Antworten! Bitte lest erst den 
ganzen Text von mir. Ich habe unten einen aktualisierten Code, wodurch 
vieles einfacher zu verstehen ist.

Lothar M. schrieb:
>> receivedByte |= (receivedBit << 0);
> Ähm, was tut diese Zeile?

Diese Zeile soll das erste aus 8 Datenbits einlesen. Danach werden dann 
die restlichen 7 Datenbits gelesen. (Hier habe ich noch das Paritybit 
vergessen.)

Lothar M. schrieb:
> Dieser Code ist auf einem AVR extrem ungünstig, weil der nämlich keinen
> Barrelshifter hat

Ok, das wusste ich nicht, bzw. ich habe mir darüber überhaupt keine 
Gedanken gemacht. Ich werde mir das noch einmal genau anschauen, um es 
zu verstehen. Das Einsparungspotenzial scheint ja immens zu sein.

Lothar M. schrieb:
> Was ergibt das in Echtzeit? Eher 1us oder eher 1ms?

In Echtzeit ergibt das nach folgender Rechnung 24,8us:
1/4 * 372/3750000 = 2,48 * 10^-5 s = 0,0000248s = 24,8us

Kann es sein, dass mein Code zu langsam ist und ich deshalb die 
Wartezeiten falsch angepasst habe?

Lothar M. schrieb:
> Denn es gilt ja X=Y/2.

Muss es nicht X = Y + Y/2 sein? Ich möchte ja 1,5 Bits abwarten. Das 
Startbit und bis zur Mitte des ersten Bits. (X=Y/2 gilt für den Code in 
diesem Beitrag.)

Lothar M. schrieb:
> Quarzfrequenz

3,75MHz

Peter D. schrieb:
> Ich hab keine Ahnung, was etu oder 372 bedeuten soll.

ETU bedeutet elementary time unit und beschreibt nur die Länge eines 
Bits. 372 ist einfach eine Konstante.


Nochmal zum Protokoll:
Ein Bit hat die Länge von einer etu (in diesem Fall 24,8us). Es gibt ein 
Startbit, 8 Datenbits und ein Paritybit. Es soll immer zur Mitte der 
Bits "gelesen" werden. Siehe Bild.

Mir sind selber noch ein paar Fehler aufgefallen, nachdem Lothar nach 
der einen Zeile gefragt hat. Mein aktualisierter Code sollte viel besser 
verständlich sein:
1
uint8_t receiveByte(int etu)
2
{
3
  uint8_t receivedByte = 0x00; 
4
  uint8_t receiveMask = 0x01;    
5
  volatile int8_t parityCounter = 0;
6
  startBitFlag = 0;        
7
  PCICR |= (1 << PCIE1);                    //Pin Change Interrupt anschalten
8
  PCMSK1 |= (1 << PCINT14);        
9
  while(startBitFlag != 1);                //warte auf Startbit
10
  startBitFlag = 0;        
11
  PCICR = 0x00;                            //Pin Change Interrupt ausschalten
12
  PCMSK1 = 0x00;
13
  receiveLock = 1;                        //receiveLock aktivieren (warten bis erstes Bit vorbei, dann nächstes einlesen)
14
  startTimer2(Y/2);
15
  while(receiveLock);                    //Abwarten bis Mitte des Startbits
16
  receiveLock = 1;
17
  startTimer2(Y);                          //Weiter warten bis zur Mitte des 1. Datenbits
18
    for (int i = 0; i < 8; i++)                //8 Bits lesen (8 Datenbits)
19
    {
20
          while(receiveLock);
21
            receivedByte |= receiveMask;
22
            receiveMask <<= 1;                // <<<< Maske schieben
23
          if(receivedBit == 1)
24
            parityCounter++;
25
          receiveLock = 1;
26
    }
27
  while(receiveLock);                    //Paritybit empfangen
28
  stopTimer2(); 
29
  if(receivedBit)                      //Letztes Bit ist das Paritybit
30
  {        
31
    parityCounter++;
32
  }
33
  if (parityCounter % 2)
34
  {
35
    uart0_putstring("Error 1: Parity Check failed.\n");
36
  }
37
  return receivedByte;
38
}

Ich denke mit der aktualisierten Fassung, habe ich auch Lothars 
Anmerkungen zu dieser Zeile: "receivedByte |= (receivedBit << 0);" und 
die Anmerkung "X=Y/2" verstanden und verbessert.

Gruß Niclas

von Uwe S. (de0508)


Lesenswert?

Hallo Niclas,

ich habe seiner Zeit den Code "Software UART mit FIFO" von Peter 
Dannegger (peda) analysiert und dann verwendet.

Beitrag "Software UART mit FIFO"

Die/ seine Ideen sind dann eingeflossen in der Realisierung von "3-fach 
TX Software Uart nicht blockierend".

Uart per Software benötigt sehr viel Rechenzeit bezogen auf die Baudrate 
in Relation zur CPU-Taktfrequenz.

Hier als Berechnung für eine 16MHz CPU-Taktfrequenz.

Für 9600 Bit/s ergeben sich 195/1667 Takten, dies entspricht ~11,70% 
Auslastung für drei Sendetasks.

Für 57600 Bit/s sieht das noch etwas anders aus,
195/278 Takten entsprechen ~70,2% Auslastung für drei Sendetasks.

von Peter II (Gast)


Lesenswert?

1
ISR(PCINT1_vect)
2
{
3
  cli();                  
4
  if( ( PINB & (1 << IO) ) == 0 )      //Wenn 0 anliegt...
5
    startBitFlag = 1;
6
  sei();                  
7
}

cli und sei hat in der ISR nichts zu suchen, das erzeugt nur noch 
kompliziertere Fehler und braucht auch noch jeweils ein Takt.

von Peter D. (peda)


Lesenswert?

Niclas schrieb:
> 3,75MHz

Niclas schrieb:
> Ein Bit hat die Länge von einer etu (in diesem Fall 24,8us).

Das sind ja nur 93 CPU Zyklen.
Das ist sportlich, da muß man beim Programmieren schon sehr optimieren 
(kein int usw.). Jede Zeile kostet CPU-Zeit, die man berücksichtigen 
muß.

Wenn Du eh nur pollst, dann kannst Du schonmal den ganzen 
Interruptoverhead weglassen und die IO-Bits direkt pollen und 
rücksetzen. Ein Interrupt frißt schonmal mindestens 30 Zyklen.

Der ATmega644 kann doch bis 20MHz, dann wird es deutlich leichter (496 
Zyklen).

von eProfi (Gast)


Lesenswert?

> ich versuche eine UART ähnliche Verbindung zu implementieren.

Was ist der Unterschied deines Signals zu einer UART (von der der 644A 
zwei Stück hat)?

Ich würde häufiger sampeln (z.B. 3 mal pro Bit) und eine 
Mehrheitsentscheidung machen.

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.