Hallo zusammen!
Zur Prozessorauslastung:
Ich würde gern wissen wie es um die CPU Zeit auf meinem Atmega aussieht. Nur .. wie feststellen. Ich habe keinen Taskmanager
Mein Programmaufbau ist eigentlich immer gleich: I2C/UART/SPI läuft im Interrupt und wird über fifos gefüttert und gelesen. Hauptprogramm macht den Rest wie die generellen Abläufe, stößt dann über die Fifos senden and Display etc an. Nur: Wie kann ich sagen wieviel CPU Zeit ich noch zu Verfügung habe. Einfach über einen Ausgang und Oszi die Zeit eines Main while Schleifen Durchgang messen?
Wie macht ihr das? Klar kommt das darauf an, wie viel Zeit ich für eine Schleife haben will.
Zum Fehlerhandling:
Sagenb wir mal es fällt ein I2C Slave aus. Dann bekomm ich ja vom I2C eine Antwort dass der Slave nicht antwortet. Hier kann man bsp. im Display eine Seite machen die Zeigt ob die Slaves i.O. sind. und für den "Gesamtstatus" noch eine klassische LED direkt am Atmega.
Wie macht ihr das?
MfG (Mit feinem Grübeln) Wir unterstützen dich bei deinen Projekten, aber wir entwickeln sie nicht für dich. (radbruch) "Irgendwas" geht "irgendwie" immer...(Rabenauge) Machs - und berichte.(oberallgeier) Man weißt wie, aber nie warum. Gut zu wissen, was man nicht weiß. Zuerst messen, danach fragen. Was heute geht, wurde gestern gebastelt. http://www.youtube.com/watch?v=qOAnVO3y2u8 Danke!
Du vermischt hier ein bischen was...
Betriebssysteme managen die CPU-Nutzung normalerweise via Dispatcher / Prozess-Scheduler.
Da Atmegas aber in den seltensten Fällen mit Betriebssystem laufen, musst du selbst die Sytem-Resourcen verwalten.
Ansonsten ist die CPU-Auslastung ist immer 100%.
Na ich kenn das bsp. von Beckhoffsteuerungen. Hier läuft auch der Echtzeitteil in einer Endlosschleife, aber in Zeitscheiben. Ok. Ich werd einfach den Durchlauf einer Main while schleife messen.
Wie macht ihr bsp. das Fehlerhandling bei nicht antwortenden Slaves?
Diese Messungen sind so ne Sache.... Prozessorauslastung ... auf meinem Atmega ... I2C/UART/SPI läuft im Interrupt ... über fifos gefüttert ...
Du KÖNNTEST natürlich in jeder ISR zu Beginn ne LED anknippsen und vorm RETI wieder ausmachen. Das wäre ziemlich real, total chaotisch und es fehlt dabei der ISR-Overhead zum Sichern und Wiederbeleben von Registern etc.
Die andere Möglichkeit ist etwas Handarbeit (Kopfarbeit): in der Datei deinfile.lls steht der komplette Code als Hex, dazu disassembliert - also Mnemonics - und als Assemblercode mit den zugehörigen, absoluten Adressen. Hier kannst Du Dir also anhand der jeweiligen Befehlslaufdauer (Zyklenzahl) und der Anzahl Befehle die benötigte Laufdauer (beachte die Zeit pro Befehl - abhängig vom Quarztakt) von Programmabschnitten, Subroutinen und ISRs errechnen.
Nachtrag zu Fehlerbehandlung:
Je nach (z.B. I²C-) Bibliothek bzw. Gestaltung der Subroutinen muss evtl. der Hänger abgefangen werden. Und die Fehlererkennung/-meldung/-behandlung geht bei mir z.B. so :... Wie macht ihr bsp. das Fehlerhandling bei nicht antwortenden Slaves ...
das sieht dann in der *.lls so aus (und da drin stehen dann wirklich auch die entsprechenden Zeilen mit dem Quellcode ! ! !) :Code:if(!(i2c_start(SLAVE_MoCo+I2C_WRITE))) //Slave bereit zum schreiben? { // i2cdmy = i2c_write (0x01); // Buffer Startadresse 01 setzen i2cdmy = i2c_write (byte1); // zum Schreiben. 01 {0, 10} i2cdmy = i2c_write (byte2); // i2cdmy = i2c_write (byte3); // i2c_stop(); // Zugriff beenden } // else // Melde jetzt: Kein Byte geschrieben { // und Fehlerblinken uputs0("\r\n\tKein I2C-Schreiben möglich.\r\n"); // i2cerr = 0b00000010; // i2c-write nicht möglich for(i=0; i<2; i++) { // Fehlermeldung: LED i-fach blinken SetBit(PORTC, 2); // LED EIN, HELL waitms(3); // ClrBit(PORTC, 2); // LED AUS, Dunkel } // Ende if(!(i2c_start(SLAVE_MoCo+I2C_WRITE))) // Schreiben an Slave ist erledigt oder nicht - dann FehlermeldungCode:if(!(i2c_start(SLAVE_MoCo+I2C_WRITE))) //Slave bereit zum schreiben? 1d38: 82 e8 ldi r24, 0x82 ; 130 1d3a: 0e 94 6d 00 call 0xda ; 0xda <i2c_start> 1d3e: 88 23 and r24, r24 1d40: d9 f5 brne .+118 ; 0x1db8 <I2CTST01+0x10c> { // i2cdmy = i2c_write (0x01); // Buffer Startadresse 01 setzen 1d42: 81 e0 ldi r24, 0x01 ; 1 1d44: 0e 94 c6 00 call 0x18c ; 0x18c <i2c_write> i2cdmy = i2c_write (byte1); // zum Schreiben. 01 {0, 10} 1d48: 80 2f mov r24, r16 1d4a: 0e 94 c6 00 call 0x18c ; 0x18c <i2c_write> i2cdmy = i2c_write (byte2); // 1d4e: 81 2f mov r24, r17 1d50: 0e 94 c6 00 call 0x18c ; 0x18c <i2c_write> i2cdmy = i2c_write (byte3); // 1d54: 8f 2d mov r24, r15 1d56: 0e 94 c6 00 call 0x18c ; 0x18c <i2c_write> Terminates the data transfer and releases the I2C bus *************************************************************************/ void i2c_stop(void) { /* send stop condition */ TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO); 1d5a: 60 92 bc 00 sts 0x00BC, r6 // wait until stop condition is executed and bus released while(TWCR & (1<<TWSTO)); 1d5e: 80 91 bc 00 lds r24, 0x00BC 1d62: 84 fd sbrc r24, 4 1d64: fc cf rjmp .-8 ; 0x1d5e <I2CTST01+0xb2> i2c_stop(); // Zugriff beenden // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uputs0("\t3 Bytes an Slave\t"); // Melde: Bytes wurden geschrieben an 1d66: 85 e1 ldi r24, 0x15 ; 21 1d68: 94 e0 ldi r25, 0x04 ; 4 1d6a: 0e 94 b1 01 call 0x362 ; 0x362 <uputs0_> uputs0i (SLAVE_MoCo+I2C_WRITE); // Slaveadresse 1d6e: 82 e8 ldi r24, 0x82 ; 130 1d70: 90 e0 ldi r25, 0x00 ; 0 1d72: 0e 94 b7 0b call 0x176e ; 0x176e <uputs0i> uputs0(" =>\t"); // 1d76: 88 e2 ldi r24, 0x28 ; 40 1d78: 94 e0 ldi r25, 0x04 ; 4 1d7a: 0e 94 b1 01 call 0x362 ; 0x362 <uputs0_> uputs0i(byte1); uputs0(" "); 1d7e: 80 2f mov r24, r16 1d80: 90 e0 ldi r25, 0x00 ; 0 1d82: 0e 94 b7 0b call 0x176e ; 0x176e <uputs0i> 1d86: 8f e2 ldi r24, 0x2F ; 47 1d88: 94 e0 ldi r25, 0x04 ; 4 1d8a: 0e 94 b1 01 call 0x362 ; 0x362 <uputs0_> uputs0i(byte2); uputs0(" "); 1d8e: 81 2f mov r24, r17 1d90: 90 e0 ldi r25, 0x00 ; 0 1d92: 0e 94 b7 0b call 0x176e ; 0x176e <uputs0i> 1d96: 8f e2 ldi r24, 0x2F ; 47 1d98: 94 e0 ldi r25, 0x04 ; 4 1d9a: 0e 94 b1 01 call 0x362 ; 0x362 <uputs0_> uputs0i(byte3); uputs0(" "); 1d9e: 8f 2d mov r24, r15 1da0: 90 e0 ldi r25, 0x00 ; 0 1da2: 0e 94 b7 0b call 0x176e ; 0x176e <uputs0i> 1da6: 8f e2 ldi r24, 0x2F ; 47 1da8: 94 e0 ldi r25, 0x04 ; 4 1daa: 0e 94 b1 01 call 0x362 ; 0x362 <uputs0_> uputs0("\r\n"); // Zeilenvorschub 1dae: 87 e4 ldi r24, 0x47 ; 71 1db0: 91 e0 ldi r25, 0x01 ; 1 1db2: 0e 94 b1 01 call 0x362 ; 0x362 <uputs0_> 1db6: 1c c0 rjmp .+56 ; 0x1df0 <I2CTST01+0x144> } // else // Melde jetzt: Kein Byte geschrieben { // und Fehlerblinken uputs0("\r\n\tKein I2C-Schreiben möglich.\r\n"); // 1db8: 89 e5 ldi r24, 0x59 ; 89 1dba: 94 e0 ldi r25, 0x04 ; 4 1dbc: 0e 94 b1 01 call 0x362 ; 0x362 <uputs0_> i2cerr = 0b00000010; // i2c-write nicht möglich 1dc0: 30 92 3f 0f sts 0x0F3F, r3 1dc4: 20 e0 ldi r18, 0x00 ; 0 for(i=0; i<2; i++) { // Fehlermeldung: LED i-fach blinken SetBit(PORTC, 2); // LED EIN, HELL 1dc6: 42 9a sbi 0x08, 2 ; 8 1dc8: c6 01 movw r24, r12 1dca: 01 97 sbiw r24, 0x01 ; 1 1dcc: f1 f7 brne .-4 ; 0x1dca <I2CTST01+0x11e> 1dce: c6 01 movw r24, r12 1dd0: 01 97 sbiw r24, 0x01 ; 1 1dd2: f1 f7 brne .-4 ; 0x1dd0 <I2CTST01+0x124> 1dd4: c6 01 movw r24, r12 1dd6: 01 97 sbiw r24, 0x01 ; 1 1dd8: f1 f7 brne .-4 ; 0x1dd6 <I2CTST01+0x12a> waitms(3); // ClrBit(PORTC, 2); // LED AUS, Dunkel 1dda: 42 98 cbi 0x08, 2 ; 8 1ddc: 8f e2 ldi r24, 0x2F ; 47 1dde: 90 e0 ldi r25, 0x00 ; 0 1de0: f6 01 movw r30, r12 1de2: 31 97 sbiw r30, 0x01 ; 1 1de4: f1 f7 brne .-4 ; 0x1de2 <I2CTST01+0x136> // ============================================================================= = // ============================================================================= = //### Programm pausieren lassen !! Der Pausenwert ist nur experimentell ! void waitms(uint16_t ms) // { // for(; ms>0; ms--) 1de6: 01 97 sbiw r24, 0x01 ; 1 1de8: d9 f7 brne .-10 ; 0x1de0 <I2CTST01+0x134> } // else // Melde jetzt: Kein Byte geschrieben { // und Fehlerblinken uputs0("\r\n\tKein I2C-Schreiben möglich.\r\n"); // i2cerr = 0b00000010; // i2c-write nicht möglich for(i=0; i<2; i++) { // Fehlermeldung: LED i-fach blinken 1dea: 2f 5f subi r18, 0xFF ; 255 1dec: 22 30 cpi r18, 0x02 ; 2 1dee: 59 f7 brne .-42 ; 0x1dc6 <I2CTST01+0x11a> } // Ende if(!(i2c_start(SLAVE_MoCo+I2C_WRITE))) // Schreiben an Slave ist erledigt oder nicht - dann Fehlermeldung
Geändert von oberallgeier (02.09.2014 um 16:22 Uhr) Grund: Nachtrag zur Fehlerbehandlung
Ciao sagt der JoeamBerg
Du musst mit der "freien" Zeit etwas Anfangen das Messbar ist. Wenn deine Hauptschleife so schnell läuft wie möglich, hast du 100% Auslastung. Wenn deine Hauptschleife (etwa über einen Timer) getaktet läuft, verbringt sie einen Teil der Zeit damit, auf einen Zählerstand oder Interrupt zu warten. In dieser Warteschleife könntest du nun einen Pin togglen und dann auf einem Oszi das Verhältnis zwischen den Ruhephasen und Umschaltphasen messen. Oder du inkrementierst einen Zähler, misst parallel dazu die Zeit und kannst dann über die Anzahl Instruktionen je Zählerinkrement durch die Anzahl der Takte/Zeiteinheit die Prozessorauslastung ermitteln. Der erste Ansatz hat den Vorteil, dass du die Worst-Case-Situation erkennen kannst (etwa weil verschiedene Interrupts zusammen kommen).
Noch eine Anmerkung zu den 100%. Wenn du deinen Prozessor schlafen legst, durch einen Timer-Interrupt aufwachst und eine LED an/ausschaltest, hast du auch 100% Auslastung. Nämlich 100% der Takte in denen die CPU überhaupt läuft
mfG
Markus
Tiny ASURO Library: Thread und sf.net Seite
Bei so etwas wie UART, IC2 und ähnliches per Interrupt ist weniger die Prozessorauslastung (in der Regel 100% wenn die Hauptschleife nicht definiert wartet), sondern oft mehr der Anteil den die CPU in den ISRs im Mittel verbringt und dann die Worst case Antwortzeit für Interrupts. Den Anteil der Interrupts an der Gesamtlaufzeit lässt sich ggf. über die Geschwindigkeit der Hauptschleife messen, sofern die noch relativ einfach ist - sonst kann man besser rechnen aus den Aufruf-Frequenzen für die ISRs und die jeweiligen Laufzeiten. Die Interrupts Latenz kann man Rechnen: die Laufzeit der einzelnen ISRs lässt sich meist ganz gut im Simulator bestimmen. Den Worst case kann man dann ausrechnen als die Verzögerung durch die ggf. vorrangig ausgeführten ISRs + die eine die noch zu Ende erledigt werden muss und ggf. Verzögerungen aus CLI-SEI Blöcken.
Es sind übliche weise die 2 Bedingungen die passen müssen: Einhalten einer maximalen Verzögerung für Interrupts und genügend restliche Rechenzeit für da Hauptprogramm.
Also kann ich hier einen ähnlichen Ansatz wählen wie unter Industriesteuerungen. Meine maximale Zeitscheibe definieren, bsp. 1 ms, in der meine Aktoren bedient werden sollen. Worst case Bedingungen herausfinden und optimieren.
Ich habe eben die ganzen Interface Geschichten mit Absicht als IRQ laufen lassen, damit ich eben sowohl SPI, UART, und TWI parallel arbeiten lassen kann. Bsp. twimaster.c von P.Fleury ist ja polling basiert. Hier kann immer nur eine Sache, der TWI laufen. (ok ok, andere IRQs gehen auch, aber der TWI ist hier gepollt.) Meine IRQ basierten Interface Geschichten haben eben Abfragen wie "is_busy()/is_error()" um dann im Scheduler was anderes machen zu können wenn bsp. UART oder TWI noch nicht fertig ist. Alle Aktionen welche bsp. über TWI laufen müssen natürlich sequentiell laufen, wenn ich wie beim Atmega nur ein TWI habe ...das muss ich sicherstellen in meinem Scheduler, der ja "Auftraggeber" über die fifos ist. Damit habe ich aus Softwaresicht ein schön gelayertes Design. Oben der Scheduler und im unteren Layer die Hardwareschicht.
Bei Industriesteuerungen läuft halt der main task immer im fixen Interval. Bei Zeitscheibenverletzungen kann man, muss man aber nicht, sollte aber, reagieren. Mir ging es darum zu erfahren wie ihr die Zykluszeiten aus dem Atmega herausholt. Sprich die Messwerte. Da scheint mir die Methode mit dem Togglepin von Markus recht gut. Das muss ich ausprobieren! Die Zählvariante der Takte ist sicher die genauste, aber aufwendigste. Danke für die Tipps an alle!
Gruß
Georg
Das weiß ich nicht, weil ich Industriesteuerungen zu gering kenne, aber bei µCs läuft es meistens so: http://rn-wissen.de/wiki/index.php/P...r#Multitasking .
MfG (Mit feinem Grübeln) Wir unterstützen dich bei deinen Projekten, aber wir entwickeln sie nicht für dich. (radbruch) "Irgendwas" geht "irgendwie" immer...(Rabenauge) Machs - und berichte.(oberallgeier) Man weißt wie, aber nie warum. Gut zu wissen, was man nicht weiß. Zuerst messen, danach fragen. Was heute geht, wurde gestern gebastelt. http://www.youtube.com/watch?v=qOAnVO3y2u8 Danke!
In der Realen HW kann man per Toogle/Signal Pin messen.
Das Zählen / Aufaddieren der Takte kann einem größtenteils der Simulator abnehmen: einfach 2 Break-Punkte auf den Anfang und das Ende der ISR, und dann den Zyklenzähler nutzen. Der Aufwand hält sich so in Grenzen - mit etwas Übung eher einfacher als über die Hardware.
Lesezeichen