Arduino clock/gate modules for analog synths - Am I doing good?

Hi,

This project might be a little complicated for a beginner with no background but I want to stop blinking LEDs to do stuff I'd eventually use. I have gotten into modular synthesis recently and started to code a little clock/gate module. Everything's going fine so far but I wanted to post my code, In case there's something wrong in my approach that would force me to start again from zero at a later time...

The module is supposed to take an incoming clock signal that will be the reference for all events generated by the module. I chose to read that clock from digital pin 2 and use an interrupt, so that switch reading or whatever happening in the loop section would not cause delay/skip/glitch. I have no idea if that's useful but in the process I learnt about interrupts which was probably worth it anyway. :slight_smile: Right now I'm simulating the external clock using timer1 and I'm sending it from pin 13 to pin2 with the help of a pull-down resistor.
What the module then does with this clock depends on the mode selected by the user. All modes output 8 signals so far. Modes 0 to 3 are simple clock dividers with different settings (output 1 = incoming clock and outputs 2 to 8 are divisions of that clock). Mode 4 is a random gate generator that reads from a pot to determine the probability that the signal would toggle for each output.
I'm using 8 LEDs to display output activity and a 7 segment LED display, both driven by 2 chained shift registers. 3 switches allow the user to browse and select modes.

Works like this so far:

I will also need to protect the Arduino pins from two things:

  • voltage ranges that can vary from -15V to +15V
  • accidental connection of a clock to the output pins

I've asked that question on a DIY forum about modular synthesizers and I got this answer:

Electric Druid:
My current preferred solution is a simple transistor buffer. You wire up a basic NPN transistor (2N3904 or BC547 are classics). The modular input goes via a 10K resistor to the base. The emitter goes to ground. The collector is tied high to +5V by another 10K resistor. The Arduino input is taken from the collector. When a positive input arrives at the input, the transistor switches on, and drags the collector low - this is an inverting buffer, but that's easily dealt with in software. I've tested it with 30V inputs, and it's fine. That covers you for people connecting the input to the rail voltage in a +/-15V modular system, even if the reference was the other rail. Note that you don't need anything like this much voltage to trigger it - a simple +5V pulse is plenty.

For the minimal cost of a transistor and a couple of resistors, I'd protect all digital external inputs in this way. You can put whatever you like in, and you know what you're getting out - simple.

ElektroSteam:
This method is fundamentally flawed without a tiny modification: When the input goes negative, the transistor base/emitter diode is reverse biased. Most small signal transistors has a limit of about -3V to -5V for the base/emitter breakdown voltage, after which the victim will slowly start to suffer increasingly degraded performance.

The solution is to use a small signal diode, like an 1N4148, connected 'in reverse' across base and emitter (ground), diode cathode to NPN transistor base. This way the diode will start to conduct once the base voltage goes below -0.6V or so, which is perfectly safe for the transistor. So the input is protected against the full range of input voltages of +/- 10V or even more. The 10K base resistor limits the current to sensible values for both polarities.

Does this sound correct to everybody?

Here’s my code if you wanna check it:

// Uses adapted portions the following codes
//   timer interrupts
//   by Amanda Ghassaei
//   June 2012
//   http://www.instructables.com/id/Arduino-Timer-Interrupts/
//  
//   Arduino ShiftOut tutorial
//   http://www.arduino.cc/en/Tutorial/ShiftOut

boolean timerToggle = 0;
byte gatesOut = 0; // output values stores in a bytes to send to the shift register
int nbModes = 5;
//  pulse counters for each output
int clockCountExpo[] = {1, 2, 4, 8, 16, 32, 64, 128};
int clockCountLinear[] = {1, 2, 3, 4, 5, 6, 7, 8};
int clockCountClassic[] = {1, 2, 3, 4, 6, 8, 12, 16};
int clockCountOdd[] = {1, 3, 5, 7, 9, 11, 13, 15};
//  set default counter
volatile int counter[] = {0, 0, 0, 0, 0, 0, 0, 0};
int maxCount[] = {1, 2, 4, 8, 16, 32, 64, 128};

