- Conhecendo o co-processador ULP (Ultra Low Power) do ESP32
- Usando o ULP do ESP32 em projetos Low Power
- Utilizando o ULP do ESP32 para co-processamento
Vamos dar início à programação do ULP do ESP32 e finalmente testar esse item incrível e relativamente raro entre microcontroladores. A programação do sistema principal é feita em C/C++, entretanto, o ULP é programado em Assembly e, por conta disso, afasta muitas pessoas que não têm um contato tão grande com programação de baixa abstração. Mas vamos em frente que tudo será devidamente explicado e comentado.
O código basicamente irá colocar o ESP32 em Deep Sleep, enquanto o ULP lê um sensor analógico (LDR) para detectar sombras no local. Caso perceba uma sombra, efetuará uma ação como acordar o ESP32 e incrementar uma variável persistente entre os ciclos de Sleep <-> Wakeup, que também é outro item importante, já que a Flash tem ciclo de escrita baixo.
Código em C++:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
extern "C" { #include <driver/gpio.h> #include <driver/rtc_io.h> #include <ulp/ulp.c> #include <ulp_main.h> #include <driver/adc.h> #include <esp_log.h> #include <esp_system.h> } static const char* tag = "ESP32";//TAG usada no LOG (Serial monitor) extern const uint8_t ulp_main_bin_start[] asm("_binary_ulp_main_bin_start");//Inicio do binario extern const uint8_t ulp_main_bin_end[] asm("_binary_ulp_main_bin_end");//Fim do binario RTC_DATA_ATTR uint8_t wk_ctr = 0;//Variavel alocada na RTC_RAM, os dados se mantem intactos durante e apos o deep sleep void initULP() { //Configura o pino 34 para ADC com 12bits e 3.3V para uso do ULP adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11); adc1_config_width(ADC_WIDTH_BIT_12); adc1_ulp_enable(); //Configura o GPIO2 como saida no RTC Domain para uso do ULP rtc_gpio_init(GPIO_NUM_2); rtc_gpio_set_direction(GPIO_NUM_2, RTC_GPIO_MODE_OUTPUT_ONLY); ulp_set_wakeup_period(0, 100000);//Configura o Timer0 de wakeup do ULP para 100ms ulp_load_binary(0, ulp_main_bin_start, (ulp_main_bin_end - ulp_main_bin_start) / sizeof(uint32_t));//Carrega o binario na RTC_SLOW_MEM ulp_run((&ulp_main - RTC_SLOW_MEM) / sizeof(uint32_t));//Inicializa o ULP } extern "C" void app_main() { //Essa variavel mantem a contagem de ciclos (Sleep -> Wake), apenas um contador a cada wakeup wk_ctr++; ESP_LOGI(tag, "Wakeup counter: %d", wk_ctr);//Mostra a quantidade de wakeups no Monitor if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_ULP)//Se o motivo do wakeup for do ULP { ESP_LOGI(tag, "ULP Wakeup");//Mostra que foi o ULP } else { ESP_LOGI(tag, "!= Wakeup");//Se nao, diz que foi um motivo diferente initULP();//Inicializa o ULP } esp_sleep_enable_ulp_wakeup();//Ativa o wakeup pelo ULP esp_deep_sleep(1800000000);//Dorme por 30 minutos } |
Código em Assembly:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
#include "soc/soc_ulp.h" #include "soc/rtc_io_reg.h" #include "soc/sens_reg.h" #include "soc/rtc_cntl_reg.h" .bss//Declaracao de variaveis aqui .text .global main main://Inicio do codigo (Entry point) WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TC_S+12, 1, 1)//LED OFF (GPIO2 = LOW) loop: move r0, 0 //R0 = 0 move r1, 0 //R1 = 0 stage_rst //stage_cnt = 0 //Aqui sera feito um laco FOR() para 4 leituras do ADC e depois, tiramos uma media 1: stage_inc 1 //stage_cnt++ adc r1, 0, 7 //efetua a leitura ADC do GPIO34 e guarda no R1 add r0, r0, r1 //R0 = R0 + R1 (guarda o total das leituras) wait 65000 wait 12000 //delay de 10ms jumps 1b, 4, lt //retorna a label 1 enquanto stage_cnt < 4 rsh r0, r0, 2 //divide o total das leituras (4) por 4 jumpr wkup, 1600, ge //se valor do ADC >= 1600, ativa o LED para indicar o evento, acorda o sistema principal e encerra o ULP //entretanto, o Timer0 foi ativado para 100ms, entao apos 100ms do HALT, ULP iniciara novamente jump loop//retorn ao loop wkup: WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S+12, 1, 1)//LED ON (GPIO2 = 1) stage_rst 1: stage_inc 1 wait 32000 jumps 1b, 125, lt //delay de 500ms WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TC_S+12, 1, 1)//LED OFF (GPIO2 = 0) wake //Acorda o sistema principal halt //Encerra o codigo do ULP, mas iniciara novamente apos 100ms |
Testando esse código, podemos observar que mesmo com o sistema principal em Deep Sleep, o ULP continua em execução normal, que no caso é fazendo a leitura do sensor analógico (LDR) e, caso o valor (luminosidade) chegue ao valor definido, irá piscar um LED indicativo e acordará o sistema principal, que também mostrará no Serial Monitor a quantidade de vezes que ocorreu o Wakeup. Em teoria, removendo todos os componentes extras do circuito, como sensores e atuadores, o consumo com o sistema principal em Deep Sleep enquanto o ULP está em execução deve ser <=150 uA. Imagine quantas aplicações podemos dar a esse pequeno guerreiro para projetos de Low Power? Praticamente um microcontrolador dentro de outro, porém de baixo consumo energético!
Utilizamos uma variável na memória RAM do RTC Domain, logo, não perdemos informação durante ou após o Deep Sleep, sendo uma ótima maneira de manter dados entre ciclos de Sleep <-> Wakeup sem precisar se preocupar com ciclos baixos de escrita na Flash.
No próximo post dessa série sobre o ULP do ESP32, vamos colocá-lo para ajudar o sistema principal no processamento, ficando encarregado de ler os sensores e ativar atuadores, já que essa tarefa interfere no bom funcionamento do Flow code.
Muito bom irmão, vlw de mais. Ajudou muiiiittooooo!
Tenho uma bronca aqui quando vou compilar o código, as bibliotecas
#include ulp/ulp.c>
#include ulp_main.h>
Não são encontradas, e eu já instalei o que era necessário na IDe do arduino para usar o ESP 32.
O que devo fazer pra colocar essas bibliotecas serem adicionadas ?
Show de bola esse tutorial, aguardando os proximos!!