sansui_circle.ino
/*
 * This is a repair of the "source selection" circuit of the Sansui AU-X517R audio amplifier,
 * using an Arduino Nano.
 * 
 * In the second approach, I am using a circle method, because the first approach failed in 
 * handling movement of the selector button more than one position.
 * Now, I mimic exactly the behaviour of the rotary switch, dividing the circle in 360 pieces
 * and perform what is expected from the switch in each piece.
 * 
 * The original circuit utilizes an ALPS (Japan) module, containing three sets of rotary switches.
 * Each set has a total of 12 pins, where one pin is used as a common and is connected
 * in a rotary fashion to each of the other 11 pins. 
 * All three sets are rotated together via a motor. The two sets are wired to 6 sources 
 * (PHONO, TUNER, CD, LINE, TAPE1, TAPE2) and each of these sets is associated with 
 * the Left and Right channel, respectively. 
 * 
 * The third set is wired to a resistor ladder. The output of the resistor ladder is used
 * by the original microcontroller to identify the position of the switch, via an ADC. 
 * Subsequently, the controller activates the motor to bring the switch to the desired location.
 * 
 * The intermediate pins of the audio switches (2, 4, 6, 8, 10, 12) are wired to audio ground. 
 * The intermediate pins of the resistor ladder (2, 4, 6, 8, 10, 12) are left unconnected.
 * 
 * Due to a broken plastic gear of the original circuit and since the module is no longer available
 * in the market (https://www.aliexpress.com/item/bella-Japanese-original-band-switch-ALPS-Alps-motor-shaft-length-30MM-6-speed-rotary-switch/1363271059.html), 
 * I will replace it with an Arduino board which mimics the behaviour of the original
 * circuit. In detail:
 * 
 * Two digital inputs are connected to the motor control circuit (LB1641). Their input table is:
 * 
 * IN1  IN2  Action
 * ------------------
 * 0  0  Brake
 * 1  0  Forward drive
 * 0  1  Reverse drive
 * 1  1  Brake
 * 
 * Based on the above instruction, the new circuit will cycle through a set of pins which provide a GND
 * to the external resistor ladder. When the desired position is reached as defined by the setting of the 
 * control to brake, the new position will have been identified and a relay will activate the audio input 
 * from the desired audio source.
 * 
 *  Please note that we do not use the analog voltage provided by the Selector button to control our movement.
 *  This happens because the system can be remotely operated via an infrared remote control.
 *  Therefore, we only use the position of the Selector for the initial startup and
 *  we use the motor control pins for any subsequent movement.
 *  
 */
 
// Digital output pins activating the relays
// D4, D5, D6, D7, D8 and D9
const int audioControlPins[] = {4, 5, 6, 7, 8, 9};
 
// Pins connected to the switch position control resistor ladder.
// The first pin is the first resistor divider and the output of the ladder. 
// We can even use the A/D converter of the Arduino
// to read the analog voltage of this pin.
const int resistorLadderPins[] = {A5, A4, A3, A2, A1, A0};
 
// Number of audio sources. This must be the number of
// elements in the above arrays
const int NUM_SOURCES = 6;
 
// The two pins which controlled the movement of the motor
// and are now becoming our control input.
// D10 going high is forward and D11 going high is reverse 
const int motorControlPins[] = {10, 11};
 
// Pin connected to the selector resistor ladder
const int selectorAnalogPin = A6;
 
// Variable to set the current index in the above arrays 
int currentSourceIndex;
// Variable to set the previous source index
int previousSourceIndex;
 
// From 0 to 359
int positionInCircle;
// One of the 6 circle segments. From 0 to 5
int circleSegment;
 
// The time required for the simulated motor to travel one of the 360 degrees
// in milliseconds
const int ROTATION_SPEED = 5;
 
// Digital output state that activates an audio relay
const bool AUDIO_ACTIVE = HIGH;
 
