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:
Detect pin state change
Start debounce timer
Only accept change if pin remains stable for >50ms
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_HOOKbit in the phone state bitmaskLogs 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:
Configure GPIO 32 as input with pull-up
Read initial hook state
Initialize debounce state variables
Create monitoring FreeRTOS task
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):
Build and flash firmware
Monitor serial output
Short GPIO 32 to GND → Should log “Phone off-hook detected”
Release GPIO 32 → Should log “Phone on-hook detected”
Check
/statusendpoint →hookfield should reflect current state
With SLIC Connected:
Connect telephone handset to SLIC
Lift handset → Should log “Phone off-hook detected”
Replace handset → Should log “Phone on-hook detected”
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:
Ring Detection (GPIO 33):
Monitor SLIC RD pin for ring relay activation
Count ring cycles
Detect ring cadence patterns
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
Interrupt-Based Detection:
Replace polling with GPIO edge interrupts
Reduce CPU overhead
Improve response time
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- Implementationmain/hardware/slic_interface.h- Public API
Dependencies:
main/config/pin_assignments.h- GPIO pin definitionsmain/app/state/ma_bell_state.h- State management APIFreeRTOS - 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 documentationdocs/source/implementation/circuit/pin-assignments.rst- GPIO pin assignments