สารบัญ:

ตัวตรวจจับ DTMF: 4 ขั้นตอน
ตัวตรวจจับ DTMF: 4 ขั้นตอน

วีดีโอ: ตัวตรวจจับ DTMF: 4 ขั้นตอน

วีดีโอ: ตัวตรวจจับ DTMF: 4 ขั้นตอน
วีดีโอ: DTMF Remote 1CH 2024, พฤศจิกายน
Anonim
Image
Image

ภาพรวม

ฉันได้รับแรงบันดาลใจในการสร้างอุปกรณ์นี้จากการบ้านในหลักสูตรออนไลน์เกี่ยวกับการประมวลผลสัญญาณดิจิทัล นี่คือตัวถอดรหัส DTMF ที่ใช้งานกับ Arduino UNO โดยจะตรวจจับตัวเลขที่กดบนแป้นกดของโทรศัพท์ในโหมดโทนโดยเสียงที่สร้าง

ขั้นตอนที่ 1: ทำความเข้าใจอัลกอริทึม

รหัส
รหัส

ใน DTMF แต่ละสัญลักษณ์จะถูกเข้ารหัสด้วยความถี่สองความถี่ตามตารางในภาพ

อุปกรณ์จับอินพุตจากไมโครโฟนและคำนวณแอมพลิจูดแปดความถี่ สองความถี่ที่มีแอมพลิจูดสูงสุดให้แถวและคอลัมน์ของสัญลักษณ์ที่เข้ารหัส

การเก็บข้อมูล

เพื่อที่จะทำการวิเคราะห์สเปกตรัม ตัวอย่างควรถูกจับที่ความถี่ที่คาดการณ์ได้ เพื่อให้บรรลุสิ่งนี้ ฉันใช้โหมด ADC แบบรันฟรีพร้อมความแม่นยำสูงสุด (พรีสเกลเลอร์ 128) ซึ่งให้อัตราการสุ่มตัวอย่าง 9615Hz รหัสด้านล่างแสดงวิธีกำหนดค่า ADC ของ Arduino

