banner

Ir para o Forum

Controle de volume por WiFi



Controle um volume digital por WiFi!
Imagine que você tem uma caixa de som em um outro ambiente e quer controlar o volume deste sistema de som através de um smartphone. Isso é possível por meio de um ESP32. Vou, então, te apresentar as chaves analógicas de estado sólido e possíveis aplicações, utilizar o CD4066 para controlar o nível de um sinal e implementar um controle via WiFi para o CD4066. Destaco que esse exemplo não serve somente para controle de volume, mas pode também chavear um vídeo analógico.





Demonstração





Recursos usados

·         Um ESP WiFi LoRa 32
·         Um LM386 – Amplificador de áudio de baixa tensão e consumo
·         Um CD4066 – Quatro chaves bilaterais analógicas
·         Seis resistores
·         Um capacitor
·         Fios para conexões
·         Um protoboard
·         Um cabo USB para o ESP




Chaves analógicas de estado sólido

·         Amplamente utilizadas em aplicações que necessitam de multiplexação e/ou controle de múltiplos canais, sejam para aquisição de dados, controle de processos, sistemas de áudio e vídeo, telecomunicações e etc.
·         Inicialmente projetadas e produzidas com componentes MOSFET discretos (final dos anos 60) e usados em módulos ou placas, tornaram-se circuitos integrados acompanhando a evolução dos processos de fabricação, dando origem a várias famílias de ci’s.
·         As melhorias alcançadas foram: a redução da resistência (Ron) da chave, o chaveamento rápido para circuitos de alta frequência, menor tensão de alimentação e potência dissipada, menor custo, miniaturização.


Circuito básico de uma chave CMOS usando um par complementar para reduzir o Ron (Analog Switches and Multiplexers Basics Tutorial – Anolog Devices)



CD4066

·         Existem vários CI’s de chaves analógicas com diversas propriedades diferentes, adequadas a uma diversidade de aplicações, como MAX4660 que podem substituir relés   para baixas correntes (até 150mA) ou HV2631 que possui 16 canais de alta tensão (220V).
·         Vamos utilizar o CD4066, devido ao seu baixo custo, facilidade de ser encontrado e por ser um circuito muito conhecido.




CD4066 – Circuito interno

·         É formado por um circuito de controle e um circuito de chaveamento.
·         Os quatro canais de sinais (SIG) são bidirecionais.
·         Os controles ativam as chaves quando em nível alto.
·         Quando desligadas as chaves apresentam alta impedância.
·         Quando fechadas possuem Ron de aproximadamente 125ohms






·         Disposição das  chaves no CD4066






Exemplos de aplicações

Amp-op inversor com ganho selecionável


Seletor de 4 sinais para um único ADC (ex. ESP8266)

Chaveamento de sinais




Controlando o nível do sinal de entrada de um amplificador de áudio

·         Como exemplo prático, vamos controlar o nível do sinal de entrada de um amplificado de áudio.
·         Para isso vamos utilizar um amplificador de áudio bastante conhecido e de fácil implementação, o LM386.
·         O LM386 possui baixo consumo, funciona com baixas tensões e possui um ganho fixado em 20, mas que pode ser ajustado de 20 a 200 através dos pinos 1 e 8.
·         Neste exemplo utilizaremos o ganho fixo de 20, e mudaremos o nível do sinal de entrada, mas seria possível aplicar a mesma ideia alterando o ganho do LM386.

Pin-out LM386


Circuito básico de ganho 20 sugerido pelo fabricante




Controlando o nível do sinal de entrada de um amplificador de áudio – Esquema utilizado

·         Substituímos o potenciômetro da entrada por um divisor de tensão selecionável



Tabela do divisor de tensão

·         O cálculo do divisor é simples mas pode ser um tanto enfadonho.
·         É importante levar em consideração as resistências apresentadas pelas chaves (aproximadamente 125 ohms).
·         Se as resistências do divisor forem altas o bastante, as resistências das chaves podem ser irrelevantes no divisor e poderão ser ignoradas.
·         Opte sempre por configurações em que as resistências das chaves não influenciem o circuito, removendo assim uma variável de seu projeto.


Os valores apresentados na tabela e utilizados no circuito foram escolhidos para atender a demonstração. Os parâmetros foram a adequação do sinal de entrada máximo para o amplificador dentro da faixa sugerida pelo fabricante (+-0,4Vpp), a disponibilidade de resistores (valores comerciais).



