Siendo curioso yo mismo he hecho algunas mediciones simples en un 32L152CDISCOVERY tablero. Mi programa de prueba consiste en 4 pruebas, cada una de las cuales muestra los segundos transcurridos en la pantalla LCD. La diferencia entre las pruebas es lo que hacen hasta que transcurre un segundo.
-
La primera prueba no hace nada dentro del bucle. Es un puro "bucle ocupado", que gira hasta que una variable cambia.
-
El segundo bucle tiene un único NOP
instrucción en el interior.
-
El tercer bucle ejecuta 1000 NOP
instrucciones seguidas.
-
El cuarto bucle ejecuta un WFI
instrucción, durmiendo hasta que se produzca una interrupción.
Pulsando el botón azul se pasa a la siguiente prueba.
Hay dos parámetros de compilación.
- Definición de
SLOWTICK
cambia la SysTick
frecuencia de interrupción de 1 kHz a 1 Hz.
- Definición de
ALIGN_OFF
inserta un NOP
antes de cada bucle de prueba, cambiando su alineación.
El código:
#define STM32L152xC
#include "stm32l1xx.h"
#include "lcd.h"
//#define SLOWTICK
//#define ALIGN_OFF
volatile int tick_s;
void SysTick_Handler() {
#ifdef SLOWTICK
tick_s += 1;
#else
static int tick_ms;
tick_ms += 1;
if(tick_ms == 1000) {
tick_ms = 0;
tick_s += 1;
}
#endif
}
void hw_init() {
// use the 16MHz internal HSI as clock source, no PLL
RCC->CR |= RCC_CR_HSION;
while(!(RCC->CR & RCC_CR_HSIRDY))
;
RCC->CFGR |= RCC_CFGR_SW_HSI;
while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSI)
;
SystemCoreClockUpdate();
// enable GPIOs for the LCD and the pushbutton
RCC->AHBENR = RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN | RCC_AHBENR_GPIOCEN;
__ISB(); // wait a bit, see STM32L1 errata on RCC
lcd_init();
#ifdef SLOWTICK
SysTick_Config(SystemCoreClock);
#else
SysTick_Config(SystemCoreClock / 1000);
#endif
}
int last_s = -1;
void display_s() {
int d1 = (last_s / 10) % 10;
int d2 = last_s % 10;
lcd_displaychar(d1 + '0', 0, 0, 5);
lcd_displaychar(d2 + '0', 0, 0, 6);
lcd_update();
}
// repeats an instruction 10 times
#define TEN(x) ({ ({ x; }); ({ x; }); ({ x; }); ({ x; }); ({ x; }); \
({ x; }); ({ x; }); ({ x; }); ({ x; }); ({ x; }); })
int button_pressed() {
return GPIOA->IDR & 1;
}
int main() {
hw_init();
int temp_s;
while(1) {
lcd_displaytext("0NOP");
asm volatile(".align 4");
#ifdef ALIGN_OFF
asm volatile("nop");
#endif
while(1) {
while(1) {
temp_s = tick_s;
if(temp_s != last_s)
break;
}
last_s = temp_s;
display_s();
if(button_pressed())
break;
}
while(button_pressed())
;
lcd_displaytext("1NOP");
asm volatile(".align 4");
#ifdef ALIGN_OFF
asm volatile("nop");
#endif
while(1) {
while(1) {
temp_s = tick_s;
if(temp_s != last_s)
break;
asm volatile("nop");
}
last_s = temp_s;
display_s();
if(button_pressed())
break;
}
while(button_pressed())
;
lcd_displaytext("xNOP");
asm volatile(".align 4");
#ifdef ALIGN_OFF
asm volatile("nop");
#endif
while(1) {
while(1) {
temp_s = tick_s;
if(temp_s != last_s)
break;
// triple nesting repeats 10*10*10 times
TEN(TEN(TEN(asm volatile("nop"))));
}
last_s = temp_s;
display_s();
if(button_pressed())
break;
}
while(button_pressed())
;
lcd_displaytext("WFI");
asm volatile(".align 4");
#ifdef ALIGN_OFF
asm volatile("nop");
#endif
while(1) {
while(1) {
temp_s = tick_s;
if(temp_s != last_s)
break;
asm volatile("wfi");
}
last_s = temp_s;
display_s();
if(button_pressed())
break;
}
while(button_pressed())
;
}
}
El consumo de energía de la MCU se midió conectando un amperímetro entre los pines 1 y 2 de JP1. El depurador se desconectó quitando los tapones del puente de CN3.
+--------+--------+--------+--------+
| Test 1 | Test 2 | Test 3 | Test 4 |
| 0NOP | 1NOP | xNOP | WFI |
+---------------------+--------+--------+--------+--------+
| //#define SLOWTICK | | | | |
| //#define ALIGN_OFF | 4.3 mA | 4.7 mA | 2.8 mA | 1.4 mA |
+---------------------+--------+--------+--------+--------+
| //#define SLOWTICK | | | | |
| #define ALIGN_OFF | 5.0 mA | 4.8 mA | 2.8 mA | 1.4 mA |
+---------------------+--------+--------+--------+--------+
| #define SLOWTICK | | | | |
| //#define ALIGN_OFF | 4.3 mA | 4.7 mA | 2.8 mA | 1.4 mA |
+---------------------+--------+--------+--------+--------+
| #define SLOWTICK | | | | |
| #define ALIGN_OFF | 4.9 mA | 4.8 mA | 2.8 mA | 1.4 mA |
+---------------------+--------+--------+--------+--------+
Conclusiones:
- Cambiar la alineación del código puede afectar significativamente al consumo.
- Ejecutar un montón de
NOP
instrucciones reduce el consumo, pero puede ser complicado generar el número exacto de instrucciones necesarias para un determinado retraso. Por no hablar de los requisitos de memoria.
- Poner el controlador en reposo es el verdadero ahorro de energía.