Optocoupler-based volume control controlled with rotary encoder

I am working on building a 5 channel "passive preamp" ie volume control that will accept data from 5 push button rotary encoders to control the volume and mute of each channel. I've got code that successfully increments in steps of one between 0 and a set value. I have separate code working with the push button part of the rotary encoder that I would like to use to "mute" the volume of that channel.

//define variables for incrementing and instantiation logic.
int vol1 = 0;  //variable for storing a volume value until I flesh out the volume control portion of the electronics

char name1[ ] = "one "; //temporary, for differentiating channels during testing

//variables to keep track of buttonpress state
int mute1Store = 0;         //storage for volume when muted
int mute1Counter = 0;       //variable to count button presses
int mute1State = 0;         // current state of the button
int mute1Last = 0;          // previous state of the button

Rotary r = Rotary(2, 3);

void setup() {
  Serial.begin(9600);
  
  //initialize mutepin as input
  pinMode(mutePin1, INPUT);
}

void loop() {
//read mutePin1 state
  mute1State = digitalRead(mutePin1);
  
   //check for button press
  if(mute1State != mute1Last) {
   // if the state has changed, increment the counter
    if (mute1State == HIGH){
      // if the current state is HIGH(button was pressed), the counter does not indicate a muted state
      //and the volume is not 0 then increment the counter
      mute1Counter++;
     }
   }
  mute1Last = mute1State;

//check if mute button has been pressed, if it has been pressed 
//and the volume is not 0, store the volume and set the volume to 0
    if ((mute1Counter % 2 == 0) && (vol1 != 0) && (mute1Counter != 0)) {
      mute1Store = vol1;
      vol1 = 0;
      Serial.print(name1);
      Serial.println(vol1);
       } else if((mute1Counter % 2 != 0) && (mute1State != mute1Last)){
         vol1 = mute1Store;
         Serial.println(vol1); 
        }
       
  
  // code to increment counter based on rotary encoder input (CW or CCW) and constrain counter to 0 through 20
  unsigned char result = r.process();
  if (result == DIR_CCW){
    if(vol1 < 20){
      vol1 = vol1 + 1;
    }
    Serial.print(name1);
    Serial.println(vol1);
  }
   else if(result == DIR_CW) {
     if(vol1 > 0){
     vol1 = vol1 - 1;
      }
      Serial.print(name1);
    Serial.println(vol1);
    }
}


}

Currently the rotary encoder works, and pushing the button once sets vol1 to 0, but pushing it again does not set it back to the previous value. Any suggestions for that problem, or ways to do what I'm doing better would be greatly appreciated.

Bit of a convoluted way of doing it...

Personally I'd have a separate "muted" variable, and not manipulate the volume variable at all.

VolumeChanged := false

If button pressed
  Muted := not Muted
  VolumeChanged := true
End If

If turned clockwise
  Volume := Volume + 1 limit 100
  VolumeChanged := true
End If

If turned counterclockwise
  Volume := Volume - 1 limit 0
  VolumeChanged := true
End If

If VolumeChanged == true
  If Muted
    Program 0 volume into amp
  Else
    Program Volume Into Amp
  End If
End If

I think that makes good sense, thanks. I'll see what I can come up with.

It might be useful for people to know a little more about how the circuit works. A lot of people in the diy audio community are using silonex nsl-32 optocouplers in pairs for volume control, they function as a digital potentiometer. My circuit will consist of a DAC being driven over I2C by the arduino setting the levels of the optocouplers. The idea being that I can use the arduino to compensate for inconsistency and non-linearity in the optocouplers and avoid having to either buy lots of optocouplers and match them myself, or buy matched pairs (at 17.50 a pair that would be nearly $90 for all five pairs.) The testing and compensation routine was already worked out by someone else, so I'm not working totally from scratch, but I'm not using precisely the same devices and I'm trying to do 5 channels instead of two(stereo) so there's still a lot to figure out.

I figure that all the banging my head against the wall (and all the experience programming arduinos) will be worth that money. It also has the advantage of being able to account for drift in the devices due to age or temperature.

I've got the code working for one rotary encoder and momentary switch for muting pretty much the way majenko described. Now I need to poll all five encoders/switches. Obviously it would be pretty easy to kludge it together by just duplicating the code five times, but that's inelegant and poor coding practice. So, I think a multidimensional array like I have at the top of the code is probably the way I need to deal with it. But I'm not sure how to read (for example)just one column of each row. The array has the human friendly channel number 1-5 in the first column and then the remaining variables as described in the code that is commented out below the array. Is there a better way to deal with this? otherwise, how would I go about using an array in that manner?

