Управление громкостью многозонного усилителя при помощи приложения для Android и Arduino

Прежде всего хочу поблагодарить 470 читателей проголосовавших за продолжение в статье про многозонный усилитель.Эксплуатация усилителя показала, что регулировать громкость не очень удобно находясь в помещении, которое может быть достаточно далеко находиться от кладовки где расположен усилитель с регуляторами громкости. Приходится несколько раз сбегать в кладовку и обратно добиваясь желаемого уровня громкости.

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

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

Это одноканальный потенциометр, следовательно, на 1 стерео усилитель потребуется 2 штуки. Существуют также сдвоенные версии из этой-же серии (MCP42XXX), но мне технологически было удобнее использовать 2 раздельных. Рассмотрим вкратце как он работает.

5165fd340f244f6f867b52fda89a710b.png

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

Доработка усилителя.Как я рассказывал в предыдущей статье, я выбрал самый дешевый из готовых усилителей за $2.7 и мне его было не жалко курочить ради эксперимента. Для начала удаляем (аккуратно выпаиваем) механический потенциометр как показано на картинке:

393487e84ce647c4894f2a53b3ad54ed.png

В освободившееся место будет установлен наш сдвоенный электронный регулятор.Сборка регулятора.Разрежем макетную плату вдоль, а потом еще поперек на 3 части как показано на картинке:

d45dc3ccc67d47d6969b8568f9cb3f26.png

Если пару раз провести острым ножом вдоль отверстий, то плата легко ломается руками как печенье. После этого нужно напильником слегка подравнять края. Из получившихся кусков нам понадобятся 2 маленьких, они имеют размер примерно 1.5×2 см. Выводы 2–4, 8 чипов соединяются параллельно, поэтому удобно собрать обе платы в виде сэндвича:

39f4b9747e5a462ba056e2549a30e595.png

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

Собранный регулятор после предварительного тестирования впаиваем в усилитель:

3f206e91485b45e394c8b75b5dc092ab.png

Как показало тестирование, добавление 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 CLKdpot high working = working << 1 ; // Bit-shift the working byte digitalWrite(CLKdpot,LOW) ; // Pulse the CLKdpot low } } Функция 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 #include 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] = {0×00,0×01,0×02,0×03,0×04,0×05}; 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 CLKdpot high working = working << 1 ; // Bit-shift the working byte digitalWrite(CLKdpot,LOW) ; // Pulse the CLKdpot low } }

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("»); client.println (»»); client.println (»»); 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, а также названия зон.

de3dd8b144ea4dc1bf61a7f81f819918.png

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

c7e0511b6e224ed79a857bad570d7362.png

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

d5a455cf919f4d5ea9cbbf8ec14f38f4.png

К примеру, при изменении позиции регулятора левого канала первой зоны она будет вызвана так:

e79b63ce07ad4923b6f919f279a7a375.png

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

6c6a6b921e064715a55471d0630dca1e.png

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

edf2874f3b2b43e4b53a41afc3343b95.png

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

В заключение, привожу видео демонстрирующее работу приложения. Задержка с переключением экрана связана с тем что приложение запущено в MIT AI2 Companion.

[embedded content]

© Habrahabr.ru