Forum: Compiler & IDEs gemessene Frequenz mit UART verschicken


von Karl N. (karlnapf)


Lesenswert?

Hallo zusammen!

Ich messe mit dem ICP eine Frequenz (eigentlich die Zeit zwischen zwei 
Impulsen, was funktioniert), die ich anschließend an einen anderen 
Atmega verschicken will.

Mein Problem ist, dass die Frequenz in einem regelmäßigen Abstand von 
ca. 1ms vom Empfänger-Controller "angefordert" werden soll.
Wenn also ein Impuls vom Empfänger! kommt (INT0 am Sender wird high 
gesetzt -Interrupt ausglöst), soll er das Frequenzmessen sein lassen und 
sofort! die letzte gemessene Frequenz verschicken.
Ich möchte damit vermeiden, dass nicht nach jeder Frequenzmessung das 
Ergebnis verschickt wird sondern nur wenn Pin D2 high gesetzt wird, so 
dass meine Übertragung nicht mehr abhängig von der Dauer der zwei 
IC-Impulse ist.

Der auskommentierte Bereich in der main funktioniert, ist aber genau das 
was ich vermeiden will!
1
#include <stdlib.h>
2
#include <stdio.h>
3
#include <avr/io.h>
4
#include <util/delay.h>
5
#include <avr/interrupt.h>
6
7
#ifndef F_CPU
8
#define F_CPU 16000000           
9
#endif
10
11
volatile unsigned int overflow = 0;
12
volatile unsigned long ic_zp_A = 0;  // ICR-Wert bei 1.High-Flanke speichern
13
volatile unsigned long ic_zp_B = 0;  // ICR-Wert bei 2.High-Flanke speichern
14
volatile unsigned char UpdateUART;    //Job Flag
15
volatile uint8_t send = 0;
16
unsigned char i = 0;
17
uint32_t zw_Erg = 0;
18
uint32_t Erg = 0;
19
20
ISR(TIMER1_CAPT_vect)
21
{
22
  if( UpdateUART == 1 )   //auf vorherige Messung warten
23
        return;
24
25
  if (i == 0)    // 1.High Flanke
26
  {
27
    ic_zp_A = ICR1;
28
    overflow = 0;
29
    i = 1;
30
  }
31
  else           // 2.High Flanke
32
  {
33
    ic_zp_B = ICR1;
34
    UpdateUART = 1;
35
    i = 0;
36
  }
37
}
38
39
ISR(TIMER1_OVF_vect)
40
{
41
  overflow++;
42
}
43
44
ISR(INT0_vect) //*******hier ist das Problem********
45
{
46
    while (!(UCSRA & (1<<UDRE)))    // warten bis Senden moeglich
47
    {
48
    }
49
    UDR = send;              // sende Zeichen
50
}
51
52
int main(void)
53
{
54
  TCCR1B = (1<<ICES1)  | (1<<CS10) ; // Input Capture Edge, kein PreScale ->Taktfrequenz: 16MHz externer Quarz
55
  TIMSK = (1<<TICIE1) | (1<<TOIE1);  // Interrupts akivieren, Capture + Overflow
56
  
57
  //init UART
58
  UBRRH  = 0;                             // Highbyte ist 0
59
  UBRRL  = 3;                              // Lowbyte ist 3 Baudrate ist 250kBps
60
  UCSRB |= (1<<TXEN);          //auf senden gestellt
61
  UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0);    
62
  
63
  //Externer Interrupt
64
  GICR |= (1<<INT0);
65
  MCUCR |= (1<<ISC01)|(1<<ISC00);    //auf steigende Flanke
66
  
67
  //Steuerleitung
68
  DDRD &= ~(1<<PD2);    //Eingang INT0
69
  
70
  sei();
71
  
72
  for( ; ; )
