Инструменты пользователя

Инструменты сайта


ob:o7:debug

Это старая версия документа!


Отладка программ на языке Оберон-07

Отладка программ на Обероне при использовании компилятора O7 осуществляется главным образом через обработку аварийных остановок (TRAP) с помощью модуля MicroARMv{6,7}MTraps. При срабатывании аварийной остановки, микроконтроллер перезагружается, переменная этого модуля trapFlag становится TRUE, а информация о причине перезагрузки доступна в переменной trap.

Так во время разработки и отладки программы удобным является следующий прием:

MODULE MyProgram;
  IMPORT Traps := MicroARMv7MTraps;

  PROCEDURE Init;
  BEGIN
    ...
    IF Traps.trapFlag THEN Traps.ClearTrapFlag; ... handle Traps.trap ... END
    ...
  END Init;

BEGIN
  Init;
  MainLoop
END MyProgram.

Аварийные остановки происходят по ряду причин.

Программные причины (Oberon):

  • Код 1: index check
  • Код 2: type cast
  • Код 3: array size
  • Код 4: nil check
  • Код 5: nil check (procedure call)
  • Код 6: bad divisor (must be > 0)
  • Код 7: assert

Аппаратные причины (ARMv{6,7}-M):

  • Код 10: hard fault
  • Код 11: memory manage
  • Код 12: bus fault
  • Код 13: usage fault

Аварийные остановки сообщают program counter pc и положение в исходном тексте модуля pos, поэтому, чтобы узнать место в программе, в котором произошло исключение, надо:

  1. Используя значение pc узнать модуль, в котором оно произошло.
  2. По номеру положения pos (номер символа) найти место в исходном тексте модуля (O7→Position)

Для микроконтроллеров NXP достаточно pc поделить на 2 (т. к. команды Thumb 2-байтные). Потом сравнить полученное число со списком вывода команды O7ARMv{6,7}MLinker.Link. Ошибка произошла в том модуле, где самое близкое из чисел меньших pc.

Для микроконтроллеров STM32 надо предварительно из pc вычесть 8000000H, так как программа в ROM начинается с этого положения.

ASSERT

ASSERT в готовых модулях служит для проверки предусловия или постусловия в рамках контрактного программирования.

SYSTEM.GET(SYSTEM.REG(SP), stkPos); (* SP = 13 *)
ASSERT(stkPos > MicroKernel0.heapLim);

Этот код проверит, не начал ли стек «заползать» на кучу. И если такая ошибка происходит, произойдет аварийная остановка.

Чтобы узнать больше про особенности работы с памятью читайте эту заметку.

Пример отладчика

Чтобы осуществлять отладку микроконтроллера необходимо обмениваться сообщениями. Более подробно организация обмена описана в заметке Обмен сообщениями с контроллером.

MODULE MobxAssert;

	IMPORT SYSTEM, ARMv7M := MicroARMv7M,
		Traps := MicroARMv7MTraps,
		Sys := MicroSTM32F103x8System,
		Pins := MicroSTM32F10xPinCfg,
		TPs := MicroSTM32F1TPorts;

	CONST
		idInit = 0CBX; idTrap = 0DEX;

	VAR
		p0: TPs.Port;
		
	PROCEDURE Receive (id: CHAR; a: ARRAY OF CHAR; len: INTEGER);
	BEGIN
		ASSERT(FALSE)
	END Receive;

	PROCEDURE MainLoop;
	BEGIN
		REPEAT
			TPs.Receive(p0);
			ARMv7M.WFI
		UNTIL FALSE
	END MainLoop;

	PROCEDURE Init;
		VAR ok: BOOLEAN; par: TPs.InitPar;
	BEGIN
		par.n := TPs.USART1;
		par.RXPinPort := Pins.A; par.RXPinN := 10;
		par.TXPinPort := Pins.A; par.TXPinN := 9;
		par.UCLK := Sys.PCLK2;
		par.baud := 19200;
		par.parity := TPs.parityNone;
		par.receive := Receive;
		par.version2 := 2;
		TPs.Init(p0, par);
		
		TPs.Send(p0, idInit, 0, 0, ok); (* required for "heating" UART *)
		IF Traps.trapFlag THEN
			Traps.ClearTrapFlag;
			TPs.Send(p0, idTrap, SYSTEM.ADR(Traps.trap), 9, ok)
		END
	END Init;

BEGIN
	Init;
	MainLoop
END MobxAssert.

Собирать командой:

^Q O7ARMv7MLinker.Link STM32F103C8 MobxAssert

В журнал будет выведена информация о модулях.

linking MobxAssert Ok
	MicroARMv7M	256
	MicroKernel0	301
	MicroARMv7MTraps	987
	MicroSTM32F10xxD	1283
	MicroSTM32F103x8System	1286
	MicroSTM32F10xPinCfg	1535
	MicroSTM32F1TPorts	1662
	MobxAssert	3146
ROM: 6816 B; RAM: 748 B

Программа для Блэкбокса, которая покажет отладочную информацию

