
أنا صاحب جهاز رائع - مسجل GPS Holux M-241. الشيء مريح ومفيد للغاية عند السفر. بمساعدة مسجل ، أكتب مسار GPS لرحلة ، يمكنك من خلالها رؤية طريقك بالتفصيل ، وكذلك إرفاق الصور التي تلتقطها بإحداثيات GPS. لديه أيضًا شاشة صغيرة تعرض معلومات إضافية - ساعات وسرعة حالية والارتفاع والاتجاه وعداد المسافات وأكثر من ذلك بكثير.
هنا كتبت ذات مرة مراجعة قصيرة.
مع كل مزايا قطعة الحديد ، بدأت في النمو منها. أفتقد بعض الأشياء الصغيرة ولكنها مفيدة: عدد قليل من عدادات المسافات ، تظهر السرعة الرأسية ، تقيس معلمات قسم المسار. يبدو أن هناك أشياء صغيرة ، لكن شركة Holux وجدت أن هذا ليس مفيدًا بما يكفي للتنفيذ في البرامج الثابتة. أيضًا ، لا أحب بعض معلمات الأجهزة ، وبعض الأشياء أصبحت قديمة منذ 10 سنوات ...
في مرحلة ما ، أدركت أنه يمكنني بنفسي إنشاء مسجل بميزات مثل ما أحتاج إليه. لحسن الحظ ، جميع المكونات الضرورية رخيصة للغاية وبأسعار معقولة. بدأت في جعل التنفيذ الخاص بي يعتمد على Arduino. تحت القص ، مذكرات البناء حيث حاولت رسم حلولي الفنية.
تحديد الميزات
سيتساءل الكثيرون لماذا أحتاج إلى إنشاء مسجِّل خاص بي ، إذا كان هناك بالتأكيد شيء جاهز للمصنعين البارزين. ربما. لأكون صريحًا ، لم أكن أبحث عنه حقًا. ولكن بالتأكيد سيكون هناك شيء مفقود. على أي حال ، هذا المشروع من المعجبين بي. لماذا لا نبدأ في بناء جهاز أحلامنا؟
لذلك ، على ما أقدر جهاز Holux M-241.
- تصنع الشاشة "صندوقًا أسود" ، تتاح نتائجه فقط بعد الرحلة ، وهي أداة مريحة للغاية ، وقراءاتها متاحة هنا والآن. وجود شاشة يجعل كل الميزات الموجودة في هذه القائمة ممكنة تقريبًا.
- الساعة مفيدة في حد ذاتها. في رحلات نظام تحديد المواقع العالمي (GPS) ، غالبًا ما يتبين أن المسجل المتدلي على سلسلة حول رقبته أقرب من هاتف محمول في جيبه أو في حقيبة ظهر. تدعم الساعة جميع المناطق الزمنية (وإن كان مع التبديل اليدوي)
- يسمح لك زر POI بوضع علامة على الإحداثيات الحالية على المسار. على سبيل المثال ، لملاحظة معلم انزلق خارج نافذة الحافلة ، والتي أريد البحث عنها لاحقًا.
- باستخدام عداد المسافات ، يمكنك قياس المسافة المقطوعة من نقطة ما. على سبيل المثال ، المسافة المقطوعة يوميًا أو طول المسار.
- تساعدك السرعة والارتفاع والاتجاه الحالي في العثور على نفسك في الفضاء
- تتيح لك قابلية البقاء على قيد الحياة لمدة 12-14 ساعة من بطارية AA واحدة في معظم الحالات عدم التفكير في مشكلات إمدادات الطاقة. على سبيل المثال تكفي دائمًا تقريبًا ليوم كامل من السفر.
- مدمجة وسهلة الاستخدام - الأشياء في العالم الحديث لطيفة للغاية
ومع ذلك ، يمكن القيام ببعض الأشياء بشكل أفضل قليلاً:
- يتم كتابة النظام الفرعي للطاقة على بطاريات AA من قبل الكثير باعتباره زائد محدد - بطارية واحدة تدوم لفترة طويلة ، ويمكنك تجديد الإمداد في أي برية. يمكنك تخزين ما لا يقل عن شهر من التخييم المستقل.
لكن بالنسبة لي ، عمر البطارية هو البواسير المطلقة. يجب أن تحمل حفنة من البطاريات ومن يدري مدى جودتها (فجأة كانت ملقاة على رف لمدة 5 سنوات وتفريغها ذاتيًا بالفعل). مع البطاريات ، يكون النزف أكبر. لا يمكن شحن الشاحن إلا في أزواج. علينا تفريغ البطاريات حتى تكون بنفس درجة التفريغ. ونتيجة لذلك ، لا تتذكر أبدًا المكان الذي تم تصريفه بالفعل ، والمكان الذي لم يتم فيه التصريف بعد.
لمدة 6 سنوات من استخدام المسجل ، انتهى بي الأمر فقط في البرية بدون كهرباء بضع مرات. كقاعدة ، أحصل على منفذ على الأقل مرة واحدة في اليوم. في هذه الحالة ، ستكون بطارية الليثيوم المدمجة أكثر ملاءمة. حسنًا ، في الحالات القصوى ، لديّ بنك مدفعي.
- يتم الإشارة إلى درجة التفريغ بغباء شديد - يبدأ المؤشر في الوميض عندما تكون البطارية على وشك التفريغ. علاوة على ذلك ، يمكن أن يموت في 5 دقائق ، وربما يعمل ساعة أخرى. من السهل جدًا تفويت هذه اللحظة وفقد جزء من السجل.
- كشخص مهتم بالطيران ، سيكون من المثير للاهتمام للغاية ملاحظة السرعة الرأسية الحالية .
- عدد قليل من أجهزة قياس المسافات - غالبًا ما يكون من المثير للاهتمام قياس أكثر من مسافة واحدة. على سبيل المثال ، المسافة المقطوعة في اليوم وللرحلة بأكملها.
- تتم إعادة تعيين عداد المسافات عند إيقاف تشغيل الجهاز أو عند استبدال البطارية. هذا غير مريح للغاية. إذا توقفت لتناول وجبة في مقهى ، فلا يمكن إيقاف تشغيل مسجل GPS لأنه سيتم إعادة تعيين القيمة. عليه أن يتركها قيد التشغيل ويستمر في رياح الكيلومترات وأكل البطارية. سيكون من الملائم أكثر أن تكون قادرًا على إيقاف عداد المسافات مؤقتًا وحفظ القيم بين الشوائب.
- قياس معلمات الموقع . عند التزلج ، على سبيل المثال ، أنا مهتم بطول الهبوط والارتفاع والمتوسط والسرعة القصوى على الموقع والوقت المستغرق. ما تريد معرفته على الفور ، وليس في المنزل عند تنزيل المسار.
- الدقة ضعيفة. عندما تتحرك بسرعة - لا شيء آخر. ولكن عندما تكون السرعة صغيرة على المضمار ، تكون "الضوضاء" + - 50 متر مرئية بوضوح. ولمدة ساعة من الوقوف ، يمكنك "الإصرار" على بعد كيلومتر واحد تقريبًا. لقد ذهبت فائدة التكنولوجيا لمدة 10 سنوات إلى الأمام بكثير ، وتعطي أجهزة الاستقبال الحديثة دقة أكبر بكثير.
- تبلغ سرعة دمج المسارات 38400 فقط. لا ، حسنًا ، ليس من الخطورة في عام 2017 استخدام منفذ COM لنقل كميات كبيرة من البيانات. يستغرق دمج 2 ميغابايت من الفلاش الداخلي أكثر من 20 دقيقة.
بالإضافة إلى ذلك ، لا يستطيع كل برنامج استيعاب تنسيق المسارات المدمجة. الأداة المساعدة الأصلية سيئة للغاية. لحسن الحظ ، هناك BT747 ، التي يمكنها دمج المسار بشكل كاف وتحويله إلى نوع من التنسيق القابل للهضم.
- حجم محرك الأقراص المحمول هو 2 ميغابايت فقط. من ناحية ، هذا يكفي لرحلة لمدة أسبوعين مع توفير نقاط كل 5 ثوانٍ. ولكن أولاً ، التنسيق الداخلي المعبأ
يتطلب التحويل ، وثانيًا لا يسمح بزيادة الحجم - جهاز التخزين الضخم لسبب ما ليس الآن في الموضة. تحاول الواجهات الحديثة إخفاء حقيقة وجود الملفات. أنا مع أجهزة الكمبيوتر منذ 25 عامًا ، والعمل مع الملفات مباشرة أكثر ملاءمة بالنسبة لي من أي طريقة أخرى.
لا يوجد شيء هنا لا يمكن تحقيقه دون بذل جهود كبيرة.
أي شيء مختلف. أنا لا أستخدمه بنفسي ، ولكن فجأة شخص مفيد:
- يعرض الإحداثيات الحالية (خط العرض ، خط الطول)
- يتم رسم رموز مختلفة على الجانب الأيسر من الشاشة ، والتي لا يمكنني حتى تذكر جوهرها بدون دليل.
- هناك تبديل متر / كم - قدم / ميل.
- بلوتوث - يمكن توصيل المسجل بالهواتف المحمولة بدون GPS.
- المسافة المطلقة إلى النقطة.
- تسجيل الوقت (كل N ثانية) أو بالمسافة (كل X متر).
- دعم للغات مختلفة.
اختر الحديد
المتطلبات محددة بشكل أو بآخر. حان الوقت لفهم كيف يمكن تنفيذ كل هذا. المكونات الرئيسية التي سأحصل عليها هي:
- متحكم دقيق - ليس لدي أي خطط لأي خوارزميات حسابية معقدة ، لذا فإن قوة معالجة النواة ليست مهمة بشكل خاص. ليس لدي أيضًا متطلبات خاصة للحشو - ستقوم مجموعة من الأجهزة الطرفية القياسية بذلك.
في متناول اليد كان مجرد تناثر arduinoes المتنوعة ، فضلا عن اثنين من stm32f103c8t6. قررت أن أبدأ بـ AVR ، الذي أعرفه جيدًا على مستوى وحدة التحكم / التسجيلات / الأجهزة الطرفية. إذا واجهت قيودًا - سيكون هناك سبب للشعور بـ STM32.
- تم اختيار جهاز استقبال GPS من وحدات NEO6MV2 و Beitan BN-800 و Beitan BN-880. منتديات Googled لبعض الوقت. قال ذوي الخبرة أن المتلقي الأول هو القرن الماضي. يختلف الآخران عن بعضهما البعض فقط في موقع الهوائي - في BN-800 يتم تعليقه على السلك ، وفي BN-880 يتم لصقه مع شطيرة إلى الوحدة الرئيسية. أخذت BN-880 .
- الشاشة - يستخدم الأصل شاشة LCD 128 × 32 مع إضاءة خلفية. لم أجد نفس الشيء بالضبط. اشتريت جهاز OLED 0.91 بوصة على وحدة التحكم SSD1306 وشاشة LCD مقاس 1.2 بوصة على وحدة التحكم ST7565R . قررت أن أبدأ من الأول ، لأن من الأسهل الاتصال بمشط قياسي وفقًا لـ I2C أو SPI. ولكنها أصغر قليلاً مقارنة بالأصل ، ولن تعمل أيضًا على عرض الصورة باستمرار لأسباب تتعلق بكفاءة استهلاك الوقود. يجب أن تكون الشاشة الثانية أقل شرهًا ، لكنك تحتاج إلى لحام موصل صعب لذلك ومعرفة كيفية تشغيل الإضاءة الخلفية.
من الأشياء الصغيرة:
- اشترت الأزرار مرة واحدة حقيبة كاملة ؛
- درع لبطاقة SD - مستلقية أيضًا في متناول اليد ؛
- اشتريت زوجًا من وحدات التحكم في الشحن المختلفة لبطاريات الليثيوم ، لكنني ما زلت لا أفهمها.
قررت تصميم اللوحة في النهاية ، عندما تكون البرامج الثابتة جاهزة. بحلول هذا الوقت ، سوف أقرر في النهاية المكونات الرئيسية ومخطط إدراجها. في المرحلة الأولى ، قررت إجراء التصحيح على لوحة التوصيل عن طريق توصيل المكونات باستخدام أسلاك التصحيح.
ولكن عليك أولاً أن تقرر بشأن قضية مهمة للغاية - تغذية المكونات. بدا لي من المعقول تشغيل كل شيء من 3.3V: GPS والشاشة فقط عليه ومعرفة كيفية العمل. هذا هو أيضًا الجهد الأصلي لـ USB و SD. بالإضافة إلى ذلك ، يمكن تشغيل الدائرة من علبة ليثيوم واحدة.
وقع الاختيار على Arduino Pro Mini ، والذي يمكن العثور عليه في إصدار 8MHz / 3.3V. لكنها لم يكن لديها USB على متن الطائرة - اضطررت إلى استخدام محول USB-UART.
الخطوات الأولى
في البداية ، تم إنشاء المشروع في Arduino IDE. ولكن لنكون صادقين ، لا تجرؤ لغتي على تسميتها IDE - مثل محرر نصوص مع مترجم. على أي حال ، بعد Visual Studio ، الذي كنت أعمل فيه منذ 13 عامًا ، لا يمكنني فعل أي شيء جاد في Arduino IDE بدون دموع أو matyuk.
لحسن الحظ ، هناك Atmel Studio مجاني ، حيث يتم تضمين Visual Assist من خارج الصندوق !!! البرنامج يعرف كل ما هو مطلوب ، كل شيء مألوف ومكانه. حسنًا ، كل شيء تقريبًا (لم أجد كيفية ترجمة ملف واحد فقط ، على سبيل المثال ، للتحقق من البنية)