Thanks,
Tom

#include <rotary.h>
#include <Bounce.h>

int ChannelMatrix[5][8] = {
  {1, 14, 0, 0, 0, 0, 0, 2},
  {2, 15, 0, 0, 0, 0, 0, 2},
  {3, 16, 0, 0, 0, 0, 0, 2},
  {4, 17, 0, 0, 0, 0, 0, 2},
  {5, 18, 0, 0, 0, 0, 0, 2}
};


/*
//channel 1 variables
int mutePin1 = 14;              //Input pin for mute Control
int mute1PinState = LOW;        // current state of the button
int mute1LedState = LOW;        //State of mute LED
boolean vol1Change = false;     //storage for volume when muted
boolean mute1Set = false;       //Mute State
int vol1 = 0;                   //Volume Value
int output1 = 2;                //mute switch machine value

//channel 2 variables
int mutePin2 = 15;              //Input pin for mute Control
int mute2PinState = 0;          // current state of the button
int mute2LedState = LOW;        //State of mute LED
boolean vol2Change = false;     //storage for volume when muted
boolean mute2Set = false;       //Mute State
int vol2 = 0;                   //Volume Value
int output2 = 2;                //mute switch machine value


//channel 3 variables
int mutePin3 = 16;              //Input pin for mute Control
int mute3PinState = 0;          // current state of the button
int mute3LedState = LOW;        //State of mute LED
boolean vol3Change = false;     //storage for volume when muted
boolean mute3Set = false;       //Mute State
int vol3 = 0;                   //Volume Value
int output3 = 2;                //mute switch machine value

//channel 4 variables
int mutePin4 = 17;              //Input pin for mute Control
int mute4PinState = 0;          // current state of the button
int mute4LedState = LOW;        //State of mute LED
boolean vol4Change = false;     //storage for volume when muted
boolean mute4Set = false;       //Mute State
int vol4 = 0;                   //Volume Value
int output4 = 2;                //mute switch machine value

//channel 5 variables
int mutePin5 = 18;              //Input pin for mute Control
int mute5PinState = 0;          // current state of the button
int mute5LedState = LOW;        //State of mute LED
boolean vol5Change = false;     //storage for volume when muted
boolean mute5Set = false;       //Mute State
int vol5 = 0;                   //Volume Value
int output5 = 2;                //mute switch machine value
*/

//debounce time in milliseconds
int debounce = 8;

//top of Volume scale
int volmax = 20;