CD4066 versus potenciômetros digitais

·         Para uma aplicação mais refinada, torna-se mais interessante a aplicação de potenciômetros digitais. Estes dispositivos especializados superam em muitos aspectos a abordagem apresentada aqui.
·         Tomemos como exemplo a família X9C:
·         São potenciômetros de estado sólido
·         Utilizam uma interface serial de três fios (chip select, incrementar/decrementar e incremento).
·         Podem ser utilizados como potenciômetros de três terminais ou resistores variáveis de dois terminais.
·         Possuem uma matriz interna de 99 resistores.
·         Podem ser encontrados em valores de 1k, 10k, 50k e 100k ohms. (X9C102, X9C103, X9C503 e X9C104 respectivamente.)
·         Podem armazenar um estado em uma memória interna não volátil.
·         Nossa escolha por esta abordagem é simplesmente didática, já que a única vantagem seria a simples disponibilidade do CD4066.
·         Além do mais, esta demonstração pode servir de base para outras formas de controle.




Código-fonte do teste de todos os níveis (sem conexão WiFi)


Código-fonte: #includes e #defines

//Bibliotecas para utilização do display OLED
#include <Wire.h>  // Necessário apenas para o Arduino 1.6.5 e posterior
#include "SSD1306.h" // o mesmo que #include "SSD1306Wire.h"

//Os pinos do OLED estão conectados ao ESP32 pelos seguintes GPIO's:
//OLED_SDA -- GPIO4
//OLED_SCL -- GPIO15
//OLED_RST -- GPIO16

#define SDA    4
#define SCL   15
#define RST   16 //RST deve ser ajustado por software



Código-fonte: Objetos, constantes e variáveis

//Instanciando e ajustando os pinos do objeto "display"
SSD1306  display(0x3c, SDA, SCL, RST);

const int pinoA = 21; //pino A
const int pinoB = 13; //pino B
const int pinoC = 12; //pino C
const int pinoD = 14; //pino D

//Variáveis que guardam os estados dos pinos
boolean statusA = true;
boolean statusB = true;
boolean statusC = true;
boolean statusD = true;

//Variável que determina a seleção
byte selecao = 0;


Código-fonte: Setup()

void setup()
{
  pinMode(pinoA, OUTPUT); //pino 21 como saída
  pinMode(pinoB, OUTPUT); //pino 13 como saída
  pinMode(pinoC, OUTPUT); //pino 12 como saída
  pinMode(pinoD, OUTPUT); //pino 14 como saída

  // Inicia o display
  display.init();

  //Vira a tela verticalmente
  display.flipScreenVertically();
}


Código-fonte: Loop() – Switch(selecao)

