Forum: Mikrocontroller und Digitale Elektronik Simple software UART, sieht jemand den Fehler?


von C. H. (hedie)


Lesenswert?

Hallo zusammen

Folgendes Szenario:
Ich habe ein GPS Modul, welches theoretisch SPI können müsste.
Leider ist der Hersteller (Maestro GPS) nicht kompetent genug um mir bei 
der Fehlerbehebung der eigenen Module behilflich zu sein.

Deshalb habe ich das Modul auf UART umgestellt.
Dieses hängt nun am hardware SPI Port des Atmega324p

Deshalb dachte ich mir, ich bastle mir schnell eine soft-uart.
Leider klappt diese noch nicht so wie sie sollte.

Vielleicht hat mir ja noch jemand tipps.
Kurz zur konfiguration:
Baudrate 4800
Parity: none
Stoppbit: 1

Meine Idee:

Es gibt einen Pin-Change interrupt für den RX Pin sowie einen Timer0.

Zuerst soll synchronisiert werden. Dazu setze ich den Timer0 auf
die halbe bit-breite. Nun prüfe ich 40mal hintereinander ob RX High ist.
Somit erhoffe ich mir, die Pause zwischen den Sendezeiten des Moduls zu 
erwischen.

Wenn dies erfolgreich war, setzte ich ein Flag in der Variable ucStatus
und aktiviere den PinChange Interrupt.

Wenn nun eine negative flanke eintritt, weiss ich dass dies das Startbit 
sein muss.

Nun möchte ich den Timer Starten, jedoch mit nur der halben bit-zeit, 
damit ich, wenn ich später auf die volle-bitzeit wechsle immer in der 
mitte des bits lande.

Zudem setze ich ein flag dass das startbit erkant wurde.

Nun bin ich das erste mal im timer interrupt, hier setze ich den 
timerwert auf die volle bitzeit und verlasse den interrupt wieder.

Die nächsten male sammle ich die bits ein und beginne mit dem flag 
wieder erneut.



Ich wäre auch bereit ein Struktogram zu machen, sollte dies zu 
unübersichtlich sein.


Hier nun mein code:


