SLIC Interface Monitoring

The SLIC interface module (main/hardware/slic_interface.c) provides real-time detection of telephone line events from the HC-5504B SLIC, including off-hook detection, ring status, and (future) dial pulse detection.

Overview

The module interfaces with the SLIC’s status output pins to monitor the physical telephone state and update the system state accordingly. This enables the ESP32 to respond to user actions on the telephone handset.

Monitored Signals:

  • Off-Hook Detection (GPIO 32) - Detects when the telephone handset is lifted

  • Ring Status (GPIO 33) - Monitors ring relay activation (future)

  • Dial Pulses (GPIO 34) - Detects rotary dial pulses (future)

Off-Hook Detection

The primary function of the phone hardware module is detecting when the telephone handset is lifted (off-hook) or replaced (on-hook).

Hardware Connection

HC-5504B SLIC                    ESP32-WROVER
┌────────────┐                  ┌─────────────┐
│            │                  │             │
│  Pin 13    ├──────────────────┤ GPIO 32     │
│  (SHD)     │   Active LOW     │ (Input)     │
│            │                  │             │
└────────────┘                  └─────────────┘
  • SLIC Pin 13 (SHD): Switch Hook Detect output

  • Signal Logic: Active LOW (0V = off-hook, 3.3V = on-hook)

  • ESP32 GPIO 32: Configured as input with internal pull-up resistor

GPIO Configuration

The off-hook detect pin is configured during slic_interface_init():

gpio_config_t io_conf = {
    .pin_bit_mask = (1ULL << PIN_OFF_HOOK_DETECT),  // GPIO 32
    .mode = GPIO_MODE_INPUT,
    .pull_up_en = GPIO_PULLUP_ENABLE,   // Pull-up for safe default
    .pull_down_en = GPIO_PULLDOWN_DISABLE,
    .intr_type = GPIO_INTR_DISABLE      // Polling-based detection
};

Why pull-up resistor? The internal pull-up ensures the pin reads HIGH (on-hook) when the SLIC is not connected, preventing false off-hook detection during development or when the SLIC circuit is unpowered.

Polling Implementation

The module uses a FreeRTOS task to poll the GPIO at regular intervals:

static void slic_monitor_task(void *arg)
{
    while (1) {
        // Read GPIO level
        int gpio_level = gpio_get_level(PIN_OFF_HOOK_DETECT);
        bool current_hook_state = (gpio_level == 1);  // true = on-hook

        // Debounce and detect state changes
        // ... (see debouncing section)

        vTaskDelay(pdMS_TO_TICKS(POLL_INTERVAL_MS));  // 20ms
    }
}

Polling Parameters:

  • Poll Interval: 20ms (50 Hz update rate)

  • Task Priority: 5 (moderate priority)

  • Stack Size: 2048 bytes

Alternative: Interrupt-Based Detection

The current implementation uses polling for simplicity. For more efficient CPU usage, the module could be enhanced to use GPIO interrupts (GPIO_INTR_ANYEDGE) to detect state changes only when they occur.

Debouncing

Mechanical telephone switches produce contact bounce, causing brief voltage fluctuations during state transitions. The module implements software debouncing to filter these transients:

#define HOOK_DEBOUNCE_MS 50

if (current_hook_state != last_hook_state) {
    TickType_t now = xTaskGetTickCount();
    TickType_t elapsed_ms = pdTICKS_TO_MS(now - last_hook_change_time);

    if (elapsed_ms > HOOK_DEBOUNCE_MS) {
        // State change confirmed - update last_hook_state
        last_hook_change_time = now;
        // ... update system state
    }
}

Debounce Logic:

  1. Detect pin state change

  2. Start debounce timer

  3. Only accept change if pin remains stable for >50ms

  4. Update state and log transition

This prevents spurious state changes from mechanical bounce or electrical noise.

State Updates

When a valid state change is detected, the module updates the global phone state:

