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
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.
So ich habe noch ein kleines Diagram erstellt.
Die roten Striche entsprechen den Interrupts des Timers.
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.
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
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?
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.
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!
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.
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?
------- 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.
|