void loop()
{
  //pelo valor da seleção, determina o estado dos pinos
  switch (selecao)
  {
    case 0:
      statusA = 0;
      statusB = 0;
      statusC = 0;
      statusD = 0;
      break;
    case 1:
      statusA = 0;
      statusB = 0;
      statusC = 0;
      statusD = 1;
      break;
    case 2:
      statusA = 0;
      statusB = 0;
      statusC = 1;
      statusD = 0;
      break;
    case 3:
      statusA = 0;
      statusB = 0;
      statusC = 1;
      statusD = 1;
      break;
    case 4:
      statusA = 0;
      statusB = 1;
      statusC = 0;
      statusD = 0;
      break;
    case 5:
      statusA = 0;
      statusB = 1;
      statusC = 0;
      statusD = 1;
      break;
    case 6:
      statusA = 0;
      statusB = 1;
      statusC = 1;
      statusD = 0;
      break;
    case 7:
      statusA = 0;
      statusB = 1;
      statusC = 1;
      statusD = 1;
      break;
    case 8:
      statusA = 1;
      statusB = 0;
      statusC = 0;
      statusD = 0;
      break;
    case 9:
      statusA = 1;
      statusB = 0;
      statusC = 0;
      statusD = 1;
      break;
    case 10:
      statusA = 1;
      statusB = 0;
      statusC = 1;
      statusD = 0;
      break;
    case 11:
      statusA = 1;
      statusB = 0;
      statusC = 1;
      statusD = 1;
      break;
    case 12:
      statusA = 1;
      statusB = 1;
      statusC = 0;
      statusD = 0;
      break;
    case 13:
      statusA = 1;
      statusB = 1;
      statusC = 0;
      statusD = 1;
      break;
    case 14:
      statusA = 1;
      statusB = 1;
      statusC = 1;
      statusD = 0;
      break;
    case 15:
      statusA = 1;
      statusB = 1;
      statusC = 1;
      statusD = 1;
      break;
  }


Código-fonte: Loop() – Ajusta os pinos e incrementa

/*Ajusta pinos com inversão para que
    o estado 1 represente o resistor incluido
    no divisor de tensão.
    (somente por conveniência)
  */
  digitalWrite(pinoA, !statusA);
  digitalWrite(pinoB, !statusB);
  digitalWrite(pinoC, !statusC);
  digitalWrite(pinoD, !statusD);

  //incrementa a seleção para varrer todos os estados
  selecao++;
  
  //verifica se o décimo sexto estado foi atingido
  if (selecao > 15) {
    selecao = 0;//se verdadeiro, volta para o primeiro estado
  }

*Observe a inversão ao ajustar os pinos. Essa inversão foi colocada somente por conveniência, para que um bit alto representasse o resistor participando do divisor e não o contrário.


Código-fonte: Loop() – Mostra os valores após 5s de ativação 

if (millis() > (5000)) //se está ligado a mais que 5 segundos
  {
    //Limpa o buffer do display
    display.clear();
    //ajusta o alinhamento para a esquerda
    display.setTextAlignment(TEXT_ALIGN_LEFT);
    //ajusta a fonte para Arial 16
    display.setFont(ArialMT_Plain_16);
    //Escreve no buffer do display 
    display.drawString(0, 0, "Seleção atual");
    display.drawString(0, 16, "N  ABCD");
    display.drawString(0, 32, String(selecao) + "  " + String(statusA) + String(statusB) + String(statusC) + String(statusD));
  }


Código-fonte: Tela inicial exibida por 5s após ativação


else //se está ligado a menos de 5 segundos, exibe a tela inicial
  {
    //limpa o buffer do display
    display.clear();
    //Ajusta o alinhamento para a esquerda
    display.setTextAlignment(TEXT_ALIGN_CENTER);
    //ajusta a fonte para Arial 16
    display.setFont(ArialMT_Plain_16);
    //escreve no buffer
    display.drawString(64, 0, "Ajuste de nível");
    //escreve no buffer
    display.drawString(64, 18, "com CD4066");
    //ajusta a fonte para Arial 10
    display.setFont(ArialMT_Plain_10);
    //escreve no buffer
    display.drawString(64, 44, "ESP-WiFi-Lora");
  }
  display.display();//transfere o buffer para o display

  delay(100);//aguarda um momento antes de continuar
}


Código-fonte do controle via http

Código-fonte: #includes e #defines

//Inclui biblioteca Wifi
#include <WiFi.h>
//Bibliotecas para utilização do display OLED
#include <Wire.h>  // Necessário apenas para o Arduino 1.6.5 e posterior
#include "SSD1306.h" // o mesmo que #include "SSD1306Wire.h"

//Os pinos do OLED estão conectados ao ESP32 pelos seguintes GPIO's:
//OLED_SDA -- GPIO4
//OLED_SCL -- GPIO15
//OLED_RST -- GPIO16

#define SDA    4
#define SCL   15
#define RST   16 //RST deve ser ajustado por software


Código-fonte: Objetos, constantes e variáveis

//Instanciando e ajustando os pinos do objeto "display"
SSD1306  display(0x3c, SDA, SCL, RST);

//constantes que representam os pinos
const int pinoA = 21; //pino A
const int pinoB = 13; //pino B
const int pinoC = 12; //pino C
const int pinoD = 14; //pino D

//Variáveis que guardam os estados dos pinos
boolean statusA = true;
boolean statusB = true;
boolean statusC = true;
boolean statusD = true;

//Cria um server na porta 80
//(porta padrão para onde os navegadores enviam as requisições http)
WiFiServer server(80);


Código-fonte: Setup() – saídas e mensagem inicial