73
  {  
74
  if( UpdateUART == 1 )
75
    {
76
      zw_Erg = ((overflow * 65536) + ic_zp_B - ic_zp_A);  // Overflow berücksichtigen
77
      Erg = 16000000 / zw_Erg;      //16MHz Quarz umrechnen in Frequenz
78
      send = Erg;              //von 32bit in 16bit Variable schreiben
79
      /*
80
      if (PIND & (1<<PIND2))
81
      {
82
        uart_putc((uint8_t) (Send >> 8) & 0xFF);  //highbyte senden
83
        uart_putc((uint8_t) (Send >> 0) & 0xFF);  //lowbyte senden
84
      }
85
      */
86
      UpdateUART = 0;
87
    }
88
  }
89
  return 0;
90
}

Viele Grüße

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Erstens hast du wohl vergessen, dein Problem zu beschreiben, was du
überhaupt damit hast.  Den benutzten Prozessor dazuzuschreiben, könnte
auch hilfreich sein.

Zweitens glaube ich dir einfach mal gar nicht, dass der
auskommentierte Bereich auch nur ansatzweise funktioniert: es gibt
nirgends in dem Code eine Definition einer Variablen namens "Send".
Falls das Ganze ein Schreibfehler sein soll (was hast du dann beim
Abtippen noch alles verschrieben?) und "send" gemeint ist: wie kann es
funktioniert haben, wenn send doch eine 8-bit-Variable ist, du aber
ein "low- und high-Byte" von dieser ausgeben willst?

von Wolfram (Gast)


Lesenswert?

>ISR(INT0_vect) //*******hier ist das Problem********
>{
>    while (!(UCSRA & (1<<UDRE)))    // warten bis Senden moeglich
>    {
>    }
>    UDR = send;              // sende Zeichen
>}
Oh ja das ist ein Problem.
Warum versuchen Leute immer, als wenn sie in Basic wären, aus einer 
Interruptroutine zu senden. Bist du bewußt was du da hingeschrieben 
hast???
-> warte bis ich wieder senden darf; bei einer UART
Datenrate 9600 -> ein Zeichen dauert 1ms
Datenrate 115200 -> 86 us
Datenrate 250000 -> 40 us
In dieser Zeit kannst du nichts tun, alle interrupts sind gesperrt!
Du willst den Controller doch noch zu was anderen als nur der Messung 
benutzen?
Überlege es dir doch mal selbst, effektiv wilst du in 1ms 1 Zeichen 
senden.
Aufgrund deines Konzepts bist du genötigt die Datenrate so hoch zu 
nehmen,
daß du in der Zeit 25 Zeichen senden könntest. Da sollte doch langsam 
klar werden das hier konzeptionell was nicht stimmt.
Setze das alles in die main Schleife und sende mit TX(UDRE) interrupt 
und Puffer. Dann wird nämlich das was du jetzt machst nur noch <1% Last 
bedeuten.
und du hast auch kein Problem 16 oder 32 Bit zu senden
Du schreibst im Kommentar das du 16 Bit senden willst, sendest aber nur 
8Bit???


von Karl N. (karlnapf)


Lesenswert?

Den auskommentierten Bereich am besten ignorieren den hab ich noch aus 
nem anderen Programm reinkopiert. War nur gedacht als Gegenbeispiel wie 
ich's nicht machen will.
Ist ein Atmega16 mit 16MHz.
Das Problem ist, dass er nichts verschickt und ich nicht weiß ob das 
überhaupt so funktionieren könnte.(In einer ISR über UART was 
rausschicken).
Gibt es Alternativen zu meinem Versuch hier?
Denn wenn ich die Daten in der main über Polling verschicke dann dauert 
das immer viel länger als die eigentliche Dauer für die Frequenzmessung 
und ist zudem noch Frequenz abhänig.
Ein Beispiel: gemessene Frequenz 100Hz -> dauer zwischen zwei Impulsen 
0,01s -> am Empfänger die UART Übertragung gemessen: 4ms!
woran liegt das?
Deshalb will ich die Sache mit dem externen Interrupt lösen, oder wie 
würdet/st ihr/du das machen?

von Peter D. (peda)


Lesenswert?

