
दिल
मैंने पहले से ही कई अलग-अलग शौक इलेक्ट्रॉनिक उपकरणों का निर्माण किया है, और मेरे पास एक अजीब विशेषता है: अगर बोर्ड पर एक ध्वनि पीज़ोइलेक्ट्रिक एमिटर (बजर) है, तो, मैं परियोजना पर मुख्य काम खत्म करने के बाद, बकवास से पीड़ित होना शुरू कर दूंगा और उसे विभिन्न धुनों को खेलने दूंगा (जितना संभव हो उतना) )। ध्यान आकर्षित करने के लिए एक लंबी प्रक्रिया के अंत में एक राग शामिल करना विशेष रूप से उपयोगी है। उदाहरण के लिए, मैंने इसका उपयोग तब किया जब मैंने फोटोरिस्ट को रोशन करने के लिए एक मेशिफ्ट एक्सपोज़र कैमरा बनाया।
लेकिन जब मैंने नेटवर्क पर एवीआर के लिए आवृत्ति पीढ़ी के उदाहरणों की तलाश शुरू की, तो किसी कारण से मैं राक्षसी या अपर्याप्त रूप से संक्षिप्त परियोजनाओं में आया, जो शुद्ध रूप से प्रोग्रामेटिक तरीके से ध्वनि आवृत्तियों की पीढ़ी को लागू करते हैं। और फिर मैंने खुद इसका पता लगाने का फैसला किया ...
गीतात्मक विषयांतर
मेरे शौक में माइक्रोकंट्रोलर पर विभिन्न उपकरणों का निर्माण शामिल है, क्योंकि यह मेरे प्रोफेसर के साथ अंतर नहीं करता है। गतिविधि (सॉफ्टवेयर विकास), मैं खुद को एक पूर्ण आत्म-शिक्षा मानता हूं, और इलेक्ट्रॉनिक्स में भी मजबूत नहीं है। वास्तव में, मैं PIC माइक्रोकंट्रोलर्स को पसंद करता हूं, लेकिन यह सिर्फ इतना हुआ कि मैंने एक निश्चित संख्या में Atmel AVR माइक्रोकंट्रोलर्स (अब पहले से ही माइक्रोचिप) जमा कर लिया है। तुरंत एक आरक्षण करें कि मेरे हाथों में एवीआर कभी नहीं था, अर्थात्। यह Atmelga48pa, Atmel MCU पर मेरा पहला प्रोजेक्ट है। परियोजना खुद कुछ पेलोड को वहन करती है, लेकिन यहां मैं ध्वनि आवृत्ति की पीढ़ी से संबंधित इसके केवल भाग का वर्णन करूंगा। आवृत्तियों को उत्पन्न करने के लिए परीक्षण जिसे मैंने "बज़िक" कहा, बजर के संगीत के लिए एक संक्षिप्त नाम के रूप में। हां, मैं लगभग भूल गया था: हैबर पर उपनाम
बज़िक के साथ एक उपयोगकर्ता है, मैं तुरंत चेतावनी देना चाहता था कि यह मेमो किसी भी तरह से उसके लिए लागू नहीं होता है, और सिर्फ मामले में, मैं तुरंत "बुज़िक" पत्र संयोजन का उपयोग करने के लिए माफी मांगता हूं।
तो चलिए चलते हैं
मैं नेटवर्क से बड़ी संख्या में उदाहरणों से परिचित हुआ - ये सभी फर्मवेयर के मुख्य बॉडी में सरलतम चक्र पर या टाइमर के व्यवधान में निर्मित होते हैं। लेकिन वे सभी आवृत्ति उत्पन्न करने के लिए एक ही दृष्टिकोण का उपयोग करते हैं:
- माइक्रोकंट्रोलर के पैर को एक उच्च स्तर खिलाएं
- देरी करें
- माइक्रोकंट्रोलर के पैर को कम फ़ीड
देरी और टाइमर सेटिंग्स बदलना - आवृत्ति को समायोजित करें।
यह दृष्टिकोण मुझे बहुत पसंद नहीं आया, क्योंकि मुझे माइक्रोकंट्रोलर के पैर के मैनुअल नियंत्रण के लिए कोड लिखने की कोई इच्छा नहीं थी। मैं चाहूंगा कि "पत्थर" खुद मेरे लिए ध्वनि आवृत्ति उत्पन्न करे, और मैं बस कुछ रजिस्टरों के मूल्यों को निर्धारित करता हूं, जिससे यह (आवृत्ति) बदल जाता है।
डेटाशीट का अध्ययन करते समय (बाद में डीएस के रूप में संदर्भित), मुझे अभी भी टाइमर मोड की आवश्यकता थी - और यह मोड, जैसा कि आपने अनुमान लगाया है, सीटीसी (मैच की तुलना पर स्पष्ट टाइमर) मोड है। चूंकि संगीत बजाने का कार्य है, इसे हल्के ढंग से रखना, मुख्य कार्यक्षमता नहीं है, मैंने इसके लिए टाइमर 2 (एसडी के पैराग्राफ 22) का चयन करना पसंद किया।
हर कोई जानता है कि व्यावहारिक रूप से किसी भी माइक्रोकंट्रोलर के पास पीडब्लूएम सिग्नल जेनरेशन मोड है जो टाइमर पर लागू होता है और यह पूरी तरह से हार्डवेयर है। लेकिन इस कार्य में, PWM उपयुक्त नहीं है क्योंकि हार्डवेयर में केवल एक आवृत्ति उत्पन्न होगी। इसलिए, हमें PFM (पल्स फ़्रीक्वेंसी मॉड्यूलेशन) की आवश्यकता है। पीएफएम की कुछ समानता सीटीसी टाइमर मोड (क्लॉज 22.7.2 एलएच) है।
सीटीसी मोड
Atmega48pa माइक्रोकंट्रोलर में टाइमर 2 8-बिट है, अर्थात यह 0 से 255 तक "टिक" करता है और फिर एक सर्कल में जाता है। वैसे, टाइमर एक अलग दिशा में जा सकता है, लेकिन हमारे मामले में नहीं। अगला आवश्यक घटक तुलना इकाई है। बहुत गंभीरता से बोलते हुए, यह मॉड्यूल टाइमर से संबंधित किसी भी घटना का आरंभकर्ता है। घटनाएँ अलग हो सकती हैं - जैसे रुकावट, माइक्रोकंट्रोलर के कुछ पैरों के स्तर में बदलाव, आदि (जाहिर है, हम दूसरे में रुचि रखते हैं)। जैसा कि आप अनुमान लगा सकते हैं, तुलना मॉड्यूल सिर्फ नाम का नहीं है - यह फर्मवेयर डेवलपर द्वारा वर्तमान टाइमर मूल्य के साथ चयनित एक विशिष्ट मूल्य की तुलना करता है। यदि टाइमर मान हमारे द्वारा निर्धारित मूल्य तक पहुँचता है, तो एक घटना होती है। इवेंट तब भी हो सकते हैं जब टाइमर ओवरफ्लो हो या रीसेट के दौरान।
ठीक है, हम इस निष्कर्ष पर पहुंचे हैं कि यह निश्चित समय पर हमारे लिए सुविधाजनक है कि टाइमर, तुलना मॉड्यूल के साथ मिलकर, स्वतंत्र रूप से माइक्रोकंट्रोलर के पैर के स्तर को विपरीत - इस प्रकार उत्पन्न करने वाली दालों को बदल देता है।दूसरा काम इन दालों के बीच के अंतराल को निर्धारित करना है - अर्थात पीढ़ी की आवृत्ति को नियंत्रित करें। CTC मोड की संपूर्ण विशिष्टता इस तथ्य में निहित है कि इस मोड में टाइमर अंत (255) पर नहीं जाता है, लेकिन सेट वैल्यू तक पहुंचने पर रीसेट हो जाता है। तदनुसार, इस मान को बदलकर, हम वास्तव में आवृत्ति को नियंत्रित कर सकते हैं। उदाहरण के लिए, यदि हम तुलना मॉड्यूल के मान को 10 पर सेट करते हैं, तो माइक्रोकंट्रोलर के पैर में स्तर परिवर्तन 20 गुना अधिक बार होगा यदि हम इसे (तुलना मॉड्यूल के मूल्य) को 200 पर सेट करते हैं।
अब हम आवृत्ति को नियंत्रित कर सकते हैं!
लोहा

