Jan Beran

View on GitHub

Úkol 6: Low Power Core


ESP32-C5 má dvě jádra: high-power (HP) jádro pro standardní použití a low-power (LP) jádro pro minimalizaci spotřeby.

Druhému jádru se hlavně v angličtině často říká Ultra-Low-Power (ULP) core a je určené především na to, aby zvládalo jednoduché úlohy, zatímco je hlavní (HP) jádro v režimu spánku, čímž výrazně klesá spotřeba elektrické energie. Tuto funkci jistě ocení ti, kteří staví projekty napájené z baterií, kde se nehraje tolik na výkon, jako spíše na efektivitu.

ULP jádro může fungovat nezávisle na HP jádře, kdy může například sbírat data ze senzorů a provádět jejich základní zpracování, nebo ovládání GPIO, to vše při naprosto minimální spotřebě a taktu 20 MHz, což je srovnatelné například s Arduinem UNO. Pokud vás zajímá kompletní přehled toho, co toto jádro dokáže, navštivte dokumentaci o ULP LP-Core Coprocessor Programming.

(U)LP jádro

Můžete se podívat na DevCon23 talk “Low-Power Features of ESP32-C6: Target Wake Time + LP Core”, který pokrývá některé aspekty LP jádra a TWT:

Sledovat na YouTube

ULP pinout

ULP jádro používá specifický set pinů. Pokud budete potřebovat vědět detaily, použijte rozložení pinů, abyste věděli, které piny budou s LP jádrem spolupracovat.

Praktická práce s LP jádrem

V této ukázce si napíšeme kód, který rozbliká LED. Jednou se o řízení bude starat “velké” HP jádro, podruhé tutéž práci zastane ULP jádro. Zkusíme přitom porovnat spotřebu.

Poznámka: Pro tento úkol vytvoříme nový prázdný projekt. Pro podrobnější instrukce navštivte 1. kapitolu (vytváření projektu z příkladu sample_project).

Také bude důležité, abyste vždy upravovali správný soubor - v projektu bude několik stejnojmenných souborů main.c a CMakeLists.txt.

  1. Vytvořte složku main/ulp a uvnitř soubor main.c
#include <stdint.h>
#include <stdbool.h>
#include "ulp_lp_core.h"
#include "ulp_lp_core_utils.h"
#include "ulp_lp_core_gpio.h"
#include "ulp_lp_core_interrupts.h"

#define WAKEUP_PIN LP_IO_NUM_0
#define BLUE_PIN    LP_IO_NUM_4
#define GREEN_PIN  LP_IO_NUM_5

static uint32_t wakeup_count;
uint32_t start_toggle;

void LP_CORE_ISR_ATTR ulp_lp_core_lp_io_intr_handler(void)
{
    ulp_lp_core_gpio_clear_intr_status();
    wakeup_count++;
}

int main (void)
{
    /* Register interrupt for the wakeup pin */
    ulp_lp_core_intr_enable();
    ulp_lp_core_gpio_intr_enable(WAKEUP_PIN, LP_IO_INTR_POSEDGE);

    int level = 0;
    while (1) {
        /* Toggle the Red LED GPIO */
        ulp_lp_core_gpio_set_level(GREEN_PIN, 0);
        ulp_lp_core_gpio_set_level(BLUE_PIN, level);
        level = level ? 0 : 1;
        ulp_lp_core_delay_us(1000000);

        /* Wakeup the main processor after 4 toggles of the button */
        if (wakeup_count >= 4) {
            ulp_lp_core_gpio_set_level(BLUE_PIN, 0);
            ulp_lp_core_wakeup_main_processor();
            wakeup_count = 0;
        }
    }
    /* ulp_lp_core_halt() is called automatically when main exits */
    return 0;
}

V tomto kódu povolujeme přerušení (interrupt) na LP jádře pomocí ulp_lp_core_intr_enable, přičemž nastavujeme GPIO0 jako vstupní pin, aktivovaný vzestupnou hranou (přechod signálu ze stavu LOW do stavu HIGH). K připojení pinu a přerušení používáme funkci ulp_lp_core_gpio_intr_enable. Wake up counter bude obstaráván interrupt handlerem ulp_lp_core_lp_io_intr_handler.

