¿El uso de una función de retraso de software () tiene un mayor consumo de energía que el uso de una interrupción del temporizador del sistema? [cerrado]

Tuve una discusión con alguien sobre el uso de un retraso de software (), y cómo ejecuta los comandos NOP durante una cantidad específica de ciclos de reloj de acuerdo con su retraso y, por lo tanto, está funcionando y consumiendo energía constantemente. La alternativa según mi lectura es usar temporizadores del sistema. Pero, una vez que estos temporizadores están habilitados, también aumentan durante la misma cantidad de períodos de reloj, por lo que creo que el consumo de energía sería similar.

Quería saber si hay una diferencia en el uso de energía entre los dos, dejando de lado el hecho de que delay() bloquea su programa y esas cosas. ¿Existen métodos de retraso alternativos que desconozco y que conducen a un mejor consumo de energía?

Como hay demasiadas variables para responder correctamente, intentaré dar un ejemplo. La tarea de ejemplo es mostrar continuamente la cantidad de segundos transcurridos en una pantalla LED de 7 segmentos, volviendo a 0 una vez que se alcanzan los 60 segundos. Así que supongo que el objetivo sería comparar la eficiencia de un retraso de 1 segundo en el uso de una función frente a SysTick IRQ

el retraso del software no ejecuta solo NOP. ... puede usar NOP para retrasos muy cortos. ... probablemente observa el temporizador del sistema en un bucle
si la interrupción despierta la CPU del modo de suspensión, eso ahorraría energía... si la interrupción redirige la ejecución a un ISR mientras la CPU está ocupada haciendo otra cosa, entonces eso no ahorraría energía
Esta pregunta es demasiado amplia, ya que no tiene respuesta en el caso general y no se dan detalles específicos. Una función de retardo puede ser muchas cosas diferentes, un sistema puede estar ejecutando otras tareas y puede o no tener escalado de frecuencia o la capacidad de ingresar a un modo de bajo consumo cuando no está haciendo mucho.
@ChrisStratton Veo tu punto. De hecho, me preguntaron sobre esto en una entrevista y el punto principal fue el consumo de energía para usar una función de retraso de bloqueo (). Editaré mi publicación para dar un ejemplo específico.
Hay muchas discusiones significativas que podría tener después de una pregunta de este tipo y, al hacerlo, mostrar un conocimiento profundo del tema. Pero este no es un sitio de discusión. Considere el caso de interrupción del temporizador; ¿Qué estará haciendo el programa hasta que se dispare la interrupción? A menos que pueda ceder() a otro subproceso útil, o volver a marcar el consumo de energía del procesador, algo como estar ocupado esperando que cambie un indicador establecido por el ISR no es más barato que contar los NOP en primer lugar. En los sistemas multitarea también considere que sleep(1)es a la vez bloqueador y potencialmente eficiente.

Respuestas (3)

Siendo curioso, hice algunas medidas simples en una placa 32L152CDISCOVERY . Mi programa de prueba consta de 4 pruebas, cada una de las cuales muestra los segundos transcurridos en la pantalla LCD. La diferencia entre la prueba es lo que hacen hasta que transcurre un segundo.

  • La primera prueba no hace nada dentro del ciclo. Es un "bucle ocupado" puro, que gira hasta que cambia una variable.

  • El segundo ciclo tiene una sola NOPinstrucción dentro.

  • El tercer bucle ejecuta 1000 NOPinstrucciones seguidas.

  • El cuarto ciclo ejecuta una WFIinstrucción, durmiendo hasta que ocurre una interrupción.

Presionar el botón azul avanza a la siguiente prueba.

Hay dos parámetros de tiempo de compilación.

  • La definición SLOWTICKcambia la SysTickfrecuencia de interrupción de 1 kHz a 1 Hz.
  • La definición ALIGN_OFFde insertos NOPantes 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. La guerra del depurador se desconectó quitando las tapas de los puentes 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 el consumo.
  • Ejecutar muchas NOPinstrucciones reduce el consumo, pero puede ser complicado generar la cantidad exacta de instrucciones necesarias para un retraso en particular. Sin mencionar el requisito de memoria.
  • Poner el controlador en modo de suspensión es el verdadero ahorro de energía.

Por lo tanto, un ciclo ocupado (como el delay()que está describiendo, probablemente no el que delayrealmente está usando) mantendrá el núcleo de su CPU funcionando a toda velocidad.

Cuando simplemente inactiva el núcleo de la CPU y espera una interrupción, solo se ejecuta un contador de hardware mucho, mucho más simple. "Mucho más simple" significa que el reloj cambia muchos menos transistores y el uso de energía en los circuitos digitales suele ser la energía que se pierde al cambiar un transistor.

Durante mucho tiempo, realmente puede apagar el generador de reloj del núcleo de la CPU, y eso ahorra aún más energía.

Esta pregunta va a estar influenciada por tantas variables que se vuelve esencialmente incontestable. Aquí hay algunas razones de por qué:

  1. En el caso de los bucles NOP, ¿el temporizador ya se está ejecutando haciendo otra cosa?
  2. En el caso de utilizar el temporizador para el retraso, ¿el sondeo del software está esperando a que expire el temporizador?
  3. Cuando el temporizador está en uso para el retraso, ¿la ejecución del software se pone en modo SLEEP?
  4. ¿El software mantiene el contador de bucle para el contador NOP en un registro o en una ubicación de memoria?
  5. Si se utiliza una ubicación de memoria, ¿la memoria está en el chip o es externa en otro componente?
  6. Si es externo, ¿el procesador implementa algún tipo de algoritmo de almacenamiento en caché de memoria externa?

Hay otras consideraciones también.

Sospecho que si está utilizando una MCU moderna con memoria de programa integrada y memoria de datos, será difícil ver mucha diferencia de potencia para retrasos cortos. En este escenario, se pueden obtener ahorros de energía medibles durante largos retrasos cuando se utiliza el modo SLEEP de la MCU.

Gracias por todos los puntos, traté de proporcionar un ejemplo simple en mi publicación editada. ¿Eso ayuda a reducir las cosas o es todavía demasiado general?