Verbraucht die Verwendung einer Software-Funktion delay() mehr Strom als die Verwendung eines System-Timer-Interrupts? [geschlossen]

Ich hatte eine Diskussion mit jemandem über die Verwendung einer Softwareverzögerung () und wie sie nur NOP-Befehle für eine bestimmte Anzahl von Taktzyklen gemäß Ihrer Verzögerung ausführt und daher ständig läuft und Strom verbraucht. Die Alternative nach meiner Lektüre ist die Verwendung von Systemtimern. Aber sobald diese Timer aktiviert sind, werden sie auch um die gleiche Anzahl von Taktperioden erhöht, sodass ich denke, dass der Stromverbrauch ähnlich wäre.

Ich wollte wissen, ob es einen Unterschied im Stromverbrauch zwischen den beiden gibt, abgesehen von der Tatsache, dass delay() Ihr Programm und Ihre Sachen blockiert. Gibt es alternative Verzögerungsmethoden, die mir ebenfalls nicht bekannt sind und zu einem besseren Stromverbrauch führen?

Da es zu viele Variablen gibt, um sie richtig zu beantworten, werde ich versuchen, ein Beispiel zu geben. Die Beispielaufgabe besteht darin, die Anzahl der verstrichenen Sekunden auf einer 7-Segment-LED-Anzeige kontinuierlich anzuzeigen und auf 0 zurückzugehen, sobald 60 Sekunden erreicht sind. Ich denke, das Ziel wäre es, die Effizienz einer 1-Sekunden-Verzögerung bei der Verwendung einer Funktion mit SysTick IRQ zu vergleichen

Softwareverzögerung führt nicht nur NOP aus. ... es kann NOP für sehr kurze Verzögerungen verwenden. ... es überwacht wahrscheinlich den Systemtimer in einer Schleife
Wenn der Interrupt die CPU aus dem Ruhezustand aufweckt, würde das Strom sparen. Wenn der Interrupt die Ausführung auf eine ISR umleitet, während die CPU mit etwas anderem beschäftigt ist, würde das keinen Strom sparen
Diese Frage ist zu weit gefasst, da sie im Allgemeinen nicht beantwortet werden kann und keine spezifischen Angaben gemacht werden. Eine Verzögerungsfunktion kann viele verschiedene Dinge sein, ein System kann andere Aufgaben ausführen und es kann eine Frequenzskalierung oder die Fähigkeit haben, in einen Energiesparmodus zu wechseln, wenn es nicht viel tut.
@ChrisStratton Ich verstehe deinen Punkt. Ich wurde tatsächlich in einem Interview danach gefragt und der Hauptpunkt war der Stromverbrauch für die Verwendung einer blockierenden Verzögerungsfunktion (). Ich werde meinen Beitrag bearbeiten, um ein bestimmtes Beispiel zu geben.
Es gibt viele sinnvolle Diskussionen, die Sie im Anschluss an eine solche Frage führen könnten, und zeigen Sie dabei ein tiefes Wissen über das Thema. Dies ist jedoch keine Diskussionsseite. Betrachten Sie den Timer-Interrupt-Fall; Was wird das Programm tun, bis der Interrupt ausgelöst wird? Wenn Sie nicht auf einen anderen nützlichen Thread nachgeben () oder den Stromverbrauch des Prozessors zurücksetzen können, ist so etwas wie das geschäftige Warten auf die Änderung eines von der ISR gesetzten Flags nicht billiger als das Zählen von NOPs an erster Stelle. Bedenken Sie bei Multitasking-Systemen auch, dass dies sleep(1)sowohl blockierend als auch potenziell effizient ist.

Antworten (3)

