Multi-zone amplifier volume control with Android and Arduino app


    First of all, I want to thank 470 readers who voted for the continuation in the article about the multi-zone amplifier.

    The operation of the amplifier showed that it is not very convenient to adjust the volume while in a room that can be quite far from the storage room where the amplifier with volume controls is located. We have to run several times into the pantry and back to achieve the desired volume level.

    So the idea of ​​a regulator was born, which is always with you in your pocket, i.e. phone applications that can control the amplifier through a Wi-Fi network.

    To implement digital control of the amplifier volume level, the mechanical potentiometer will be replaced by an electronic one (DPOT - Digital Potentiometer). Among the not very large variety of available DPOTs, the MCP41050 with a nominal value of 50 kOhm was selected, which corresponds to the nominal value of the replaced mechanical analog.

    This is a single-channel potentiometer, therefore, 2 pieces are required per 1 stereo amplifier. There are also dual versions from the same series (MCP42XXX), but it was technologically more convenient for me to use 2 separate ones. Let us briefly consider how it works.



    The analog part is represented by pins 5-7, pin 6 (PW0) is the engine (Wiper) of the potentiometer. Management is via the SPI (Serial Peripheral Interface) (pins 1-3). 5V power is supplied to the Vss and Vdd pins. The programming of the chip consists in sending Command Byte and Data Byte sequentially setting the position of the potentiometer slider to position 0-255.

    Modification of the amplifier.
    As I said in a previous article, I chose the cheapest of the ready-made amplifiers for $ 2.7 and I was not sorry to screw it up for the sake of experiment. To begin with, we remove (carefully solder) the mechanical potentiometer as shown in the picture:



    Our dual electronic controller will be installed in the vacated space.
    Assembly of the regulator.
    We cut the breadboard along and then crosswise into 3 parts as shown in the picture:



    If you draw a sharp knife along the holes a couple of times, then the board easily breaks with your hands like cookies. After that you need to slightly trim the edges with a file. Of the resulting pieces, we need 2 small ones, they have a size of about 1.5 x 2 cm.
    The outputs of 2-4, 8 chips are connected in parallel, so it’s convenient to assemble both boards in the form of a sandwich:



    To connect the control circuit and power to the Arduino board, we use a piece of cable . At the same time, we place digital control lines away from analog circuits in order to avoid interference.

    After preliminary testing, the assembled regulator is soldered into the amplifier:



    As testing showed, the addition of a DPOT with digital control circuits to the input circuits of the amplifier did not lead to the appearance of noise or noise noticeable by ear.

    Schematic diagram



    Program for Arduino.
    The control method “SPI by Hand” (“SPI by Hand”) described here is little-scale.blogspot.it/2007/07/spi-by-hand.html . 2 functions are essential in it.
    The spi_transfer function bit bytes sends the byte to the chip.

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


    The spi_out function sends the bytes of the command and data to the chip that is selected by setting to logical 0 of the CS line.

    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
    }
    


    Since it was decided to implement the control over the local network, and not via Bluetooth, the Ethernet shield, the Web server in the standard connection, were involved in the scheme. Looking a little ahead, it should be noted that the program for the phone was created in the MIT App Inventor for which there is no TCP client implementation. Therefore, the control had to be done by sending commands in the parameters of the GET request.

    After selecting commands (param) and values ​​(value) from the query string, they are sent to control our DPOTs:

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


    Commands V1L, V1R - set the volume of the first left / right channel to the value value which can be equal to 0 - 255.
    Commands MU1, UM1 - Mute, Unmute. Temporary muting (initial level / 5) and returning the volume to its original value.

    Whole sketch

    #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] = {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("");
              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” app for Android.
    The application was created using the MIT App Inventor tool. The application has 2 screens: the main screen and the settings screen. The main screen includes 4 identical sections, one per zone. The settings screen contains controls for setting the URL to the corresponding Arduino IP address, as well as the names of the zones.



    A few details explaining the program.
    The settings and positions of the volume controls are saved in TinyDB and are used to initialize the application when it is opened. Example of saving the left channel level of the first zone when closing the application:



    As mentioned above, the WebViewer component is used to send commands using the Get method as part of a request to a web server running on Arduino.
    Sending commands as a frequently repeated operation is allocated to the SendCommand procedure.



    For example, when changing the position of the left channel control of the first zone, it will be called like this:



    In this case, a request of the form http://192.168.6.25/?V1L=156 will be sent.
    If the application is running on a smartphone, the sound can be automatically muffled when answering a call and restore when it ends:



    When you click on the “Mute” button, the Mute procedure is called, which in turn calls SendCommand and changes the color and name of the button: I



    will send the project file for App Inventor 2 to anyone who wishes.

    In conclusion, I give a video demonstrating the operation of the application. The delay with switching the screen is due to the fact that the application is running in the MIT AI2 Companion.


    Also popular now: