Arduino ड्यू पर ध्वनि उत्पादन

इस लेख में, मैं प्रोसेसर के सक्रिय उपयोग के बिना Arduino ड्यू के लिए ध्वनि उत्पादन करने के तरीके के बारे में बात करूंगा।

बोर्ड प्राप्त करने और रेखाचित्रों के साथ प्रयोग करने के बाद, मुझे एहसास हुआ कि मैं मानक आईडीई में फर्मवेयर नहीं लिखूंगा और विकल्प की तलाश शुरू कर दूंगा। मेरी पसंद Atmel स्टूडियो संस्करण 6.0 थी। मुझे वास्तव में यह आईडीई पसंद आया, जो विज़ुअल स्टूडियो 2010 पर आधारित है। वह विशेष रूप से इस तथ्य को पसंद करते हैं कि सब कुछ बॉक्स से बाहर काम करता है। एक नया प्रोजेक्ट बनाने के लिए विज़ार्ड में, मैंने Arduino ड्यू बोर्ड का चयन किया, प्रोजेक्ट को एक ला "हैलो वर्ल्ड" (एलईडी चमकती), संकलित और लॉन्च किया। यह विशेष रूप से मनभावन था कि कोई परत या लाइब्रेरी मुझसे छिपी नहीं थी। फर्मवेयर पूरी तरह से स्रोत कोड से इकट्ठा किया गया था, जिसने अंततः मुझे रिश्वत दी, और मैं Atmel स्टूडियो में रहा। संयोग से, विजुअल असिस्ट पहले से ही इसमें बनाया गया है, जो लेखन कोड को और भी अधिक आरामदायक बनाता है।

और इसलिए, मुझे DACC (एनालॉग कन्वर्टर कंट्रोलर) के माध्यम से ध्वनि के उत्पादन के कार्य का सामना करना पड़ा, लेकिन 100% प्रोसेसर लोड के बिना। आदर्श रूप से, मैं डेटा के अगले बैच को DACC को भेजना चाहता था और इसके बारे में भूल गया जब तक कि मुझे एक नया बैच भेजने की आवश्यकता नहीं थी।
ऐसा करने के लिए, मुझे पीडीसी (पेरिफेरल डीएमए कंट्रोलर) और टीसी (टाइमर काउंटर) का उपयोग करना पड़ा। अंत में, सब कुछ काफी सरल हो गया, लेकिन इससे पहले कि मैं काम करता, मुझे थोड़ा सा नुकसान हुआ। अगर दिलचस्पी है, तो मैं एक बिल्ली के लिए पूछता हूं।

ध्वनि उत्पादन स्वयं किसी जटिलता का नहीं है। हम केवल DACC चैनल को अगले 12-बिट मान लिखते हैं, इन ऑपरेशनों के बीच रुकते हुए। एक ऑडियो आउटपुट उदाहरण Atmel Studio (DAC Sinewave Example) में उपलब्ध है। इस उदाहरण में, सिस्टम टाइमर को इनिशियलाइज़ किया गया है, जिसमें इंटरफेयर हैंडलर में DACC चैनल को आयाम मान लिखा गया है। इसके अलावा, पहली नज़र में, प्रोसेसर को अधिक लोड नहीं करना चाहिए, लेकिन 32 kHz की आवृत्ति के लिए हमें पहले से ही प्रति सेकंड 32,000 इंटरप्ट की आवश्यकता होती है, इस तथ्य का उल्लेख नहीं करने के लिए कि सिस्टम टाइमर का उपयोग अन्य उद्देश्यों के लिए नहीं किया जा सकता है, क्योंकि इसे हर बार वांछित आवृत्ति पर पुन: कॉन्फ़िगर करना होगा।

