Forum: Compiler & IDEs Software-UART (AVR304) empfängt nur Müll


von TecDroiD (Gast)


Lesenswert?

Aalso ich hab mir die AppNote AVR304 etwas angepasst und mir ein 
Testprogramm geschrieben, welches Daten vom PC empfängt und an diesen 
zurück gibt.
1
...
2
  for( ; ; )
3
  {
4
      // Wenn Daten am SW-UART angekommen sind
5
     if ( swuart_getstate () == DATA_PENDING) {
6
  // hole diese ab
7
       data = swuart_getchar();
8
  // und sende sie zurück
9
   swuart_putchar(data);
10
     }
11
12
  }
13
...

Zurück bekomme ich aber nur Datenmüll..
Interessant ist allerdings, dass dieser Datenmüll Struktur hat.
Sende ich beispielsweise die Zahl 85 (praktisch, weil binär 01010101) 
bekomme ich 234 (11101010) zurück. Sende ich 175 (binär 10101010) 
bekomme ich 96 (binär 01100000).

Jetzt hab ich mir den Spaß gemacht, das Empfangene in den EEPROM zu 
schreiben und siehe da, genau das, was ich zurück bekomme, ist auch 
gespeichert- der Sender funktioniert also problemfrei- nur am Empfang 
haperts.

Die AppNote beschreibt eine einfache State machine, die durch beim 
Empfang int0 getriggert wird. Da der Port bei dem von mir genutzten 
Tiny25 mit am I²C hängt, den ich anderweitig brauche, hab ich einfach 
mal die Dreistigkeit besessen und auf nen PCINT umgestellt- wodurch mir 
zwar die Flankenmessung verloren geht, aber das Problem hab ich 
hoffentlich umgangen, indem ich vor dem Aktivieren des Interrupts auf 
ein High am Eingang warte..

Der PCINT aktiviert nun den Timer-Interrupt mit CTC. Zuerst wird 1.5 * T 
(T=1/Baudrate) gewartet, um in der Mitte des ersten Bits zu liegen. Das 
erste Bit wird empfangen. Anschließend wird der OCR auf 1T umgestellt. 
Somit können sehr große Instabilitäten in der Taktung abgefangen werden.

Da der Sender ja einwandfrei funktioniert, frage ich mich, ob die 
Umstellung des OCR nicht funktioniert..

Hier mal der Code der ISR()s
1
/*
2
 * Timer-Interrupt Routine
3
 * State-Machine über den AsynchronousStates_t
4
 */
5
ISR(TIMER0_COMPA_vect) {
6
  switch (state) {
7
  // Transmit the start bit
8
  case TRANSMIT_START_BIT:
9
         ...
10
  break;
11
12
  // Go to idle after stop bit was sent.
13
  case TRANSMIT_STOP_BIT1: 
14
         ...
15
       break;
16
17
  // Go to idle after stop bit was sent.
18
  case TRANSMIT_STOP_BIT2: 
19
         ...
20
       break;
21
22
  case TRANSMIT_END:
23
         ...
24
       break;
25
26
  //Receive Byte.
27
  case RECEIVE:
28
    OCR = TICKS2WAITONE;                  // Count one period after the falling edge is trigged.
29
    //Receiving, LSB first.
30
    if( SwUartRXBitCount < 8 ) {
31
        SwUartRXBitCount++;
32
        SwUartRXData = (SwUartRXData>>1);   // Shift due to receiving LSB first.
33
        if( GET_RX_PIN( ) != 0 ) {
34
            SwUartRXData |= 0x80;           // If a logical 1 is read, let the data mirror this.
35
        }
36
    }
37
38
    //Done receiving
39
    else {
40
        state = DATA_PENDING;               // Enter DATA_PENDING when one byte is received.
41
        DISABLE_TIMER_INTERRUPT( );         // Disable this interrupt.
42
        EXT_IFR |= (1 << INTF0 );           // Reset flag not to enter the ISR one extra time.
43
        ENABLE_EXTERNAL0_INTERRUPT( );      // Enable interrupt to receive more bytes.
44
    }
45
  break;
46
47
  // Unknown state.
48
  default:        
49
    state = IDLE;                           // Error, should not occur. Going to a safe state.
50
  }
51
}
1
/*
2
 * Externer Interrupt- Es wird etwas empfangen..
3
 */
