संक्षिप्त उत्तर: मिलिस रोलओवर को "हैंडल" करने की कोशिश न करें, इसके बजाय रोलओवर-सुरक्षित कोड लिखें। ट्यूटोरियल से आपका उदाहरण कोड ठीक है। यदि आप सुधारात्मक उपायों को लागू करने के लिए रोलओवर का पता लगाने की कोशिश करते हैं, तो संभावना है कि आप कुछ गलत कर रहे हैं। अधिकांश Arduino कार्यक्रमों को केवल उन घटनाओं का प्रबंधन करना होता है, जो अपेक्षाकृत कम अवधि की होती हैं, जैसे कि 50 ms के लिए एक बटन को डीब्यू करना, या 12 घंटे के लिए हीटर चालू करना ... फिर, और यहां तक कि अगर प्रोग्राम को एक समय में चलाने के लिए है, मिलिस रोलओवर एक चिंता का विषय नहीं होना चाहिए।
प्रबंधन करने का सही तरीका (या बल्कि, प्रबंधन करने से बचना) रोलओवर समस्या को मॉड्यूलर अंकगणित के संदर्भ में दिए unsigned long
गए संख्या के
बारे में सोचना है । गणितीय रूप से झुकाव के लिए, इस अवधारणा के साथ कुछ परिचित प्रोग्रामिंग करते समय बहुत उपयोगी होते हैं। आप निक गैमन के लेख मिलिस () अतिप्रवाह ... एक बुरी चीज में कार्रवाई में गणित देख सकते हैं ? । जो लोग कम्प्यूटेशनल विवरणों के माध्यम से नहीं जाना चाहते हैं, मैं उनके बारे में सोचने का एक वैकल्पिक (उम्मीद से सरल) तरीका पेश करता हूं। यह इंस्टेंट और ड्यूरेशन के बीच सरल अंतर पर आधारित है । जब तक आपके परीक्षणों में केवल अवधि की तुलना शामिल होती है, आपको ठीक होना चाहिए।millis()
माइक्रोस पर ध्यान दें () : यहां कहा गया सब कुछ millis()
समान रूप से लागू होता है micros()
, इस तथ्य को छोड़कर कि micros()
हर 71.6 मिनट में रोल setMillis()
करता है , और नीचे दिए गए फ़ंक्शन को प्रभावित नहीं करता है micros()
।
इंस्टेंट, टाइमस्टैम्प और अवधि
समय के साथ काम करते समय, हमें कम से कम दो अलग-अलग अवधारणाओं के बीच अंतर करना होगा: उदाहरण और अवधि । एक समय अक्ष पर एक बिंदु है। एक अवधि एक समय अंतराल की लंबाई होती है, यानी उन अंतरालों के बीच की दूरी जो अंतराल की शुरुआत और अंत को परिभाषित करते हैं। इन अवधारणाओं के बीच का अंतर हमेशा रोजमर्रा की भाषा में बहुत तेज नहीं होता है। उदाहरण के लिए, यदि मैं कहता हूं कि " मैं पांच मिनट में वापस आ जाऊंगा ", तो " पांच मिनट " मेरी अनुपस्थिति की अनुमानित
अवधि है, जबकि " पांच मिनट में " तत्काल है
मेरे आने की भविष्यवाणी की। भेद को ध्यान में रखना महत्वपूर्ण है, क्योंकि यह रोलओवर समस्या से पूरी तरह से बचने का सबसे सरल तरीका है।
वापसी मूल्य millis()
को एक अवधि के रूप में व्याख्या किया जा सकता है: कार्यक्रम की शुरुआत से अब तक का समय। यह व्याख्या, हालांकि, मिलीसेक ओवरफ्लो होते ही टूट जाती है। यह millis()
एक टाइमस्टैम्प को वापस
करने के बारे में सोचने के लिए आम तौर पर कहीं अधिक उपयोगी है , अर्थात एक विशेष लेबल की पहचान करने वाला "लेबल"। यह तर्क दिया जा सकता है कि यह व्याख्या इन लेबल से अस्पष्ट है, क्योंकि वे हर 49.7 दिनों में पुन: उपयोग किए जाते हैं। हालांकि, यह एक समस्या है, शायद ही कभी: ज्यादातर एम्बेडेड अनुप्रयोगों में, 49.7 दिन पहले जो कुछ भी हुआ वह प्राचीन इतिहास है जिसकी हमें परवाह नहीं है। इस प्रकार, पुराने लेबल को पुनर्चक्रित करना एक मुद्दा नहीं होना चाहिए।
टाइमस्टैम्प की तुलना न करें
यह पता लगाने की कोशिश की जा रही है कि दो टाइमस्टैम्प्स में से कौन सा अन्य की तुलना में अधिक है, इसका कोई मतलब नहीं है। उदाहरण:
unsigned long t1 = millis();
delay(3000);
unsigned long t2 = millis();
if (t2 > t1) { ... }
ईमानदारी से, एक की स्थिति if ()
हमेशा सच होने की उम्मीद होगी । लेकिन यह वास्तव में असत्य होगा यदि मिल के दौरान बहुत अधिक हो
delay(3000)
। T1 और t2 को रिसाइकिल लेबल के रूप में सोचना त्रुटि से बचने का सबसे सरल तरीका है: लेबल t1 को स्पष्ट रूप से t2 से पहले इंस्टेंट को सौंपा गया है, लेकिन 49.7 दिनों में इसे भविष्य के इंस्टेंट पर फिर से असाइन किया जाएगा। इस प्रकार, टी 1 टी 2 से पहले और बाद में दोनों होता है । यह स्पष्ट करना चाहिए कि अभिव्यक्ति का t2 > t1
कोई मतलब नहीं है।
लेकिन, अगर ये केवल लेबल हैं, तो स्पष्ट सवाल यह है: हम उनके साथ कोई उपयोगी समय गणना कैसे कर सकते हैं? इसका उत्तर है: खुद को केवल दो गणनाओं तक सीमित करके, जो टाइमस्टैम्प के लिए समझ में आता है:
later_timestamp - earlier_timestamp
एक अवधि की पैदावार, अर्थात् पहले के तात्कालिक और बाद के तात्कालिक के बीच का समय समाप्त हो जाता है। यह टाइमस्टैम्प से जुड़े सबसे उपयोगी अंकगणितीय ऑपरेशन है।
timestamp ± duration
एक टाइमस्टैम्प की पैदावार करता है जो प्रारंभिक टाइमस्टैम्प के बाद (यदि +) का उपयोग करने से पहले या (यदि -) है। यह उतना उपयोगी नहीं है जितना लगता है, क्योंकि टाइमस्टैम्प का उपयोग केवल दो प्रकार की गणनाओं में किया जा सकता है ...
मॉड्यूलर अंकगणित के लिए धन्यवाद, इन दोनों को मिलिस रोल ओवर में ठीक काम करने की गारंटी है, कम से कम जब तक शामिल विलंब 49.7 दिनों से कम हो।
तुलना करना ठीक है
एक अवधि कुछ समय के अंतराल के दौरान केवल मिलीसेकंड की मात्रा होती है। जब तक हमें 49.7 दिनों से अधिक अवधि को संभालने की आवश्यकता नहीं है, तब तक कोई भी ऑपरेशन जो शारीरिक रूप से समझ में आता है, उसे भी समझ में आना चाहिए। हम, उदाहरण के लिए, एक आवृत्ति को कई बार प्राप्त करने के लिए एक आवृत्ति से गुणा कर सकते हैं। या हम यह जानने के लिए दो अवधि की तुलना कर सकते हैं कि कौन अधिक लंबी है। उदाहरण के लिए, यहां दो वैकल्पिक कार्यान्वयन हैं delay()
। सबसे पहले, छोटी गाड़ी एक:
void myDelay(unsigned long ms) { // ms: duration
unsigned long start = millis(); // start: timestamp
unsigned long finished = start + ms; // finished: timestamp
for (;;) {
unsigned long now = millis(); // now: timestamp
if (now >= finished) // comparing timestamps: BUG!
return;
}
}
और यहाँ सही है:
void myDelay(unsigned long ms) { // ms: duration
unsigned long start = millis(); // start: timestamp
for (;;) {
unsigned long now = millis(); // now: timestamp
unsigned long elapsed = now - start; // elapsed: duration
if (elapsed >= ms) // comparing durations: OK
return;
}
}
अधिकांश सी प्रोग्रामर ऊपर दिए गए लूपों को एक टेस्टर रूप में लिखते हैं, जैसे
while (millis() < start + ms) ; // BUGGY version
तथा
while (millis() - start < ms) ; // CORRECT version
हालांकि वे भ्रामक रूप से समान दिखते हैं, टाइमस्टैम्प / अवधि भेद स्पष्ट करना चाहिए कि कौन से छोटी गाड़ी है और कौन सी सही है।
क्या होगा अगर मुझे वास्तव में टाइमस्टैम्प की तुलना करने की आवश्यकता है?
बेहतर है कि स्थिति से बचने की कोशिश करें। यदि यह अपरिहार्य है, तो अभी भी उम्मीद है यदि यह ज्ञात है कि संबंधित इंस्टेंट काफी करीब हैं: 24.85 दिनों की तुलना में करीब। हां, 49.7 दिनों की हमारी अधिकतम प्रबंधनीय देरी सिर्फ आधे में कट गई।
स्पष्ट समाधान हमारी टाइमस्टैम्प तुलना समस्या को अवधि तुलना समस्या में बदलना है। कहें कि हमें यह जानना चाहिए कि तत्काल t1 t2 से पहले या बाद में है। हम उनके सामान्य अतीत में कुछ संदर्भ तुरंत चुनते हैं, और इस संदर्भ से अवधि की तुलना t1 और t2 दोनों तक करते हैं। संदर्भ तत्काल को t1 या t2 में से एक लंबी पर्याप्त अवधि घटाकर प्राप्त किया जाता है:
unsigned long reference_instant = t2 - LONG_ENOUGH_DURATION;
unsigned long from_reference_until_t1 = t1 - reference_instant;
unsigned long from_reference_until_t2 = t2 - reference_instant;
if (from_reference_until_t1 < from_reference_until_t2)
// t1 is before t2
इसे सरल बनाया जा सकता है:
if (t1 - t2 + LONG_ENOUGH_DURATION < LONG_ENOUGH_DURATION)
// t1 is before t2
यह और सरल बनाने के लिए लुभावना है if (t1 - t2 < 0)
। जाहिर है, यह काम नहीं करता है, क्योंकि t1 - t2
, एक अहस्ताक्षरित संख्या के रूप में गणना की जा सकती है, नकारात्मक नहीं हो सकती है। यह, हालांकि, पोर्टेबल नहीं है, काम करता है:
if ((signed long)(t1 - t2) < 0) // works with gcc
// t1 is before t2
signed
ऊपर दिया गया कीवर्ड निरर्थक है (एक प्लेन long
हमेशा हस्ताक्षरित है), लेकिन यह इरादे को स्पष्ट करने में मदद करता है। एक हस्ताक्षरित लंबे समय तक परिवर्तित करना LONG_ENOUGH_DURATION
24.85 दिनों के बराबर स्थापित करने के बराबर है। चाल पोर्टेबल नहीं है, क्योंकि सी मानक के अनुसार, परिणाम कार्यान्वयन परिभाषित है । लेकिन चूंकि gcc कंपाइलर सही काम करने का वादा करता है, इसलिए यह Arduino पर मज़बूती से काम करता है। यदि हम कार्यान्वयन परिभाषित व्यवहार से बचना चाहते हैं, तो ऊपर हस्ताक्षरित तुलना गणितीय रूप से इसके समकक्ष है:
#include <limits.h>
if (t1 - t2 > LONG_MAX) // too big to be believed
// t1 is before t2
एकमात्र समस्या है कि तुलना पीछे की ओर दिखती है। यह समतुल्य है, जब तक कि 32-बिट्स हैं, इस एकल-बिट परीक्षण के लिए:
if ((t1 - t2) & 0x80000000) // test the "sign" bit
// t1 is before t2
पिछले तीन परीक्षण वास्तव में एक ही मशीन कोड में gcc द्वारा संकलित किए गए हैं।
मैं मिलिस रोलओवर के खिलाफ अपने स्केच का परीक्षण कैसे करूं
यदि आप ऊपर दिए गए उपदेशों का पालन करते हैं, तो आपको सभी अच्छे होने चाहिए। यदि आप फिर भी परीक्षण करना चाहते हैं, तो इस फ़ंक्शन को अपने स्केच में जोड़ें:
#include <util/atomic.h>
void setMillis(unsigned long ms)
{
extern unsigned long timer0_millis;
ATOMIC_BLOCK (ATOMIC_RESTORESTATE) {
timer0_millis = ms;
}
}
और अब आप अपने कार्यक्रम को कॉल करके समय-यात्रा कर सकते हैं
setMillis(destination)
। यदि आप चाहते हैं कि यह बार-बार मिलिस ओवरफ्लो से गुज़रता है, तो फिल कोनहॉग डे को जीते हुए फिल कॉनर्स की तरह, आप इसे अंदर रख सकते हैं loop()
:
// 6-second time loop starting at rollover - 3 seconds
if (millis() - (-3000) >= 6000)
setMillis(-3000);
ऊपर (-3000) ऋणात्मक टाइमस्टैम्प को संकलक द्वारा 3000 मिलीसेकंड से पहले एक अहस्ताक्षरित लंबे समय तक रोलओवर (इसे 4294964296 में बदल दिया जाता है) से बदल दिया जाता है।
क्या होगा यदि मुझे वास्तव में बहुत लंबी अवधि को ट्रैक करने की आवश्यकता है?
यदि आपको तीन महीने बाद एक रिले को चालू करने और इसे बंद करने की आवश्यकता है, तो आपको वास्तव में मिली ओवरफ्लो को ट्रैक करने की आवश्यकता है। ऐसा करने के कई तरीके हैं। सबसे सरल समाधान millis()
64 बिट्स का विस्तार करना हो सकता है :
uint64_t millis64() {
static uint32_t low32, high32;
uint32_t new_low32 = millis();
if (new_low32 < low32) high32++;
low32 = new_low32;
return (uint64_t) high32 << 32 | low32;
}
यह अनिवार्य रूप से रोलओवर घटनाओं की गिनती कर रहा है, और 64 बिट मिलीसेकंड गिनती के 32 सबसे महत्वपूर्ण बिट्स के रूप में इस गिनती का उपयोग कर रहा है। इस गिनती को ठीक से काम करने के लिए, फ़ंक्शन को हर 49.7 दिनों में कम से कम एक बार बुलाया जाना चाहिए। हालांकि, अगर इसे केवल 49.7 दिनों के लिए एक बार कहा जाता है, तो कुछ मामलों में यह संभव है कि चेक (new_low32 < low32)
विफल हो जाता है और कोड की एक गिनती याद आती है high32
। मिलिस (एक विशिष्ट 49.7 दिन की खिड़की) के एकल "रैप" में इस कोड को एकमात्र कॉल करने के लिए निर्णय लेने के लिए मिलिस () का उपयोग करना बहुत खतरनाक हो सकता है, यह इस बात पर निर्भर करता है कि समय सीमा कैसे रेखाबद्ध होती है। सुरक्षा के लिए, यदि केवल कॉल करने के लिए मिलिस () का उपयोग करके मिलिस 64 () में एकमात्र कॉल करना है, तो प्रत्येक 49.7 दिन की विंडो में कम से कम दो कॉल होनी चाहिए।
हालांकि, ध्यान रखें कि Arduino पर 64 बिट अंकगणित महंगा है। यह 32 बिट्स पर रहने के लिए समय रिज़ॉल्यूशन को कम करने के लिए लायक हो सकता है।