مكتبة مولد جامع التعليمات البرمجية للميكروكونترولر AVR. الجزء 4

← الجزء 3. معالجة غير مباشرة والتحكم في التدفق
الجزء 5. تصميم تطبيقات متعددة الخيوط.


مكتبة مولد المجمع كود ل AVR Microcontrollers


الجزء 4. برمجة الأجهزة الطرفية والتعامل مع المقاطعات


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


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


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


لنبدأ بأبسط الأجهزة وأكثرها شيوعًا - منفذ الإدخال / الإخراج الرقمي. يجمع هذا المنفذ ما يصل إلى 8 قنوات ، يمكن تكوين كل منها بشكل مستقل للإدخال أو الإخراج. يوضح التوضيح إلى 8 أن بنية التحكم تتضمن إمكانية تعيين وظائف بديلة لبتات المنافذ الفردية ، مما يستبعد استخدامها كمنافذ للماء / الإخراج ، مما يقلل من عدد البتات المتاحة. يمكن إجراء الإعداد والمزيد من العمل على مستوى البت المنفصل ، وعلى مستوى المنفذ ككل (كتابة وقراءة كل 8 بتات بأمر واحد). تحتوي وحدة التحكم Mega328 المستخدمة في الأمثلة على 3 منافذ: B و C و D. في الحالة الأولية ، من وجهة نظر المكتبة ، تكون تصريفات جميع المنافذ محايدة. هذا يعني أنه من أجل التنشيط ، من الضروري الإشارة إلى طريقة استخدامها. في حالة محاولة الوصول إلى منفذ غير نشط ، سيقوم البرنامج بإنشاء خطأ في التحويل البرمجي. يتم ذلك من أجل القضاء على التعارضات المحتملة عند تعيين وظائف بديلة. لتبديل المنافذ إلى وضع الإدخال / الإخراج ، استخدم أوامر الوضع لتعيين وضع بت واحد ، والاتجاه لتعيين وضع جميع وحدات بت المنفذ بأمر واحد. من وجهة نظر البرمجة ، تكون جميع المنافذ متماثلة ويتم وصف سلوكها بواسطة فصل واحد.


var m = new Mega328(); m.PortB[0].Mode = ePinMode.OUT;// 0   B    m.PortC.Direction(0xFF);//       m.PortB.Activate(); //   m.PortC.Activate(); //  C //   m.PortB[0].Set(); //  0   B  1 m.PortB[0].Clear();//  0   B  0 m.PortB[0].Toggle();//  0   B   m.PortC.Write(0b11000000);// 6  7        var rr = m.REG(); //     rr.Load(0xC0); m.PortC.Write(rr);//      rr var t = AVRASM.Text(m); 

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


  m.PortB.Activate(); //  B m.PortC.Activate(); //  C Bit dd = m.BIT(); //     Register rr = m.REG(); //     m.PortB[0].Read(dd); //  0   B m.PortC.Read(rr);//      rr var t = AVRASM.Text(m); 

في هذا المثال ، ظهر نوع بيانات بت جديد. أقرب تماثل من هذا النوع باللغات عالية المستوى هو النوع المنطقي . يتم استخدام نوع بيانات البت لتخزين جزء واحد فقط من المعلومات ويسمح باستخدام قيمته كشرط في العمليات المتفرعة. من أجل حفظ الذاكرة ، يتم دمج متغيرات البت أثناء التخزين في كتل بطريقة يتم استخدام سجل RON واحد لتخزين 8 متغيرات من النوع Bit . بالإضافة إلى النوع الموصوف ، تحتوي المكتبة على نوعين آخرين من بيانات البت: Pin ، الذي له نفس وظيفة Bit ، لكنه يستخدم سجلات IO و Mbit لتخزين متغيرات البت في ذاكرة RAM. دعونا نرى كيف يمكنك استخدام متغيرات بت لتنظيم الفروع


 m.IF(m.PortB[0], () => AVRASM.Comment(",   = 1")); var b = m.BIT(); b.Set(); m.IF(b, () => AVRASM.Comment(",   b ")); 