4
ISR (PCINT0_vect) {
5
  //if ((TRXPIN & (1 << RX_PIN)) == 0 ) { // beim Flankenwechsel nach low
6
    state = RECEIVE;                  // Change state
7
    DISABLE_EXTERNAL0_INTERRUPT( );   // Disable interrupt during the data bits.
8
9
    DISABLE_TIMER_INTERRUPT( );       // Disable timer to change its registers.
10
    TCCR_P &= ~( 1 << CS01 );         // Reset prescaler counter.
11
12
    TCNT0 = INTERRUPT_EXEC_CYCL;      // Clear counter register. Include time to run interrupt rutine.
13
14
    TCCR_P |= ( 1 << CS01 );          // Start prescaler clock.
15
16
    OCR = TICKS2WAITONE_HALF;         // Count one and a half period into the future.
17
18
    SwUartRXBitCount = 0;             // Clear received bit counter.
19
    CLEAR_TIMER_INTERRUPT( );         // Clear interrupt bits
20
    ENABLE_TIMER_INTERRUPT( );        // Enable timer0 interrupt on again
21
  //}
22
}

Die definierten Makros sind diese:
1
#define TICKS2COUNT         103  //!< Ticks between two bits.
2
#define TICKS2WAITONE       103  //!< Wait one bit period.
3
#define TICKS2WAITONE_HALF  155  //!< Wait one and a half bit period.
4
5
#define INTERRUPT_EXEC_CYCL   9       //!< Cycles to execute interrupt rutine from interrupt.
6
7
#define ENABLE_TIMER_INTERRUPT( )       ( TIMSK |= ( 1<< OCIE0A ) )
8
#define DISABLE_TIMER_INTERRUPT( )      ( TIMSK &= ~( 1<< OCIE0A ) )
9
#define CLEAR_TIMER_INTERRUPT( )        ( TIFR |= ((1 << OCF0A) ) )
10
#define ENABLE_EXTERNAL0_INTERRUPT( )   ( GIMSK |= ( 1<< PCIE ) )
11
#define DISABLE_EXTERNAL0_INTERRUPT( )  ( GIMSK &= ~( 1<< PCIE ) )
12
13
#define TX_PIN           PB4                //!< Transmit data pin
14
#define RX_PIN           PB3                //!< Receive data pin
15
#define RX_PCINT     PCINT3        //!< Receive PCINT
16
#define TCCR             TCCR0A             //!< Timer/Counter Control Register
17
#define TCCR_P           TCCR0B             //!< Timer/Counter Control (Prescaler) Register
18
#define OCR              OCR0A              //!< Output Compare Register
19
#define EXT_IFR          GIFR               //!< External Interrupt Flag Register
20
#define EXT_ICR          MCUCR              //!< External Interrupt Control Register
21
#define TRXDDR       DDRB        //!< DDR-Port für den UART
22
#define TRXPORT      PORTB        //!< Ausgangsport für den UART
23
#define TRXPIN       PINB        //!< Eingangsport für den UART
24
25
26
#define SET_TX_PIN( )    ( TRXPORT |= ( 1 << TX_PIN ) )
27
#define CLEAR_TX_PIN( )  ( TRXPORT &= ~( 1 << TX_PIN ) )
28
#define GET_RX_PIN( )    ( TRXPIN & ( 1 << RX_PIN ) )
29
30
31
typedef enum
32
{
33
    IDLE,                                       //!< Idle state, both transmit and receive possible.
34
    TRANSMIT,                                   //!< Transmitting byte.
35
    TRANSMIT_STOP_BIT,                          //!< Transmitting stop bit.
36
    RECEIVE,                                    //!< Receiving byte.
37
    DATA_PENDING                                //!< Byte received and ready to read.
Hab ich in meinem Interruptwahn irgendwas übersehen?

von Stefan E. (sternst)


Lesenswert?

1)
In der PCINT-ISR liegt einiger Code vor dem Starten des Timers und in 
der COMPA-ISR wiederum einiger Code vor dem Einlesen des Pins. Dein 
Abtastpunkt ist also nicht in der Mitte, sondern reichlich nach hinten 
verschoben. Der Ausgleich per INTERRUPT_EXEC_CYCL ist da ja nur ein 
Tropfen auf den heißen Stein.

