Leitura de sensores analógicos pelo ADC com ULP
Neste tutorial, aprenderemos a ler um sensor analógico através do ADC no ESP32 em Deep Sleep, utilizando o ULP. Esse método de leitura, permite uma economia de bateria gigantesca, já que não precisamos acordar o microcontrolador para efetuar a leitura, situação na qual o gasto é bem maior do que quando mesmo procedimento é feito pelo ULP.
[toc]
Por que utilizar?
Imagine que em seu projeto portátil, é preciso ler o sensor de temperatura a cada 30 segundos e para isso, logicamente, precisamos acordar o microcontrolador para efetuar essa tarefa, entretanto, o consumo do ESP32 nesse caso seria de 40mA apenas para ler o sensor. Com o ULP, podemos fazer essa leitura em Deep Sleep e o consumo enquanto o ULP está funcionando não passaria de 150uA, com uma média de consumo dependendo do duty cycle do ULP. Com isso, economizaríamos nesse caso de 150uA (100% duty cycle), aproximadamente 270x mais bateria do que se acordássemos o microcontrolador para essa mesma tarefa.
É fácil perceber o aumento incrível na duração da bateria que poderia ser obtido apenas ao usar o ULP para ler o sensor, as aplicações desse guerreiro são muito grandes, mas o foco é para Sleep.
Mãos a obra – Lendo um sensor de temperatura em Deep Sleep
Componentes necessários
- 1x – ESP32 (Usaremos o NodeMCU 32).
- 1x – LM35.
Códigos do projeto
– Main code (C ou C++), responsável pela programação do ESP32 em si.
#include <C:/msys32/ESP32/ESP32/components/arduino/cores/esp32/Arduino.h> #include <C:/msys32/ESP32/esp-idf/components/driver/include/driver/rtc_io.h> #include <C:/msys32/ESP32/esp-idf/components/driver/include/driver/adc.h> #include <C:/msys32/ESP32/esp-idf/components/ulp/ulp.c> #include <C:/msys32/ESP32/ESP32/build/main/ulp_main.h> extern "C" { #include <C:/msys32/ESP32/esp-idf/components/esp32/include/esp_clk.h> } //Pode ser preciso arrumar os diretorios das bibliotecas //Pode ser preciso remover o "extern 'C'{}" e definir a biblioteca fora dele, alguns usuarios relatam erro sem o uso do extern 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"); void ulp(); extern "C" void app_main() { initArduino();//inicia configuracoes do arduino, caso nao use o Arduino component, remova essa linha pinMode(2, OUTPUT); if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_ULP)//se o wakeup for por causa do ULP, tomara alguma atitude { digitalWrite(2, 1); delay(500); digitalWrite(2, 0); } else//se nao, iniciara o ULP { ulp();//configura e inicializa o ulp } esp_sleep_enable_ulp_wakeup();//habilita o wakeup pelo ULP esp_deep_sleep_start();//entra em deep sleep eterno } void ulp() { adc1_config_channel_atten(ADC1_CHANNEL_4, ADC_ATTEN_11db); adc1_config_width(ADC_WIDTH_12Bit); adc1_ulp_enable(); //configura o ADC1 #4 (GPIO32) para 3.3V 12bit e atribui o uso ao ULP ulp_set_wakeup_period(0, 10000000);//ativa o timer de wakeup do ULP apos cada HALT para 10seg ulp_load_binary(0, ulp_main_bin_start, (ulp_main_bin_end - ulp_main_bin_start) / sizeof(uint32_t));//carrega os arquivos ulp_run((&ulp_main - RTC_SLOW_MEM) / sizeof(uint32_t));//inicia o ULP }
– ULP code (Assembly .S), responsável pela programação do ULP em si.
#include "soc/soc_ulp.h" #include "soc/rtc_io_reg.h" #include "soc/sens_reg.h" #include "soc/rtc_cntl_reg.h" .bss//secao das variaveis .text//secao do codigo .global main main: move r0, 0//r0 = 0 move r1, 0//r1 = 0 stage_rst//stage_cnt = 0 leitura: stage_inc 1//stage_cnt++ adc r1, 0, 4+1//r1 = leitura ADC GPIO32 add r0, r0, r1//r0 = r0+r1 jumps leitura, 4, lt//loop responsavel pelas leituras, equivale a um FOR() rsh r0, r0, 2//calcula a media das 4 leituras r0 = r0/4 jumpr wakeup, 496, ge//se a media da leitura que esta em r0 for maior ou igual que 496 (40 graus celsius), acorda o mcu halt//coloca o ULP em sleep e ativa o timer de wakeup (definido com 10 segundos no main code) wakeup: wake//acorda o mcu halt
Entendendo a fundo
Software
leitura: stage_inc 1 adc r1, 0, 4+1 add r0, r0, r1 jumps leitura, 4, lt rsh r0, r0, 2 jumpr wakeup, 496, ge halt
A parte interessante e diferente do código é justamente a leitura do canal ADC, nós poderíamos fazer mais simples sem utilizar uma estrutura de repetição FOR(), entretanto, é sempre interessante fazer uma média de leituras (inclusive com delay’s) para evitar ruídos e coisas do tipo. Já ensinamos a fazer o laço FOR() no tutorial introdutório do ULP.
- É feito a leitura do canal ADC no GPIO32 4x e a cada leitura, o valor é somado no registrador R0.
- Após as 4 leituras precisamos tirar a média entre eles, entretanto, não há mnemônicos simples para divisão como “DIV”. Usaremos os operadores de BitWise (RSH – Right Shift Bit) para dividir o valor por 4. Por causa da divisão facilitada com o RSH na base 2 (2, 4, 8, 16…), também faremos uma quantidade de leituras na base 2.
Não se esqueça de conferir o Set de Instruções do ULP nas referências em caso de dúvidas.
-Mnemônico ADC
adc r1, 0, 4+1
Efetua a leitura do ADC no GPIO32 e atribui o valor ao registrador R1.
O segundo e terceiro operador (parâmetro) deste mnemônico se refere a tabela abaixo, onde:
Segundo operador: Controlador ADC (ADC1 = 0 ou ADC2 = 1). Usamos “0” pois o GPIO32 faz parte do ADC1.
Terceiro operador: MUX+1. O GPIO32 esta descrito como “…CH4”, logo foi usado seu canal+1 (4+1).
-Mnemônico RSH
rsh r0, r0, 2
O Right Shift Bit foi usado para calcular a média das 4 leituras. Lembre-se que os valores decimais (após a vírgula) são excluídos, restando apenas os inteiros.
-Mnemônico JUMPR
jumpr wakeup, 496, ge
Esse é o nosso “IF a moda antiga”, que pula para a label “wakeup” se o valor do ADC for maior ou igual que 496. Ocasionando no Wake up do ESP32 pelo ULP.
Pelo fato de operações aritméticas não serem tão simples neste Assembly do ULP, em vez da condicional (jumpr) que faz o wake up do ESP32 usar valores como 32°C, onde é preciso efetuar varias contas, usaremos o valor direto do ADC que economiza processamento e tempo. O valor 496 no ADC com o LM35 equivale a 40°C .
Mais informações sobre o LM35 podem ser vistas clicando aqui.
Considerações finais
Mesmo que as aplicações do ULP estejam voltadas a Sleep, podemos usa-lo até com o ESP32 ligado, para por exemplo ler o canal ADC enquanto o ESP32 faz outra tarefa em que não se possa “perder tempo” lendo os lentos canais de ADC. Também é possível criar funções ISR para criar interrupções entre ULP e Main Core, deixando a brincadeira em um nível muito mais sério e interessante.
Desafios
O desafio desta vez é criar a rotina de interrupção citada acima (ISR) para uma comunicação extremamente rápida e eficiente entre ULP e Main Core. Você pode procurar no datasheet sobre o registrador (bit) que o comando WAKE do ULP ativa quando o ESP32 não esta em Sleep e criar a ISR.
Referências
http://esp-idf.readthedocs.io/en/latest/api-guides/ulp_instruction_set.html
Blz José, consegui fazer e ficou muito bom, o RCT funciona no modo deep seep e acordado, segue como eu fiz
now.tv_sec = sec – 10800; // 10800 ofset da região
setenv(“BRT”, “UTC-3”, 1);
tzset();
settimeofday(&now, NULL);
Agradeço muito o retorno, e´difícil encontrar material sobre RTC do ESP32, vc teria algum exemplo ??
estou usando a biblioteca “time.h” , quando retorno do deep sleep a hora fica zerada.
time_t t = now();
Serial.print(hour(t));
Serial.print(“/”);
Serial.print( minute(t));
Serial.print(“/”);
Serial.print( second(t));
Serial.println(“”);
estou usando o Arduino
Ela retorna zero pois é iniciada em zero. Você deve usar a função “settimeofday()” para configurar a data atual. Após isso ela será mantida atualizada até que a bateria/alimentação se esgote.
Ola
Como posso incrementar a hora na ULP , p quando o esp32 sair do modo deep sleep poder recuperar a hora
Se habilitado (que é por padrão) o clock interno de 150KHz pro RTC, isso já é feito automaticamente, bastando você requisitar a hora/dia/etc. As funções para vc “setar” ou ler a data atual são as mesmas que a biblioteca “time.h” da linguagem C. https://www.tutorialspoint.com/c_standard_library/time_h.htm
Por exexemplo, usando time(&*variavel*) retornaria a data configurada ou o tempo desde que esta ligado.
O set de instruções(assembly) do ULP, é diferente das instruções dos cores normais ?
Não que eu esteja com a real intenção de aprender os dois sets de instrução, mas só como curiosidade, por enquanto.
Sim. Se quiser programar o esp32 em asm, procure sobre o set de instruções da arquitetura XTENSA (https://github.com/eerimoq/hardware-reference/blob/master/esp32/xtensa%20Instruction%20Set%20Architecture%20(ISA)%20Reference%20Manual.pdf)