เป็นโมฆะ initADC () {

// เริ่ม ADC; f = (16MHz/พรีสเกลเลอร์) / 13 รอบ/การแปลง ADMUX = 0; // Channel sel, right-adj, ใช้พิน AREF ADCSRA = _BV(ADEN) | // เปิดใช้งาน ADC _BV(ADSC) | // ADC start _BV(ADATE) | // ทริกเกอร์อัตโนมัติ _BV(ADIE) | // เปิดใช้งานการขัดจังหวะ _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // 128: 1 / 13 = 9615 เฮิร์ตซ์ ADCSRB = 0; // โหมดรันฟรี DIDR0 = _BV(0); // ปิดอินพุตดิจิตอลสำหรับพิน ADC TIMSK0 = 0; // Timer0 off } และตัวจัดการขัดจังหวะดูเหมือนว่า ISR (ADC_vect) { uint16_t sample = ADC;samples [samplePos++] = ตัวอย่าง - 400; ถ้า (samplePos >= N) { ADCSRA &= ~_BV (ADIE); // บัฟเฟอร์เต็ม ขัดจังหวะปิด } }

การวิเคราะห์สเปกตรัม

หลังจากรวบรวมตัวอย่าง ฉันจะคำนวณแอมพลิจูดของ 8 สัญลักษณ์การเข้ารหัสความถี่ ฉันไม่ต้องการใช้ FFT เต็มรูปแบบสำหรับสิ่งนี้ ดังนั้นฉันจึงใช้อัลกอริทึมของ Goertzel

เป็นโมฆะ goertzel (uint8_t * ตัวอย่าง, ลอย * สเปกตรัม) {

ลอย v_0, v_1, v_2; ลอยใหม่ im แอมป์; สำหรับ (uint8_t k = 0; k < IX_LEN; k++) { float c = pgm_read_float(&(cos_t[k])); float s = pgm_read_float(&(sin_t[k])); ลอย a = 2. * c; v_0 = v_1 = v_2 = 0; สำหรับ (uint16_t i = 0; i < N; i++) { v_0 = v_1; v_1 = v_2; v_2 = (ลอย)(ตัวอย่าง) + a * v_1 - v_0; } re = c * v_2 - v_1; im = s * v_2; amp = sqrt(re * re + im * im); สเปกตรัม[k] = แอมป์; } }

ขั้นตอนที่ 2: รหัส

ภาพด้านบนแสดงตัวอย่างการเข้ารหัสตัวเลข 3 ซึ่งแอมพลิจูดสูงสุดสอดคล้องกับความถี่ 697Hz และ 1477Hz

ร่างที่สมบูรณ์มีลักษณะดังนี้

/** * การเชื่อมต่อ: * [ไมค์กับ Arduino] * - ออก -> A0 * - Vcc -> 3.3V * - Gnd -> Gnd * - Arduino: AREF -> 3.3V * [แสดงผลไปยัง Arduino] * - Vcc - > 5V * - Gnd -> Gnd * - DIN -> D11 * - CLK -> D13 * - CS -> D9 */ #include #include

#รวม

#define CS_PIN 9

#define N 256

#define IX_LEN 8 #กำหนด THRESHOLD 20

LEDMatrixDriver lmd (1, CS_PIN);

ตัวอย่าง uint8_t[N];

ระเหย uint16_t samplePos = 0;

สเปกตรัมลอย[IX_LEN];

// ความถี่ [697.0, 770.0, 852.0, 941.0, 1209.0, 1336.0, 1477.0, 1633.0]

// คำนวณจาก 9615Hz 256 ตัวอย่าง const float cos_t[IX_LEN] PROGMEM = { 0.8932243011955153, 0.8700869911087115, 0.8448535652497071, 0.8032075314806449, 0.6895405447370669, 0.6343932841636456, 0.55557023673096023, 250.47199; const float sin_t [IX_LEN] PROGMEM = { 0.44961132965460654, 0.49289819222978404, 0.5349976198870972, 0.5956993044924334, 0.7242470829514669, 0.7730104533627369, 0.8314696123025451, 0.8819212643483549 };

โครงสร้าง typedef {

ตัวเลขถ่าน; ดัชนี uint8_t; } digit_t;

digit_t ตรวจพบ_digit;

ตารางอักขระ const [4][4] PROGMEM = {

{'1', '2', '3', 'A'}, {'4', '5', '6', 'B'}, {'7', '8', '9', ' C'}, {'*', '0', '#', 'D'} };

const uint8_t char_indexes [4][4] PROGMEM = {

{1, 2, 3, 10}, {4, 5, 6, 11}, {7, 8, 9, 12}, {15, 0, 14, 13} };

แบบอักษรไบต์[16][8] = {

{0x00, 0x38, 0x44, 0x4c, 0x54, 0x64, 0x44, 0x38}, // 0 {0x04, 0x0c, 0x14, 0x24, 0x04, 0x04, 0x04, 0x04}, // 1 {0x00, 0x30, 0x48, 0x04, 0x04, 0x38, 0x40, 0x7c}, // 2 {0x00, 0x38, 0x04, 0x04, 0x18, 0x04, 0x44, 0x38}, // 3 {0x00, 0x04, 0x0c, 0x14, 0x24, 0x7e, 0x04, 0x04 } // 4 {0x00, 0x7c, 0x40, 0x40, 0x78, 0x04, 0x04, 0x38}, // 5 {0x00, 0x38, 0x40, 0x40, 0x78, 0x44, 0x44, 0x38}, // 6 {0x00, 0x7c, 0x04, 0x04, 0x08, 0x08, 0x10, 0x10}, // 7 {0x00, 0x3c, 0x44, 0x44, 0x38, 0x44, 0x44, 0x78}, // 8 {0x00, 0x38, 0x44, 0x44, 0x3c, 0x04, 0x04, 0x78}, // 9 {0x00, 0x1c, 0x22, 0x42, 0x42, 0x7e, 0x42, 0x42}, // A {0x00, 0x78, 0x44, 0x44, 0x78, 0x44, 0x44, 0x7c}, / / B {0x00, 0x3c, 0x44, 0x40, 0x40, 0x40, 0x44, 0x7c}, // C {0x00, 0x7c, 0x42, 0x42, 0x42, 0x42, 0x44, 0x78}, // D {0x00, 0x0a, 0x7f, 0x14, 0x28, 0xfe, 0x50, 0x00}, // # {0x00, 0x10, 0x54, 0x38, 0x10, 0x38, 0x54, 0x10} // * };

เป็นโมฆะ initADC () {

// เริ่ม ADC; f = (16MHz/พรีสเกลเลอร์) / 13 รอบ/การแปลง ADMUX = 0; // Channel sel, right-adj, ใช้พิน AREF ADCSRA = _BV(ADEN) | // เปิดใช้งาน ADC _BV(ADSC) | // ADC start _BV(ADATE) | // ทริกเกอร์อัตโนมัติ _BV(ADIE) | // เปิดใช้งานการขัดจังหวะ _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // 128: 1 / 13 = 9615 เฮิร์ตซ์ ADCSRB = 0; // โหมดรันฟรี DIDR0 = _BV(0); // ปิดอินพุตดิจิตอลสำหรับพิน ADC TIMSK0 = 0; // ปิดตัวจับเวลา0 }

โมฆะ goertzel (uint8_t * ตัวอย่าง, ลอย * สเปกตรัม) {

ลอย v_0, v_1, v_2; ลอยใหม่ im แอมป์; สำหรับ (uint8_t k = 0; k < IX_LEN; k++) { float c = pgm_read_float(&(cos_t[k])); float s = pgm_read_float(&(sin_t[k])); ลอย a = 2. * c; v_0 = v_1 = v_2 = 0; สำหรับ (uint16_t i = 0; i < N; i++) { v_0 = v_1; v_1 = v_2; v_2 = (ลอย)(ตัวอย่าง) + a * v_1 - v_0; } re = c * v_2 - v_1; im = s * v_2; amp = sqrt(re * re + im * im); สเปกตรัม[k] = แอมป์; } }

float เฉลี่ย (float *a, uint16_t len) {

ผลลอย =.0; สำหรับ (uint16_t i = 0; i < len; i++) { ผลลัพธ์ += a; } ส่งคืนผลลัพธ์ / len; }

int8_t get_single_index_above_threshold (float *a, uint16_t len, เกณฑ์การลอย) {

ถ้า (เกณฑ์ < THRESHOLD) { กลับ -1; } int8_t ix = -1; สำหรับ (uint16_t i = 0; i เกณฑ์) { if (ix == -1) { ix = i; } อื่น ๆ { กลับ -1; } } } ส่งคืน ix; }

เป็นโมฆะ detect_digit (ลอย * สเปกตรัม) {

float avg_row = เฉลี่ย (สเปกตรัม 4); float avg_col = avg(&สเปกตรัม[4], 4); int8_t แถว = get_single_index_above_threshold (สเปกตรัม, 4, avg_row); int8_t col = get_single_index_above_threshold(&สเปกตรัม[4], 4, avg_col); if (แถว != -1 && col != -1 && avg_col > 200) { ตรวจพบ_digit.digit = pgm_read_byte(&(table[row][col])); ตรวจพบ_digit.index = pgm_read_byte(&(char_indexes[row][col])); } อื่น ๆ { ตรวจพบ_digit.digit = 0; } }

เป็นโมฆะ drawSprite (ไบต์ * สไปรท์) {

// หน้ากากใช้เพื่อรับบิตคอลัมน์จากมาสก์แถวสไปรท์ = B10000000; สำหรับ (int iy = 0; iy <8; iy++) { สำหรับ (int ix = 0; ix <8; ix++) { lmd.setPixel(7 - iy, ix, (bool) (sprite [iy] & mask));

// เลื่อนหน้ากากไปทางขวาหนึ่งพิกเซล

หน้ากาก = หน้ากาก >> 1; }

// รีเซ็ตคอลัมน์มาสก์

หน้ากาก = B10000000; } }