2)
1
EXT_IFR |= (1 << INTF0 );

Wieso INTF0? Du hast doch auf PCINT umgestellt. Außerdem löscht man 
gezielt ein einzelnes Bit mit einem "=", nicht mit "|=".
Das ist aber nicht der Auslöser deines aktuellen Problems, denn die 
beiden Fehler "ergänzen" sich quasi. Das "|=" sorgt dafür, dass das 
richtige Bit "versehentlich" mit gelöscht wird.

von Stefan E. (sternst)


Lesenswert?

PS:
Und ändere bei CLEAR_TIMER_INTERRUPT das "|=" auch gleich in ein "=".

von TecDroiD (Gast)


Lesenswert?

Aaalso ich versteh nicht wirklich, warum ich ein Bit mittels = löschen 
sollte. Zumal das | ein Bit setzt ;) Löschen passiert bei der 
Und-Verknüpfung mit dem Reziproken Wert
das = würde das ganze Byte verändern, ich will aber nur mit einem Bit 
maskieren..

Trotzdem stimmt es, dass das Int0 ein Relikt ist.. Der Kommentar hinter 
dem ist auch etwas uneindeutig für mich..
Werd mir die Interrupt-Maskierung nochmal ansehen..

von Stefan E. (sternst)


Lesenswert?

TecDroiD wrote:
> Aaalso ich versteh nicht wirklich, warum ich ein Bit mittels = löschen
> sollte. Zumal das | ein Bit setzt ;) Löschen passiert bei der
> Und-Verknüpfung mit dem Reziproken Wert
> das = würde das ganze Byte verändern, ich will aber nur mit einem Bit
> maskieren.

Nein! Interruptflags werden durch Schreiben einer Eins in das 
entsprechende Bit gelöscht. Mit einem "|=" löscht du daher gleich alle 
Flags in dem jeweiligen Register.

Aber wie ich bereits sagte, ist das nicht der Auslöser deines Problems. 
Der ist eher im Abtastzeitpunkt zu suchen.

von TecDroiD (Gast)


Lesenswert?

hmmm...
1 << 4  = 0b00010000

0b10001001 |= 0b00010000 entspricht
0b10001001 | 0b00010000 = 0b10011001

die oder-verknüpfung setzt eine 1, wenn auf einem der verknüpften bits 
eine 1 steht..


gut, wenn anschließend  eine zuweisung passiert hab ich da gelöscht, wo 
vorher eine 1 war..

von Stefan E. (sternst)


Lesenswert?

TecDroiD wrote:

> gut, wenn anschließend  eine zuweisung passiert hab ich da gelöscht, wo
> vorher eine 1 war..

Wieso "wenn"? Die Zuweisung ist doch im "|=" enthalten, die passiert auf 
jeden Fall.

von TecDroiD (Gast)


Lesenswert?

das meint ich ja ;)

von TecDroiD (Gast)


Lesenswert?

trotzdem hab ich nen massives timing-problem..
wie rechne ich denn jetzt aus, wie weit ich den OCR verkleinern muss?

von Stefan E. (sternst)


Lesenswert?

Du könntest z.B. damit anfangen, das Einlesen des Pins ganz an den 
Anfang der COMPA-ISR zu verlagern (noch vor das switch).
Und dann kannst du einen Blick in den Asm-Code werfen, und abzählen, wie 
viele Clocks Versatz du in den beiden ISRs zusammen etwa hast. Um diesen 
Wert musst du den ersten Timer-Durchlauf verkürzen.

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Mit der Definition für

#define TICKS2WAITONE       103  //!< Wait one bit period.

sieht es nach gewünschter Baudrate 9600 aus.

1/9600s => ca. 0,104 ms/Bit => ca. 104 µs/Bit. Der Timer könnte dann mit 
1us Ticks arbeiten, d.h. z.B. kein Prescaler und F_CPU 1 MHz. Du kannst 
im Prinzip in die gleichen Probleme mit Baudratenerror kommen wie bei 
einer Hardware-UART, d.h. z.B. ungenauer, temperaturabhängiger interner 
RC-Oszillator...