DACC को बैचों में डेटा भेजने के लिए, आप PDC का उपयोग कर सकते हैं, लेकिन यह बिना विराम के डेटा भेजता है, अर्थात। उम्मीद है कि अगले आधे शब्द को लोहे द्वारा स्वीकार किया जाएगा और तुरंत अगले को भेजा जाएगा। यह हमारे लिए ध्वनि उत्पादन के लिए पर्याप्त नहीं है, क्योंकि डेटा को एक विशिष्ट आवृत्ति पर दर्ज किया जाना चाहिए। यहां DACC ऑपरेटिंग मोड में से एक हमारी सहायता के लिए आता है, अर्थात्, वह जानता है कि टीसी को ट्रिगर के रूप में कैसे उपयोग किया जाए। तो, यह हमारे लिए वांछित आवृत्ति के लिए टीसी आवृत्ति को ट्यून करने के लिए रहता है और डीएसीसी को टीसी चैनलों में से एक का उपयोग करने के लिए कहता है।

टीसी को "तरंग" मोड में ट्यून किया गया है। ऐसा करने के लिए, काउंटर मान आरए रजिस्टर में दर्ज किया जाता है, जिस पर आउटपुट में सिग्नल दिखाई देता है, और आरसी रजिस्टर में, मूल्य (हमारे मामले में, रा + 1), जिस पर आउटपुट सिग्नल रीसेट है। इस प्रकार, हम एक दी गई आवृत्ति का संकेत उत्पन्न करते हैं, जो DACC इनपुट को प्रेषित होता है।

