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.

Figura 1 – ULP.

[toc]

kit robotica educacional com Arduino ESP ou Microbit

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.

Se você ainda não conhece o ULP, clique aqui para ver a introdução sobre este incrível coprocessador de baixo consumo presente no ESP32.


Mãos a obra – Lendo um sensor de temperatura em Deep Sleep

Componentes necessários

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.

  1. É feito a leitura do canal ADC no GPIO32 4x e a cada leitura, o valor é somado no registrador R0.
  2. 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).

Figura 2 – Pinagem MUX do ADC.

-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

Ultra Low Power coprocessor (ULP) – ESP32

Privacy Preference Center