1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include "soft_uart.h"
4
#include "ports.h"
5
6
#define RX_PIN ( PINB & (1<<PINB6) )
7
8
#define BAUDRATE_0  4800
9
#define TIMER_PRESC  256
10
#define CPU_CLOCK  10000000
11
#define TIMER_VALUE (((CPU_CLOCK/TIMER_PRESC)/BAUDRATE_0)-1)
12
13
volatile unsigned char ucBitCount = 0;
14
volatile unsigned char ucStatus = 0;
15
volatile unsigned char ucBuffer [8];
16
volatile unsigned char ucNewChar;
17
volatile unsigned char ucTempByte = 0;
18
volatile unsigned char ucBufferCounter = 0;
19
volatile unsigned char ucSynchroCount = 0;
20
volatile unsigned char ucSynchro = 0;
21
22
ISR(PCINT1_vect)
23
{
24
  if(RX_PIN == 0) //Negative Flanke
25
  {
26
    if((ucStatus & 0x01))        //0x01 ist gesetzt, dies bedeutet wir haben vorhin eine längere high-phase erkannt.
27
    {
28
      PCICR  &= ~(1<<PCIE1);      //Interrupt deaktivieren für den PinChange
29
30
      TIMSK0 |= (1<<OCIE0A);      //Timer Interrupt aktivieren
31
      TCNT0 = 0;             //Timer Counter auf 0
32
      OCR0A   = (TIMER_VALUE / 2)  ;   //Timing auf ein Halbes bit setzen
33
34
      ucStatus &= ~(1<<0x01);     //Startbit erkennung deaktivieren, bzw. wurde erkannt
35
    }
36
  }
37
}
38
39
40
ISR(TIMER0_COMPA_vect)
41
{
42
  //LED_1;
43
44
  if(ucSynchro == 0)  //Kein Synchronisationsmodus
45
  {
46
    if((ucStatus & 0x02) == 0)  //0x02 ist nicht gesetzt, wir sind das erstemal nach der Startbiterkennung hier
47
    {
48
      TCNT0 = 0;          //Timerzähler auf 0 setzen
49
      OCR0A   = TIMER_VALUE;   //Timing auf ein ganzes bit setzen
50
      ucStatus |= 0x02;
51
    }
52
    else //wir sind nicht das erste mal hier, somit befinden wir uns im Datenbyte oder den Steuerbits (parity oder stopbit)
53
    {
54
      //if(ucBitCount == 0)
55
      //{
56
      //  OCR0A   = TIMER_VALUE;   //Timing auf ein ganzes bit setzen
57
      //}
58
      if(ucBitCount < 8)  //Wir lesen nun die Bits ein
59
      {
60
        if(RX_PIN)
61
        {
62
          ucTempByte |= 0x80;
63
        }
64
65
        if(ucBitCount != 7)
66
        {
67
          ucTempByte = ucTempByte >> 1;
68
        }
69
70
      }
71
      ucBitCount++;
72
73
      if(ucBitCount == 9)  //Hier befindet sich das Stoppbit
74
      {
75
        LED_1;
76
        if(RX_PIN) //Stoppbit erkannt
77
        {
78
          ucBuffer[ucBufferCounter] = ucTempByte;
79
          ucBufferCounter++;
80
          if(ucBufferCounter == 8) ucBufferCounter = 0;
81
          ucNewChar = 1;
82
83
          ucStatus = 0x01;    //Bereit für nächstes Startbit
84
          PCICR  |= (1<<PCIE1);  //Interrupt aktivieren für den PinChange
85
          TIMSK0 &= ~(1<<OCIE0A); //Interrupt deaktivieren
86
          LED_0;
87
        }
88
        else //kein Stoppbit erkannt
89
        {
90
          ucSynchro = 1; //Synchronisieren
91
          //Interrupt für den Timer nicht deaktivieren.
92
        }
93
94
        ucBitCount = 0;
95
      }
96
97
    }
98
99
100
101
102
  }
103
  else // Synchronisationsmodus
104
  {
105
    //LED_1
106
    if(RX_PIN)  //Prüfen ob RX high ist
107
    {
108
      ucSynchroCount++;
109
    }
110
    else
111
    {
112
      ucSynchroCount = 0;    //Sobald RX einmal low war, wieder auf 0 setzen
113
    }
114
115
    if(ucSynchroCount == 40)  //Es wurde 20bitzeiten lang high erkannt, somit muss nun das startbit folgen
116
    {              //auflösung ist ein halsbes bit
117
      ucSynchroCount = 0;
118
      TIMSK0 &= ~(1<<OCIE0A); //TIMER Interrupt deaktivieren und somit auf startbit warten
119
      ucSynchro = 0;      //Synchrobit löschen
120
      PCICR  |= (1<<PCIE1);  //PinChange Interrupt aktivieren
121
      ucStatus = 0x01;    //Startbit aktivieren
122
    }
123
124
  }
125
  //LED_0;
126
}
127
128
129
void init_soft_uart(void)
130
{
131
132
  //PCINT14 ist der Interrupt des RX-Pins
133
  PCMSK1 |= (1<<PCINT14); //Interruptgruppe 1
134
135
  ucSynchro = 1;        //Synchronisation aktiveren
136
137
  OCR0A   = TIMER_VALUE;    //Timer auf eine ganze bitlänge setzen
138
  TCCR0A |= (1<<WGM01);     //CTC MODE
139
  TCCR0B |= (1<<CS02);    //256 Prescaler
140
  TIMSK0 |= (1<<OCIE0A);    //Interrupt aktivieren
141
142
}

Das Problem:

mit folgendem Code teil funktioniert es nicht:
1
if((ucStatus & 0x02) == 0)  //0x02 ist nicht gesetzt, wir sind das erstemal nach der Startbiterkennung hier
2
    {
3
      TCNT0 = 0;          //Timerzähler auf 0 setzen
4
      OCR0A   = TIMER_VALUE;   //Timing auf ein ganzes bit setzen
5
      ucStatus |= 0x02;
6
    }