पीडीसी का उपयोग करते समय, आपको यह विचार करने की आवश्यकता है कि यह केवल रैम से रोम से डेटा स्थानांतरित नहीं कर सकता है।
ध्वनि मॉड्यूल sound.c फ़ाइल में है
//   static uint16_t g_buffer[4096]; //   ,        static volatile uint16_t g_buffer_head, g_buffer_tail, g_buffer_size; //         static inline uint16_t get_continuous_data_size_irq(void) { return g_buffer_head <= g_buffer_tail ? g_buffer_tail - g_buffer_head : ARRAYSIZE(g_buffer) - g_buffer_head; } //       static inline uint16_t get_buffer_capacity(void) { return g_buffer_size; } //     static uint16_t copy_in_buffer(const uint16_t * data, uint16_t size) { uint16_t tail = g_buffer_tail; //        uint32_t to_copy = min(size, ARRAYSIZE(g_buffer) - tail); memcpy((void *)(g_buffer + tail), (void *)data, (size_t)(to_copy << 1)); if (to_copy < size) { //        memcpy(g_buffer, data + to_copy, (size - to_copy) << 1); tail = size - to_copy; } else { //        tail += size; } return tail; } //     ,    static inline void move_head_irq(uint16_t size) { g_buffer_head += size; g_buffer_size += size; if (g_buffer_head >= ARRAYSIZE(g_buffer) && g_buffer_head > g_buffer_tail) g_buffer_head -= ARRAYSIZE(g_buffer); } //      PDC //    static pdc_packet_t g_pdc_packet; //     PDC static volatile uint32_t g_pdc_packet_size; //   0,  PDC    static volatile uint8_t g_pdc_active; //    DACC static inline void disable_irq(void) { dacc_disable_interrupt(DACC, DACC_IER_ENDTX); } //  PDC ,     DACC static inline void restore_irq(void) { if (g_pdc_active) dacc_enable_interrupt(DACC, DACC_IER_ENDTX); } #define DACC_PDC_PACKET_MAX_SIZE 512 //   DACC // ,      PDC void DACC_Handler(void) { //   uint32_t status = dacc_get_interrupt_status(DACC); if (status & DACC_IER_ENDTX) { //   ,   move_head_irq(g_pdc_packet_size); uint16_t data_size = get_continuous_data_size_irq(); if (data_size == 0) { //   g_pdc_packet_size = 0; g_pdc_active = 0; //    DACC dacc_disable_interrupt(DACC, DACC_IDR_ENDTX); } else { //  PDC     g_pdc_packet.ul_addr = (uint32_t)(g_buffer + g_buffer_head); g_pdc_packet_size = min(DACC_PDC_PACKET_MAX_SIZE, data_size); g_pdc_packet.ul_size = g_pdc_packet_size; //  pdc_tx_init(PDC_DACC, &g_pdc_packet, NULL); } } } //   0 TC      static void tc_adjust_frequency(uint16_t frequency) { uint32_t divider = sysclk_get_cpu_hz() / frequency; divider >>= 1; tc_write_ra(TC0, 0, divider); tc_write_rc(TC0, 0, divider + 1); } //   void sound_init_hardware(void) { //   TC sysclk_enable_peripheral_clock(ID_TC0); //  TC0   waveform tc_init(TC0, 0, TC_CMR_TCCLKS_TIMER_CLOCK1 | TC_CMR_WAVE /* Waveform mode is enabled */ | TC_CMR_ACPA_SET /* RA Compare Effect: set */ | TC_CMR_ACPC_CLEAR /* RC Compare Effect: clear */ | TC_CMR_CPCTRG /* UP mode with automatic trigger on RC Compare */ ); //  -   TC,    tc_adjust_frequency(8000); //   tc_start(TC0, 0); //   DACC sysclk_enable_peripheral_clock(ID_DACC); //    DACC dacc_reset(DACC); //  DACC   "half word" dacc_set_transfer_mode(DACC, 0); //    dacc_set_power_save(DACC, 0, 0); //   TC0    dacc_set_trigger(DACC, 1); //  TAG     DACC dacc_set_channel_selection(DACC, 1); //    DACC dacc_enable_channel(DACC, 1); //     dacc_set_analog_control(DACC, DACC_ACR_IBCTLCH0(0x02) | DACC_ACR_IBCTLCH1(0x02) | DACC_ACR_IBCTLDACCORE(0x01)); //    DACC   NVIC_DisableIRQ(DACC_IRQn); NVIC_ClearPendingIRQ(DACC_IRQn); NVIC_EnableIRQ(DACC_IRQn); //   PDC     DACC pdc_enable_transfer(PDC_DACC, PERIPH_PTCR_TXTEN); //    g_pdc_packet_size = 0; g_pdc_active = 0; } //        void sound_start(uint16_t frequency) { //     sound_stop(); //   tc_adjust_frequency(frequency); } //    void sound_stop(void) { //   DACC  dacc_disable_interrupt(DACC, DACC_IER_ENDTX); g_pdc_packet_size = 0; g_buffer_head = g_buffer_tail = 0; g_buffer_size = ARRAYSIZE(g_buffer); g_pdc_active = 0; } //          uint8_t sound_data(const uint16_t * data, uint16_t size) { //    uint16_t capacity = get_buffer_capacity(); if (capacity < size) return 0; //     uint16_t tail = copy_in_buffer(data, size); //    disable_irq(); g_buffer_tail = tail; g_buffer_size -= size; if (g_buffer_head >= ARRAYSIZE(g_buffer)) g_buffer_head -= ARRAYSIZE(g_buffer); g_pdc_active = 1; restore_irq(); //    return 1; } 


मैंने एक छोटा सा उपयोग उदाहरण लिखा।
 #include <asf.h> #include "sound.h" #define STRING_HEADER "-- Sound firmware --\r\n" \ "-- "BOARD_NAME" --\r\n" \ "-- Compiled: "__DATE__" "__TIME__" --\r" static void configure_console(void) { const usart_serial_options_t uart_serial_options = { .baudrate = 115200, .paritytype = UART_MR_PAR_NO }; sysclk_enable_peripheral_clock(ID_UART); stdio_serial_init(UART, &uart_serial_options); } #include "sound_data.c" static uint32_t g_sound_pos = 0; int main (void) { sysclk_init(); board_init(); configure_console(); puts(STRING_HEADER); printf("CPU Frequency %li MHz\r\n", sysclk_get_cpu_hz() / 1000000); sound_init_hardware(); sound_start(16000); puts("Work...\r"); while (1) { //    512   ,   uint32_t size = min(512, ARRAYSIZE(g_sound_data) - g_sound_pos); if (sound_data(g_sound_data + g_sound_pos, size)) { //      g_sound_pos += size; if (g_sound_pos >= ARRAYSIZE(g_sound_data)) g_sound_pos = 0; } else { //    ,    } } } 