Da ich die Initialisierung des Timers und die Taktrate des µC nicht 
sehe, kann ich das aber nur raten.

Zum Ausrechnen sehe ich da kaum eine Chance. Man könnte die Bitbreite am 
Oszi ausmessen und dann korrigieren. Oder empirisch vorgehen und mit 
einem +/i Taster im Betrieb die OCR manipulieren. Oder den PC einen 
bekannten Datenstrom senden lassen und auf dem AVR auf fehlerfreie 
Erkennung synchronisieren lassen. D.h. du machst eine Kalibrierung. Btw. 
da sollte es aber auch eine Appnote geben, wie man den internen 
Taktgeber kalibriert.

von TecDroiD (Gast)


Angehängte Dateien:

Lesenswert?

die cpu-frequenz liegt bei 8 MHz. ich häng mal den kompletten code an.
Vielleicht hat ja mal wer zeit, nen max232 da ran zu packen..

von Felix N. (time2ride)


Lesenswert?

Stefan Ernst wrote:

> ... Außerdem löscht man
> gezielt ein einzelnes Bit mit einem "=", nicht mit "|=".

Einzelne Bits kann man auch zuverlässig so löschen:
1
PORTA &= ~(1<<PA1)
für Pin1

und bei mehreren Bits einfach verodert anhängen:
1
PORTA &= ~(1<<PA1 | 1<<PA5)
für Pin1 und Pin5

von TecDroiD (Gast)


Lesenswert?

gut, das findet man in jedem C-Tutorial, was mir aber hier nichts

von Stefan E. (sternst)


Lesenswert?

@ Felix C.:

Lies erstmal richtig, worum es überhaupt genau ging, nämlich um 
Interruptflags.

von TecDroiD (Gast)


Lesenswert?

mal so als dumme Frage: sollte sich das synchronisieren nicht umgehen 
lassen, wenn ich einfach nen Quartz-Oszillator mit 8 MHz da dran hänge?

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Viel gewinnst du bei UART nicht, du brauchst weiterhin die Erkennung der 
fallenden Flanke des Startbits. Und du musst weiterhin einen Timer 
mitlaufen lassen, um die Abtastzeitpunkte zu erzeugen bei denen du die 
Bits eines Bytes am Portpin erkennen willst. Mit einer exakteren 
Taktquelle funktioniert das letztere exakter.

Pi*Daumen gerechnet wieviel Genauigkeit du brauchst:

Die benötigte Genauigkeit ist ja um die +- 1/2 Bitdauer auf den typ. 
folgenden 8 Bits (8N1), wenn der erste Abtastzeitpunkt gut in der Mitte 
des 2. Bits liegt. Also +- 1/16 Bitdauer worst case bei den folgenden 8 
einzelnen Bits. Bei 9600 Baud dauert 1 Bit ca. 104 µs, d.h. du kannst 
einen Fehler von +-  6,5 µs verschmerzen, selbst wenn er sich über die 
10 Bits aufsummiert. Das sind wuchtige +- 6,25 % Toleranz bzw. +- 4,8%, 
wenn man auf alle 10 Bits rechnet! Bei einer Hardware-UART sagt man, 
dass 2% die Obergrenze sind. So gesehen ist der Software-UART Code sogar 
robuster, oder?

Bist du jetzt eigentlich weiter gekommen, läuft die Übertragung 
fehlerfrei, oder soll man noch nach deinem Code schauen?

von TecDroiD (Gast)


Lesenswert?

Da ich ein wenig Familie zuhause habe, kann ich leider nicht immer so, 
wie ich will.. werd aber heut abend mal nen Experimentierbrett 
verdrahten und dann schauen, was es bringt, nen Quartz dran zu hängen.

Nichts desto trotz darf sich gern jemand meinen Code anschauen. Ich hab 
die state machine aus der AppNote ein wenig verändert. So hat sie z.b. 
zwei Stopbits.

Allerdings ist sie auch ein wenig umständlicher geworden.

von TecDroiD (Gast)


Lesenswert?

also ich hab jetzt mal den prescaler von 8 auf 64 gesetzt. das ergibt 
eine baudrate von 1200. trotzdem empfange ich nur müll. das realterm 
zeigt mir übrigens immer einen framing error an..

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.