Meine gwünschte Verögerung nach dem Startbit muss ich merkwürdigerweise 
im obigen Codeblock setzen mit: OCR0A = TIMER_VALUE * 2;

Dann muss ich auch folgenden Code einsetzen:
1
if(ucBitCount == 0)
2
      {
3
        OCR0A   = TIMER_VALUE;   //Timing auf ein ganzes bit setzen
4
      }

Doch ich verstehe nicht, weshalb mein zuerst geposteter code so wie er 
ist nicht läuft.


Wenn ich ihn entsprechend anpasse, klappt es mit maximal 2 zeichen 
hintereinander. Dann verschluckt er sich.


Ich hoffe jemand kann mir ein paar tipps geben.

Danke

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Claudio Hediger schrieb:
> Nun möchte ich den Timer Starten, jedoch mit nur der halben bit-zeit,
> damit ich, wenn ich später auf die volle-bitzeit wechsle immer in der
> mitte des bits lande.

Ohne jetzt deinen ganzen Code durchzugehen, aber warum nimmst du da 
nicht die 1 1/2-fache Bitlänge? Das Startbit brauchst du ja nicht und 
kannst dann gleich anfangen, bits in den RX-Buffer zu schieben.

von C. H. (hedie)


Angehängte Dateien:

Lesenswert?

So ich habe noch ein kleines Diagram erstellt.

Die roten Striche entsprechen den Interrupts des Timers.

von Stefan E. (sternst)


Lesenswert?

Ein schwerwiegendes Problem (und möglicherweise auch die Ursache für 
deine Beobachtungen) ist, dass du die Interrupt-Flags nie zurücksetzt. 
Während des Daten-Einlesens ist der Pin-Change-Interrupt zwar 
deaktiviert, aber es wird ja trotzdem bei jeder Flanke das entsprechende 
Flag gesetzt. Wenn du dann den Interrupt wieder freigibst, löst er wegen 
des bereits gesetzten Flags sofort aus, und nicht erst bei der nächsten 
Flanke. Und für das Compare-Flag gilt das gleiche.

von C. H. (hedie)


Lesenswert?

Matthias Sch. schrieb:
> Ohne jetzt deinen ganzen Code durchzugehen, aber warum nimmst du da
> nicht die 1 1/2-fache Bitlänge? Das Startbit brauchst du ja nicht und
> kannst dann gleich anfangen, bits in den RX-Buffer zu schieben.

Gut da hast du recht, dies ergibt sinn.

Das problem ist jedoch, dass aus irgendeinem mir unbekannten Grund,
der Timer diese Zeit nicht "übernimmt"

Damit meine ich, dass direkt nach diesem Code:
1
ISR(PCINT1_vect)
2
{
3
  if(RX_PIN == 0) //Negative Flanke
4
  {
5
    if((ucStatus & 0x01))        //0x01 ist gesetzt, dies bedeutet wir haben vorhin eine längere high-phase erkannt.
6
    {
7
      PCICR  &= ~(1<<PCIE1);      //Interrupt deaktivieren für den PinChange
8
9
      TIMSK0 |= (1<<OCIE0A);      //Timer Interrupt aktivieren
10
      TCNT0 = 0;             //Timer Counter auf 0
11
      OCR0A   = (TIMER_VALUE / 2)  ;   //Timing auf ein Halbes bit setzen
12
13
      ucStatus &= ~(1<<0x01);     //Startbit erkennung deaktivieren, bzw. wurde erkannt
14
    }
15
  }
16
}
der Timerinterrupt ausgelöst wird

Die gewünschte Verzögerung tritt somit nicht ein

von C. H. (hedie)


Lesenswert?

Stefan Ernst schrieb:
> Ein schwerwiegendes Problem (und möglicherweise auch die Ursache für
> deine Beobachtungen) ist, dass du die Interrupt-Flags nie zurücksetzt.

Ahhhhh :)

Das wird wohl wirklich der Grund sein!

Werde ich nach dem Essen gleich mal versuchen...