माइक्रोकंट्रोलर के पिनआउट से पता चलता है कि हमें अपने बजर को PB3 (OC2A) के पैर या PD3 (OC2B) के पैर से जोड़ने की जरूरत है, क्योंकि OC2A और OC2B का अर्थ है कि इन पैरों पर टाइमर 2 सिग्नल उत्पन्न कर सकता है।
आमतौर पर बजर को जोड़ने के लिए मैं जिस योजना का उपयोग करता हूं वह है:
और इसलिए हमने डिवाइस को इकट्ठा किया।रजिस्टरों
पिछले पैराग्राफ में, हमने पैर की पसंद पर फैसला किया - यह PB3 (OC2A) है, हम इसके साथ काम करेंगे। यदि आपको पीडी 3 की आवश्यकता है, तो उसके लिए सब कुछ वही होगा, जो कहानी से स्पष्ट रूप से दिखाई देगा।
हम 3 रजिस्टरों को बदलकर अपने टाइमर 2 को कॉन्फ़िगर करेंगे:
- TCCR2A - मोड सेटिंग्स और व्यवहार चयन
- TCCR2B - मोड सेटिंग्स और टाइमर आवृत्ति विभक्त (FOC बिट्स - हम उनका उपयोग नहीं करते हैं)
- OCR2A (PD3 लेग केस के लिए OCR2B) - तुलना मॉड्यूल का मूल्य
पहले TCCR2A और TCCR2B रजिस्टर पर विचार करें