MODULE MobxDebug;
	IMPORT SYSTEM, TP := MicroTPorts, Log := StdLog;
	
	TYPE
		MessagesHandler = POINTER TO RECORD (TP.MessagesHandler) END;
	
	CONST
		id = 20X;
		idTrap = 0DEX;
		
	VAR
		p: TP.Port;
		mh: MessagesHandler;

	PROCEDURE Send*(t: INTEGER);
		VAR a: ARRAY 4 OF SHORTCHAR;
	BEGIN
		SYSTEM.PUT(SYSTEM.ADR(a), t);
		TP.SendMessage(p, id, a, 4)
	END Send;

	PROCEDURE (h: MessagesHandler) MessageReceived (id: SHORTCHAR; IN a: ARRAY OF SHORTCHAR; len: INTEGER);
		VAR  pc, pos, code: INTEGER;
	BEGIN
		IF id = idTrap THEN
			SYSTEM.GET(SYSTEM.ADR (a), pc);
			SYSTEM.GET(SYSTEM.ADR (a)+4, pos);
			code := ORD(a[8]);
			IF pc >= 8000000H THEN pc := pc - 8000000H END;
			Log.String("pc: "); Log.Int(pc DIV 2); Log.Ln;
			Log.String("pos: "); Log.Int(pos); Log.Ln;
			Log.String("code: "); Log.Int(code); Log.Ln;
		END
	END MessageReceived;

	PROCEDURE Open*(port: ARRAY OF CHAR);
	BEGIN
		IF p = NIL THEN
			p := TP.NewPort(port, 19200, {}, mh);
			Log.Bool(p # NIL); Log.Ln;
		END
	END Open;

	PROCEDURE Close*;
	BEGIN
		TP.Close(p)
	END Close;

BEGIN
	NEW(mh)
CLOSE
	Close
END MobxDebug.

В Windows запускается командой:

^Q "MobxDebug.Open('COM1')"

Для Linux будет следующая команда:

^Q "MobxDebug.Open('/dev/ttyUSB0')"

Любая отправка МК команды, вызовет аварийную остановку из-за ASSERT(FALSE) в модуле прошивки.

^Q "MobxDebug.Send(0)"

В журнал Блэкбокса будет выведена информация:

pc:  3155
pos:  337
code:  7

Program Counter (pc) равен 3155, что больше 3146 (см. информацию при сборке), а значит аварийная остановка произошла в модуле MobxAssert в положении 337 с кодом 7.

Чтобы перейти к положению ошибки в модуле, удобно воспользоваться инструментом O7→Position.

Сторожевой таймер

MODULE MobxSTM32F4WWDG;

	IMPORT SYSTEM,
		ARMv7M := MicroARMv7M,
		Traps := MicroARMv7MTraps,
		MCU := MicroSTM32F405;

	CONST
		SP = 13;
		SR0 = 0; SR1 = 1; SR2 = 2; SR3 = 3; SR12 = 4; SLR = 5; SPC = 6;

		Int = MCU.WWDGInt;
		int = Int MOD 32;
		ISER = ARMv7M.NVICISER0 + (Int DIV 32) * 4;
		ICER = ARMv7M.NVICICER0 + (Int DIV 32) * 4;
		IPR = ARMv7M.NVICIPR0 + Int;

	PROCEDURE* InterruptsHandler;
		CONST nLVars = 1;
		VAR pc: INTEGER;
	BEGIN
		(* compiler-dependent *)
			SYSTEM.GET(SYSTEM.REG(SP) + (SPC + nLVars + 1 + 8) * 4, pc);
		Traps.trapHandler(0EX (* 14 *), pc, 0)
	END InterruptsHandler;

	PROCEDURE Update*;
	BEGIN
		SYSTEM.PUT(MCU.WWDGCR, {0..7})
	END Update;

	PROCEDURE Init*;
		CONST
			(* RCCAPB1ENR bits: *)
				WWDGEN = 11;
			(* WWDGCR bits: *)
				WDGA = 7;
			(* WWDGCFR bits: *)
				EWI = 9;
			(* WWDGSR bits: *)
				EWIF = 0;
		VAR x: SET;
			i: INTEGER;
	BEGIN
		SYSTEM.PUT(ICER, {int}); ARMv7M.ISB;

		(* timeout = 4096 * 8 / PCLK1 * 64 = 52.4288 ms @ PCLK1 = 40 MHz *)

		SYSTEM.GET(MCU.RCCAPB1ENR, x);
		SYSTEM.PUT(MCU.RCCAPB1ENR, x + {WWDGEN}); ARMv7M.DSB;

		SYSTEM.PUT(MCU.WWDGCFR, {0..8} + {EWI}); (* W := 127; WDGTB := 3; !EWI *)

		SYSTEM.PUT(Traps.vectorsOrg + (ARMv7M.ExternalInterrupt0 + Int) * 4, InterruptsHandler);

		(* decrease priority of all interrupts *)
			i := 0;
			WHILE i < 60 DO
				SYSTEM.PUT(ARMv7M.NVICIPR0 + i, 80808080H); (* set priorities to 8 *)
				INC(i)
			END;
		(* increase priority of WWDG interrupts *)
			SYSTEM.PUT(IPR, 0X); (* set priority to 0 *)

		SYSTEM.GET(MCU.WWDGSR, x);
		SYSTEM.PUT(MCU.WWDGSR, x - {EWIF});

		SYSTEM.PUT(MCU.WWDGCR, {0..6} + {WDGA}); (* enable *)

		SYSTEM.PUT(ISER, {int})
	END Init;

END MobxSTM32F4WWDG.

Нужно периодически вызывать процедуру Update.

При «зависании» микроконтроллера сработает аварийная остановка с кодом 14. И можно определить место в программе, где это произошло (Trap.pc)

«Независимый» сторожевой таймер (но без возможности определения места остановки): см. MicroSTM32xIWDG


Авторы заметки: И. А. Денисов, А. В. Ширяев

ob/o7/debug.1527156102.txt.gz · Последнее изменение: 2020/10/29 07:08 (внешнее изменение)