Damit diese Thread jedoch nicht gleich endet noch eine Frage
Ist meine Realisierung bzw. mein Konzept so überhaupt sinnvoll
oder gibt es da besseres?

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Noch ein Tipp: Du kannst Flags einfacher behandeln (ohne die ganze 
fehlerträchtige Maskiererei), indem du dir ein kleines Struct 
zusammenbastelst:
1
typedef struct serialflags
2
{
3
  uint8_t bufferEmpty: 1;   // no data yet
4
  uint8_t startbitReceived : 1; // we have a startbit - sample the da.
5
  uint8_t rxBusy : 1;    // if the receiver is in the middle of a byte.
6
} serialflags_t;
7
// nun kannst du damit eine Variable bauen:
8
volatile serialflags_t rxFlags;
9
10
// und im Code dann
11
rxFlags.startbitReceived = FALSE;
12
rxFlags.bufferEmpty = TRUE;
13
14
 if (rxFlags.startbitReceived) {
15
 // 
16
 }
usw. Das ist doch viel übersichtlicher und man kommt nicht so schnell 
mit den bits durcheinander.

von C. H. (hedie)


Lesenswert?

Matthias Sch. schrieb:
> Diesen Beitrag bewerten:
>   ▲ lesenswert
>
>   ▼ nicht lesenswert
>
>
>
>       Noch ein Tipp: Du kannst Flags einfacher behandeln (ohne die ganze
> fehlerträchtige Maskiererei), indem du dir ein kleines Struct
> zusammenbastelst:

Vielen Dank!

Das ist wirklich ein genialer Tipp!

Ich habe mir nämlich wirklich die Variable ucSynchro aufgesetzt,
da ich mit dem Bit Maskieren ein durcheinander hatte :)

Wird sogleich implementiert!

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Ich habe nochmal gesucht, da ich einen recht zuverlässigen Code für 
einen Empfänger (aufm Tiny45/85) habe, den ich mal für ein Tablet-USB 
Adapter gebaut habe. Der kommt mit den Datenströmen des Tabletts recht 
gut klar:
1
// Konstanten
2
/* software serial port */
3
/* timer periods for 9600 baud, 16mhz and a prescaler of 32 */
4
/* resolution is 2 uSec per timer tick          */ 
5
#define period 256-52      /* time from bit to bit = 104 uSecs*/
6
#define startperiod 256-74     /* time from start of start bit to middle of LSB */ 
7
// Variablen
8
/* softserial IRQ values */
9
volatile uint8_t  offset;    /* bit shifter */
10
volatile uint8_t  val;    /* receive value */
11
/* buffer properties */
12
static   uint8_t   rxbuf[16];
13
static   uint8_t   rxpos;
Hier ist der Startbit Detektor:
1
/* Let us once more implement the serial receiver routine in interrupt, this time in C.
2
PCINT0 is fired by the startbit of incoming serial and then prepares variables and starts the timer 
3
- after that it blocks itself . It is freed again by the completion of the timer routine. 
4
never touch the global irq flag 'cause the USB routines need it */
5
ISR(PCINT0_vect,ISR_NOBLOCK)
6
{
7
  if (bit_is_clear(PINB,BIT_RXD))    /* only a falling edge will trigger */
8
    {
9
      /* Start timer 1 with a prescaler of 32 */
10
      TCNT1 = startperiod;
11
      TCCR1 |= _BV(CS12) | _BV(CS11);  /* start with prescaler / 32  */
12
    GIMSK &= ~(_BV(PCIE));  /* block myself */
13
     GIFR |= _BV(PCIF);     /* clr pendings on me */
14
    TIFR  |= _BV(TOV1);    /* clr pending on timer */
15
    TIMSK |= _BV(TOIE1);    /* enable Timer 1 irq */
16
    offset = 0;        /* shifter init */
17
    val = 0;          /* value init */
18
    }
19
  return;
20
};
Und hier der Timerinterrupt. Die sind beide auf ISR_NOBLOCK, weil da im 
Hintergrund V-USB läuft, was immer der Chef ist.
1
/* the timer routine takes care of incoming bits from software serial 
2
 * it is initialized by the PC_INT0 Routine . the timer IRQ now sets the timer to the 
3
 * real bit rate and samples the incoming data line in the middle of the bit 
4
 * keep in mind that RS232 sends the LSB first in contrary to e.g. I2C. 
5
 * 
6
 * the assembled value is in 'val' 
7
 */
