अंजीर। www.extremetech.com/wp-content/uploads/2016/07/MegaProcessor-Featre.ppg से लिया गयासभी को अच्छा स्वास्थ्य!
पिछले लेख में, मैंने C ++ में CortexM कोर के साथ एक माइक्रोकंट्रोलर के रजिस्टरों तक पहुंच के मुद्दे की जांच की और कुछ समस्याओं का सरल समाधान दिखाया।
आज मैं एसवीडी फाइलों से उत्पन्न सी ++ कक्षाओं का उपयोग करके, बिना दक्षता के बलिदान के रजिस्टर और उसके क्षेत्रों तक पहुंच बनाने के तरीके के बारे में विचार करना चाहता हूं।
हर कोई आप में रुचि रखते हैं, बिल्ली का स्वागत करते हैं। बहुत सारे कोड होंगे।
परिचय
C ++ हार्डवेयर रजिस्टर एक्सेस रेडक्स लेख में, केन स्मिथ ने रजिस्टरों के साथ सुरक्षित रूप से और कुशलता से काम करने का तरीका दिखाया और यहां तक कि इसे उदाहरण के रूप में
github.com/kensmith/cppmmio के साथ दिखाया।
फिर कई लोगों ने इस विचार को विकसित किया, उदाहरण के लिए,
निकल्स हौसर ने एक अद्भुत समीक्षा की और रजिस्टरों को सुरक्षित रूप से एक्सेस करने के कई और तरीके सुझाए।
इनमें से कुछ विचार पहले से ही विभिन्न पुस्तकालयों में लागू किए गए हैं, विशेष रूप से
मॉडम में । अच्छे के लिए, आप वास्तविक जीवन में इन सभी वाक्यों का उपयोग कर सकते हैं। लेकिन इन पुस्तकालयों के विकास के समय, बाह्य उपकरणों और रजिस्टरों के विवरणों का मानकीकरण होना शुरू हो गया था, और इसलिए कुछ चीजें इस उद्देश्य से की गई थीं कि रजिस्टरों का वर्णन करने का मुख्य कार्य प्रोग्रामर के पास होगा। इसके अलावा, कुछ समाधान कोड और माइक्रोकंट्रोलर संसाधनों के संदर्भ में प्रभावी नहीं हैं।
आज, एआरएम माइक्रोकंट्रोलर का प्रत्येक निर्माता एसवीडी प्रारूप में सभी रजिस्टरों का विवरण प्रदान करता है। इन विवरणों से हैडर फाइलें उत्पन्न की जा सकती हैं; इसलिए, रजिस्टरों का सरल विवरण नहीं, बल्कि अधिक जटिल एक, लेकिन एक ही समय में बनाना संभव है, जिससे आपके कोड की विश्वसनीयता बढ़ जाएगी। और यह बहुत अच्छा है कि आउटपुट फ़ाइल किसी भी भाषा, C, C ++, या
D में भी हो सकती है
लेकिन चलो इसे क्रम में लेते हैं जो आम तौर पर रजिस्टरों तक सुरक्षित पहुंच है, और यह बिल्कुल क्यों आवश्यक है। स्पष्टीकरण को सरल सिंथेटिक पर दिखाया जा सकता है, सबसे अधिक संभावना नहीं है, लेकिन काफी संभव उदाहरण हैं:
int main(void) {
ये सभी मामले व्यवहार में संभव हैं, और मैंने निश्चित रूप से अपने छात्रों से ऐसा कुछ देखा है। यह बहुत अच्छा होगा यदि आप ऐसी गलतियाँ करने से मना कर सकते हैं।
मुझे यह भी लगता है कि यह बहुत सुखद है जब कोड साफ दिखता है और टिप्पणियों की आवश्यकता नहीं होती है। उदाहरण के लिए, भले ही आप STM32F411 माइक्रोकंट्रोलर को अच्छी तरह से जानते हों, यह समझना हमेशा संभव नहीं होता है कि इस कोड में क्या हो रहा है:
int main() { uint32 temp = GPIOA->OSPEEDR ; temp &=~ GPIO_OSPEEDR_OSPEED0_Msk ; temp = (GPIO_OSPEEDR_OSPEED0_0 | GPIO_OSPEEDR_OSPEED0_1) ; GPIOA->OSPEEDR = temp; }
यहां कोई टिप्पणी नहीं कर सकता। कोड GPIOA.0 पोर्ट की ऑपरेटिंग आवृत्ति को अधिकतम (
mctMaks से स्पष्टीकरण)
सेट करता है : वास्तव में, यह पैरामीटर सामने के उदय के समय (यानी, इसकी स्थिरता) को प्रभावित करता है, और इसका मतलब है कि पोर्ट किसी दिए गए (VeryLow \ Low \ Medium) पर सामान्य रूप से डिजिटल सिग्नल को संसाधित कर सकता है उच्च आवृत्ति)।
आइए इन कमियों से छुटकारा पाने की कोशिश करें।
रजिस्टर अमूर्तता
सबसे पहले, आपको यह पता लगाने की आवश्यकता है कि प्रोग्रामर और प्रोग्राम के दृष्टिकोण से रजिस्टर क्या है।
रजिस्टर में एक पता, लंबाई या आकार, एक्सेस मोड है: कुछ रजिस्टरों को लिखा जा सकता है, कुछ को केवल पढ़ा जा सकता है, और अधिकांश को पढ़ा और लिखा जा सकता है।
इसके अलावा, रजिस्टर को खेतों के एक समूह के रूप में दर्शाया जा सकता है। एक फ़ील्ड में एक बिट या कई बिट्स शामिल हो सकते हैं और रजिस्टर में कहीं भी स्थित हो सकते हैं।
इसलिए, निम्नलिखित फ़ील्ड विशेषताएँ हमारे लिए महत्वपूर्ण हैं: लंबाई या आकार (
चौड़ाई या
आकार ), रजिस्टर की शुरुआत से
ऑफसेट (
ऑफसेट ), और मूल्य।
फ़ील्ड मान सभी संभव मात्राओं का स्थान है जो फ़ील्ड ले सकता है और यह फ़ील्ड की लंबाई पर निर्भर करता है। यानी यदि फ़ील्ड की लंबाई 2 है, तो 4 संभावित फ़ील्ड मान (0,1,2,3) हैं। रजिस्टर की तरह, फ़ील्ड और फ़ील्ड मान में एक एक्सेस मोड है (पढ़ें, लिखना, पढ़ना और लिखना)
इसे स्पष्ट करने के लिए, आइए STM32F411 माइक्रोकंट्रोलर से TIM1 CR1 रजिस्टर लें। योजनाबद्ध रूप से, यह इस तरह दिखता है:

- बिट 0 CEN: काउंटर सक्षम करें
0: काउंटर सक्षम: अक्षम करें
1: काउंटर बंद: सक्षम करें
- UDIS बिट 1: UEV इवेंट को सक्षम / अक्षम करें
0: UEV इवेंट सक्षम: सक्षम करें
1: UEV इवेंट बंद: अक्षम करें
- URS बिट 2: UEV इवेंट जनरेशन सोर्स का चयन करें
0: UGV ओवरफ्लो होने पर या UG सेट करते समय उत्पन्न होता है: कोई भी बिट
1: UEV केवल अतिप्रवाह पर उत्पन्न होता है: अतिप्रवाह
- बिट 3 ओपीएम: वन टाइम ऑपरेशन
0: टाइमर UEV के बाद आगे की गिनती जारी रखता है: ContinueAfterUEV घटना
1: टाइमर UEV: StopAfterUEV घटना के बाद बंद हो जाता है
- बिट 4 डीआईआर: गणना दिशा
0: डायरेक्ट अकाउंट: एनकाउंटर
1: काउंट डाउन : डाउनकाउंटर
- बिट 6: 5 सीएमएस: संरेखण मोड
0: संरेखण मोड 0: CenterAlignedMode0
1: संरेखण मोड 1: CenterAlignedMode1
2: संरेखण मोड 2: CenterAlignedMode2
3: संरेखण मोड 3: CenterAlignedMode3
- बिट 7 एपीआरई: एआरआर रजिस्टर के लिए प्रीलोड मोड
0: TIMx_ARR रजिस्टर बफ़र्ड नहीं है: ARRNotBuffered
1: TIMx_ARR रजिस्टर बफ़र्ड नहीं है: ARRBuffered
- बिट 8: 9 सीकेडी: क्लॉक डिवाइडर
0: tDTS = tCK_INT: ClockDevidedBy1
1: tDTS = 2 * tCK_INT: ClockDevidedBy2
2: tDTS = 4 * tCK_INT: ClockDevidedBy4
3: आरक्षित: आरक्षित
यहाँ, उदाहरण के लिए, CEN रजिस्टर की शुरुआत के लिए 0 के ऑफसेट के साथ 1-बिट फ़ील्ड है। और
Enable (1) और
Disable (0) इसके संभावित मान हैं।
हम इस बात पर ध्यान केंद्रित नहीं करेंगे कि इस रजिस्टर का प्रत्येक क्षेत्र किसके लिए विशेष रूप से जिम्मेदार है, यह हमारे लिए महत्वपूर्ण है कि प्रत्येक फ़ील्ड और फ़ील्ड मान में एक नाम होता है जो सिमेंटिक लोड करता है और जिससे हम सिद्धांत रूप में समझ सकते हैं कि यह क्या करता है।
हमारे पास रजिस्टर और फ़ील्ड और उसके मूल्य दोनों तक पहुंच होनी चाहिए। इसलिए, एक बहुत ही अनुमानित रूप में, रजिस्टर अमूर्त को निम्नलिखित वर्गों द्वारा दर्शाया जा सकता है:

कक्षाओं के अलावा, यह हमारे लिए भी महत्वपूर्ण है कि रजिस्टर और व्यक्तिगत क्षेत्रों में कुछ गुण होते हैं, रजिस्टर में एक पता, आकार, एक्सेस मोड (रीड-ओनली, राइट-ओनली, या दोनों) होता है।
फ़ील्ड में आकार, ऑफसेट और एक्सेस मोड भी है। इसके अलावा, फ़ील्ड में उस रजिस्टर का लिंक होना चाहिए, जो उसका है।
फ़ील्ड मान में फ़ील्ड का लिंक और एक अतिरिक्त विशेषता होना चाहिए - मान।
इसलिए, अधिक विस्तृत संस्करण में, हमारा अमूर्त इस तरह दिखेगा:

विशेषताओं के अलावा, हमारे अमूर्त में संशोधन और पहुंच के तरीके होने चाहिए। सादगी के लिए, हम खुद को स्थापना / लेखन और पढ़ने के तरीकों तक सीमित रखते हैं।

