Управління гучністю багатозонного підсилювача за допомогою програми для Android і Arduino

Насамперед хочу подякувати 470 читачів, які проголосували за продовження статті про багатозонний підсилювач.

Експлуатація підсилювача показала, що регулювати гучність не дуже зручно перебуваючи у приміщенні, яке може перебувати досить далеко від комори, де розташований підсилювач з регулятором гучності. Доводиться кілька разів збігати в комору і назад домагаючись бажаного рівня гучності.

Так народилася ідея регулятора, який завжди з тобою в кишені, тобто програми для телефону яке може керувати підсилювачем через Wi-Fi мережу.

Для реалізації цифрового управління рівнем гучності підсилювача механічний потенціометр буде замінений електронним (DPOT – Digital Potentiometer). Серед не особливо великого розмаїття доступних DPOT був обраний MCP41050 номіналом в 50 кОм що відповідає номіналу заміщається механічного аналога.

Це одноканальний потенціометр, отже, на 1 стерео підсилювач потрібно 2 штуки. Існують також здвоєні версії з цієї ж серії (MCP42XXX), але мені технологічно було зручніше використовувати 2 роздільних. Розглянемо коротко як він працює.



Аналогова частина представлена висновками 5-7, висновок 6 (PW0) є двигуном (Wiper) потенціометра. Управління проводиться за допомогою SPI (Serial Peripheral Interface) (висновки 1-3). До висновків Vss і Vdd підводиться живлення 5V. Програмування чіпа полягає в послідовній посилці Command Byte і Data Byte встановлює позицію движка потенціометра в позицію 0-255.

Доробка підсилювача.
Як я розповідав у попередній статті, я вибрав найдешевший з готових підсилювачів за $2.7 і мені його було не шкода курочіть заради експерименту. Для початку видаляємо (акуратно випаюємо) механічний потенціометр як показано на картинці:



В звільнене місце буде встановлено наш здвоєний електронний регулятор.
Збірка регулятора.
Разрежем макетну плату вздовж, а потім поперек-на 3 частини як показано на картинці:



Якщо пару раз провести гострим ножем уздовж отворів, то плата легко ламається руками як печиво. Після цього потрібно напилком злегка підрівняти краю. З одержаних шматків нам знадобляться 2 маленьких, вони мають розмір приблизно 1.5 x 2 див.
Висновки 2-4, 8 чіпів з'єднуються паралельно, тому зручно зібрати обидві плати у вигляді сендвіча:



Для з'єднання керуючої ланцюга живлення з платою Arduino використовуємо шматок кабелю-шлейфа. Цифрові лінії управління при цьому маємо подалі від аналогових ланцюгів уникнути наведень.

Зібраний регулятор після попереднього тестування упаюємо в підсилювач:



Як показало тестування, додавання DPOT з цифровими ланцюгами управління у вхідні ланцюга підсилювача не призвело до появи помітних на слух шумів або наведень.

Програма для Arduino.
За основу взято метод керування SPI вручну" («SPI by Hand») описаний тут little-scale.blogspot.it/2007/07/spi-by-hand.html. У ньому істотні 2 функції.
Функція spi_transfer побітно пересилає байт в чіп.

void spi_transfer(byte working)
{
for(int i = 1; i < = 8; i++) // Set up a loop of 8 iterations (8 bits in a byte)
{ if (working > 127) { digitalWrite (POT_MOSI,HIGH) ; } // If the MSB is a 1 then set MOSI high
else { digitalWrite (POT_MOSI, LOW) ; } // If the MSB is a 0 then set MOSI low
digitalWrite (CLKdpot,HIGH) ; // Pulse the high CLKdpot
working = working << 1 ; // Bit-shift the working byte
digitalWrite(CLKdpot,LOW) ; // Pulse the low CLKdpot
}
}


Функція spi_out посилає байти команди і даних в чіп який обраний установкою в логічний 0 лінії CS.

void spi_out(int CS, byte cmd_byte, byte data_byte)
{
digitalWrite (CS, LOW); // Set the passed ChipSelect pin to low to start programming
spi_transfer(cmd_byte); // Send the passed COMMAND BYTE
delay(2);
spi_transfer(data_byte); // Send the passed DATA BYTE
delay(2);
digitalWrite(CS, HIGH); // Set the passed ChipSelect pin to high to end programming
}