void setup()
{
  pinMode(pinoA, OUTPUT); //pino 21 como saída
  pinMode(pinoB, OUTPUT); //pino 13 como saída
  pinMode(pinoC, OUTPUT); //pino 12 como saída
  pinMode(pinoD, OUTPUT); //pino 14 como saída

  // Inicia o display
  display.init();

  //Vira a tela verticalmente
  display.flipScreenVertically();

  //Limpa o buffer do display
  display.clear();

  //ajusta o alinhamento para a esquerda
  display.setTextAlignment(TEXT_ALIGN_LEFT);

  //ajusta a fonte para Arial 16
  display.setFont(ArialMT_Plain_16);

  //Escreve no buffer do display
  display.drawString(0, 0, "Conectando");

  //Exibe o buffer
  display.display();


Código-fonte: Setup() - tenta se conectar...

//Faz o ESP se conectar à rede WiFi disponível no local de uso.
  //No nosso exemplo o ssid da rede é redeWiFi e a senha é senhateste
  WiFi.begin("redeWiFi", "senhateste");

  //Enquanto o ESP não se conectar à rede
  while (WiFi.status() != WL_CONNECTED)
  {
    //Esperamos 100 milisegundos
    delay(100);
    display.drawString(0, 16, ".");
    display.display();
  }

  //Se chegou aqui é porque conectou à rede,
  //então mostramos no display para termos um feedback
  display.clear();
  display.drawString(0, 0, "Conectou!");
  display.display();
  delay(1000);


Código-fonte: Setup() – configurações de rede

//Configurações do IP fixo.
  //Você pode alterar conforme a sua rede.
  IPAddress ip(192, 168, 0, 119);
  IPAddress gateway(192, 168, 0, 1);
  IPAddress subnet(255, 255, 255, 0);

  display.clear();
  display.drawString(0, 0, "Configurando IP");
  display.drawString(0, 16, "fixo para : ");
  display.drawString(0, 32, ip.toString());
  display.display();

  //Envia a configuração
  WiFi.config(ip, gateway, subnet);

  //Inicializa o server que criamos na porta 80
  server.begin();

  delay(1000);
  //Mostramos no display o IP que o ESP possui
  //para verificarmos se é o mesmo que configuramos
  display.clear();
  display.drawString(0, 0, "Server em:");
  display.drawString(0, 16, WiFi.localIP().toString());
  display.display();
}


Código-fonte: Loop() – Aguarda por um cliente...