1
zw_Erg = ((overflow * 65536) + ic_zp_B - ic_zp_A);  // Overflow berücksichtigen

Das geht schief.
Zwar nur bei bestimmten Frequenzen, dann aber gründlich.

Du mußt zu jedem Capture den Overflowzähler speichern und erstmal 
feststellen, ob er vor oder hinter dem Capture kam.

Sonst hast Du manchmal 65536 Zyklen Meßfehler.

Auch sollte man ne maximale Zyklenzahl definieren, ab der eine zu 
geringe Frequenz anliegt und dann den Wert 0Hz ausgeben.


Peter

von Wolfram (Gast)


Lesenswert?

>oder wie würdet/st ihr/du das machen?
nimm den auskommentierten Bereich wieder rein. Genau so mußt du es 
machen
nur uart_putc darf nicht mit Polling arbeiten sondern muß mit 
Zwischenpuffer und UDRE Interrupt arbeiten! Das Senden läuft dann 
parallel zur Messung.

von Karl N. (karlnapf)


Lesenswert?

@Wolfram:
das eignetliche Problem ist, dass ich die Frequenz nur ca. jede 1ms am 
Empfänger benötige, dann aber sofort (deshalb die hohe Baudrate)(wird 
für eine Regelung verwendet). Sprich der Empfänger soll sich zu 
bestimmten Zeiten die Frequenz holen und dann möglichst schnell.

Zuerst wollte ich das mit mit 8bit versuchen später sollen Frequenzen 
bis 65kHz übertragen werden, also 16bit.

Ich hab den UART Interrupt nicht ganz verstanden. Du meinst doch den TX 
complete interrupt. Wie soll ich den verwenden, ich will ja nichts 
machen wenn der USRT fertig ist sonder er soll senden wenn ein Signal 
vom Empfänger kommt.

Grüße

von Karl N. (karlnapf)


Lesenswert?

Ah, der UDRE Interrupt.
Das heißt der Puffer wird gefüllt und erst dann wenn vom Empfänger ein 
Signal kommt verschickt?
Das wäre die Lösung werds probieren.

von Karl H. (kbuchegg)


Lesenswert?

Holger Brenner wrote:
> Das Problem ist, dass er nichts verschickt und ich nicht weiß ob das
> überhaupt so funktionieren könnte.(In einer ISR über UART was
> rausschicken).
> Gibt es Alternativen zu meinem Versuch hier?

Die grundsärtzliche Idee mit dem INT0 ist ja nicht
schlecht. Nur darfst (sollst) du nicht in der ISR die
Daten verschicken.

Mach doch das selbe Spielchen, dass du mit dem
UpadateUart Job Flag gemacht hast einfach nochmal:

In der ISR wird eine globale Variable (das Jobflag)
auf 1 gesetzt.

in der main() wird neben all den anderen Dingen auch
dieses Jobflag noch überprüft und wenn es 1 ist, dann
wird über die UART verschickt.

int main(void)
{
  ...

  for( ; ; )
  {
    if( UpdateUART == 1 )
    {
      ...
      UpdateUART = 0;
    }

    if( SendUART == 1 )
    {
      ...
      SendUART = 0;
    }
  }
  return 0;
}

und die ISR setzt ganz einfach SendUART auf 1


Wenn das immer noch nicht reicht, dann muss man die
Verschickerei selbst wieder über einen Interrupt machen:
Man stellt das Zeichen in UDR und die UART benachrichtigt einen
mit einem Interrupt wenn es fertig verschickt wurde und
das nächste Zeichen verschickt werden kann.

von Wolfram (Gast)


Lesenswert?

