При выполнении команды 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
Автор заметки: И.А. Денисов