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.