Introduction: WiFi Volume Control

About: Do you like technology? Follow my channel on Youtube and my Blog. In them I put videos every week of microcontrollers, arduinos, networks, among other subjects.

Imagine that you have a speaker box in a different room, and you want to control the speaker volume through a smartphone. This is possible through an ESP32. I'm going to introduce you to solid-state analog keys and their possible applications, using the CD4066 to control the level of a signal and implementing a WiFi control for the CD4066. I point out that this example is not only for volume control, but it can also switch analog video.

Step 1: Demonstration

Step 2: Resources Used

• One ESP WiFi LoRa 32

• An LM386 - Low Voltage Audio Amplifier and Consumption

• One CD4066 - Four Analog Bilateral Keys

• Six resistors

• A capacitor

• Connection wires

• A protoboard

• One USB cable for ESP

Step 3: Solid State Analog Switches

• Widely used in applications requiring multiplexing and / or multi-channel control, whether for data acquisition, process control, audio and video systems, telecommunications, and so on.

• Initially designed and produced with discrete MOSFET components (late 1960s) and used in modules or boards. They became integrated circuits following the evolution of manufacturing processes, giving rise to several families of CIs.

• The improvements achieved were: reduction of resistance (Ron) of the switch, fast switching to high frequency circuits, lower supply voltage and dissipated power, lower cost, and miniaturization.

The basic circuit of a CMOS key uses a complementary pair to reduce the Ron (Analog Switches and Multiplexers Basics Tutorial - Analog Devices)

Step 4: CD4066

• There are several ICs of analog keys with different properties, suitable for a variety of applications, such as MAX4660 that can replace low current relays (up to 150mA) or HV2631, which has 16 high voltage (220V) channels.

• We will use the CD4066, due to the fact that it’s cheap and easy to find, and that it’s a well-known circuit.

Step 5: CD4066 - Internal Circuit

• It consists of a control circuit and a switching circuit.

• The four signal channels (GIS) are bidirectional.

• The controls activate the keys when at high levels.

• When switched off, the switches have high impedance.

• When closed, they have approximately 125ohms of Ron

• Key layout on CD4066

Step 6: Application Examples

Amp-op inverter with selectable gain

4-signal selector for a single ADC (e.g. ESP8266)

Signal Switching

Step 7: Controlling the Input Signal Level of an Audio Amplifier

• As a practical example, let's control the input signal level of an audio amplifier.

• For this, we will use a well-known and easily implemented audio amplifier, the LM386.

• The LM386 is low power, operates at low voltages, and has a gain set at 20. But it can be adjusted from 20 to 200 via pins 1 and 8.

• In this example, we will use the fixed gain of 20, and we will change the level of the input signal. But it would be possible to apply the same idea by changing the gain of the LM386.

Pin-out LM386

Basic gain circuit 20 suggested by the manufacturer

Step 8: Controlling the Input Signal Level of an Audio Amplifier - Schematic Used

• We replace the input potentiometer with a selectable voltage divider

Step 9: Table of the Voltage Divider

• The divisor calculation is simple but can be somewhat cumbersome.

• It is important to take into consideration the resistances presented by the switches (approximately 125 ohms).

• If the divider resistors are high enough, the resistors of the switches may be irrelevant in the splitter and may be ignored.

• Always opt for configurations where the resistances of the switches do not influence the circuit, thus removing a variable from your design.

The values presented in the table and used in the circuit were chosen to meet those of the demonstration. The parameters were the suitability of the maximum input signal to the amplifier within the range suggested by the manufacturer (+ -0.4Vpp), and the availability of resistors (commercial values).

Step 10: CD4066 Versus Digital Potentiometers

• For a more refined application, the applications of digital potentiometers become more interesting. These specialized devices far outweigh the approach presented here.

• Take the X9C family as an example:

• They are solid state potentiometers

• They use a three-wire serial interface (chip select, increment / decrement, and increment).

• They can be used as three-terminal potentiometers or variable resistors of two terminals.

• They have an internal array of 99 resistors.

• They can be found in values of 1k, 10k, 50k, and 100k ohms. (X9C102, X9C103, X9C503, and X9C104, respectively.)

• They can store a state in non-volatile internal memory.

• Our choice for this approach is simply didactic, since the only advantage would be the simple availability of the CD4066.

• Also, this demonstration can serve as a basis for other forms of control.

Step 11: Test Source Code of All Levels (no WiFi Connection)

Source code: #includes and #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

Source code: Constant and variable objects

//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;

Source Code: 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(); }

Source Code: Loop () - Switch (selection)

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; }

Source Code: Loop () - Adjusts the pins and increments

* Note the inversion when adjusting the pins. This inversion was placed only for convenience, so that a high bit represented the resistor participating in the divisor and not the other.

/*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 }

Source code: Loop () - Displays values after 5s of activation

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)); }

Source code: Initial screen displayed by 5s after activation

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 }

Step 12: Control Source Via Http

Source code: #includes and #defines

//Inclui biblioteca Wifi
#include <Wifi.h> //Bibliotecas para utilização do display OLED #include // 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

Source code: Objects, constants and variables

//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);

Source code: Setup () - outputs and initial message

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();

Source Code: Setup () - tries to connect ...

//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);

Source: Setup () - network settings

//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(); }

Source code: Loop () - Waiting for a client ...

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');

Source code: Loop () - Prepares the page html (part 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;" "}"

Source code: Loop () - Prepares the page html (part 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><a href='?acao=4'><button>4</button></a></p>"

"<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><a href='?acao=8'><button>8</button></a></p>"

"<a href='?acao=9'><button>9</button></a>"

"<a href='?acao=10'><button>10</button></a>" "<a href='?acao=11'><button>11</button></a>" "<p><a href='?acao=12'><button>12</button></a></p>"

"<a href='?acao=13'><button>13</button></a>"
"<a href='?acao=14'><button>14</button></a>" "<a href='?acao=15'><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();

Source Code: Loop () - Evaluates the request

//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; }

Source: Loop () - adjusts and terminates the connection

//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(); }

Source Code: Loop () - Waiting for client ...

Image

Step 13: Files

Download the files:

INO

PDF