if (current_hook_state) {
    // On-hook (handset replaced)
    ESP_LOGI(TAG, "Phone on-hook detected");
    ma_bell_state_update_phone_bits(0, PHONE_STATE_OFF_HOOK);
} else {
    // Off-hook (handset lifted)
    ESP_LOGI(TAG, "Phone off-hook detected");
    ma_bell_state_update_phone_bits(PHONE_STATE_OFF_HOOK, 0);
}

The ma_bell_state_update_phone_bits() function:

  • Sets or clears the PHONE_STATE_OFF_HOOK bit in the phone state bitmask

  • Logs the state change

  • Sends notifications to registered tasks (via FreeRTOS task notifications)

For details on the state management system, see State Management.

Initialization

The SLIC interface module is initialized during the hardware subsystem startup:

Call Hierarchy:

main()
  └─ hardware_init()          (main/hardware/hardware_init.c)
       └─ slic_interface_init()  (main/hardware/slic_interface.c)

Initialization Steps:

  1. Configure GPIO 32 as input with pull-up

  2. Read initial hook state

  3. Initialize debounce state variables

  4. Create monitoring FreeRTOS task

  5. Log initialization status

Error Handling:

If GPIO configuration or task creation fails, slic_interface_init() returns an error code and logs the failure. The main initialization routine handles this with ESP_ERROR_CHECK(), which will halt the system if SLIC interface initialization fails.

Diagnostic Output

The module provides detailed logging for troubleshooting:

Initialization Logs:

I (1234) slic_if: Initializing SLIC interface monitoring
I (1235) slic_if: GPIO 32 configured for off-hook detection
I (1236) slic_if: Initial hook state: on-hook
I (1237) slic_if: SLIC monitor task started
I (1238) slic_if: SLIC interface monitoring started successfully

Runtime Logs:

I (5432) slic_if: Phone off-hook detected
I (5434) ma_bell_state: Phone state changed: 0x00 -> 0x01

I (12345) slic_if: Phone on-hook detected
I (12346) ma_bell_state: Phone state changed: 0x01 -> 0x00

Web API Integration

The off-hook state is exposed through the web interface at /status:

{
  "phone": {
    "status": {
      "hook": "Off-hook",     // or "On-hook"
      "ringing": false,
      "ring_count": 0,
      "dialing": false,
      "last_digit": "None"
    }
  }
}

The hook field updates in real-time based on the monitored GPIO state.

Testing

Hardware Testing

Without SLIC (Development):

  1. Build and flash firmware

  2. Monitor serial output

  3. Short GPIO 32 to GND → Should log “Phone off-hook detected”

  4. Release GPIO 32 → Should log “Phone on-hook detected”

  5. Check /status endpoint → hook field should reflect current state

With SLIC Connected:

  1. Connect telephone handset to SLIC

  2. Lift handset → Should log “Phone off-hook detected”

  3. Replace handset → Should log “Phone on-hook detected”

  4. Verify no false triggers from line noise or ringing voltage

Software Testing

State Verification:

const ma_bell_state_t* state = ma_bell_state_get();

if (ma_bell_state_phone_bits_set(PHONE_STATE_OFF_HOOK)) {
    // Phone is currently off-hook
}

State Change Notifications:

Tasks can register to receive notifications when phone state changes:

// Register for phone state notifications
ma_bell_state_register_for_notifications(NOTIFY_PHONE_STATE_CHANGED);

// Wait for notification
uint32_t bits = ma_bell_state_wait_for_notification(
    NOTIFY_PHONE_STATE_CHANGED,
    5000  // 5 second timeout
);

Ring Control

The ESP32 controls the ringing cadence by driving the SLIC RC (Ring Command) pin. The SLIC does NOT generate the 90V AC ring signal—it only controls the relay that switches between normal battery feed and the external ring generator.

For hardware details on the ring generator circuit, see Ring Generator.

Hardware Connection

