पॉलीफोनी समर्थन के साथ तरंग तालिकाओं की विधि द्वारा AVR माइक्रोकंट्रोलर पर ध्वनि पीढ़ी

AVR माइक्रोकंट्रोलर काफी सस्ते और व्यापक हैं। शायद, लगभग कोई भी एम्बेडेड डेवलपर उनके साथ शुरू होता है। और शौकीनों के बीच, Arduino गेंद पर शासन करता है, जिसका दिल आमतौर पर ATmega328p है। निश्चित रूप से कई लोग आश्चर्यचकित थे: आप उन्हें आवाज़ कैसे दे सकते हैं?

यदि आप मौजूदा परियोजनाओं को देखते हैं, तो वे कई प्रकार के होते हैं:

  1. स्क्वायर पल्स जनरेटर। पीडब्लूएम या यैंक पिन का उपयोग करके व्यवधान उत्पन्न करें। किसी भी मामले में, एक बहुत ही विशेषता चीख़ने वाली ध्वनि प्राप्त की जाती है।
  2. एमपी डिकोडर जैसे बाहरी उपकरणों का उपयोग करना।
  3. पीसीएम या एडीपीसीएम प्रारूप में 8 बिट (कभी-कभी 16 बिट) ध्वनि का उत्पादन करने के लिए पीडब्लूएम का उपयोग करना। चूंकि माइक्रोकंट्रोलर्स में मेमोरी स्पष्ट रूप से इसके लिए पर्याप्त नहीं है, वे आमतौर पर एसडी कार्ड का उपयोग करते हैं।
  4. मिडी जैसे तरंग तालिकाओं के आधार पर ध्वनि उत्पन्न करने के लिए पीडब्लूएम का उपयोग करना।

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



रुचि, मैं बिल्ली के लिए पूछना।

तो, उपकरण:

  • ATmega8 या ATmega328। अन्य ATmega के लिए पोर्ट करना मुश्किल नहीं है। और एटीटीनी पर भी, लेकिन बाद में उस पर अधिक;
  • रोकनेवाला;
  • कंडेनसर;
  • स्पीकर या हेडफ़ोन;
  • पोषण;

सब कुछ पसंद है।

एक साधारण आरसी सर्किट एक स्पीकर के साथ माइक्रोकंट्रोलर के आउटपुट से जुड़ा होता है। आउटपुट एक 8-बिट ध्वनि है जिसमें 31250Hz का नमूना आवृत्ति है। 8 मेगाहर्ट्ज की एक क्रिस्टल आवृत्ति पर, 5 ध्वनि चैनल तक + पर्क्यूशन के लिए एक शोर चैनल उत्पन्न किया जा सकता है। इस स्थिति में, लगभग सभी प्रोसेसर समय का उपयोग किया जाता है, लेकिन बफर भरने के बाद, प्रोसेसर को ध्वनि के अतिरिक्त कुछ उपयोगी के साथ रखा जा सकता है:


यह उदाहरण पूरी तरह से ATmega8 मेमोरी में फिट बैठता है, 5 चैनल + शोर 8 मेगाहर्ट्ज की एक क्रिस्टल आवृत्ति पर संसाधित होते हैं और प्रदर्शन पर एनीमेशन के लिए बहुत कम समय होता है।

इस उदाहरण में, मैं यह भी दिखाना चाहता था कि पुस्तकालय का उपयोग न केवल एक नियमित संगीत पोस्टकार्ड के रूप में किया जा सकता है, बल्कि ध्वनि को मौजूदा परियोजनाओं से जोड़ने के लिए भी किया जा सकता है, उदाहरण के लिए, सूचनाओं के लिए। और यहां तक ​​कि सिर्फ एक साउंड चैनल का उपयोग करते समय, सूचनाएं एक साधारण ट्वीटर की तुलना में अधिक दिलचस्प हो सकती हैं।

और अब विवरण ...

वेव टेबल या वेवटेबल्स


गणित बेहद सरल है। एक आवधिक टोन फ़ंक्शन है, उदाहरण के लिए टोन (t) = sin (t * freq / (2 * Pi))

