![](https://habrastorage.org/files/ed9/58b/4b5/ed958b4b518a4710a0cdf4bffce3d505.png)
まず、マルチゾーンアンプに関する記事の続きに投票した470人の読者に感謝します。
アンプの操作により、音量調節機能付きアンプが置かれている保管室からかなり離れた部屋にいる間、音量を調整することはあまり便利ではないことがわかりました。 目的の音量レベルを達成するには、パントリーに数回実行してから戻る必要があります。
それで、規制当局のアイデアが生まれました。それは常にあなたのポケットの中にあります。 Wi-Fiネットワークを介してアンプを制御できる電話用アプリケーション。
アンプの音量レベルのデジタル制御を実装するには、機械式ポテンショメータを電子式ポテンショメータに置き換えます(DPOT-デジタルポテンショメータ)。 それほど広くない種類の入手可能なDPOTの中から、公称値50 kOhmのMCP41050が選択されました。これは、交換された機械的アナログの公称値に対応します。
これはシングルチャンネルのポテンショメータであるため、ステレオアンプごとに2個必要です。 同じシリーズ(MCP42XXX)のデュアルバージョンもありますが、2つの個別のバージョンを使用する方が技術的に便利でした。 それがどのように機能するかを簡単に考えてみましょう。
![](https://habrastorage.org/files/516/5fd/340/5165fd340f244f6f867b52fda89a710b.png)
アナログ部分はピン5〜7で表され、ピン6(PW0)はポテンショメーターのエンジン(ワイパー)です。 管理はSPI(Serial Peripheral Interface)を介して行われます(結論1〜3)。 5Vの電力がVssおよびVddピンに供給されます。 チップのプログラミングは、ポテンショメータースライダーの位置を0〜255の位置に順次設定するコマンドバイトとデータバイトを送信することで構成されます。
アンプの改造。
前の記事で述べたように、私は2.7ドルで最も安価な既製のアンプを選択しましたが、実験のためにそれを台無しにするのは残念ではありませんでした。 最初に、図のように機械式ポテンショメーターを取り外します(はんだ付けします)。
![](https://habrastorage.org/files/393/487/e84/393487e84ce647c4894f2a53b3ad54ed.png)
空きスペースには、デュアル電子コントローラーが設置されます。
レギュレーターの組み立て。
写真に示すように、ブレッドボードを切り取り、次に幅方向に3つの部分に分けます。
![](https://habrastorage.org/files/d45/dc3/ccc/d45dc3ccc67d47d6969b8568f9cb3f26.png)
穴に沿って鋭いナイフを数回保持すると、クッキーのようにボードが手で簡単に壊れます。 その後、ファイルでエッジをわずかにトリミングする必要があります。 結果として生じるピースのうち、2つの小さなピースが必要です。それらのサイズは約1.5 x 2 cmです。
2-4、8チップの結論は並列に接続されているため、サンドイッチの形で両方のボードを組み立てるのが便利です。
![](https://habrastorage.org/files/39f/4b9/747/39f4b9747e5a462ba056e2549a30e595.png)
制御回路と電源をArduinoボードに接続するには、ループケーブルを使用します。 同時に、干渉を避けるために、デジタル制御線をアナログ回路から離して配置します。
予備テストの後、組み立てられたレギュレーターがアンプにはんだ付けされます。
![](https://habrastorage.org/files/3f2/06e/914/3f206e91485b45e394c8b75b5dc092ab.png)
テストが示したように、アンプの入力回路にデジタル制御回路を備えたDPOTを追加しても、可聴ノイズや干渉は発生しませんでした。
回路図
![](https://habrastorage.org/files/5f2/6ad/b28/5f26adb284dc4fb6b080378fd3ab015f.png)
Arduino用のプログラム。
ここで説明する制御方法「SPI by Hand」(「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関数は、コマンドとデータのバイトを、CSラインの論理0に設定することで選択されたチップに送信します。
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経由ではなくローカルネットワーク上で制御を実装することが決定されたため、標準接続のWebサーバーであるイーサネットシールドがこのスキームに関与していました。 少し先を見ると、電話用のプログラムは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-最初の左/右チャネルの音量レベルを対応する値に設定します。値は0〜255に等しくすることができます。
コマンドMU1、UM1-ミュート、ミュート解除。 一時的にミュートし(初期レベル/ 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 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("<!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); } } }
Android用の「ボリュームコントロール」アプリケーション。
アプリケーションは、MIT App Inventorツールを使用して作成されました。 アプリケーションには、メイン画面と設定画面の2つの画面があります。 メイン画面には、ゾーンごとに1つの4つの同一のセクションが含まれています。 設定画面には、ゾーン名だけでなく、対応するArduino IPアドレスへのURLを設定するためのコントロールが含まれています。
![](https://habrastorage.org/files/de3/dd8/b14/de3dd8b144ea4dc1bf61a7f81f819918.png)
プログラムを説明するいくつかの詳細。
ボリュームコントロールの設定と位置はTinyDBに保存され、アプリケーションを開いたときにアプリケーションを初期化するために使用されます。 アプリケーションを閉じるときに最初のゾーンの左チャンネルレベルを保存する例:
![](https://habrastorage.org/files/c7e/051/1b6/c7e0511b6e224ed79a857bad570d7362.png)
前述のとおり、WebViewerコンポーネントは、Arduinoで実行されているWebサーバーへの要求の一部としてGetメソッドを使用してコマンドを送信するために使用されます。
頻繁に繰り返される操作としてのコマンドの送信は、SendCommandプロシージャに割り当てられます。
![](https://habrastorage.org/files/d5a/455/cf9/d5a455cf919f4d5ea9cbbf8ec14f38f4.png)
たとえば、最初のゾーンの左チャンネルコントロールの位置を変更すると、次のように呼び出されます。
![](https://habrastorage.org/files/e79/b63/ce0/e79b63ce07ad4923b6f919f279a7a375.png)
http://192.168.6.25/?V1L=156という形式のリクエストが送信されます。
アプリケーションがスマートフォンで実行されている場合、呼び出しに応答するときに音声を自動的に消音し、終了時に復元できます。
![](https://habrastorage.org/files/6c6/a6b/921/6c6a6b921e064715a55471d0630dca1e.png)
[ミュート]ボタンをクリックすると、ミュートプロシージャが呼び出され、SendCommandが呼び出されてボタンの色と名前が変更されます。
![](https://habrastorage.org/files/edf/287/4f3/edf2874f3b2b43e4b53a41afc3343b95.png)
App Inventor 2のプロジェクトファイルは、リクエストに応じて希望する人に送信されます。
結論として、私はアプリケーションを示すビデオを持ってきます。 画面の切り替えに伴う遅延は、アプリケーションがMIT AI2 Companionで実行されているためです。