जैसा कि आप देख सकते हैं, हमारे पास बिट्स के 3 समूह हैं जो हमारे लिए महत्वपूर्ण हैं - ये COM2xx, WGM2x और CS2x श्रृंखला के बिट्स हैं
WGM2x को बदलने के लिए हमें जो पहली चीज़ चाहिए वह है - जेनरेशन मोड को चुनने की यह मुख्य चीज़ है - इन बिट्स का उपयोग हमारे CTC मोड को चुनने के लिए किया जाता है।
ध्यान दें: जाहिर है LH में "OCR0x का अपडेट" में टाइपो OCR2x होना चाहिएयानी कोड इस तरह होगा:
TCCR2A = _BV(WGM21) ;
जैसा कि आप देख सकते हैं, TCCR2B अभी तक उपयोग नहीं किया गया है। WGM22 शून्य होना चाहिए, लेकिन यह पहले से ही शून्य है।
अगला कदम COM2xx बिट्स को कॉन्फ़िगर करना है, अधिक सटीक COM2Ax - क्योंकि हम पैर के साथ काम करते हैं PB3 (PD3 COM2Bx के लिए उसी तरह से उपयोग किया जाता है)। हमारे पीबी 3 लेग का क्या होगा यह उन पर निर्भर करता है।
COM2xx बिट्स उस मोड पर निर्भर करता है जिसे हमने WGM2x बिट्स के साथ चुना था, इसलिए हमें LH में संबंधित सेक्शन को खोजना होगा। क्योंकि हमारे पास CTC मोड है, अर्थात पीडब्लूएम नहीं, तो हम एक प्लेट की तलाश कर रहे हैं "आउटपुट मोड की तुलना करें, गैर-पीडब्लूएम", यहां यह है:

यहां आपको "टॉगल" का चयन करने की आवश्यकता है - ताकि टाइमर के सेट मान तक पहुंचने पर पैर का स्तर विपरीत में बदल जाए। लगातार स्तर परिवर्तन और हमारे द्वारा आवश्यक आवृत्ति की पीढ़ी को लागू करता है।
क्योंकि COM2xx बिट्स TCCR2A रजिस्टर में भी हैं - केवल यह बदलता है:
TCCR2A = _BV(COM2A0) | _BV(WGM21) ;
स्वाभाविक रूप से, आपको CS2x बिट्स के साथ फ़्रीक्वेंसी डिवाइडर का चयन करना होगा, और निश्चित रूप से, PB3 फ़ुट को आउटपुट पर सेट करना होगा ... लेकिन हमने ऐसा अभी तक नहीं किया है कि जब हम MK को चालू करते हैं, तो हमें एक समझ से बाहर आवृत्ति पर पियर्स स्क्रीच नहीं मिलती है, लेकिन जब हम अन्य सभी सेटिंग्स और करते हैं। बाहर निकलने के लिए पैर चालू करें - नीचे वर्णित किया जाएगा।
तो आइए, हमारे आरंभ को पूर्ण रूप में लाएँ:
#include <avr/io.h> //set bit - using bitwise OR operator #define sbi(x,y) x |= _BV(y) //clear bit - using bitwise AND operator #define cbi(x,y) x &= ~(_BV(y)) #define BUZ_PIN PB3 void timer2_buzzer_init() { // PB3 cbi(PORTB, BUZ_PIN); // PB3 , cbi(DDRB, BUZ_PIN); // TCCR2A = _BV(COM2A0) | _BV(WGM21) ; // ( ) OCR2A = 0; }
मैंने अलग-अलग बिट्स सेट करने के लिए cbi और sbi macros (नेटवर्क पर कहीं भी जासूसी) का इस्तेमाल किया, और इसे इस तरह छोड़ दिया। बेशक, इन मैक्रोज़ को हेडर फ़ाइल में रखा गया है, लेकिन स्पष्टता के लिए, मैंने उन्हें यहाँ रखा है।
नोटों की आवृत्ति और अवधि की गणना
अब हम मुद्दे के बहुत सार पर आते हैं। कुछ समय पहले, संगीतकारों के परिचितों ने मेरे प्रोग्रामर के मस्तिष्क में एक संगीत स्टाफ के बारे में कुछ निश्चित जानकारी चलाने की कोशिश की, मेरा दिमाग लगभग उबल गया, लेकिन फिर भी मैंने इन वार्तालापों से एक उपयोगी अनाज निकाला।
मैं आपको तुरंत चेतावनी देता हूं - विशाल अशुद्धियां संभव हैं।
- प्रत्येक माप में 4 क्वार्टर होते हैं
- प्रत्येक राग में एक टेम्पो होता है - अर्थात प्रति मिनट ऐसे क्वार्टरों की संख्या
- प्रत्येक नोट को पूरे संपूर्ण माप के साथ-साथ इसके भाग 1/2, 1/3, 1/4, आदि के रूप में खेला जा सकता है।
- प्रत्येक नोट, निश्चित रूप से, एक निश्चित आवृत्ति है
हमने सबसे सामान्य मामले की जांच की, वास्तव में, वहां सब कुछ अधिक जटिल है, कम से कम मेरे लिए, इसलिए मैं इस विषय पर इस कहानी के ढांचे में चर्चा नहीं करूंगा।
ठीक है, ठीक है, हमारे पास जो भी है उसके साथ काम करेंगे। हमारे लिए सबसे महत्वपूर्ण बात अंततः नोट की आवृत्ति प्राप्त करना है (वास्तव में, OCR2A रजिस्टर का मूल्य) और इसकी अवधि, उदाहरण के लिए, मिलीसेकंड में। तदनुसार, कुछ गणना करना आवश्यक है।
क्योंकि हम एक प्रोग्रामिंग भाषा के ढांचे के भीतर हैं, धुनों को आसानी से एक सरणी में संग्रहीत किया जाता है। प्रारूप में सरणी के प्रत्येक तत्व को सेट करने का सबसे तार्किक तरीका नोट + अवधि है। बाइट्स में तत्व के आकार की गणना करना आवश्यक है, क्योंकि हम माइक्रोकंट्रोलर के नीचे लिखते हैं और यहां संसाधनों के साथ यह तंग है - इसका मतलब है कि बाइट्स में तत्व का आकार पर्याप्त होना चाहिए।
आवृत्ति
चलो आवृत्ति के साथ शुरू करते हैं। क्योंकि हमारे पास 8-बिट टाइमर 2 है, OCR2A तुलना रजिस्टर भी 8-बिट है। यही है, माधुर्य सरणी का हमारा तत्व पहले से ही कम से कम 2 बाइट्स होगा, क्योंकि आपको अभी भी अवधि को बचाने की आवश्यकता है। वास्तव में, इस तरह के शिल्प के लिए 2 बाइट्स की सीमा है। हम अभी भी इसे अच्छी तरह से लगाने के लिए एक अच्छी आवाज नहीं पाते हैं, और अधिक बाइट खर्च करना अनुचित है।
तो, हम 2 बाइट्स पर रुक गए।आवृत्ति की गणना करते समय, वास्तव में, एक और बड़ी समस्या सामने आती है।यदि आप नोटों की आवृत्तियों को देखते हैं, तो हम देखेंगे कि वे सप्तक में विभाजित हैं।

सबसे सरल धुनों के लिए, 3 सप्तक पर्याप्त हैं, लेकिन मैंने 6: बड़े, छोटे और अगले 4 को चकमा देने और लागू करने का फैसला किया।
अब आइए संगीत से हटें और माइक्रोकंट्रोलर प्रोग्रामिंग की दुनिया में वापसी करें।
AVR (और अन्य MK के विशाल बहुमत) में कोई भी टाइमर MK की आवृत्ति से बंधा हुआ है। मेरे सर्किट में क्वार्ट्ज की आवृत्ति 16Mhz है। उसी आवृत्ति को F_CPU द्वारा परिभाषित किया गया है "परिभाषित" मेरे मामले में 16000000 के बराबर है। TCCR2B रजिस्टर में, हम आवृत्ति डिवाइडर का चयन कर सकते हैं ताकि हमारा टाइमर 2 प्रति सेकंड 16x00 बार की उन्मत्त गति से "टिक" न करे, लेकिन थोड़ा धीमा। आवृत्ति विभक्त का चयन CS2x बिट्स द्वारा किया जाता है, जैसा कि ऊपर बताया गया है।
नोट: जाहिर है LH में "CA2x" के बजाय एक टाइपो CS2x होना चाहिएसवाल उठता है - डिवाइडर को कैसे कॉन्फ़िगर करें?
ऐसा करने के लिए, आपको यह समझने की आवश्यकता है कि ओसीआर 2 ए रजिस्टर के लिए मूल्यों की गणना कैसे करें। और इसकी गणना करना काफी सरल है:
OCR2A = F_CPU / (क्वार्ट्ज फ्रीक्वेंसी डिवाइडर * 2) / नोट फ्रीक्वेंसीउदाहरण के लिए, नोट को पहले सप्तक और भाजक 256 (CS22 = 1, CS21 = 1, CS20 = 0) से पहले लें:
OCR2A = 16000000 / (256 * 2) / 261 = 119
मैं तुरंत समझाऊंगा कि 2 का गुणन कहाँ से आया है। तथ्य यह है कि हमने COM2Ax रजिस्टरों के साथ "टॉगल" मोड का चयन किया है, जिसका अर्थ है कि पैर पर निम्न से उच्च (या इसके विपरीत और पीछे के स्तर में परिवर्तन टाइमर के 2 पास में होगा: पहला टाइमर OCR2A के मान तक पहुंचता है और 1 से 0 तक, उदाहरण के लिए, माइक्रोकंट्रोलर के पैर को बदल देता है, रीसेट किया जाता है और केवल दूसरी लैप पर 0 से 1. बदल जाता है। इसलिए, टाइमर के 2 लैप प्रत्येक पूर्ण तरंग के लिए जाते हैं, क्रमशः, विभाजक को 2 से गुणा किया जाना चाहिए, अन्यथा हम केवल प्राप्त करते हैं हमारे नोट की आधी आवृत्ति।
इसलिए उपर्युक्त दुर्भाग्य ...
अगर हम नोट को बड़े ऑक्टेव से पहले ले जाते हैं और डिविज़र 256 छोड़ देते हैं:
OCR2A = 16000000 / (256 * 2) / 65 = 480 !!!
480 - यह संख्या स्पष्ट रूप से 255 से अधिक है और शारीरिक रूप से 8-बिट OCR2A रजिस्टर में फिट नहीं होती है।क्या करें? स्पष्ट रूप से डिवाइडर को बदलना, लेकिन अगर हम 1024 को विभाजित करते हैं, तो एक बड़े सप्तक के साथ, सब कुछ ठीक हो जाएगा। ऊपरी सप्तक के साथ समस्याएं शुरू होंगी:
LA 4th Octave - OCR2A = 16000000 / (1024 * 2) / 3520 = 4
एक तेज चौथा सप्तक - ओसीआर 2 ए = 16000000 / (1024 * 2) / 3729 = 4
OCR2A मान अब अलग नहीं हैं, जिसका अर्थ है कि ध्वनि भी अलग होना बंद कर देगी।केवल एक ही रास्ता है: नोटों की आवृत्ति के लिए, आपको न केवल ओसीआर 2 ए रजिस्टर के मूल्यों को संग्रहीत करने की आवश्यकता है, बल्कि क्वार्ट्ज आवृत्ति डिवाइडर के बिट्स भी हैं। क्योंकि विभिन्न सप्तक के लिए क्वार्ट्ज आवृत्ति विभक्त का एक अलग मूल्य होगा, जिसे हमें TCCR2B रजिस्टर में सेट करना होगा!अब सबकुछ ठीक हो गया है - और मैंने आखिरकार बताया कि हम टाइमर 2_buzzer_init () फ़ंक्शन में विभाजक मान को तुरंत क्यों नहीं भर सकते हैं।
दुर्भाग्य से, आवृत्ति विभक्त 3 अधिक बिट्स है। और उन्हें मेलोडी एरे के तत्व के दूसरे बाइट में लेना होगा।
लंबे समय तक मैक्रोज़ रहते हैं #define DIV_MASK (_BV(CS20) | _BV(CS21) | _BV(CS22)) #define DIV_1024 (_BV(CS20) | _BV(CS21) | _BV(CS22)) #define DIV_256 (_BV(CS21) | _BV(CS22)) #define DIV_128 (_BV(CS20) | _BV(CS22)) #define DIV_64 _BV(CS22) #define DIV_32 (_BV(CS20) | _BV(CS21)) #define NOTE_1024( x ) ((F_CPU / (1024 * 2) / x) | (DIV_1024 << 8)) #define NOTE_256( x ) ((F_CPU / (256 * 2) / x) | (DIV_256 << 8)) #define NOTE_128( x ) ((F_CPU / (128 * 2) / x) | (DIV_128 << 8)) #define NOTE_64( x ) ((F_CPU / (64 * 2) / x) | (DIV_64 << 8)) #define NOTE_32( x ) ((F_CPU / (32 * 2) / x) | (DIV_32 << 8))
और नोट की अवधि के लिए, हमारे पास केवल 5 बिट्स बचे हैं, तो चलो अवधि की गणना करें।
अवधि
पहले आपको अस्थायी इकाइयों में अस्थायी मान का अनुवाद करने की आवश्यकता है (उदाहरण के लिए, मिलीसेकंड में) - मैंने इसे इस तरह किया:
Ms = (60,000 ms * 4 तिमाहियों) / टेम्पो मूल्य में एक संगीत माप की अवधि।तदनुसार, यदि हम बीट भागों के बारे में बात कर रहे हैं, तो इस मूल्य को विभाजित करने की आवश्यकता है, और सबसे पहले मैंने सोचा था कि डिवाइडर के लिए सामान्य बाएं शिफ्ट पर्याप्त होगा। यानी कोड यह था:
uint16_t calc_note_delay(uint16_t precalced_tempo, uint16_t note) { return (precalced_tempo / _BV((note >> 11) & 0b00111)); }
यानी मैंने 3 बिट्स का इस्तेमाल किया (शेष 5 में से) और म्यूजिकल बीट के अंशों को 2 डिग्री से लेकर 1/128 तक मिला। लेकिन जब मैंने एक मित्र को मेरे लोहे के टुकड़े को किसी तरह की रिंगटोन लिखने के लिए कहा, तो सवाल थे कि कोई 1/3 या 1/6 वां क्यों नहीं है और मैं सोचने लगा ...
अंत में, मैंने इस तरह की अवधि प्राप्त करने के लिए एक मुश्किल प्रणाली बनाई। शेष 2x में से एक बिट - मैंने पाली के बाद प्राप्त घड़ी डिवाइडर के लिए 3 से गुणा के संकेत पर खर्च किया। और अंतिम बिट इंगित करना है कि क्या घटाना आवश्यक है 1. यह वर्णन करना मुश्किल है, कोड को देखना आसान है:
uint16_t calc_note_delay(uint16_t precalced_tempo, uint16_t note) { note >>= 11; uint8_t divider = _BV(note & 0b00111); note >>= 3; divider *= ((note & 0b01) ? 3 : 1); divider -= (note >> 1); return (precalced_tempo / divider); }
तब मैंने सभी संभावित (1/128 से कम वाले) नोटों को छोड़कर सभी को "परिभाषित" किया।
यहाँ वे हैं #define DEL_MINUS_1 0b10000 #define DEL_MUL_3 0b01000 #define DEL_1 0 #define DEL_1N2 1 #define DEL_1N3 (2 | DEL_MINUS_1) #define DEL_1N4 2 #define DEL_1N5 (1 | DEL_MINUS_1 | DEL_MUL_3) #define DEL_1N6 (1 | DEL_MUL_3) #define DEL_1N7 (3 | DEL_MINUS_1) #define DEL_1N8 3 #define DEL_1N11 (2 | DEL_MUL_3 | DEL_MINUS_1) #define DEL_1N12 (2 | DEL_MUL_3) #define DEL_1N15 (4 | DEL_MINUS_1) #define DEL_1N16 4 #define DEL_1N23 (3 | DEL_MUL_3 | DEL_MINUS_1) #define DEL_1N24 (3 | DEL_MUL_3) #define DEL_1N31 (5 | DEL_MINUS_1) #define DEL_1N32 5 #define DEL_1N47 (4 | DEL_MUL_3 | DEL_MINUS_1) #define DEL_1N48 (4 | DEL_MUL_3) #define DEL_1N63 (6 | DEL_MINUS_1) #define DEL_1N64 6 #define DEL_1N95 (5 | DEL_MUL_3 | DEL_MINUS_1) #define DEL_1N96 (5 | DEL_MUL_3) #define DEL_1N127 (7 | DEL_MINUS_1) #define DEL_1N128 7
यह सब एक साथ रखना
कुल, हमारे पास हमारे रिंगटोन सरणी के तत्व के लिए निम्न प्रारूप है।
- 1bit: देरी विभक्त - 1
- 1bit: देरी विभक्त * 3
- 3bit: देरी विभक्त पारी
- 3 बिट: सीपीयू घड़ी विभक्त
- 8bit: OCR2A मान
केवल 16 बिट्स।
प्रिय पाठक, यदि आप चाहें, तो प्रारूप पर स्वप्न देख सकते हैं, हो सकता है कि मेरी तुलना में कुछ अधिक क्षमता का जन्म हो।
हम एक खाली नोट जोड़ना भूल गए, अर्थात चुप्पी। और अंत में, मैंने बताया कि क्यों बहुत शुरुआत में, टाइमर 2_buzzer_init () फ़ंक्शन में, हम विशेष रूप से PB3 पैर इनपुट पर सेट करते हैं और आउटपुट नहीं। रजिस्टर DDRB को बदलते हुए, हम "साइलेंस" या कम्पोज़िशन के प्लेबैक को पूर्ण रूप से चालू और बंद कर देंगे। क्योंकि हमारे पास 0 के मान वाले नोट नहीं हो सकते हैं - यह एक "खाली" नोट होगा।
ध्वनि सृजन को सक्षम करने के लिए लापता मैक्रोज़ और फ़ंक्शन को परिभाषित करें:
#define EMPTY_NOTE 0 #define NOTE(delay, note) (uint16_t)((delay << 11) | note) ........ ........ ........ void play_music_note(uint16_t note) { if (note) { TCCR2B = (note >> 8) & DIV_MASK; OCR2A = note & 0xff; sbi(DDRB, BUZ_PIN); } else cbi(DDRB, BUZ_PIN); }
अब मैं आपको दिखाऊंगा कि इस सिद्धांत के अनुसार लिखी गई रिंगटोन कैसी दिखती है:
const uint16_t king[] PROGMEM = { NOTE(DEL_1N4, MI3), NOTE(DEL_1N4, FA_3), NOTE(DEL_1N4, SOL3), NOTE(DEL_1N4, LA3), NOTE(DEL_1N4, SI3), NOTE(DEL_1N4, SOL3), NOTE(DEL_1N2, SI3), NOTE(DEL_1N4, LA_3), NOTE(DEL_1N4, FA_3), NOTE(DEL_1N4, LA_3), NOTE(DEL_1N4, EMPTY_NOTE), NOTE(DEL_1N4, LA3), NOTE(DEL_1N4, FA3), NOTE(DEL_1N2, LA3), NOTE(DEL_1N4, MI3), NOTE(DEL_1N4, FA_3), NOTE(DEL_1N4, SOL3), NOTE(DEL_1N4, LA3), NOTE(DEL_1N4, SI3), NOTE(DEL_1N4, SOL3), NOTE(DEL_1N4, SI3), NOTE(DEL_1N4, MI4), NOTE(DEL_1N4, RE4), NOTE(DEL_1N4, SI3), NOTE(DEL_1N4, SOL3), NOTE(DEL_1N4, SI3), NOTE(DEL_1N2, RE4), NOTE(DEL_1N2, EMPTY_NOTE), };
रिंगटोन बजाना
हमारा अभी भी एक काम है - मेलोडी खेलना। ऐसा करने के लिए, हमें रिंगटोन सरणी के माध्यम से "रन" करने की आवश्यकता है, उचित ठहराव को समझना और नोटों की आवृत्तियों को स्विच करना। जाहिर है, हमें एक और टाइमर की आवश्यकता है, जो वैसे, अन्य सामान्य कार्यों के लिए उपयोग किया जा सकता है, जैसा कि मैं आमतौर पर करता हूं। इसके अलावा, आप इस टाइमर के रुकावट में या मुख्य लूप में सरणी तत्वों के बीच स्विच कर सकते हैं, और समय की गणना करने के लिए टाइमर का उपयोग कर सकते हैं। इस उदाहरण में, मैंने 2nd विकल्प का उपयोग किया।
जैसा कि आप जानते हैं, एमके के लिए किसी भी कार्यक्रम के शरीर में एक अनंत लूप शामिल है:
int main(void) { for(;;) {
इसमें हम अपने एरे के साथ "रन" करेंगे। लेकिन हमें WinApi से GetTickCount के समान एक फ़ंक्शन की आवश्यकता है, जो विंडोज ऑपरेटिंग सिस्टम पर मिलीसेकंड की संख्या लौटाता है। लेकिन स्वाभाविक रूप से, एमके की दुनिया में "बॉक्स से बाहर" ऐसे कोई कार्य नहीं हैं, इसलिए हमें इसे स्वयं लिखना होगा।
टाइमर 1
समय अंतराल की गणना करने के लिए (मैं जानबूझकर मिलीसेकंड नहीं लिखता हूं, बाद में आप समझ जाएंगे कि क्यों) मैंने पहले से ही ज्ञात सीटीसी मोड के साथ संयोजन में टाइमर 1 का उपयोग किया। टाइमर 1 एक 16-बिट टाइमर है, जिसका अर्थ है कि इसके लिए तुलना मॉड्यूल का मूल्य पहले से ही 2 8-बिट रजिस्टरों OCR1AH और OCR1AL द्वारा इंगित किया जाता है - क्रमशः उच्च और निम्न बाइट्स के लिए। मैं टाइमर 1 के साथ काम का विस्तार से वर्णन नहीं करना चाहता, क्योंकि यह इस ज्ञापन के मुख्य विषय पर लागू नहीं होता है। इसलिए, मैं आपको केवल 2 शब्दों में बताऊंगा।
हमें वास्तव में 3 कार्यों की आवश्यकता है:
- टाइमर प्रारंभिक
- टाइमर बाधित हैंडलर
- फ़ंक्शन जो समय अंतराल की संख्या लौटाता है।
कोड सी फ़ाइल #include <avr/io.h> #include <avr/interrupt.h> #include <util/atomic.h> #include "timer1_ticks.h" volatile unsigned long timer1_ticks; // ISR (TIMER1_COMPA_vect) { timer1_ticks++; } void timer1_ticks_init() { // // CTC , 8 TCCR1B |= (1 << WGM12) | (1 << CS11); // OCR1AH = (uint8_t)(CTC_MATCH_OVERFLOW >> 8); OCR1AL = (uint8_t) CTC_MATCH_OVERFLOW; // TIMSK1 |= (1 << OCIE1A); } unsigned long ticks() { unsigned long ticks_return; // , ticks_return // ATOMIC_BLOCK(ATOMIC_FORCEON) { ticks_return = timer1_ticks; } return ticks_return; }
इससे पहले कि मैं एक निश्चित स्थिर CTC_MATCH_OVERFLOW के साथ हेडर फ़ाइल दिखाऊं, हमें
"अवधि" खंड में समय पर वापस जाने की जरूरत है और माधुर्य के लिए मैक्रो को सबसे महत्वपूर्ण निर्धारित करना है, जो मेलोडी के टेम्पो की गणना करता है। मैंने इसे निर्धारित करने के लिए एक लंबा समय इंतजार किया, क्योंकि यह सीधे खिलाड़ी से जुड़ा हुआ है, और इसलिए टाइमर 1 के साथ।
पहले सन्निकटन में, यह इस तरह दिखता था ("अवधि" खंड में गणना देखें):
#define TEMPO( x ) (60000 * 4 / x)
मूल्य जो हमें आउटपुट पर मिलता है, हमें बाद में फ़ंक्शन में पहला तर्क देना चाहिए
calc_note_delay । अब इस पंक्ति के calc_note_delay फ़ंक्शन पर करीब से नज़र डालें:
return (precalced_tempo / divider);
हम देखते हैं कि TEMPO मैक्रो की गणना करके प्राप्त मूल्य एक निश्चित विभाजक द्वारा विभाजित है। याद रखें कि हमने जो अधिकतम विभाजक परिभाषित किया है वह
DEL_1N128 है , अर्थात विभाजक 128 होगा।
अब चलो आम टेम्पो वैल्यू को 240 के बराबर लेते हैं और कुछ सरल गणना करते हैं:
60000 * 4/240 = 1000अरे डरपोक! हमें केवल 1000 मिले, क्योंकि यह मूल्य अभी भी 128 से विभाजित होगा, हम उच्च दर पर 0 पर फिसलने का जोखिम चलाते हैं।
यह अवधि का दूसरा अंक है।इसे कैसे हल करें? जाहिर है, टेम्पो मूल्यों की सीमा का विस्तार करने के लिए, हमें किसी तरह TEMPO मैक्रो की गणना करके प्राप्त संख्या को बढ़ाने की आवश्यकता है। यह केवल एक ही तरीके से किया जा सकता है - मिलीसेकंड से दूर होने और निश्चित समय अंतराल में समय की गणना करने के लिए। अब आप समझते हैं कि इस बार मैंने कहानी में "मिलीसेकंड" का उल्लेख करने से परहेज क्यों किया। आइए एक और मैक्रो को परिभाषित करें:
#define MS_DIVIDER 4
इसे हमारे मिलिसेकंड का भाजक बनने दें - मिलीसेकंड को विभाजित करें, उदाहरण के लिए, 4 (250 μs)।
फिर आपको TEMPO मैक्रो बदलने की आवश्यकता है:
#define TEMPO( x ) (60000 * MS_DIVIDER * 4 / x)
अब, स्पष्ट विवेक के साथ, मैं टाइमर 1 के साथ काम करने के लिए हेडर फ़ाइल दूंगा:
#ifndef TIMER1_TICKS_H_INCLUDED #define TIMER1_TICKS_H_INCLUDED #define MS_DIVIDER 4 #define CTC_MATCH_OVERFLOW ((F_CPU / 1000) / (8 * MS_DIVIDER)) void timer1_ticks_init(); unsigned long ticks(); #endif
अब हम MS_DIVIDER को बदल सकते हैं, हमारे कार्यों के लिए सीमा को समायोजित कर सकते हैं - मेरे पास मेरे कोड में 4 हैं - यह मेरे कार्यों के लिए पर्याप्त था।
ध्यान दें: यदि आपके पास अभी भी टाइमर 1 के लिए "बंधे" कोई कार्य हैं, तो MS_DIVIDER द्वारा उनके लिए समय नियंत्रण मूल्यों को गुणा / विभाजित करना न भूलें।टर्नटेबल
अब हमारे खिलाड़ी लिखते हैं। मुझे लगता है कि कोड और टिप्पणियों से सबकुछ स्पष्ट हो जाएगा।
int main(void) { timer1_ticks_init();
निष्कर्ष
मुझे उम्मीद है कि यह ज्ञापन एक सम्मानित पाठक और खुद के लिए उपयोगी होगा, ताकि मैं धुनों को बजाने की सभी बारीकियों को भूल न जाऊं, अगर मैं फिर से एवीआर माइक्रोकंट्रोलर्स उठाऊं।
खैर, परंपरागत रूप से वीडियो और स्रोत कोड (मैंने इसे कोड ब्लॉक परिवेश में विकसित किया है, इसलिए अस्पष्ट फ़ाइलों से डरो मत):
स्रोत कोड