Оскільки управління було вирішено реалізувати по локальній мережі, а не через Bluetooth, схему замішані Еthernet shield, Web server у стандартному включенні. Забігаючи трохи наперед треба зазначити, що програма для телефону створювалася в MIT App Inventor для якого не існує реалізації TCP клієнта. Тому управління довелося робити пересиланням команд в параметри запиту GET.

Після виділення команд (param) і значень (value) з рядка запиту вони надсилаються для управління нашими DPOT-ми:

param = readString.substring(6,9);
value = readString.substring(10,13).toInt();
if (param=="V1L") {V1L=value; spi_out(CS1, cmd_byte, V1L);}
if (param=="V1R") {V1R=value; spi_out(CS2, cmd_byte, V1R);}
if (param=="MU1") {spi_out(CS1, cmd_byte, V1L/5); spi_out(CS2, cmd_byte, V1R/5);}
if (param=="UM1") {spi_out(CS1, cmd_byte, V1L); spi_out(CS2, cmd_byte, V1R);}


Команди V1L, V1R – встановити рівень гучності першого лівого/правого каналу відповідним значенням value яке може бути рівним 0 – 255.
Команди MU1, UM1 – Mute, Unmute. Тимчасове вимкнення (вихідний рівень /5) і повернення гучності до вихідного значення.

Скетч цілком
#include <UIPEthernet.h>
#include < String.h>
int CS1 = 19; // Chip Select
int CS2 = 18;
int CS3 = 17;
int CS4 = 16;
int CS5 = 15;
int CS6 = 14;
int CS7 = 8;
int CS8 = 7;
int CLKdpot = 4; // Clock pin 4 arduino
int POT_MOSI = 5; // MOSI pin 5 arduino
byte cmd_byte = B00010011 ; // Command byte 'write' data to POT
uint8_t POTposition1 = 10; //initialize DPOT set initial position
uint8_t POTposition2 = 10;
uint8_t POTposition3 = 10; 
uint8_t POTposition4 = 10;
uint8_t POTposition5 = 10; 
uint8_t POTposition6 = 10;
uint8_t POTposition7 = 10; 
uint8_t POTposition8 = 10;
uint8_t mac[6] = {0x00,0x01,0x02,0x03,0x04,0x05};
uint8_t ip[4] = {192, 168, 6, 25}; // IP address for the webserver
uint16_t port = 80; // Use port 80 - the standard for HTTP
EthernetServer server(80);
String readString = String(100);
String param = String(3); 
int value = 0;
int V1L = 0;
int V1R = 0;
int V2L = 0;
int V2R = 0;
int V3L = 0;
int V3R = 0;
int V4L = 0;
int V4R = 0;

void spi_transfer(byte working)
{
for(int i = 1; i < = 8; i++) // Set up a loop of 8 iterations (8 bits in a byte)
{ if (working > 127) { digitalWrite (POT_MOSI,HIGH) ; } // If the MSB is a 1 then set MOSI high
else { digitalWrite (POT_MOSI, LOW) ; } // If the MSB is a 0 then set MOSI low
digitalWrite (CLKdpot,HIGH) ; // Pulse the high CLKdpot
working = working << 1 ; // Bit-shift the working byte
digitalWrite(CLKdpot,LOW) ; // Pulse the low CLKdpot
}
}

void spi_out(int CS, byte cmd_byte, byte data_byte)
{
digitalWrite (CS, LOW); // Set the passed ChipSelect pin to low to start programming
spi_transfer(cmd_byte); // Send the passed COMMAND BYTE
delay(2);
spi_transfer(data_byte); // Send the passed DATA BYTE
delay(2);
digitalWrite(CS, HIGH); // Set the passed ChipSelect pin to high to end programming
}

void setup() {
Serial.begin(9600);
pinMode (CS1, OUTPUT);
pinMode (CS2, OUTPUT);
pinMode (CS3, OUTPUT);
pinMode (CS4, OUTPUT);
pinMode (CS5, OUTPUT);
pinMode (CS6, OUTPUT);
pinMode (CS7, OUTPUT);
pinMode (CS8, OUTPUT);
pinMode (CLKdpot, OUTPUT);
pinMode (POT_MOSI, OUTPUT);
spi_out(CS1, cmd_byte, POTposition1);
spi_out(CS2, cmd_byte, POTposition2);
spi_out(CS3, cmd_byte, POTposition3);
spi_out(CS4, cmd_byte, POTposition4);
spi_out(CS5, cmd_byte, POTposition5);
spi_out(CS6, cmd_byte, POTposition6);
spi_out(CS7, cmd_byte, POTposition7);
spi_out(CS8, cmd_byte, POTposition8);

// start the Ethernet connection and the server:
Ethernet.begin(mac, ip);
server.begin();
Serial.print("server is at ");
Serial.println(Ethernet.localIP());
}