8
ISR(TIM1_OVF_vect,ISR_NOBLOCK)
9
{
10
   TCNT1 = period ;    /* restart timer in any case to maintain best timing */
11
  if ( offset > 7 ) {      /* swallow stop bit */
12
      TIMSK &= ~(_BV(TOIE1));  /* block myself */
13
     GIFR |= _BV(PCIF);     /* clr pendings on PC_INT*/
14
     PCMSK |= _BV(BIT_RXD);   /* prepare pinchange irq */  
15
      TCCR1 = 0;        /* stop timer */
16
    GIMSK |= _BV(PCIE);    /* enable PC INT0 we don't expect a Pinchange between stop bit and idle */
17
    offset += 1;      /* at the end of a completed byte we should have offset == 9 */
18
    return;          /* out of here */
19
  }
20
  val >>= 1;           /*shift one bit to the right - LSB comes first */ 
21
  if (bit_is_set(PINB,BIT_RXD)) {
22
    val |= 0x80 ; /* set the highest data bit */
23
  }
24
  offset += 1;    /* outerloop counter */
25
  return;  
26
};
Vllt. kannst du da ja noch was rausziehen. Meine Strategie ist 
geringfügig anders, da ich den Timer vorbesetze und dann auf einen 
Überlauf warte. Das ist aber recht wurscht, wie man das macht.

von C. H. (hedie)


Angehängte Dateien:

Lesenswert?

Vielen Dank für deinen Code

Ich werde ihn mir mal genauer ansehen.
Aber bevor ich hier meinen Code komplett umstelle, würde es mich schon 
noch interessieren, ob jemand eine Idee hat, weshalb ich etwas falsches 
empfange.

Ich sende zweimal hintereinander 01010101 01010101 und erhalte 01101010 
01010101 zurück

Die Fehler sind sporadisch. Manchmal kommen 3 Zeichen hintereinander 
korrekt an, manchmal zweimal der selbe fehler und dann ein richtiges 
(wenn 3 aufeinanderfolgend gesendet werden)

Anbei ein Bild vom Oszi für das obige Beispiel

die gelben Spikes ist die LED
Diese wird vor dem if(RX_PIN) aktiviert und nach dem schieben der 
Variable ucTempByte wieder deaktiviert.

Man sieht somit, dass der uC zur richtigen Zeit prüft, jedoch liest er 
einen falschen Pin-Zustand ein.

Hat jemand eine Idee?

von C. H. (hedie)


Lesenswert?

------- PROBLEM GELÖST!!!! ---------

So ich konnte das Problem lösen.

Es hatte mit dem Speicherzugriff zu tun.

Folgendes war geschehen:

Ich habe in diesem Codeblock:
1
if(ucBitCount < 8)  //Wir lesen nun die Bits ein
2
      {
3
        if(RX_PIN)
4
        {
5
          ucTempByte |= 0x80;
6
        }
7
8
        if(ucBitCount != 7)
9
        {
10
          ucTempByte = ucTempByte >> 1;
11
        }
12
13
      }

Immer wieder auf eine Volatile Variable zugegriffen.
Volatile sagt dem compiler, dass er den Speicher, sobald Änderungen 
auftauchen aktualisieren muss!

So nun habe ich an diesen stellen, anstelle der volatile eine in der ISR 
aufgesetzte
1
static unsigned char ucLocalTemp;

Static variable verändert und beim erkennen des Stoppbits den Inhalt 
dieser in die Globale, volatile kopiert.

Nun funktioniert alles wunderbar!

Ich würde mich freuen, falls sich jemand noch ein bisschen genauer damit 
auskennt, wenn dies jemand noch etwas detailierter ausführen würde.


Danke an euch!

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.