아두이노만으로 DTMF 디코더 구축하기 – 추가 IC 불필요
부품 및 소모품
| 아두이노 우노 (Arduino UNO) |
1개 |
| LED (범용) |
10개 |
| 브레드보드 (범용) |
1개 |
| 점퍼 와이어 (범용) |
20개 |
앱 및 온라인 서비스
프로젝트 소개
학기 프로젝트 목록을 검토하던 중 CM8870 IC를 DTMF 디코더로 사용하는 DTMF 제어 자동차 프로젝트를 발견했습니다. 디코더 기능이 핵심인데, 외부 IC를 통합하기가 까다로워 이를 코드나 라이브러리로 대체할 방법을 찾던 중 게르첼 알고리즘(Goertzel algorithm) 기반의 Goertzel.h 라이브러리를 찾았습니다. 이 라이브러리는 특정 주파수가 톤에 존재하는지 펄스 수와 진폭 기여도를 계산해 판별합니다.
기본 예제 코드를 바탕으로, 전화 통화 중 상대방이 누른 번호에 해당하는 DTMF 주파수를 감지하고 디코딩하는 코드를 작성했습니다.
코드
아두이노 DTMF 디코더 코드
아두이노 IDE에서 코드를 컴파일하고 업로드하세요. 라이브러리(Goertzel)를 먼저 추가하는 것을 잊지 마세요.
/* DTMF 디코더 기본 구현: 아날로그 핀 A0에서 16개 DTMF 문자를 감지하고,
DTMF 매트릭스의 상위/하위 톤을 확인해 해당 숫자를 디코딩합니다.
숫자 0-9는 해당 디지털 핀(2~12)을 HIGH로 설정하고,
특수 문자는 시리얼 모니터에 출력합니다.
Kevin Banks의 게르첼 알고리즘 구현(https://www.embedded.com/design/embedded/4024443/The-Goertzel-Algorithm)을 기반으로 하며,
위키피디아(https://en.wikipedia.org/wiki/Goertzel_algorithm)에도 상세 설명이 있습니다.
게르첼 알고리즘은 FFT 대안으로 단일 주파수만 탐색하므로 오버헤드가 적고 빠릅니다.
작성/수정: Mian Mohammad Shoaib (MMSHOAIB8452@GMAIL.COM) */
#include <Goertzel.h>
int sensorPin = A0;
const int N = 100; // 샘플 수 (민감도 조절 가능, 너무 크면 속도 저하)
const float threshold = 2000; // 톤 진폭 임계값 (민감도 조절용)
const float sampling_freq = 8900; // 샘플링 주파수 (아두이노 우노 16MHz 기준 최대 8900Hz)
float x_frequencies[4]; // 열(Column) 주파수
float y_frequencies[4]; // 행(Row) 주파수
void setup() {
pinMode(13, OUTPUT); // 톤 감지 표시용 LED
for (int i = 2; i <= 12; i++) pinMode(i, OUTPUT); // DTMF 출력 핀 (0-9)
Serial.begin(9600);
// DTMF 주파수 매트릭스 초기화
x_frequencies[0] = 1209; x_frequencies[1] = 1336;
x_frequencies[2] = 1477; x_frequencies[3] = 1633;
y_frequencies[0] = 697; y_frequencies[1] = 770;
y_frequencies[2] = 852; y_frequencies[3] = 941;
}
bool detect_tone(float freq) {
Goertzel goertzel = Goertzel(freq, N, sampling_freq);
goertzel.sample(sensorPin);
float magnitude = goertzel.detect();
if (magnitude > threshold) {
digitalWrite(13, HIGH);
delay(250);
digitalWrite(13, LOW);
Serial.println(freq);
return true;
}
return false;
}
void print_number(int row, int column) {
int number = 0;
// 행/열 조합으로 키패드 번호 매핑
if (row == 0) {
if (column == 0) number = 1;
else if (column == 1) number = 2;
else if (column == 2) number = 3;
else if (column == 3) number = 10; // A
} else if (row == 1) {
if (column == 0) number = 4;
else if (column == 1) number = 5;
else if (column == 2) number = 6;
else if (column == 3) number = 11; // B
} else if (row == 2) {
if (column == 0) number = 7;
else if (column == 1) number = 8;
else if (column == 2) number = 9;
else if (column == 3) number = 12; // C
} else if (row == 3) {
if (column == 0) number = 14; // *
else if (column == 1) number = 0;
else if (column == 2) number = 15; // #
else if (column == 3) number = 13; // D
}
if (number < 10) {
digitalWrite(number + 2, HIGH);
Serial.print(number);
} else if (number == 10) Serial.print('A');
else if (number == 11) Serial.print('B');
else if (number == 12) Serial.print('C');
else if (number == 13) Serial.print('D');
else if (number == 14) Serial.print('*');
else if (number == 15) Serial.print('#');
Serial.println();
delay(800);
for (int i = 2; i <= 12; i++) digitalWrite(i, LOW);
}
void loop() {
int column = 0, row = 0, i = 0;
// 열(Column) 주파수 탐지
while (1) {
if (detect_tone(x_frequencies[i])) { column = i; break; }
i++; if (i == 4) i = 0;
}
// 행(Row) 주파수 탐지
i = 0;
while (1) {
if (detect_tone(y_frequencies[i])) { row = i; break; }
i++; if (i == 4) i = 0;
}
print_number(row, column);
}
회로도
디지털 핀 2~12에 LED 10개를 연결해 숫자 0~9 입력 시 해당 LED가 켜지도록 하고, 핀 13에는 펄스 감지용 LED를 연결합니다. 특수 문자(*, #, A-D)는 시리얼 모니터에 출력됩니다.
오디오 잭 또는 AUX 케이블의 입력은 1uF 커패시터를 직렬로 연결해 아날로그 핀 A0에 연결합니다. 이 커패시터는 저주파와 DC 오프셋을 필터링합니다.
