يوفر وجود EEPROM للمطورين أداة ملائمة لحفظ معلمات التكوين أو حالة متغيرة ببطء ينبغي أن ينقطع فيها انقطاع التيار الكهربائي. في هذه المقالة سوف ننظر في كيفية القيام بذلك بأمان وسهولة أكبر قدر ممكن حتى لا ننسى أي شيء ولا أن نتذكر ما لم يكن هناك.
افترض أن لدينا متغير ونريد تخزينه في EEPROM. يبدو أن جميع الأدوات اللازمة لذلك هي في أيدينا:
#include <EEPROM.h> int my_var = DEFAULT_VALUE; EEPROM.get(MY_VAR_ADDR, my_var); my_var = NEW_VALUE; EEPROM.put(MY_VAR_ADDR, my_var);
ومع ذلك ، تكشف نظرة فاحصة أن هذا النهج يخلق مشاكل أكثر مما يحل. سنناقشها بالترتيب.
1. كيفية التأكد من أننا نقرأ بالضبط ما كتبناه (لضمان
النزاهة )؟ تخيل الصورة التالية. نكتب خطابًا إلى أنفسنا في حالة موتنا المفاجئ من فقدان الطاقة أو إشارة إعادة الضبط ووضعها في درج مكتبي. في الحياة التالية ، نفتح درج المكتب ونخرج قطعة من الورق ونقرأ الرسالة ونواصل مهمتنا. تكمن المشكلة في أن هناك دائمًا ورقة من الورق مملوءة بنص عشوائي. لذلك نحن بحاجة إلى طريقة لتمييز الرسالة الصحيحة عن الرسالة العشوائية. يمكن للمرء أن يؤكد له من كاتب العدل ، ولكن في أبسط الحالات ، فإن توقيعه يكفي إذا كان لدينا وسيلة للتحقق من صحتها. على سبيل المثال ، يمكننا استخدام نتيجة تعبير رياضي اعتمادًا على النص كتوقيع ، بحيث يكون احتمال الصدفة العشوائية صغيرًا بدرجة كافية. في أبسط الحالات ، هذا هو CRC أو المجموع الاختباري. سوف يحمينا ليس فقط من قراءة ما لم نكتب ، ولكن أيضا من قراءة رسالة تالفة. بعد كل شيء ، يتلاشى النص بمرور الوقت ، وتكون الإلكترونات الموجودة في المغلاق المعزول أقل قدرة على التحمل - يطير جسيم من الفضاء بقدرة كافية ، وسوف يتغير البتة. ولكن هناك طريقة أخرى للحصول على رسالة تالفة - هذه ليست لإضافتها إلى النهاية. ليس غريباً للغاية ، لأنه في وقت التسجيل ، يزيد الاستهلاك الحالي بشكل حاد ، مما قد يؤدي إلى وفاة الكاتب قبل الأوان.
2. لنفترض أننا كنا مقتنعين بصحة الرسالة ، لكن كيف يمكنني التأكد من أنني كنت من كتبها (لضمان
الأصالة ). كما يقول المثل ، أنا مختلف. فجأة ، كان هناك شخص آخر جالس على هذه الطاولة قبل أن أتجسد ، وكان له مهمة مختلفة ، ولأي سبب سوف أسترشد الآن برسائله؟ إذا زودنا ملاحظاتنا بتسمية معينة ، فسيكون من الأسهل بالنسبة لنا التمييز بيننا وبين الغرباء. على سبيل المثال ، يمكن أن يكون هذا التصنيف هو اسم المتغير الذي نقوم بحفظه. المشكلة الوحيدة هي أنه في EEPROM لا توجد مساحة كبيرة لوضع أسماء المتغيرات هناك ، وليس من المناسب القيام بذلك ، لأنها ذات أطوال مختلفة. ولكن لحسن الحظ هناك طريقة أكثر بساطة - يمكنك حساب المجموع الاختباري نيابة عن المتغير واستخدامه كاختصار. في نفس الوقت ، من المفيد إضافة حجم المتغير بالبايتات إلى هذا المجموع الاختباري حتى لا تقرأ المبلغ الخاطئ بطريق الخطأ. حسنًا ، من أجل الاكتمال ، نضيف معرفًا رقميًا آخر هناك ، لضمان تمييز المتغير الخاص بنا عن شخص آخر ، حتى لو تم تسميته بنفسه. نسمي هذا الرقم معرف المثيل (مستوحى من OOP إذا كان اسم المتغير يعتبر حقل كائن). إذا قمنا في أي وقت بترقية مهمتنا إلى إصدار جديد بشكل جذري ، بحيث يجعل هذا التحديث كل شيء لا معنى له تم حفظه من قبل ، نحتاج فقط إلى تغيير معرف المثيل لإبطال كل شيء تم حفظه بواسطة الإصدار القديم.
3. كيف يمكنني إجراء عملية كتابة غير مكتملة اترك القيمة المخزنة القديمة دون تغيير؟ أي ، يجب أن تنجح عملية الحفظ ، أو يجب ألا يكون لها أي تأثير ملحوظ على الإطلاق. بمعنى آخر ، يجب أن يكون الأمر
ذريًا أو معاملاتًا إذا كنا نتحدث عن معاملة تتعلق بتحديث غير مشروط لقيمة واحدة. من الواضح ، أننا لا نستطيع ضمان atomicity السجل من خلال إعادة كتابة القيمة السابقة ، يجب أن نكتب إلى مكان جديد بحيث تظل القيمة المخزنة القديمة سليمة ، على الأقل حتى الانتهاء من تسجيل السجل الجديد. غالبًا ما تسمى هذه التقنية "نسخ عند الكتابة" إذا تم تحديث جزء فقط من القيمة المحفوظة ، ولكن الجزء الذي لم يتغير ما زال يتم نسخه وكتابته إلى موقع جديد. بتطوير قياسنا ، سنقوم بكتابة رسائل إلى أنفسنا ، مع ترك الأحرف القديمة دون أن تمس ، ولكن مع تزويد كل حرف برقم تسلسلي متزايد حتى يتسنى لنا في حياتنا القادمة العثور على آخر حرف كتبناه. ومع ذلك ، في الوقت نفسه ، تنشأ مشكلة جديدة - المكان في المربع الذي وضعنا فيه الحروف سينتهي عاجلاً أم آجلاً إذا لم نتخلص من الحروف القديمة التي أصبحت غير ذات صلة. من السهل أن نفهم أنه يكفي تخزين حرفين فقط - واحد قديم وآخر جديد ، وقد يكون في طور الكتابة. وفقًا لذلك ، لا يحتاج رقم الرسالة أيضًا إلى العديد من وحدات البت.
ومن الغريب أن المؤلف لم يتمكن من العثور على تطبيق واحد من شأنه أن يسمح بتنظيم تخزين البيانات في EEPROM ، مع ضمان النزاهة والأصالة والذرية. اضطررت إلى الكتابة إلى
github.com/olegv142/NvTx بنفسي
لحفظ كل متغير في EEPROM ، يتم استخدام منطقتين متتاليتين - خلايا لها نفس البنية. تتم كتابة معرف المتغير المحسوب على أساس حجمه وتسمية النص ومعرف المثيل في أول 2 بايت. بعد ذلك ، تتم كتابة البيانات ، متبوعًا بـ 2 بايت من المجموع الاختباري. في البايت الأول ، يكون للبتين غرض خاص. الشيء الأكثر أهمية هو العلامة الصحيحة ؛ عند الكتابة ، يتم ضبطها دائمًا على واحدة. يتم استخدام بت ذات الترتيب المنخفض كرقم بت واحد من العصر ؛ هناك حاجة للعثور على الرسالة الأخيرة. يتم التسجيل في الخلايا "في دائرة". يتغير رقم العصر في كل مرة يتم فيها إنشاء سجل في الخلية الأولى. ومن هنا تأتي الخوارزمية لتحديد آخر خلية مسجلة: إذا كانت عهود الخلايا متماثلة ، فستكتب الثانية أخيرًا ، إذا كانت مختلفة - ثم الأولى.
يبدو بت الصحيح زائداً عن الحاجة ، لكن لديه وظيفة مهمة. بادئ ذي بدء ، نقرأ البيانات المخزنة ونتحقق من صحة كلتا الخليتين. إذا لم تنجح الخلية في التحقق من المعرف أو المجموع الاختباري الصحيح ، فسنقوم بإعادة تعيين بت الصحة. قد لا تتحقق عمليات الكتابة اللاحقة من صحة الخلايا ، ولكن تعتمد على هذه العلامة ، مما يقلل من الحمل بمقدار 2 مرات تقريبًا.
يمكن لأولئك الذين يرغبون في الخوض في تفاصيل التنفيذ مشاهدة الصور والرمز في
المستودع . أنا ، من أجل عدم تحمل القارئ ، انتقل إلى استخدام. تتلقى وظائف كتابة / قراءة البيانات 5 معلمات ، لذلك يتم التضحية براحة استخدامها لصالح المرونة. لكن يتم تعويضها بسخاء بواسطة مجموعتين من وحدات الماكرو ، مما يجعل استخدام المكتبة بهذه البساطة كما في حالة EEPROM.get / put. يتم استخدام المجموعة الأولى من وحدات الماكرو إذا كنت تريد فقط حفظ المتغير إلى العنوان المحدد:
#include <NvTx.h> int my_var = DEFAULT_VALUE; bool have_my_var = NvTxGetAt(my_var, MY_VAR_ADDR); my_var = NEW_VALUE; NvTxPutAt(my_var, MY_VAR_ADDR);
إذا كان هناك العديد من المتغيرات التي سيتم حفظها ، فسوف يتعين على كل منها تحديد العنوان وفي نفس الوقت بشكل صحيح الحجم بحيث لا تتداخل مناطق الذاكرة حيث يتم تخزين المتغيرات. لتبسيط المهمة ، تقوم المجموعة الثانية من وحدات الماكرو بتنفيذ تخصيص العنوان التلقائي ، ويقوم بذلك
في وقت الترجمة . على سبيل المثال ، يمكن
لمكتبة Arduino-EEPROMEx تخصيص ذاكرة في وقت التشغيل ، بينما تخزن العنوان في ذاكرة الوصول العشوائي لكل متغير مخزن. تخصص مكتبة
NvTx مساحة في EEPROM دون إضافة أي شيء إلى التعليمات البرمجية القابلة للتنفيذ أو محتويات RAM.
#include <NvTx.h> int my_var = DEFAULT_VALUE; char my_string[16] = ""; NvPlace(my_var, MY_START_ADDR, MY_INST_ID); NvAfter(my_string, my_var); bool have_my_var = NvTxGet(my_var); my_var = NEW_VALUE; NvTxPut(my_var);
يعين الماكرو NvPlace عنوان البداية لمنطقة EEPROM ، حيث سنقوم بتخزين المتغيرات ومعرف المثيل. يحجز الماكرو NvAfter منطقة ذاكرة لتخزين الوسيطة الأولى الخاصة به مباشرة بعد منطقة الذاكرة المحجوزة للثاني. عند تخصيص الذاكرة ، يتم التحقق أيضًا من أننا لم نتجاوز حجم EEPROM المتاح ، وأيضًا أننا لم نحتفظ بمناطق الذاكرة المتداخلة (يمكن أن يحدث هذا إذا كان لدى ماكرو NvAfter نفس الوسيطة الثانية). في حالة انتهاك أحد الشرطين المحددين ، لا يتم تجميع البرنامج ببساطة. أولئك الذين يرغبون في التعامل مع آلية تخصيص الذاكرة
سيجدونها في ملف رأس
NvTx.h. جميع وحدات الماكرو NvPlace و NvAfter هي تحديد التعدادات ، وتشكيل أسمائهم بناءً على أسماء المتغيرات ، وكذلك استخدام البناء الاصطلاحي المفيد للغاية
لتأكيد وقت الترجمة .
نأمل أن
تساعد مكتبة
NvTx القراء في كتابة كود موثوق به من الدرجة الصناعية.