Software UART von Peter Dannegger

Gast #1979641
Hallo zusammen,

ich habe gerade den Software UART von Peter Dannegger auf nem ATMega48 
getestet. Läuft super und schön schlank gehalten.

Jetzt würde ich den Code gerne folgendermaßen abändern:

Peter benutzt zur Erkennung des Datenempfangs den Input Capture 
Interrupt. Ich würd das gerne auf nen Pin Change Interrupt ändern, um 
beim Pin für den Empfang variabel zu sein.

Was ich nicht ganz verstehe ist, wie er in seinem Code den Wert aus dem 
ICR1 Register verwendet. Hier die beiden Interrupts:
1
/******************************  Interrupts *******************************/
2

3
ISR(TIMER1_CAPT_vect)        // start detection
4
{
5
  s16 i = ICR1 - BIT_TIME / 2;      // scan at 0.5 bit time
6

7
  OPTR18          // avoid disoptimization
8
  if( i < 0 )
9
    i += BIT_TIME;        // wrap around
10
  OCR1B = i;
11
  srx_state = 10;
12
  TIFR1 = 1<<OCF1B;        // clear pending interrupt
13
  if( SRXD_PIN == 0 )        // still low
14
    TIMSK1 = 1<<OCIE1A^1<<OCIE1B;    // wait for first bit
15
}
16

17

18
ISR(TIMER1_COMPB_vect)        // receive data bits
19
{
20
  u8 i;
21

22
  switch( --srx_state ){
23

24
    case 9:  if( SRXD_PIN == 0 )    // start bit valid
25
         return;
26
       break;
27

28
    default: i = srx_data >> 1;            // LSB first
29
       if( SRXD_PIN == 1 )
30
         i |= 0x80;      // data bit = 1
31
       srx_data = i;
32
       return;
33

34
    case 0:  if( SRXD_PIN == 1 ){    // stop bit valid
35
         i = srx_in;
36
         ROLLOVER( i, SRX_SIZE );
37
         if( i != srx_out ){    // no buffer overflow
38
     srx_buff[srx_in] = srx_data;
39
     srx_in = i;      // advance in pointer
40
         }
41
       }
42
       TIFR1 = 1<<ICF1;      // clear pending interrupt
43
  }
44
  TIMSK1 = 1<<ICIE1^1<<OCIE1A;      // enable next start
45
}

Kann mir das jemand erklären? Vielen Dank!
Gast #1979811
Was ich nicht verstehe ist, dass der Input Capture doch die Zeit 
zwischen zwei Events misst. Doch hier wird der Input Capture doch schon 
nach dem ersten Durchlauf verwendet und hat immer einen anderen Wert.

Also wozu genau ist dieser Wert gut?
Moderator (Firma: Titel) Persönliche Seite #1979815
Bjoern schrieb:
> Jetzt würde ich den Code gerne folgendermaßen abändern:
Das solltest du nur tun, wenn du genau verstanden hast, was da 
passiert.

> dass der Input Capture doch die Zeit zwischen zwei Events misst.
> Also wozu genau ist dieser Wert gut?
Er zeigt an, wieviele Bits (vielfache einer Bitdauer) zwischen zwei 
Capture Ereignissen high- oder low (= 0 oder 1) waren...
Gast #1979843
>> dass der Input Capture doch die Zeit zwischen zwei Events misst.
>> Also wozu genau ist dieser Wert gut?
>Er zeigt an, wieviele Bits (vielfache einer Bitdauer) zwischen zwei
>Capture Ereignissen high- oder low (= 0 oder 1) waren...

Ok, das wäre also die Dauer des ersten Low Pulses meines zu empfangenden 
Zeichen. Siehe Bild im Anhang. Richtig?
Angehängte Dateien:
Gast #1979942
Ne, meine Vermutung ist nicht richtig! Hab mir gerade mal nen anderen 
Pin zu vergleichen getoggelt.

Doch welche Bits werden dann gemessen? Und zwischen welchen Ereignissen?
Moderator (Firma: Titel) Persönliche Seite #1979968
Bjoern schrieb:
> Doch welche Bits werden dann gemessen? Und zwischen welchen Ereignissen?
Ich war auf dem Irrweg... :-(
Da wird gar nichts gemessen, es wird einfach die erste fallende Flanke 
genommen, und dann der Compare-Timer so gesetzt, dass ab da jedes Bit in 
der Mitte abgetastet wird...

Der Pin und der zugehörige Timer wird in 2 Betriebsarten betrieben:
1) Bis zum Erkennen des Startbits ist das ein Input-Capture.
1a) Dann wird umgeschaltet auf den Timer-Compare:
   TIMSK1 = 1<<OCIE1A^1<<OCIE1B;    // wait for first bit
2)Danach werden die Bits empfangen.
2a)Und anschliessend wird wieder auf Capture-Interrupt umgeschaltet:
   TIMSK1 = 1<<ICIE1^1<<OCIE1A;      // enable next start