Da ich selbst neugierig bin, habe ich einige einfache Messungen an einem 32L152CDISCOVERY- Board durchgeführt. Mein Testprogramm besteht aus 4 Tests, die jeweils die verstrichenen Sekunden auf dem LCD anzeigen. Der Unterschied zwischen den Tests besteht darin, was sie tun, bis eine Sekunde verstreicht.

  • Der erste Test macht nichts innerhalb der Schleife. Es ist eine reine "Besetztschleife", die sich dreht, bis sich eine Variable ändert.

  • Die zweite Schleife enthält eine einzelne NOPAnweisung.

  • Die dritte Schleife führt 1000 NOPAnweisungen hintereinander aus.

  • Die vierte Schleife führt eine WFIAnweisung aus und schläft, bis ein Interrupt auftritt.

Durch Drücken der blauen Taste gelangen Sie zum nächsten Test.

Es gibt zwei Kompilierzeitparameter.

  • Definieren SLOWTICKändert die SysTickInterruptfrequenz von 1 kHz auf 1 Hz.
  • Definieren fügt vor jeder Testschleife ALIGN_OFFein ein und ändert ihre Ausrichtung.NOP

Der Code:

#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())
            ;
    }
}

Der Stromverbrauch der MCU wurde gemessen, indem ein Amperemeter zwischen Pin 1 und 2 von JP1 angeschlossen wurde. Der Debugger wurde getrennt, indem die Jumperkappen von CN3 entfernt wurden.

                      +--------+--------+--------+--------+
                      | 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 |
+---------------------+--------+--------+--------+--------+

Schlussfolgerungen:

  • Eine Änderung der Codeausrichtung kann den Verbrauch erheblich beeinflussen.
  • Das Ausführen vieler NOPAnweisungen reduziert den Verbrauch, aber es kann schwierig sein, die genaue Anzahl von Anweisungen zu generieren, die für eine bestimmte Verzögerung benötigt werden. Vom Speicherbedarf ganz zu schweigen.
  • Den Controller in den Ruhezustand zu versetzen, ist der wahre Energiesparer.

Eine Belegtschleife (wie die, die delay()Sie beschreiben, wahrscheinlich nicht die, die delaySie tatsächlich verwenden) hält Ihren CPU-Kern auf voller Geschwindigkeit.

Wenn Sie den CPU-Kern einfach im Leerlauf halten und auf einen Interrupt warten, läuft nur ein viel, viel einfacherer Hardwarezähler. "Viel einfacher" bedeutet, dass eine Taktung, die viel weniger Transistoren schaltet, und der Stromverbrauch in digitalen Schaltungen normalerweise die Energie ist, die beim Schalten eines Transistors verloren geht.

Den Taktgenerator des CPU-Kerns kann man wirklich lange abschalten, das spart noch mehr Strom.

Diese Frage wird von so vielen Variablen beeinflusst, dass sie im Wesentlichen unbeantwortbar wird. Hier sind einige Gründe dafür:

  1. Im Fall der NOP-Schleifen läuft der Timer bereits und macht etwas anderes?
  2. Falls der Timer für die Verzögerung verwendet wird, wartet die Softwareabfrage darauf, dass der Timer abläuft?
  3. Wenn der Timer für die Verzögerung verwendet wird, wird die Softwareausführung in einen SLEEP-Modus versetzt?
  4. Behält die Software den Schleifenzähler für den NOP-Zähler in einem Register oder einer Speicherstelle?
  5. Wenn ein Speicherort verwendet wird, ist der Speicher auf dem Chip oder extern in einer anderen Komponente?
  6. Wenn extern, implementiert der Prozessor eine Art von Caching-Algorithmus für externen Speicher?

Es gibt auch andere Überlegungen.

Ich würde vermuten, dass es bei Verwendung einer modernen MCU mit integriertem Programmspeicher und Datenspeicher schwierig sein wird, bei kurzen Verzögerungen einen großen Leistungsunterschied zu erkennen. In diesem Szenario können messbare Energieeinsparungen für lange Verzögerungen realisiert werden, wenn der SLEEP-Modus der MCU verwendet wird.

Danke für all die Punkte, ich habe versucht, in meinem bearbeiteten Beitrag ein einfaches Beispiel zu geben. Hilft das bei der Eingrenzung oder ist es immer noch zu pauschal?