حيلة أردوينو: كيفية مشاركة برنامج خدمة المقاطعة ISR بين المكاتب والكود الرئيسي
المشكلة
أحد المحدوديات التي يسببها استخدام لغة وبيئة الأردوينو للتطوير SDK هي التضارب الذي قد يحصل عند استخدام المكاتب وتحديداً تضارب استخدام المقاطعات ISR (Interrupt Service Routine)، حيث تقوم كل مكتبة بتعريف define المقاطعة في ملف الكود المصدري ولاحقاً قد يحتاج المطور في الكود الرئيسي للبرنامج لاستخدام نفس هذه المقاطعة مما يؤدي إلى خطأ في الترجمة compilation بسبب تعريف نفس المقاطعة مرتين
على سبيل المثال: إذا تم استخدام المكتبة المضمنة في نواة بيئة الأردوينو Arduino core والمسماة SoftwareSerial فلن يستطيع المطور لاحقاً استخدام أي من المقاطعات الثلاثة المتوفرة للوحدة PCINT (Pin Change Interrupt) (في متحكم Atmega328 على سبيل المثال) والتي تستخدم لاكتشاف تغير السوية المنطقية لإشارات الدخل. بالرغم من حاجة المطور للمكتبة وأيضاً المقاطعات بنفس الوقت. إن أمثلة أخرى كثيرة يمكن ذكرها لمثل هذه الحالة ومنها مقاطعات المؤقتات Timers.
في هذه التدوينة المصغرة سوف نناقش حل مقترح بحيث نحافظ على روح البساطة التي تقدمها بيئة التطوير أردوينو وبنفس الوقت إمكانية الاستفادة من المقاطعات لأغراض غير المكتبة. هذا الحل يمكن تطبيقه في المكاتب التي تأتي مضمنة مع البرنامج أردوينو أو من أجل مكاتب طرف ثالث يتم تطويرها مستقبلاً.
الحل المقترح: استخدام توابع التوسيع handler (Hook) Functions
قد لا يكون من المناسب أن يقوم المطور بتعريف المقاطعات في الكود الرئيسي حيث أن المكاتب تقدم حل متكامل وجاهز للاستخدام مباشرة. الفكرة المقترحة تعتمد على طريقة لتوسيع المقاطعة لاحقاً بإضافة توابع تسمى Handler وتسمى أحياناً اصطلاحاً hook أو call-back. هذا يمكن أن يتم بإضافة هذه التوابع على أن يتم ربطها لاحقاً بتوابع أخرى يتم تعريفها من قبل المطور.
من أجل توضيح الحل المقترح سنكتب مكتبة كمثال لدارة Arduino UNO بحيث يتم تعريف المقاطعة TIMER1_COMPA_vect للمؤقت Timer1. سوف نعرف المقاطعة ونضيف شيء ما سنراه لاحقاً لإضافة ميزة التوسيع. سيتم هذا من خلال ميزة في لغة السي تسمى function pointer، وهو مؤشر ولكن ليس مؤشر لمتحول وإنما مؤشر لتابع. لمعرفة المزيد عن مؤشرات التوابع يمكن قراءة المقدمة الجيدة من موقع Geeksforgeeks.
لإضافة هذا إلى المكتبة سنتبع الخطوات التالية:
1- إضافة تعريف لمؤشر لتابع في الملف الرأسي للمكتبة
static void (*__timer1Hook)(void);
2- إضافة method من أجل إسناد التابع الذي يكتبه المطور إلى المؤشر الذي تم تعريفه سابقاً.
void Timer1::timer1Hook(void (*function)(void)) { __timer1Hook = function; }
3- استدعاء المؤشر للتابع في جسم برنامج المقاطعة
ISR (TIMER1_COMPA_vect) { Serial.println("TIMER1 ISR"); __timer1Hook(); }
مثال المكتبة كاملاً
demoLib.h
#ifndef demoLib_h #define demoLib_h #if (ARDUINO >= 100) #include "Arduino.h" #else #include "WProgram.h" #endif static void (*__timer1Hook)(void); class Timer1 { public: void start(); void timer1Hook(void (*function)(void)); private: void t1Init(void); }; extern Timer1 t1; #endif
demoLib.cpp
#include"demoLib.h" Timer1 t1; void Timer1::start() { this->t1Init(); } void Timer1::timer1Hook(void (*function)(void)) { __timer1Hook = function; } void Timer1::t1Init() { TCCR1B = (1<<WGM12) |(1<<CS12)|(1<<CS10) ; TCCR1A = 0; TCNT1 = 0; OCR1A = 15625; // T/C match it evey 1000ms TIMSK1 = (1<<OCIE1A) | (1<<ICIE1) ; } ISR (TIMER1_COMPA_vect) { Serial.println("TIMER1 ISR"); __timer1Hook(); }
Application.ino
#include "demoLib.h" void timer1extend() { Serial.println("Extended"); } void setup() { // put your setup code here, to run once: Serial.begin(9600); t1.start(); t1.timer1Hook(timer1extend); } void loop() { // put your main code here, to run repeatedly: }
أمثلة حقيقية
-
-
مكتبات من تطوير طرف ثالث مثل Timer library والتي تستخدم لكتابة مجدول مهام بسيط scheduler. إن الفكرة الأساسية من هذه المكتبة هو إضافة مؤشرات لتوابع في المقاطعة لكل مهمة task وفي المقاطعة يتم تحديد أي مهمة يجب أن تنفذ الآن.