#17 Coding the Arduino(s)

T1_OFFH T1_ONH T1_N0 T1_N1 T1_N2 T1_N3 T1_N4 T1_N5 T1_N6 T1_N7 T1_N8 T1_N9 T2_OFFH T2_ONH T2_N0 T2_N1 T2_N2 T2_N3 T2_N4 T2_N5 T2_N6 T2_N7 T2_N8 T2_N9 R1_OPEN R1_CLOSE R2_OPEN R2_CLOSE

Yesterday, I successfully made my 9V DC circuit and ran some basic tests using old Arduino code. Today’s goal was more ambitious: writing the full production code to track the phone’s various states and prepare the data to be sent to the pi via the Serial Monitor.

I hit a brief moment of panic when the hook detection and dialing weren’t registering at all. I worried I did something wrong with the circuit, but the fix was actually quite simple. First, I realized I needed to use analog pins instead of digital ones to get a more granular reading of the voltage and spikes. Second, I had moved my measurement points from the positive to the negative side of the circuit, which inverted my logic. Now, when the phone is on-hook, the value reads near 0 instead of 900. After accounting for these hardware shifts in the code, everything worked perfectly.

Functional Requirements

The Arduino code is designed to handle three main tasks:

Hook Detection: Monitor if the handset is on or off the hook.

Pulse Dialing: Detect and count the pulses to determine which number was dialed.

Relay Control: Open and close the relays to manage the bell and the audio connection.

Communication Protocol

To communicate with the Raspberry Pi, I established a set of Serial codes. This allows the Pi to act as the “brain” while the Arduino handles the real-time hardware sensing.

Outgoing signals to the Pi:

TX_OFFH: Phone picked up.

TX_ONH: Phone hung up.

TX_N[0-9]: Number dialed (e.g., TX_N5 for digit 5).

note: X stands for the phone number. T1 is phone 1, T2 is phone 2.

Incoming commands from the Pi:

R1_OPEN / R1_CLOSE: Connect or disconnect the phone lines.

The code

The code uses a threshold-based system to distinguish between a temporary “break” (a pulse during dialing) and a long “break” (hanging up the phone). By measuring the time the voltage stays low—using a timeout of 600ms—the code can accurately determine if the user is finished dialing a digit or has ended the call entirely.

struct PhoneConfig {
  int pickupThreshold;
  int hangupThreshold;
  int timeout;
};

const PhoneConfig phoneConfigs[2] = {
    // pickupThreshold (High/Rising), hangupThreshold (Low/Falling), timeout(ms)
    {45, 20, 600}, // T1
    {60, 30, 600}  // T2
};

//--- STATE & VARIABLES
struct PhoneState {
  bool isOffHook;
  int pulseCount;
  bool inPulse; // currently in a pulse break
  unsigned long lastPulseTime;
  bool isActuallyHangingUp; // distinguish pulse vs hangup
};

PhoneState phoneStates[2] = {
    // isOffHook, pulseCount, inPulse, lastPulseTime, isActuallyHangingUp
    {false, 0, false, 0, false}, // T1
    {false, 0, false, 0, false}  // T2
};
void processLine(int value, int phoneID) {
  const PhoneConfig &config = phoneConfigs[phoneID];
  PhoneState &state = phoneStates[phoneID];

  // Off-Hook Detection
  if (!state.isOffHook && value > config.pickupThreshold) {
    state.isOffHook = true;
    state.pulseCount = 0;
    Serial.print("T");
    Serial.print(phoneID + 1);
    Serial.println("_OFFH");
  }

  // Dial & On-Hook Detection
  if (state.isOffHook) {
    // Detection of a pulse
    if (value < config.hangupThreshold && !state.inPulse) {
      state.inPulse = true;
      state.lastPulseTime = millis();
    }

    // Count the pulses
    else if (value > config.pickupThreshold && state.inPulse) {
      state.inPulse = false;
      state.pulseCount++;
      state.lastPulseTime = millis();
    }

    // Is the pulse a Digit or a Hang-up?
    if (millis() - state.lastPulseTime > config.timeout) {
      if (state.inPulse) // inPulse means the voltage is low -> doesn't go back
                         // up? => HANGUP
      {
        state.isOffHook = false;
        state.inPulse = false;
        state.pulseCount = 0;
        Serial.print("T");
        Serial.print(phoneID + 1);
        Serial.println("_ONH");
      } else if (state.pulseCount >
                 0) // Line stayed high & pulseCount > 0 -> DIGIT FINISHED
      {
        int digit = (state.pulseCount == 10) ? 0 : state.pulseCount;
        Serial.print("T");
        Serial.print(phoneID + 1);
        Serial.print("_N");
        Serial.println(digit);
        state.pulseCount = 0;
      }
    }
  }
}

So at the end of the day, I could manage the whole state of both phones. And all the serial communication was prepared to be sent to the Pi.

next post
previous post
Jana Elst

elst.jana@gmail.com

Ghent, Belgium