هيكلة البرنامج وكتابته: التهيئة والتأجيل والبرنامج الرئيسي
يتعلّم التلميذ كتابة برنامج كامل للميكرومراقب PIC16F84A: بنية الملف المصدر والخوارزمية البيانية، برنامج التهيئة وتبديل البنك، البرنامج الفرعي للتأجيل بالحلقات المتداخلة مع الحساب الدقيق لزمنه، البرنامج الرئيسي للتحكم في مصباح بزر ضاغط، والتأجيل بالمؤقت TMR0، ويُختم بتمارين محلولة بنمط البكالوريا.
يتكوّن الدرس من 6 أجزاء: 1. بنية البرنامج ومنهجية كتابته، 2. برنامج التهيئة: المخارج والاتجاهات والخيارات، 3. البرنامج الفرعي للتأجيل بالحلقات المتداخلة وحساب زمنه، 4. البرنامج الرئيسي: التحكم في مصباح بزر ضاغط، 5. التأجيل بالمؤقت TMR0، 6. تمارين محلولة بنمط البكالوريا.
1. بنية البرنامج ومنهجية كتابته
برنامج لا ينتهي أبداً
تعرّفنا في الدرسين السابقين على هيكلة الميكرومراقب PIC16F84A وسجلاته، ثم على تعليمات لغة المجمع وتوجيهاتها. نجمع الآن هذه المعارف في برنامج كامل: ملف مصدر واحد يحوّله المجمِّع MPASM إلى شفرة آلية تُشحن في ذاكرة البرنامج.
يختلف برنامج الميكرومراقب عن برنامج الحاسوب في نقطتين أساسيتين:
- يبدأ التنفيذ دائماً من العنوان 0000h بعد وصل التغذية أو بعد إعادة التهيئة بالرجل $\overline{MCLR}$؛
- لا ينتهي البرنامج أبداً: الميكرومراقب يراقب نظاماً آلياً ما دام مغذّى، فيدور البرنامج الرئيسي في حلقة لا نهائية (boucle infinie) تقرأ المداخل وتقرر وتكتب المخارج. التوجيه `END` لا يوقف الميكرومراقب، بل يُعلم المجمِّع فقط بنهاية الملف المصدر.
الأقسام السبعة للملف المصدر

يبيّن الشكل ترتيب الأقسام من أعلى الملف إلى أسفله: القفزة من شعاع إعادة التهيئة إلى `init` (السهم الأحمر)، والحلقة اللانهائية للبرنامج الرئيسي (السهم الأخضر)، واستدعاء البرنامج الفرعي بـ`CALL` والرجوع منه بـ`RETURN` (السهمان الأزرقان).
| القسم | دوره | مثال | هل يولّد شفرة؟ | |---|---|---|---| | الترويسة | تعليقات: اسم البرنامج ودوره وربط الأرجل | `; BP sur RA0` | لا | | 1. التوجيهات | نوع الميكرومراقب، ملف التعريفات، كلمة الإعداد | `LIST`، `#include`، `CONFIG` | `CONFIG` وحده (في 2007h) | | 2. المتغيرات | أسماء خانات الذاكرة RAM من 0Ch إلى 4Fh | `CPT1 EQU 0x0C` | لا | | 3. شعاع إعادة التهيئة | قفزة من العنوان 000h إلى التهيئة | `ORG 0x000` ثم `GOTO init` | نعم | | 4. التهيئة (initialisation) | تُنفَّذ مرة واحدة: المخارج، الاتجاهات، الخيارات | `init CLRF PORTB` … | نعم | | 5. البرنامج الرئيسي (programme principal) | حلقة لا نهائية تنفّذ دفتر الشروط | `main` … `GOTO main` | نعم | | 6. البرامج الفرعية (sous-programmes) | مقاطع تُستدعى بـCALL وتنتهي بـRETURN | `tempo` … `RETURN` | نعم | | 7. النهاية | نهاية الملف المصدر | `END` | لا |
> قاعدة: البرامج الفرعية توضع بعد الحلقة اللانهائية للبرنامج الرئيسي، حتى لا يدخلها التنفيذ إلا بالتعليمة CALL. لو دخلها البرنامج متسلسلاً لنفّذ RETURN دون CALL فيضيع.
من دفتر الشروط إلى البرنامج
لا نكتب التعليمات مباشرة، بل نتّبع خطوات ثابتة:
- قراءة دفتر الشروط: ماذا يجب أن يفعل النظام؟ متى؟ وبأي ترتيب؟
- جدول تخصيص المداخل والمخارج: لكل ملتقط أو زر رجل مدخل، ولكل منفذ متصدر رجل مخرج، مع المستوى النشط (هل الضغط يعطي 1 أم 0؟).
- الخوارزمية البيانية (organigramme): رسم يصف تسلسل العمليات والاختبارات.
- الترجمة إلى لغة المجمع: كل رمز من الخوارزمية يقابله مقطع من التعليمات.
- التجميع والمحاكاة: التجميع بـMPLAB، ثم الاختبار على محاكٍ قبل شحن الميكرومراقب.
رموز الخوارزمية البيانية
| الرمز | شكله | معناه | ترجمته في المجمع | |---|---|---|---| | طرفي (terminal) | مستطيل بزوايا دائرية | بداية البرنامج أو بداية برنامج فرعي ونهايته | وسم، أو `RETURN` | | معالجة (traitement) | مستطيل | عمل: كتابة مخرج، شحن عداد، إنقاص | `BSF` و`BCF` و`MOVLW` و`MOVWF` و`DECF` | | اختبار (test) | معيّن (losange) بمخرجين: نعم / لا | سؤال عن بت: مدخل أو علم | `BTFSS` أو `BTFSC` متبوعة بـ`GOTO` | | برنامج فرعي | مستطيل بخطين عموديين على جانبيه | استدعاء مقطع معرّف في مكان آخر | `CALL` | | خط السير | سهم | ترتيب التنفيذ؛ السهم الراجع إلى الأعلى حلقة | `GOTO` |
مثال كامل: مصباح يشتعل ما دام الزر مضغوطاً
دفتر الشروط: يشتعل المصباح H ما دام الزر الضاغط BP مضغوطاً، وينطفئ عند تحريره. الميكرومراقب PIC16F84A ببلور 4 MHz.
جدول المداخل والمخارج (الدارة الكاملة في الجزء الموالي):
| العنصر | الرجل | الاتجاه | المستوى النشط | |---|---|---|---| | الزر BP (مع مقاومة سحب نحو الأرضي) | RA0 (الرجل 17) | مدخل | الضغط يعطي 1 | | المرحل KA الذي يغذي المصباح H | RB0 (الرجل 6) | مخرج | 1 يشغّل المصباح |
الأرجل غير المستعملة تُبرمج مداخل، فيكون TRISA يساوي 1Fh وTRISB يساوي FEh.

