3.26. ناقل CAN في الشيفرة

يغلّف machine.CAN متحكم CAN عتاديّاً. شغّله بمعرّف الناقل ومعدل البت:

from machine import CAN

can = CAN(1, 500_000)
can.set_filters(None)        # accept all incoming IDs

يجب أن يطابق معدل البت كل عقدة أخرى على الناقل تماماً -- و125_000 و250_000 و500_000 و1_000_000 هي القيم الشائعة. ويُهيئ set_filters() المعرّفات التي سيمررها المتحكم؛ وNone يعني قبول كل شيء (مفيد أثناء تشغيل وصلة، وأقل فائدةً متى انشغل الناقل).

3.26.1. داخل المتحكم

تقع ثلاث قطع عتادية بين شيفرة Python في الكاميرا والناقل -- صناديق بريد TX، ومرشّح قبول، وطابور RX FIFO. وتُناظِر كل واحدة منها جزءاً من واجهة CAN المستخدمة أدناه.

Block diagram of the CAN controller. On the TX side (top), can.send() drops frames into one of three TX mailboxes, and an arbitrate block picks which mailbox goes to the bus next. On the RX side (bottom), incoming frames pass through an acceptance filter; accepted frames land in the RX FIFO and can.recv() reads the FIFO from its oldest end.

صناديق بريد TX ومرشّح القبول وطابور RX FIFO في المتحكم بين البرمجيات والناقل.

  • صناديق بريد TX. مجموعة صغيرة من الخانات العتادية (ثلاث عادةً) تحتفظ بالإطارات الصادرة التي سلّمتها الكاميرا عبر send() لكنها لم تصل بعد إلى الناقل. وعندما يكون الناقل خاملاً، يختار المتحكم صندوق البريد ذا المعرّف الأدنى رقماً (الأعلى أولويةً) ويحكّم على الناقل نيابةً عنه. ولا تختار الكاميرا صندوق البريد؛ بل يعيّن المتحكم واحداً ويعيد فهرسه من send().

  • مرشّح القبول. عتاد قابل للتهيئة يقارن معرّف كل إطار وارد بقائمة من الأنماط ويتخلص من كل ما لا يطابق. وتتابع الإطارات التي تجتاز سيرها إلى طابور RX FIFO؛ أما الإطارات المرفوضة فيتخلص منها المتحكم دون أن تصل إلى Python أبداً. ويُهيئ set_filters() هذه الأنماط.

  • RX FIFO. طابور الداخل أولاً يخرج أولاً (first-in, first-out) -- فالإطار الأول داخلاً هو أيضاً الأول خارجاً، مثل صف عند شباك التذاكر. ويُلحق المتحكم الإطارات المستقبَلة بمؤخرة الطابور كلما وصلت، ويسحبها recv() من المقدمة بالترتيب نفسه. ويهم هذا الطابور لأن الناقل يلتقط الإطارات في الخلفية بينما تكون Python مشغولة في مكان آخر؛ ثم تستنزفها الكاميرا واحداً تلو الآخر دون فقدان أي منها، ما دام الطابور FIFO لم يفِض.

3.26.2. إرسال إطار

يُدرج send() إطاراً في طابور الإرسال:

can.send(0x123, b"\x01\x02\x03\x04")

الوسيط الأول هو المعرّف (عدد صحيح بطول 11 بت لإطار قياسي)؛ والثاني هو الحمولة (من 0 إلى 8 بايت لـ CAN Classic). ويعيد الاستدعاء فهرساً صحيحاً صغيراً يحدد صندوق البريد العتادي الذي ذهب إليه الإطار. ويحكّم المتحكم مع أي مرسلات أخرى على الناقل ويعيد الإرسال حسب الحاجة دون مزيد من المساعدة من البرمجيات.

للمعرّفات الموسّعة (29 بت)، أجرِ عملية OR للراية CAN.FLAG_EXT_ID مع الوسيط الثالث:

can.send(0x18FF1234, b"hello", CAN.FLAG_EXT_ID)