void loop()
{
  //Verifica se algum cliente está tentando se conectar
  WiFiClient client = server.available();
  if (!client)
  {
    //Se não houver nenhum cliente podemos retornar pois não há nada a fazer
    return;
  }

  //Mas se um cliente se conectar, atualizamos o display
  display.clear();
  display.drawString(0, 0, "Server em:");
  display.drawString(0, 16, WiFi.localIP().toString());
  display.drawString(0, 32, "Novo cliente");
  display.drawString(0, 48, "conectado!");
  display.display();

  //Fazemos a leitura da requisição
  String req = client.readStringUntil('\r');


Código-fonte: Loop() – Prepara o html da página (parte 1)

//Este é o html que iremos retornar para o cliente
  //É composto basicamente de botões numerados indicando os níveis de 0 a 15
  //A parte que nos interessa é o <a href=' com a ação vinculada a cada botão
  //Quando clicamos em um destes botões essa informação chegará até o ESP para
  //que ele verifique qual ação deve executar
  //A parte dentro de '<style>' é apenas para modificarmos o visual da página
  //que será exibida, você pode alterá-la como queira
  String html =
    "<html>"
    "<head>"
    "<meta name='viewport' content='width=device-width, initial-scale=1, user-scalable=no'/>"
    "<title>Controle de nível de sinal</title>"
    "<style>"
    "body{"
    "text-align: center;"
    "font-family: sans-serif;"
    "font-size:25px;"
    "padding: 25px;"
    "}"
    "p{"
    "color:#444;"
    "}"
    "button{"
    "outline: none;"
    "border: 2px solid #1fa3ec;"
    "border-radius:18px;"
    "background-color:#FFF;"
    "color: #1fa3ec;"
    "padding: 5px 25px;"
    "}"
    "button:active{"
    "color: #fff;"
    "background-color:#1fa3ec;"
    "}"
    "button:hover{"
    "border-color:#0000ff;"
    "}"


Código-fonte: Loop() – Prepara o html da página (parte 2)

"</style>"
    "</head>"
    "<body>"
    "<p>Escolha o nivel</p>"
    "<p><a href='?acao=0'><button>0</button></a>"
    "<a href='?acao=1'><button>1</button></a>"
    "<a href='?acao=2'><button>2</button></a>"
    "<a href='?acao=3'><button>3</button></a></p>"
    "<p><a href='?acao=4'><button>4</button></a>"
    "<a href='?acao=5'><button>5</button></a>"
    "<a href='?acao=6'><button>6</button></a>"
    "<a href='?acao=7'><button>7</button></a></p>"
    "<p><a href='?acao=8'><button>8</button></a>"
    "<a href='?acao=9'><button>9</button></a>"
    "<a href='?acao=A'><button>10</button></a>"
    "<a href='?acao=B'><button>11</button></a></p>"
    "<p><a href='?acao=C'><button>12</button></a>"
    "<a href='?acao=D'><button>13</button></a>"
    "<a href='?acao=E'><button>14</button></a>"
    "<a href='?acao=F'><button>15</button></a></p>"
    "</body>"
    "</html>";
  //Escreve o html no buffer que será enviado para o cliente
  client.print(html);
  //Envia os dados do buffer para o cliente
  client.flush();


Código-fonte: Loop() – Imagem da página servida



Código-fonte: Loop() – Avalia a requisição

//A partir daqui, verificamos se a requisição possui algum comando de
  //ajuste de sinal
  if (req.indexOf("acao=0") != -1)
  {
    statusA = 0;
    statusB = 0;
    statusC = 0;
    statusD = 0;
  }
  else if (req.indexOf("acao=1") != -1)
  {
    statusA = 0;
    statusB = 0;
    statusC = 0;
    statusD = 1;
  }

  if (req.indexOf("acao=2") != -1)
  {
    statusA = 0;
    statusB = 0;
    statusC = 1;
    statusD = 0;
  }
  else if (req.indexOf("acao=3") != -1)
  {
    statusA = 0;
    statusB = 0;
    statusC = 1;
    statusD = 1;
  }

  if (req.indexOf("acao=4") != -1)
  {
    statusA = 0;
    statusB = 1;
    statusC = 0;
    statusD = 0;
  }
  else if (req.indexOf("acao=5") != -1)
  {
    statusA = 0;
    statusB = 1;
    statusC = 0;
    statusD = 1;
  }

  if (req.indexOf("acao=6") != -1)
  {
    statusA = 0;
    statusB = 1;
    statusC = 1;
    statusD = 0;
  }
  else if (req.indexOf("acao=7") != -1)
  {
    statusA = 0;
    statusB = 1;
    statusC = 1;
    statusD = 1;
  }

  if (req.indexOf("acao=8") != -1)
  {
    statusA = 1;
    statusB = 0;
    statusC = 0;
    statusD = 0;
  }
  else if (req.indexOf("acao=9") != -1)
  {
    statusA = 1;
    statusB = 0;
    statusC = 0;
    statusD = 1;
  }

  if (req.indexOf("acao=A") != -1)
  {
    statusA = 1;
    statusB = 0;
    statusC = 1;
    statusD = 0;
  }
  else if (req.indexOf("acao=B") != -1)
  {
    statusA = 1;
    statusB = 0;
    statusC = 1;
    statusD = 1;
  }

  if (req.indexOf("acao=C") != -1)
  {
    statusA = 1;
    statusB = 1;
    statusC = 0;
    statusD = 0;
  }
  else if (req.indexOf("acao=D") != -1)
  {
    statusA = 1;
    statusB = 1;
    statusC = 0;
    statusD = 1;
  }

  if (req.indexOf("acao=E") != -1)
  {
    statusA = 1;
    statusB = 1;
    statusC = 1;
    statusD = 0;
  }
  else if (req.indexOf("acao=F") != -1)
  {
    statusA = 1;
    statusB = 1;
    statusC = 1;
    statusD = 1;
  }


Código-fonte: Loop() – ajusta e finaliza a conexão

 //Ajusta os estados dos pinos (a inversão segue o princípio descrito anteriormente)
  digitalWrite(pinoA, !statusA);
  digitalWrite(pinoB, !statusB);
  digitalWrite(pinoC, !statusC);
  digitalWrite(pinoD, !statusD);

  //aguarda para que o trafego das informações seja concluído
  delay(200);
  //Fecha a conexão com o cliente
  client.stop();
  //Ao desconectar o cliente, atualiza o display
  display.clear();
  display.drawString(0, 0, "Server em:");
  display.drawString(0, 16, WiFi.localIP().toString());
  display.display();

}


Código-fonte: Loop() – Aguardando cliente...





Faça o download dos arquivos:




2 comentários:

Tecnologia do Blogger.