تم البدء من الشاشة - وهذا ضروري لتصحيح الهيكل العظمي للبرامج الثابتة ، ثم ملؤه بالوظيفة. توقف عند
أول مكتبة متاحة لـ Adafruit SSD1306 . إنها تعرف كل ما هو مطلوب وتوفر واجهة بسيطة للغاية.
لعبت مع الخطوط. اتضح أن خطًا واحدًا يمكن أن يستغرق ما يصل إلى 8 كيلو بايت (حجم الحروف هو 24 نقطة) - لا يمكنك التنقل بشكل خاص في وحدة تحكم 32 كيلو بايت. هناك حاجة لخطوط كبيرة ، على سبيل المثال ، لعرض الوقت.
رمز عينة الخط#include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <gfxfont.h> #include <fonts/FreeMono12pt7b.h> #include <fonts/FreeMono18pt7b.h> ... #include <fonts/FreeSerifItalic24pt7b.h> #include <fonts/FreeSerifItalic9pt7b.h> #include <fonts/TomThumb.h> struct font_and_name { const char * PROGMEM name; GFXfont * font; }; #define FONT(name) {#name, &name} const font_and_name fonts[] = { // FONT(FreeMono12pt7b), FONT(FreeMono18pt7b), /* FONT(FreeMono24pt7b), FONT(FreeMono9pt7b), FONT(FreeMonoBold12pt7b), ... FONT(FreeSerifItalic9pt7b), FONT(TomThumb)*/ }; const unsigned int fonts_count = sizeof(fonts) / sizeof(font_and_name); unsigned int current_font = 0; extern Adafruit_SSD1306 display; void RunFontTest() { display.clearDisplay(); display.setCursor(0,30); display.setFont(fonts[current_font].font); display.print("12:34:56"); display.setCursor(0,6); display.setFont(&TomThumb); display.print(fonts[current_font].name); display.display(); } void SwitchToNextFont() { current_font = ++current_font % fonts_count; }
الخطوط الكاملة مع المكتبة خرقاء للغاية. اتضح أن الخط أحادي المسافة واسع جدًا - الخط "12:34:56" غير مناسب ، Serif - جميع الأرقام ذات أوزان مختلفة. ما لم يكن الخط القياسي 5 × 7 في المكتبة يبدو صالحًا للأكل.