Nyní se spustí smyčka pro blikání a wake up counter. Hodnota našeho GPIO je nastavená funkcí ulp_lp_core_gpio_set_level. Pokud je počet zmáčknutí tlačítka 4 nebo výše, spustí se HP jádro funkcí ulp_lp_core_wakeup_main_processor.

  1. Změny v main/CMakeLists.txt

V CMake musíme nastavit jméno ULP aplikace, zdrojové soubory a další…

# Set usual component variables
set(app_sources "main.c")
idf_component_register(SRCS ${app_sources}
                       REQUIRES ulp
                       WHOLE_ARCHIVE)
#
# ULP support additions to component CMakeLists.txt.
#
# 1. The ULP app name must be unique (if multiple components use ULP).
set(ulp_app_name ulp_${COMPONENT_NAME})
#
# 2. Specify all C and Assembly source files.
#    Files should be placed into a separate directory (in this case, ulp/),
#    which should not be added to COMPONENT_SRCS.
set(ulp_sources "ulp/main.c")
#
# 3. List all the component source files which include automatically
#    generated ULP export file, ${ulp_app_name}.h:
set(ulp_exp_dep_srcs ${app_sources})
#
# 4. Call function to build ULP binary and embed in project using the argument
#    values above.
ulp_embed_binary(${ulp_app_name} "${ulp_sources}" "${ulp_exp_dep_srcs}")

  1. Kód v main/main.c pro HP jádro
#include <stdio.h>
#include "esp_sleep.h"
#include "driver/gpio.h"
#include "driver/rtc_io.h"
#include "ulp_lp_core.h"
#include "ulp_main.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

extern const uint8_t ulp_main_bin_start[] asm("_binary_ulp_main_bin_start");
extern const uint8_t ulp_main_bin_end[]   asm("_binary_ulp_main_bin_end");

static void init_ulp_program(void);

#define WAKEUP_PIN  GPIO_NUM_0
#define BLUE_PIN    GPIO_NUM_4
#define GREEN_PIN   GPIO_NUM_5

void app_main(void)
{
    vTaskDelay(pdMS_TO_TICKS(1000));

    esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
    if (cause == ESP_SLEEP_WAKEUP_ULP) {
        printf("ULP woke up the main CPU! \n");
        ulp_lp_core_stop();
    }

    printf("In active mode\n");
    printf("Long press the wake button to put the chip to sleep and run the ULP\n");

    rtc_gpio_init(WAKEUP_PIN);
    rtc_gpio_set_direction(WAKEUP_PIN, RTC_GPIO_MODE_INPUT_ONLY);
    rtc_gpio_pulldown_dis(WAKEUP_PIN);
    rtc_gpio_pullup_dis(WAKEUP_PIN);

    rtc_gpio_init(BLUE_PIN);
    rtc_gpio_set_direction(BLUE_PIN, RTC_GPIO_MODE_OUTPUT_ONLY);
    rtc_gpio_pulldown_dis(BLUE_PIN);
    rtc_gpio_pullup_dis(BLUE_PIN);

    rtc_gpio_init(GREEN_PIN);
    rtc_gpio_set_direction(GREEN_PIN, RTC_GPIO_MODE_OUTPUT_ONLY);
    rtc_gpio_pulldown_dis(GREEN_PIN);
    rtc_gpio_pullup_dis(GREEN_PIN);

    int gpio_level = 0;
    int previous_gpio_level = 0;
    int cnt = 0;

    while (1) {
        rtc_gpio_set_level(BLUE_PIN, 0);
        rtc_gpio_set_level(GREEN_PIN, 1);
        vTaskDelay(pdMS_TO_TICKS(1000));
        rtc_gpio_set_level(GREEN_PIN, 0);
        vTaskDelay(pdMS_TO_TICKS(1000));

        gpio_level = rtc_gpio_get_level(WAKEUP_PIN);
        if (gpio_level != previous_gpio_level) {
            previous_gpio_level = gpio_level;
            cnt++;
            if (cnt > 1) {
                rtc_gpio_set_level(GREEN_PIN, 0);
                cnt = 0;
                break;
            }
        }
    }

    init_ulp_program();

    printf("Entering Deep-sleep mode\n\n");
    printf("Press the wake button at least 3 or 4 times to wake up the main CPU again\n");
    vTaskDelay(10);

    ESP_ERROR_CHECK( esp_sleep_enable_ulp_wakeup());

    esp_deep_sleep_start();
}