Und damit geht es wieder bei 1) los.

Weil das alles so hübsch auf den Timer zugeschnitten ist, wirst du damit 
keine chance haben:
>>> Ich würd das gerne auf nen Pin Change Interrupt ändern
Gast #1980054
>Da wird gar nichts gemessen, es wird einfach die erste fallende Flanke
>genommen, und dann der Compare-Timer so gesetzt, dass ab da jedes Bit in
>der Mitte abgetastet wird...

Doch wie wird das genau gemacht? Der ICR1 Wert sieht bei jedem Sprung in 
den Interrupt anders aus. Warum? Wie krieg ich es dann hin die Bits in 
der Mitte abzutasten?
#1980067
Kann es sein, dass der Timer selber dann auch noch im CTC Modus 
betrieben wird?

Wo ist die generelle Timer Konfiguration?

Wenn da noch ein CTC Modus am Timer läuft, würde das so manches 
erklären.
Kann natürlich auch sein, dass der CTC indirekt über einen

ISR(TIMER1_COMPA_vect)
{
  TCNT1 = 0;
}

realisiert ist.
Gast #1980070
Yep, ein CTC ist drin. Hier die Timer Konfiguration:
1
void suart_init( void )
2
{
3
  OCR1A = BIT_TIME - 1;
4
  TCCR1A = TX_HIGH;      // set OC1A high, T1 mode 4
5
  TCCR1B = 1<<ICNC1^1<<WGM12^1<<CS10;  // noise canceler, 1-0 transition,
6
          // CLK/1, T1 mode 4 (CTC)
7
  TCCR1C = 1<<FOC1A;
8
  stx_state = 0;
9
  stx_in = 0;
10
  stx_out = 0;
11
  srx_in = 0;
12
  srx_out = 0;
13
  STXD_DDR = 1;        // output enable
14
  TIFR1 = 1<<ICF1;      // clear pending interrupt
15
  TIMSK1 = 1<<ICIE1^1<<OCIE1A;    // enable tx and wait for start
16
}
#1980072
So wie das für mich aussieht, läuft der Timer insofern durch als einmal 
'Rundum' des Timers genau der Bitzeit eines UART Bits entspricht.

Das bedeutet: Der Input Capture Wert ist im Grunde einfach nur der 
Offset, wann das erste Bit in Bezug zum 0-Durchgang des Timers begonnen 
hat.
Von diesem Wert ausgehend wird dann ausgerechnet, bei welchem Timerwert 
jeweils das Bit zur Hälfte durch ist und der Compare am B Kanal 
entsprechend eingerichtet.

Clever
#1980076
Ja, sehr clever. Das erspart einem nämlich die Taktzählerei, die man 
betreiben müßte, wollte man den Timer bei jedem Interrupt zurücksetzen, 
ohne sich einen Jitter oder systematischen Fehler einzuhandeln.
Gast #1980123
Und welche Möglichkeit habe ich jetzt, dass ganze mit nem Pin Change 
Interrupt zu machen? Oder besser gefragt, wie kann ich diesen Offset 
sonst noch messen?
#1980148
Du müßtest in der ISR statt des Capture-Registers den aktuellen 
Zählerstand auslesen.

Weil aber zwischen der Flanke des Startbits und dem Auslesen des Zählers 
noch etwas Zeit vergeht (in der der Zähler weiterzählt), bekommst Du auf 
die Weise entweder einen systematischen Fehler, d.H. Du samplest immer 
ein Stück hinter der Mitte der Bits, oder Du mußt die oben erwähnte 
Taktzählerei betreiben, um zu wissen, wieviel Du vom Zählerstand 
abziehen mußt, um diesen Fehler auszugleichen.

Wie groß der Fehler ist und ob man ihn herausrechnen muß oder ignorieren 
kann, hängt vom Verhältnis zwischen Taktfrequenz und Baudrate und von 
Deinem Baudratenfehler ab.

Antwort schreiben

Eine eigene Frage? Dieser Thread ist über 15 Jahre alt. Stell sie in einem eigenen Thread – dort lesen mehr Leute mit, und sie wird öfter und schneller beantwortet.

Neuen Thread starten

Eine Lösung, Ergänzung oder Rückmeldung zu diesem Thread? Dann antworte hier.

Formatierung
  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] – Link zu einem Artikel
  • Verweis auf einen anderen Beitrag: Rechtsklick auf dessen Beitrags-ID (#8078628), „Adresse kopieren“, in den Text einfügen

Mehr zur Formatierung

Längeren Sourcecode als Anhang, nicht in den Text. JPG nur für Fotos und Scans; Zeichnungen und Screenshots als PNG oder GIF. Siehe Bildformate.

Schon registriert? Anmelden

Wird nicht angezeigt. Wir schicken dir einen Link; sobald du damit bestätigst, erscheint dein Beitrag.

Mit dem Abschicken legst du einen kostenlosen Account an und erkennst die Nutzungsbedingungen und die Datenschutzerklärung an.