اتضح أن هذه الخطوط تم تحويلها من بعض خطوط ttf مفتوحة المصدر التي لم يتم تحسينها ببساطة للاستبانة الصغيرة.
كان علي رسم خطوطي. بتعبير أدق ، أولاً ، استخرج الرموز الفردية من الرموز النهائية. الرمز ':' في جدول ASCII مفيد جدًا بعد الأرقام مباشرة ويمكن شراؤه في كتلة واحدة. من الملائم أيضًا أنه يمكنك إنشاء خط ليس على جميع الأحرف ، ولكن فقط على نطاق ، على سبيل المثال ، من 0x30 ('0') إلى 0x3a (':'). T.O. من FreeSans18pt7b اتضح أن صنع خط مضغوط للغاية للأحرف الضرورية فقط. صحيح ، كان علي تعديل العرض قليلاً حتى يتناسب النص مع عرض الشاشة.
الخطوط الفرعية FreeSans18pt7b اتضح أن الخط 18pt يبلغ ارتفاعه في الواقع 25 بكسل. وبسبب هذا ، فهو يناسب قليلاً نقشًا آخر

بالمناسبة ، تساعد الشاشة المقلوبة على فهم مكان حدود منطقة الرسم في الواقع وكيف يقع الخط بالنسبة لهذه الحدود - تحتوي الشاشة على إطارات كبيرة جدًا.
Googled لفترة طويلة الخطوط الجاهزة ، لكنها لا تناسب الحجم أو الشكل أو المحتوى. على سبيل المثال ، على الإنترنت ، عمود خط 8 × 12 (مقالب لمولدات أحرف بطاقة VGA). ولكن في الواقع ، هذه الخطوط هي 6x8 ، أي الكثير من المسيرات في الفضاء - في حالة وجود مثل هذا الحجم والحجم الصغير مثلما هو بالغ الأهمية.
كان علي أن أرسم الخطوط الخاصة بي ، لأن تنسيق الخط في مكتبة Adafruit بسيط للغاية. أعددت الصورة في Paint.net - قمت ببساطة برسم الحروف بالخط الصحيح ، ثم قمت بتصحيحها قليلاً بقلم رصاص. قمت بحفظ الصورة بصيغة png ، ثم أرسلتها بسرعة إلى نص بايثون المكتوب على ركبتي. أنشأ هذا البرنامج النصي رمزًا شبه منتهيًا يحكم بالفعل القواعد في IDE في الرموز السداسية.

