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
Hier nochmal der ganze Code:
//HEADERDATEIEN
#include <avr/io.h>
#include <util/delay.h>
#include <avr/own_lcd.h>
#include <avr/own_uart.h>
#include <avr/interrupt.h>
#include <avr/stdint.h>
#include <math.h>
//FESTLEGUNG UNABHÄNGIGER TYPENNAMEN
typedef unsigned int uint16;
typedef signed int sint16;
typedef unsigned char uint8;
typedef signed char sint8;
//DEFINITION GLOBALER VARIABLEN
volatile uint8 SCA_L; //unteres Register vom SCA
volatile uint8 SCA_H; //oberes Register vom SCA
uint16 SCA_HH; //oberes Register nach Bitmanipulation
uint16 SCA_LL; //unteres Register nach Bitmanipulation
double SCA_wert=0;
double SCA_wert1=0;
double SCA_wert2=0;
double SCA_wert3=0;
double winkel;
//FUNKTIONSPROTOTYPEN
void nm_intro (void);
//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
}
//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;
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
}
//Ermittlung des Maximums
if((SCA_wert3<SCA_wert2)&&(SCA_wert2<SCA_wert1))
{
set_cursor(0,1);
lcd_string("XXXXXXX");
}
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
SCA_wert1=0;
SCA_wert2=0;
SCA_wert3=0;
_delay_ms(100);
}
return 0;
}
Tom wrote: > Ich habe leider keine Ahnung, wie ich das mit der Interruptsteuerung > realisieren soll, bzw. wann/wo das Programm nach Empfang von UART > weitermacht. Dann lies dich ein. Startpunkt ist z.B. http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Interruptbetrieb Leider noch eine Baustelle, aber unter den sort angegebenen Links ist schon was hilfreiches. Eine weitere Infoquelle ist http://www.roboternetz.de/wissen/index.php/UART_mit_avr-gcc#Variante_2:_Mit_Interrupts
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
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.