При выполнении команды O7ARMv{6,7}Linker.Link в рабочий журнал будет выведено, сколько полученная программа потребляет ROM и RAM. Например, для программы из первого урока требуется 880 байт ROM и 16 байт RAM.
Доступная память RAM используется под следующие ресурсы:
Куча используется только при динамическом выделении памяти через команду NEW, и она заполняется снизу вверх. Для сборки мусора в куче надо вызывать команду MicroGC0.Collect. Количество выделенной динамической памяти доступно в переменной MicroKernel0.allocated, начало кучи — MicroKernel0.heapOrg, конец — MicroKernel0.heapLim. Следовательно общий размер легко вычислить как их разность.
Стек заполняет выделенную под него память сверху вниз. И когда этот объем полностью заполняется, он начинает расти поверх кучи. Поэтому большие переменные рекомендуется размещать либо глобально, либо в куче, выход за границы которой контролируется автоматически. При переполнении кучи указатель будет равен NIL после вызова NEW.
Размер памяти, выделяемой под стек, определяется константой StkSize в модуле System для конкретного контроллера. Например, для STM32F4 это 8192 байта, для STM32F103x{8,B} — 2048 байт. Это значение вы можете легко изменить исходя из требований вашего проекта. Если вы используете рекурсивные алгоритмы, то скорее всего вам потребуется зарезервировать больше места под стек.
Степень заполнение стека возможно вычислить, исходя из положения начала и его текущего положения:
SYSTEM.GET(SYSTEM.REG(MT), stkOrg); (* MT = 6 *) SYSTEM.GET(SYSTEM.REG(SP), stkPos); (* SP = 13 *) stkLen := stkOrg - stkPos;
Так возможно контролировать при отладке, не превышает ли stkLen значение StkSize
Переполнение стека возможно обнаружить и более простым путем, сравнив stkPos с MicroKernel0.heapLim. Если он меньше, значит стек уже начал писаться в область памяти, выделенной для кучи. Так возможно добавить ASSERT для аварийной остановки в случае переполнения стека. Про обработку аварийных остановок читайте заметку про отладку.
Александр Ширяев разработал модуль для тестирования сборщика мусора:
MODULE TmpTestGC0;
(*
Alexander Shiryaev, 2019.10
Dynamic memory (and garbage collection) test
Board: Nucleo-F446RE
PA2: UART TX
PA3: UART RX
PA5: LED
*)
IMPORT
SYSTEM,
ARMv7M := MicroARMv7M,
Traps := MicroARMv7MTraps,
Kernel := MicroKernel0,
GC := MicroGC0,
TPorts := MicroSTM32F4TPorts,
Pins := MicroSTM32F4Pins,
Sys := MicroSTM32F405System,
SysTick0 := DplaARMv7MSTM32SysTick0,
WWDG := MicroARMv7MSTM32F4WWDG;
CONST
enableLED = TRUE;
led0PinPort = Pins.A;
led0PinN = 5;
led0BSRR = Pins.GPIOABSRR;
freq0 = 128; (* Hz *)
TYPE
Operation = POINTER TO OperationDesc;
OperationDesc = RECORD
END;
Repeat = POINTER TO RepeatDesc;
RepeatDesc = RECORD (OperationDesc)
value: INTEGER
END;
End = POINTER TO EndDesc;
EndDesc = RECORD (OperationDesc)
END;
VAR
cnt0: INTEGER;
operations: ARRAY 100 OF Operation;
nOp: INTEGER;
port: TPorts.Port;
PROCEDURE LEDOn;
BEGIN
SYSTEM.PUT(led0BSRR, {led0PinN})
END LEDOn;
PROCEDURE LEDOff;
BEGIN
SYSTEM.PUT(led0BSRR, {led0PinN+16})
END LEDOff;
PROCEDURE InitOperations;
VAR i: INTEGER;
BEGIN
i := 0;
WHILE i < LEN(operations) DO
operations[i] := NIL;
INC(i)
END;
GC.Collect;
nOp := 0
END InitOperations;
PROCEDURE AddRepeat (val: INTEGER);
VAR repeat: Repeat;
BEGIN
repeat := NIL;
NEW(repeat);
repeat.value := val;
operations[nOp] := repeat;
INC(nOp)
END AddRepeat;
PROCEDURE AddEnd;
VAR end: Repeat;
BEGIN
end := NIL;
NEW(end);
INC(nOp)
END AddEnd;
PROCEDURE PopulateOperations;
VAR i: INTEGER;
BEGIN
i := 0;
WHILE i < 10 DO
AddRepeat(i);
INC(i)
END;
i := 0;
WHILE i < 5 DO
AddEnd;
INC(i)
END
END PopulateOperations;
PROCEDURE Receive (id: CHAR; a: ARRAY OF CHAR; len: INTEGER);
VAR ok: BOOLEAN;
i: INTEGER;
BEGIN
IF id = 20X (* communication test *) THEN
TPorts.Send(port, 21X, SYSTEM.ADR(a), len, ok)
ELSIF id = 22X (* clear dynamic memory *) THEN
IF len = 0 THEN
InitOperations;
TPorts.Send(port, 23X, 0, 0, ok)
END
ELSIF id = 24X (* populate dynamic memory *) THEN
IF len = 0 THEN
PopulateOperations;
TPorts.Send(port, 25X, 0, 0, ok)
END
ELSIF id = 26X (* Kernel.allocated query *) THEN
IF len = 0 THEN
TPorts.Send(port, 27X, SYSTEM.ADR(Kernel.allocated), 4, ok)
END
ELSIF id = 28X (* TRAP test *) THEN
IF len = 0 THEN
ASSERT(FALSE)
END
ELSIF id = 2AX (* hangup test *) THEN
IF len = 0 THEN
REPEAT UNTIL FALSE
END
ELSIF id = 2CX (* multiple clear/populate calls *) THEN
IF len = 0 THEN
i := 1000;
REPEAT DEC(i);
InitOperations;
PopulateOperations;
WWDG.Update
UNTIL i = 0;
TPorts.Send(port, 2DX, 0, 0, ok)
END
END
END Receive;
PROCEDURE OnFreq0;
BEGIN
IF enableLED THEN
(* LED blink *)
INC(cnt0);
IF cnt0 MOD (freq0 DIV 4) = 0 THEN
LEDOn
ELSIF cnt0 MOD (freq0 DIV 4) = freq0 DIV 8 THEN
LEDOff
END
END;
WWDG.Update (* reset watchdog timer *)
END OnFreq0;
PROCEDURE MainLoop;
BEGIN
REPEAT
TPorts.Receive(port);
IF SysTick0.OnTimer() THEN
OnFreq0
END;
ARMv7M.WFI
UNTIL FALSE
END MainLoop;
PROCEDURE Init;
VAR ok: BOOLEAN;
i: INTEGER;
PROCEDURE InitPort;
VAR p: TPorts.InitPar;
BEGIN
p.n := TPorts.USART2;
p.RXPinPort := Pins.A;
p.RXPinN := 3;
p.RXPinAF := Pins.AF7;
p.TXPinPort := Pins.A;
p.TXPinN := 2;
p.TXPinAF := Pins.AF7;
p.UCLK := Sys.PCLK1;
p.baud := 115200;
p.parity := TPorts.parityNone;
p.receive := Receive;
p.version1 := 1;
TPorts.Init(port, p)
END InitPort;
PROCEDURE InitLED;
BEGIN
Pins.Configure(led0PinPort, led0PinN,
Pins.output, Pins.pushPull, Pins.low, Pins.noPull, 0)
END InitLED;
BEGIN
InitPort; (* initialize communication port *)
IF enableLED THEN
InitLED; LEDOff; cnt0 := 0 (* initialize LED and related logic *)
END;
WWDG.Init(WWDG.WDGTBMax, WWDG.WMax, WWDG.TMax); (* initialize watchdog timer *)
SysTick0.Init(Sys.HCLK, freq0); (* initialize periodic timer *)
IF Traps.trapFlag THEN Traps.ClearTrapFlag;
i := 3;
REPEAT DEC(i);
TPorts.Send(port, 0DEX, SYSTEM.ADR(Traps.trap), 9, ok)
UNTIL i = 0
ELSE
i := 3;
REPEAT DEC(i);
TPorts.Send(port, 0CBX, 0, 0, ok)
UNTIL i = 0
END
END Init;
BEGIN
Init;
MainLoop
END TmpTestGC0.
O7ARMv7MLinker.Link STM32F446RE TmpTestGC0
Автор заметки: И.А. Денисов