int switch1State;
int switch2State;
int switch3State;
int lastSwitch1State = LOW;
int lastSwitch2State = LOW;
int lastSwitch3State = LOW;
int debounceMillis = 80;
unsigned long debounceTimer1;
unsigned long debounceTimer2;
unsigned long debounceTimer3;
int mode = 0;
int tempMode = 0;
int digit = mode;
boolean selecting = false;
int digitBlinkingMillis = 250;
unsigned long digitBlinkingTimer;
unsigned long digitBlinkingLastBlink;
int selectionTime = 3000; //  millis
unsigned long selectionTimer;
int potValue;

//  7 segment
//  digits bytes  BAFG.CDE
byte digits[] = {B11100111,  /* 0 */
                 B10000100, // 132
                 B11010011,
                 B11010110,
                 B10110100,
                 B01110110,  /* 5 */
                 B01110111,
                 B11000100,
                 B11110111,
                 B11110110,
                 B11110101, // A
                 B00110111, // b
                 B01100011, // c
                 B10010111, // d
                 B01110011, // E
                 B01110001, // F
                 255,       // 16 - all LEDs ON
                 0};        // 17 - all LEDs OFF

// digital pins
int clockInPin = 2;
int clockOutPin = 13;
int shiftRegLatchPin = 8;
int shiftRegClockPin = 12;
int shiftRegDataPin = 11;
int modeLEDPin = 5;
int switch1Pin = 7;
int switch2Pin = 6;
int switch3Pin = 4;

// analog pins
int potPin = 0;

void setup(){
  //set pins modes
  pinMode(clockOutPin, OUTPUT);
  pinMode(clockInPin, INPUT);
  pinMode(shiftRegLatchPin, OUTPUT);
  pinMode(shiftRegClockPin, OUTPUT);
  pinMode(shiftRegDataPin, OUTPUT);
  pinMode(switch3Pin, INPUT);
  pinMode(modeLEDPin, OUTPUT);

  cli();//stop interrupts
  
  //set timer1 interrupt
  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  // set compare match register for desired increments
  OCR1A = 975;// = (16*10^6) / (s*1024) - 1 (must be <65536)
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set CS12 and CS10 bits for 1024 prescaler
  TCCR1B |= (1 << CS12) | (1 << CS10);  
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);
  
  attachInterrupt(0, act, CHANGE);

  sei();//allow interrupts
  
  // Debug
  //Serial.begin(9600);

}//end setup

ISR(TIMER1_COMPA_vect){//timer1 interrupt toggles pin 13
  if (timerToggle){
    digitalWrite(clockOutPin,HIGH);
    timerToggle = 0;
  }
  else{
    digitalWrite(clockOutPin,LOW);
    timerToggle = 1;
  }
}
  
