Forum: Mikrocontroller und Digitale Elektronik Software UART von Peter Dannegger


von Bjoern (Gast)


Lesenswert?

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!

von Bjoern (Gast)


Lesenswert?

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?

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


Lesenswert?

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...

von Bjoern (Gast)


Angehängte Dateien:

Lesenswert?

>> 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?

von Bjoern (Gast)


Lesenswert?

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?

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


Lesenswert?

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

von Bjoern (Gast)


Lesenswert?

>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?

von Karl H. (kbuchegg)


Lesenswert?

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.

von Bjoern (Gast)


Lesenswert?

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
}

von Karl H. (kbuchegg)


Lesenswert?

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

von R. M. (rmax)


Lesenswert?

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.

von Bjoern (Gast)


Lesenswert?

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?

von R. M. (rmax)


Lesenswert?

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.

von Huch (Gast)


Lesenswert?

Hier gibt es eine Implementierung mit dem Flankeninterrupt. 
Beitrag "Re: Software UART" Vielleicht hilft Dir 
das weiter.

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.