Skip to content
pschatzmann edited this page Oct 29, 2026 · 1 revision

DTMF (dual tone multi frequency) is the touch tone signaling of telephones: each of the 16 keys 0-9, *, # and A-D is the sum of a row tone (697, 770, 852 or 941 Hz) and a column tone (1209, 1336, 1477 or 1633 Hz). It is not a data modem, but it is understood by telephones, telephone systems, radio repeaters and many cheap decoder chips.

Advantages

  • Compatible with telephones, radios (remote control of repeaters) and DTMF decoder chips.
  • Simple and robust: the receiver accepts frequency deviations of ±1.5% (like ITU-T Q.24) and ignores the echo at the end of a tone.
  • All tones are below 1700 Hz: they pass every telephone line and radio.

Disadvantages

  • Only 16 different characters: other characters are ignored by the transmitter.
  • Slow: about 7 keys per second with the default timing.
  • No error detection.

Usage

I2SStream i2s;
DTMFStream dtmf(i2s, RXTX_MODE);

void setup() {
  DTMFConfig cfg = dtmf.defaultConfig();
  cfg.sample_rate = 8000;
  ...
  dtmf.begin(cfg);
}

void loop() {
  dtmf.print("*123#");  // dial
  dtmf.flush();
  while (dtmf.available()) Serial.write(dtmf.read());  // received keys
}
echo -n "0123456789*#ABCD" | tinymodem --tx -f dtmf.wav dtmf
tinymodem --rx -f dtmf.wav dtmf

DTMFConfig, DTMFEncoder, DTMFDecoder, DTMFStream, DTMFStreamRTOS and DTMFKeys (the tones of the keys) are the DTMF classes. The encoder accepts the keys in upper or lower case. A , adds a pause of comma_ms, all other characters are ignored. writeKey() sends a single key. On the command line, -R, -v, -q, --rx-one and --Xrxnoise are supported.

DTMFConfig field Default
tone_ms 70 Transmitter: duration of a tone
pause_ms 70 Transmitter: pause after a tone
comma_ms 500 Transmitter: pause for a ,
volume 1.0 Transmit amplitude of the sum of both tones
min_amplitude 0.01 Receiver: minimum amplitude of each tone
max_twist_db 16 Receiver: max level difference of the row and the column tone
group_ratio_db 6 Receiver: a tone must be this much stronger than the other tones of its group
rx_timeout_ms 250 Receiver: a transmission ends after this silence
rx_one false Receiver: stop after the first transmission
channel 0 Receiver: input channel
sample_rate, channels, bits_per_sample 48000, 1, 16 Audio format

A small speaker attenuates the row tones and distorts: over the speaker and microphone of the ESP32-P4-WIFI6, max_twist_db = 26, group_ratio_db = 3 and a transmit volume of 0.4 were needed.

How it works

The transmitter sends the two tones with a fade in and out of 2 ms, to avoid clicks.

The receiver measures the 8 tones with Goertzel filters over windows of 25.6 ms (205 samples at 8000 Hz). Four windows overlap, so a new result is available every 6.4 ms. A key is detected when the strongest row and column tone are clearly stronger than the other tones of their group, have a similar level (twist) and contain most of the signal energy. A key is reported after 3 consecutive detections and released after 3 windows without it. The same key is only reported again if it is at least half as strong as before: this ignores the echo of a room at the end of a tone. The receiver needs tones and pauses of at least about 45 ms.

Measured performance

  • All 16 keys at 8000 to 48000 Hz, also with 45 ms tones and pauses, with ±1.5% frequency deviation and with 5% of full scale.
  • Noise (--Xrxnoise) up to 1.0 at 8000 Hz.
  • No keys are reported from noise or a single tone.
  • Over the air on the ESP32-P4 (speaker to the onboard microphone of the same board): all keys with the settings above.

Clone this wiki locally