Einmal 42 und zurück > Programmierung > Speicher beim STM32 (Cortex-M3/Cortex-M0) sparen
Speicher beim STM32 (Cortex-M3/Cortex-M0) sparen
In der letzten Zeit programmiere ich recht viel mit dem STM32. Dabei handelt es sich um einen Microcontroller mit ARM-v7 Kern (Cortex-M0, Cortex-M3, Cortex-M4), der sich grob im Preisniveau von AVRs bewegt, aber deutlich performanter ist. Seitens des Herstellers (ST Microelectronics) wird eine angenehm nutzbare Firmware-Library (Standard Peripherals Library) mitgeliefert, sodass man eigentlich direkt starten kann. Achsoo, GCC gibts natürlich auch als Compiler: . Das ganze wird dann unter Eclipse programmiert und mit OpenOCD debuggt.
Jetzt aber zum eigentlichen Thema. Wenn man nämlich wie von ST in den Beispielen der Firmware-Library die Peripherie initialisiert, dann sieht das in etwa so aus:
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE); GPIO_InitTypeDef gpio; gpio.GPIO_Mode = GPIO_Mode_OUT; gpio.GPIO_OType = GPIO_OType_PP; gpio.GPIO_PuPd = GPIO_PuPd_NOPULL; gpio.GPIO_Speed = GPIO_Speed_Level_1; gpio.GPIO_Pin = GPIO_Pin_14 | GPIO_Pin_15; GPIO_Init(GPIOC, &gpio);
Zum einen ist dieser Code recht unübersichtlich, zum anderen passiert aber hier etwas, was man eigentlich gar nicht möchte. Zuerst wird nämlich auf dem Stack Speicher für die Struktur reserviert, dann im Programm Feld für Feld mit Werten gefüllt und danach an die Init-Funktion übergeben. Im Assembler sieht das übrigens so aus:
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE); 800899a: 2380 movs r3, #128 ; 0x80 800899c: 031b lsls r3, r3, #12 800899e: 1c18 adds r0, r3, #0 80089a0: 2101 movs r1, #1 80089a2: f7fa f919 bl 8002bd8 <RCC_AHBPeriphClockCmd> GPIO_InitTypeDef gpio; gpio.GPIO_Mode = GPIO_Mode_OUT; 80089a6: 1c3b adds r3, r7, #0 80089a8: 2201 movs r2, #1 80089aa: 711a strb r2, [r3, #4] gpio.GPIO_OType = GPIO_OType_PP; 80089ac: 1c3b adds r3, r7, #0 80089ae: 2200 movs r2, #0 80089b0: 719a strb r2, [r3, #6] gpio.GPIO_PuPd = GPIO_PuPd_NOPULL; 80089b2: 1c3b adds r3, r7, #0 80089b4: 2200 movs r2, #0 80089b6: 71da strb r2, [r3, #7] gpio.GPIO_Speed = GPIO_Speed_Level_1; 80089b8: 1c3b adds r3, r7, #0 80089ba: 2200 movs r2, #0 80089bc: 715a strb r2, [r3, #5] gpio.GPIO_Pin = GPIO_Pin_14 | GPIO_Pin_15; 80089be: 1c3b adds r3, r7, #0 80089c0: 22c0 movs r2, #192 ; 0xc0 80089c2: 0212 lsls r2, r2, #8 80089c4: 601a str r2, [r3, #0] GPIO_Init(GPIOC, &gpio); 80089c6: 4a04 ldr r2, [pc, #16] ; (80089d8 <DIR_Config+0x44>) 80089c8: 1c3b adds r3, r7, #0 80089ca: 1c10 adds r0, r2, #0 80089cc: 1c19 adds r1, r3, #0 80089ce: f7f9 fddb bl 8002588 <GPIO_Init>
Ziemlich aufwendig für ein wenig Initialisierung, die sich nie ändert, gell? In Summe sind das 56 Bytes Programmcode.
Wenn man nun geschickt C99-Struct-Initialisierungen mit ein paar Speichermodifiern kombiniert, so erhält man folgenden Code, der effektiv das Gleiche macht:
/* GPIOC Periph clock enable */ RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE); const GPIO_InitTypeDef gpio = { .GPIO_Mode = GPIO_Mode_OUT, .GPIO_OType = GPIO_OType_PP, .GPIO_PuPd = GPIO_PuPd_NOPULL, .GPIO_Speed = GPIO_Speed_Level_1, .GPIO_Pin = GPIO_Pin_14 | GPIO_Pin_15 }; GPIO_Init(GPIOC, &gpio);
Zusätzlich kann man noch die Funktionssignatur der Funktion GPIO_Init ändern, sodass man einen Pointer übergibt, an dem sich weder die Adresse noch der Inhalt ändert:
void GPIO_Init(GPIO_TypeDef* GPIOx, const GPIO_InitTypeDef* const GPIO_InitStruct);
Damit werden die Daten nicht erst im RAM aufbereitet und dann an die GPIO_Init übergeben sondern die Funktion bekommt direkt die Flash-Adresse der Daten übergeben. Das Ergebnis sieht erstaunlich simpel im Assembler-Code aus:
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE); 800899a: 2380 movs r3, #128 ; 0x80 800899c: 031b lsls r3, r3, #12 800899e: 1c18 adds r0, r3, #0 80089a0: 2101 movs r1, #1 80089a2: f7fa f919 bl 8002bd8 <RCC_AHBPeriphClockCmd> const GPIO_InitTypeDef gpio = {...} 80089a6: 1c3b adds r3, r7, #0 80089a8: 4a05 ldr r2, [pc, #20] ; (80089c0 <DIR_Config+0x2c>) 80089aa: ca03 ldmia r2!, {r0, r1} 80089ac: c303 stmia r3!, {r0, r1} GPIO_Init(GPIOC, &gpio); 80089ae: 4a05 ldr r2, [pc, #20] ; (80089c4 <DIR_Config+0x30>) 80089b0: 1c3b adds r3, r7, #0 80089b2: 1c10 adds r0, r2, #0 80089b4: 1c19 adds r1, r3, #0 80089b6: f7f9 fde7 bl 8002588 <GPIO_Init>
Macht in Summe 32 Bytes Programmcode. Also haben wir uns einen Haufen Anweisungen, Stack-Speicher in Größe der Struktur und 24 Bytes (43%) Code gespart. Das mag in erster Instanz lächerlich wirken, jedoch wird bei der Initialisierung mehr als ein Pin initialisiert. Prinzipiell lässt sich dieser Trick nämlich auch bei DMA-, ADC-, TIMer-, DAC-, …. -Initialisierungen verwenden.