على سبيل المثال ، هذه هي الطريقة التي تبدو بها عملية إنشاء خط أحادي المسافة 8 × 12 بحرف صغير وتباعد الأسطر. وتبين أن كل حرف في النهاية يبلغ تقريبًا 7 × 10 ، ويشغل بشكل افتراضي 10 بايت. سيكون من الممكن حزم كل حرف في 8-9 بايت (تسمح المكتبة بذلك) ، لكنني لم أزعجني. بالإضافة إلى ذلك ، في هذا النموذج ، يمكنك تحرير وحدات البكسل الفردية مباشرة في التعليمات البرمجية.
الإطار
يوفر الجهاز الأصلي واجهة بسيطة ومريحة للغاية. يتم تجميع المعلومات في فئات يتم عرضها من صفحات (شاشات) فردية. باستخدام الزر ، يمكنك التنقل بين الصفحات ، واستخدام الزر الثاني لتحديد العنصر الحالي أو تنفيذ الإجراء الموضح في التوقيع تحت الزر. يبدو لي هذا النهج مريحًا للغاية ولا حاجة لتغيير أي شيء.
أنا أحب جمال OOP ، لأنني أبهرت على الفور واجهة صغيرة ، كل صفحة تنفذ الواجهة كما تتطلب. تعرف الصفحة كيفية رسم نفسها وتنفيذ رد الفعل على الأزرار.
class Screen { Screen * nextScreen; public: Screen(); virtual ~Screen() {} virtual void drawScreen() = 0; virtual void drawHeader(); virtual void onSelButton(); virtual void onOkButton(); virtual PROGMEM const char * getSelButtonText(); virtual PROGMEM const char * getOkButtonText(); Screen * addScreen(Screen * screen); };
يمكن للأزرار تنفيذ إجراءات مختلفة اعتمادًا على الشاشة الحالية. لذلك ، فإن الجزء العلوي من الشاشة بارتفاع 8 بكسل ، قمت بتعيينه لتسميات الأزرار. يعتمد نص التواقيع على الشاشة الحالية ويتم إرجاعه بواسطة الدوال الظاهرية getSelButtonText () و getOkButtonText (). كما سيتم عرض عناصر خدمة الرأس مثل قوة إشارة GPS وشحن البطارية. تتوفر الشاشات المتبقية للحصول على معلومات مفيدة.
كما قلت ، يمكن قلب الشاشات ، مما يعني أنه في مكان ما يجب أن يكون هناك قائمة بالكائنات لصفحات مختلفة. في أكثر من شاشة واحدة - يمكن أن تتداخل الشاشات ، مثل القائمة الفرعية. لقد بدأت حتى فئة ScreenManager ، التي كان من المفترض أن تدير هذه القوائم ، ولكن بعد ذلك وجدت الحل أسهل.
لذلك ، تحتوي كل شاشة ببساطة على مؤشر إلى التالي. إذا كانت الشاشة تسمح لك بإدخال القائمة الفرعية ، فإنها تضيف مؤشرًا آخر إلى شاشة هذه القائمة الفرعية
class Screen { Screen * nextScreen; … }; class ParentScreen : public Screen { Screen * childScreen; … };
بشكل افتراضي ، يستدعي معالج الزر ببساطة وظيفة تغيير الشاشة ، ويمررها المؤشر المطلوب. اتضح أن الوظيفة تافهة - فقد حولت المؤشر إلى الشاشة الحالية. لضمان تداخل الشاشات ، قمت بعمل مجموعة صغيرة. لذا فإن مدير الشاشة بالكامل يناسب 25 سطرًا و 4 وظائف صغيرة.
Screen * screenStack[3]; int screenIdx = 0; void setCurrentScreen(Screen * screen) { screenStack[screenIdx] = screen; } Screen * getCurrentScreen() { return screenStack[screenIdx]; } void enterChildScreen(Screen * screen) { screenIdx++;
صحيح أن رمز ملء هذه الهياكل لا يبدو لطيفًا جدًا ، ولكن حتى الآن لم يتم اختراعه بشكل أفضل.
Screen * createCurrentTimeScreen() { TimeZoneScreen * tzScreen = new TimeZoneScreen(1, 30); tzScreen = tzScreen->addScreen(new TimeZoneScreen(2, 45)); tzScreen = tzScreen->addScreen(new TimeZoneScreen(-3, 30));
الفكرأصبحت البنية بالطبع جميلة ، لكنني أخشى أنها تأكل الكثير من الذاكرة. عليك أن تذهب ضد نفسك و zafigachit طاولة ثابتة كبيرة مع مؤشرات.
المضي قدما. في تنفيذي للواجهة ، أردت أن أفعل شيئًا مثل مربع رسالة ، رسالة قصيرة تظهر لمدة ثانية أو اثنتين ، ثم تختفي. على سبيل المثال ، إذا قمت بالضغط على زر POI (نقطة الاهتمام) على الشاشة بالإحداثيات الحالية ، فبالإضافة إلى كتابة النقطة إلى المسار ، سيكون من الجيد أن تظهر للمستخدم رسالة "تم حفظ نقطة الطريق" (في الجهاز الأصلي ، يتم عرض رمز إضافي لمدة ثانية فقط). أو عندما تكون البطارية منخفضة ، "ابتهج" المستخدم برسالة.

نظرًا لأن البيانات من نظام تحديد المواقع العالمي (GPS) ستأتي باستمرار ، فلا يمكن الحديث عن أي وظائف حظر. لذلك ، كان عليّ اختراع آلة حالة بسيطة (جهاز الحالة) ، والتي ستختار في وظيفة الحلقة () ما يجب فعله - إظهار الشاشة الحالية أو مربع الرسالة.
enum State { IDLE_DISPLAY_OFF, IDLE, MESSAGE_BOX, BUTTON_PRESSED, };
كما أنها مريحة للتعامل مع مكابس الأزرار باستخدام آلة الحالة. ربما سيكون صحيحا من خلال المقاطعات ، ولكن اتضح بشكل جيد أيضا. إنه يعمل على هذا النحو: إذا تم الضغط على زر في حالة IDLE ، فتذكر وقت الضغط عليه وانتقل إلى حالة BUTTON_PRESSED. في هذه الحالة ، ننتظر حتى يقوم المستخدم بتحرير الزر. هنا يمكننا حساب المدة التي تم فيها الضغط على الزر. يتم تجاهل الردود القصيرة (<30 مللي ثانية) بكل بساطة - على الأرجح أن هذا يمثل ارتدادًا لجهات الاتصال. يمكن بالفعل تفسير الرحلات الطويلة بالضغط على زر.
أخطط لاستخدام كل من الضغطات القصيرة للأزرار للإجراءات العادية والطويلة (> 1 ج) للوظائف الخاصة. على سبيل المثال ، تعمل ضغطة قصيرة على تشغيل عداد المسافات أو إيقافه مؤقتًا ، بينما تعمل ضغطة طويلة على إعادة تعيين العداد إلى 0.
ربما ستضاف دول أخرى. لذلك ، على سبيل المثال ، في المسجل الأصلي بعد التبديل إلى الصفحة التالية ، تتغير القيم على الشاشة في كثير من الأحيان ، وأقل في كثير من الأحيان بعد بضع ثوانٍ - مرة واحدة في الثانية. يمكن القيام بذلك عن طريق إضافة دولة أخرى.
عندما كان الإطار جاهزًا ، بدأت بالفعل في توصيل GPS. ولكن هنا كانت هناك فروق دقيقة جعلتني أجل هذه المهمة.
تحسين البرامج الثابتة
قبل الانتقال ، أحتاج إلى تشتيت بعض التفاصيل الفنية. والحقيقة هي أنني بدأت في هذا المكان تقريبًا مع زيادة استهلاك الذاكرة. اتضح أن الخط الذي تم الإعلان عنه بتهور بدون تعديل PROGMEM في بداية البرنامج الثابت يتم نسخه إلى ذاكرة الوصول العشوائي ويأخذ مساحة هناك طوال وقت التشغيل.
معماريات مختلفةباختصار. على أجهزة الكمبيوتر الكبيرة ،
يتم استخدام بنية Von Neumann حيث توجد التعليمات البرمجية والبيانات في نفس مساحة العنوان. على سبيل المثال سيتم قراءة البيانات من ذاكرة الوصول العشوائي وذاكرة القراءة فقط بنفس الطريقة.
تستخدم وحدات التحكم الدقيقة عادةً
بنية هارفارد ، حيث يتم فصل الكود والبيانات. T.O. عليك استخدام وظائف مختلفة لقراءة الذاكرة والفلاش. من وجهة نظر لغة C / C ++ ، تبدو المؤشرات متشابهة ، ولكن عند كتابة برنامج ، نحتاج إلى معرفة مكان الذاكرة التي يشير إليها المؤشر بالضبط واستدعاء الوظائف المقابلة.
لحسن الحظ ، اهتم مطورو المكتبات بهذا الأمر جزئيًا. الفئة الرئيسية لمكتبة العرض - Adafruit_SSD1306 موروثة من فئة الطباعة من مكتبة Arduino القياسية.
هذا يوفر لنا سلسلة كاملة من التعديلات المختلفة على طريقة الطباعة - لطباعة السلاسل ، والأحرف الفردية ، والأرقام ، وشيء آخر. لذلك لديها وظيفتان منفصلتان لطباعة الخطوط:
size_t print(const __FlashStringHelper *); size_t print(const char[]);
يعرف الأول أنك بحاجة إلى طباعة خط من محرك أقراص محمول وتحميله حرفًا تلو الآخر. الثاني يطبع أحرف من ذاكرة الوصول العشوائي. في الواقع ، تأخذ كلتا الدالتين المؤشر إلى سلسلة ، فقط من مساحات العناوين المختلفة.
لفترة طويلة بحثت في كود اردوينو عن هذا __FlashStringHelper لتعلم كيفية استدعاء وظيفة الطباعة () المطلوبة. اتضح أن اللاعبين قاموا بالحيلة: أعلنوا ببساطة هذا النوع باستخدام إعلان أمامي (دون الإعلان عن النوع نفسه) وكتبوا ماكرو يلقي مؤشرات إلى خطوط في ومضة لنوع __FlashStringHelper. لمجرد المترجم لتحديد وظيفة الزائد اللازمة
class __FlashStringHelper; #define F(string_literal) (reinterpret_cast<const __FlashStringHelper *>(PSTR(string_literal)))
هذا يسمح لك بالكتابة مثل هذا:
display.print(F(“String in flash memory”));
ولكن لا يسمح بالكتابة مثل
const char text[] PROGMEM = "String in flash memory"; display.print(F(text));
وعلى ما يبدو ، لا توفر المكتبة أي شيء يمكن القيام به بهذه الطريقة. أعلم أنه ليس من الجيد استخدام أجزاء المكتبة الخاصة في الكود الخاص بي ، ولكن ماذا أفعل؟ وجهت الماكرو الخاص بي ، الذي فعل ما أحتاجه.
#define USE_PGM_STRING(x) reinterpret_cast<const __FlashStringHelper *>(x)
لذلك بدأت وظيفة رسم القبعة تبدو كما يلي:
void Screen::drawHeader() { display.setFont(NULL); display.setCursor(20, 0); display.print('\x1e'); display.print(USE_PGM_STRING(getSelButtonText())); display.setCursor(80, 0); display.print('\x1e'); display.print(USE_PGM_STRING(getOkButtonText())); }
حسنًا ، منذ أن دخلت إلى البرامج الثابتة ذات المستوى المنخفض ، قررت أن أدرس بعمق أكثر كيف يعمل كل شيء في الداخل.
بشكل عام ، يحتاج الرجال الذين توصلوا إلى Arduino إلى إقامة نصب تذكاري. جعلوا منصة بسيطة ومريحة للنماذج والحرف اليدوية. تمكن عدد كبير من الأشخاص الذين لديهم معرفة قليلة بالالكترونيات والبرمجة من دخول عالم Arduino. ولكن كل هذا سلس وجميل أثناء القيام بالقمامة مثل الومضات المزودة بمصابيح LED أو قراءة مقياس الحرارة. بمجرد أن تتأرجح في شيء خطير ، عليك على الفور أن تفهم أعمق مما كنت تريد من البداية.
لذا ، بعد كل مكتبة تمت إضافتها أو حتى الفصل ، لاحظت مدى سرعة نمو استهلاك الذاكرة. في هذه المرحلة ، كنت مشغولًا بأكثر من 14 كيلوبايت من 32 كيلوبايت من الفلاش و 1300 بايت من ذاكرة الوصول العشوائي (من أصل 2 كيلو بايت). أضافت كل حركة إهمال 10 في المائة أخرى للحركة المستخدمة بالفعل. لكنني ما زلت لم أربط مكتبات GPS و SD / FAT32 حقًا ، وكانت القطة نفسها تبكي. اضطررت إلى التقاط
مدقق التفكيك ودراسة ما فعله المترجم.
تمنيت سرًا أن يرتب الرابط الوظائف غير المستخدمة. ولكن اتضح أن بعضها يضيف الرابط بالكامل تقريبًا. في البرامج الثابتة ، وجدت وظائف رسم الخطوط وبعض الوظائف الأخرى من مكتبة العمل مع الشاشة ، على الرغم من أنني في الرمز لم أتصل بها في ذلك الوقت. ضمنيًا ، لا يجب أن يتم تسميتها أيضًا - فلماذا أحتاج إلى وظيفة رسم الخط إذا قمت برسم أحرف من الصور النقطية فقط؟ أكثر من 5.2 كيلوبايت من اللون الأزرق (وهذا لا يحسب الخطوط).
بالإضافة إلى مكتبة التحكم في العرض ، وجدت أيضًا:
- 2.6 كيلو بايت - على SoftwareSerial (قمت بسحبه إلى المشروع في وقت ما)
- 1.6 كيلوبايت - I2C
- 1.3 كيلو بايت - HardwareSerial
- 2 كيلو بايت - TinyGPS
- 2.5 كيلو بايت على اردوينو الفعلي (التهيئة ، الدبابيس ، جميع أنواع الجداول ، الموقت الرئيسي للوظائف ميليس () والتأخير ()) ،
والأرقام إرشادية للغاية ، مثل المحسن هو خلط التعليمات البرمجية بجدية. قد تبدأ بعض الوظائف في مكان واحد ، ثم يمكن أن تتبعها وظيفة أخرى من مكتبة أخرى ، تسمى من الأولى ، على الفور. علاوة على ذلك ، يمكن وضع فروع منفصلة لهذه الوظائف في الطرف الآخر من الفلاش.
أيضًا في الرمز الذي وجدته:
- التحكم في الشاشة عن طريق SPI (على الرغم من أنني قمت بتوصيله عبر I2C)
- طرق الفئات الأساسية التي لا تسمى نفسها ، لأن إعادة تعريفها في الورثة
- المدمرات التي لم يتم استدعاؤها من قبل التصميم
- وظائف الرسم (وليس كلها - جزء من الوظائف التي لا يزال الرابط يقوم بها)
- malloc / مجاني بينما في الكود الخاص بي تكون جميع الأشياء ثابتة في الأساس
ولكن ليس فقط استهلاك الذاكرة المحمولة ، ولكن أيضًا SRAM ينمو بسرعة فائقة:
- 130 بايت - I2C
- 100 بايت - SoftwareSerial
- 157 بايت - المسلسل
- 558 بايت - عرض (منها 512 هو الإطار المؤقت للإطار)
كان قسم البيانات. هناك حوالي 700 بايت ويتم تحميل هذا الشيء من فلاش إلى ذاكرة الوصول العشوائي في البداية. اتضح أن هناك أماكن محجوزة للمتغيرات في الذاكرة ، إلى جانب قيم التهيئة. هنا نعيش هذه المتغيرات والثوابت التي نسيت أن تعلن أنها ثابتة PROGMEM.
وكان من بين هذه المجموعة مجموعة كبيرة تحتوي على "شاشة البداية" للشاشة - وهي القيم الأولية لمخزن الإطارات المؤقت. من الناحية النظرية ، إذا قمت بعمل شاشة العرض () مباشرة بعد البداية ، يمكنك رؤية الزهرة ونقش Adafruit ، ولكن في حالتي ، لا داعي لقضاء ذاكرة فلاش على هذا.
يحتوي القسم .data أيضًا على vtables. يتم نسخها إلى الذاكرة من محرك أقراص فلاش ، على ما يبدو لأسباب الكفاءة في وقت التشغيل. ولكن عليك التضحية بقطعة كبيرة من ذاكرة الوصول العشوائي - أكثر من اثني عشر فئة تزيد عن 150 بايت. علاوة على ذلك ، يبدو أنه لا يوجد مفتاح مترجم ، بالتضحية بالأداء ، سيترك الجداول الافتراضية في ذاكرة فلاش.
ماذا تفعل حيال ذلك؟ لا اعرف حتى الان. سيعتمد على كيفية استمرار الاستهلاك في النمو. لحاجات جيدة وجدت تحتاج إلى إصلاح بلا رحمة. في جميع الاحتمالات ، سيكون عليّ رسم جميع المكتبات في مشروعي بشكل صريح ثم تغطيتها بدقة. وقد تضطر أيضًا إلى إعادة كتابة بعض القطع بشكل مختلف من أجل تحسين الذاكرة. أو التبديل إلى أجهزة أكثر قوة. على أي حال ، أنا الآن أعرف المشكلة وهناك استراتيجية حول كيفية إصلاحها.
تحديث:تقدم ضئيل في كفاءة الموارد. أقوم بتحديث لهذا الجزء ، لأنه في اليوم التالي أريد التركيز على أشياء مختلفة تمامًا.
في التعليقات هناك بعض الحيرة حول استخدام C ++. على وجه الخصوص ، لماذا هو سيئ للغاية ويحافظ على vtable في ذاكرة الوصول العشوائي الثمينة؟ بشكل عام ، تكون الوظائف الافتراضية والمنشّئات والمدمّرات هي النفقات العامة. لماذا؟ دعونا نكتشف ذلك!
فيما يلي إحصاءات عن الذاكرة في مرحلة ما من المشروع
حجم البرنامج: 15458 بايت (يستخدم 50٪ من 30720 بايت كحد أقصى) (2.45 ثانية)
الحد الأدنى من استخدام الذاكرة: 1258 بايت (61٪ من 2048 بايت كحد أقصى)
التجربة رقم 1 - أعد الكتابة إلى C.
رميت الطبقات ، أعيدت كتابة كل شيء على طاولات مع مؤشرات للوظائف.
نظرًا لأن الشاشات دائمًا ما يكون لها نفس البنية دائمًا ، فقد أصبح جميع أعضاء البيانات متغيرات عالمية عادية.الإحصائيات بعد إعادةحجم البرنامج: 14 568 بايت (تستخدم 47٪ من 30720 بايت كحد أقصى) (2.35 ثانية)الحد الأدنى لاستخدام الذاكرة: 1176 بايت (57٪ من 2048 بايت كحد أقصى)الإجمالي. ربح 900 بايت من الفلاش و 80 بايت من ذاكرة الوصول العشوائي. ما ترك بالضبط الفلاش لم يحفر. 80 بايت من ذاكرة الوصول العشوائي هي فقط حجم vtable. بقيت جميع البيانات الأخرى (أعضاء الصف) بطريقة أو بأخرى.يجب أن أقول إنني لم أفسد كل شيء - أردت فقط رؤية الصورة الكبيرة دون قضاء الكثير من الوقت في ذلك. بعد إعادة الهيكلة ، "فقدت" لقطات الشاشة المتداخلة. معهم ، سيكون الاستهلاك أكثر قليلاً.لكن الشيء الأكثر أهمية في هذه التجربة هو أن جودة الكود قد تدهورت بشكل ملحوظ. أصبح رمز وظيفة واحدة منتشرًا على عدة ملفات. بالنسبة لبعض أجزاء البيانات ، توقف "مالك واحد" عن الوجود ، وبدأت بعض الوحدات في الصعود إلى ذاكرة الآخرين. أصبحت الشفرة كاسحة وقبيحة.التجربة رقم 2 - ضغط البايت من C ++لقد رجعت تجربتي ، قررت ترك كل شيء كصفوف. في هذه المرة فقط ، تعمل الشاشات على كائنات موزعة بشكل ثابت. تم إصلاح بنية الصفحات على شاشاتي. يمكنك تحديده في مرحلة التجميع دون استخدام جديد / حذف.حجم البرنامج: 15408 بايت (يستخدم 50٪ من 30720 بايت كحد أقصى) (2.60 ثانية)الحد الأدنى لاستخدام الذاكرة: 1273 بايت (62٪ من 2048 بايت كحد أقصى)زاد استهلاك ذاكرة الوصول العشوائي بشكل طفيف. ولكن هذا ، في الواقع ، للأفضل. يتم تفسير زيادة استهلاك ذاكرة الوصول العشوائي عن طريق تحريك الكائنات من كومة الذاكرة المؤقتة إلى منطقة ذاكرة موزعة بشكل ثابت. على سبيل المثال
في الواقع ، تم إنشاء الأشياء من قبل ، ولكن هذا لم يذهب إلى الإحصاءات. والآن يتم أخذ هذه الأشياء في الاعتبار بشكل صريح.ولكن للحد بشكل كبير من استهلاك الفلاش لم ينجح. لا يزال الرمز يحتوي على المنشئات نفسها ، والتي لا تزال تسمى عند بدء التشغيل. على سبيل المثال
لم يتمكن المترجم من "تنفيذها" مقدمًا ووضع جميع القيم في مناطق مخصصة مسبقًا. ولا يزال هناك مدمرون في الكود ، على الرغم من أنه من الواضح للقنفذ أن الكائنات لن يتم حذفها أبدًا.في محاولة لإنقاذ القليل على الأقل ، قمت بحذف جميع المدمرات في التسلسل الهرمي ، ولا سيما المدمر الظاهري في الفئة الأساسية. كانت الفكرة لتحرير بضع بايت في كل vtable. ثم انتظرتني مفاجأة:حجم البرنامج: 14 704 بايت (تم استخدام 48٪ من 30720 بايت كحد أقصى) (2.94 ثانية)الحد الأدنى لاستخدام الذاكرة: 1211 بايت (59٪ من 2048 بايت كحد أقصى)اتضح أن vtable لم يذهب بمؤشر واحد ، ولكن بالفعل بمقدار 2. علاوة على ذلك ، كان على حد سواء علاقة المدمر. مدمر واحد فقط فارغ (مرئي للكائنات على المكدس) ، والآخر بمكالمة مجانية ، مرئي للكائنات في كومة الذاكرة المؤقتة (-12 بايت من ذاكرة الوصول العشوائي). أيضًا ، اختفت المتغيرات المرتبطة بالورك (8 بايت) وتسميات الكائنات التي لم يتم إنشاؤها أبدًا (الشاشة ، ParentScreen - 40 بايت) .انخفضاستهلاك الفلاش بشكل ملحوظ - بمقدار 700 بايت. ليس فقط المدمرون أنفسهم ، ولكن أيضًا تم التخلص من تطبيقات malloc / free / new / delete. 700 بايت لمدمِّر افتراضي فارغ! 700 بايت ، كارل!هذا لن يندفع ذهابًا وإيابًا ، إليك كل الأرقام في مكان واحدالخلاصة: تبين أن الاستهلاك في C ++ هو نفسه تقريبًا كما هو الحال في C. ولكن في نفس الوقت ، فإن التغليف والميراث وتعدد الأشكال هي قوة. أنا مستعد لدفع مبالغ زائدة لهذا مع بعض الزيادة في الاستهلاك. ربما لا يمكنني الكتابة بشكل جميل في لغة C ، ولكن لماذا ، إذا كنت أستطيع الكتابة بشكل جميل في لغة C ++؟خاتمة
في البداية ، كنت أرغب في كتابة مقال واحد في نهاية المشروع. ولكن بما أن الملاحظات تتراكم بسرعة عالية مع تقدم العمل ، فإن المقال يهدد بأن يكون كبيرًا جدًا. لذلك قررت تقسيمها إلى عدة أجزاء. في هذا الجزء ، تحدثت عن المراحل التحضيرية: فهم ما أريده حقًا ، واختيار النظام الأساسي ، وتنفيذ إطار التطبيق.في الجزء التالي ، أخطط للانتقال إلى تنفيذ الوظائف الأساسية - العمل مع GPS. لقد واجهت بالفعل بضع مكابس مثيرة للاهتمام أود أن أخبر عنها.لأكثر من 10 سنوات لم أبرمج بجدية للمتحكم الدقيق. اتضح لي أنني كنت مدللًا إلى حد ما بسبب وفرة موارد أجهزة الكمبيوتر الكبيرة وهي مكتظة في حقائق ATMega32. لذلك ، كان علي أن أفكر في خيارات النسخ الاحتياطي المختلفة ، مثل تقليم وظائف المكتبات أو إعادة تصميم التطبيق باسم الاستخدام الفعال للذاكرة. أنا أيضًا لا أستبعد إمكانية التبديل إلى وحدات تحكم أكثر قوة - ATMega64 أو شيء من خط STM32.حسب الأسلوب ، تبين أن المقالة تشبه مجلة البناء. وسأكون سعيدًا بالتعليقات البناءة - لم يفت الأوان لتغيير أي شيء. أولئك الذين يرغبون في الانضمام إلى مشروعي على جيثب .نهاية الجزء الأول.الجزء الثاني