जब हमने केस एब्स्ट्रेक्शन पर निर्णय लिया, तो हमें यह जाँचने की आवश्यकता है कि यह एब्सट्रैक्शन एसवीडी फ़ाइल में वर्णित किस प्रकार है।
सिस्टम दृश्य विवरण (SVD) फ़ाइल
CMSIS सिस्टम प्रस्तुति विवरण प्रारूप (CMSIS-SVD) ARM Cortex-M प्रोसेसर पर आधारित माइक्रोकंट्रोलर रजिस्टरों का एक औपचारिक विवरण है। सिस्टम प्रतिनिधित्व के विवरण में निहित जानकारी, व्यावहारिक रूप से उपकरणों के लिए संदर्भ मैनुअल में डेटा से मेल खाती है। इस तरह की फ़ाइल में रजिस्टरों का वर्णन उच्च-स्तरीय जानकारी और रजिस्टर में फ़ील्ड के एक बिट के उद्देश्य दोनों को शामिल कर सकता है।
योजनाबद्ध रूप से, ऐसी फ़ाइल में जानकारी के स्तर का वर्णन निम्नलिखित योजना द्वारा किया जा सकता है,
जिसे केइल वेबसाइट पर लिया गया है :

विवरण SVD फाइलें निर्माताओं द्वारा आपूर्ति की जाती हैं और माइक्रोकंट्रोलर और रजिस्टरों के बारे में जानकारी प्रदर्शित करने के लिए डिबगिंग के दौरान उपयोग की जाती हैं। उदाहरण के लिए, IAR उन्हें व्यू-> रजिस्टर पैनल में जानकारी प्रदर्शित करने के लिए उपयोग करता है। फ़ाइलें स्वयं फ़ोल्डर प्रोग्राम फ़ाइलें (x86) \ IAR सिस्टम \ एम्बेडेड कार्यक्षेत्र 8.3 \ arm \ config \ डीबगर में हैं।
JetBrains से Clion भी डीबगिंग के दौरान रजिस्टर जानकारी प्रदर्शित करने के लिए svd फ़ाइलों का उपयोग करता है।
आप हमेशा निर्माता की साइटों से विवरण डाउनलोड कर सकते हैं।
यहां आप STM32F411 माइक्रोकंट्रोलर के लिए SVD फाइल ले सकते हैंसामान्य तौर पर, एसवीडी प्रारूप एक मानक है जो निर्माताओं का समर्थन करता है। आइए देखें कि एसवीडी में विवरण स्तर क्या हैं।
कुल मिलाकर, 5 स्तर प्रतिष्ठित हैं, डिवाइस स्तर, माइक्रोकंट्रोलर स्तर, रजिस्टर स्तर, फ़ील्ड स्तर, प्रगणित मूल्यों का स्तर।
- डिवाइस स्तर : सिस्टम दृश्य का शीर्ष स्तर का वर्णन डिवाइस है। इस स्तर पर, संपूर्ण रूप से डिवाइस से संबंधित गुणों का वर्णन किया गया है। उदाहरण के लिए, एक उपकरण का नाम, विवरण या संस्करण। न्यूनतम पता योग्य इकाई, साथ ही डेटा बस की थोड़ी गहराई। रजिस्टर विशेषताओं के लिए डिफ़ॉल्ट मान, जैसे कि रजिस्टर आकार, रीसेट मान, और एक्सेस अनुमतियाँ, इस स्तर पर पूरे डिवाइस के लिए सेट की जा सकती हैं और अनुमानित रूप से विवरण के निम्न स्तर से विरासत में मिली हैं।
- माइक्रोकंट्रोलर स्तर: सीपीयू अनुभाग माइक्रोकंट्रोलर की कोर और इसकी विशेषताओं का वर्णन करता है। यदि डिवाइस हेडर फ़ाइल बनाने के लिए SVD फ़ाइल का उपयोग किया जाता है तो यह अनुभाग आवश्यक है।
- परिधीय परत : एक परिधीय उपकरण रजिस्टरों का एक नामित संग्रह है। एक परिधीय उपकरण को डिवाइस के पता स्थान में एक विशिष्ट आधार पते पर मैप किया जाता है।
- रजिस्टर स्तर : एक रजिस्टर एक नामित प्रोग्राम संसाधन है जो एक परिधीय उपकरण के अंतर्गत आता है। रजिस्टरों को डिवाइस के एड्रेस स्पेस में एक विशिष्ट पते पर मैप किया जाता है। यह पता आधार परिधीय पते के सापेक्ष है। इसके अलावा, रजिस्टर के लिए, एक्सेस मोड (रीड / राइट) इंगित किया गया है।
- फ़ील्ड स्तर : जैसा कि ऊपर उल्लेख किया गया है, रजिस्टरों को विभिन्न कार्यक्षमता - फ़ील्ड के बिट्स के टुकड़ों में विभाजित किया जा सकता है। इस स्तर में फ़ील्ड्स के नाम हैं जो एक ही रजिस्टर के भीतर अद्वितीय हैं, उनका आकार, रजिस्टर की शुरुआत के सापेक्ष, साथ ही एक्सेस मोड भी है।
- प्रगणित फ़ील्ड मानों का स्तर : वास्तव में, इन्हें फ़ील्ड मानों का नाम दिया जाता है जिनका उपयोग C, C ++, D, और इसी तरह की सुविधा के लिए किया जा सकता है।
वास्तव में, SVD फाइलें सिस्टम की पूरी जानकारी के साथ साधारण xml फाइलें होती हैं। उदाहरण के लिए,
यहां कोड के लिए svd फ़ाइल कन्वर्टर्स
हैं , जो प्रत्येक परिधि के लिए सी-फ्रेंडली हेडर और स्ट्रक्चर उत्पन्न करते हैं और रजिस्टर करते हैं।
Phyton में एक
cmsis-svd SVD फ़ाइल पार्सर भी लिखा है जो Phython वर्ग की वस्तुओं में एक फ़ाइल से डेटा deserializing जैसा कुछ करता है, जो तब आपके कोड जनरेशन प्रोग्राम में आसानी से उपयोग किया जाता है।
एसटीएम 32 एफ 411 माइक्रोकंट्रोलर के रजिस्टर के विवरण का एक उदाहरण स्पॉइलर के नीचे देखा जा सकता है:
उदाहरण रजिस्टर CR1 टाइमर TIM1 <peripheral> <name>TIM1</name> <description>Advanced-timers</description> <groupName>TIM</groupName> <baseAddress>0x40010000</baseAddress> <addressBlock> <offset>0x0</offset> <size>0x400</size> <usage>registers</usage> </addressBlock> <registers> <register> <name>CR1</name> <displayName>CR1</displayName> <description>control register 1</description> <addressOffset>0x0</addressOffset> <size>0x20</size> <access>read-write</access> <resetValue>0x0000</resetValue> <fields> <field> <name>CKD</name> <description>Clock division</description> <bitOffset>8</bitOffset> <bitWidth>2</bitWidth> </field> <field> <name>ARPE</name> <description>Auto-reload preload enable</description> <bitOffset>7</bitOffset> <bitWidth>1</bitWidth> </field> <field> <name>CMS</name> <description>Center-aligned mode selection</description> <bitOffset>5</bitOffset> <bitWidth>2</bitWidth> </field> <field> <name>DIR</name> <description>Direction</description> <bitOffset>4</bitOffset> <bitWidth>1</bitWidth> </field> <field> <name>OPM</name> <description>One-pulse mode</description> <bitOffset>3</bitOffset> <bitWidth>1</bitWidth> </field> <field> <name>URS</name> <description>Update request source</description> <bitOffset>2</bitOffset> <bitWidth>1</bitWidth> </field> <field> <name>UDIS</name> <description>Update disable</description> <bitOffset>1</bitOffset> <bitWidth>1</bitWidth> </field> <field> <name>CEN</name> <description>Counter enable</description> <bitOffset>0</bitOffset> <bitWidth>1</bitWidth> </field> </fields> </register> <register>
जैसा कि आप देख सकते हैं, खेतों के विशिष्ट बिट्स के मूल्यों के विवरण को छोड़कर, हमारे अमूर्त के लिए आवश्यक सभी जानकारी है।
सभी निर्माता अपने सिस्टम के पूर्ण विवरण पर समय नहीं बिताना चाहते हैं, इसलिए जैसा कि आप देख सकते हैं, एसटी क्षेत्र के मूल्यों का वर्णन नहीं करना चाहता था और इस बोझ को ग्राहक प्रोग्रामर को हस्तांतरित कर दिया था। लेकिन टीआई अपने ग्राहकों का ध्यान रखता है और सिस्टम का पूरी तरह से वर्णन करता है, जिसमें क्षेत्र मूल्यों का वर्णन भी शामिल है।
उपरोक्त दिखाता है कि एसवीडी विवरण का प्रारूप हमारे मामले में अमूर्तता के अनुरूप है। रजिस्टर में पूरी तरह से वर्णन करने के लिए फ़ाइल में सभी आवश्यक जानकारी है।
कार्यान्वयन
रजिस्टर
अब जब हमने रजिस्टर का एक अमूर्त बना दिया है और हमारे पास निर्माताओं से svd के रूप में रजिस्टरों का विवरण है जो आदर्श रूप से इस अमूर्त के लिए अनुकूल है, तो हम सीधे कार्यान्वयन पर जा सकते हैं।
हमारा कार्यान्वयन सी कोड और उपयोगकर्ता के अनुकूल के रूप में प्रभावी होना चाहिए। मैं चाहूंगा कि रजिस्टरों की पहुंच यथासंभव स्पष्ट हो, उदाहरण के लिए:
if (TIM1::CR1::CKD::DividedBy2::IsSet()) { TIM1::ARR::Set(10_ms) ; TIM1::CR1::CEN::Enable::Set() ; }
याद रखें कि पूर्णांक रजिस्टर पते का उपयोग करने के लिए, आपको reinterpret_cast का उपयोग करने की आवश्यकता है:
*reinterpret_cast<volatile uint32_t *>(0x40010000) = (1U << 5U) ;
रजिस्टर क्लास को पहले ही ऊपर वर्णित किया जा चुका है, इसमें एक पता, आकार और एक्सेस मोड, साथ ही दो
Get()
और
Set()
:
हम एड्रेस को पास करते हैं, टेम्प्लेट मापदंडों के लिए लंबाई और एक्सेस मोड रजिस्टर करते हैं (यह भी एक वर्ग है)।
SFINAE तंत्र का उपयोग करके, अर्थात्
enable_if
मेटाफ़ंक्शन, हम रजिस्टरों के लिए
Set()
या
Get()
एक्सेस फ़ंक्शंस को "बाहर फेंक देंगे" जो उनका समर्थन नहीं करना चाहिए। उदाहरण के लिए, यदि रजिस्टर केवल पढ़ने के लिए है, तो हम टेम्पलेट पैरामीटर
ReadMode
प्रकार को
ReadMode
करेंगे,
enable_if
यह जांच करेगा कि क्या एक्सेस
ReadMode
का उत्तराधिकारी है और यदि नहीं, तो यह एक नियंत्रित त्रुटि पैदा करेगा (टाइप टी प्रदर्शित नहीं किया जा सकता) और कंपाइलर
Set()
विधि को शामिल नहीं करेगा
Set()
ऐसे रजिस्टर के लिए। वही केवल लिखने के लिए अभिप्रेत रजिस्टर के लिए जाता है।
अभिगम नियंत्रण के लिए हम कक्षाओं का उपयोग करेंगे:
रजिस्टर विभिन्न आकारों में आते हैं: 8, 16, 32, 64 बिट्स। उनमें से प्रत्येक के लिए, हम अपना प्रकार निर्धारित करते हैं:
आकार के आधार पर रजिस्टरों का प्रकार template <uint32_t size> struct RegisterType {} ; template<> struct RegisterType<8> { using Type = uint8_t ; } ; template<> struct RegisterType<16> { using Type = uint16_t ; } ; template<> struct RegisterType<32> { using Type = uint32_t ; } ; template<> struct RegisterType<64> { using Type = uint64_t ; } ;
उसके बाद, TIM1 टाइमर के लिए, आप CR1 रजिस्टर को परिभाषित कर सकते हैं, उदाहरण के लिए, EGR रजिस्टर इस तरह से:
struct TIM1 { struct CR1 : public RegisterBase<0x40010000, 32, ReadWriteMode> { } struct EGR : public RegisterBase<0x40010014, 32, WriteMode> { } } int main() { TIM1::CR1::Set(10) ; auto reg = TIM1::CR1::Get() ;
चूंकि कंपाइलर केवल रजिस्टरों के लिए
Get()
विधि प्रदर्शित करता है जिसमें एक्सेस मोड
ReadMode
से विरासत में मिला है, और रजिस्टरों के लिए
Set()
तरीके जिसमें एक्सेस मोड
WriteMode
से विरासत में मिला है, एक्सेस विधियों के गलत उपयोग के मामले में, आपको संकलन चरण में एक त्रुटि प्राप्त होगी। और यदि आप आधुनिक विकास उपकरण, जैसे कि Clion का उपयोग करते हैं, तो कोडिंग चरण में भी आपको कोड विश्लेषक से एक चेतावनी दिखाई देगी:

खैर, रजिस्टरों तक पहुंच अब सुरक्षित हो गई है, हमारा कोड आपको उन चीजों को करने की अनुमति नहीं देता है जो इस रजिस्टर के लिए अस्वीकार्य हैं, लेकिन हम आगे जाना चाहते हैं और पूरे रजिस्टर को नहीं, बल्कि इसके क्षेत्रों को देखें।
खेतों
पते के बजाय फ़ील्ड में रजिस्टर की शुरुआत के सापेक्ष शिफ्ट वैल्यू है। इसके अलावा, उस पते या प्रकार को जानने के लिए जिसमें फ़ील्ड का मान लाया जाना चाहिए, उसके पास रजिस्टर का लिंक होना चाहिए:
उसके बाद निम्नलिखित चीजें करना पहले से ही संभव है:
struct TIM1 { struct CR1 : public RegisterBase<0x40010000, 32, ReadWriteMode> { using CKD = RegisterField<TIM1::CR1, 8, 2, ReadWriteMode> ; using ARPE = RegisterField<TIM1::CR1, 7, 1, ReadWriteMode> ; using CMS = RegisterField<TIM1::CR1, 5, 2, ReadWriteMode> ; using DIR = RegisterField<TIM1::CR1, 4, 1, ReadWriteMode> ; using OPM = RegisterField<TIM1::CR1, 3, 1, ReadWriteMode> ; using URS = RegisterField<TIM1::CR1, 2, 1, ReadWriteMode> ; using UDIS = RegisterField<TIM1::CR1, 1, 1, ReadWriteMode> ; using CEN = RegisterField<TIM1::CR1, 0, 1, ReadWriteMode> ; } } int main() {
हालाँकि सब कुछ बहुत अच्छा लग रहा है, फिर भी यह पूरी तरह से स्पष्ट नहीं है कि
TIM1::CR1::CKD::Set(2)
का क्या अर्थ है, मैजिक टू
Set()
फ़ंक्शन का क्या अर्थ है? और
TIM1::CR1::CEN::Get()
विधि द्वारा दी गई संख्या का क्या
TIM1::CR1::CEN::Get()
?
क्षेत्र के मूल्यों पर निर्बाध रूप से आगे बढ़ें।
फ़ील्ड मान
एक क्षेत्र मूल्य का अमूर्त अनिवार्य रूप से एक क्षेत्र है, लेकिन केवल एक राज्य को स्वीकार करने में सक्षम है। फील्ड एब्सट्रैक्शन में विशेषताएँ जोड़ी जाती हैं - वास्तविक मूल्य और फ़ील्ड की एक कड़ी। फ़ील्ड मान
Set()
करने का
Set()
विधि फ़ील्ड
Set()
करने के
Set()
विधि के समान है, सिवाय इसके कि मूल्य स्वयं विधि को पारित करने की आवश्यकता नहीं है, यह पहले से ज्ञात है, इसे बस सेट करने की आवश्यकता है। लेकिन
Get()
विधि का कोई मतलब नहीं है, इसके बजाय, यह जांचना बेहतर है कि यह मान सेट है या नहीं, इस विधि को
IsSet()
विधि से
IsSet()
।
रजिस्टर फ़ील्ड को अब इसके मूल्यों के एक सेट द्वारा वर्णित किया जा सकता है:
CR1 के मान टाइमर TIM1 के फ़ील्ड को पंजीकृत करते हैं template <typename Reg, size_t offset, size_t size, typename AccessMode> struct TIM_CR_CKD_Values: public RegisterField<Reg, offset, size, AccessMode> { using DividedBy1 = FieldValue<TIM_CR_CKD_Values, 0U> ; using DividedBy2 = FieldValue<TIM_CR_CKD_Values, 1U> ; using DividedBy4 = FieldValue<TIM_CR_CKD_Values, 2U> ; using Reserved = FieldValue<TIM_CR_CKD_Values, 3U> ; } ; template <typename Reg, size_t offset, size_t size, typename AccessMode> struct TIM_CR_ARPE_Values: public RegisterField<Reg, offset, size, AccessMode> { using ARRNotBuffered = FieldValue<TIM_CR_ARPE_Values, 0U> ; using ARRBuffered = FieldValue<TIM_CR_ARPE_Values, 1U> ; } ; template <typename Reg, size_t offset, size_t size, typename AccessMode> struct TIM_CR_CMS_Values: public RegisterField<Reg, offset, size, AccessMode> { using CenterAlignedMode0 = FieldValue<TIM_CR_CMS_Values, 0U> ; using CenterAlignedMode1 = FieldValue<TIM_CR_CMS_Values, 1U> ; using CenterAlignedMode2 = FieldValue<TIM_CR_CMS_Values, 2U> ; using CenterAlignedMode3 = FieldValue<TIM_CR_CMS_Values, 3U> ; } ; template <typename Reg, size_t offset, size_t size, typename AccessMode> struct TIM_CR_DIR_Values: public RegisterField<Reg, offset, size, AccessMode> { using Upcounter = FieldValue<TIM_CR_DIR_Values, 0U> ; using Downcounter = FieldValue<TIM_CR_DIR_Values, 1U> ; } ; template <typename Reg, size_t offset, size_t size, typename AccessMode> struct TIM_CR_OPM_Values: public RegisterField<Reg, offset, size, AccessMode> { using ContinueAfterUEV = FieldValue<TIM_CR_OPM_Values, 0U> ; using StopAfterUEV = FieldValue<TIM_CR_OPM_Values, 1U> ; } ; template <typename Reg, size_t offset, size_t size, typename AccessMode> struct TIM_CR_URS_Values: public RegisterField<Reg, offset, size, AccessMode> { using Any = FieldValue<TIM_CR_URS_Values, 0U> ; using Overflow = FieldValue<TIM_CR_URS_Values, 1U> ; } ; template <typename Reg, size_t offset, size_t size, typename AccessMode> struct TIM_CR_UDIS_Values: public RegisterField<Reg, offset, size, AccessMode> { using Enable = FieldValue<TIM_CR_UDIS_Values, 0U> ; using Disable = FieldValue<TIM_CR_UDIS_Values, 1U> ; } ; template <typename Reg, size_t offset, size_t size, typename AccessMode> struct TIM_CR_CEN_Values: public RegisterField<Reg, offset, size, AccessMode> { using Disable = FieldValue<TIM_CR_CEN_Values, 0U> ; using Enable = FieldValue<TIM_CR_CEN_Values, 1U> ; } ;
फिर CR1 रजिस्टर को पहले ही इस प्रकार वर्णित किया जाएगा:
struct TIM1 { struct CR1 : public RegisterBase<0x40010000, 32, ReadWriteMode> { using CKD = TIM_CR1_CKD_Values<TIM1::CR1, 8, 2, ReadWriteMode> ; using ARPE = TIM_CR1_ARPE_Values<TIM1::CR1, 7, 1, ReadWriteMode> ; using CMS = TIM_CR1_CMS_Values<TIM1::CR1, 5, 2, ReadWriteMode> ; using DIR = TIM_CR1_DIR_Values<TIM1::CR1, 4, 1, ReadWriteMode> ; using OPM = TIM_CR1_OPM_Values<TIM1::CR1, 3, 1, ReadWriteMode> ; using URS = TIM_CR1_URS_Values<TIM1::CR1, 2, 1, ReadWriteMode> ; using UDIS = TIM_CR1_UDIS_Values<TIM1::CR1, 1, 1, ReadWriteMode> ; using CEN = TIM_CR1_CEN_Values<TIM1::CR1, 0, 1, ReadWriteMode> ; } ; }
अब आप रजिस्टर फ़ील्ड के मान को सीधे सेट और पढ़ सकते हैं: उदाहरण के लिए, यदि आप खाते पर टाइमर को सक्षम करना चाहते हैं, तो टाइमर के CIM फ़ील्ड के
Enable
मान पर
Set()
विधि को कॉल
Set()
TIM1 के रजिस्टर CR1 के
Enable
मूल्य:
TIM1::CR1::CEN::Enable::Set() ;
। कोड में, यह इस तरह दिखेगा:
int main() { if (TIM1::CR1::CKD::DividedBy2::IsSet()) { TIM1::ARR::Set(100U) ; TIM1::CR1::CEN::Enable::Set() ; } }
तुलना के लिए, सी हेडर का उपयोग कर एक ही बात: int main() { if((TIM1->CR1 & TIM_CR1_CKD_Msk) == TIM_CR1_CKD_0) { TIM1->ARR = 100U ; regValue = TIM1->CR1 ; regValue &=~(TIM_CR1_CEN_Msk) ; regValue |= TIM_CR1_CEN ; TIM1->CR1 = regValue ; } }
इसलिए, मुख्य सुधार किए गए हैं, हमारे पास रजिस्टर, इसके क्षेत्र और मूल्यों तक सरल और समझने योग्य पहुंच हो सकती है। , , , , .
, . , :
int main() { uint32_t regValue = TIM1->CR1 ; regValue &=~(TIM_CR1_CKD_Msk | TIM_CR1_DIR) ; regValue |= (TIM_CR1_CEN | TIM_CR1_CKD_0 | TIM_CR1_CKD_0) ; TIM1->CR1 = regValue ; }
Set(...)
, , . यानी :
int main() {
चूंकि फ़ंक्शन के तर्कों की एक चर संख्या के साथ विकल्प हमेशा संकलक द्वारा अनुकूलित नहीं किया जाएगा और वास्तव में सभी मापदंडों को रजिस्टरों और स्टैक के माध्यम से स्थानांतरित किया जाएगा, जो रैम की गति और खर्चों को प्रभावित कर सकता है, मैंने दूसरा दृष्टिकोण चुना, जब सेट किए जाने वाले बिट्स के मुखौटा की गणना मंच पर होगी। संकलन।हम एक तर्कों का एक चर संख्या के साथ एक टेम्पलेट का उपयोग करेंगे। मूल्यों की सूची को प्रकारों की सूची के रूप में पारित किया जाता है:
रजिस्टर में हमें वांछित मूल्य निर्धारित करने के लिए:- रजिस्टरों में वांछित बिट्स को रीसेट करने के लिए, मानों के पूरे सेट से एक मुखौटा बनाने के लिए।
- मूल्यों के पूरे सेट से, वांछित बिट्स सेट करने के लिए एक मान उत्पन्न करें।
ये ऐसे जटिल तरीके होने चाहिए जो संकलन चरण में सभी आवश्यक कार्य करेंगे:
यह केवल सार्वजनिक तरीकों को परिभाषित करने के लिए बना हुआ है Set()
और IsSet()
:
लगभग सब कुछ, एक छोटी सी समस्या बनी हुई है, हम ऐसी मूर्खता कर सकते हैं: int main() {
जाहिर है, आपको किसी तरह जांचने की जरूरत है कि हमारे मूल्यों का सेट केस-संवेदी है, ऐसा करने के लिए यह काफी सरल है, बस टेम्पलेट पैरामीटर में एक अतिरिक्त प्रकार जोड़ें, चलो इसे कॉल करें FieldValueBaseType
। अब रजिस्टर और इस रजिस्टर में सेट किए जा सकने वाले फ़ील्ड दोनों के मान एक ही FieldValueBaseType
प्रकार के होने चाहिए :इस रजिस्टर में फ़ील्ड मान के स्वामित्व के लिए एक चेक जोड़ें template<uint32_t address, size_t size, typename AccessMode, typename FieldValueBaseType, typename ...Args> class Register { private:
एकाधिक फ़ील्ड मान सेट करते समय, SFINAE तंत्र फिर से जाँचता है कि क्या यह मान रजिस्टर फ़ील्ड के लिए अनुमत प्रकार के समान है, यदि हां, तो संकलक द्वारा विधि प्रदर्शित की जाती है, यदि नहीं, तो संकलन करते समय आपको एक त्रुटि मिलेगी।TIM1 के CR1 रजिस्टर का पूरा विवरण इस प्रकार होगा: struct TIM1 { struct TIM1CR1Base {} ; struct CR1 : public RegisterBase<0x40010000, 32, ReadWriteMode> { using CKD = TIM_CR_CKD_Values<TIM1::CR1, 8, 2, ReadWriteMode, TIM1CR1Base> ; using ARPE = TIM_CR_ARPE_Values<TIM1::CR1, 7, 1, ReadWriteMode, TIM1CR1Base> ; using CMS = TIM_CR_CMS_Values<TIM1::CR1, 5, 2, ReadWriteMode, TIM1CR1Base> ; using DIR = TIM_CR_DIR_Values<TIM1::CR1, 4, 1, ReadWriteMode, TIM1CR1Base> ; using OPM = TIM_CR_OPM_Values<TIM1::CR1, 3, 1, ReadWriteMode, TIM1CR1Base> ; using URS = TIM_CR_URS_Values<TIM1::CR1, 2, 1, ReadWriteMode, TIM1CR1Base> ; using UDIS = TIM_CR_UDIS_Values<TIM1::CR1, 1, 1, ReadWriteMode, TIM1CR1Base> ; using CEN = TIM_CR_CEN_Values<TIM1::CR1, 0, 1, ReadWriteMode, TIM1CR1Base> ; } ; }
तो, अब रजिस्टर फ़ील्ड के किसी भी मूल्य को अलग से बदलना और जांचना संभव है, आप एक साथ कई फ़ील्ड मान सेट और चेक कर सकते हैं। आप फ़ील्ड के मान बदल सकते हैं या प्राप्त कर सकते हैं, या एक ही समय में पूरी तरह से सुनिश्चित हो सकते हैं कि आप कुछ भी भ्रमित नहीं करेंगे और आप अमान्य ऑपरेशन नहीं कर पाएंगे, या रजिस्टर फ़ील्ड में गलत बिट लिख सकते हैं।अब चलो C में मूल संस्करण पर वापस आते हैं, जहाँ हमने बकवास का एक गुच्छा किया:और नए दृष्टिकोण के साथ ऐसा ही करने का प्रयास करें: int main(void) {
इन मामलों में से प्रत्येक में, हमें संकलन चरण में एक त्रुटि मिलती है, जो कि हमने प्राप्त की है।ठीक है, हमने रजिस्टर और उसके खेतों तक एक सुंदर, सुरक्षित पहुंच प्रदान की, लेकिन गति के बारे में क्या?गति
तुलना करने के लिए, हमारा दृष्टिकोण कितना इष्टतम है, हम C और C ++ कोड का उपयोग करेंगे जो घड़ी को पोर्ट ए में फीड करता है, तीन पोर्ट को आउटपुट मोड पर सेट करता है, और इन तीन पोर्ट में आउटपुट पोर्ट सेट करता है 1:सी कोड: int main() { uint32_t res = RCC->AHB2ENR; res &=~ RCC_AHB1ENR_GPIOAEN_Msk ; res |= RCC_AHB1ENR_GPIOAEN ; RCC->AHB2ENR = res ; res = GPIOA->MODER ; res &=~ (GPIO_MODER_MODER5 | GPIO_MODER_MODER4 | GPIO_MODER_MODER1) ; res |= (GPIO_MODER_MODER5_0 | GPIO_MODER_MODER4_0 | GPIO_MODER_MODER1_0) ; GPIOA->MODER = res ; GPIOA->BSRR = (GPIO_BSRR_BS5 | GPIO_BSRR_BS4 | GPIO_BSRR_BS1) ; return 0 ; }
C ++ कोड: int main() { RCC::AHB1ENR::GPIOAEN::Enable::Set() ; GPIOA::MODERPack< GPIOA::MODER::MODER5::Output, GPIOA::MODER::MODER4::Output, GPIOA::MODER::MODER1::Output>::Set() ; GPIOA::BSRRPack< GPIOA::BSRR::BS5::Set, GPIOA::BSRR::BS4::Set, GPIOA::BSRR::BS1::Set>::Write() ; return 0 ; }
IAR. : :
:

C++ :

18 , , .
, :

13 .
++ :

: , .
, . , ?
हमें रजिस्टरों के लिए विश्वसनीय, सुविधाजनक और त्वरित पहुंच मिली। एक सवाल बना हुआ है। सभी रजिस्टरों का वर्णन कैसे करें, माइक्रोकंट्रोलर के लिए सौ के नीचे भी हैं। यह सभी रजिस्टरों का वर्णन करने में कितना समय लगता है, क्योंकि आप इस तरह के एक नियमित काम में बहुत सारी गलतियां कर सकते हैं। हां, आपको इसे मैन्युअल रूप से करने की आवश्यकता नहीं है। इसके बजाय, हम एसवीडी फ़ाइल से कोड जनरेटर का उपयोग करेंगे, जो, जैसा कि मैंने लेख की शुरुआत में ऊपर संकेत दिया था, पूरी तरह से रजिस्टर अमूर्त को कवर करता है जिसे मैंने स्वीकार किया था।मैंने एक सहयोगी की स्क्रिप्ट को अंतिम रूप दिया, जो इस विचार के आधार पर, उसी के बारे में था, लेकिन फ़ील्ड मानों के लिए कक्षाओं के बजाय एनम का उपयोग करना थोड़ा आसान था। स्क्रिप्ट केवल परीक्षण और विचारों की जांच के लिए बनाई गई है, इसलिए यह इष्टतम नहीं है, लेकिन यह आपको ऐसा कुछ उत्पन्न करने की अनुमति देता है ।
स्क्रिप्ट की देखभाल कौन करता हैपरिणाम
नतीजतन, प्रोग्रामर का काम केवल उत्पन्न फ़ाइल को ठीक से कनेक्ट करना है। यदि आपको रजिस्टर का उपयोग करने की आवश्यकता है, तो gpioa या आरसीसी मॉड्यूल के बारे में कहें, आपको बस वांछित हेडर फ़ाइल को शामिल करना है: #include "gpioaregisters.hpp"
मैं दोहराता हूं, एसवीडी फ़ाइल निर्माता की वेबसाइट से डाउनलोड की जा सकती है, आप इसे विकास के माहौल से बाहर निकाल सकते हैं, इसे स्क्रिप्ट इनपुट में जमा कर सकते हैं और बस।लेकिन, जैसा कि मैंने ऊपर कहा, सभी निर्माता अपने उपभोक्ताओं की परवाह नहीं करते हैं, इसलिए, सभी को एसवीडी फ़ाइल में स्थानांतरण नहीं होता है, इस वजह से, एसटी माइक्रोकंट्रोलर के लिए सभी स्थानान्तरण इस तरह से पीढ़ी के बाद दिखते हैं: template <typename Reg, size_t offset, size_t size, typename AccessMode, typename BaseType> struct GPIOA_MODER_MODER_Values: public RegisterField<Reg, offset, size, AccessMode> { using Value0 = FieldValue<GPIOA_MODER_MODER_Values, BaseType, 0U> ; using Value1 = FieldValue<GPIOA_MODER_MODER_Values, BaseType, 1U> ; using Value2 = FieldValue<GPIOA_MODER_MODER_Values, BaseType, 2U> ; using Value3 = FieldValue<GPIOA_MODER_MODER_Values, BaseType, 3U> ; } ;
उस क्षण जब आपको उनका उपयोग करने की आवश्यकता होती है, तो आप प्रलेखन में देख सकते हैं और शब्दों को बदल सकते हैं, कुछ अधिक समझदारी के लिए: template <typename Reg, size_t offset, size_t size, typename AccessMode, typename BaseType> struct GPIOA_MODER_MODER_Values: public RegisterField<Reg, offset, size, AccessMode> { using Input = FieldValue<GPIOA_MODER_MODER_Values, BaseType, 0U> ; using Output = FieldValue<GPIOA_MODER_MODER_Values, BaseType, 1U> ; using Alternate = FieldValue<GPIOA_MODER_MODER_Values, BaseType, 2U> ; using Analog = FieldValue<GPIOA_MODER_MODER_Values, BaseType, 3U> ; } ;
.
, ST , 0.
, , enum .
, .
IAR 8.40.1
« Online GDB»PS:
putyavka RegisterField::Get()
Ryppka assert.
Typesafe Register Access in C++One Approach to Using Hardware Registers in C++SVD Description (*.svd) Format