void loop() { // listen for incoming clients
readString="";
EthernetClient client = server.available();
if (client) {
Serial.println("new client");
// an http request ends with a blank line
boolean currentLineIsBlank = true;
while (client.connected()) {
if (client.available()) {
char c = client.read();
size_t pos = 0;
if (readString.length() < 16) {
//store characters to string
readString +=c;
} 
// if you've gotten to the end of the line (received a newline
// character) and the line is blank, the http request has ended,
// so you can send a reply
if (c == '\n' && currentLineIsBlank) {
// send a standard http response header
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("Connection: close");
client.println();
client.println("<!DOCTYPE HTML>");
client.println("<html>");
client.println("</html>");
break;
}
if (c == '\n') {
// you're starting a new line
currentLineIsBlank = true;
}
else if (c != '\r') {
// you've gotten a character on the current line
currentLineIsBlank = false;
}
}
}
// give the web browser time to receive the data
delay(1);
// close the connection:
client.stop();
Serial.println("client disconnected");
Serial.println(readString);
param = readString.substring(6,9);
value = readString.substring(10,13).toInt();
Serial.println(param);
Serial.println(value);

if (param=="V1L") {V1L=value; spi_out(CS1, cmd_byte, V1L);}
if (param=="V1R") {V1R=value; spi_out(CS2, cmd_byte, V1R);}
if (param=="V2L") {V2L=value; spi_out(CS3, cmd_byte, V2L);}
if (param=="V2R") {V2R=value; spi_out(CS4, cmd_byte, V2R);}
if (param=="V3L") {V3L=value; spi_out(CS5, cmd_byte, V3L);}
if (param=="V3R") {V3R=value; spi_out(CS6, cmd_byte, V3R);}
if (param=="V4L") {V4L=value; spi_out(CS7, cmd_byte, V4L);}
if (param=="V4R") {V4R=value; spi_out(CS8, cmd_byte, V4R);}

if (param=="MU1") {
spi_out(CS1, cmd_byte, V1L/5); spi_out(CS2, cmd_byte, V1R/5);
spi_out(CS3, cmd_byte, V2L/5); spi_out(CS4, cmd_byte, V2R/5);
spi_out(CS5, cmd_byte, V3L/5); spi_out(CS6, cmd_byte, V3R/5);
spi_out(CS7, cmd_byte, V4L/5); spi_out(CS8, cmd_byte, V4R/5);
}
if (param=="UM1") {
spi_out(CS1, cmd_byte, V1L); spi_out(CS2, cmd_byte, V1R);
spi_out(CS3, cmd_byte, V2L); spi_out(CS4, cmd_byte, V2R);
spi_out(CS5, cmd_byte, V3L); spi_out(CS6, cmd_byte, V3R);
spi_out(CS7, cmd_byte, V4L); spi_out(CS8, cmd_byte, V4R);
}
}
}



Додаток «Volume Control» для Android.
Додаток створено за допомогою інструменту MIT App Inventor. Додаток має 2 екрану: основний екран та екран установок. Основний екран включає 4 ідентичні секції, по одній на зону. Екран установок містить контроли для установки URL відповідного IP адресою Arduino, а також назви зон.



Трохи деталей, що пояснюють роботу програми.
Значення установок і позиції регуляторів гучності зберігаються в TinyDB і використовуються при ініціалізації додатка при його відкритті. Приклад збереження значення рівня лівого каналу першої зони при закритті додатка:



Як вже було сказано вище, використовується компонент WebViewer для посилки команд методом Get у складі запиту до веб сервера, запущеного на Arduino.
Посилка команд як часто повторювана операція виділена в процедуру SendCommand.



Наприклад, при зміні позиції регулятора лівого каналу першої зони вона буде викликана так:



При цьому буде посланий запит виду http://192.168.6.25/?V1L=156
Якщо програму запущено на смартфоні, то звук можна автоматично приглушити при відповіді на дзвінок і відновити при його закінченні:



При натисканні на кнопку «Mute» викликається процедура Mute яка в свою чергу викликає SendCommand і змінює колір і назву кнопки:



Файл проекту для App Inventor 2 бажаючим вишлю за запитом.

На закінчення, наводжу відео демонструє роботу додатка. Затримка з перемиканням екрану пов'язана з тим, що додаток запущено в MIT AI2 Companion.


Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.