controlling multiple solenoids with different (array of) timings

I'm helping a friend who's making a sculpture with singing mouths. The mouths are controlled with solenoids. I wrote him the following code to control the opening and closing of the mouths with two arrays that contain the timings. It works but because I'm using the 'delay' function, I can only control all the mouths (solenoids) at the same time.

// the setup function runs once when you press reset or power the board
int triggerTime[] = {
  700,400,300,400,250,300,760,230,850,200,300,250,200,300}; // length of delay in milliseconds 
int mouthPin1 = 8;
int openTime[] = {
  400,500,700,400,300,400,300,800,400,700,400,300,400,400}; // length of delay in milliseconds 


void setup() {
  Serial.begin(9600);
  pinMode(mouthPin1, OUTPUT);
}

void loop() {
for (int i = 0; i < (sizeof(triggerTime)/sizeof(int)) - 1; i++) {
  delay(triggerTime[i]);
  digitalWrite(mouthPin1, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(openTime[i]);              // wait for a second
  digitalWrite(mouthPin1, LOW);    // turn the LED off by making the voltage LOW
  }
// after running through the arrays, the program will wait between 5 and 15 seconds (randomly)
int CoolingTime = random (5000, 15000);
delay(CoolingTime);
}

To control multiple solenoids with different timings, I was looking at this multi-tasking / OOP solution on adafruit. Trying to adapt this solution, I wrote the following code (I'm not very experienced in functions and not sure if this is the way I can use them with an array)

class Singer
{
  // Class Member Variables
  // These are initialized at startup
  int mouthPin;      // the number of the mouth pin
  int OnTime[];     // array containing milliseconds of on-time
  int OffTime[];    // array containing milliseconds of off-time
  int i;             // index for array of on- and off-times
 
  // These maintain the current state
  int mouthState;                 // used to set the mouth
  unsigned long previousMillis;   // will store last time mouth was updated
 
  // Constructor - creates a Singer 
  // and initializes the member variables and state
  public:
  Singer(int pin, int on[], int off[])
  {
  mouthPin = pin;
  pinMode(mouthPin, OUTPUT);     
    
  OnTime[] = on[];
  OffTime[] = off[];
  
  mouthState = LOW; 
  previousMillis = 0;
  i=0;
  }
 
  void Update()
  {
    // check to see if it's time to change the state of the mouth
    unsigned long currentMillis = millis();
    if((mouthState == HIGH) && (currentMillis - previousMillis >= OnTime[i]))
    {
      mouthState = LOW;  // Turn it off
      previousMillis = currentMillis;  // Remember the time
      digitalWrite(mouthPin, mouthState);  // Update the actual mouth
      if(i = (sizeof(OnTime)/sizeof(int)) - 1) {
        i=0;}
      else {i++;}   
    }
    else if ((mouthState == LOW) && (currentMillis - previousMillis >= OffTime[i]))
    {
      mouthState = HIGH;  // turn it on
      previousMillis = currentMillis;   // Remember the time
      digitalWrite(mouthPin, mouthState);   // Update the actual mouth
    }
  }
};
 
int On1[] = {400,600};     // milliseconds of mouth1 on-time
int Off1[] = {600,400};    // milliseconds of mouth1 off-time
int On2[] = {3000,200};    // milliseconds of mouth2 on-time
int Off2[] = {700,500};    // milliseconds of mouth2 off-time
Singer mouth1(12, On1, Off1);
Singer mouth2(13, On2, Off2);
 
void setup()
{
  Serial.begin(9600);
}
 
void loop()
{
  mouth1.Update();
  mouth2.Update();
}

It doesn't work. I get the following error:

Arduino: 1.6.5 (Mac OS X), Board: "Arduino/Genuino Uno"

jabberjaw4_T_class.ino: In constructor 'Singer::Singer(int, int*, int*)':
jabberjaw4_T_class:22: error: expected primary-expression before ']' token
jabberjaw4_T_class:22: error: expected primary-expression before ']' token
jabberjaw4_T_class:23: error: expected primary-expression before ']' token
jabberjaw4_T_class:23: error: expected primary-expression before ']' token
expected primary-expression before ']' token

I suspect this has to do with using the array with the function. Could anyone point me in the right direction? Thanks.

I suspect this has to do with using the array with the function.

No, it has to do with trying to define an array without specifying the size. That won't work.

meneer:
I'm helping a friend who's making a sculpture with singing mouths. The mouths are controlled with solenoids. I wrote him the following code to control the opening and closing of the mouths with two arrays that contain the timings. It works but because I'm using the 'delay' function, I can only control all the mouths (solenoids) at the same time.

Setup a single ISR that runs at a particular rate (maybe 10 or 100 calls per second - depending on how fine of control you need).

Then, define a VOLATILE variable for each thing you want to time and place them inside the ISR. The ISR will decrement each one if it's not zero (like this):

volatile uint32_t mouth1, mouth2, mouth3, mouth4;
ISR (TIMER0_COMPA_vect)
{
    if (mouth1--); // decrement mouth1 if non-zero
    if (mouth2--); // ditto
    if (mouth3--); // ditto
    if (mouth4--); // ditto
}

Now, set each var to a different time, like this:

while (1) {
    noInterrupts(); // interrupts off for atomic test of var
    if (mouth1 == 0) { do mouth 1 action; mouth0 = 100; } // run action, then reload timer
    if (mouth2 == 0) { do mouth 2 action; mouth1 = 50;  } // ditto
    if (mouth3 == 0) { do mouth 3 action; mouth2 = 200; }
    if (mouth4 == 0) { do mouth 4 action; mouth3 = 500; }
    interrupts(); // interrupts back on
}

See what happens there? "mouth1" runs every 100 counts, regardless of the rest. "mouth2" runs every 50 counts, regardless of the rest, etc.... Note that the example is NOT the entire code... just the concept. You need to figure out how to setup the ISR (check out the Timer1 library at Arduino Playground - HomePage for code and ideas).

You need to turn off interrupts while testing each var for zero so that you do an atomic read. Unless you use an 8 bit variable, the processor actually decrements each 8 bits at a time and if you catch it in between updates, you can get "faked out" reading a zero when it's really not. Turning off interrupts prevents that problem.

Hope this helps.

I'd stick with working out the class, so later you can add different vocalist or even create a Chorus!

This does something... but still lots of problems to fix:

  1. Like humans, all the mouths should not work at exactly the same time. Maybe using a slightly different _startTime would be enough... have to look at that
    2: we may not want a song to loop
    3: we need todeternin if the song is done
    4: etc.

Take a look and see if it gets you closer to what you were hoping to do. I roughed this out, maybe I'll build something for the kids!

class Singer {
  public:
  Singer();
  ~Singer();
  void begin(int pin, int time);
  void setSong(int* song, size_t arraySize, unsigned long startMillis);
  void setSong(int* song, size_t arraySize);
  void sing();
  enum LoopState{
    LOOP_SONG,
    DO_NOT_LOOP
  };  
  
  private:
  int _pin;
  int _badTiming;
  bool _mouthOpen;
  unsigned long _startMillis;
  int* _currentSong;
  size_t _numIntervals;
  int _nextInterval;
  int _index;
  unsigned long _runTime = 0;
  LoopState _loopState;
};

enum LoopState{
  LOOP_SONG,
  DO_NOT_LOOP
};

Singer::Singer(){randomSeed(analogRead(A4));}
Singer::~Singer(){}

void Singer::begin(int pin, int time)
{
  _pin = pin;
  pinMode(_pin, OUTPUT);
  digitalWrite(_pin, LOW);
  _badTiming = time;
  _mouthOpen = false;
  _index = 0;
}

void Singer::setSong(int* song, size_t arraySize, unsigned long startMillis)
{
  _currentSong = song;
  _numIntervals = arraySize;
  _nextInterval = _currentSong[0];
  _startMillis = startMillis;
  _index++;
}

void Singer::setSong(int* song, size_t arraySize)
{
  this->setSong(song, arraySize, millis());
}

void Singer::sing()
{
  if(millis() - (_startMillis + _runTime) > _nextInterval)
  {
    _mouthOpen = !_mouthOpen;
    Serial.println(_mouthOpen? F("Mouth open") : F("Mouth Closed"));
    Serial.print("Segment ");
    Serial.print(_index + 1);
    Serial.print(" of ");
    Serial.println(_numIntervals);
    digitalWrite(_pin, _mouthOpen);
    _runTime += _nextInterval;
    _index++;
    if(_index >= _numIntervals) 
    {
      Serial.println("song looped");
      _index = 0;
      _startMillis = millis();
      _runTime = 0;
      _mouthOpen = false;
      digitalWrite(_pin, _mouthOpen);
    }
    _nextInterval = _currentSong[_index];
  }
}

int mySong[] = {1000, 250, 1000, 250, 1000, 3500, 1000, 230, 1000, 1000, 250, 1000, 250, 1000, 3500, 1000, 230, 1000, 1000, 250, 1000, 250, 1000, 3500, 1000, 230, 1000};

Singer baritone;
Singer soprano;

void setup() 
{
  Serial.begin(9600);
  soprano.begin(13, 250);  // set the pin and the max random timing that singer is "off timing"
  baritone.begin(12, 250);
  unsigned long justNow = millis();
  baritone.setSong(mySong, sizeof(mySong)/sizeof(mySong[0]), justNow);
  soprano.setSong(mySong, sizeof(mySong)/sizeof(mySong[0]), justNow);  // synch them to the same start time.
}

void loop() 
{
  soprano.sing();
  // baritone.sing(); // you will get double Serial Output if you make the baritone sing
  // put your main code here, to run repeatedly:

}

Apologies for the late response, I hadn't set my notifications right so only seeing this now. Many thanks for all your suggestions, will try these and report on results.