20 de janeiro de 2022

Do bit Ao Byte

Embarcados, Linux e programação

Como usar microfone no ESP32 (ESP32 voice streamer)

Domótica - MQTT e Raspberry - servidor NTP no Raspberry

O ESP32 é incrível e já deve ter sido possível reparar que é o meu preferido pela quantidade de artigos que escrevo sobre ele. Usar microfone no ESP32, vejam só, é tão simples quanto usar um display. Ou talvez mais simples.

Com um módulo microfone de eletreto, um ESP32, jumpers e um pouco de código, faremos o ESP32 enviar streming de áudio para um Raspberry, que será aplicado em um próximo artigo em algo bem interessante.

Materiais

Para esse projeto você precisará:

  • Comprar ESP32 (parceiro CurtoCircuito).
  • Módulo microfone de eletreto MAX9814 Agc (fico devendo link, nenhum dos parceiros tem).
  • Jumpers.
  • Comprar um Raspberry para receber o áudio, caso não tenha (também do parceiro CurtoCircuito).

Circuito

O circuito é simples. Alimenta-se com 5V e GND, coloca-se um divisor de tensão na saída e pronto. O divisor de tensão é necessário porque a saída do módulo varia entre 0 e 2.25V e o ADC do ESP32 é de apenas 1V (mas 12 bits). Para conectar o microfone no ESP32, utilize a saída do jumper verde, como mostrado no desenho abaixo.

microfone no ESP32

Não sei como aproximar mais de 1V, esses resistores são valores padrão, não adianta eu colocar 5k e 4k pra bater 1V. A diferença representa 7%, talvez passar um pouquinho não seja problema. No caso, 4k3 e 5k6.

Configurar a recepção no Raspberry

Antes de darmos sequência à configuração do microfone no ESP32, vamos ao Raspberry Pi. Faça login no sistema e como root, execute o configurador do sistema. No meu caso, o IP do Raspberry é fixo porque ele também é meu servidor DNS e broker MQTT. Seu IP é 192.168.1.2, por isso daqui em diante será utilizado no exemplo. Além disso, configurei o usuário root nele:

ssh root@192.168.1.2
#após login:
raspi-config

No menu em ncurses que se abre, vá em Advanced Options > Audio > Force 3.5mm (‘headphone’) jack.

Ou então, configure diretamente no boot.txt para funcionar no boot e para funcionar imediatamente, digite:

modprobe snd_bcm2835
depmod -a
amixer cset numid=1

Se (em ambos os casos) obtiver um erro como “amixer: Control default open error: No such file or directory”, tente atualizar o sistema:

apt-get update
apt-get upgrade

Reinicie o sistema e tente novamente.

NetCat

O programa NetCat é utilizado para estabelecer conexões e escutas UDP e TCP. A porta utilizada nesse exemplo é a 8888, mas você pode optar por qualquer porta livre no sistema, acima de 1024. Se quiser verificar previamente se a porta que escolheu está em uso, faça-o com o comando:

netstat -naut|awk '{print $4}'| egrep '8888$'

Substituindo 8888 pelo número da porta escolhida, claro. Esse comando só retornará alguma coisa se a porta estiver em uso. Por exemplo:

microfone no ESP32

Nesse caso, selecione outra porta ou pare o serviço que estiver rodando nela, se for dispensável.

Aplay

O Aplay é um programa de linha de comando para gravar (arecord) e tocar áudio com ALSA.  Passaremos o formato e a taxa de amostragem em Hz. Esse formato mataria de infarto qualquer audiófilo, mas devemos lembrar que estamos fazendo quase uma mágica aqui. O comando a seguir abre a recepção por rede e toca o áudio recebido. Coloque uma caixa de som no jack do Raspberry ou use um headphone para ouvir.

nc -l 8888 | aplay -r 8000 -f U8

Microfone no ESP32

Agora que o Raspberry está pronto para escuta, vamos subir o sketch que habilitará o microfone no ESP32.

#include <Arduino.h>
#include <WiFi.h>
#include <driver/adc.h>

#define AUDIO_BUFFER_MAX 800

uint8_t audioBuffer[AUDIO_BUFFER_MAX];
uint8_t transmitBuffer[AUDIO_BUFFER_MAX];
uint32_t bufferPointer = 0;

const char* ssid     = "SUA_REDE_AQUI";
const char* password = "SUA_SENHA_AQUI";
const char* host     = "IP_DO_RASPBERRY";

bool transmitNow = false;

WiFiClient client;

hw_timer_t * timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; 

void IRAM_ATTR onTimer() {
  portENTER_CRITICAL_ISR(&timerMux); // para rodar um código crítico sem ser interrompido.
  int adcVal = adc1_get_voltage(ADC1_CHANNEL_0); // faz a leitura do ADC
  uint8_t value = map(adcVal, 0 , 4096, 0, 255);  // mapeamento para 8 bits
  audioBuffer[bufferPointer] = value; // armazenamento do valor
  bufferPointer++;
 
 // Ação no preenchimento do buffer
  if (bufferPointer == AUDIO_BUFFER_MAX) {
    bufferPointer = 0;
    memcpy(transmitBuffer, audioBuffer, AUDIO_BUFFER_MAX); // transfere o buffer
    transmitNow = true; // flag para envio do buffer
  }
  portEXIT_CRITICAL_ISR(&timerMux); // prioridade no código crítico
}


void setup() {
  Serial.begin(115200);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("MY IP address: ");
  Serial.println(WiFi.localIP());
  
  adc1_config_width(ADC_WIDTH_12Bit);
  adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_0db); //ADC 1 canal 0 GPIO36

  const int port = 8888;
  while (!client.connect(host, port)) {
    Serial.println("connection failed");
    delay(1000);
  }

  Serial.println("connected to server");

  timer = timerBegin(0, 80, true);
  timerAttachInterrupt(timer, &onTimer, true);
  timerAlarmWrite(timer, 125, true);
  timerAlarmEnable(timer);

}

void loop() {
  // aguarda pela ordem de envio
  if (transmitNow) {
    transmitNow = false;
    client.write((const uint8_t *)audioBuffer, sizeof(audioBuffer));
  }
}

Invés de utilizar um flag no loop, podemos criar uma task no núcleo 0 do ESP32 a ser iniciada em cada preenchimento do buffer. Do jeito que está, o ESP32 não tem outra aplicação que não seja a de transmitir áudio.  Mas isso veremos em outro artigo, onde pretendo inserir mais alguns recursos. Manterei a surpresa.

Pra reforçar, esse código é só uma prova de conceito, sem tratamento de exceções, mas já será modificado no próximo artigo relacionado, quando estaremos adicionando mais recursos ao sistema. Nesse momento, só rode o sketch do ESP32 após iniciar a escuta no Raspberry, ok?

Aproveitei a ocasião para dar uma mexida no Raspberry e acabei prejudicando o sistema. Agora estou baixando uma imagem nova e precisarei reconfigurar o servidor DNS, broker MQTT, reconhecimento facial e o servidor de streaming de áudio. Por isso, devo demorar uns dias pra postar o vídeo, mas logo mais você poderá vê-lo em nosso canal DobitAoByteBrasil no Youtube. Se inscreva, clique no sininho para receber notificações e, por favor, gaste 1 clique de mouse dando seu like para motivar mais vídeos.

Até a próxima!