यहां लूप में हम सरणी से आउटपुट कतार तक डेटा भेजते हैं, सफलता के मामले में हम सरणी के अंत तक जाते हैं, अंत तक पहुंचते हैं, शुरुआत में लौटते हैं। हम अंतहीन काम करते हैं।

मैं एक अन्य परियोजना में इस मॉड्यूल का उपयोग करता हूं, वहां मैं ध्वनि का उत्पादन करता हूं जब मुझे यूएसबी के माध्यम से अगला पैकेट प्राप्त होता है, इसलिए प्रोसेसर को लोड नहीं करने का कार्य पूरा हो गया था।
आप पैकेट के बीच पूर्व-परिकलित ठहराव के साथ ऑडियो डेटा भी भेज सकते हैं, उदाहरण के लिए, यदि पैकेट का आकार 512 अर्ध-शब्द है और ऑडियो आवृत्ति 8 kHz है, तो भेजने के बीच का ठहराव 512/8000 = 64 एमएस होगा।

यदि हम सुनिश्चित करें कि वे रोम से नहीं आ रहे हैं, तो डेटा कॉपी से छुटकारा पाना अच्छा होगा। और पीडीसी एक ही बार में दो पैकेट ट्रांसमिट कर सकता है, यह भी इस्तेमाल किया जा सकता है।

गतिशील रूप से ध्वनि की आवृत्ति को बदलते हुए, आप इसके भरने में असमानता को सुचारू कर सकते हैं। उदाहरण के लिए, जब इसकी मात्रा के 2/3 बफ़र को भरते हैं, तो हम आवृत्ति को थोड़ा बढ़ाते हैं ताकि यह तेजी से खाली हो।

मैं Arduino IDE से मानक उपयोगिता का उपयोग करके बोर्ड को फ्लैश कर रहा था, वैकल्पिक तरीकों की तलाश में बहुत आलसी था, और इसने मुझे पूरी तरह से अनुकूल बनाया। आप कमांड के साथ कंट्रोलर के लिए एक उदाहरण भेज सकते हैं:
bossac.exe --port=COM4 -U false -e -w -v -b sound.bin –R

Bossac.exe - Arduino IDE से उपयोगिता
COM पोर्ट का नाम दर्ज करें।
आपको अपने प्रोजेक्ट के डीबग या रिलीज़ निर्देशिका में फ़र्मवेयर फ़ाइल मिलेगी।

पूरे प्रोजेक्ट को यहाँ github.com/artalex/arduino_due_sound पाया जा सकता है

Atmel स्टूडियो के पास Arduino ड्यू के लिए बहुत सारे उदाहरण हैं, मैं आपको उन्हें अध्ययन करने के लिए दृढ़ता से सलाह देता हूं।

बोर्ड के पिनआउट का वर्णन यहाँ arduino.cc/en/Hacking/PinMappingSAM3X है
यहाँ SAM3X नियंत्रक के विवरण के लिए एक लिंक दिया गया है: www.atmel.com/Images/doc11057.pdf

उदाहरण के लिए एक सरणी wave2hex.py स्क्रिप्ट द्वारा प्राप्त की गई थी। यह परियोजना में स्क्रिप्ट निर्देशिका में निहित है।
इसे इस तरह उपयोग करें: wave2hex.py –I some.wav , जहाँ wave2hex.py –I some.wav उस फ़ाइल का नाम है जिसे आप कनवर्ट करना चाहते हैं।

Source: https://habr.com/ru/post/In208436/


All Articles