3.26.3. استقبال الإطارات

يبدأ المتحكم دون مرشّح مثبَّت ويُسقط كل إطار وارد. وقبل أن يعيد recv() أي شيء، استدعِ set_filters() مرة واحدة -- وأبسط صورة هي None، التي تقبل كل معرّف:

can.set_filters(None)   # accept every frame

ثم يعيد recv() الإطار التالي في طابور الاستقبال FIFO، أو None إن لم يكن هناك شيء منتظر:

msg = can.recv()
if msg is not None:
    can_id, data, flags, errs = msg
    print("got", hex(can_id), bytes(data))

يملأ الناقل طابور RX FIFO في الخلفية، فيكتفي الحلقة الرئيسية باستنزافه بأسرع ما تتكرر. وما دام الطابور FIFO أعمق من أطول فجوة بين عمليات الاستنزاف، فلا تُفقد أي إطارات.

3.26.4. المرشّحات

عادةً ما يكون الناقل الحقيقي مزدحماً بإطارات لا تهتم بها الكاميرا. وتتيح المرشّحات العتادية للمتحكم إسقاط المعرّفات غير المرغوبة قبل وصولها إلى طابور FIFO. ويأخذ set_filters() قائمة من صفوف (id, mask, flags)؛ ويجتاز الإطار المرشّح إذا طابق معرّفه، مقنّعاً بـ mask، المعرّفَ المهيأ id:

# Accept only IDs 0x100 - 0x10F (mask off the bottom 4 bits)
can.set_filters(((0x100, 0x7F0, 0),))

# Accept IDs 0x300 and 0x700 exactly
can.set_filters(((0x300, 0x7FF, 0),
                 (0x700, 0x7FF, 0)))

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

3.26.5. حالات الخطأ والتعافي

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

  • نشط الخطأ (TEC وREC كلاهما دون 96). التشغيل العادي. عندما تكشف العقدة خطأ ناقل ترسل إطار خطأ نشطاً مهيمناً، مما يجبر كل عقدة أخرى على التخلص من الإطار الجاري كي يتمكن المرسِل من إعادة المحاولة.

  • تحذير الخطأ (يبلغ أي من العدّادين 96). ما زالت نشطة بالكامل على الناقل -- فحالة التحذير هي إشارة برمجية تفيد بأن الأخطاء تتراكم، وليست تغييراً في السلوك.

  • سلبي الخطأ (يبلغ أي من العدّادين 128). ما زالت العقدة على الناقل لكنها تتوقف عن إرسال إطارات الخطأ المهيمنة؛ ويُشار إلى الأخطاء الآن بإطارات خطأ سلبية (متراجعة) كي لا تتمكن عقدة معطلة من الاستمرار في تعطيل الناقل على بقية الجميع.

  • خارج الناقل (يبلغ TEC القيمة 256). قرر المتحكم أن هذه العقدة غير موثوقة بما يكفي للمشاركة. فينفصل عن الناقل، ويتوقف عن الإرسال والإقرار، ويبقى خارجاً حتى تعيد البرمجيات تشغيله صراحةً.

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

حالة خارج الناقل هي الحالة الوحيدة التي تتطلب إجراءً برمجياً. ويعيد restart() ضبط المتحكم ويعيده إلى حالة نشط الخطأ. ومن الأنماط الشائعة فحص الحالة من الحلقة الرئيسية وإعادة التشغيل بعد تأخير قصير لمنح الناقل وقتاً ليستقر:

import time
from machine import CAN

can = CAN(1, 500_000)
can.set_filters(None)

while True:
    if can.state() == CAN.STATE_BUS_OFF:
        time.sleep_ms(100)
        can.restart()
    # ... rest of the loop

تتوفر قيم العدّادين الحالية من get_counters() لأغراض التشخيص -- فعدّاد TEC الذي يتسلق باطّراد على ناقل هادئ بخلاف ذلك يشير عادةً إلى الأسلاك أو الإنهاء أو معدل بت مهيأ بشكل خاطئ.