//------------------------------------------------------------------------------
void setup() {
 
  // As soon as possible...
  initAudioControlPins(); // It also disables all sources
 
  // Set the two control pins as Input.
  // The circuit uses a diode in each pin to identify a 0,
  // because the chip send 2V as HIGH. That is why we need the 
  // pull-up resistor, to keep the keep at 5V HIGH on idle. 
  pinMode(motorControlPins[0], INPUT_PULLUP);
  pinMode(motorControlPins[1], INPUT_PULLUP);
 
  // Sets all switch position control ladder pins initially as INPUT 
  // and simulate a high-Z state
  initResistorLadderPins();
 
  // The pin which we use for analog reading
  pinMode(selectorAnalogPin, INPUT);
 
  // We use debugging below, so it is time to activate the serial port
  Serial.begin(57600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB
  }
 
  // Read selector voltage to identify the current position of the switch
  // We will set this position as the current position of the switch
  currentSourceIndex = selectorPosition(); 
  // Simulate the switch position control ladder to be equal to the
  // selector control ladder
  setControlIndex(currentSourceIndex);
  // Let the internal microcontoller to run its own A/D
  // and understand the position of the switch.
  // This is absolutely essential. 
  // ** For some reason, without this delay, the next command did not activate the arduino pin.
  // ** Needs further investigation why this happens! How can the setting of an analog pin
  // inhibit the setting of a digital pin? Is the 2mA current too low and messes with the setting
  // of the analog pins?  ***
  // Must be higher than 15 msec. Set 200 to be safe
  // In the normal loop we do not face such problem, probably because the rotation that we 
  // implement takes a lot more time until the next source is activated.
  delay(200);
  enableSource(currentSourceIndex);
  // From our point of view, we are stable and previousSourceIndex points
  // to our current position. 
  previousSourceIndex = currentSourceIndex;   
 
 
  // The circle is divided in 6 zones, each of 60 degrees, from 0 to 59
  // From 0 to 7 we have the half of the unused contact (1 of the 12 of the rotary switch)
  // From 8 to 21, we have a gap
  // From 22 to 37 we have the valid contact
  // From 38 to 51 we have a gap
  // From 52 to 59 we have the half of the next unused contact 
  // We start considering that we are in the middle of the valid contact between 22 and 37
  positionInCircle = (currentSourceIndex * 60) + 29; 
  circleSegment = currentSourceIndex;  
}
 
//------------------------------------------------------------------------------
void loop() {
 
  bool motorMoving = false;
 
  bool input_1 = digitalRead(motorControlPins[0]);
  bool input_2 = digitalRead(motorControlPins[1]);
 
  if (input_1 == HIGH && input_2 == LOW) {
    motorMoving = true;
    disableAllSources();
    ++positionInCircle;
    if (positionInCircle >= 360) 
      positionInCircle = 0;
    circleSegment = positionInCircle / 60;
    delay(ROTATION_SPEED);
    //printPosition();
  }  
  else if (input_1 == LOW && input_2 == HIGH) {  
    motorMoving = true;
    disableAllSources();
    --positionInCircle;
    if (positionInCircle < 0) 
      positionInCircle = 359; 
    circleSegment = positionInCircle / 60;   
    delay(ROTATION_SPEED);
    //printPosition();
  } else {
     motorMoving = false;
     circleSegment = positionInCircle / 60;
     currentSourceIndex = circleSegment;
     if (previousSourceIndex != currentSourceIndex) {
       // enable only if we have a change in source
       enableSource(currentSourceIndex);
       previousSourceIndex = currentSourceIndex;
     }    
  }
 
  int indexOffset = circleSegment * 60;   
 
  if (positionInCircle >= (indexOffset + 0) && positionInCircle <= (indexOffset + 7)) {
    // unused contact
    setHighZ();
  } else if (positionInCircle >= (indexOffset + 8) && positionInCircle <= (indexOffset + 21)) {
    // gap
    setHighZ();
  } else if (positionInCircle >= (indexOffset + 22) && positionInCircle <= (indexOffset + 37)) {
    // valid contact
    setControlIndex(circleSegment);
  } else if (positionInCircle >= (indexOffset + 38) && positionInCircle <= (indexOffset + 51)) {
    // gap
    setHighZ();
  } else if (positionInCircle >= (indexOffset + 52) && positionInCircle <= (indexOffset + 59)) {
    // unused contact          
    setHighZ();
  } 
 
 
}
 
//------------------------------------------------------------------------------
// Initializes the audio control pins.
// Sets the pins as OUTPUT with initial state Inactive (= LOW)
void initAudioControlPins() {
 
  for (int index = 0; index < NUM_SOURCES; ++index) {
    // Note the notation NOT(AUDIO_ACTIVE)
    digitalWrite(audioControlPins[index], !AUDIO_ACTIVE);
    pinMode(audioControlPins[index], OUTPUT);   
  }
 
}
 
//------------------------------------------------------------------------------
// Enables an audio source by enabling the digital output
// which controls a relay. 
// The input is an index to the audioControlPins array
void enableSource(int index) {
 
  digitalWrite(audioControlPins[index], AUDIO_ACTIVE);
  Serial.print("Enabling source: ");
  Serial.print(index);
  Serial.print(", ");
  printSourceName(index);
  Serial.println();
 
}
 