समय के साथ मौलिक स्वर की मात्रा को बदलने के लिए एक फ़ंक्शन भी है, उदाहरण के लिए वॉल्यूम (टी) = ई ^ (- टी)

सरलतम स्थिति में, एक उपकरण की ध्वनि इन कार्यों के उत्पाद है (t) = टोन (t) * आयतन (t) :

चार्ट पर, यह सब कुछ इस तरह दिखता है:



अगला, हम सभी उपकरणों को लेते हैं जो एक निश्चित समय पर ध्वनि करते हैं और उन्हें कुछ मात्रा कारकों (छद्म-कोड) के साथ सारांशित करते हैं:

for (i = 0; i < CHANNELS; i++) { value += channels[i].tone(t) * channels[i].volume(t) * channels[i].volume; } 

केवल वॉल्यूम का चयन करना आवश्यक है ताकि कोई अतिप्रवाह न हो। और यह लगभग सभी है।

शोर चैनल एक ही तरह से काम करता है, लेकिन एक टोन फ़ंक्शन के बजाय एक छद्म यादृच्छिक अनुक्रम जनरेटर है।

पर्क्यूशन लगभग 50-70 हर्ट्ज पर शोर चैनल और कम आवृत्ति की लहर का मिश्रण है।
बेशक, इस तरह से उच्च-गुणवत्ता वाली ध्वनि को प्राप्त करना मुश्किल है। लेकिन हमारे पास हर चीज के लिए केवल 8 किलोबाइट हैं। उम्मीद है कि इसे माफ किया जा सकता है।

मैं 8 बिट्स में से क्या निचोड़ सकता हूं


प्रारंभ में, मैंने ATmega8 पर ध्यान केंद्रित किया। बाहरी क्वार्ट्ज के बिना, यह 8 मेगाहर्ट्ज की आवृत्ति पर संचालित होता है और इसमें 8-बिट पीडब्लूएम होता है, जो 8000000/256 = 31250 हर्ट्ज के आधार नमूने की आवृत्ति देता है। एक टाइमर आउटपुट ध्वनि के लिए पीडब्लूएम का उपयोग करता है, और यह पीडब्लूएम जनरेटर के अगले मूल्य को प्रसारित करने के लिए अतिप्रवाह के दौरान एक व्यवधान का कारण बनता है। तदनुसार, हमारे पास हर चीज के लिए नमूना मूल्य की गणना करने के लिए 256 घड़ी चक्र हैं, जिसमें इंटरफेड ओवरहेड, साउंड चैनल मापदंडों को अपडेट करना, उस समय को ट्रैक करना जब आपको अगले नोट खेलने की आवश्यकता होती है, आदि।

अनुकूलन के लिए, हम निम्नलिखित ट्रिक्स का सक्रिय रूप से उपयोग करेंगे:

  • चूंकि हमारे पास आठ-बिट प्रोसेसर है, इसलिए हम चर को समान बनाने की कोशिश करेंगे। कभी-कभी हम 16 बिट का उपयोग करेंगे।
  • गणना सशर्त रूप से बार-बार विभाजित की जाती है और ऐसा नहीं है। पहले लोगों को प्रत्येक नमूने के लिए गणना करने की आवश्यकता होती है, दूसरा - बहुत कम अक्सर, एक बार हर दसियों / सैकड़ों नमूने।
  • समय के साथ लोड को समान रूप से वितरित करने के लिए, हम एक परिपत्र बफर का उपयोग करते हैं। कार्यक्रम के मुख्य लूप में, हम बफर को भरते हैं, इसे बाधित में घटाते हैं। यदि सब कुछ ठीक है, तो बफर तेजी से भरता है इससे खाली हो जाता है और हमारे पास किसी और चीज के लिए समय है।
  • कोड बहुत सी इनलाइन के साथ C में लिखा गया है। अभ्यास से पता चलता है कि यह बहुत तेज है।
  • सभी की गणना प्रीप्रोसेसर द्वारा की जा सकती है, विशेष रूप से विभाजन की भागीदारी के साथ, प्रीप्रोसेसर द्वारा की जाती है।

