Site icon Embarcados – Sua fonte de informações sobre Sistemas Embarcados

Usando o ULP do ESP32 em projetos Low Power

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.

Figura 1 – Fluxograma do código.

Código em C++:

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:

#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.

Saiba mais

MSP430 – Modos de Low-Power

Idealização de um projeto IoT portátil

Embarcados interview: Jack Ganssle