//------------------------------------------------------------------------------
// Disables all sources. We need to make
// sure that our actions are not connecting two or more sources to each other
void disableAllSources() {
 
  for (int index = 0; index < NUM_SOURCES; ++index) {
    digitalWrite(audioControlPins[index], !AUDIO_ACTIVE);
  }  
 
}
 
//------------------------------------------------------------------------------
// Initialize all resistor ladder pins to high-Z state
void initResistorLadderPins() {
 
  for (int index = 0; index < NUM_SOURCES; ++index) {
    // Avoid the internal pullup resistor
    digitalWrite(resistorLadderPins[index], LOW);
    // Set as inputs to create a high-Z state
    pinMode(resistorLadderPins[index], INPUT);    
  } 
 
}
 
//------------------------------------------------------------------------------
// Set all resistor ladder pins to high-Z state
// Same as initResistorLadderPins, but without setting the port to LOW.
// It is set once so there is no need to spend time doing that over and over.
void setHighZ() {
 
  for (int index = 0; index < NUM_SOURCES; ++index) {
    // Set as inputs to create a high-Z state
    pinMode(resistorLadderPins[index], INPUT);    
  } 
 
}
 
//------------------------------------------------------------------------------
 
// Sets a specific resistor ladder pin to LOW
// and subsequently source current through it.
// This is achieved by setting it to output and write a 0 to it.
// Since the port register has already been initialized to 0
// we do not need to re-write the 0. Just set pin as output.
// This action simulates the position control of the rotary switch to be at 
// a specific location. The output of the switch control resistor ladder
// will provide a voltage depending on the index, as follows:
//
// 0V   TAPE2
// 0.8V TAPE1
// 1.5V LINE
// 2.3V CD
// 3V   TUNER
// 3.6V PHONO
//
// We can monitor this if we like by doing an analogRead(resistorLadderPins[0])
 
void setControlIndex(int index) {
 
    pinMode(resistorLadderPins[index], OUTPUT);    
 
}
 
 
//------------------------------------------------------------------------------
// Checks analog input of the Selector button and 
// the output of the resistor ladder.
// Used for testing
void checkAnalogPorts() {
 
    int analogInput = analogRead(resistorLadderPins[0]);
    float voltageIn = float (analogInput * 5)/1024;
    Serial.print ("Voltage input: ");
    Serial.print(voltageIn);
    Serial.print("V");
 
 
    int analogOutput = analogRead(selectorAnalogPin);
    float voltageOut = float (analogOutput * 5)/1024;
    Serial.print ("  Voltage output: ");
    Serial.print(voltageOut);
    Serial.println("V");
 
}
 
//------------------------------------------------------------------------------
// Reads the analog Selector pin to identify the current position 
// of the selector ressistor ladder.
// NOTE: This is a separate resistor ladder from the switch control ladder
//       and is used to signal the 47C440 controller about the position of
//       the selector button.
// The function is used only upon startup
int selectorPosition() {
 
    int index;
    int analogIn = analogRead(selectorAnalogPin);
    float voltage = (float) (analogIn * 5)/1024;
 
    Serial.print("Selector voltage: ");
    Serial.print(voltage);
    Serial.println("V");
 
    // Check the ladder voltage
    if (voltage <= 0.37) {
      index = 0; // 0V -> TAPE2
    } else if (voltage > 0.37 && voltage <= 1.07) {
      index = 1; // 0.8V -> TAPE1
    } else if (voltage > 1.07 && voltage <= 1.79) {
      index = 2; // 1.4V -> LINE
    } else if (voltage > 1.79 && voltage <= 2.52) {
      index = 3; // 2.2V -> CD
    } else if (voltage > 2.52 && voltage <= 3.19) {
      index = 4; // 2.9V -> TUNER
    } else {
      index = 5; // 3.5V -> PHONO
    }            
 
     Serial.print("Current Selector Position (0-5): ");
     Serial.print(index);
     Serial.print(", Source: ");
     printSourceName(index);
     Serial.println();
 
     return index;
}
 
//------------------------------------------------------------------------------
// Prints the name of the source input, based on the index
void printSourceName(int index) {
 
  switch (index) {
    case 0:
      Serial.print("TAPE2");
      break;
    case 1:
      Serial.print("TAPE1");
      break;
    case 2:
      Serial.print("LINE");
      break;
    case 3:
      Serial.print("CD");
      break;
    case 4:
      Serial.print("TUNER");
      break;
    case 5:
      Serial.print("PHONO");
      break;
    default:
      Serial.print("Unknown");  
 
  }
 
}
 
//------------------------------------------------------------------------------
// Prints the position in the circle
void printPosition() {
 
  Serial.print("Position in circle ");
  Serial.print(positionInCircle);
  Serial.print("  Index ");
  Serial.println(circleSegment);  
 
}