Agora que já sabemos programá-lo, vamos colocá-lo para ajudar no co-processamento do sistema principal, onde reside os 2 núcleos principais do ESP32, cada um rodando a 240 MHz. A ideia deste post é tentar simular alguma tarefa pesada para o microcontrolador, em que qualquer desvio de código acarreta em uma visível falha que pode ser relevante ou não.
Um método bem simples de estressar o microcontrolador é um loop infinito sem delay efetuando alguma tarefa. Pensando nisso, faremos um PWM via software que é extremamente pior do que um PWM por hardware como Timers, que não são interferidos pelo código em si, como desvio por interrupções ou delays.
O PWM por software sofrerá com qualquer desvio de programação, inclusive interrupções externas, e é isso que será mostrado.
Vamos primeiramente entender a situação que nosso microcontrolador enfrentará:
- O sistema principal irá gerar um PWM de 4 MHz continuamente, onde não pode haver flutuações na frequência;
- Há um sensor conectado no microcontrolador para ativar um atuador sonoro em caso de pânico no sistema. Isso é feito através de uma interrupção externa, onde o código é desviado para uma rotina de interrupção (ISR) que ativa a buzina. Esse é um dos métodos mais eficazes e velozes para tratar eventos com microcontroladores, entretanto, vamos verificar como o sistema se comportou mais a frente…
Agora que já sabemos como o sistema precisa se comportar, vamos primeiramente testar o método de utilizar uma interrupção no mesmo núcleo que gera o PWM de 4 MHz. Veja o fluxograma que apenas idealiza como o processo é executado:
Código C++:
void atd()
{
REG_WRITE(GPIO_OUT_W1TS_REG, BIT2);//Ativa o atuador
}
extern "C" void app_main()
{
pinMode(13, OUTPUT);//Pino do PWM 4MHz
pinMode(02, OUTPUT);//Pino do atuador
attachInterrupt(0, atd, FALLING);//Interrupcao do botao de panico
while (1)//PWM de 4MHz
{
REG_WRITE(GPIO_OUT_W1TS_REG, BIT13);
asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
REG_WRITE(GPIO_OUT_W1TC_REG, BIT13);
asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
}
}
Com um analisador lógico podemos visualizar os pinos do microcontrolador numa linha do tempo e ver como o sistema se comportou quando o botão de pânico foi pressionado.
Observações:
- Canal 0: PWM de 4 MHz;
- Canal 1: Atuador (buzina) ativo quando em nível lógico ALTO;
- Canal 2: Botão de pânico pressionado quando em nível lógico BAIXO.
É possível mais que claramente ver a ineficiência de tratamento da interrupção e gerência do PWM de alta frequência ao mesmo tempo. Mesmo o ESP32 trabalhando em 240 MHz, seu tratamento convencional de interrupções não é tão bom se comparado com outras arquiteturas de microcontroladores, como AVR, sendo possível ver que desde o botão ser pressionado e o atuador ligar, passaram-se aproximadamente 1,3 us, o que é relativamente lento. Todo o processo de desvio da interrupção até o retorno do PWM durou aproximadamente 3,5 us e isso não é tolerável no projeto. Apesar dos métodos convencionais serem lentos, é possível atribuir, via Assembly, interrupções de baixa latência diretamente na arquitetura da XTensa, mas não vamos tão a fundo por um problema que pode ser resolvido mais facilmente com algum dos outros 2 processadores.
Você pode estar se perguntando por que não atribuí a interrupção ao outro núcleo, já que o ESP32 conta com 2 núcleos principais e podemos deixar as tarefas separadas por núcleo, mas o outro núcleo também está ocupado com outra tarefa, então sobrou o ULP.
Vamos então programá-lo para ajudar o processamento do sistema principal. O ULP ficará encarregado de tratar todos sensores e atuadores do nosso sistema, que nesse caso é apenas um de cada, mas já é suficiente para analisar como é eficiente no co-processamento, visto que quanto mais sensores, mais o sistema principal seria prejudicado.
Código C++:
extern "C"
{
#include <driver/gpio.h>
#include <driver/rtc_io.h>
#include <ulp/ulp.c>
#include <ulp_main.h>
}
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
void initULP()
{
//Configura o GPIO0 como entrada no RTC Domain (ULP reside no RTC Domain)
rtc_gpio_init(GPIO_NUM_0);
rtc_gpio_set_direction(GPIO_NUM_0, RTC_GPIO_MODE_INPUT_ONLY);
//Configura o GPIO2 como saida no RTC Domain (ULP reside no RTC Domain)
rtc_gpio_init(GPIO_NUM_2);
rtc_gpio_set_direction(GPIO_NUM_2, RTC_GPIO_MODE_OUTPUT_ONLY);
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()
{
initULP();//Funcao que inicializa o ULP
//Configura o GPIO13 como saida
gpio_pad_select_gpio(13);
gpio_set_direction(GPIO_NUM_13, GPIO_MODE_OUTPUT);
while (1)//PWM 4 MHz
{
REG_WRITE(GPIO_OUT_W1TS_REG, BIT13);//GPIO13 = HIGH
asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
REG_WRITE(GPIO_OUT_W1TC_REG, BIT13);//GPIO13 = LOW
asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
}
}
Código ASM:
#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)//Desliga o atuador (GPIO2 = LOW) loop: READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S+11, 1)//Le o estado do GPIO0 e guarda no R0 jumpr on, 1, lt//Se o botao for pressionado (0), ativa o atuador WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TC_S+12, 1, 1)//Caso o botao nao esteja pressionado, mantem o atuador desligado jump loop//retorna ao loop on://ativa o atuador e retorna ao loop WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S+12, 1, 1)//Ativa o atuador (GPIO2 = HIGH) jump loop
Vamos analisar novamente pelo analisador lógico, como o sistema se comportou quando o botão de pânico foi pressionado.
Observações:
- Canal 0: PWM de 4 MHz;
- Canal 1: Atuador (buzina) ativo quando em nível lógico ALTO;
- Canal 2: Botão de pânico pressionado quando em nível lógico BAIXO.
Observe que mesmo durante o evento (pressionar do botão e atuador ativar) o PWM continuou perfeitamente como o esperado (4 MHz), mostrando a eficiência e importância de usar outro núcleo/microcontrolador para ajudar no processamento.
Os co-processadores têm uma trajetória relativamente importante para computação atual, sendo um dos mais famosos a Float Point Unit (FPU), que é um co-processador para efetuar cálculos de ponto flutuante presente na maioria dos dispositivos atuais, inclusive no ESP32. Os co-processadores livram o processador central de alguma tarefa, tornando o sistema, em geral, mais rápido.
O ULP pode ter poucos Mnemônicos (Instruction set limitado), mas se torna importantíssimo em projetos específicos, como nos 2 citados neste artigo. O simples fato de conseguir ler pinos digitais/analógicos e controlar pinos já o torna um aliado interessante para você aprender e utilizar.
Saiba mais
Idealização de um projeto IoT portátil
Embarcados interview: Jack Ganssle



