সংক্ষিপ্ত উত্তর: মিলিস রোলওভারটি "পরিচালনা" করার চেষ্টা করবেন না, পরিবর্তে রোলওভার-নিরাপদ কোড লিখুন। টিউটোরিয়াল থেকে আপনার উদাহরণ কোড ঠিক আছে। আপনি যদি সংশোধনমূলক পদক্ষেপগুলি প্রয়োগ করার জন্য রোলওভারটি সনাক্ত করার চেষ্টা করেন, সম্ভবত আপনি কোনও ভুল করছেন। বেশিরভাগ আরডিনো প্রোগ্রামগুলিতে কেবল এমন ইভেন্টগুলি পরিচালনা করতে হয় যা তুলনামূলকভাবে সংক্ষিপ্ত সময়সীমা বিস্তৃত হয়, যেমন 50 এমএসের জন্য একটি বোতাম ডিবাজ করা, বা 12 ঘন্টা একটি হিটার চালু করা ... তারপরেও, এবং এমনকি যদি প্রোগ্রামটি একসাথে কয়েক বছরের জন্য চালানো হয়, মিলিস রোলওভারটি উদ্বেগের বিষয় হওয়া উচিত নয়।
রোলওভার সমস্যাটি পরিচালনা করার সঠিক উপায় (বা বরং, পরিচালনা করা এড়ানো) হ'ল মডুলার গাণিতিকের ক্ষেত্রে unsigned long
ফিরে আসা সংখ্যাটি
ভাবা । গাণিতিকভাবে ঝুঁকির জন্য, প্রোগ্রামিং করার সময় এই ধারণার সাথে কিছু পরিচিতি খুব দরকারী। আপনি নিক গ্যামনের নিবন্ধ মিলিস () উপচে পড়া ... কোনও খারাপ জিনিসটিতে অংকিত অংকটি দেখতে পাচ্ছেন ? । যারা গণনা সংক্রান্ত বিশদটি নিয়ে যেতে চান না তাদের জন্য আমি এখানে বিকল্পের (আশাবাদী সরল) এটি সম্পর্কে চিন্তাভাবনা করার প্রস্তাব দিই। এটি তাত্ক্ষণিক ও সময়কালের মধ্যে সাধারণ পার্থক্যের ভিত্তিতে তৈরি । যতক্ষণ আপনার পরীক্ষাগুলি কেবলমাত্র সময়কালগুলির তুলনা করে জড়িত থাকে ততক্ষণ আপনার ভাল হওয়া উচিত।millis()
মাইক্রোগুলিতে নোট () : এখানে প্রতিটি কথাই millis()
সমানভাবে প্রযোজ্য micros()
, ব্যতীত micros()
প্রতিটি 71১. minutes মিনিটের পরে রোলগুলি তৈরি হয় এবং setMillis()
নীচে প্রদত্ত ফাংশনটি প্রভাবিত করে না micros()
।
তাত্ক্ষণিক, টাইমস্ট্যাম্প এবং সময়কাল
সময়ের সাথে মোকাবিলা করার সময়, আমাদের কমপক্ষে দুটি পৃথক ধারণার মধ্যে তফাত তৈরি করতে হবে: তাত্ক্ষণিক ও সময়কাল । তাত্ক্ষণিক সময় অক্ষের একটি বিন্দু। একটি সময়কাল হ'ল সময় ব্যবধানের দৈর্ঘ্য, অর্থাত্ তাত্ক্ষণিকের মধ্যে সময়ের দূরত্ব যা অন্তরালের শুরু এবং শেষকে সংজ্ঞায়িত করে। প্রতিদিনের ভাষায় এই ধারণাগুলির মধ্যে পার্থক্য সর্বদা খুব তীক্ষ্ণ হয় না। উদাহরণস্বরূপ, আমি যদি বলি যে " আমি পাঁচ মিনিটের মধ্যে ফিরে আসছি ", তবে " পাঁচ মিনিট " হ'ল আমার অনুপস্থিতির আনুমানিক
সময়কাল , যখন " পাঁচ মিনিটের মধ্যে " তাত্ক্ষণিক
আমার পূর্বাভাস ফিরে আসছে। পার্থক্য মাথায় রাখা গুরুত্বপূর্ণ, কারণ এটি সম্পূর্ণরূপে রোলওভার সমস্যা এড়ানো সহজ উপায়।
এর রিটার্ন মানটি millis()
একটি সময়কাল হিসাবে ব্যাখ্যা করা যেতে পারে: প্রোগ্রামের শুরু থেকে এখন অবধি সময় অতিবাহিত। এই ব্যাখ্যাটি অবশ্য মিলিসের উপচে পড়ার সাথে সাথেই ভেঙে যায়। millis()
একটি টাইমস্ট্যাম্প ফিরে আসার কথা
চিন্তা করার জন্য সাধারণত আরও বেশি কার্যকর , যেমন একটি নির্দিষ্ট তাত্ক্ষণিক শনাক্তকরণের একটি "লেবেল"। এটি যুক্তিযুক্ত হতে পারে যে এই ব্যাখ্যাটি এই লেবেলগুলি অস্পষ্ট হওয়ার কারণে ভোগ করে, কারণ প্রতি 49.7 দিন এগুলি পুনরায় ব্যবহার করা হয়। এটি অবশ্য খুব কমই সমস্যা: বেশিরভাগ এম্বেড থাকা অ্যাপ্লিকেশনগুলিতে, 49.7 দিন আগে যা ঘটেছিল তা প্রাচীন ইতিহাস যা আমরা যত্ন করি না। সুতরাং, পুরানো লেবেলগুলি পুনর্ব্যবহার করা কোনও সমস্যা হওয়া উচিত নয়।
টাইমস্ট্যাম্প তুলনা করবেন না
দুটি টাইমস্ট্যাম্পগুলির মধ্যে কোনটি অপরটির চেয়ে বেশি এটি সন্ধান করার চেষ্টা করা অর্থহীন নয়। উদাহরণ:
unsigned long t1 = millis();
delay(3000);
unsigned long t2 = millis();
if (t2 > t1) { ... }
উদাসীনভাবে, একজনের শর্তটি if ()
সর্বদা সত্য হওয়ার প্রত্যাশা করবে । মিলিসের সময় ওভারফ্লো হয়ে গেলে এটি আসলে মিথ্যা হবে
delay(3000)
। টি -1 এবং টি 2 কে পুনর্ব্যবহারযোগ্য লেবেল হিসাবে ভাবা ত্রুটিটি এড়ানোর সহজতম উপায়: লেবেল টি 1 টি 2 এর আগে একটি তাত্ক্ষণিকভাবে স্পষ্টভাবে বরাদ্দ করা হয়েছে, তবে 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 দিনেরও কাছাকাছি closer হ্যাঁ, আমাদের সর্বোচ্চ 49,7 দিনের ব্যবস্থাপত্র বিলম্ব অর্ধেক কেটে গেছে।
সুস্পষ্ট সমাধান হ'ল আমাদের টাইমস্ট্যাম্প তুলনা সমস্যাটিকে একটি সময়কাল তুলনামূলক সমস্যায় রূপান্তর করা। বলুন আমাদের তাত্ক্ষণিক t1 টি 2 এর আগে বা পরে আছে কিনা তা জানতে হবে। আমরা তাদের সাধারণ অতীতে কিছু রেফারেন্স তাত্ক্ষণিক চয়ন করি এবং 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 দিনের সমান সেটিংয়ের সমান। কৌশলটি পোর্টেবল নয় কারণ সি স্ট্যান্ডার্ড অনুযায়ী ফলাফলটি বাস্তবায়িত সংজ্ঞায়িত । তবে যেহেতু জিসিসি সংকলক সঠিক কাজটি করার প্রতিশ্রুতি দিয়েছে , তাই এটি আরডুইনোর উপর নির্ভরযোগ্যভাবে কাজ করে। যদি আমরা বাস্তবায়িত সংজ্ঞায়িত আচরণ এড়াতে চাই তবে উপরের স্বাক্ষরিত তুলনাটি গাণিতিকভাবে এর সমান:
#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
শেষ তিনটি পরীক্ষা প্রকৃতপক্ষে একই মেশিন কোডে জিসিসি দ্বারা সংকলিত হয়।
মিলিস রোলওভারের বিপরীতে আমি কীভাবে আমার স্কেচটি পরীক্ষা করব
আপনি যদি উপরের আদেশগুলি অনুসরণ করেন তবে আপনার ভাল হওয়া উচিত। তবুও আপনি যদি পরীক্ষা করতে চান তবে আপনার স্কেচে এই ফাংশনটি যুক্ত করুন:
#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 দিনের উইন্ডোটিতে কমপক্ষে দুটি কল থাকা উচিত।
তবে মনে রাখবেন যে, আরডুইনোতে bit৪ বিটের গাণিতিক ব্যয়বহুল। 32 বিট এ থাকার জন্য সময় রেজোলিউশনটি হ্রাস করার উপযুক্ত হতে পারে।