www.mikrocontroller.net

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


Autor: TecDroiD (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.
...
  for( ; ; )
  {
      // Wenn Daten am SW-UART angekommen sind
     if ( swuart_getstate () == DATA_PENDING) {
  // hole diese ab
       data = swuart_getchar();
  // und sende sie zurück
   swuart_putchar(data);
     }

  }
...

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
/*
 * Timer-Interrupt Routine
 * State-Machine über den AsynchronousStates_t
 */
ISR(TIMER0_COMPA_vect) {
  switch (state) {
  // Transmit the start bit
  case TRANSMIT_START_BIT:
         ...
  break;

  // Go to idle after stop bit was sent.
  case TRANSMIT_STOP_BIT1: 
         ...
       break;

  // Go to idle after stop bit was sent.
  case TRANSMIT_STOP_BIT2: 
         ...
       break;

  case TRANSMIT_END:
         ...
       break;

  //Receive Byte.
  case RECEIVE:
    OCR = TICKS2WAITONE;                  // Count one period after the falling edge is trigged.
    //Receiving, LSB first.
    if( SwUartRXBitCount < 8 ) {
        SwUartRXBitCount++;
        SwUartRXData = (SwUartRXData>>1);   // Shift due to receiving LSB first.
        if( GET_RX_PIN( ) != 0 ) {
            SwUartRXData |= 0x80;           // If a logical 1 is read, let the data mirror this.
        }
    }

    //Done receiving
    else {
        state = DATA_PENDING;               // Enter DATA_PENDING when one byte is received.
        DISABLE_TIMER_INTERRUPT( );         // Disable this interrupt.
        EXT_IFR |= (1 << INTF0 );           // Reset flag not to enter the ISR one extra time.
        ENABLE_EXTERNAL0_INTERRUPT( );      // Enable interrupt to receive more bytes.
    }
  break;

  // Unknown state.
  default:        
    state = IDLE;                           // Error, should not occur. Going to a safe state.
  }
}
/*
 * Externer Interrupt- Es wird etwas empfangen..
 */
ISR (PCINT0_vect) {
  //if ((TRXPIN & (1 << RX_PIN)) == 0 ) { // beim Flankenwechsel nach low
    state = RECEIVE;                  // Change state
    DISABLE_EXTERNAL0_INTERRUPT( );   // Disable interrupt during the data bits.

    DISABLE_TIMER_INTERRUPT( );       // Disable timer to change its registers.
    TCCR_P &= ~( 1 << CS01 );         // Reset prescaler counter.

    TCNT0 = INTERRUPT_EXEC_CYCL;      // Clear counter register. Include time to run interrupt rutine.

    TCCR_P |= ( 1 << CS01 );          // Start prescaler clock.

    OCR = TICKS2WAITONE_HALF;         // Count one and a half period into the future.

    SwUartRXBitCount = 0;             // Clear received bit counter.
    CLEAR_TIMER_INTERRUPT( );         // Clear interrupt bits
    ENABLE_TIMER_INTERRUPT( );        // Enable timer0 interrupt on again
  //}
}

Die definierten Makros sind diese:
#define TICKS2COUNT         103  //!< Ticks between two bits.
#define TICKS2WAITONE       103  //!< Wait one bit period.
#define TICKS2WAITONE_HALF  155  //!< Wait one and a half bit period.

#define INTERRUPT_EXEC_CYCL   9       //!< Cycles to execute interrupt rutine from interrupt.

#define ENABLE_TIMER_INTERRUPT( )       ( TIMSK |= ( 1<< OCIE0A ) )
#define DISABLE_TIMER_INTERRUPT( )      ( TIMSK &= ~( 1<< OCIE0A ) )
#define CLEAR_TIMER_INTERRUPT( )        ( TIFR |= ((1 << OCF0A) ) )
#define ENABLE_EXTERNAL0_INTERRUPT( )   ( GIMSK |= ( 1<< PCIE ) )
#define DISABLE_EXTERNAL0_INTERRUPT( )  ( GIMSK &= ~( 1<< PCIE ) )

#define TX_PIN           PB4                //!< Transmit data pin
#define RX_PIN           PB3                //!< Receive data pin
#define RX_PCINT     PCINT3        //!< Receive PCINT
#define TCCR             TCCR0A             //!< Timer/Counter Control Register
#define TCCR_P           TCCR0B             //!< Timer/Counter Control (Prescaler) Register
#define OCR              OCR0A              //!< Output Compare Register
#define EXT_IFR          GIFR               //!< External Interrupt Flag Register
#define EXT_ICR          MCUCR              //!< External Interrupt Control Register
#define TRXDDR       DDRB        //!< DDR-Port für den UART
#define TRXPORT      PORTB        //!< Ausgangsport für den UART
#define TRXPIN       PINB        //!< Eingangsport für den UART


#define SET_TX_PIN( )    ( TRXPORT |= ( 1 << TX_PIN ) )
#define CLEAR_TX_PIN( )  ( TRXPORT &= ~( 1 << TX_PIN ) )
#define GET_RX_PIN( )    ( TRXPIN & ( 1 << RX_PIN ) )


typedef enum
{
    IDLE,                                       //!< Idle state, both transmit and receive possible.
    TRANSMIT,                                   //!< Transmitting byte.
    TRANSMIT_STOP_BIT,                          //!< Transmitting stop bit.
    RECEIVE,                                    //!< Receiving byte.
    DATA_PENDING                                //!< Byte received and ready to read.
Hab ich in meinem Interruptwahn irgendwas übersehen?

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht 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)
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.

Autor: Stefan Ernst (sternst)
Datum:

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

Autor: TecDroiD (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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..

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: TecDroiD (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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..

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: TecDroiD (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
das meint ich ja ;)

Autor: TecDroiD (Gast)
Datum:

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

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Stefan B. (stefan) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: TecDroiD (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht 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..

Autor: Felix Nachname (time2ride)
Datum:

Bewertung
0 lesenswert
nicht 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:
PORTA &= ~(1<<PA1)
für Pin1

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

Autor: TecDroiD (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
gut, das findet man in jedem C-Tutorial, was mir aber hier nichts

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@ Felix C.:

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

Autor: TecDroiD (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Stefan B. (stefan) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: TecDroiD (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: TecDroiD (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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..

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.