PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [ERLEDIGT] Atmega: Prozessorauslastung feststellen und Fehlerhandling



schorsch_76
02.09.2014, 12:53
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?

PICture
02.09.2014, 13:16
Hallo!


Ich würde gern wissen wie es um die CPU Zeit auf meinem Atmega aussieht.

Ich denke, dass bei in einer endloser Schleife laufendem Programm die CPU immer 100 % ausgelastet ist. :confused:

Sisor
02.09.2014, 13:41
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%.

schorsch_76
02.09.2014, 14:03
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?

oberallgeier
02.09.2014, 14:33
... Prozessorauslastung ... auf meinem Atmega ... I2C/UART/SPI läuft im Interrupt ... über fifos gefüttert ...Diese Messungen sind so ne Sache.

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:

... Wie macht ihr bsp. das Fehlerhandling bei nicht antwortenden Slaves ...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 :

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 Fehlermeldung

das sieht dann in der *.lls so aus (und da drin stehen dann wirklich auch die entsprechenden Zeilen mit dem Quellcode ! ! !) :
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

markusj
02.09.2014, 15:48
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.

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

Besserwessi
02.09.2014, 18:21
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.

schorsch_76
02.09.2014, 19:00
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

PICture
02.09.2014, 19:25
Also kann ich hier einen ähnlichen Ansatz wählen wie unter Industriesteuerungen.

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/PIC_Assembler#Multitasking . ;)

Besserwessi
02.09.2014, 19:31
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.

schorsch_76
02.09.2014, 19:48
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/PIC_Assembler#Multitasking . ;)
Ok, sowas hab ich unter den AVR's noch nirgends gesehen Nicht mal gelesen oder drüber gestolpert. :eek:

peterfido
14.10.2014, 20:18
Wenn Daten per IRQ reinkommen, dann variiert die "Idle"-Zeit abhängig von den reinkommenden Daten. Ich habe das mal so gelöst, dass ich 1000 Durchläufe der Hauptschleife per Symbol im LCD angezeigt habe. Man könnte diese Zeit natürlich auch genauer als Pi mal Auge messen ;) Wird es knapp, kann man durch einen höheren Takt wieder etwas Zeit gewinnen.