// Instantiate Bounce objects for channel 1-5 with 5 millisecond debounce times
for(int c = 1; c < 5; c++){
  Bounce bounce(c) = Bounce(ChannelMatrix

Bounce bounce1 = Bounce( mutePin1,debounce );
Bounce bounce2 = Bounce( mutePin2,debounce );
Bounce bounce3 = Bounce( mutePin3,debounce );
Bounce bounce4 = Bounce( mutePin4,debounce );
Bounce bounce5 = Bounce( mutePin5,debounce );


//Instatiate Rotary objects for channel 1-5
Rotary r1 = Rotary(2, 3);
Rotary r2 = Rotary(4, 5);
Rotary r3 = Rotary(6, 7);
Rotary r4 = Rotary(8, 9);
Rotary r5 = Rotary(10, 11);



void setup() {
  Serial.begin(9600);
 
  //initialize mutepin as input
  pinMode(mutePin1, INPUT);
  pinMode(mutePin2, INPUT);
  pinMode(mutePin3, INPUT);
  pinMode(mutePin4, INPUT);
  pinMode(mutePin5, INPUT);
}

void loop() {
  
  //Read all 5 mute pins (one per channel)
  mute1PinState = digitalRead(mutePin1);
  mute2PinState = digitalRead(mutePin2);
  mute3PinState = digitalRead(mutePin3);
  mute4PinState = digitalRead(mutePin4);
  mute5PinState = digitalRead(mutePin5);
  
  //Set mute state for channel 1 based on button press
    if ( bounce1.update() ) {
     if ( bounce1.read() == HIGH) {
       if ( mute1Set == false ) {
         mute1Set = true;
         mute1LedState = HIGH;
          vol1Change = true;
       } else {
         mute1Set = false;
         vol1Change = true;
         }
      }
    }
    
 // code to increment volume counter based on rotary encoder input for Channel 1
  
  if(mute1Set == false){
    unsigned char result = r1.process();
    if (result == DIR_CCW){
      if(vol1 < volmax){
        vol1 = vol1 + 1;
        vol1Change = true;
      }
    }
     else if(result == DIR_CW) {
       if(vol1 > 0){
       vol1 = vol1 - 1;
       vol1Change = true;
        }
      }
    }
    
    //Switch Machine logic tree for controlling mute state and eventually passing volume parameter to the hardware for Channel 1
    
    if((vol1Change == true) && (mute1Set == true)){
      output1 = 0;
    }
   
    if((vol1Change == true) && (mute1Set == false)) {
       output1 = 1;  
    }
   
    if(vol1Change == false){
      output1 = 2;
    }
   
    switch (output1){
      case 0:
        Serial.println("muted");
        vol1Change = false;
      break;
     
      case 1:
        Serial.println(vol1);
        vol1Change = false;
       break;
      
       case 2:
       break;
    }
}

Multidimentional arrays, while they work in this situation, aren't the most ideal and readable way of doing it.

You should really be looking at a "struct" - a custom variable with may sub-variables that you can place in an array:

struct volumecontrol {
  int mutePin;
  int mutePinState;
  int muteLedState;
  boolean volChange;
  boolean muteSet;
  int vol;
  int output;
};

Then build your array as:

struct volumecontrol Channels[] = {
  {14, LOW, LOW, false, false, 0, 2},
  {15, LOW, LOW, false, false, 0, 2},
  {16, LOW, LOW, false, false, 0, 2},
  {17, LOW, LOW, false, false, 0, 2},
  {18, LOW, LOW, false, false, 0, 2}
};

You can then access each channel (0 to 4, not 1 to 5) with:

Channels[0..4]

And each sub-variable as:

Channels[0..4].subVariable

Such as, setting the muteLedState of channel 2 (that's array entry 2, so your channel 3) to high:

Channels[2].muteLedState = HIGH;

Or reading the mutePin value:

int mutePinValue = digitalRead(Channels[2].mutePin);

It instantly becomes much more readable, as you can see what variable it is you're working with, and not having to remember which array entry is which variable. You can also mix and match the struct member variable types, so you're not restricted to just one type. You could even have structs within structs if you wanted.

Thanks! Somehow I understand your explanation of structs a lot more than any of the other examples I've looked at.

It's probably fairly easy, but I can't figure out how to implement the struct for the debouncing. I've been trying to create an array bounce_, it seems like I should be able to cycle through i and create each instance of bounce for each channel of the "Channels*" array but the ide is saying that I can't declar a variable-sized array bounce._
here's a snippet
_
```*_
*void setup() {
  int bounce[5];
  int i;
 
  for(i = 0; i < 4; i++) {
    Bounce bounce[i] = Bounce( Channels[i].mutePin,debounce );
  }

//initialize each mutepin as input
  for(i = 0; i < 4; i++) {
    pinMode(Channels[i].mutePin, INPUT);
  }
 
  Serial.begin(9600);
}

}
_
```*_
do I need to use a different name for each var "i" that I'm using? I currently have 3 or 4 different for-loops, although I think it may be possible to reduce that number, but since my programming skills are poor enough that I rely heavily on guess and check I'm not looking at that yet.
As always, comments, suggestions, etc. are greatly appreciated.
Tom

This is useless.

for(i = 0; i < 4; i++) {
    Bounce bounce[i] = Bounce( Channels[i].mutePin,debounce );
  }

You are creating different sized arrays on each iteration of the loop. You are trying to assign one value to the whole array each time.

Worst of all, when the for loop ends, the arrays are all gone, too.

You need to define the array OUTSIDE of the loop, and in the loop, assign a value to ONE element of the array.

I thought this:

void setup() {
  int bounce[5];
  int i;

was me declaring the array outside of the loop. Is there more to it than that? Also, based on the reference for arrays
"To assign a value to an array:

mySensVals[0] = 10;
"
I thought the way I was assigning values to positions in the array was correct, but I'm also sure that it's more complicated because it's an array of functions... or objects? Anyway, is what I'm trying to do viable or should I just hard code each Bounce function? I'm not trying to be dense, I'm just still very much a neophyte when it comes to writing code.

I thought this...was me declaring the array outside of the loop.

Well, yes, but now the array is local to setup(). Declare it outside of any function to make it global. The type is also wrong.

I thought the way I was assigning values to positions in the array was correct

Assigning a value to an existing array, and declaring an array with initial values are two separate things. You are doing the latter when you mean to do he former.

but I'm also sure that it's more complicated because it's an array of functions

It's not an array of functions. It's an array of instances. You can put instances in an array. You can not put functions in an array. Function pointers, yes. Functions, no.

Anyway, is what I'm trying to do viable

Yes, when you separate the declaration of the array from the initialization of the array elements.

Bounce bouncyThings[5]; // Declare an array of Bounce instances.
bouncyThings[0] = Bounce( Channels[0].mutePin,debounce );

You can do this in a loop, to populate all 5 elements of the array.

Thank you for taking the time to clear that up for me; it really helped me understand how to make it work the way I wanted it to work.

I've got the whole thing working on a FOR loop controlling volume and mute functions.

Here's my code, and comments\suggestions\improvements are appreciated.

#include <rotary.h>
#include <Bounce.h>


//volume and mute control struct parameters
struct volumecontrol {
  int mutePin;
  int mutePinState;
  int muteLedState;
  boolean volChange;
  boolean muteSet;
  int vol;
  int output;
};

//Struct values for each channel to control mute and volume functions
struct volumecontrol Channels[] = {
  {14, LOW, LOW, false, false, 0, 2},
  {15, LOW, LOW, false, false, 0, 2},
  {16, LOW, LOW, false, false, 0, 2},
  {17, LOW, LOW, false, false, 0, 2},
  {18, LOW, LOW, false, false, 0, 2}
};

//debounce time in milliseconds
int debounce = 10;

//top of Volume scale
int volmax = 20;

// Instantiate Bounce objects for channel 1-5 with debounce time of "debounce" milliseconds
Bounce bounce[] = {//array of bounce objects for each mute button
  Bounce( Channels[0].mutePin,debounce ),
  Bounce( Channels[1].mutePin,debounce ),
  Bounce( Channels[2].mutePin,debounce ),
  Bounce( Channels[3].mutePin,debounce ),
  Bounce( Channels[4].mutePin,debounce )
};

//array of rotary objects for each volume control
Rotary volKnob[] = {
  Rotary(2, 3),
  Rotary(4, 5),
  Rotary(6, 7),
  Rotary(8, 9),
  Rotary(10, 11)
};

void setup() {
  int i;
  
  //initialize each mutepin as input
  for(i = 0; i < 4; i++) {
    pinMode(Channels[i].mutePin, INPUT);
  }
  
  Serial.begin(9600);
}

void loop() {
 
  //Read all mute pins (one per channel)
  int i;
  int mutePinState[5];
  
  
  //loop through all five channels for volume and mute controls
  for(i = 0; i < 4; i++) {
    mutePinState[i] = digitalRead(Channels[i].mutePin);

  
  //Set mute state for each channel based on button press
    if ( bounce[i].update() ) {
     if ( bounce[i].read() == HIGH) {
       if ( Channels[i].muteSet == false ) {
         Channels[i].muteSet = true;
         Channels[i].muteLedState = HIGH;
          Channels[i].volChange = true;
       } else {
         Channels[i].muteSet = false;
         Channels[i].volChange = true;
         }
      }
    }
  
  
 // code to increment volume counter based on rotary encoder input for each channel
  if(Channels[i].muteSet == false){
    unsigned char result = volKnob[i].process();
    if (result == DIR_CCW){
      if(Channels[i].vol < volmax){
        Channels[i].vol = Channels[i].vol + 1;
        Channels[i].volChange = true;
      }
    }
     else if(result == DIR_CW) {
       if(Channels[i].vol > 0){
       Channels[i].vol = Channels[i].vol - 1;
       Channels[i].volChange = true;
        }
      }
    }
    
    //Switch Machine for each channel
    if((Channels[i].volChange == true) && (Channels[i].muteSet == true)){
      Channels[i].output = 0;
    }
   
    if((Channels[i].volChange == true) && (Channels[i].muteSet == false)) {
       Channels[i].output = 1;  
    }
   
    if(Channels[i].volChange == false){
      Channels[i].output = 2;
    }
   
    switch (Channels[i].output){
      case 0:
        Serial.println("muted");
        Serial.println(Channels[i].mutePin);
        Channels[i].volChange = false;
      break;
     
      case 1:
        Serial.println(Channels[i].vol);
        Serial.println(Channels[i].mutePin);
        Channels[i].volChange = false;
       break;
      
       case 2:
       break;
    }
   }
  }

I've got the whole thing working on a FOR loop controlling volume and mute functions.

Excellent.

The only thing I would do is put each { on a new line and use Tools + Auto Format to make the code look neater.