حفظ الوقت باستخدام توقيت المقاطعات متحكم جزءا لا يتجزأ

StackOverflow https://stackoverflow.com/questions/892661

سؤال

هذا السؤال هو حول برمجة متحكم صغير دون نظام تشغيل. على وجه الخصوص، أنا مهتم في بلدان جزر المحيط الهادئ في الوقت الحالي، لكن السؤال هو العام.

لقد رأيت عدة مرات النمط التالي للحفاظ على الوقت:

رمز المقاطعة الموقت (قل حرائق الموقت كل ثانية):

...
if (sec_counter > 0)
  sec_counter--;
...

رمز الخط الرئيسي (غير المقاطعة):

sec_counter = 500; // 500 seconds

while (sec_counter)
{
  // .. do stuff
}

قد يتكرر رمز الخط الرئيسي، تعيين العداد إلى قيم مختلفة (وليس فقط ثوان) وهلم جرا.

يبدو لي أن هناك حالة سباق هنا عندما تكون المهمة sec_counter في رمز الخط الرئيسي ليس عطري. على سبيل المثال، في PIC18 يتم ترجمة المهمة إلى 4 بيانات ASM (تحميل كل بايت في ذلك الوقت وتحديد البايت الأيمن من بنك الذاكرة قبل ذلك). إذا جاء رمز المقاطعة في منتصف هذا، فقد تكون القيمة النهائية تالفة.

بفضول، إذا كانت القيمة المعينة هي أقل من 256، المهمة يكون ذرية، لذلك ليس هناك مشكلة.

هل أنا حق في هذه المشكلة؟ ما الأنماط التي تستخدمها لتنفيذ مثل هذا السلوك بشكل صحيح؟ أرى العديد من الخيارات:

  • تعطيل المقاطعات قبل كل مهمة إلى sec_counter وتمكين بعد ذلك - هذا ليس جميلا
  • لا تستخدم المقاطعة، ولكن مؤقت منفصل بدأ ثم استطلاعه. هذا نظيف، ولكنه يستخدم مؤقتا بالكامل (في الحالة السابقة يمكن استخدام مؤقت إطلاق النار من 1 ثانية لأغراض أخرى أيضا).

أي أفكار أخرى؟

هل كانت مفيدة؟

المحلول

بنية الموافقة المسبقة عن علم هي الذرية كما يحصل. إنه يضمن أن جميع عمليات القراءة - الكتابة إلى ملف الذاكرة هي "الذرية". على الرغم من أنه يستغرق 4 ساعات لإجراء كامل للقراءة والكتابة، إلا أن جميع الساعات 4 تستهلك في تعليمات واحدة وتستخدم التعليمات التالية دورة 4 ساعات التالية. إنها الطريقة التي يعمل بها خط الأنابيب. في 8 ساعات، هناك تعليمان في خط الأنابيب.

إذا كانت القيمة أكبر من 8 بت، فستصبح مشكلة مثل الموافقة المسبقة عن علم هي آلة 8 بت ويتم التعامل مع المعاملات الأكبر بتعليمات متعددة. من شأنها أن تقدم القضايا الذرية.

نصائح أخرى

اكتب القيمة ثم تحقق من أن القيمة المطلوبة يبدو أنها أبسط بديل.

do {
 sec_counter = value;
} while (sec_counter != value);

راجع للشغل يجب أن تجعل المتغير متقلب إذا كنت تستخدم C.

إذا كنت بحاجة إلى قراءة القيمة، يمكنك قراءتها مرتين.

do {
    value = sec_counter;
} while (value != sec_counter);

تحتاج بالتأكيد إلى تعطيل المقاطعة قبل تحديد العداد. قبيح كما قد يكون، فمن الضروري. إنها ممارسات جيدة لتعطيل المقاطعة دائما قبل تكوين سجلات الأجهزة أو متغيرات البرامج التي تؤثر على طريقة ISR. إذا كنت تكتب في ج، يجب عليك التفكير في جميع العمليات غير الذرية. إذا وجدت أن عليك أن تنظر إلى التجمع المولد مرات عديدة، فقد يكون من الأفضل التخلي عن ج والبرنامج في التجميع. في تجربتي، نادرا ما يكون هذا هو الحال.

فيما يتعلق بالمسألة التي تمت مناقشتها، هذا ما أقترحه:

ISR:
if (countDownFlag)
{
   sec_counter--;
}

ووضع العداد:

// make sure the countdown isn't running
sec_counter = 500;
countDownFlag = true;

...
// Countdown finished
countDownFlag = false;

تحتاج إلى متغير إضافي ومن الأفضل لف كل شيء في وظيفة:

void startCountDown(int startValue)
{
    sec_counter = 500;
    countDownFlag = true;
}

بهذه الطريقة مجردة طريقة البداية (وإخفاء القبح إذا لزم الأمر). على سبيل المثال، يمكنك بسهولة تغييره لبدء تشغيل جهاز ضبط الوقت دون التأثير على المتصلين في الطريقة.

نظرا لأن الوصول إلى متغير SEC_Counter ليس عطري، فلا توجد طريقة لتجنب تعطيل المقاطعات قبل الوصول إلى هذا المتغير في رمز الخط الرئيسي واستعادة حالة المقاطعة بعد الوصول إذا كنت تريد السلوك الحاسم. من المحتمل أن يكون هذا اختيارا أفضل من تكريس مؤقت HW لهذه المهمة (إلا إذا كان لديك فائض من أجهزة ضبط الوقت، وفي هذه الحالة قد تستخدم واحدة).

إذا قمت بتنزيل مكدس TCP / IP المجاني من Microchip، فهناك روتين هناك تستخدم تجاوز تجاوز توقيت لتتبع الوقت المنقضي. على وجه التحديد "tick.c" و "tick.h". فقط قم بنسخ تلك الملفات إلى مشروعك.

داخل تلك الملفات، يمكنك أن ترى كيف يفعلون ذلك.

ليس فضوليا للغاية عن أقل من 256 خطوة كونها ذرية - تحريك قيمة 8 بت هي Opcode واحد بحيث تكون ذرية كما تحصل عليها.

أفضل حل على مثل هذا الميكرواق الأساسي مثل الموافقة المسبقة عن علم هو تعطيل المقاطعات قبل تغيير قيمة الموقت. يمكنك حتى التحقق من قيمة علامة المقاطعة عند تغيير المتغير في الحلقة الرئيسية وتتعامل معها إذا كنت تريد. اجعلها دالة تغير قيمة المتغير ويمكنك الاتصال بها من ISR أيضا.

حسنا، كيف تبدو قانون جمعية المقارنة؟

اتخذت في الاعتبار أنها مهمة إلى أسفل، وأنها مجرد صفر قارن، يجب أن تكون آمنة إذا تحققت أولا MSB، ثم LSB. قد يكون هناك فساد، لكنه لا يهم حقا إذا جاء في الوسط بين 0x100 و 0xFF والقيمة المقارنة التالفة هو 0x1FF.

الطريقة التي تستخدمها مؤقتا الآن، لن تحسب ثوان بأكملها على أي حال، لأنك قد تغيرها في منتصف دورة. لذلك، إذا كنت لا تهتم بذلك. أفضل طريقة، في رأيي، ستكون قراءة القيمة، ثم قارنت فقط الفرق. يتطلب الأمر بضعة OPS أكثر، ولكن ليس له مشاكل متعددة الخيوط. (منذ الموقت له أولوية)

إذا كنت أكثر صرامة حول القيمة الزمنية، فسأعطاط الموقت تلقائيا بمجرد حسابها إلى 0، ومسح العداد الداخلي للموقت والتنشيط بمجرد حاجة إليه.

حرك جزء التعليمات البرمجية الذي سيكون على أساس () إلى وظيفة مناسبة، ويطلق عليه مشروطا بواسطة ISR.

أيضا، لتجنب أي نوع من التأخير أو المفقودين، اختر هذا المؤقت ISR ليكون مقاطعة عالية PRIO (يحتوي PIC18 على مستويين).

نهج واحد هو الحصول على مقاطعة إبقاء متغير البايت، ولديك شيء آخر يسمى مرة واحدة على الأقل كل 256 مرة يتم ضرب العداد؛ تفعل شيئا مثل:

// ub == char غير موقعة؛ UI == int unsigned؛ UL == غير موقعة طويلة UB Now_ctr؛ // هذا واحد هو ضرب من قبل مقاطعة UB السابق_ctr؛ ul big_ctr؛ Void Poll_Counter (باطلة) {UB Delta_ctr؛ DELTA_CTR = (UB) (now_ctr-prev_ctr)؛ big_ctr + = delta_ctr؛ السابق_ctr + = delta_ctr؛ }

اختلاف طفيف، إذا كنت لا تمانع في إجبار عداد المقاطعة للإقامة في المزامنة مع LSB من عدادك الكبير:

ul big_ctr؛ void poll_counter (void) {big_ctr + = (ub) (now_ctr - big_ctr)؛ }

لا أحد يعالج مسألة قراءة سجلات الأجهزة متعددة البياضات (على سبيل المثال مؤقتا مؤقتا. يمكن أن يتدحرج الموقت وزيادة البايت الثاني أثناء قراءته.

قل أنه 0x0001FFFF وتقرأها. قد تحصل على 0x0010FFFF، أو 0x00010000.

السجل المحيطي 16 بت هو متطايره إلى الكود الخاص بك.

لأي متطايره "المتغيرات"، أستخدم تقنية القراءة المزدوجة.

do {
       t = timer;
 } while (t != timer);
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top