17 de maio de 2021

Do bit Ao Byte

Embarcados, Linux e programação

ESP32 Modem Sleep

modem sleep | UART wakeup | sleep modes com ESP32

Da série sobre sleep modes, recomendei o artigo de modem sleep do ESP8266, que é diferente do ESP32. Quando notei que o artigo que havia escrito era pra ESP8266, resolvi escrever esse artigo adicional para completar a série, que ainda fica faltando o UART wakeup funcional.

Da série, precedem esse artigo:

Recomendo a leitura da série completa para definir o melhor modo para seu projeto.

O que é Modem Sleep?

Se não leu a série, pode ser que fique na dúvida, apesar do nome ser um forte indicativo do processo. No modem sleep desabilitamos o bluetooth e WiFi, o que economiza horrores! Nesse modo, meros 3mA sustentam o ESP32 com clock de 80MHz. Aliás, já citei como definir o clock em 80, 160 ou 240MHz, mas vou citar novamente mais adiante.

No modem sleep, deixamos o rádio desligado e reabilitamos quando necessário enviar algum dado. Se pretende utilizar esse modo no ESP8266, leia o artigo “Desativar WiFi no ESP8266 (e reativar)“.

Como configurar o Modem Sleep

Apesar de ser um processo simples, precisa ser planejado. Suponhamos que haja a necessidade de envios periódicos de telemetria para um broker MQTT. Quando iniciamos o serviço MQTT, ele ficará em execução contínua em um loop ou então em uma task, como normalmente descrevo nos artigos sobre MQTT com ESP32. Se desligarmos o rádio, o que acontecerá com a conexão MQTT? Normalmente implemento um callback de mensagem e de reconexão. Se falhar a comunicação, o processo tentará restabelecer a conexão indefinidamente, ou talvez faça o ESP32 reiniciar, ou sabe-se lá o quê. Portanto, para implementar o Modem Sleep é necessário considerar o tratamento de todos os processos que façam uso da RF: MQTT, webserver, NTP, DNS etc.

Função para ligar e desligar o WiFi e bluetooth

Para configurar o Modem Sleep, criei algumas funções básicas:

void send_and_sleep(){ 
    rf_on();
    xTaskCreatePinnedToCore(vMQTT, "vMQTT", 10000, NULL, 0, &mqttConn, 0);
    vTaskDelay(pdMS_TO_TICKS(10000));
    rf_off();
}

Essa é a função “macro”. Quando chamada, ligará o RF, iniciará a task do MQTT (que é executada de forma assíncrona, pois é uma task) e, enquanto aguardamos a finalização da task, pausamos a execução dessa função macro por 10 segundos. Passados os 10 segundos, certamente (com uma gorda margem) a tarefa do MQTT estará cumprida, então podemos desligar novamente o RF. Essa task já é famosa nos artigos relacionados ao MQTT, mas algumas modificações foram necessárias também.

Função para religar o WiFi

void rf_on(){
    //Inicializa o WiFi
    bool bye = WiFi.setSleep(false);
    WiFi.mode(WIFI_STA);
    vTaskDelay(pdMS_TO_TICKS(30));
    WiFi.begin(wifiConfig.ssid, wifiConfig.passwd);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("Connected to WiFi");
}

Para ligar é moleza. A função setSleep() retorna um boolean. O retorno está sendo pego, mas não usado. Em seguida, colocamos o WiFi no modo STA, com um gigantesco delay de 30ms, apenas para garantir que tudo estará configurado para o próximo passo. Feito isso, iniciamos o processo normal de conexão, que normalmente colocamos na função setup().

Função para desligar o WiFi e bluetooth

void rf_off(){
    bool bye = WiFi.disconnect(true);
    WiFi.mode(WIFI_OFF);
    btStop();
    bye = WiFi.setSleep(true); //recebe beacons apenas
    vTaskDelay(pdMS_TO_TICKS(100));
}

Do mesmo modo, estou pegando o retorno da desconexão do WiFi e lá embaixo, o retorno da função setSleep(). A sequência é simples: desconecta do WiFi, muda o modo para WIFI_OFF (ou WIFI_MODE_NULL, que é a mesma coisa), pára o bluetooth, coloca em sleep. Nesse modo, apenas beacons deveriam ser recebidos, mas lembre-se de que desconectamos e desabilitamos o WiFi.

Isso é tudo o que precisamos fazer. Lembre-se de tratar todas as coisas que usem a conexão WiFi, senão os problemas podem ser diversos e não adianta me culpar.

O resultado de um teste é semelhante ao desse vídeo, onde fiz o processo com um ESP8266.

Revisão: Ricardo Amaral de Andrade