>das eignetliche Problem ist, dass ich die Frequenz nur ca. jede 1ms am
>Empfänger benötige, dann aber sofort
Du stellst Konzepte auf die dich in Zeitliche Abhängigkeiten führen die 
nur sehr umständliche zu lösen sind. Dies innerhalb eines Programmes zu 
tun vielleicht, aber zwischen 2 Mikrocontrollern auf keinen Fall.
Ich vermute sehr du machst es so, dein Master-Mikrocontroller setzt 
einen Pin
und dein Meßmikrocontroller soll antworten. Richtig?
Gibt es auf der UART verbindung noch irgendwelche anderen 
Mikrocontroller?
Kann es sein, daß du das ganze nur mit 2 Mikrocontrollern löst, weil du 
Probleme hattest es zeitlich in einen zu integrieren?

von Wolfram (Gast)


Lesenswert?

>Das heißt der Puffer wird gefüllt und erst dann wenn vom Empfänger ein
>Signal kommt verschickt?
Nein, das heißt das letzte Byte wird gerade gesendet, du kannst ein 
weiteres zum Senden (wenn du es hast) an die UART geben.

von Karl N. (karlnapf)


Lesenswert?

@Peter:
werd ich gleich mal versuchen und meld mich dann.

@Wolfram:
Genau das ist das Problem.
Es gibt noch zwei weitere MeßController die nacheinander Daten zum 
MainController schicken sollen, immer mit dem gleichen Prinzip: Pin high 
setzen Controller schickt, nächster Pin high setzen nächster Controller 
schickt,...
Will eigentlich nicht von diesem Konzept abweichen, aber wenn du ne 
einfacher Lösung parat hast lass ich mich gern überzeugen.

von Oliver (Gast)


Lesenswert?

sofort! ist entweder unmöglich, oder aber sehr relativ. Was meinst du 
damit? Warum benötigt die Regelung eine Abtatszeit von 1ms? Was 
passiert, wenn der Frequenzwert bis zu einer Millisekunde "alt" ist? 
Stört die Totzeit? Was passiert bei Frequenzen unter 1kHz (da dann die 
Messung länger als 1ms dauert...)? Warum überhaupt 2 Mikrocontroller? 
Wenn es wirklich schnell gehen soll mit der Übertragung, warum dann 
uart, und nicht 8-bit (oder gleich 16) parallel? Oder SPI? ODer TWI?

???

Oliver

von Wolfram (Gast)


Lesenswert?

Konzept:
Kontinuierliche Messung Zeit zwischen den High Flanken im Interrupt
aber: bei der 2. ten Flanke wird Erg berechnet und alles zurück gesetzt
Eigentlich müßtest du wenn du Erg zuweist bei >8Bit kurz den Interrupts 
verbieten

Int0:
Du übernimmst Erg in einen Puffer(16..32Bit) und sendest diesen per 
UartInterrupt Im Int0 wird nur das Senden für das erste zeichen 
angestoßen
Die restlichen Zeichen werden jeweils in dem UDRE Interrupt nachgefüllt.
Für das letzte Zeichen nicht vergessen den UDRE Interrupt wieder 
auszuschalten


main leere Schleife
Baudrate für die UART kann ruhig 250KBaud sein

von Karl N. (karlnapf)


Lesenswert?

@ Karl Heinz:
das sieht ganz gut aus. Die Zeitdauer fürs Empfangen geht nicht über die 
1ms drüber, wenn man das so messen kann?:
am Empfänger:

PORTC |= (1<<PC6);
a = uart_getc();
PORTC &= ~(1<<PC6);

und dann am Oszi den Pin anschauen.

@ Wolfram:
das mit dem INT0 versteh ich noch nicht ganz. Wie kann ich im INT0 den 
UART anstoßen? Meinst du ein Flag setzen für die main?

von Wolfram (Gast)


Lesenswert?

nein ,erlaube UDREInt wenn du weitere zeichen aus dem Puffer senden 
willst
und sende das erste Zeichen aus dem Puffer
(Es kann kein anderes Zeichen aktuell von der UART gesendet werden, da 
du per Definition gesagt hast das erst bei INT0 gesendet wird und auch 
indirekt voraussetzt daß die letzte Sendung schon beendet ist.)

von Karl H. (kbuchegg)


Lesenswert?

Holger Brenner wrote:
> @ Karl Heinz:
> das sieht ganz gut aus.

