Hallo zusammen,
in meinem Projekt kommunizieren zwei ATmega8 via RS232 zusammen. Einer
liest ständig einen Sensor aus und schickt den aktuellen Wert (immer
zwei Datenpakete) an den zweiten ATmega8. Dieser empfängt die beiden
Datenpakete, rechnet sie geeignet um. Dieser Wert wird wiederum
verrechnet und ausgegeben.
Meine Frage ist nun:
Ich möchte immer zwei Datenpakete einlesen, den Wert speichern, die
nächsten zwei einlesen und speichern usw. Wie kreiere ich hierzu passend
die Interrupt-Steuerung?
anbei der Code der Arbeitsschleife:
while(1)
{
//lokale Variablen zur Visualisierung des aktuellen Winkels
uint8 zehner;
uint8 einer;
uint8 nachkomma;
SCA_HH = (SCA_H<<3); //Bitmanipulation MSB
SCA_LL = (SCA_L>>5); //Bitmanipulation LSB
SCA_wert=SCA_HH+SCA_LL; //Addition beider Bytes
//Erfassung dreier Werte um Maximum zu ermitteln
if((SCA_wert1==0)&&(SCA_wert2==0)&&(SCA_wert3==0))
{
SCA_wert1=SCA_wert;
UCSRB |= (1<<RXCIE); //Empfängerinterrupt frei
sei(); //Alle Interrupts global frei
}
if((SCA_wert1!=0)&&(SCA_wert2==0)&&(SCA_wert3==0))
{
SCA_wert2=SCA_wert;
UCSRB |= (1<<RXCIE); //Empfängerinterrupt frei
sei(); //Alle Interrupts global frei
}
if((SCA_wert1!=0)&&(SCA_wert2!=0)&&(SCA_wert3==0))
{
SCA_wert3=SCA_wert;
UCSRB |= (1<<RXCIE); //Empfängerinterrupt frei
sei(); //Alle Interrupts global frei
}
winkel = asin((SCA_wert-1024)/1638); //Winkel errechnen über Formel
aus Datenblatt des SCA61T
winkel=(winkel*180)/M_PI; //Umrechnen von RAD in DEG
winkel = winkel*10;
set_cursor(3,1);
lcd_string("d&b J-Serie");
if(winkel >= 0)
{
set_cursor(2,2);
lcd_string("Winkel: ");
zehner=winkel/100;
lcd_data(zehner+0x30);
einer=(winkel-(zehner*100))/10;
lcd_data(einer+0x30);
lcd_data('.');
nachkomma=(winkel-(zehner*100)-(einer*10));
lcd_data(nachkomma+0x30);
lcd_data(0xdf);
lcd_data(' ');
}
if(winkel < 0)
{
winkel=winkel*(-1);
set_cursor(2,2);
lcd_string("Winkel: -");
zehner=winkel/100;
lcd_data(zehner+0x30);
einer=(winkel-(zehner*100))/10;
lcd_data(einer+0x30);
lcd_data('.');
nachkomma=(winkel-(zehner*100)-(einer*10));
lcd_data(nachkomma+0x30);
lcd_data(0xdf);
}
UCSRB |= (1<<RXCIE); //Empfängerinterrupt frei
sei(); //Alle Interrupts global frei
_delay_ms(100);
}
return 0;
}
ist der Ansatz i. O. oder totaler Schwachsinn?
Gruß
Tom
Was sollen denn die vielen Interruptfreigaben?
Eine reicht völlig, der MC ist ja nicht dement.
Und ohne Interrutphandler schmiert Dir die Kiste eh ab.
Peter
Von deinen Interruptroutinen sieht man nicht viel. Ich würde es mit dem
klassischen interruptverfahren machen. Dazu gibt es auch schon massig
Beispielcode, deshalb hier ganz kurz in Prosa:
Vor der while(1)-Schleife im Userprogramm würde ich die UART
initialisieren und deren Interrupts einmalig aufsetzen. (Fast) die
ganzen UART- und Interruptfummeleien in deinem while(1) entfallen.
Der RX-Interrupthandler schreibt empfangene Bytes in einen Puffer und
führt Buch, ob der Puffer leer, teilgefüllt oder gar voll ist. Die
Puffergrösse kannst du frei festlegen. Ich würde den so dimensionieren,
dass keine Zeichen verloren gehen, aber nicht zuviel Platz vom kostbaren
RAM verloren geht.
Den aktuellen Pufferstand (per volatile Variable) kannst du im
Userprogramm benutzen, um festzustellen, ob mindestens 2 Bytes (= ein
Datenpaket) vorhanden sind und abgearbeitet werden können. Die
notwendige Interruptsperrung beim Transfer vom Empfangspuffer in den
Verarbeitungspuffer wurde ich so kurz wie möglich halten und auch
zentral an einer Stelle im Userprogramm unterbringen.
Ich würde mich nicht allein auf die Zählung der Bytes verlassen, sondern
auch eine Synchronisation einbauen. Im einfachsten Fall ein Start- oder
Endebyte für ein Datenpaket nach jedem/jedem x.ten Datenpaket.
Ich sehe mömentan nicht das Bild hinter dem 2 AVR Konzept, d.h. weshalb
der Sensor-AVR nicht die Aufgaben des LCD-AVR mit übernehmen kann.
Denkbar wäre vielleicht ein Mangel an IO-Pins oder eine räumliche
Trennung von Sensor-AVR und LCD-AVR.
Vorausgesetzt 2 AVRs sind nötig, würde ich den Datentransfer in ASCII
machen, d.h. dem sensor-AVR auch die itoa Wandlung aufbürden. Dann kann
man auch einen PC, PDA, Laptop... anschliessen und den Sensor-AVR
debuggen. Den LCD-AVR würdest du dann quasi als einfaches *aber
universelles* RS232-LCD-Terminal betreiben. Ich schätze da gibt es sogar
fertige Projekte zu.
Danke für Deine schnelle Antwort.
Wie würdest Du dieses Vorhaben realisieren?
Rauskommen sollte eine Maximalwertbestimmung der erhaltenen Werte (wie
gesagt je zwei Datenpakete). Wenn der Maximalwert gefunden wurde, dann
einige Werte aufzeichnen bis zum Minimum und aus diesen n Werten den
Durchschnitt berechnen.
Ich habe leider keine Ahnung, wie ich das mit der Interruptsteuerung
realisieren soll, bzw. wann/wo das Programm nach Empfang von UART
weitermacht.
Gruß
Tom
Tom wrote:
> //SERVICEROUTINE FÜR EMPFÄNGERINTERRUPT> SIGNAL(SIG_UART_RECV)> {> //Einlesen zweier Werte über RS232 und Übergabe an Hilfsvariablen> getch();> SCA_H = got_cha;> getch();> SCA_L = got_cha;> cli(); //Alle Interrupts global sperren> }
Das gefällt mir nicht, weil hier eine Lowlevel-Routine
(Interrupthandler) und Highlevelroutine (getch()) gemixt werden und die
Aufrufsequenz (1 Iterrupt pro Zeichen) nicht beachtet wird. Nebenbei ist
die SIGNAL Schreibweise inzwischen durch die modernere ISR Schreibweise
abgeläst (s. AVR-GCC Manual)
Nach
http://winavr.scienceprog.com/avr-gcc-tutorial/interrupt-driven-avr-usart-communication.html
würde der Interrupthandler z.B. so aussehen
volatile char rx_bytes;
ISR(USART_RXC_vect)
{
switch (rx_bytes)
{
case 2:
// Zeichen empfangen ohne dass Userprogramm die vorhergehenden
// verarbeitet hat. Lösung hier: Altes Datenpaket verwerfen.
rx_bytes = 0;
/* Durchfall */
case 0:
SCA_H = UDR;
break;
case 1:
SCA_L = UDR;
break;
}
rx_bytes++;
}
Und in dem Userprogramm
while(1)
{
if (rx_bytes == 2)
{
// 2 Bytes empfangen, jetzt auslesen
// Während Zugriff auf SCA_H und SCA_L und rx_buffer INTs sperren!
// Hier Brute-Force-Lösung. Kann man mit SREG schöner lösen
// s. Literatur bei atomic Zugriff
cli();
SCA_HH = SCA_H;
SCA_LL = SCA_L;
rx_bytes = 0;
sei();
// Restliche Berechnungen...
SCA_HH <<= 3; //Bitmanipulation MSB
SCA_LL >>= 5; //Bitmanipulation LSB
SCA_wert=SCA_HH+SCA_LL; //Addition beider Bytes
// ...
}
}
auch bei case 2: muss das UDR Register gelesen werden, sonst wird der
IRQ nicht gelöscht..
ich würde am anfang UDR in ei9ne temp. Var auslesn und dann erst switch
case
Hallo zusammen,
also nochmals vielen Dank für Eure Hilfe. Mittlerweile verstehe ich die
ganze Interrupt-Sache besser.
Jetzt habe ich meine Code folgendermaßen realisiert:
//SERVICEROUTINE FÜR EMPFÄNGERINTERRUPT
ISR(USART_RXC_vect)
{
rx_temp = UDR;
switch (rx_bytes)
{
case 2:
// Zeichen empfangen ohne dass Userprogramm die vorhergehenden
// verarbeitet hat. Lösung hier: Altes Datenpaket verwerfen.
rx_bytes = 0;
/* Durchfall */
case 0:
SCA_H = UDR;
break;
case 1:
SCA_L = UDR;
break;
}
rx_bytes++;
}
//HAUPTPROGRAMM
int main (void)
{
lcd_init(); //Initialisieren des Displays
nm_intro(); //Anzeige nach Start des Controllers
initusart(); //RS232 initialisieren (in own_uart.h)
lcd_clear();
set_cursor(0,1);
while(1)
{
//lokale Variablen zur Visualisierung des aktuellen Winkels
uint8 zehner;
uint8 einer;
uint8 nachkomma;
if(rx_bytes==2)
{
cli();
SCA_HH = SCA_H;
SCA_LL = SCA_L;
rx_bytes=0;
UCSRB |= (1<<RXCIE); //Empfängerinterrupt frei
sei(); //Alle Interrupts global frei
SCA_HH <<= 3; //Bitmanipulation MSB
SCA_LL >>= 5; //Bitmanipulation LSB
SCA_wert=SCA_HH+SCA_LL; //Addition beider Bytes
winkel = asin((SCA_wert-1024)/1638); //Winkel errechnen über
Formel aus Datenblatt des SCA61T
winkel=(winkel*180)/M_PI; //Umrechnen von RAD in DEG
winkel = winkel*10;
///.... WEITERER CODE
_delay_ms(100);
}
else
{
cli();
UCSRB |= (1<<RXCIE); //Empfängerinterrupt frei
sei();
}
}
return 0;
}
Das Programm mach nun genau das, was es vor der Änderung gemacht hat.
Was ja schon mal nicht schlecht ist.
Fragen:
Was ist in den Fällen rx_bytes==1 oder rx_bytes==0?
Wie schaffe ich es, viele Werte einzulesen und daraus den Mittelwert zu
bilden?
Besten Dank für Eure Hilfe!!
rx_bytes ist ein Zähler, wieviele Bytes die RX Interruptroutine in die
beiden Variablen SCA_L und SCA_H gefüllt hat.
Bei rx_bytes == 0 und rx_bytes == 1 ist der Inhalt der variablen nicht
legal auswertbar, da entweder noch keine neuen oder nur teilweise
empfangene Bytes eingetragen sind.
Nur bei rx_bytes == 2 wurden die beiden zuletzt empfangenen Bytes
eingetragen und das Userprogramm darf das auswerten. Der "Durchfall" bei
rx_bytes == 2 zum Fall case 0: in der ISR berücksichtigt die Anmerkung
von Jan-h. B.. UDR wird in diesem Fall auch gelesen - allerdings startet
in dem Fall ein neues Datenpaket. Das letzte Datenpaket ist somit für
das Userprogramm verloren.
In dem Zusammenhang halte ich auch das _delay_ms(100); für ungeschickt.
Während dieses Trödelns können Zeichen verloren gehen. Wenn die Pause
nur dazu da ist, eine stabilere LCD anzeige zu gewährleisten (kein
Flackern) würde ich das anders machen z.b. den aktuellen Wert der
Anzeige zwischenspeichern und ein Update der Anzeige bei einem neuen
Wert nur machen, wenn sich zwischengespeicherter Wert und neuer Wert
unterscheiden.
Ich habe oben dem Userprogramm die Möglichkeit gegeben, anch dem
Auswerten (Auslesen) rx_bytes zu "Nullen". Wenn kein neues Zeichen oder
Datenpaket empfangen wird, so verhindert, dass der alte Wert nochmal
ausgelesen wird.
Das Fummeln im Userprogramm an
UCSRB |= (1<<RXCIE); //Empfängerinterrupt frei
und
else
{
cli();
UCSRB |= (1<<RXCIE); //Empfängerinterrupt frei
sei();
}
würde ich unterlassen.
Der RX-Interrupt läuft (fast, s.u.) immer durch. Die Freigabe des
Interrupts sollte in initusart(); //RS232 initialisieren (in own_uart.h)
geschehen. Wenn in initusart() kein sei() gemacht wird, ist vor dem
while(1) ein sei() einzufügen.
Nur das Auslesen der 16-Bit in SCA_L und SCA_H und das Manipulieren von
rx_bytes im Userprogramm darf nicht durch den RX-Interrupt unterbrochen
werden. Daher hier die cli()/sei() Klammer wenn man nur den UART
Interrupt betreibt. Wenn man weitere Interrupts hat (Timer, ...), die
man global stoppen oder einschalten will, arbeitet man mit dem SREG.
http://www.mikrocontroller.net/articles/Interrupt#Atomarer_Datenzugriff
Die Mittelwertbildung würde ich im Userprogramm machen und zwar ab der
Stelle ///.... WEITERER CODE, wenn eine Mittelwertbildung für winkel
gemacht werden soll. Eine einfache Variante wäre es z.b. die einelnen
Winkel aufzuaddieren und durch die Anzahl der Additionen zu teilen. Das
kann für alle Einzelmessungen geschehen oder nur z.B. für die letzten
10, 100, o.ä. Messungen. Für eine speicherschonende, gleitende
Mittelwertbildung sollte es eigentlich genug Rechenvorschriften und
Democode im Netz geben (habe aber selbst noch nicht gesucht).
Edit: Beitrag "Mittelwert über 3 Werte bilden"
Das Ganze wird nie zuverlässig funktionieren.
Woher weiß denn der Empfänger, welches das 1. und welches das 2. Byte
sein soll ???
Ein Störimpuls oder einer wird resettet oder später eingeschaltet und
schon sind sie asynchron und die Bytes bedeuten nur noch Mist.
Du mußt Dir also zuerst mal ein Protokoll ausdenken, wie der Empfänger
die beiden Bytes eindeutig unterscheiden kann.
Eine Möglichkeit wäre, da Du ja nur 11 Bits übertragen willst, das Bit 7
zur Unterscheidung zu nehmen.
Oder Du nimmst den 9Bit-Mode.
Eine andere, daß beide Bytes innerhalb einer bestimmten Zeit (z.B. 10ms)
reinkommen müssen und zum nächsten Datensatz immer eine größere Zeit
(z.B. 20ms) Abstand ist. Dann kann man einen Timer aufsetzen, um das zu
erkennen.
Oder Du überträgst den Wert als Text mit "\n" als Endekennzeichen.
Peter