ESP32-WROVER                    HC-5504B SLIC
┌─────────────┐                ┌────────────┐
│             │                │            │
│  GPIO 13    ├────────────────┤ Pin 16     │
│  (Output)   │   Active LOW   │ (RC)       │
│             │                │            │
└─────────────┘                └────────────┘
  • ESP32 GPIO 13: Ring Command output

  • SLIC Pin 16 (RC): Ring Command input

  • Signal Logic: Active LOW (0V = ring, 3.3V = idle)

GPIO Configuration

The ring command pin is configured during initialization:

#define PIN_RING_COMMAND    GPIO_NUM_13

gpio_config_t io_conf = {
    .pin_bit_mask = (1ULL << PIN_RING_COMMAND),
    .mode = GPIO_MODE_OUTPUT,
    .pull_up_en = GPIO_PULLUP_DISABLE,
    .pull_down_en = GPIO_PULLDOWN_DISABLE,
    .intr_type = GPIO_INTR_DISABLE
};
gpio_config(&io_conf);
gpio_set_level(PIN_RING_COMMAND, 1);  // Start in idle state (HIGH)

Cadence Generation

Standard North American ringing uses a 2 seconds ON, 4 seconds OFF cadence. The firmware generates this pattern:

#define RING_ON_MS          2000    // 2 seconds
#define RING_OFF_MS         4000    // 4 seconds

void ring_telephone(void)
{
    int ring_count = 0;

    while (incoming_call_active && !phone_off_hook) {
        // Ring ON
        gpio_set_level(PIN_RING_COMMAND, 0);  // LOW = ring
        vTaskDelay(pdMS_TO_TICKS(RING_ON_MS));

        // Check for ring trip (off-hook)
        if (phone_off_hook) break;

        // Ring OFF
        gpio_set_level(PIN_RING_COMMAND, 1);  // HIGH = idle
        vTaskDelay(pdMS_TO_TICKS(RING_OFF_MS));

        ring_count++;
    }

    // Ensure ring is off
    gpio_set_level(PIN_RING_COMMAND, 1);
}

Cadence Parameters:

  • Ring ON: 2000ms (2 seconds)

  • Ring OFF: 4000ms (4 seconds)

  • Cycle: 6 seconds total

Ring Trip Detection

The SLIC automatically detects if the phone goes off-hook during ringing (ring trip). The firmware monitors GPIO 32 (SHD pin) to detect this condition and stop the ring sequence. When phone_off_hook becomes true, the ring loop exits and the ring command is deactivated.

This prevents the ringer from sounding after the user has answered the call.

Future Enhancements

The current implementation provides a foundation for expanded phone hardware monitoring:

Planned Additions:

  1. Ring Detection (GPIO 33):

    • Monitor SLIC RD pin for ring relay activation

    • Count ring cycles

    • Detect ring cadence patterns

  2. Pulse Dial Detection (GPIO 34):

    • Decode rotary dial pulses (10 PPS typical)

    • Accumulate digit values (1-9 pulses = 1-9, 10 pulses = 0)

    • Implement inter-digit timeout detection

  1. Interrupt-Based Detection:

    • Replace polling with GPIO edge interrupts

    • Reduce CPU overhead

    • Improve response time

  2. Event Publishing:

    • Publish hook state changes to event system

    • Enable decoupled event handling

    • Support multiple event subscribers

Module Files

Source Files:

  • main/hardware/slic_interface.c - Implementation

  • main/hardware/slic_interface.h - Public API

Dependencies:

  • main/config/pin_assignments.h - GPIO pin definitions

  • main/app/state/ma_bell_state.h - State management API

  • FreeRTOS - Task management and delays

  • ESP-IDF GPIO driver

Build Integration:

The module is included in the main component’s CMakeLists.txt:

SRCS "hardware/slic_interface.c"
     ...

References

  • State Management - State management system documentation

  • Firmware Architecture - Overall firmware architecture

  • docs/source/implementation/circuit/line-interface-hc5504b.rst - SLIC hardware documentation

  • docs/source/implementation/circuit/pin-assignments.rst - GPIO pin assignments