Vergiss es wieder.
Der Vorschlag von Wolfram ist besser.

> @ Wolfram:
> das mit dem INT0 versteh ich noch nicht ganz. Wie kann ich im INT0 den
> UART anstoßen? Meinst du ein Flag setzen für die main?

Einfach das erste Zeichen in UDR schreiben, wenn UDR
frei ist (sollte es aber sein).

Die weiteren Zeichen holt sich dann der UART per Interrupt
selbst ab.


von Karl N. (karlnapf)


Lesenswert?

Da ich erst am Amfang vom C-Programmieren bin, könnt ihr mir dazu noch 
ein bisschen mehr erzählen?

Was genau passiert in INT0:
Zuerst wird "erg" in einer Variablen abgelegt.
Dann wird der Data Register Empty Interrrupt freigegeben: (UCSRB |= 
(1<<UDRIE) ) so dass der UART Interrupt anfängt Daten zu verschicken.
Der Rest geht dann von allein? Muss ich den UART Interrupt wieder 
abschalten wenn ich nicht's senden will?
Was macht meine Frequenzmessung so lange? Passiert das quasi parallel?

Wie funktioniert das dann auf der Empfängerseite?
Dort wird über einen Timer alle 1ms der Pin high gesetzt, aber in einer 
ISR kann ich die Daten ja nicht empfangen, also auch beim Empfangen mit 
dem UART Interrupt arbeiten?

von Wolfram (Gast)


Lesenswert?

Lege dich fest, willst du ein Zeichen senden oder mehrere?
Bitte lies es dir erstmal im Datenblatt durch.

von Karl N. (karlnapf)


Lesenswert?

hab ich schon gemacht werd's aber nochmal genauer studieren.
Die Sache mit dem UART ist halt nicht so leicht zu verstehen zumal man 
verstehen muss was auf beiden Seiten passiert.
Ich muss eine zehntausender Zahl verschicken also zwei 8bit-Zeichen und 
dann wieder zusammen setzen.
Ich meld mich nochmal wenn ich besser durchblick.

von Karl H. (kbuchegg)


Lesenswert?

Holger Brenner wrote:
> Was genau passiert in INT0:
> Zuerst wird "erg" in einer Variablen abgelegt.
> Dann wird der Data Register Empty Interrrupt freigegeben: (UCSRB |=
> (1<<UDRIE) ) so dass der UART Interrupt anfängt Daten zu verschicken.

Nein. So funktioniert das nicht.
Nur weil ein Interrupt freigegeben ist, sendet der UART
noch lange nichts.

Ein Interrupt tritt immer als Reaktion auf ein Ereignis
auf. Welches ist das Ereignis? Das Ereignis ist, dass
die UART Hardware das übergebene Zeichen soweit raus-
geschickt hat, dass es das nächste Zeichen annehmen könnte.

Daraus folgt: Um den Prozess in Gang zu bringen muss man
das erste Zeichen in die UART hineinstecken (in Form von:
ins UDR Register schreiben). Die UART rappelt dann vor sich
hin und wenn sie soweit ist, kommt ein Interrupt. In der
Interrupt Routine wird dann das nächste Zeichen in die
UART hineingestopft. Die UART rappelt wieder und wenn sie
soweit ist, generiert sie den nächsten Interrupt. In der
Interrupt Routine wird das nächste Zeichen in die UART
gesteckt ... wie, da ist kein Zeichen mehr? Na wenn da
kein Zeichen mehr ist, dann macht die ISR halt gar nichts
und stopft kein neues Zeichen in die UART, sodass die UART
dann auch zur Ruhe kommt.

> Der Rest geht dann von allein? Muss ich den UART Interrupt wieder
> abschalten wenn ich nicht's senden will?

Nein. Du gibst der UART einfach kein Zeichen mehr und gut ists.

> Was macht meine Frequenzmessung so lange? Passiert das quasi parallel?

