33 KiB
Καταγραφή ήχου - Wio Terminal
Σε αυτό το μέρος του μαθήματος, θα γράψετε κώδικα για να καταγράψετε ήχο στο Wio Terminal σας. Η καταγραφή ήχου θα ελέγχεται από ένα από τα κουμπιά στην κορυφή του Wio Terminal.
Προγραμματίστε τη συσκευή για καταγραφή ήχου
Μπορείτε να καταγράψετε ήχο από το μικρόφωνο χρησιμοποιώντας κώδικα C++. Το Wio Terminal διαθέτει μόνο 192KB RAM, που δεν είναι αρκετή για να καταγράψει περισσότερα από λίγα δευτερόλεπτα ήχου. Ωστόσο, διαθέτει 4MB μνήμης flash, η οποία μπορεί να χρησιμοποιηθεί για την αποθήκευση του καταγεγραμμένου ήχου.
Το ενσωματωμένο μικρόφωνο καταγράφει ένα αναλογικό σήμα, το οποίο μετατρέπεται σε ψηφιακό σήμα που μπορεί να χρησιμοποιήσει το Wio Terminal. Κατά την καταγραφή ήχου, τα δεδομένα πρέπει να καταγράφονται στον σωστό χρόνο - για παράδειγμα, για καταγραφή ήχου στα 16KHz, ο ήχος πρέπει να καταγράφεται ακριβώς 16.000 φορές το δευτερόλεπτο, με ίσα διαστήματα μεταξύ κάθε δείγματος. Αντί να χρησιμοποιήσετε τον κώδικά σας για αυτό, μπορείτε να χρησιμοποιήσετε τον ελεγκτή άμεσης πρόσβασης μνήμης (DMAC). Αυτό είναι ένα κύκλωμα που μπορεί να καταγράψει ένα σήμα από κάπου και να το γράψει στη μνήμη, χωρίς να διακόπτει τον κώδικα που εκτελείται στον επεξεργαστή.
✅ Διαβάστε περισσότερα για το DMA στη σελίδα άμεσης πρόσβασης μνήμης στη Wikipedia.
Ο DMAC μπορεί να καταγράψει ήχο από τον ADC σε σταθερά διαστήματα, όπως 16.000 φορές το δευτερόλεπτο για ήχο 16KHz. Μπορεί να γράψει αυτά τα δεδομένα σε έναν προ-κατανεμημένο buffer μνήμης, και όταν αυτός γεμίσει, να τον κάνει διαθέσιμο στον κώδικά σας για επεξεργασία. Η χρήση αυτής της μνήμης μπορεί να καθυστερήσει την καταγραφή ήχου, αλλά μπορείτε να ρυθμίσετε πολλαπλούς buffers. Ο DMAC γράφει στον buffer 1, και όταν αυτός γεμίσει, ειδοποιεί τον κώδικά σας να επεξεργαστεί τον buffer 1, ενώ ο DMAC γράφει στον buffer 2. Όταν ο buffer 2 γεμίσει, ειδοποιεί τον κώδικά σας και επιστρέφει στη γραφή στον buffer 1. Με αυτόν τον τρόπο, όσο επεξεργάζεστε κάθε buffer σε λιγότερο χρόνο από όσο χρειάζεται για να γεμίσει ένας, δεν θα χάσετε δεδομένα.
Μόλις καταγραφεί κάθε buffer, μπορεί να γραφτεί στη μνήμη flash. Η μνήμη flash πρέπει να γράφεται χρησιμοποιώντας καθορισμένες διευθύνσεις, προσδιορίζοντας πού να γράψετε και πόσο μεγάλο να γράψετε, παρόμοια με την ενημέρωση ενός πίνακα byte στη μνήμη. Η μνήμη flash έχει granularité, που σημαίνει ότι οι λειτουργίες διαγραφής και εγγραφής βασίζονται όχι μόνο στο να είναι σταθερού μεγέθους, αλλά και στην ευθυγράμμιση με αυτό το μέγεθος. Για παράδειγμα, αν η granularité είναι 4096 byte και ζητήσετε διαγραφή στη διεύθυνση 4200, μπορεί να διαγράψει όλα τα δεδομένα από τη διεύθυνση 4096 έως 8192. Αυτό σημαίνει ότι όταν γράφετε τα δεδομένα ήχου στη μνήμη flash, πρέπει να είναι σε κομμάτια του σωστού μεγέθους.
Εργασία - ρυθμίστε τη μνήμη flash
-
Δημιουργήστε ένα ολοκαίνουργιο έργο Wio Terminal χρησιμοποιώντας το PlatformIO. Ονομάστε αυτό το έργο
smart-timer
. Προσθέστε κώδικα στη συνάρτησηsetup
για να ρυθμίσετε τη σειριακή θύρα. -
Προσθέστε τις ακόλουθες εξαρτήσεις βιβλιοθήκης στο αρχείο
platformio.ini
για να παρέχετε πρόσβαση στη μνήμη flash:lib_deps = seeed-studio/Seeed Arduino FS @ 2.1.1 seeed-studio/Seeed Arduino SFUD @ 2.0.2
-
Ανοίξτε το αρχείο
main.cpp
και προσθέστε την ακόλουθη οδηγίαinclude
για τη βιβλιοθήκη μνήμης flash στην κορυφή του αρχείου:#include <sfud.h> #include <SPI.h>
🎓 Το SFUD σημαίνει Serial Flash Universal Driver και είναι μια βιβλιοθήκη σχεδιασμένη να λειτουργεί με όλα τα τσιπ μνήμης flash.
-
Στη συνάρτηση
setup
, προσθέστε τον ακόλουθο κώδικα για να ρυθμίσετε τη βιβλιοθήκη αποθήκευσης flash:while (!(sfud_init() == SFUD_SUCCESS)) ; sfud_qspi_fast_read_enable(sfud_get_device(SFUD_W25Q32_DEVICE_INDEX), 2);
Αυτό επαναλαμβάνεται μέχρι να αρχικοποιηθεί η βιβλιοθήκη SFUD και στη συνέχεια ενεργοποιεί τις γρήγορες αναγνώσεις. Η ενσωματωμένη μνήμη flash μπορεί να προσπελαστεί χρησιμοποιώντας ένα Queued Serial Peripheral Interface (QSPI), έναν τύπο ελεγκτή SPI που επιτρέπει συνεχή πρόσβαση μέσω μιας ουράς με ελάχιστη χρήση του επεξεργαστή. Αυτό την καθιστά ταχύτερη για ανάγνωση και εγγραφή στη μνήμη flash.
-
Δημιουργήστε ένα νέο αρχείο στον φάκελο
src
με όνομαflash_writer.h
. -
Προσθέστε τα ακόλουθα στην κορυφή αυτού του αρχείου:
#pragma once #include <Arduino.h> #include <sfud.h>
Αυτό περιλαμβάνει μερικά απαραίτητα αρχεία κεφαλίδας, συμπεριλαμβανομένου του αρχείου κεφαλίδας για τη βιβλιοθήκη SFUD για αλληλεπίδραση με τη μνήμη flash.
-
Ορίστε μια κλάση σε αυτό το νέο αρχείο κεφαλίδας με όνομα
FlashWriter
:class FlashWriter { public: private: };
-
Στην ενότητα
private
, προσθέστε τον ακόλουθο κώδικα:byte *_sfudBuffer; size_t _sfudBufferSize; size_t _sfudBufferPos; size_t _sfudBufferWritePos; const sfud_flash *_flash;
Αυτό ορίζει μερικά πεδία για τον buffer που θα χρησιμοποιηθεί για την αποθήκευση δεδομένων πριν από τη γραφή στη μνήμη flash. Υπάρχει ένας πίνακας byte,
_sfudBuffer
, για τη γραφή δεδομένων, και όταν αυτός γεμίσει, τα δεδομένα γράφονται στη μνήμη flash. Το πεδίο_sfudBufferPos
αποθηκεύει την τρέχουσα θέση για εγγραφή σε αυτόν τον buffer, και το_sfudBufferWritePos
αποθηκεύει τη θέση στη μνήμη flash για εγγραφή. Το_flash
είναι ένας δείκτης στη μνήμη flash για εγγραφή - ορισμένοι μικροελεγκτές διαθέτουν πολλαπλά τσιπ μνήμης flash. -
Προσθέστε την ακόλουθη μέθοδο στην ενότητα
public
για να αρχικοποιήσετε αυτήν την κλάση:void init() { _flash = sfud_get_device_table() + 0; _sfudBufferSize = _flash->chip.erase_gran; _sfudBuffer = new byte[_sfudBufferSize]; _sfudBufferPos = 0; _sfudBufferWritePos = 0; }
Αυτό ρυθμίζει τη μνήμη flash στο Wio Terminal για εγγραφή και ρυθμίζει τους buffers με βάση το μέγεθος του grain της μνήμης flash. Αυτό βρίσκεται σε μια μέθοδο
init
, αντί για έναν κατασκευαστή, καθώς πρέπει να κληθεί μετά τη ρύθμιση της μνήμης flash στη συνάρτησηsetup
. -
Προσθέστε τον ακόλουθο κώδικα στην ενότητα
public
:void writeSfudBuffer(byte b) { _sfudBuffer[_sfudBufferPos++] = b; if (_sfudBufferPos == _sfudBufferSize) { sfud_erase_write(_flash, _sfudBufferWritePos, _sfudBufferSize, _sfudBuffer); _sfudBufferWritePos += _sfudBufferSize; _sfudBufferPos = 0; } } void writeSfudBuffer(byte *b, size_t len) { for (size_t i = 0; i < len; ++i) { writeSfudBuffer(b[i]); } } void flushSfudBuffer() { if (_sfudBufferPos > 0) { sfud_erase_write(_flash, _sfudBufferWritePos, _sfudBufferSize, _sfudBuffer); _sfudBufferWritePos += _sfudBufferSize; _sfudBufferPos = 0; } }
Αυτός ο κώδικας ορίζει μεθόδους για τη γραφή byte στο σύστημα αποθήκευσης flash. Λειτουργεί γράφοντας σε έναν buffer στη μνήμη που έχει το σωστό μέγεθος για τη μνήμη flash, και όταν αυτός γεμίσει, γράφεται στη μνήμη flash, διαγράφοντας τυχόν υπάρχοντα δεδομένα σε αυτήν τη θέση. Υπάρχει επίσης μια μέθοδος
flushSfudBuffer
για τη γραφή ενός μη ολοκληρωμένου buffer, καθώς τα δεδομένα που καταγράφονται δεν θα είναι ακριβή πολλαπλάσια του μεγέθους του grain, οπότε το τελικό μέρος των δεδομένων πρέπει να γραφτεί.💁 Το τελικό μέρος των δεδομένων θα γράψει επιπλέον ανεπιθύμητα δεδομένα, αλλά αυτό είναι εντάξει καθώς θα διαβαστούν μόνο τα απαραίτητα δεδομένα.
Εργασία - ρυθμίστε την καταγραφή ήχου
-
Δημιουργήστε ένα νέο αρχείο στον φάκελο
src
με όνομαconfig.h
. -
Προσθέστε τα ακόλουθα στην κορυφή αυτού του αρχείου:
#pragma once #define RATE 16000 #define SAMPLE_LENGTH_SECONDS 4 #define SAMPLES RATE * SAMPLE_LENGTH_SECONDS #define BUFFER_SIZE (SAMPLES * 2) + 44 #define ADC_BUF_LEN 1600
Αυτός ο κώδικας ρυθμίζει μερικές σταθερές για την καταγραφή ήχου.
Σταθερά Τιμή Περιγραφή RATE 16000 Ο ρυθμός δειγματοληψίας για τον ήχο. 16.000 είναι 16KHz SAMPLE_LENGTH_SECONDS 4 Η διάρκεια του ήχου που θα καταγραφεί. Αυτό έχει οριστεί σε 4 δευτερόλεπτα. Για να καταγράψετε μεγαλύτερο ήχο, αυξήστε αυτήν την τιμή. SAMPLES 64000 Ο συνολικός αριθμός δειγμάτων ήχου που θα καταγραφούν. Ορίζεται ως ο ρυθμός δειγματοληψίας * ο αριθμός των δευτερολέπτων BUFFER_SIZE 128044 Το μέγεθος του buffer ήχου για καταγραφή. Ο ήχος θα καταγραφεί ως αρχείο WAV, το οποίο είναι 44 byte κεφαλίδας, και στη συνέχεια 128.000 byte δεδομένων ήχου (κάθε δείγμα είναι 2 byte) ADC_BUF_LEN 1600 Το μέγεθος των buffers που θα χρησιμοποιηθούν για την καταγραφή ήχου από τον DMAC 💁 Αν διαπιστώσετε ότι τα 4 δευτερόλεπτα είναι πολύ λίγα για να ζητήσετε έναν χρονοδιακόπτη, μπορείτε να αυξήσετε την τιμή
SAMPLE_LENGTH_SECONDS
, και όλες οι άλλες τιμές θα επαναϋπολογιστούν. -
Δημιουργήστε ένα νέο αρχείο στον φάκελο
src
με όνομαmic.h
. -
Προσθέστε τα ακόλουθα στην κορυφή αυτού του αρχείου:
#pragma once #include <Arduino.h> #include "config.h" #include "flash_writer.h"
Αυτό περιλαμβάνει μερικά απαραίτητα αρχεία κεφαλίδας, συμπεριλαμβανομένων των αρχείων
config.h
καιFlashWriter
. -
Προσθέστε τα ακόλουθα για να ορίσετε μια κλάση
Mic
που μπορεί να καταγράψει από το μικρόφωνο:class Mic { public: Mic() { _isRecording = false; _isRecordingReady = false; } void startRecording() { _isRecording = true; _isRecordingReady = false; } bool isRecording() { return _isRecording; } bool isRecordingReady() { return _isRecordingReady; } private: volatile bool _isRecording; volatile bool _isRecordingReady; FlashWriter _writer; }; Mic mic;
Αυτή η κλάση έχει προς το παρόν μόνο μερικά πεδία για να παρακολουθεί αν η καταγραφή έχει ξεκινήσει και αν μια καταγραφή είναι έτοιμη για χρήση. Όταν ο DMAC ρυθμιστεί, γράφει συνεχώς σε buffers μνήμης, οπότε η σημαία
_isRecording
καθορίζει αν αυτά πρέπει να επεξεργαστούν ή να αγνοηθούν. Η σημαία_isRecordingReady
θα οριστεί όταν καταγραφούν τα απαιτούμενα 4 δευτερόλεπτα ήχου. Το πεδίο_writer
χρησιμοποιείται για την αποθήκευση των δεδομένων ήχου στη μνήμη flash.Έπειτα δηλώνεται μια καθολική μεταβλητή για μια παρουσία της κλάσης
Mic
. -
Προσθέστε τον ακόλουθο κώδικα στην ενότητα
private
της κλάσηςMic
:typedef struct { uint16_t btctrl; uint16_t btcnt; uint32_t srcaddr; uint32_t dstaddr; uint32_t descaddr; } dmacdescriptor; // Globals - DMA and ADC volatile dmacdescriptor _wrb[DMAC_CH_NUM] __attribute__((aligned(16))); dmacdescriptor _descriptor_section[DMAC_CH_NUM] __attribute__((aligned(16))); dmacdescriptor _descriptor __attribute__((aligned(16))); void configureDmaAdc() { // Configure DMA to sample from ADC at a regular interval (triggered by timer/counter) DMAC->BASEADDR.reg = (uint32_t)_descriptor_section; // Specify the location of the descriptors DMAC->WRBADDR.reg = (uint32_t)_wrb; // Specify the location of the write back descriptors DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf); // Enable the DMAC peripheral DMAC->Channel[1].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(TC5_DMAC_ID_OVF) | // Set DMAC to trigger on TC5 timer overflow DMAC_CHCTRLA_TRIGACT_BURST; // DMAC burst transfer _descriptor.descaddr = (uint32_t)&_descriptor_section[1]; // Set up a circular descriptor _descriptor.srcaddr = (uint32_t)&ADC1->RESULT.reg; // Take the result from the ADC0 RESULT register _descriptor.dstaddr = (uint32_t)_adc_buf_0 + sizeof(uint16_t) * ADC_BUF_LEN; // Place it in the adc_buf_0 array _descriptor.btcnt = ADC_BUF_LEN; // Beat count _descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD | // Beat size is HWORD (16-bits) DMAC_BTCTRL_DSTINC | // Increment the destination address DMAC_BTCTRL_VALID | // Descriptor is valid DMAC_BTCTRL_BLOCKACT_SUSPEND; // Suspend DMAC channel 0 after block transfer memcpy(&_descriptor_section[0], &_descriptor, sizeof(_descriptor)); // Copy the descriptor to the descriptor section _descriptor.descaddr = (uint32_t)&_descriptor_section[0]; // Set up a circular descriptor _descriptor.srcaddr = (uint32_t)&ADC1->RESULT.reg; // Take the result from the ADC0 RESULT register _descriptor.dstaddr = (uint32_t)_adc_buf_1 + sizeof(uint16_t) * ADC_BUF_LEN; // Place it in the adc_buf_1 array _descriptor.btcnt = ADC_BUF_LEN; // Beat count _descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD | // Beat size is HWORD (16-bits) DMAC_BTCTRL_DSTINC | // Increment the destination address DMAC_BTCTRL_VALID | // Descriptor is valid DMAC_BTCTRL_BLOCKACT_SUSPEND; // Suspend DMAC channel 0 after block transfer memcpy(&_descriptor_section[1], &_descriptor, sizeof(_descriptor)); // Copy the descriptor to the descriptor section // Configure NVIC NVIC_SetPriority(DMAC_1_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for DMAC1 to 0 (highest) NVIC_EnableIRQ(DMAC_1_IRQn); // Connect DMAC1 to Nested Vector Interrupt Controller (NVIC) // Activate the suspend (SUSP) interrupt on DMAC channel 1 DMAC->Channel[1].CHINTENSET.reg = DMAC_CHINTENSET_SUSP; // Configure ADC ADC1->INPUTCTRL.bit.MUXPOS = ADC_INPUTCTRL_MUXPOS_AIN12_Val; // Set the analog input to ADC0/AIN2 (PB08 - A4 on Metro M4) while (ADC1->SYNCBUSY.bit.INPUTCTRL) ; // Wait for synchronization ADC1->SAMPCTRL.bit.SAMPLEN = 0x00; // Set max Sampling Time Length to half divided ADC clock pulse (2.66us) while (ADC1->SYNCBUSY.bit.SAMPCTRL) ; // Wait for synchronization ADC1->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV128; // Divide Clock ADC GCLK by 128 (48MHz/128 = 375kHz) ADC1->CTRLB.reg = ADC_CTRLB_RESSEL_12BIT | // Set ADC resolution to 12 bits ADC_CTRLB_FREERUN; // Set ADC to free run mode while (ADC1->SYNCBUSY.bit.CTRLB) ; // Wait for synchronization ADC1->CTRLA.bit.ENABLE = 1; // Enable the ADC while (ADC1->SYNCBUSY.bit.ENABLE) ; // Wait for synchronization ADC1->SWTRIG.bit.START = 1; // Initiate a software trigger to start an ADC conversion while (ADC1->SYNCBUSY.bit.SWTRIG) ; // Wait for synchronization // Enable DMA channel 1 DMAC->Channel[1].CHCTRLA.bit.ENABLE = 1; // Configure Timer/Counter 5 GCLK->PCHCTRL[TC5_GCLK_ID].reg = GCLK_PCHCTRL_CHEN | // Enable peripheral channel for TC5 GCLK_PCHCTRL_GEN_GCLK1; // Connect generic clock 0 at 48MHz TC5->COUNT16.WAVE.reg = TC_WAVE_WAVEGEN_MFRQ; // Set TC5 to Match Frequency (MFRQ) mode TC5->COUNT16.CC[0].reg = 3000 - 1; // Set the trigger to 16 kHz: (4Mhz / 16000) - 1 while (TC5->COUNT16.SYNCBUSY.bit.CC0) ; // Wait for synchronization // Start Timer/Counter 5 TC5->COUNT16.CTRLA.bit.ENABLE = 1; // Enable the TC5 timer while (TC5->COUNT16.SYNCBUSY.bit.ENABLE) ; // Wait for synchronization } uint16_t _adc_buf_0[ADC_BUF_LEN]; uint16_t _adc_buf_1[ADC_BUF_LEN];
Αυτός ο κώδικας ορίζει μια μέθοδο
configureDmaAdc
που ρυθμίζει τον DMAC, συνδέοντάς τον με τον ADC και ρυθμίζοντάς τον να γεμίζει δύο διαφορετικούς εναλλασσόμενους buffers,_adc_buf_0
και_adc_buf_1
.💁 Ένα από τα μειονεκτήματα της ανάπτυξης για μικροελεγκτές είναι η πολυπλοκότητα του κώδικα που απαιτείται για την αλληλεπίδραση με το υλικό, καθώς ο κώδικάς σας εκτελείται σε πολύ χαμηλό επίπεδο αλληλεπιδρώντας απευθείας με το υλικό. Αυτός ο κώδικας είναι πιο περίπλοκος από αυτόν που θα γράφατε για έναν υπολογιστή ή έναν υπολογιστή μονού πίνακα, καθώς δεν υπάρχει λειτουργικό σύστημα για να βοηθήσει. Υπάρχουν διαθέσιμες βιβλιοθήκες που μπορούν να απλοποιήσουν αυτό, αλλά εξακολουθεί να υπάρχει αρκετή πολυπλοκότητα.
-
Παρακάτω, προσθέστε τον ακόλουθο κώδικα:
// WAV files have a header. This struct defines that header struct wavFileHeader { char riff[4]; /* "RIFF" */ long flength; /* file length in bytes */ char wave[4]; /* "WAVE" */ char fmt[4]; /* "fmt " */ long chunk_size; /* size of FMT chunk in bytes (usually 16) */ short format_tag; /* 1=PCM, 257=Mu-Law, 258=A-Law, 259=ADPCM */ short num_chans; /* 1=mono, 2=stereo */ long srate; /* Sampling rate in samples per second */ long bytes_per_sec; /* bytes per second = srate*bytes_per_samp */ short bytes_per_samp; /* 2=16-bit mono, 4=16-bit stereo */ short bits_per_samp; /* Number of bits per sample */ char data[4]; /* "data" */ long dlength; /* data length in bytes (filelength - 44) */ }; void initBufferHeader() { wavFileHeader wavh; strncpy(wavh.riff, "RIFF", 4); strncpy(wavh.wave, "WAVE", 4); strncpy(wavh.fmt, "fmt ", 4); strncpy(wavh.data, "data", 4); wavh.chunk_size = 16; wavh.format_tag = 1; // PCM wavh.num_chans = 1; // mono wavh.srate = RATE; wavh.bytes_per_sec = (RATE * 1 * 16 * 1) / 8; wavh.bytes_per_samp = 2; wavh.bits_per_samp = 16; wavh.dlength = RATE * 2 * 1 * 16 / 2; wavh.flength = wavh.dlength + 44; _writer.writeSfudBuffer((byte *)&wavh, 44); }
Αυτός ο κώδικας ορίζει την κεφαλίδα WAV ως μια δομή που καταλαμβάνει 44 byte μνήμης. Γράφει λεπτομέρειες σε αυτήν σχετικά με τον ρυθμό αρχείου ήχου, το μέγεθος και τον αριθμό των καναλιών. Αυτή η κεφαλίδα στη συνέχεια γράφεται στη μνήμη flash.
-
Παρακάτω από αυτόν τον κώδικα, προσθέστε τα ακόλουθα για να δηλώσετε μια μέθοδο που θα καλείται όταν οι buffers ήχου είναι έτοιμοι για επεξεργασία:
void audioCallback(uint16_t *buf, uint32_t buf_len) { static uint32_t idx = 44; if (_isRecording) { for (uint32_t i = 0; i < buf_len; i++) { int16_t audio_value = ((int16_t)buf[i] - 2048) * 16; _writer.writeSfudBuffer(audio_value & 0xFF); _writer.writeSfudBuffer((audio_value >> 8) & 0xFF); } idx += buf_len; if (idx >= BUFFER_SIZE) { _writer.flushSfudBuffer(); idx = 44; _isRecording = false; _isRecordingReady = true; } } }
Οι buffers ήχου είναι πίνακες 16-bit ακέραιων που περιέχουν τον ήχο από τον ADC. Ο ADC επιστρέφει 12-bit μη υπογεγραμμένες τιμές (0-1023), οπότε αυτές πρέπει να μετατραπούν σε 16-bit υπογεγραμμένες τιμές και στη συνέχεια να μετατραπούν σε 2 byte για να αποθηκευτούν ως ακατέργαστα δυαδικά δεδομένα.
Αυτά τα byte γράφονται στους buffers μνήμης flash. Η εγγραφή ξεκινά από τον δείκτη 44 - αυτή είναι η μετατόπιση από τα 44 byte που γράφτηκαν ως κεφαλίδα αρχείου WAV. Μόλις καταγραφούν όλα τα byte που χρειάζονται για την απαιτούμενη διάρκεια ήχου, τα υπόλοιπα δεδομένα γράφονται στη μνήμη flash.
-
Στην ενότητα
public
της κλάσηςMic
, προσθέστε τον ακόλουθο κώδικα:void dmaHandler() { static uint8_t count = 0; if (DMAC->Channel[1].CHINTFLAG.bit.SUSP) { DMAC->Channel[1].CHCTRLB.reg = DMAC_CHCTRLB_CMD_RESUME; DMAC->Channel[1].CHINTFLAG.bit.SUSP = 1; if (count) { audioCallback(_adc_buf_0, ADC_BUF_LEN); } else { audioCallback(_adc_buf_1, ADC_BUF_LEN); } count = (count + 1) % 2; } }
Αυτός ο κώδικας θα καλείται από τον DMAC για να ενημερώσει τον κώδικά σας να επεξεργαστεί τους buffers. Ελέγχει ότι υπάρχουν δεδομένα για επεξεργασία και καλεί τη μέθοδο
audioCallback
με τον σχετικό buffer. -
Εκτός της κλάσης, μετά τη δήλωση
Mic mic;
, προσθέστε τον ακόλουθο κώδικα:void DMAC_1_Handler() { mic.dmaHandler(); }
Ο
DMAC_1_Handler
θα καλείται από τον DMAC όταν οι buffers είναι έτοιμοι για επεξεργασία. Αυτή η συνάρτηση εντοπίζεται με το όνομα, οπότε απλώς πρέπει να υπάρχει για να καλείται. -
Προσθέστε τις ακόλουθες δύο μεθόδους στην ενότητα
public
της κλάσηςMic
:void init() { analogReference(AR_INTERNAL2V23); _writer.init(); initBufferHeader(); configureDmaAdc(); } void reset() { _isRecordingReady = false; _isRecording = false; _writer.reset(); initBufferHeader(); }
Η μέθοδος
init
περιέχει κώδικα για την αρχικοποίηση της κλάσηςMic
. Αυτή η μέθοδος ρυθμίζει τη σωστή τάση για την ακίδα του μικροφώνου, ρυθμίζει τον συγγραφέα μνήμης flash, γράφει την κεφαλίδα WAV και ρυθμίζει τον DMAC. Η μέθοδοςreset
επαναφέρει τη μνήμη flash και ξαναγράφει την κεφαλίδα μετά την καταγραφή και χρήση του ήχου.
Εργασία - καταγράψτε ήχο
-
Στο αρχείο
main.cpp
, προσθέστε μια οδηγίαinclude
για το αρχείο κεφαλίδαςmic.h
:#include "mic.h"
-
Στη συνάρτηση
setup
, αρχικοποιήστε το κουμπί C. Η καταγραφή ήχου θα ξεκινήσει όταν πατηθεί αυτό το κουμπί και θα συνεχιστεί για 4 δευτερόλεπτα:pinMode(WIO_KEY_C, INPUT_PULLUP);
-
Παρακάτω από αυτό, αρχικοποιήστε το μικρόφωνο και στη συνέχεια εκτυπώστε στην κονσόλα ότι ο ήχος είναι έτοιμος για καταγραφή:
mic.init(); Serial.println("Ready.");
-
Πάνω από τη συνάρτηση
loop
, ορίστε μια συνάρτηση για την επεξεργασία του καταγεγραμμένου ήχου. Προς το παρόν,
💁 Μπορείτε να βρείτε αυτόν τον κώδικα στον φάκελο code-record/wio-terminal. 😀 Το πρόγραμμα ηχογράφησης ήχου σας ήταν επιτυχία!
Αποποίηση ευθύνης:
Αυτό το έγγραφο έχει μεταφραστεί χρησιμοποιώντας την υπηρεσία αυτόματης μετάφρασης Co-op Translator. Παρόλο που καταβάλλουμε προσπάθειες για ακρίβεια, παρακαλούμε να έχετε υπόψη ότι οι αυτοματοποιημένες μεταφράσεις ενδέχεται να περιέχουν λάθη ή ανακρίβειες. Το πρωτότυπο έγγραφο στη μητρική του γλώσσα θα πρέπει να θεωρείται η αυθεντική πηγή. Για κρίσιμες πληροφορίες, συνιστάται επαγγελματική ανθρώπινη μετάφραση. Δεν φέρουμε ευθύνη για τυχόν παρεξηγήσεις ή εσφαλμένες ερμηνείες που προκύπτουν από τη χρήση αυτής της μετάφρασης.