يتحقق السطر الأول من حالة منفذ الإدخال ، وإذا تم إدخال رمز الكتلة الشرطية عند الإدخال 1. يحتوي السطر الأخير على مثال حيث يتم استخدام متغير نوع Bit كشرط متفرعة.


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


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


 var m = new Mega328(); m.FCLK = 16000000; //   m.CKDIV8 = false; //     //    Timer1 m.Timer1.Clock = eTimerClockSource.CLK256; //   m.Timer1.OCRA = (ushort)((0.5 * m.FCLK) / 256); //    A m.Timer1.Mode = eWaveFormMode.CTC_OCRA; //    m.Timer1.Activate(); //    Timer1 

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


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


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

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


 var m = new Mega328(); m.FCLK = 16000000; m.CKDIV8 = false; var bit1 = m.PortB[0]; bit1.Mode = ePinMode.OUT; var bit2 = m.PortB[1]; bit2.Mode = ePinMode.OUT; m.PortB.Activate(); //  0  1   B   //     m.Timer0.Clock = eTimerClockSource.CLK; m.Timer0.OCRA = 50; m.Timer0.OCRB = 170; m.Timer0.Mode = eWaveFormMode.PWMPC_TOP8; //   m.Timer0.OnCompareA = () => bit1.Set(); m.Timer0.OnCompareB = () =>bit2.Set(); m.Timer0.OnOverflow = () => m.PortB.Write(0); m.Timer0.Activate(); m.EnableInterrupt(); //  //   m.LOOP(m.TempH, (r, l) => m.GO(l), (r) => { }); 

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


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


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


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


  var m = new Mega328(); m.PortD[0].Mode = ePinMode.OUT; m.PortD.Write(0x0C); // pull-up   m.INT0.Mode = eExtIntMode.Falling; //  INT0  . m.INT0.OnChange = () => m.PortD[0].Set(); //      1 m.INT1.Mode = eExtIntMode.Falling; //  INT1  . m.INT1.OnChange = () => m.PortD[0].Clear(); //     //  m.INT0.Activate(); m.INT1.Activate(); m.PortD.Activate(); m.EnableInterrupt(); //   //  m.LOOP(m.TempL, (r, l) => m.GO(l), (r, l) => { }); 

سمح لنا استخدام المقاطعات الخارجية بحل مشكلتنا بالبساطة والوضوح قدر الإمكان.


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


 m.Timer0.CompareModeA = eCompareMatchMode.Set; 

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


آخر جهاز سننظر إليه في هذا الجزء من المقالة سيكون المنفذ التسلسلي USART. وظائف هذا الجهاز واسعة للغاية ، ولكن حتى الآن سنتطرق فقط إلى واحدة من أكثر حالات الاستخدام شيوعًا لهذا الجهاز.


حالة الاستخدام الأكثر شيوعًا لهذا المنفذ هي توصيل جهاز تسلسلي بمعلومات نصية للإدخال / الإخراج. قد يبدو جزء الكود المتعلق بإعدادات المنفذ في هذه الحالة كما يلي


 m.FCLK = 16000000; //   m.CKDIV8 = false; //     m.Usart.Mode = eUartMode.UART; //    UART m.Usart.Baudrate = 9600; //   9600  m.Usart.FrameFormat = eUartFrame.U8N1; //   8N1 

تتزامن الإعدادات المحددة مع الإعدادات الافتراضية لـ USART في المكتبة ، وبالتالي ، يمكن تخطيها جزئيًا أو كليًا في نص البرنامج.


النظر في مثال صغير نخرج فيه نصًا ثابتًا إلى الجهاز الطرفي. من أجل عدم تضخيم الشفرة ، فإننا نقتصر على الإخراج إلى محطة "Hello world!" الكلاسيكية. في بداية البرنامج.


  var m = new Mega328(); var ptr = m.ROMPTR(); //      m.CKDIV8 = false; m.FCLK = 16000000; //      m.Usart.Mode = eUartMode.UART; m.Usart.Baudrate = 9600; m.Usart.FrameFormat = eUartFrame.U8N1; //         m.Usart.OnTransmitComplete = () => { ptr.MLoadInc(m.TempL); m.IF(m.TempL!=0,()=>m.Usart.Transmit(m.TempL)); }; m.Usart.Activate(); m.EnableInterrupt(); //   var str = Const.String("Hello world!"); //   ptr.Load(str); //     ptr.MloadInc(m.TempL); //    m.Usart.Transmit(m.TempL); //   . m.LOOP(m.TempL, (r, l) => m.GO(l), (r,l) => { }); 

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


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


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

Source: https://habr.com/ru/post/ar463627/


All Articles