การตั้งค่าเป็นโมฆะ () {

cli(); initADC(); เส ();

Serial.begin(115200);

lmd.setEnabled(จริง); lmd.setIntensity(2); lmd.clear(); lmd.display();

ตรวจพบ_digit.digit = 0;

}

ยาวที่ไม่ได้ลงนาม z = 0;

วงเป็นโมฆะ () {

ในขณะที่(ADCSRA & _BV(ADIE)); // รอให้การสุ่มตัวอย่างเสียงเสร็จสิ้น goertzel (ตัวอย่าง, สเปกตรัม); detect_digit(สเปกตรัม);

ถ้า (detected_digit.digit != 0) {

drawSprite(ฟอนต์[detected_digit.index]); lmd.display(); } ถ้า (z % 5 == 0) { สำหรับ (int i = 0; i < IX_LEN; i++) { Serial.print(spectrum ); Serial.print("\t"); } Serial.println(); Serial.println((int)detected_digit.digit); } z++;

ตัวอย่างPos = 0;

ADCSRA |= _BV(ADIE); // หยุดการสุ่มตัวอย่างต่อ

}

ISR (ADC_vect) {

ตัวอย่าง uint16_t = ADC;

ตัวอย่าง[samplePos++] = ตัวอย่าง - 400;

ถ้า (samplePos >= N) { ADCSRA &= ~_BV (ADIE); // บัฟเฟอร์เต็ม ขัดจังหวะปิด } }

ขั้นตอนที่ 3: แผนผัง

แผนผัง
แผนผัง

ควรทำการเชื่อมต่อต่อไปนี้:

ไมค์ไปยัง Arduino

ออก -> A0

Vcc -> 3.3V Gnd -> Gnd

การเชื่อมต่อ AREF กับ 3.3V เป็นสิ่งสำคัญ

แสดงผลไปยัง Arduino

Vcc -> 5V

Gnd -> Gnd DIN -> D11 CLK -> D13 CS -> D9

ขั้นตอนที่ 4: บทสรุป

สิ่งที่สามารถปรับปรุงได้ที่นี่? ฉันใช้ตัวอย่าง N = 256 ที่อัตรา 9615Hz ซึ่งมีคลื่นความถี่รั่ว ถ้า N = 205 และอัตราคือ 8000Hz แสดงว่าความถี่ที่ต้องการจะตรงกับกริดการแยกส่วน สำหรับ ADC นั้นควรใช้ในโหมดโอเวอร์โฟลว์ตัวจับเวลา

แนะนำ: