ÍNDICE DE CONTEÚDO
- 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
O ESP32, SoC da Espressif, além de todo potencial que é mostrado pelos mais diversos autores, conta com um terceiro processador, que ainda não foi dito aqui e em muitos lugares. Este terceiro processador é chamado de ULP (Ultra Low Power), feito especificamente para operação em Low Power, já que o consumo é de aproximadamente 150 uA, ou onde precisamos de co-processador para efetuar algum processamento paralelo ao sistema principal, como ler estados de pinos (digitais e analógicos), efetuar operações aritméticas/lógicas e até comunicações com outros sistemas embarcados enquanto o sistema principal faz algum processamento pesado ou de tempo crítico. O ULP é programado no ambiente de desenvolvimento chamado ESP-IDF, que é o local padrão de desenvolvimento do ESP32 e conta com todas ferramentas e features disponíveis para esse microcontrolador.
Por que e quando devo utilizar o ULP?
O ULP é indicado para operações e projetos Low Power ou ajudar o sistema principal em alguma tarefa (co-processamento). Vamos justificar estes 2 usos básicos:
- Low Power: Normalmente em dispositivos portáteis, o poder de processamento é baixo ou depende de algum evento externo para que ocorra alguma ação, por exemplo um sensor chegar em um valor específico. Por conta disso, não é indicado o uso de todo o potencial do microcontrolador, aí entramos com os métodos de economia do microcontrolador (Sleeps).
O ULP entraria em ação neste caso para, por exemplo, fazer Polling do sensor, já que seu consumo (150 uA) é extremamente mais baixo que o sistema principal (30-50 mA). Quando o ULP verificar que o sensor gerou um evento necessário para a ação do sistema, acordará o sistema principal para que seja feito o serviço pesado, como envio de dados ao banco de dados.
- Co-processamento: Imagine que seu microcontrolador está trabalhando quase no seu limite. Por exemplo, fazendo leitura de dezenas de sensores e controlando diversos atuadores; isso tudo sem que haja perda de sincronismo por conta de um passo longo a ser realizado para tomada de decisão, como uma operação matemática complexa ou tratamento dos sensores e atuadores. Com esse cenário, é comum adicionar outros microcontroladores para ajudar a efetuar algumas tarefas específicas e livrar o sistema principal para focar na tarefa crítica.
O ULP entraria nesse cenário justamente efetuando alguma dessas 3 ações. Podemos atribuí-lo tanto a leitura dos sensores quanto o controle dos atuadores e até as contas matemáticas para tomada de decisões. Ele seria visto como um outro microcontrolador do sistema, mas este “mora“ dentro do próprio ESP32, o que ajuda extremamente na velocidade de processamento e evita comunicações lentas entre sistemas separados. Poderíamos deixar o controle dos atuadores por conta total do ULP, assim livramos o sistema principal dessa tarefa e deixariamos a leitura dos sensores melhores, podendo-se adicionar um Polling mais profundo.
Faremos esses 2 projetos de forma simples, separadamente, para mostrar os conceitos de aplicação do incrível ULP:
- Low Power: Deixaremos o microcontrolador em Deep Sleep para economia de energia, enquanto o ULP ficará lendo um sensor analógico. Quando for detectado o nível de tensão definido, efetuará alguma ação como acordar o microcontrolador para uma tarefa pesada ou algo do tipo;
- Co-processamento: Faremos alguma tarefa pesada de tempo crítico com o sistema principal em que qualquer desvio de processamento prejudicará essa tarefa designada. O ULP ficará lendo sensores para detectar se há alguma mudança que precise ser feita na tarefa do sistema principal e, assim, livramos o sistema principal de fazer a checagem de sensores, visto que iria interferir no tempo de processamento e atrapalhar o Flow code.
Não será ensinado aqui como instalar a ESP-IDF + ULP, entretanto nos links abaixo há todas as instruções para instalação da ESP-IDF e do ULP na IDF.
Instalação da ESP-IDF
Instalação do ULP na IDF
Características do ULP
O ULP pode ser pensado como um microcontrolador dentro de outro. Há algumas restrições de acesso às memórias e periféricos, mas vamos adotá-lo como um microcontrolador independente dentro do ESP32 para facilitar a didática. Ele é focado em Low Power e, por causa disso, seu consumo é extremamente baixo. Mesmo sabendo desse foco, podemos utilizar para o que bem entendermos, como no co-processamento.
- Consumo máximo (100% duty cycle): 150 uA;
- Clock: 8,5 MHz +- 7%;
- Memória (RTC_SLOW_MEM): 8 KB;
- 4 Registradores de 16 bits para uso geral (R0-R3);
- 1 Registrador de 8 bits para contagens e loops (STAGE_CNT);
- 26 Mnemônicos.
Aqui me cabe fazer algumas observações importantes para que não haja confusões na hora de você testar na prática.
- O consumo máximo é obtido quando o ULP fica 100% do tempo ligado, entretanto, é comum de utilizá-lo como se fosse um LED piscando com PWM, por isso foi dito com 100% duty cycle. O método utilizado para baixar ainda mais seu consumo é criar paradigmas para que seja feito algum processamento como ler um sensor, voltar a dormir e acordar algum tempo predefinido depois para ler novamente o sensor ou efetuar sua tarefa.
- O clock ainda é uma variável para muitos, já que foi dito nos datasheets que seu clock é de 8 MHz, entretanto isso não é uma verdade absoluta. O clock do ULP está numa grande faixa de variação de chip para chip, que varia inclusive pela temperatura em que o microcontrolador se encontra. A faixa de variação do Clock é de 7,905 MHz até 9,095 MHz.
Por conta dessa grande variação de chip para chip e ainda a diferença pela temperatura, para nossa sorte, os desenvolvedores do ESP32 criaram uma função na IDF que retorna o clock aproximado e, com isso, conseguimos utilizar esse clock calculado para efetuar tarefas de tempo crítico ou comunicações seriais de alta frequência, onde o tempo é crucial. Ainda sim, essa função nem sempre retorna o mesmo valor, fazendo com que tenhamos que fazer alguma espécie de benchmark para cálculo real do clock, como algum PWM e osciloscópio, analisador lógico ou frequencímetro.
- A memória dedicada do ULP é de 8 KB (RTC_SLOW_MEM), onde fica seu código e variáveis, entretanto, é óbvio que podemos comunicar-se com o sistema principal, já que essa memória é compartilhada entre os 3 processadores. Podemos fazer envio de variáveis para lá e também a obtenção delas, onde há uma imensa quantidade de memória disponível e, com isso, criar métodos para expandir a memória do ULP através da memória principal.
Podemos tanto acessar variáveis criadas no sistema principal (Main Core programado em C/C++) como também acessar as variáveis criadas no ULP (programado em Assembly) pelo sistema principal. Essa capacidade de comunicação entre os 2 sistemas permite uma incrível e gigantesca gama de aplicações.
- Os Mnemônicos, ou Instruction set disponíveis para uso no ULP, estão disponíveis tanto no Datasheet quanto nesta página web.
Entendendo o funcionamento do ULP
Vamos começar com um fluxograma de como é o funcionamento do sistema inteiro até que o ULP entre em execução. O sistema principal deve definir (reservar) a memória de uso e também seu Entry Point antes que o ULP entre em execução, ou seja, ele é dependente do sistema principal para ser iniciado.
Ainda podemos utilizá-lo (para uma economia de energia maior) similarmente a um LED com PWM. Isso ocorre com a junção de 3 itens:
- Timer do ULP: Responsável por acordá-lo com o tempo pré-definido. O período deste pode ser definido e alterado a qualquer momento, tanto pelo sistema principal quanto pelo próprio ULP. Após otTimer acordar o ULP, o periférico entra em uma espécie de “espera” e fica inativo até que o comando HALT seja executado pelo ULP, onde todo o processo é reiniciado e o timer volta a contar para acordar o ULP novamente.
- Execução do código: Após ser acordado, começará a execução normal de seu código.
- Mnemônico HALT: Responsável por fazê-lo dormir.
Vejamos um gráfico do funcionamento do método citado acima, que é bastante utilizado para operações e projetos de Low Power:
- Timer habilitado. Como pode ser visto, o Sleep cycles é de 500, logo, após 500 ciclos do Timer, o ULP acordará automaticamente;
- 500 ciclos expirado e ULP entra em execução do seu código;
- Ao fim de seu código, o comando HALT é executado, colocando o ULP para dormir novamente. O Timer também é reativado e retorna a contagem dos ciclos;
- Sleep cycles mudado para 1100, a frequência de execução do código será menor que antes;
- Timer desabilitado. Caso você execute o HALT após o Timer estar desabilitado, o ULP não acordará novamente.
Já para projetos onde é usado para ajudar no processamento principal, normalmente, não é utilizado essa técnica para baixar o consumo. Então nós iremos apenas ativar o ULP e mantê-lo funcionando o tempo todo ou desejado.
No próximo post desta série sobre ULP vamos começar a programá-lo focando em operações e projetos de Low Power, deixaremos o sistema principal em Deep Sleep enquanto o ULP lê um sensor analógico para efetuar alguma ação, como acordar o ESP32 do Deep Sleep. Os projetos feitos serão simples apenas para demonstração básica do funcionamento e não dificultar o entendimento geral.