أخذت نظرة نحو mbed. للوهلة الأولى ، بدا الأمر مثيرًا للاهتمام للغاية - إطار مستقل عن الحديد ، في C ++ ، مع دعم مجموعة من المتحكمات الدقيقة واللوحات التجريبية ، وهو مترجم عبر الإنترنت مع الاندماج في نظام التحكم في الإصدار. مجموعة من الأمثلة التي تقنع المزيد من أناقة الإطار. يمكن الوصول إلى جميع واجهات وحدة التحكم الدقيقة مباشرةً من الصندوق باستخدام الفئات المقابلة التي تم تنفيذها بالفعل. خذها مباشرة من الصندوق والبرنامج في C ++ دون النظر إلى ورقة البيانات من متحكم - أليس هذا حلما؟منصة الاختبار هي STM Nucleo F030 منذ فترة طويلة ، والتي تدعمها هذه المنصة. هناك الكثير من البرامج التعليمية الجيدة حول كيفية التسجيل وبدء المشروع الأول ، لن نتحدث عن ذلك. دعنا نذهب مباشرة إلى المثير للاهتمام.هذا المنتدى لا يحتوي على الكثير من الأجهزة الطرفية "على متن". LED وزر - هذا كله ثروة. حسنًا ، يومض المشروع الأول - "Hello world" الكلاسيكي من عالم المتحكم الدقيق - مع LED. هذا هو الرمز:#include "mbed.h"
DigitalOut myled(LED1);
int main() {
while(1)
{
wait_ms(500);
myled = myled ^ 1;
}
}
جميل بعد كل شيء ، أليس كذلك؟ لا تحتاج حقًا إلى إلقاء نظرة على ورقة بيانات وحدة التحكم!بالمناسبة ، "Hello world" الكلاسيكي متاح أيضًا خارج الصندوق:#include "mbed.h"
Serial pc(USBTX, USBRX);
int main() {
pc.printf("Hello world\n\r");
while(1)
{
}
}
أليس هذا رائعًا حقًا؟ "خارج الصندوق" هل لدينا وحدة تحكم تعمل فيها طباعة نظام قياسي؟ بالمناسبة ، يظهر المنفذ أيضًا فورًا عندما تكون اللوحة متصلة بالكمبيوتر ، جنبًا إلى جنب مع قرص افتراضي ، تحتاج فقط إلى نسخ ثنائي مجمّع - وستتم إعادة تمهيد اللوحة نفسها.ولكن مرة أخرى إلى الصمام الوامض. تجميع ، تنزيل على السبورة - يومض! ولكن بطريقة أو بأخرى ، بشكل واضح أكثر من مرة في الثانية ، ولكن على الأقل 5 مرات ... عفوًا ...أنا مشتت قليلاً بسبب "تجريد هولي" المذكور في العنوان. قرأت عن هذا المصطلح مع جويل سبولسكي. هنا هو اقتباسه:إذا قمت بتدريس مبرمجي لغة C ++ ، فسيكون أمرًا رائعًا إذا لم أكن بحاجة إلى إخبارهم عن تشار * وحساب المؤشر ، ولكن يمكنني أن أذهب مباشرة إلى الأسطر من مكتبة النماذج القياسية. ولكن في يوم من الأيام سيكتبون "foo" + "bar" وستظهر مشاكل غريبة ، ولكن لا يزال يتعين عليّ أن أشرح لهم ما هو char *. أو سيحاولون استدعاء وظيفة Windows باستخدام معلمة من النوع LPTSTR ولا يمكنهم ذلك حتى يتعلموا char * والمؤشرات وملفات رأس Unicode و wchar_t و TCHAR - كل ذلك يضيء من خلال الثقوب في التجريد. "لذا ، من المفارقات ، قانون تجريد هولي لا يسمح للمرء بأخذ وبدء برمجة متحكم دقيق مثل هذا (حتى لو كان هذا أبسط "عالم مرحبا" من ثلاثة أسطر) ، دون النظر إلى ورقة البيانات الخاصة به وليس لديه فكرة أن وحدة التحكم الدقيقة لديها نفس الداخل ، وكذلك ما وراء كل هذه الفئات في التجريد. على عكس وعود المسوقين ...لذا ، بعد أن تنهدنا بشدة ، نبدأ البحث. إذا لم يكن الأمر أمام عيني تجريدًا ، بل كان بيئة تطوير كاملة للتحكم ، فسيكون من الواضح مكان الحفر. ولكن أين يحفر في حالة الرمز أعلاه؟ إذا لم تسمح محتويات ملف mbed.h برؤية التجريد؟لحسن الحظ ، مكتبة mbed نفسها مفتوحة المصدر ، ويمكنك تنزيلها ورؤية ما بداخلها. اتضح ، مرة أخرى ، لحسن الحظ ، أنه من خلال التجريد للمبرمج ، يمكن الوصول إلى جميع سجلات وحدة التحكم الدقيقة تمامًا ، على الرغم من عدم الإعلان عنها صراحة. أفضل بالفعل. على سبيل المثال ، يمكنك التحقق من أن لدينا سرعة على مدار الساعة من خلال تشغيل هذا (الذي كان علي أن ألقي نظرة عليه في أوراق البيانات وحفر mbed بشكل جيد): RCC_OscInitTypeDef str;
RCC_ClkInitTypeDef clk;
pc.printf("SystemCoreClock = %d Hz\n\r", HAL_RCC_GetSysClockFreq());
HAL_RCC_GetOscConfig(&str);
pc.printf("->HSIState %d\n\r", str.HSIState);
pc.printf("->PLL.PLLState %d\n\r", str.PLL.PLLState);
pc.printf("->PLL.PLLSource %d\n\r", str.PLL.PLLSource);
pc.printf("->PLL.PLLMUL %d\n\r", str.PLL.PLLMUL);
pc.printf("->PLL.PREDIV %d\n\r", str.PLL.PREDIV);
pc.printf("\n\r");
HAL_RCC_GetClockConfig(&clk, &flat);
pc.printf("ClockType %d\n\r", clk.ClockType);
pc.printf("SYSCLKSource %d\n\r", clk.SYSCLKSource );
pc.printf("AHBCLKDivider %d\n\r", clk.AHBCLKDivider );
pc.printf("APB1CLKDivider %d\n\r", clk.APB1CLKDivider );
كل شيء سار بشكل جيد مع التردد ؛ كان 48 ميجاهرتز ، كما كان مقصودًا.حسنا ، حفر أكثر. نحن نحاول توصيل موقِّت بدلاً من wait_ms (): Timer timer;
timer.start();
while(1) {
myled ^= 1;
t1 = timer.read_ms();
t2 = timer.read_ms();
while (t2 - t1 < 500)
{
t2 = timer.read_ms();
}
}
إنها جميلة ، أليس كذلك؟ لكن الصمام لا يزال يومض 5 مرات أسرع من المطلوب ...حسنًا ، نحن نتعمق أكثر ونرى ما لدينا خلف الموقت السحري ، والذي ، كما وعدت الوثائق ، يمكن إنشاؤه بأي كمية ، بغض النظر عن عدد أجهزة ضبط الوقت الموجودة متحكم. رائع ، نعم ...وفي حالة STM F030 ، يتم إخفاء مؤقت جهاز التحكم TIM1 خلفه ، مبرمجًا لحساب القراد بالميكروثانية. وعلى هذا الأساس ، تم بناء كل شيء آخر بالفعل.لذا ، الجو حار بالفعل. تذكر ، قلت أن جميع السجلات متاحة؟ ننظر إلى ما يلي:pc.printf("PSC: %d\n\r", TIM1->PSC);
هاهو! هذا يضع حدًا للتحقيق حول من يقع اللوم: يتم إرسال الرقم 7 إلى وحدة التحكم. في سجل PSC (الكاتب؟ هل هو مجرد مصادفة؟) هناك قيمة ، عند الوصول سيؤدّي المؤقت إلى مقاطعة ويبدأ في العد مرة أخرى. وعلى هذا الانقطاع وتعليق عداد الميكروثانية. وهكذا ، عند تردد 48 ميجاهرتز ، يحدث الانقطاع مرة واحدة في كل ميكروثانية ، يجب ألا يكون هناك 7 على الإطلاق ، ولكن 47. و 7 وصلت ، على الأرجح ، في المرحلة الأولى من تحميل وحدة التحكم الدقيقة ، لأن يبدأ عند 8 ميغا هرتز ثم يعيد PLL بحيث يتم ضرب التردد في 6 ، مما يعطي 48 ميغا هرتز المطلوب. ويبدو أن المؤقت يبدأ التهيئة في وقت قريب جدًا ... علىمن يقع اللوم بالطبع. ولكن ماذا تفعل؟ أدت حفريات الإطار إلى سلاسل المكالمات التالية:أولاً: SetSysClock_PLL_HSI () -> HAL_RCC_OscConfig () ، HAL_RCC_ClockConfig () -> HAL_InitTick () - عند تغيير التردد ، اتصل بالوظيفة التي تحدد علامة الميكروثانية.ثانيًا: HAL_Init () -> HAL_InitTick () -> HAL_TIM_Base_Init () -> TIM_Base_SetConfig () -> TIMx-> PSC - تم استدعاؤه من إحدى أكثر الوظائف العالمية ، يكتب HAL_InitTick القيمة المطلوبة في سجل PSC ، اعتمادًا على الساعة الحالية الترددات ...ما يبقى لغزًا بالنسبة لي هو أنه بالنسبة لعائلة STM F0 ، لا يتم استدعاء السلسلة الثانية. لم أجد أي مكالمات للدالة HAL_Init ()!علاوة على ذلك ، إذا نظرت إلى تنفيذ HAL_InitTick () ، فسيكون هناك في بدايته الأسطر التالية: static uint32_t ticker_inited=0;
if(ticker_inited)return HAL_OK;
ticker_inited=1;
استدعاء هذه الوظيفة مرة واحدة بالضبط. أي أنه يمكنك الاتصال بها عدة مرات كما تشاء ، لكنها لن تفعل أي شيء لجميع المكالمات اللاحقة. وحقيقة أن الإطار يسميها في حالة حدوث تغيير في تردد الساعة غير مجدية ...كنت كسولًا جدًا لإعادة بناء المكتبة ، لذلك اتخذ التصحيح في هذا الشكل: السطر الأول في الوظيفة الرئيسية كان:TIM1->PSC = (SystemCoreClock / 1000000) - 1;
بعد ذلك ، بدأ LED في الوميض أخيرًا بالتردد الصحيح لـ 1 هرتز ...أتساءل ما هي المرحلة التي يمكن أن يتوقفها شخص افتراضي ، الذي أقنع المسوقين بمحاولة جعل برمجة وحدة التحكم الدقيقة سهلة جدًا من الصفر؟ إذا لم يواجه المتحكمات الدقيقة من قبل؟حسنًا ، إذا لم أكن متعبًا للغاية ، فإننا نمضي قدمًا. إن وميض LED أمر جيد ، ولكنه ليس مثيرًا للاهتمام. اريد شيئا اكثر. تم العثور على مستشعر درجة الحرارة DS1820 من جهاز استشعار أكبر ، وتم الانتهاء من المكتبة النهائية له. سهل الاستخدام ، يخفي تعقيد العمل مع هذا المستشعر بالداخل. كل ما هو مطلوب لكتابة شيء مثل#include "DS1820.h"
DS1820 probe[1] = {D4};
probe[0].convert_temperature(DS1820::all_devices);
float temperature = probe[0].temperature('c');
ما رأيك حدث بعد التجميع والإطلاق؟ بشكل صحيح. لم ينجح :)التجريد متسرب ، نعم. حسنًا ، يبدو أن دراسة أخرى مثيرة للداخلية الداخلية تنتظرنا. وتفاصيل المستشعر نفسه.جهاز الاستشعار مثير للغاية. مع واجهته الرقمية. في سطر واحد ، أي يعمل في وضع نصف مزدوج. يبدأ جهاز التحكم في تبادل البيانات ، ويرسل المستشعر سلسلة من البتات استجابة. خاصة لمثل هذه الحالات ، يحتوي إطار عمل mbed على فئة DigitalInOut ، والتي يمكنك من خلالها تغيير اتجاه عملها على دبوس GPIO على الفور. شيء من هذا القبيل:bool DS1820::onewire_bit_in(DigitalInOut *pin) {
bool answer;
pin->output();
pin->write(0);
wait_us(3);
pin->input();
wait_us(10);
answer = pin->read();
wait_us(45);
return answer;
}
يرسل جهاز التحكم نبضة "1 -> 0 -> 1" إلى المستشعر ، وهي إشارة لإرسالها بتة واحدة استجابة. ما الذي قد لا يعمل هنا؟ إليك قطعة من ورقة بيانات المستشعر:
كما ترى ، بعد أن أرسلنا دفعة إلى المستشعر ، لقراءة البت ، لدينا ما يصل إلى 15 ميكروثانية.نقوم بتوصيل مرسمة الذبذبات ونرى أن المستشعر يرسل قطعًا إلى نفسه ، كما هو موضح في ورقة البيانات. ولكن هنا يقرأ المتحكم القمامة. ماذا قد يكون السبب؟لن أكتب كثيرًا عن كيف جئت لتنفيذ هذا الرمز: pin->output();
t1 = timer.read_us();
pin->input();
t4 = timer.read_us();
pc.printf("Time: %d us\n\r", t4-t1);
حسنًا ، من يخمن ، دون قراءة المزيد ، المدة التي تستغرقها وحدة التحكم الدقيقة بتردد ساعة 48 ميجاهرتز لتغيير اتجاه دبوس GPIO واحد (في الواقع ، هو كتابة واحدة إلى السجل ، إذا كان ذلك) ، إذا تم ذلك باستخدام إطار عمل mbed المكتوب في C ++ باستخدام طبقة مستقلة من الحديد؟13 ميكروثانية.ولقراءة استجابة المستشعر ، لدينا ما يصل إلى 15. وحتى إذا قمنا بإزالة wait_us (10) تمامًا من الرمز أعلاه ، فإن الأمر answer = pin-> read ()؛ يستغرق أيضًا بعض الوقت أطول من 2 ميكروثانية. ما يكفي لعدم توفر الوقت لقراءة الجواب.لحسن الحظ ، كان من الممكن إرسال دفعة إلى STM دون تغيير اتجاه GPIO. في وضع الإدخال ، عند توصيل المقاوم PullDown المدمج ، يكون التأثير هو نفسه. ومرة أخرى ، لحسن الحظ ، لا يتطلب استدعاء وضع> pin (PullUp) بدلاً من pin-> input () سوى 6 ميكروثانية.ما يكفي من الوقت لقراءة الجواب.وأخيرًا ، ثقب آخر في التجريد. بعد عمل DS1820 ، كان المستشعر التالي الذي تم توفيره هو DHT21 ، وهو مستشعر يقيس درجة الحرارة والرطوبة. أيضا مع واجهة 1 سلك ، ولكن هذه المرة استجابة ترميز مع مدة النبض. يبدو أن الحل الواضح هو تعليق هذا الخط على مقاطعة إدخال GPIO ، مما يجعل من السهل قياس مدة النبض. وهناك حتى طبقة في الأفق لهذا. بل إنه يعمل كما هو موثق.لكن المشكلة هي أن المستشعر يعمل أيضًا في نصف مزدوج. ولكي يبدأ في إرسال البيانات ، يحتاج إلى إرسال دفعة "1 -> 0 -> 1" كما في المثال أعلاه.وهنا mbed لا يسمح بذلك. أو تعلن المنفذ على أنه DigitalInOut ، ولكن بعد ذلك لا يمكنك استخدام المقاطعات. أو تعلن عن المنفذ على أنه InterruptIn ، ولكن بعد ذلك لا يمكنك إرسال أي شيء إليه. لسوء الحظ ، لم تعمل خدعة PullDown هنا. يحتوي المستشعر على PullUp مدمج ، و متحكم PullDown المدمج لا يكفي لسحب دبوس إلى 0.كان علي أن أقوم بدورة استقصاء خط أقل جمالا نتيجة لذلك. كل شيء ، بالطبع ، كان يعمل على هذا النحو ، لكنه لم يعمل بشكل جميل. والتي لن تكون مشكلة في الوصول المباشر إلى السجلات ...ها هو. محاولة لإجراء تجريد بحيث يمكن لأي شخص أن يبدأ على الفور في برمجة وحدات التحكم الدقيقة الجديرة بالاهتمام. العديد من الأشياء مبسطة حقًا ، بل وتعمل. ولكن ، مثل أي تجريد عالي المستوى ، فإن هذا التجريد مليء أيضًا بالثغرات. وفي التصادم مع أي من الثقوب العديدة يجب أن تنزل من السماء إلى الأرض.