void loop(){
  //  reading & debouncing switch 1
  int switchReading = digitalRead(switch1Pin);
  if (switchReading != lastSwitch1State) {
    debounceTimer1 = millis();
  }
  if (millis()-debounceTimer1 > debounceMillis) {
    if (switchReading != switch1State) {
      switch1State = switchReading;
      if (switch1State == HIGH) {
        tempMode--;
        if (tempMode < 0) tempMode = 0;
        //  engage blinking
        selecting = true;
        selectionTimer = millis();
        digit = tempMode;
      }
    }
  }
  lastSwitch1State = switchReading;
  
  //  reading & debouncing switch 2
  switchReading = digitalRead(switch2Pin);
  if (switchReading != lastSwitch2State) {
    debounceTimer2 = millis();
  }
  if (millis()-debounceTimer2 > debounceMillis) {
    if (switchReading != switch2State) {
      switch2State = switchReading;
      if (switch2State == HIGH) {
        tempMode++;
        if (tempMode >= nbModes) tempMode = nbModes - 1; // -1 because 0 indexed
        //  engage blinking
        selecting = true;
        selectionTimer = millis();
        digit = tempMode;
      }
    }
  }
  lastSwitch2State = switchReading;
  
  //  reading & debouncing switch 3
  switchReading = digitalRead(switch3Pin);
  if (switchReading != lastSwitch3State) {
    debounceTimer3 = millis();
  }
  if (millis()-debounceTimer3 > debounceMillis) {
    if (switchReading != switch3State) {
      switch3State = switchReading;
      if (switch3State == HIGH) {
        //  if entering new mode
        if (tempMode != mode) {
          //  set mode
          mode = tempMode;
          //  exit selection
          selecting = false;
          //  reset gates
          for (int i = 0; i < 8; i++) {
            counter[i] = 0;
          }
          gatesOut = 0;
          //  set new counters
          if (mode == 0)
            for (int i = 0; i < 8; i++)
              maxCount[i] = clockCountExpo[i];
          else if (mode == 1)
            for (int i = 0; i < 8; i++)
              maxCount[i] = clockCountLinear[i];
          else if (mode == 2)
            for (int i = 0; i < 8; i++)
              maxCount[i] = clockCountClassic[i];
          else if (mode == 3)
            for (int i = 0; i < 8; i++)
              maxCount[i] = clockCountOdd[i];
        }
        //  if selected mode is already running exit selection
        else selecting = false;
        //  reset digit whether mode is new or not
        digit = mode;
      }
    }
  }
  //digitalWrite(modeLEDPin, modeLEDState);
  lastSwitch3State = switchReading;
  
  //  blinking digit if necessary
  if (selecting) {
    if (millis() - digitBlinkingLastBlink > digitBlinkingMillis) {
      digitBlinkingLastBlink = millis();
      if (digit == 17)  //  17 in the table means all LEDs OFF
        digit = tempMode;
      else digit = 17;
    }
  }
  
  //  exit selection mode if timer elapsed
  if (millis() - selectionTimer > selectionTime && selecting == true) {
    tempMode = mode;
    digit = mode;
    selecting = false;
  }
  
  //  read pot
  potValue = analogRead(potPin);
}


void act() {
  switch(mode) {
    case 0:
    case 1:
    case 2:
    case 3:
      for (int i = 0; i < 8; i++) {
        counter[i]++;
        if (counter[i] >= maxCount[i]) {
          counter[i] = 0;
          shiftRegToggle(i);
        }
      }
      //Serial.println(gatesOut, BIN);
      break;
    //  random
    case 4:
      for (int i = 0; i < 8; i++) {
        if (random(potValue/128) == 0) shiftRegToggle(i);
      }
  }
}

// This method toggles and sends bits to the shift register:
void shiftRegToggle(int pin) {
  //  turn off the output so the pins don't light up
  digitalWrite(shiftRegLatchPin, LOW);
  // read bit state
  int currentState = bitRead(gatesOut, pin);
  // change bit state
  currentState = !currentState;
  // toggle the bit in gatesOut:
  bitWrite(gatesOut, pin, currentState);
  // shift the bits out:
  shiftOut(shiftRegDataPin, shiftRegClockPin, MSBFIRST, digits[digit]);
  shiftOut(shiftRegDataPin, shiftRegClockPin, MSBFIRST, gatesOut);
  // turn on the output so the LEDs can light up:
  digitalWrite(shiftRegLatchPin, HIGH);
}

Suggestions welcome!

Or is that too much asking? :blush:

The transistor/diode inverter circuit sounds OKish, but if the
incoming signal is slowly varying you may get multiple triggers.

Feed incoming signal via 10k resistor to input of a 74HC14 inverter,
add two schottky protection diodes from that input to 5V and GND to
prevent exceeding the 0..5V range. Now the output from the 74HC14
is a clean fast logic signal and the hysteresis in the '14 should prevent
multiple transitions.