static void init_ulp_program(void)
{
    esp_err_t err = ulp_lp_core_load_binary(ulp_main_bin_start,
                        (ulp_main_bin_end - ulp_main_bin_start));
    ESP_ERROR_CHECK(err);

    ulp_lp_core_cfg_t cfg = {
        .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU,
    };

    err = ulp_lp_core_run(&cfg);
    ESP_ERROR_CHECK(err);
}
  1. Zapnutí LP jádra v konfiguraci

Abychom LP jádro zapnuli a mohli pro něj překládat, musíme nastavit následující konfigurační parametry v konfiguraci projektu. Například tak, že vytvoříme sdkconfig.defaults s následujícím obsahem:

# Enable ULP
CONFIG_ULP_COPROC_ENABLED=y
CONFIG_ULP_COPROC_TYPE_LP_CORE=y
CONFIG_ULP_COPROC_RESERVE_MEM=4096
# Set log level to Warning to produce clean output
CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y
CONFIG_BOOTLOADER_LOG_LEVEL=2
CONFIG_LOG_DEFAULT_LEVEL_WARN=y
CONFIG_LOG_DEFAULT_LEVEL=2
  1. Nastavení HW

Na tenhle příklad budete navíc potřebovat 2 LED a jedno tlačítko připojené na následující piny:

Schéma tlačítka

Schéma tlačítka

Kompletní schéma zapojení

Kompletní schéma ukazující, jak připojit obě LED (GPIO4, GPIO5) a tlačítko (GPIO0) k ESP32-C5-DevKitC-1.

  1. Build, flash, a monitor výstupu z desky

Zkontrolujte si, že pro nahrávání a následné monitorování výstupu používáte USB port s nápisem UART. Nic nezkazíte ani vymazáním flash paměti (příkaz Erase Flash) předtím, než nahrajeme tenhle příklad.

Co by se mělo stát…

Po naflashování by měla začít každou vteřinu blikat zelená LED s následujícím výstupem:

In active mode
Long press the wake button to put the chip to sleep and run the ULP

Pokud dlouze stiskneme tlačítko, mělo by dojít k aktivaci LP jádra a přepnutí HP jádra do Deep-sleep mode. Modrá LED začne blikat s frekvencí jedné vteřiny s následujícím výstupem:

Entering Deep-sleep mode
Press the wake button at least 3 or 4 times to wake up the main CPU again

Probuzení z Deep-sleep mode provedeme čtyřkliknutím na tlačítko:

ULP woke up the main CPU!
In active mode
Long press the wake button to put the chip to sleep and run the ULP

Pro měření spotřeby používáme vyvedený konektor J5 a vhodný nástroj, třeba JouleScope nebo PPK2.

LED blikání s HP jádrem

S využitím “velkého” HP jádra je průměrná spotřeba za 10 vteřin zhruba 22.32mA.

Graf spotřeby HP jádra

LED blikání s LP jádrem

Pokud ale aplikaci přeneseme na LP jádro, rázem spadneme o řád níže: 2.97mA.

Graf spotřeby LP jádra

Když přepneme jádra, dosáhneme úspory (až) 86.7% pro stejný úkol. Příklad je ale pouze orientační a reálné hodnoty se samozřejmě budou lišit.

Více příkladů s LP jádrem (anglicky):

Závěr

Moc děkujeme za účast v našem workshopu a doufáme, že vám přinesl něco užitečného!

Během workshopu jsme si prošli několika různými tématy:

I přesto, že jsme prošli několik vcelku různorodých témat, sotva jsme sklouzli po povrchu. Jak ESP32, tak i ESP-IDF toho nabízejí mnohem více. Všichni ale doufáme, že jsme vám tímhle workshopem dali pevný základ, na kterém budete schopní sami dále pracovat a rozvíjet vaše projekty.

Ještě jednou děkujeme za účast a těšíme se na vaše projekty!


← Zpět na domovskou stránku C5 workshopu