सबसे पहले, समय को 4 मिलीसेकंड के अंतराल में विभाजित करें (मैंने उन्हें टिक कहा जाता है)। 31250Hz के नमूने की आवृत्ति पर, हमें प्रति टिक 125 नमूने मिलते हैं। तथ्य यह है कि प्रत्येक नमूना पढ़ा जाना चाहिए हर नमूने की गणना की जानी चाहिए, और बाकी - एक बार टिक या उससे कम। उदाहरण के लिए, एक टिक के भीतर, साधन की मात्रा स्थिर होगी: साधन (टी) = टोन (टी) * currentVolume ; और currentVolume को एक बार खाते की मात्रा (t) और साउंड चैनल के चयनित वॉल्यूम में ले जाने वाले टिक के अनुसार पुनर्गणना होगी।

एक साधारण 8-बिट सीमा के आधार पर 4ms की एक टिक अवधि को चुना गया था: आठ-बिट नमूना काउंटर के साथ, आप 64 kHz तक की नमूना आवृत्ति के साथ काम कर सकते हैं, आठ-बिट टिक काउंटर के साथ हम 1 सेकंड तक का समय माप सकते हैं।

कुछ कोड


चैनल इस संरचना द्वारा वर्णित है:

 typedef struct { // Info about wave const int8_t* waveForm; // Wave table array uint16_t waveSample; // High byte is an index in waveForm array uint16_t waveStep; // Frequency, how waveSample is changed in time // Info about volume envelope const uint8_t* volumeForm; // Array of volume change in time uint8_t volumeFormLength; // Length of volumeForm uint8_t volumeTicksPerSample; // How many ticks should pass before index of volumeForm is changed uint8_t volumeTicksCounter; // Counter for volumeTicksPerSample // Info about volume uint8_t currentVolume; // Precalculated volume for current tick uint8_t instrumentVolume; // Volume of channel } waveChannel; 

सशर्त रूप से, यहां डेटा को 3 भागों में विभाजित किया गया है:

  1. तरंग, चरण, आवृत्ति के बारे में जानकारी।

    waveForm: टोन (t) फ़ंक्शन के बारे में जानकारी: लंबे समय तक 256 बाइट की एक सरणी का संदर्भ। टोन, इंस्ट्रूमेंट साउंड सेट करता है।

    waveSample: हाई बाइट वेवफॉर्म सरणी के वर्तमान सूचकांक को इंगित करता है।

    waveStep: आवृत्ति सेट करता है जिसके द्वारा अगले नमूने की गिनती करते समय waveSample को बढ़ाया जाएगा।

    प्रत्येक नमूने को कुछ इस तरह माना जाता है:

     int8_t tone = channelData.waveForm[channelData.waveSample >> 8]; channelData.waveSample += channelaData.waveStep; return tone * channelData.currentVolume; 

  2. मात्रा की जानकारी। समय के साथ मात्रा बदलने का कार्य निर्धारित करता है। चूंकि वॉल्यूम में इतनी बार बदलाव नहीं होता है, इसलिए आप इसे कम बार, एक बार टिक के माध्यम से पुन: लिख सकते हैं। यह इस प्रकार किया जाता है:

     if ((channel->volumeTicksCounter--) == 0 && channel->volumeFormLength > 0) { channel->volumeTicksCounter = channel->volumeTicksPerSample; channel->volumeFormLength--; channel->volumeForm++; } channel->currentVolume = channel->volumeForm * channel->instrumentVolume >> 8; 

  3. चैनल की मात्रा और गणना की गई वर्तमान मात्रा निर्धारित करता है।

कृपया ध्यान दें: तरंग आठ-बिट है, वॉल्यूम भी आठ-बिट है, और परिणाम 16-बिट है। प्रदर्शन में मामूली नुकसान के साथ, आप ध्वनि (लगभग) 16 बिट कर सकते हैं।

उत्पादकता के संघर्ष में, मुझे कुछ काले जादू का सहारा लेना पड़ा।

उदाहरण संख्या 1. चैनलों के आयतन की गणना कैसे करें:

 if ((tickSampleCounter--) == 0) { //    tickSampleCounter = SAMPLES_PER_TICK – 1; //   - } // volume recalculation should no be done so often for all channels if (tickSampleCounter < CHANNELS_SIZE) { recalculateVolume(channels[tickSampleCounter]); } 

इस प्रकार, सभी चैनल प्रति बार एक बार वॉल्यूम की गणना करते हैं, लेकिन एक साथ नहीं।

उदाहरण संख्या 2. स्थिर संरचना में चैनल की जानकारी रखना किसी ऐरे से सस्ता है। Wavechannel.h के कार्यान्वयन के विवरण में जाने के बिना, मैं कहूंगा कि इस फाइल को विभिन्न प्रीप्रोसेसर निर्देशों के साथ कई बार (चैनलों की संख्या के बराबर) कोड में डाला जाता है। प्रत्येक प्रविष्टि नए वैश्विक चर और एक नया चैनल गणना फ़ंक्शन बनाता है, जो तब मुख्य कोड में इनलाइन होता है:

 #if CHANNELS_SIZE >= 1 val += channel0NextSample(); #endif #if CHANNELS_SIZE >= 2 val += channel1NextSample(); #endif … 

उदाहरण संख्या 3. यदि हम अगले नोट को थोड़ी देर बाद खेलना शुरू करते हैं, तो कोई भी नोटिस नहीं करेगा। आइए स्थिति की कल्पना करें: हमने प्रोसेसर को कुछ के साथ लिया और इस दौरान बफर लगभग खाली था। फिर हम इसे भरना शुरू करते हैं और अचानक यह पता चलता है कि एक नया उपाय आ रहा है: हमें वर्तमान नोटों को अपडेट करने की आवश्यकता है, उस सरणी से पढ़ें, जो आदि। यदि हमारे पास समय नहीं है, तो विशेषता हकलाना होगा। पुराने डेटा के साथ बफर को भरना थोड़ा बेहतर है, और उसके बाद ही चैनलों की स्थिति को अपडेट करें।

 while ((samplesToWrite) > 4) { //          fillBuffer(SAMPLES_PER_TICK); //     -  updateMusicData(); //    } 

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

संगीत


आठ-बिट टिक काउंटर का उपयोग किया जाता है। जब शून्य तक पहुंच जाता है, तो एक नया उपाय शुरू होता है, काउंटर को माप की अवधि (टिक्सेस में) सौंपी जाती है, थोड़ी देर बाद संगीत कमांड की सरणी की जांच की जाती है।

संगीत डेटा बाइट्स की एक सरणी में संग्रहीत किया जाता है। यह कुछ इस तरह से लिखा गया है:

 const uint8_t demoSample[] PROGMEM = { DATA_TEMPO(160), // Set beats per minute DATA_INSTRUMENT(0, 1), // Assign instrument 1 (see setSample) to channel 0 DATA_INSTRUMENT(1, 1), // Assign instrument 1 (see setSample) to channel 1 DATA_VOLUME(0, 128), // Set volume 128 to channel 0 DATA_VOLUME(1, 128), // Set volume 128 to channel 1 DATA_PLAY(0, NOTE_A4, 1), // Play note A4 on channel 0 and wait 1 beat DATA_PLAY(1, NOTE_A3, 1), // Play note A3 on channel 1 and wait 1 beat DATA_WAIT(63), // Wait 63 beats DATA_END() // End of data stream }; 

DATA_ के साथ शुरू होने वाले सभी प्रीप्रोसेसर मैक्रोज़ हैं जो डेटा बाइट्स की आवश्यक संख्या में मापदंडों का विस्तार करते हैं।

उदाहरण के लिए, DATA_PLAY कमांड को 2 बाइट्स में विस्तारित किया गया है, जिसमें संग्रहीत हैं: कमांड मार्कर (1 बिट), अगले कमांड (3 बिट्स) से पहले विराम, नोट को खेलने के लिए चैनल नंबर (4 बिट्स), नोट के बारे में जानकारी (8 बिट्स)। सबसे महत्वपूर्ण सीमा यह है कि इस कमांड का उपयोग लंबे समय तक, अधिकतम 7 चक्रों के लिए नहीं किया जा सकता है। यदि आपको अधिक आवश्यकता है, तो आपको DATA_WAIT कमांड (63 उपायों तक) का उपयोग करने की आवश्यकता है। दुर्भाग्य से, मैंने यह नहीं पाया कि मैक्रो को मैक्रो पैरामीटर के आधार पर सरणी के एक अलग संख्या बाइट्स में विस्तारित किया जा सकता है या नहीं। और चेतावनी देते हुए भी मुझे नहीं पता कि कैसे प्रदर्शित किया जाए। शायद आप मुझे बताएं।

के उपयोग


डेमो निर्देशिका में विभिन्न माइक्रोकंट्रोलर के लिए कई उदाहरण हैं। लेकिन संक्षेप में, यहाँ readme से एक टुकड़ा है, मेरे पास वास्तव में जोड़ने के लिए कुछ भी नहीं है:

 #include "../../microsound/devices/atmega8timer1.h" #include "../../microsound/micromusic.h" // Make some settings #define CHANNELS_SIZE 5 #define SAMPLES_SIZE 16 #define USE_NOISE_CHANNEL initMusic(); // Init music data and sound control sei(); // Enable interrupts, silence sound should be generated setSample(0, instrument1); // Use instrument1 as sample 0 setSample(1, instrument2); // Init all other instruments… playMusic(mySong); // Start playing music at pointer mySong while (!isMusicStopped) { fillMusicBuffer(); // Fill music buffer in loop // Do some other stuff } 

यदि आप संगीत के अलावा कुछ और करना चाहते हैं, तो आप BUFFER_SIZE का उपयोग करके बफर का आकार बढ़ा सकते हैं। बफर का आकार 2 ^ n होना चाहिए, लेकिन, दुर्भाग्य से, 256 के आकार के साथ, प्रदर्शन में गिरावट होती है। जब तक मैंने इसका पता लगा लिया।

उत्पादकता बढ़ाने के लिए, आप बाहरी क्वार्ट्ज के साथ आवृत्ति बढ़ा सकते हैं, आप चैनलों की संख्या कम कर सकते हैं, आप नमूना आवृत्ति को कम कर सकते हैं। नवीनतम तकनीक के साथ, आप रैखिक प्रक्षेप का उपयोग कर सकते हैं, जो कुछ हद तक ध्वनि की गुणवत्ता में गिरावट के लिए क्षतिपूर्ति करता है।

किसी भी देरी की सिफारिश नहीं की जाती है, क्योंकि सीपीयू का समय बर्बाद होता है। इसके बजाय, इसकी अपनी विधि माइक्रोसाउंड / देरी में लागू की जाती है। h फाइल , जो स्वयं पॉज़ के अलावा बफर को भरने में शामिल है। यह विधि अल्प विराम पर बहुत सटीक रूप से काम नहीं कर सकती है, लेकिन लंबे समय तक अधिक या कम गति पर रुक सकती है।

अपना खुद का संगीत बनाना


यदि आप मैन्युअल रूप से कमांड लिखते हैं, तो आपको यह सुनने में सक्षम होना चाहिए कि क्या होता है माइक्रोकंट्रोलर में प्रत्येक परिवर्तन को डालना सुविधाजनक नहीं है, खासकर अगर कोई विकल्प है।

एक बल्कि एक मज़ेदार सेवा wavepot.com है - एक ऑनलाइन जावास्क्रिप्ट संपादक जिसमें आपको समय-समय पर ध्वनि संकेत के कार्य को सेट करने की आवश्यकता होती है, और यह संकेत ध्वनि कार्ड के लिए आउटपुट है। सबसे सरल उदाहरण:

 function dsp(t) { return 0.1 * Math.sin(2 * Math.PI * t * 440); } 

मैंने इंजन को जावास्क्रिप्ट में पोर्ट किया, यह डेमो / वेवपॉट.जेएस में स्थित है। फ़ाइल की सामग्री को संपादक wavepot.com में डाला जाना चाहिए और आप प्रयोगों का संचालन कर सकते हैं। हम अपने डेटा को साउंडडेटा सरणी में लिखते हैं, सुनो, बचाने के लिए मत भूलना।

हमें simulate8bits चर का भी उल्लेख करना चाहिए। नाम के अनुसार, वह आठ-बिट ध्वनि का अनुकरण करती है। यदि अचानक ऐसा लगता है कि ढोल गूंज रहे हैं, और शोर शांत आवाज़ के साथ नम उपकरणों में दिखाई देता है, तो यह वह है, जो आठ-बिट ध्वनि का विरूपण है। आप इस विकल्प को अक्षम करने और अंतर को सुनने का प्रयास कर सकते हैं। संगीत में मौन न होने पर समस्या बहुत कम देखने को मिलती है।

संबंध


एक साधारण संस्करण में, सर्किट इस तरह दिखता है:

 +5V ^ MCU | +-------+ +---+VC | R1 | Pin+---/\/\--+-----> OUT | | | +---+GN | === C1 | +-------+ | | | --- Grnd --- Grnd 

आउटपुट पिन माइक्रोकंट्रोलर पर निर्भर करता है। रेसिस्टर आर 1 और कैपेसिटर सी 1 को लोड, एम्पलीफायर (यदि कोई हो), आदि के आधार पर चुना जाना चाहिए। मैं एक इलेक्ट्रॉनिक इंजीनियर नहीं हूं और मुझे सूत्र नहीं दिए गए हैं, वे ऑनलाइन कैलकुलेटर के साथ Google के लिए आसान हैं।

मेरे पास आर 1 = 130 ओह्स, सी 1 = 0.33 यूएफ है। आउटपुट के लिए मैं साधारण चीनी हेडफ़ोन कनेक्ट करता हूं।

16 बिट ध्वनि के बारे में क्या था?


जैसा कि मैंने ऊपर कहा, जब दो आठ-बिट संख्या (आवृत्ति और वॉल्यूम) को गुणा करते हैं, तो हमें 16-बिट संख्या मिलती है। आप इसे आठ बिट्स पर गोल नहीं कर सकते, लेकिन दोनों बाइट्स को 2 पीडब्लूएम चैनलों में आउटपुट करते हैं। यदि आप इन 2 चैनलों को 1/256 अनुपात में मिलाते हैं, तो हम 16 बिट ध्वनि प्राप्त कर सकते हैं। आठ-बिट के साथ अंतर केवल क्षणों में सुचारू रूप से लुप्त होती आवाज़ और ड्रम पर सुनने में आसान होता है जब केवल एक उपकरण लगता है।

16-बिट आउटपुट कनेक्शन:

 +5V ^ MCU | +-------+ +---+VCC | R1 | PinH+---/\/\--+-----> OUT | | | | | R2 | | PinL+---/\/\--+ +---+GND | | | +-------+ === C1 | | --- Grnd --- Grnd 

2 आउटपुट को सही ढंग से मिश्रण करना महत्वपूर्ण है: आर 2 प्रतिरोध आर 1 प्रतिरोध से 256 गुना अधिक होना चाहिए। जितना सटीक, उतना बेहतर। दुर्भाग्य से, 1% की त्रुटि वाले प्रतिरोधक भी आवश्यक सटीकता नहीं देते हैं। हालांकि, यहां तक ​​कि प्रतिरोधों के बहुत सटीक चयन के साथ, विरूपण को ध्यान से देखा जा सकता है।

दुर्भाग्य से, 16-बिट ध्वनि का उपयोग करते समय, प्रदर्शन में गिरावट और 5 चैनल + शोर के लिए आवंटित 256 घड़ी चक्रों में संसाधित होने का समय नहीं है।

क्या यह Arduino पर संभव है?


हाँ आप कर सकते हैं। मेरे पास केवल ATmega328p पर एक चीनी नैनो क्लोन है, यह उस पर काम करता है। सबसे अधिक संभावना है, ATmega328p पर अन्य arduins भी काम करना चाहिए। ATmega168 को एक ही टाइमर नियंत्रण रजिस्टर लगता है। सबसे अधिक संभावना है कि वे अपरिवर्तित काम करेंगे। अन्य माइक्रोकंट्रोलर्स पर जिन्हें आपको जांचना है, आपको ड्राइवर जोड़ने की आवश्यकता हो सकती है।

डेमो / arduino328p में एक स्केच है, लेकिन अरुडिनो आईडीई में सामान्य रूप से इसे खोलने के लिए, आपको इसे परियोजना की जड़ में कॉपी करने की आवश्यकता है।

उदाहरण में, 16-बिट ध्वनि उत्पन्न होती है और आउटपुट डी 9 और डी 10 का उपयोग किया जाता है। सरल बनाने के लिए, आप अपने आप को 8-बिट ध्वनि तक सीमित कर सकते हैं और केवल एक डी 9 आउटपुट का उपयोग कर सकते हैं।

चूंकि लगभग सभी आर्डिनेंस 16 मेगाहर्ट्ज पर काम करते हैं, इसलिए, यदि वांछित है, तो आप चैनलों की संख्या 8 तक बढ़ा सकते हैं।

ATtiny के बारे में क्या?


ATtiny में कोई हार्डवेयर गुणन नहीं है। सॉफ्टवेयर गुणन कि संकलक का उपयोग बेतहाशा धीमा है और सबसे अच्छा बचा जाता है। अनुकूलित कोडांतरक आवेषण का उपयोग करते समय, ATmega की तुलना में प्रदर्शन 2 गुना कम हो जाता है। ऐसा लगता है कि ATtiny का उपयोग करने का कोई मतलब नहीं है, लेकिन ...

कुछ ATtiny में एक आवृत्ति गुणक, PLL होता है। और इसका मतलब है कि ऐसे माइक्रोकंट्रोलर पर 2 दिलचस्प विशेषताएं हैं:

  1. पीडब्लूएम जनरेटर की आवृत्ति 64 मेगाहर्ट्ज है, जो पीडब्लूएम की अवधि 250 kHz देता है, जो कि 8 मेगाहर्ट्ज पर 31 250 हर्ट्ज से बेहतर है या किसी ATmega पर 16 मेगाहर्ट्ज पर क्वार्ट्ज के साथ 62500 हर्ट्ज है।
  2. एक ही आवृत्ति गुणक क्रिस्टल को क्वार्ट्ज के बिना 16 मेगाहर्ट्ज पर घड़ी करने की अनुमति देता है।

इसलिए निष्कर्ष: ध्वनि उत्पन्न करने के लिए कुछ ATtiny का उपयोग किया जा सकता है। वे समान 5 उपकरणों + शोर चैनल को संसाधित करने का प्रबंधन करते हैं, लेकिन 16 मेगाहर्ट्ज पर और उन्हें बाहरी क्वार्ट्ज की आवश्यकता नहीं है।

नकारात्मक पक्ष यह है कि आवृत्ति अब और नहीं बढ़ सकती है, और गणना लगभग हर समय होती है। संसाधनों को मुक्त करने के लिए, आप चैनलों की संख्या या नमूना दर को कम कर सकते हैं।

एक और माइनस को एक साथ दो टाइमर का उपयोग करने की आवश्यकता है: एक पीडब्लूएम के लिए, दूसरा रुकावट के लिए। यह वह जगह है जहां टाइमर आमतौर पर समाप्त होते हैं।

PLL के माइक्रोकंट्रोलर जो मुझे पता है, मैं ATtiny85 / 45/25 (8 पैर), ATtiny861 / 461/261 (20 पैर), ATtiny26 (20 पैर) का उल्लेख कर सकता हूं।

स्मृति के लिए, एटीमेगा के साथ अंतर महान नहीं है। 8kb में, कई उपकरण और धुन पूरी तरह से फिट होंगे। 4kb में आप 1-2 यंत्र और 1-2 धुन लगा सकते हैं। 2 किलोबाइट में कुछ डालना मुश्किल है, लेकिन अगर आप वास्तव में चाहते हैं, तो आप कर सकते हैं। तरीकों को अलग करना आवश्यक है, कुछ कार्यों को अक्षम करें जैसे कि चैनलों पर वॉल्यूम नियंत्रण, नमूना आवृत्ति और चैनलों की संख्या को कम करें। सामान्य तौर पर, एक शौकिया के लिए, लेकिन एट्टीनी26 पर एक कामकाजी उदाहरण है।

समस्याओं


समस्याएं हैं। और सबसे बड़ी समस्या कंप्यूटिंग की गति है। कोड पूरी तरह से C में ATtiny के लिए छोटे कोडांतर गुणन आवेषण के साथ लिखा गया है। अनुकूलन संकलक को दिया जाता है और यह कभी-कभी अजीब व्यवहार करता है। छोटे परिवर्तनों के साथ जो कुछ भी प्रभावित नहीं करना चाहिए, आप प्रदर्शन में ध्यान देने योग्य कमी प्राप्त कर सकते हैं। इसके अलावा, -O3 से -OO में बदलने से हमेशा मदद नहीं मिलती है। ऐसा ही एक उदाहरण 256 बाइट बफर का उपयोग है। विशेष रूप से अप्रिय यह है कि कोई गारंटी नहीं है कि संकलक के नए संस्करणों में हमें उसी कोड पर प्रदर्शन में गिरावट नहीं मिलेगी।

एक और समस्या यह है कि अगले नोट से पहले क्षीणन तंत्र बिल्कुल भी लागू नहीं होता है। यानी जब एक चैनल पर एक नोट को दूसरे द्वारा बदल दिया जाता है, तो पुरानी ध्वनि अचानक बाधित हो जाती है, कभी-कभी एक छोटा सा क्लिक सुनाई देता है। मैं बिना प्रदर्शन गंवाए इससे छुटकारा पाने का एक तरीका खोजना चाहूंगा, लेकिन अभी तक।

वॉल्यूम को सुचारू रूप से बढ़ाने / घटाने के लिए कोई आदेश नहीं हैं। यह विशेष रूप से लघु अधिसूचना रिंगटोन के लिए महत्वपूर्ण है, जहां अंत में आपको वॉल्यूम का एक त्वरित क्षीणन बनाने की आवश्यकता होती है ताकि ध्वनि में कोई तेज ब्रेक न हो। समस्या का हिस्सा मैन्युअल रूप से वॉल्यूम और एक छोटा ठहराव सेट करने के साथ आदेशों की एक श्रृंखला लिखकर है।

चुना हुआ दृष्टिकोण, सिद्धांत रूप में, उपकरणों के लिए एक प्राकृतिक ध्वनि प्रदान करने में सक्षम नहीं है। अधिक प्राकृतिक ध्वनि के लिए, आपको उपकरणों की आवाज़ को अटैक-सस्टेन-रिलीज़ में विभाजित करने की आवश्यकता है, कम से कम पहले 2 भागों का उपयोग करें और एक दोलन अवधि की तुलना में बहुत लंबे समय तक। लेकिन फिर उपकरण के लिए डेटा को और अधिक की आवश्यकता होगी। उदाहरण के लिए, शॉर्ट वेव टेबल का उपयोग करने का एक विचार था, उदाहरण के लिए, 256 के बजाय 32 बाइट्स में, लेकिन प्रक्षेप के बिना, ध्वनि की गुणवत्ता नाटकीय रूप से कम हो जाती है, और प्रक्षेप के साथ, प्रदर्शन कम हो जाता है। और नमूने के एक और 8 बिट स्पष्ट रूप से संगीत के लिए पर्याप्त नहीं हैं, लेकिन इसे दरकिनार किया जा सकता है।

बफर का आकार 256 नमूनों तक सीमित है। यह लगभग 8 मिलीसेकंड से मेल खाता है और यह अधिकतम अभिन्न समय अवधि है जिसे अन्य कार्यों के लिए दिया जा सकता है। उसी समय, कार्यों का निष्पादन अभी भी समय-समय पर रुकावटों द्वारा निलंबित किया जाता है।

मानक देरी की जगह छोटे ठहराव के लिए बहुत सटीक रूप से काम नहीं करता है।

मुझे यकीन है कि यह पूरी सूची नहीं है।

संदर्भ


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


All Articles