Ja.
Die UART arbeitet ganz von alleine und das bischen Zeichenschubserei
in der Interrupt Routine macht der Prozessor mit links.

>
> Wie funktioniert das dann auf der Empfängerseite?
> Dort wird über einen Timer alle 1ms der Pin high gesetzt, aber in einer
> ISR kann ich die Daten ja nicht empfangen, also auch beim Empfangen mit
> dem UART Interrupt arbeiten?

Das ist wieder eine ganz andere Geschichte.
Auf der Empfangsseite kannst du auch mit Interrupts arbeiten.
Die UART empfängt ein Zeichen ganz von alleine. Wenn es eines
hat (Achtung: da ist es wieder. Ein Ereignis ist eingetreten,
ein Zeichen wurde empfangen), dann passiert was: Genau
ein Interrupt wird ausgelöst.


Eventuell wäre es sinnvoll, du legst dein Projekt mal
zur Seite und schaust dir in einem Testprojekt einfach mal
an, welche Möglichkeiten man mit dem UART hat.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Ich widerspreche Karl Heinz ja selten, aber...

Karl heinz Buchegger wrote:

> Ein Interrupt tritt immer als Reaktion auf ein Ereignis auf. Welches
> ist das Ereignis? Das Ereignis ist, dass die UART Hardware das
> übergebene Zeichen soweit rausgeschickt hat, dass es das nächste
> Zeichen annehmen könnte.

Dieses ,Ereignis' ist aber praktisch immer präsent, wenn die UART
nichts zu tun hat: dann ist ihr (Sende-)Datenregister nämlich immer
leer.  Damit genügt also das bloße Aktivieren dieses Interrupts, um
wenige Takte später gleich einen IRQ zu bekommen.

>> ... Muss ich den UART Interrupt wieder
>> abschalten wenn ich nicht's senden will?

> Nein.

Doch, muss er in diesem Falle.  Wenn man nichts mehr zu senden hat,
muss man den UDRE-Interrupt wieder ausschalten, sonst triggert er
(aus eben beschriebenem Grunde) immer wieder.

Ob man nun das erste Zeichen in die UART reinstopft, bevor man den
UDRE-Interrupt freigibt oder es gleich der ISR überlässt, dieses
Zeichen auszugeben, ist eher nebensächlich.

von Karl H. (kbuchegg)


Lesenswert?

Jörg Wunsch wrote:
> Ich widerspreche Karl Heinz ja selten, aber...

Tus ruhig :-)

>
> Karl heinz Buchegger wrote:
>
>> Ein Interrupt tritt immer als Reaktion auf ein Ereignis auf. Welches
>> ist das Ereignis? Das Ereignis ist, dass die UART Hardware das
>> übergebene Zeichen soweit rausgeschickt hat, dass es das nächste
>> Zeichen annehmen könnte.
>
> Dieses ,Ereignis' ist aber praktisch immer präsent, wenn die UART
> nichts zu tun hat: dann ist ihr (Sende-)Datenregister nämlich immer
> leer.  Damit genügt also das bloße Aktivieren dieses Interrupts, um
> wenige Takte später gleich einen IRQ zu bekommen.

Ich hatte eigentlich eher den TX Complete Interrupt im Auge. Ich
wusste aber nicht mehr wann der Int. jetzt exakt auftritt, daher
die eher nebulöse Beschreibung da oben. War wohl etwas misverständlich
formuliert.


von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Der Vorteil von UDRE statt TXC ist, dass man bereits wieder Daten in
das tx buffer register schreiben kann, während die aktuellen Daten
gerade rausgeschoben werden.  Dadurch kann man eine "Rücken an
Rücken"-Übertragung realisieren, d. h. nach dem Stop-Bit folgt
wirklich sofort ein Startbit.

Wenn man TXC statt UDRE nimmt, stimmt Karl Heinz' Beschreibung
100%ig.

von Profibauer (Gast)


Lesenswert?

Beitrag "3 Atmega8 mit Atmega32 verbinden"

Info: hier gab es das Problem schon einmal.

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.