Table des matières:
2025 Auteur: John Day | [email protected]. Dernière modifié: 2025-01-13 06:57
Aperçu
J'ai été inspiré pour construire cet appareil par un devoir à domicile sur le cours en ligne de traitement du signal numérique. Il s'agit d'un décodeur DTMF implémenté avec Arduino UNO, il détecte un chiffre pressé sur un clavier téléphonique en mode tonalité par le son qu'il produit.
Étape 1: Comprendre l'algorithme
En DTMF, chaque symbole est codé avec deux fréquences selon le tableau sur l'image.
L'appareil capture l'entrée du microphone et calcule les amplitudes de huit fréquences. Deux fréquences avec des amplitudes maximales donnent une ligne et une colonne du symbole codé.
L'acquisition des données
Afin d'effectuer une analyse de spectre, les échantillons doivent être capturés à une certaine fréquence prévisible. Pour y parvenir, j'ai utilisé le mode ADC à exécution libre avec une précision maximale (prescaler 128), il donne un taux d'échantillonnage de 9615 Hz. Le code ci-dessous montre comment configurer l'ADC d'Arduino.
void initADC() {
// Init ADC; f = (16MHz/prescaler) / 13 cycles/conversion ADMUX = 0; // Channel sel, right-adj, utiliser la broche AREF ADCSRA = _BV(ADEN) | // Activer l'ADC _BV(ADSC) | // Démarrage ADC _BV(ADATE) | // Déclenchement automatique _BV(ADIE) | // Activation d'interruption _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // 128:1 / 13 = 9615 Hz ADCSRB = 0; // Mode libre DIDR0 = _BV(0); // Désactiver l'entrée numérique pour la broche ADC TIMSK0 = 0; // Timer0 off } Et le gestionnaire d'interruption ressemble à ceci ISR(ADC_vect) { uint16_t sample = ADC;samples[samplePos++] = sample - 400; if(samplePos >= N) { ADCSRA &= ~_BV(ADIE); // Tampon plein, interruption désactivée } }
Analyse de spectre
Après avoir collecté des échantillons, je calcule les amplitudes de 8 fréquences encodant des symboles. Je n'ai pas besoin d'exécuter une FFT complète pour cela, j'ai donc utilisé l'algorithme de Goertzel.
void goertzel (uint8_t *échantillons, float *spectre) {
flottant v_0, v_1, v_2; flotteur re, im, amp; for (uint8_t k = 0; k < IX_LEN; k++) { float c = pgm_read_float(&(cos_t[k])); float s = pgm_read_float(&(sin_t[k])); flotteur a = 2. * c; v_0 = v_1 = v_2 = 0; pour (uint16_t i = 0; i < N; i++) { v_0 = v_1; v_1 = v_2; v_2 = (float)(samples) + a * v_1 - v_0; } re = c * v_2 - v_1; im = s * v_2; amp = sqrt(re * re + im * im); spectre[k] = amp; } }
Étape 2: le code
L'image ci-dessus montre l'exemple d'encodage du chiffre 3 où l'amplitude maximale correspond aux fréquences 697Hz et 1477Hz.
Le croquis complet se présente comme suit
/** * Connexions: * [Mic to Arduino] * - Out -> A0 * - Vcc -> 3.3V * - Gnd -> Gnd * - Arduino: AREF -> 3.3V * [Display to Arduino] * - Vcc - > 5V * - Gnd -> Gnd * - DIN -> D11 * - CLK -> D13 * - CS -> D9 */ #include #include
#comprendre
#définir CS_PIN 9
#définir N 256
#définir IX_LEN 8 #définir SEUIL 20
LEDMatrixDriver lmd(1, CS_PIN);
uint8_t échantillons[N];
volatile uint16_t samplePos = 0;
spectre flottant[IX_LEN];
// Fréquences [697.0, 770.0, 852.0, 941.0, 1209.0, 1336,0, 1477.0, 1633.0]
// Calculé pour 9615Hz 256 échantillons const float cos_t[IX_LEN] PROGMEM = { 0.8932243011955153, 0.8700869911087115, 0.8448535652497071, 0.8032075314806449, 0.6895405447370669, 0.6343932841636456, 0.555570233019606723, 0. const float sin_t[IX_LEN] PROGMEM = { 0.44961132965460654, 0.49289819222978404, 0.5349976198870972, 0.5956993044924334, 0.7242470829514669, 0.7730104533627369, 0.8314696123025451, 0.881921}2643483549
struct typedef {
chiffre de caractère; uint8_t index; } chiffre_t;
chiffre_t chiffre_détecté;
table de caractères 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} };
police d'octets[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} // * };
void initADC() {
// Init ADC; f = (16MHz/prescaler) / 13 cycles/conversion ADMUX = 0; // Channel sel, right-adj, utiliser la broche AREF ADCSRA = _BV(ADEN) | // Activer l'ADC _BV(ADSC) | // Démarrage ADC _BV(ADATE) | // Déclenchement automatique _BV(ADIE) | // Activation d'interruption _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // 128:1 / 13 = 9615 Hz ADCSRB = 0; // Mode libre DIDR0 = _BV(0); // Désactiver l'entrée numérique pour la broche ADC TIMSK0 = 0; // Minuterie 0 désactivée }
void goertzel (uint8_t *échantillons, float *spectre) {
flottant v_0, v_1, v_2; flotteur re, im, amp; for (uint8_t k = 0; k < IX_LEN; k++) { float c = pgm_read_float(&(cos_t[k])); float s = pgm_read_float(&(sin_t[k])); flotteur a = 2. * c; v_0 = v_1 = v_2 = 0; pour (uint16_t i = 0; i < N; i++) { v_0 = v_1; v_1 = v_2; v_2 = (float)(samples) + a * v_1 - v_0; } re = c * v_2 - v_1; im = s * v_2; amp = sqrt(re * re + im * im); spectre[k] = amp; } }
float avg(float *a, uint16_t len) {
résultat flottant =.0; for (uint16_t i = 0; i < len; i++) { result += a; } renvoie le résultat / len; }
int8_t get_single_index_above_threshold (float *a, uint16_t len, seuil flottant) {
if (seuil < SEUIL) { return -1; } int8_t ix = -1; pour (uint16_t i = 0; seuil i) { if (ix == -1) { ix = i; } else { return -1; } } } renvoie ix; }
void detect_digit(float *spectre) {
float avg_row = avg(spectre, 4); float col_moy = moy(&spectrum[4], 4); int8_t row = get_single_index_above_threshold (spectre, 4, avg_row); int8_t col = get_single_index_above_threshold(&spectrum[4], 4, avg_col); if (ligne != -1 && col != -1 && col_moy > 200) {digit_détecté.chiffre = pgm_read_byte(&(table[ligne][col])); detect_digit.index = pgm_read_byte(&(char_indexes[row][col])); } else { détecté_chiffre.chiffre = 0; } }
void drawSprite(byte* sprite) {
// Le masque est utilisé pour obtenir le bit de colonne à partir du sprite row byte mask = B10000000; for(int iy = 0; iy < 8; iy++) { for(int ix = 0; ix < 8; ix++) { lmd.setPixel(7 - iy, ix, (bool)(sprite[iy] & mask));
// décaler le masque d'un pixel vers la droite
masque = masque >> 1; }
// réinitialiser le masque de colonne
masque = B10000000; } }
void setup() {
cli(); initADC(); sei();
Serial.begin(115200);
lmd.setEnabled(true); lmd.setIntensity(2); lmd.clear(); lmd.display();
chiffre_détecté.chiffre = 0;
}
z long non signé = 0;
boucle vide() {
while(ADCSRA & _BV(ADIE)); // Attend la fin de l'échantillonnage audio goertzel(samples, spectrum); detect_digit(spectre);
if (chiffre_détecté.chiffre != 0) {
drawSprite(font[detected_digit.index]); lmd.display(); } if (z % 5 == 0) { for (int i = 0; i < IX_LEN; i++) { Serial.print(spectrum); Serial.print("\t"); } Serial.println(); Serial.println((int)detected_digit.digit); } z++;
ExemplePos = 0;
ADCSRA |= _BV(ADIE); // Reprise de l'interruption d'échantillonnage
}
ISR(ADC_vect) {
uint16_t échantillon = ADC;
échantillons[samplePos++] = échantillon - 400;
if(samplePos >= N) { ADCSRA &= ~_BV(ADIE); // Tampon plein, interruption désactivée } }
Étape 3: Schémas
Les connexions suivantes doivent être effectuées:
Micro vers Arduino
Sortie -> A0
Vcc -> 3.3V Gnd -> Gnd
Il est important de connecter AREF à 3.3V
Afficher sur Arduino
Vcc -> 5V
Gnd -> Gnd DIN -> D11 CLK -> D13 CS -> D9
Étape 4: Conclusion
Qu'est-ce qui pourrait être amélioré ici? J'ai utilisé N = 256 échantillons à une fréquence de 9615 Hz qui présente une certaine fuite spectrale. Si N = 205 et une fréquence de 8000 Hz, les fréquences souhaitées coïncident avec la grille de discrétisation. Pour cela, l'ADC doit être utilisé en mode débordement de la minuterie.