7 Fehleranalyse/Debugging

Ein grundsätzliches Problem bei Hardware naher Softwareentwicklung stellt das Debugging und die Programmkontrolle dar.

Es existieren auch für die ATMEL Prozessoren Software-Emulatoren, die Prozessoren und Register, sowie Ein-/Ausgebeports nachbilden können. Diese Werkzeuge stellen eine gute Möglichkeit zum Kennen lernen der Mikrocontrollerhardware oder für Unterrichtszwecke dar, doch eine Reaktion von Hardwarebausteinen, wie z.B. ein Zählsignal am Timer/Counter1 Eingang, ist mit Emulatoren nicht oder nur sehr umständlich abbildbar.

Dennoch kann es oft notwendig sein, dass in der Entwickungsphase an beliebiger Stelle des Programmablaufs eine Systemrückmeldung zur Programmkontrolle erforderlich ist, die von einem kurzen Aufleuchten einer Leuchtdiode bis zur Klartext-Fehlermeldung reichen kann. Diese Systemreaktionen müssen zudem noch einfach ein- und ausschaltbar sein.

Eine kleine Bibliothek von Makros und Unterprogrammen wurde geschaffen, die über eine Assemblerdirektive während der Entwicklungsphase zu Testzwecken bedingt in die Hauptdatei RF_Control.asm eingebunden werden kann.

	; ----------------------------------
	; debug functions
	; ----------------------------------
	.ifdef DEBUG
	.include "debug_fun.inc"   ; debug functions (conditional include)
	.endif 
Um debug-kritische Situationen austesten zu können, wurden in einer weiteren Include-Datei (testfunc.asm) entsprechende Testfunktionen angelegt, die Initialisierungen vornahmen und Unterprogramme zu Testzwecken ausführten.

Die beiden Hilfsdateien befinden sich laut Anhang D.2 im Hauptverzeichnis der Entwicklungsumgebung (siehe auch Absch. 7.5.3).
Makros zur Ausgabe des Inhaltes eines Registers oder Registerpaares wurden erstellt, die keine Seiteneffekte aufweisen und an jeder Stelle des Programms aufgerufen werden können, ohne den Programmablauf (abgesehen vom Status der LCD-Anzeige, in der die Werte ausgegeben werden) sonst irgendwie zu beeinflussen.

	; ----------------------------------
	; display a character as prefix and the
	; hexadecimal value of a register
	;
	; affected: none (not even the flags)
	; Parameter: @0 .. prefix char
	;            @1 .. register
	.macro DEBUG_REGISTER
	  PUSHF                   ; save flags 
	  push  iic_sendbyte
	  push  @1                ; put register value onto stack
	  ldi   iic_sendbyte,@0   ; write out ascii character
	  rcall LCDINIT_SEND_ACHAR
	  pop   iic_sendbyte
	  rcall LCDINIT_SEND_HEX  ; write out hex value
	  pop   iic_sendbyte
	  POPF                    ; restore flags
	.endmacro
Verwendung:

	DEBUG_REGISTER 'R',return2
Ein Beispiel das den Einsatz demonstriert.

In Abschnitt 7.8.1.3, der sich mit der 5-Phasensteuerung beschäftigt, wird darauf hingewiesen, dass der in der Phase I (S. [*]) gemessene ,,Losfahrwert'', als Zielwert für die Phase III (S. [*]) angesteuert wird. Außerdem stellte dieser Rotations-Startwert einen Anhaltspunkt für die Festlegung eines sinnvollen Steuerungs-Startwertes für die Phase I dar. Ein Startwert von 0 würde nur unnötige Wartezeit bewirken, da die Drehbewegung erst bei einem wesentlich höheren Startwert (ca. 130) einsetzt.

Um diese konkreten Werte zu ermitteln, wurde in einer früheren Code-Version das ,,DEBUG_REGISTER'' Makro eingesetzt.

	phase_speed_down:
	  push  temp1
	  push  temp2
	  push  ltmp2
	  STSB  rotation_mode, ModeSpeedDown ; rotation mode is go down
	  lds   temp1, rot_speed_value       ; get current rotation speed value
	  lds   ltmp2, rot_contrl_minimum    ; get value of rotation startup value
	LCDINIT_ERASE_LINE 0
	DEBUG_REGISTER 's',temp1
	DEBUG_REGISTER 'm',ltmp2
	BLINK_RED_LED
	decrease_speed:                      ; decreasing speed until
	  in    temp2, TIMSK                 ; read TIMSK port
	  sbrc  temp2, OCIE1B                ; is bit OCIE1B set?
	  rjmp  speed_slowdown_fast          ; if yes, back to loop 
Die Display-Zeile 0 ist üblicherweise mit einem fixen Text versehen und ,,verdeckt'' durch die Debug-Ausgabe keine betriebsrelevanten Informationen, das Makro ,,LCDINIT_ERASE_LINE'' sichert ebenfalls alle Register und verändert damit keine Register, Flags sind an dieser Stelle nicht relevant. Dies führt zu einer Anzeige, wie sie in Abb. 7.5 beispielhaft dargestellt ist.
Figure 7.5: LCD-Anzeige mit Debug-Ausgabe
\includegraphics[%
width=4cm]{graphics/DebugMode.ps}

Wie werden diese Werte nun interpretiert?

Eine Wartezeit von einer Sekunde mit dem Aufblinken der roten LED informiert den Anwender mit dem Makro ,,BLINK_RED_LED'' über diese Debug-Phase.

Sollten kompliziertere Zustände untersucht werden, die umfangreicheren Code benötigen, wurden diese Routinen als Unterprogramme ebenfalls in die Debug-Datei mit aufgenommen und in Abhängigkeit von der DEBUG-Direktive bedingt über ein Makro aufgerufen.

	; ----------------------------------
	; conditional DEBUG call
	; this is executed if DEBUG directive is set...
	; the subroutine should be between .ifdef DEBUG and .endif
	; to avoid assemblation of the debug code if DEBUG
	; directive is not set
	; ----------------------------------
	; Parameter: @0 .. debug subroutine
	.macro CALL_DEBUG
	.ifdef DEBUG
	rcall @0
	.endif
	.endmacro 
Verwendung:

	CALL_DEBUG show_preproc_state
	CALL_DEBUG debug_rotation_mode
	CALL_DEBUG debug_show_angle 
Diese Aufrufe verzweigen in Unterprogramme, die umfangreichere Statusinformationen ausgeben, wenn die DEBUG-Direktive gesetzt ist, wenn nicht, werden die Anweisungen ignoriert.

Als Beispiel wird die Funktion show_preproc_state betrachtet, die in Version 0.8 der Steuerungssoftware noch vorhanden war und am Ende der Routine rotation_preproc aufgerufen wurde.

	show_prepoc_state:
	  push  temp1
	  PUSHW REG_Z
	  PUSHW REG_B
	  LCDINIT_ERASE_LINE 0
	  INWR  REG_B, TCNT1           ; get current value currpos(REG_B)
	  LCDINIT_WRITE_HEXWORD REG_B
	  LCDINIT_WRITE_CHARACTER '-'
	  LCDINIT_WRITE_HEXWORD REG_A
	  LCDINIT_ERASE_LINE 1
	  LCDINIT_WRITE_CHARACTER '='
	  LCDINIT_WRITE_SRAM rot_angle_godown+1
	  LCDINIT_WRITE_SRAM rot_angle_godown
	  LCDINIT_ERASE_LINE 2
	  in    iic_sendbyte, TIMSK
	  rcall LCDINIT_SEND_BINARY
	  rcall wait_key_confirm
	  POPW  REG_B
	  POPW  REG_Z
	  pop   temp1
	  ret  
Bemerkungen:
Das Unterprogramm rotation_preproc ermittelt unter anderem den Rotations-Endwinkel (REG_A), in Registerpaar REG_B wurde der aktuelle Winkel (Inhalt des Zählers TCNT1) eingelesen. Das Makro ,,LCDINIT_WRITE_SRAM'' gibt den Inhalt einer Speicheradresse am LCD aus, im vorliegenden Fall einen 16-Bit Wert im LSB-Format, der in der Speicherstelle (Variablen) rot_angle_godown und der Folgeadresse abgelegt ist.
Das Unterprogramm zeigt dabei folgende Daten an (Abb. 7.6):

Figure 7.6: LCD-Anzeige mit Debug Inhalt
\includegraphics[%
width=4cm]{graphics/DebugScreen.ps}

  1. In Zeile 1 wird der aktuelle Rotationwinkel (0x0F2=121°, Inhalt des Zählers TCNT1) und der vorberechnete Endwinkel (0x1E4=242°) angezeigt.
  2. Der vorberechnete ,,SpeedDown'' Wert (0x1B4=218°), der Beginn der Steuerungsphase III, wird nach dem ,,='' Zeichen dargestellt (genaueres dazu in Absch. 7.8.1.2).
  3. Die binäre Darstellung des Inhaltes des Timer-Interrupt Mask Registers TIMSK wird in Zeile 3 ausgegeben. Das ,,Schlüsselsymbol'' rechts unten erscheint im DEBUG-Modus, wenn auf einen Tastendruck zum Fortfahren des Programms gewartet wird.

gerhard.reithofer@tech-edv.co.at