تبيّن الخوارزمية أن التهيئة تُنفَّذ مرة واحدة، ثم يتكرر الاختبار «هل $RA0 = 1$؟» بلا نهاية: إن كان الجواب نعم نكتب 1 في RB0، وإلا نكتب 0، ثم نرجع إلى الاختبار.
لا يحتاج هذا البرنامج إلى متغيرات ولا إلى برنامج فرعي، فالقسمان 2 و6 فارغان. نلاحظ أيضاً:
- كلمة الإعداد 3FF1h: دون حماية للشفرة، ومؤقت الحراسة معطل، ومؤقت التشغيل مفعّل، والمذبذب في النمط XT؛
- القفزة `GOTO init` في 000h ثم `ORG 0x005`: نترك العنوان 0004h (شعاع المقاطعة) حراً، وهي عادة جيدة حتى في برنامج بلا مقاطعات؛
- كل وسم في العمود الأول، وكل تعليمة بعد مسافة.
كم مرة يقرأ البرنامج الزر؟
نعدّ دورات الحلقة الرئيسية ($T_{cy} = 1\ \mu s$ مع بلور 4 MHz):
- الزر مضغوط: BTFSS مع قفز (2) ثم BSF (1) ثم GOTO main (2): 5 دورات؛
- الزر محرر: BTFSS دون قفز (1) ثم GOTO eteindre (2) ثم BCF (1) ثم GOTO main (2): 6 دورات.
يُقرأ الزر إذن كل 5 أو 6 µs، أي بين $166\,000$ و$200\,000$ مرة تقريباً في الثانية: يتبع المصباح الزر دون تأخير محسوس.
أخطاء شائعة
- وضع البرنامج الفرعي بين التهيئة والبرنامج الرئيسي دون قفزة فوقه: يُنفَّذ RETURN دون CALL.
- نسيان الحلقة `GOTO main`: يتابع الميكرومراقب تنفيذ خانات فارغة من الذاكرة.
- الظن أن `END` يوقف البرنامج: هو توجيه للمجمِّع لا تعليمة.
- كتابة الخوارزمية بعد البرنامج: الخوارزمية أداة تفكير قبل الكتابة، وهي أيضاً سؤال متكرر في الامتحان.
2. برنامج التهيئة: المخارج والاتجاهات والخيارات
دور برنامج التهيئة
برنامج التهيئة (programme d'initialisation) مقطع يُنفَّذ مرة واحدة بعد كل إعادة تهيئة، قبل البرنامج الرئيسي. دوره أن يضع النظام في حالة معروفة وآمنة: كل المنفذات المتصدرة متوقفة، وكل رجل في الاتجاه الذي تفرضه الدارة، وكل خيار (مقاومات السحب، المؤقت TMR0) مضبوط، وكل متغير مشحون بقيمته الأولى.
عند وصل التغذية تكون بتات TRISA وTRISB كلها 1 (كل الأرجل مداخل)، وOPTION_REG يساوي FFh، والبنك 0 مختار ($RP0 = 0$)، أما محتوى PORTA وPORTB فغير محدد.
من مخطط الربط إلى التهيئة

يبيّن الشكل الدارة الكاملة للتطبيق الذي نبرمجه في هذا الدرس:
- التغذية وإعادة التهيئة: VDD (الرجل 14) على +5 V وVSS (الرجل 5) على الأرضي، والمقاومة R3 (10 kΩ) تبقي $\overline{MCLR}$ (الرجل 4) في المستوى 1؛
- المذبذب: بلور 4 MHz بين OSC1 وOSC2 (الرجلان 16 و15) مع المكثفين C1 وC2 (22 pF مثلاً) نحو الأرضي؛
- المدخل: الزر BP بين +5 V والرجل RA0 (الرجل 17)، والمقاومة R1 (10 kΩ) تسحب المدخل نحو الأرضي (pull-down): الزر محرر يعطي $RA0 = 0$، والزر مضغوط يعطي $RA0 = 1$؛
- المخرج: RB0 (الرجل 6) عبر المقاومة R2 نحو قاعدة المقحل T من نوع NPN، ووشيعة المرحل KA بين +12 V والمجمّع، والثنائية D على التفرع معها (ثنائية العجلة الحرة) تحمي المقحل عند القطع؛
- دارة الاستطاعة: تماس المرحل KA يغلق دارة المصباح H على الشبكة 230 V~. الميكرومراقب لا يلمس أبداً توتر الشبكة.
نستخرج من الشكل: RA0 مدخل، وRB0 مخرج، والباقي غير مستعمل فيُبرمج مدخلاً:
| البت | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |---|---|---|---|---|---|---|---|---| | TRISA | — | — | — | 1 | 1 | 1 | 1 | 1 | | TRISB | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
أي $TRISA = 1\,1111_{(2)} = 1Fh$ و$TRISB = 1111\,1110_{(2)} = FEh$.
محتوى برنامج التهيئة وترتيبه
> الترتيب الصحيح: (1) قيم الراحة للمخارج في PORTA وPORTB ونحن في البنك 0؛ (2) `BSF STATUS,RP0`؛ (3) كتابة TRISA وTRISB وOPTION_REG؛ (4) `BCF STATUS,RP0`؛ (5) شحن المتغيرات.
لماذا نكتب PORTB قبل TRISB؟ لحظة جعل الرجل مخرجاً يظهر عليها محتوى ماسكة المنفذ، وهو غير محدد عند التشغيل. لو كتبنا TRISB أولاً لظهرت على RB0 قيمة عشوائية بضع دورات قبل `CLRF PORTB`. هذه النبضة القصيرة لا تحرك مرحلاً، لكنها تكفي لقدح تيريستور أو لتقديم عداد أو محرك خطوة/خطوة. الترتيب الصحيح لا يكلّف شيئاً.
لماذا تبديل البنك؟ التعليمة تحمل 7 بتات فقط من عنوان السجل، والبت الثامن هو RP0 (البت 5 من STATUS). لذلك `MOVWF TRISB` و`MOVWF PORTB` لهما الشفرة نفسها، والفرق كله في قيمة RP0 لحظة التنفيذ.
| البنك 0 فقط ($RP0 = 0$) | البنك 1 فقط ($RP0 = 1$) | في البنكين معاً | |---|---|---| | TMR0 وPORTA وPORTB وEEDATA وEEADR | OPTION_REG وTRISA وTRISB وEECON1 وEECON2 | STATUS وINTCON وPCL وFSR وPCLATH والمتغيرات من 0Ch إلى 4Fh |
الكتابات `BSF STATUS,RP0` و`BSF STATUS,5` و`BSF 0x03,5` متكافئة تماماً، والأولى أوضح. والرسالة 302 التي يعطيها المجمِّع عند كل كتابة في سجل من البنك 1 تذكير لا خطأ.
مثال 1: تهيئة التطبيق
مدة هذا المقطع 7 دورات، أي 7 µs: لا تؤثر في النظام.
مثال 2: تهيئة في برنامج فرعي مع مقاومات السحب الداخلية
دفتر الشروط: ثلاثة أزرار BP1 وBP2 وBP3 موصولة بين الأرجل RB4 وRB5 وRB6 والأرضي، دون مقاومات خارجية؛ وثلاث ثنائيات L1 وL2 وL3 على RA0 وRA1 وRA2. تشتعل كل ثنائية ما دام زرها مضغوطاً.
الأزرار تحتاج مقاومات سحب نحو VDD: نفعّل المقاومات الداخلية للمنفذ B بوضع $\overline{RBPU} = 0$ (البت 7 من OPTION_REG). عندئذ الزر المحرر يعطي 1 والمضغوط يعطي 0. الاتجاهات: $TRISA = 1\,1000_{(2)} = 18h$ (RA3 وRA4 غير مستعملين فهما مدخلان)، و$TRISB = FFh$.
ملاحظات:
- `BCF OPTION_REG,7` تغيّر البت 7 وحده، فيصير OPTION_REG مساوياً 7Fh بعد أن كان FFh. هذا أسلم من شحن بايت كامل لا نحتاج منه إلا بتاً واحداً؛
- التهيئة هنا برنامج فرعي يُستدعى مرة واحدة، وهي موضوعة بعد الحلقة الرئيسية؛
- `SWAPF PORTB,W` تضع RB4 وRB5 وRB6 في البتات 0 و1 و2 من W، ثم `XORLW` تعكسها لأن الزر المضغوط يعطي 0، ثم `ANDLW` تمحو البتات الأخرى. مثلاً BP2 وحده مضغوط: $PORTB = 1101\,1111_{(2)}$، فبعد SWAPF يكون $W = 1111\,1101_{(2)}$، وبعد XORLW يكون $W = 1111\,1010_{(2)}$، وبعد ANDLW يكون $W = 0000\,0010_{(2)}$: L2 وحدها تشتعل.
حالة الرجل RA4
الرجل RA4/T0CKI مخرج بمصرف مفتوح (drain ouvert): يستطيع فرض 0 فقط. إذا استعملناها مخرجاً وجب وضع مقاومة سحب خارجية نحو +5 V. لذلك تُترك غالباً مدخلاً، أو تُستعمل مدخلاً لنبضات العداد TMR0.
في أسئلة البكالوريا
يتكرر سؤال «أكمل برنامج التهيئة» بجدول تعليمات معطى. الجواب يتبع دائماً الهيكل نفسه، ويقبل عدة كتابات متكافئة:
| المطلوب | كتابات مقبولة | |---|---| | المنفذ B كله مخارج | `CLRF TRISB`، أو `MOVLW 0x00` ثم `MOVWF TRISB` | | شحن TRISA بالقيمة 1Dh | `MOVLW 0x1D` أو `MOVLW H'1D'` أو `MOVLW B'00011101'`، ثم `MOVWF TRISA` | | اختيار البنك 1 | `BSF STATUS,RP0` أو `BSF STATUS,5` |
أخطاء شائعة
- كتابة TRISx قبل `BSF STATUS,RP0`: تذهب القيمة إلى PORTx في البنك 0.
- نسيان `BCF STATUS,RP0` في آخر التهيئة: كل `BSF PORTB,0` لاحقة تكتب في TRISB فتجعل RB0 مدخلاً.
- كتابة OPTION_REG في البنك 0: القيمة تذهب إلى TMR0 (العنوان 01h)، ويبقى OPTION_REG على FFh.
- قلب الرمزين: 1 للمدخل (Input) و0 للمخرج (Output).
- تفعيل مقاومات السحب الداخلية ($\overline{RBPU} = 0$) ثم اختبار الزر على أنه نشط بـ1: مع السحب نحو VDD يعطي الزر المضغوط 0.
3. البرنامج الفرعي للتأجيل بالحلقات المتداخلة وحساب زمنه
مبدأ التأجيل البرمجي
نحتاج في كل نظام آلي إلى أزمنة: إبقاء صمّامة مفتوحة 3 s، وميض مصباح، انتظار انتهاء ارتدادات زر. أبسط طريقة هي التأجيل البرمجي (temporisation logicielle): نجعل الميكرومراقب ينفّذ عدداً معروفاً من الدورات دون عمل مفيد. هذا هو مبدأ المؤجلة بعداد التي درسناها في المنطق التعاقبي: عداد يُشحن بقيمة وينقص حتى الصفر، وكل نبضة تدوم دورة آلة.
$t = n \times T_{cy}$
حيث n عدد دورات الآلة و$T_{cy} = 4/f_{osc}$، أي $1\ \mu s$ مع بلور 4 MHz و$0{,}5\ \mu s$ مع بلور 8 MHz.
> اصطلاح العدّ في هذا الدرس: نعدّ كل التعليمات من `CALL` إلى `RETURN` ضمناً. كل تعليمة دورة واحدة، إلا GOTO وCALL وRETURN فلها دورتان، وكذلك DECFSZ وBTFSS وBTFSC عندما يحدث القفز. هذا هو اصطلاح العلاقة $3N + 5$ التي رأيناها في درس لغة المجمع.
من الحلقة الواحدة إلى الحلقتين
الحلقة الواحدة (DECFSZ ثم GOTO) مستدعاة بـCALL تدوم $3N + 5$ دورة. أكبر قيمة لها نحصل عليها بشحن العداد بـ0، لأن أول إنقاص يعطي FFh فتدور الحلقة 256 مرة: $3 \times 256 + 5 = 773\ \mu s$ عند 4 MHz. هذا أقل من ميلي ثانية: لا يكفي لإبقاء مصباح مشتعلاً زمناً يُرى. الحل أن نكرر الحلقة الداخلية عدة مرات بحلقة خارجية: حلقتان متداخلتان (boucles imbriquées) بعدادين CPT1 وCPT2.

تبيّن الخوارزمية أن الحلقة الداخلية (على اليمين) تنقص CPT1 حتى الصفر، ثم تنقص الحلقة الخارجية (على اليسار) CPT2 وتعيد شحن CPT1 بالقيمة N1، وهكذا N2 مرة.
العدّ الدقيق للدورات
نعدّ كم مرة تُنفَّذ كل تعليمة، بقيمتين N1 وN2 للعدادين:
| التعليمة | عدد مرات التنفيذ | عدد الدورات | |---|---|---| | `CALL tempo` | 1 | 2 | | `MOVLW N2` و`MOVWF CPT2` | مرة واحدة لكل منهما | 2 | | `MOVLW N1` و`MOVWF CPT1` | N2 مرة لكل منهما | $2N_2$ | | الحلقة الداخلية DECFSZ وGOTO | N2 مرة | $N_2\,(3N_1 - 1)$ | | `DECFSZ CPT2,F` | N2 مرة، الأخيرة بالقفز | $N_2 + 1$ | | `GOTO boucle2` | $N_2 - 1$ مرة | $2N_2 - 2$ | | `RETURN` | 1 | 2 |
نجمع: $2 + 2 + 2N_2 + 3N_1N_2 - N_2 + N_2 + 1 + 2N_2 - 2 + 2$، أي:
$n = N_2\,(3N_1 + 4) + 5$
> كل مرور في الحلقة الخارجية يكلف $3N_1 + 4$ دورة (شحن CPT1، ثم الحلقة الداخلية، ثم DECFSZ وGOTO)، ونضيف 5 دورات مرة واحدة (CALL والشحن الأول وRETURN، ناقص دورة واحدة لأن آخر مرور بلا GOTO).
توقيت التأجيل دورة بدورة

لنتحقق من العلاقة بقيمتين صغيرتين: $N_1 = 3$ و$N_2 = 2$. يبيّن الشكل تطور CPT2 (بالأحمر) وCPT1 (بالأخضر) على محور الزمن بالميكروثانية: 4 دورات للاستدعاء والشحن الأول، ثم مرور أول من 13 دورة (شحن CPT1 دورتان، الحلقة الداخلية $3 \times 3 - 1 = 8$ دورات، DECFSZ وGOTO ثلاث دورات)، ثم مرور ثانٍ من 12 دورة لأن DECFSZ تقفز فلا تُنفَّذ GOTO، ثم RETURN دورتان. المجموع $4 + 13 + 12 + 2 = 31$ دورة، وهو ما تعطيه العلاقة: $2 \times (3 \times 3 + 4) + 5 = 31$.
أمثلة محلولة
مثال 1 (أكبر القيم المعتادة): $N_1 = N_2 = 255$ وبلور 4 MHz.
- مرور واحد: $3 \times 255 + 4 = 769$ دورة؛
- المجموع: $255 \times 769 + 5 = 196\,095 + 5 = 196\,100$ دورة؛
- الزمن: $t = 196\,100\ \mu s \approx 0{,}196\ s$.
التقريب الشائع $3N_1N_2 = 195\,075\ \mu s$ يخطئ بحوالي 0,5 %: مقبول إذا قبله الموضوع، لكن الحساب الدقيق أعلاه هو المطلوب عندما يُسأل عن «عدد الدورات».
مثال 2 (أكبر تأجيل بحلقتين): نشحن العدادين بـ0، فيدور كل منهما 256 مرة: $256 \times (3 \times 256 + 4) + 5 = 256 \times 772 + 5 = 197\,637\ \mu s$. للحصول على أكثر من 0,2 s نحتاج حلقة ثالثة أو المؤقت TMR0.
مثال 3 (اختيار N1 وN2 لزمن مطلوب): نريد حوالي 100 ms، أي $100\,000$ دورة عند 4 MHz.
- نختار N1 بحيث يكون المرور الواحد عدداً مستديراً: $3N_1 + 4 = 400$ يعطي $N_1 = 132$؛
- ثم $N_2 = 100\,000 / 400 = 250$؛
- التحقق: $n = 250 \times 400 + 5 = 100\,005$ دورة، أي $100{,}005\ ms$. الخطأ 5 µs فقط.
هذه هي القيم المكتوبة في البرنامج الفرعي tempo أعلاه.
تأجيل دقيق بالميلي ثانية
نضيف NOP في الحلقة الداخلية فيصير كل مرور فيها 4 دورات (والأخير 3)، أي $4N_1 - 1$ دورة. مع $N_1 = 249$ تدوم الحلقة الداخلية $995$ دورة، ويدوم المرور الخارجي $2 + 995 + 3 = 1000$ دورة بالضبط: ميلي ثانية واحدة. نجعل عدد الميلي ثوانٍ معاملاً يُعطى في W:
العدّ بقيمة m في W: CALL (2) وMOVWF (1)، ثم $m - 1$ مروراً بـ1000 دورة، والمرور الأخير 999 دورة (DECFSZ تقفز ولا GOTO)، ثم RETURN (2):
$n = 1000\,m + 4$
ومع التعليمة `MOVLW m` التي تسبق CALL يصير $1000\,m + 5$. مثلاً `MOVLW D'20'` ثم `CALL tempo_ms` تدومان $20\,005\ \mu s$، أي 20 ms بخطأ 5 µs.
تأجيلات أطول: ثلاثة مستويات
لثانية واحدة نستدعي tempo_ms أربع مرات بالقيمة 250:
العدّ: $2 + 2 + 4 \times (1 + 250\,004) + (3 \times 4 - 1) + 2 = 1\,000\,037$ دورة، أي $1{,}000037\ s$.
قاعدتان لا يجوز نسيانهما:
- لكل مستوى عداده الخاص: tempo_1s تستعمل CPT3، وtempo_ms تستعمل CPT1 وCPT2. لو استعملت tempo_1s السجل CPT2 لمحته tempo_ms عند كل استدعاء؛
- عمق المكدس: البرنامج الرئيسي يستدعي tempo_1s التي تستدعي tempo_ms، أي مستويان من ثمانية.
برنامج كامل: وميض بالحلقات
TRISA لا يُكتب هنا لأن قيمته عند التشغيل 1Fh (كل المنفذ A مداخل) وهي المطلوبة. زمن `CALL t500ms`: $2 + 2 \times (1 + 250\,004) + 2 = 500\,014$ دورة. يتغير المخرج عند نهاية BSF أو BCF، فنعدّ زمن كل حالة من التعليمة التي تلي التبديل حتى التبديل الموالي ضمناً:
- المصباح مشتعل: `CALL t500ms` ثم BCF، أي $500\,014 + 1 = 500\,015\ \mu s$؛
- المصباح منطفئ: `CALL t500ms` ثم GOTO main ثم BSF، أي $500\,014 + 2 + 1 = 500\,017\ \mu s$؛
- الدور $T = 1\,000\,032\ \mu s$، أي تواتر $f \approx 0{,}99997\ Hz$: وميض بـ1 Hz عملياً.
ملاحظة للامتحان
- اكتب العلاقة التي تستعمل وقل ماذا تعدّ (هل تشمل CALL وRETURN؟). إذا أعطى الموضوع علاقة أخرى، فاتبعه.
- إذا طُلب «الزمن التقريبي» يكفي $3N_1N_2\,T_{cy}$، أما «عدد الدورات» فيُحسب تعليمة بتعليمة.
- القيمة 0 في العداد تعني 256 مروراً، لا صفراً.
- لا تكتب `MOVLW 250` بقصد مئتين وخمسين: دون بادئة يقرأ المجمِّع العدد بالست عشري (250h لا تدخل في بايت). اكتب `D'250'`.
4. البرنامج الرئيسي: التحكم في مصباح بزر ضاغط
دور البرنامج الرئيسي
البرنامج الرئيسي (programme principal) هو ترجمة دفتر الشروط: حلقة لا نهائية تقرأ المداخل، وتقرر، وتكتب المخارج، وتستدعي البرامج الفرعية عند الحاجة (التأجيل خاصة). نبنيه دائماً من الخوارزمية البيانية، ونترجم كل رمز بمقطع ثابت:
| في الخوارزمية | في المجمع (الزر على RA0، الضغط يعطي 1) | |---|---| | انتظر الضغط ($RA0 = 1$) | `att BTFSS PORTA,0` ثم `GOTO att` | | انتظر التحرير ($RA0 = 0$) | `rel BTFSC PORTA,0` ثم `GOTO rel` | | إذا كان $RA0 = 0$ اذهب إلى suite | `BTFSS PORTA,0` ثم `GOTO suite` | | شغّل المصباح / أطفئه | `BSF PORTB,0` / `BCF PORTB,0` | | اعكس حالة المصباح | `MOVLW B'00000001'` ثم `XORWF PORTB,F` | | تأجيل | `MOVLW …` ثم `CALL tempo_ms` | | ارجع إلى البداية | `GOTO …` |
في هذا الجزء نبرمج على الدارة نفسها (الشكل في الجزء 2: BP على RA0 مع سحب نحو الأرضي، والمرحل KA للمصباح H على RB0) تطبيقين نموذجيين للتحكم في مصباح بزر ضاغط: الضاغط العاكس (télérupteur) والمؤقت الضوئي (minuterie).
مشكلة ارتداد التماس
عند الضغط على زر ميكانيكي لا يُغلق التماس مرة واحدة: يرتد عدة مرات خلال بضعة ميلي ثوانٍ (عادة أقل من 10 ms) قبل أن يستقر. وكذلك عند التحرير. الإنسان لا يرى ذلك، لكن البرنامج يقرأ الزر كل بضع ميكروثوانٍ فيرى عدة ضغطات.
> إزالة الارتداد برمجياً (anti-rebond logiciel): عند اكتشاف تغيّر في حالة الزر ننتظر 20 ms (أطول من الارتدادات)، ثم نقرأ الزر من جديد لنتأكد من الحالة المستقرة.
يمكن أيضاً إزالة الارتداد مادياً بخلية RC مع قادح شميت أو بماسك RS كما رأينا في المنطق التعاقبي، لكن الحل البرمجي لا يكلف أي عنصر.
التطبيق 1: الضاغط العاكس (télérupteur)
دفتر الشروط: كل ضغطة على BP تعكس حالة المصباح H: ضغطة تشعله، والضغطة الموالية تطفئه. الضغطة الطويلة تُحسب مرة واحدة، والتشويش القصير (أقل من 20 ms) يُهمل.

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

يبيّن المخطط الزمني أن المصباح لا يتغير عند أول جبهة للزر، بل بعد 20 ms من بدايتها، عندما تكون الارتدادات قد انتهت، وأن ارتدادات التحرير (في الإطار الرمادي) لا تغيّر شيئاً.
شرح المقاطع:
- الانتظار `appui`: تدور الحلقة ما دام $RA0 = 0$؛
- التأكيد: بعد 20 ms نقرأ RA0 من جديد. إذا وجدناه 0 فما رأيناه كان تشويشاً (نبضة كهرومغنطيسية مثلاً) فنرجع إلى الانتظار دون أي تغيير؛
- العكس: `XORWF PORTB,F` مع القناع 01h تعكس البت 0 وحده. التعليمة `COMF PORTB,F` تعكس كل بتات المنفذ، فتفسد أي مخرج آخر قد يُضاف على المنفذ B؛
- انتظار التحرير ضروري: دونه يرى البرنامج الزر ما زال مضغوطاً فيعكس المصباح من جديد كل 20 ms، فيومض ما دام الإصبع على الزر؛
- التأجيل بعد التحرير يمنع ارتدادات التحرير من الظهور كضغطة جديدة.
مدة الرد: من بداية الضغط إلى تغيّر المصباح حوالي 20 ms (`MOVLW` ثم `CALL tempo_ms` تدومان $20\,005\ \mu s$، مع بضع دورات للاختبارات)، وهو زمن لا يحسّ به المستعمل.
التطبيق 2: المؤقت الضوئي (minuterie)
دفتر الشروط: ضغطة على BP تشعل المصباح H مدة 30 s ثم ينطفئ (إنارة درج). الضغط أثناء الإشعال لا يؤثر.
$30\ s = 120 \times 250\ ms$: نستدعي tempo_ms بالقيمة 250 مئة وعشرين مرة، بعداد ثالث CPT3.
الحساب الدقيق لمدة الإشعال (من التعليمة التي تلي BSF حتى BCF ضمناً، كما اصطلحنا في الجزء 3):
| المقطع | الدورات | |---|---| | `MOVLW D'120'` و`MOVWF CPT3` | 2 | | 120 مروراً: `MOVLW D'250'` ثم استدعاء tempo_ms | $120 \times (1 + 250\,004) = 30\,000\,600$ | | `DECFSZ CPT3,F` و`GOTO b_30s` | $3 \times 120 - 1 = 359$ | | `BCF PORTB,0` | 1 | | المجموع | $30\,000\,962$ |
فيدوم الإشعال $30\,000\,962\ \mu s \approx 30{,}001\ s$: الخطأ أقل من ميلي ثانية.
سلوك النظام:
- ضغطة أثناء الإشعال لا تُقرأ، لأن البرنامج داخل حلقة التأجيل؛
- إذا بقي الزر مضغوطاً عند نهاية 30 s، يجده الاختبار `attente` مضغوطاً فيشعل المصباح فوراً لمدة جديدة: هذا سلوك مقبول لإنارة درج؛
- لا حاجة لإزالة الارتداد هنا: الارتدادات تحدث في بداية 30 s وهي مهملة.
ملاحظة للامتحان
في البكالوريا يُعطى غالباً برنامج فرعي للتأجيل باسم temp أو tempo، ويُطلب إكمال البرنامج الرئيسي. مثال: «إذا وصل الإذن على RB1 (نشط بـ1) يُشغَّل المرحل على RA1 مدة التأجيل ثم يتوقف»:
| التعليمة | التعليق | |---|---| | `Start BTFSS PORTB,1` | هل الإذن موجود ($RB1 = 1$)؟ إن كان كذلك اقفز | | `GOTO Start` | لا: واصل الانتظار | | `BSF PORTA,1` | تشغيل المرحل | | `CALL temp` | التأجيل | | `BCF PORTA,1` | إيقاف المرحل | | `GOTO Start` | انتظار إذن جديد |
أخطاء شائعة
- اختبار الزر مرة واحدة دون انتظار التحرير: يتكرر العمل ما دام الزر مضغوطاً.
- استعمال `COMF PORTB,F` لعكس مخرج واحد.
- نسيان أن الحلقة الرئيسية لا ترى المداخل أثناء التأجيل البرمجي.
- استعمال سجل العداد نفسه في البرنامج الرئيسي وفي البرنامج الفرعي.
5. التأجيل بالمؤقت TMR0
لماذا TMR0؟
في التأجيل بالحلقات يُحسب الزمن من عدد التعليمات: كل تعديل في البرنامج الفرعي يغيّر الزمن، والتأجيلات الطويلة تحتاج عدة عدادات. المؤقت TMR0 (Timer 0) يعدّ وحده، مادياً، دورات الآلة بعد قاسم التردد: يكفي أن نختار نسبة القاسم K والقيمة الابتدائية $N_0$، ثم ننتظر العلم T0IF. الزمن لا يتعلق إذن بتعليمات الانتظار، ويمكن للبرنامج أن ينجز أعمالاً أخرى ثم يعود لاختبار العلم.
نذكّر بالسجلات الثلاثة (التفاصيل في درس هيكلة الميكرومراقب):
| السجل | العنوان والبنك | ما يهمنا هنا | |---|---|---| | OPTION_REG | 81h، البنك 1 | $T0CS = 0$ ساعة داخلية؛ $PSA = 0$ القاسم مسند إلى TMR0؛ PS2 وPS1 وPS0 نسبة القاسم | | TMR0 | 01h، البنك 0 | العداد ذو 8 بتات: نشحن فيه $N_0$ | | INTCON | 0Bh، البنكان | البت 2: العلم T0IF يصير 1 عند الفيضان، والبرنامج هو الذي يمسحه |
نسبة القاسم $K = 2^{PS+1}$: من 1:2 ($PS = 000$) إلى 1:256 ($PS = 111$).
علاقة الزمن
يزداد TMR0 بواحد كل $K\,T_{cy}$، وينطلق من $N_0$، فيفيض (من FFh إلى 00h) بعد $256 - N_0$ زيادة. بتكرار الفيضان n مرة:
$t = T_{cy}\,K\,(256 - N_0)\,n$
> اصطلاح: هذه العلاقة تعطي زمن عدّ المؤقت، وهي العلاقة المعتمدة في الامتحان. التعليمات التي تعيد شحن TMR0 بعد كل فيضان تضيف بضع ميكروثوانٍ نقدّرها في آخر هذا الجزء.
اختيار K و$N_0$
نريد أن يكون عدد الزيادات $256 - N_0$ عدداً صحيحاً لا يتجاوز 256:
- نحسب عدد دورات الآلة للزمن المطلوب: $t / T_{cy}$؛
- نقسمه إلى n فيضاناً، ونختار K بحيث يكون $t / (n\,K\,T_{cy})$ صحيحاً وأصغر من 256 أو مساوياً له؛
- نستنتج $N_0 = 256 - t / (n\,K\,T_{cy})$؛
- نكتب OPTION_REG.
قيم جاهزة مع بلور 4 MHz ($T_{cy} = 1\ \mu s$):
| الزمن | K | $256 - N_0$ | $N_0$ | n | PS2 PS1 PS0 | |---|---|---|---|---|---| | 1 ms | 4 | 250 | 6 | 1 | 001 | | 2 ms | 16 | 125 | 131 | 1 | 011 | | 8 ms | 64 | 125 | 131 | 1 | 101 | | 0,5 s | 16 | 125 | 131 | 250 | 011 | | 1 s | 64 | 125 | 131 | 125 | 101 | | 0,983 s تقريباً | 256 | 256 | 0 | 15 | 111 |
مثال على الاستحالة: 10 ms تساوي $10\,000$ دورة. دون قاسم ($K = 1$ مع $PSA = 1$) يلزم $10\,000$ زيادة، ومع K من 2 إلى 16 يلزم من 5000 إلى 625 زيادة، وهذا أكثر من 256؛ ومع K من 32 إلى 256 نجد 312,5 و156,25 و78,125 و39,0625، وهي ليست أعداداً صحيحة. لا يمكن إذن الحصول على 10 ms بالضبط بفيضان واحد؛ نأخذ 5 فيضانات من 2 ms.
مراحل البرنامج الفرعي
- في التهيئة (البنك 1): شحن OPTION_REG مرة واحدة؛
- شحن عداد الفيضانات n؛
- شحن TMR0 بالقيمة $N_0$ (البنك 0)؛ هذه الكتابة تمسح أيضاً عداد القاسم؛
- مسح العلم: `BCF INTCON,T0IF`؛
- انتظار $T0IF = 1$: `BTFSS INTCON,T0IF` ثم `GOTO`؛
- إنقاص العداد n: إذا لم يصل إلى الصفر نرجع إلى المرحلة 3؛
- `RETURN`.

يبيّن الشكل ثلاث قواعد زمنية من 2 ms: يصعد TMR0 من 131 إلى 255 ثم يفيض إلى 0، أي 125 زيادة بمعدل زيادة كل 16 µs، والفيضان يرفع العلم T0IF؛ يكتشف البرنامج العلم فينقص العداد CPT، ثم يعيد شحن 131 في TMR0 ويمسح T0IF، فتبدأ قاعدة جديدة. (عرض نبضة T0IF مكبّر في الرسم: هو بضع ميكروثوانٍ فقط.)
برنامج كامل: وميض بالمؤقت TMR0
دفتر الشروط: ما دامت القاطعة S (على RA0، مغلقة تعطي 1) مغلقة، يومض المصباح H (على RB0): 0,5 s مشتعلاً و0,5 s منطفئاً. إذا فُتحت S ينطفئ المصباح. الدارة هي دارة الجزء 2 مع S مكان BP.
اختيار القاعدة الزمنية من الجدول: $0{,}5\ s = 250 \times 2\ ms$، مع $K = 16$ و$N_0 = 131$. بتات OPTION_REG:
| $\overline{RBPU}$ | INTEDG | T0CS | T0SE | PSA | PS2 | PS1 | PS0 | |---|---|---|---|---|---|---|---| | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
أي $OPTION\_REG = 1000\,0011_{(2)} = 83h$: مقاومات السحب الداخلية معطلة (لا نحتاجها)، والساعة داخلية، والقاسم 1:16 مسند إلى TMR0.
ملاحظات:
- OPTION_REG يُكتب في البنك 1 داخل التهيئة. أما TMR0 وPORTB ففي البنك 0، وINTCON في البنكين؛
- الأسماء بالرقم صفر: TMR0 وT0IF، لا TMRO ولا TOIF بالحرف O، وإلا رفضها المجمِّع؛
- لو حُذفت `BCF INTCON,T0IF` لبقي العلم على 1 بعد الفيضان الأول، فلا تنتظر الحلقة att في المرورات الموالية (كل مرور 7 دورات فقط)، ويدوم t500ms أقل من 4 ms بدل 0,5 s.
الحساب بالعلاقة: $t = 1\ \mu s \times 16 \times (256 - 131) \times 250 = 500\,000\ \mu s = 0{,}5\ s$.
الزمن الحقيقي
بعد كل فيضان تمر بضع تعليمات قبل أن يُعاد شحن TMR0: اكتشاف العلم (حتى 3 دورات في حلقة الانتظار)، ثم DECFSZ وGOTO وMOVLW وMOVWF. والكتابة في TMR0 توقف زيادته دورتين (حسب وثيقة الصانع). يضيف ذلك حوالي 9 µs لكل قاعدة: كل قاعدة تدوم حوالي $2\,009$ µs بدل $2\,000$، فتدوم t500ms حوالي 502 ms. الخطأ حوالي 0,45 %، وهو مهمل في التحكم في مصباح أو دافعة.
عندما نحتاج دقة أكبر نترك TMR0 يعدّ دون إعادة شحن (قاعدة بفيضان كامل، $N_0 = 0$): نكتب فيه مرة واحدة في البداية ثم نكتفي بمسح العلم، فيفيض كل $256\,K$ دورة بالضبط مهما كانت التعليمات. هذا ما يفعله التأجيل بـ15 فيضاناً بقاسم 1:256 ($15 \times 65\,536 = 983\,040\ \mu s$).
مقارنة طريقتي التأجيل
| المعيار | الحلقات المتداخلة | المؤقت TMR0 | |---|---|---| | ما الذي يحدد الزمن؟ | عدد التعليمات المنفّذة | K و$N_0$ وعدد الفيضانات | | الحساب | عدّ الدورات تعليمة بتعليمة | العلاقة $T_{cy}\,K\,(256 - N_0)\,n$ | | السجلات المستعملة | عداد لكل مستوى | OPTION_REG وTMR0 وعداد واحد | | المعالج أثناء التأجيل | منشغل بالكامل | يمكنه اختبار T0IF بين أعمال أخرى | | أكبر زمن بعداد واحد | حوالي 0,197 s بحلقتين | $256 \times 65\,536\ \mu s \approx 16{,}8\ s$ |
أخطاء شائعة
- كتابة OPTION_REG دون `BSF STATUS,RP0`: تذهب القيمة إلى TMR0، ويبقى OPTION_REG على FFh ($T0CS = 1$ و$PSA = 1$)، فيعدّ TMR0 نبضات RA4 وينتظر البرنامج بلا نهاية إذا لم تصل نبضات.
- الرجوع من البرنامج الفرعي في البنك 1 (نسيان `BCF STATUS,RP0`): `BSF PORTB,0` تكتب في TRISB.
- نسيان مسح T0IF قبل الانتظار وبعد كل فيضان.
- اعتبار $PS = 000$ قاسماً 1:1: هو 1:2. القسمة على 1 تتطلب $PSA = 1$.
- كتابة `MOVLW 15` بقصد خمسة عشر فيضاناً: تُشحن 15h أي 21. الصحيح `MOVLW D'15'`.
- حساب $255 - N_0$ بدل $256 - N_0$.
6. تمارين محلولة بنمط البكالوريا
التمرين 1: محطة ملء القارورات
دفتر الشروط: يضع العامل قارورة تحت فوهة الملء ويضغط على الزر dcy. إذا كانت القارورة موجودة (الملتقط $p = 1$) تُفتح الصمّامة الكهربائية EV مدة 3 s ثم تُغلق، ثم ينتظر النظام نزع القارورة ($p = 0$) قبل قبول دورة جديدة. إذا ضُغط dcy دون قارورة، يشتعل المصباح H ما دام dcy مضغوطاً.
الربط: PIC16F84A ببلور 4 MHz. الزر dcy على RB1 والملتقط p على RB2، ولكل منهما مقاومة سحب نحو الأرضي (النشط يعطي 1). الرجل RA1 تغذي عبر مقاومة ومقحل مرحلاً KA يتحكم في EV (24 V~)، والرجل RA2 تغذي الثنائية الضوئية H عبر مقاومة. الأرجل غير المستعملة مداخل.
البرنامج:
الأسئلة
- حدّد المداخل والمخارج، واملأ TRISA وTRISB بالثنائي وبالست عشري، ثم تحقق منهما في البرنامج.
- لماذا تأتي `CLRF PORTA` قبل كتابة TRISA؟ وما دور التعليمتين `BSF STATUS,RP0` و`BCF STATUS,RP0`؟
- ارسم الخوارزمية البيانية للبرنامج الرئيسي (من debut إلى retrait).
- احسب العدد الدقيق لدورات البرنامج الفرعي tempo (مع CALL وRETURN)، ثم لدورات t3s، واستنتج مدة فتح الصمّامة.
- ما التعديل اللازم لملء يدوم حوالي 5 s؟ احسب المدة الجديدة لـt3s.
- ماذا يحدث لو حُذفت الحلقة retrait وبقي العامل ضاغطاً على dcy؟
الحل
- المداخل: dcy (RB1) وp (RB2)؛ المخارج: EV (RA1) وH (RA2).
| البت | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |---|---|---|---|---|---|---|---|---| | TRISA | — | — | — | 1 | 1 | 0 | 0 | 1 | | TRISB | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
$TRISA = 1\,1001_{(2)} = 19h$ و$TRISB = FFh$، وهما القيمتان `B'00011001'` و`B'11111111'` في البرنامج.
- ماسكة المنفذ A غير محددة عند التشغيل: بتصفيرها قبل جعل RA1 وRA2 مخرجين لا تُفتح الصمّامة ولا يشتعل H لحظة التهيئة. TRISA وTRISB في البنك 1: `BSF STATUS,RP0` تضع $RP0 = 1$ للوصول إليهما، و`BCF STATUS,RP0` ترجع إلى البنك 0 حيث PORTA وPORTB.
- الخوارزمية:

يبيّن الحل أن الاختبار الأول يقرأ dcy: إن كان 0 يُطفأ H ونعود. إن كان 1 يُختبر p: دون قارورة يشتعل H ونعود، ومع القارورة يُطفأ H وتُفتح EV، ثم التأجيل 3 s، ثم تُغلق EV، ثم ينتظر البرنامج $p = 0$ قبل العودة إلى البداية.
- البرنامج الفرعي tempo بحلقتين: $N_1 = 132$ و$N_2 = 250$:
$n_1 = 250 \times (3 \times 132 + 4) + 5$
أي $n_1 = 250 \times 400 + 5 = 100\,005$ دورة. البرنامج الفرعي t3s: CALL (2)، والشحن (2)، و30 استدعاءً لـtempo ($30 \times 100\,005 = 3\,000\,150$)، والحلقة DECFSZ وGOTO ($3 \times 30 - 1 = 89$)، وRETURN (2):
$n_2 = 3\,000\,245\ \text{cycles}$
مدة فتح EV من التعليمة التي تلي `BSF PORTA,1` حتى `BCF PORTA,1` ضمناً: $3\,000\,245 + 1 = 3\,000\,246\ \mu s \approx 3{,}0002\ s$.
- نشحن CPT3 بـ50: `MOVLW D'50'`. المدة: $2 + 2 + 50 \times 100\,005 + (3 \times 50 - 1) + 2 = 5\,000\,405$ دورة، أي حوالي 5,0004 s.
- بعد إغلاق EV يرجع البرنامج فوراً إلى debut: dcy ما زال مضغوطاً والقارورة الممتلئة ما زالت موجودة ($p = 1$)، فتُفتح EV من جديد فيفيض السائل. الحلقة retrait تفرض نزع القارورة قبل أي دورة جديدة.
التمرين 2: ممر الراجلين بالطلب
دفتر الشروط: في الحالة العادية الإشارة خضراء للسيارات (V) وحمراء للراجلين (Rp). عند الضغط على الزر BP للراجلين: برتقالية للسيارات (O) مدة 3 s، ثم حمراء للسيارات (R) وخضراء للراجلين (Vp) مدة 10 s، ثم حمراء للجميع مدة 2 s (تحرير الممر)، ثم العودة إلى الحالة العادية.
الربط: PIC16F84A ببلور 4 MHz. BP على RA0 مع سحب نحو الأرضي (الضغط يعطي 1). المصابيح عبر مضخمات: V على RB0، وO على RB1، وR على RB2، وVp على RB3، وRp على RB4. الأرجل الأخرى مداخل.

يعطي المخطط الزمني حالة كل مصباح بعد الضغط على BP في اللحظة 0: البرتقالي من 0 إلى 3 s، والأحمر للسيارات من 3 إلى 15 s، والأخضر للراجلين من 3 إلى 13 s، والأخضر للسيارات يعود عند 15 s.
البرنامجان الفرعيان المعطيان (المتغيران CPT في 0Ch وNSEC في 0Dh):
الأسئلة
- أعط TRISA وTRISB بالست عشري.
- أكمل جدول الحالات بقيمة PORTB بالثنائي وبالست عشري.
- ما قيمة OPTION_REG ليعمل t1s (ساعة داخلية، قاسم 1:64 مسند إلى TMR0، مقاومات السحب معطلة، $INTEDG = 0$، $T0SE = 0$)؟
- تحقق بالعلاقة أن t1s يدوم ثانية واحدة.
- اكتب البرنامج الكامل (التهيئة والبرنامج الرئيسي).
- ما مدة دورة العبور؟ وما الخطر إذا ضغط الراجلون على BP باستمرار؟ اقترح تحسيناً.
الحل
- RA0 مدخل والمنفذ A كله مداخل: $TRISA = 1Fh$. المخارج RB0 إلى RB4، وRB5 إلى RB7 مداخل: $TRISB = 1110\,0000_{(2)} = E0h$.
- جدول الحالات (البت 1 مصباح مشتعل):
| الحالة | Rp (RB4) | Vp (RB3) | R (RB2) | O (RB1) | V (RB0) | PORTB | |---|---|---|---|---|---|---| | عادية | 1 | 0 | 0 | 0 | 1 | $0001\,0001_{(2)} = 11h$ | | برتقالي 3 s | 1 | 0 | 0 | 1 | 0 | $0001\,0010_{(2)} = 12h$ | | عبور 10 s | 0 | 1 | 1 | 0 | 0 | $0000\,1100_{(2)} = 0Ch$ | | تحرير 2 s | 1 | 0 | 1 | 0 | 0 | $0001\,0100_{(2)} = 14h$ |
- $\overline{RBPU} = 1$ و$INTEDG = 0$ و$T0CS = 0$ و$T0SE = 0$ و$PSA = 0$، والقاسم 1:64 يوافق $PS = 101$: $OPTION\_REG = 1000\,0101_{(2)} = 85h$.
- قاعدة واحدة: $1\ \mu s \times 64 \times (256 - 131) = 8000\ \mu s = 8\ ms$، و125 قاعدة تعطي $125 \times 8\ ms = 1\ s$. (إعادة شحن TMR0 تضيف حوالي 9 µs لكل قاعدة، فيدوم t1s فعلياً حوالي 1,001 s.)
- البرنامج الكامل:
في التهيئة نكتب الحالة العادية في PORTB قبل TRISB، فتظهر الإشارات الصحيحة منذ أول لحظة. وأثناء تنفيذ t1s يكون المكدس محملاً بعنوانين (البرنامج الرئيسي ثم tsec)، وهذا بعيد عن الحد 8.
- مدة الدورة $3 + 10 + 2 = 15\ s$. إذا بقي BP مضغوطاً (أو ضُغط فور العودة) تبدأ دورة جديدة بعد بضع ميكروثوانٍ من عودة الأخضر للسيارات، فلا تمر السيارات تقريباً. التحسين: ضمان أخضر أدنى للسيارات، مثلاً 20 s، بإضافة أربع تعليمات قبل `GOTO attente`: `MOVLW B'00010001'` ثم `MOVWF PORTB` (الحالة العادية) ثم `MOVLW D'20'` ثم `CALL tsec`. أثناء هذه الثواني العشرين لا يُقرأ BP: الضغطة القصيرة فيها تضيع، ويُخدم الراجل الذي يبقى ضاغطاً أو يعيد الضغط بعدها.
التمرين 3: تصحيح برنامج وميض
دفتر الشروط: ثنائية ضوئية على RB0 تومض: حوالي 0,98 s مشتعلة وحوالي 0,98 s منطفئة، بقاعدة TMR0 بفيضان كامل (قاسم 1:256، ساعة داخلية) و15 فيضاناً. بلور 4 MHz. كتب تلميذ البرنامج الآتي، فبقيت الثنائية مشتعلة دون أن تنطفئ:
الأسئلة
- يقبل المجمِّع هذا البرنامج دون خطأ. لماذا بقيت الثنائية مشتعلة؟
- استخرج الأخطاء الأخرى، واشرح أثر كل منها لو كان هو الخطأ الوحيد في البرنامج.
- اكتب البرنامج المصحح.
- احسب مدة التأجيل بالعلاقة، ثم دور الوميض وتواتره.
الحل
- `MOVWF OPTION_REG` نُفذت بعد `BCF STATUS,RP0`، أي في البنك 0: العنوان 81h يصير 01h، فكُتبت القيمة 87h في TMR0. بقي OPTION_REG على FFh: $T0CS = 1$ (ساعة خارجية على RA4) و$PSA = 1$. لا تصل أي نبضة إلى RA4، فلا يزداد TMR0 ولا يرتفع T0IF أبداً: يبقى البرنامج في الحلقة b1 بعد أن أشعل الثنائية.
- الأخطاء الأخرى:
- `GOTO tempo` بعد `BCF PORTB,0`: نصل إلى tempo دون CALL، فتأخذ RETURN عنواناً لم يُحفظ ويضيع البرنامج. الصحيح `CALL tempo`؛
- `MOVLW 15`: دون بادئة يُقرأ العدد بالست عشري، أي 15h تساوي 21: 21 فيضاناً بدل 15، أي $21 \times 65\,536 = 1\,376\,256\ \mu s \approx 1{,}38\ s$. الصحيح `MOVLW D'15'`؛
- العلم T0IF لا يُمسح أبداً: بعد الفيضان الأول يبقى 1، فتمر الإنقاصات الأربعة عشر الباقية دون انتظار، ويدوم الاستدعاء الأول فيضاناً واحداً تقريباً (حوالي 65,5 ms). وفي كل استدعاء موالٍ يجد البرنامج $T0IF = 1$ منذ البداية فلا ينتظر إطلاقاً (أقل من 0,1 ms): تتبدل الثنائية آلاف المرات في الثانية فتبدو مشتعلة بإضاءة ضعيفة. يجب `BCF INTCON,T0IF` قبل الانتظار وبعد كل فيضان؛
- (احتياط) ماسكة RB0 لم تُحدد قبل جعله مخرجاً: نضيف `BCF PORTB,0` في البنك 0 قبل `BCF TRISB,0`.
- البرنامج المصحح:
- TMR0 لا يُعاد شحنه بعد `CLRF TMR0`، فكل فيضان كامل: $t = 1\ \mu s \times 256 \times 256 \times 15 = 983\,040\ \mu s \approx 0{,}983\ s$ (تضيف التعليمات بضع ميكروثوانٍ فقط، لأن TMR0 يعدّ دون توقف). الدور $T \approx 2 \times 0{,}983 = 1{,}966\ s$، والتواتر $f = 1/T \approx 0{,}51\ Hz$.
ملاحظة للامتحان
- ابدأ كل تمرين بجدول المداخل والمخارج: منه تخرج TRISA وTRISB وطريقة اختبار كل مدخل.
- في حساب الزمن اكتب العلاقة، ثم التعويض، ثم النتيجة بوحدتها، وحدد ما تعدّه (CALL وRETURN، أو زمن المؤقت فقط).
- في سؤال «صحّح البرنامج» ابحث بالترتيب عن: البنك عند كل TRIS وOPTION_REG، وأساس العدد في MOVLW، وCALL مقابل كل RETURN، ومسح العلم T0IF، وEND.
الجزء الأول من الدرس مفتوح للجميع، وبقيته مع الاشتراك.