3.19. UART في الكود

يغلّف machine.UART قناة UART عتادية واحدة. أنشئ واحدة بمعرّف الناقل ومعدل الباود؛ ولكل شيء آخر قيم افتراضية معقولة:

from machine import UART

uart = UART(3, baudrate=115200)

يحدد المعرّف id أي UART عتادي يُستخدم؛ وتعتمد القيمة على اللوحة (راجع لوحات OpenMV للاطلاع على أرقام النواقل المتاحة وتعيينات الدبابيس على كاميرا معيّنة). تنسيق الإطار الافتراضي هو 8 بتات بيانات، دون تماثل، وبت توقف واحد -- وهو "8N1" الذي يتوقعه الجميع.

3.19.1. الكتابة والقراءة

يمر خرج UART عبر write():

uart.write("hello\n")
uart.write(b"\x01\x02\x03")

يقبل write إما str (مُرمَّزة بـ UTF-8) أو bytes / bytearray. ويعود فورًا بمجرد وضع البايتات في طابور مخزن TX المؤقت؛ وينهي العتاد إخراجها بالساعة في الخلفية.

تحدث القراءات عبر ثلاث طرق، بحسب ما هو مطلوب:

n = uart.any()              # bytes available to read right now
data = uart.read(8)         # up to 8 bytes, or None on timeout
line = uart.readline()      # bytes ending in '\n', or None on timeout

تتحقق any() من مخزن RX المؤقت دون حجب. وتقرأ read() عددًا ثابتًا من البايتات، مُعيدةً None إذا انقضت المهلة (القابلة للتكوين عبر الوسيط timeout في المُنشئ) أولًا. وتقرأ readline() حتى السطر الجديد التالي وبما يشمله، وهو مفيد للبروتوكولات القائمة على الأسطر.

حلقة بسيطة تعكس كل ما تستقبله:

uart = UART(3, baudrate=115200, timeout=100)

while True:
    if uart.any():
        data = uart.read()
        uart.write(data)

يستمر read() دون وسيط للطول في قراءة البايتات حتى يبقى خط الاستقبال هادئًا طوال المهلة timeout المكوَّنة، ثم يُعيد كل ما تراكم. وبضبط timeout=100، يُعيد كل استدعاء هنا دفقة واحدة من البايتات -- كل ما أخرجه المرسل بالساعة دون فجوة 100 ms بين البايتات. وبدون مهلة لن يكون لدى الاستدعاء أي إشارة على أن المرسل قد انتهى وقد يتوقف إلى أجل غير مسمى.

3.19.2. البيانات الثنائية مع struct

إن إرسال الأعداد الصحيحة والأعداد العشرية عبر السلك هو ما تُستخدم له الوحدة struct. فهي تحزم قيمًا ثابتة العرض في كائن bytes باستخدام سلسلة تنسيق تُسمّي ترتيب البايتات ونوع كل حقل:

import struct

uart.write(struct.pack("<lhb", count, temperature_x100, status))

تختار العلامة الأولى "<" ترتيب البايتات الصغير الطرف؛ و"l" هو عدد صحيح بإشارة بحجم 32 بت، و"h" هو عدد صحيح بإشارة بحجم 16 بت، و"b" هو عدد صحيح بإشارة بحجم 8 بت. ويفك الطرف الآخر التحزيم بسلسلة التنسيق نفسها:

payload = uart.read(7)        # 4 + 2 + 1 = 7 bytes
count, temperature_x100, status = struct.unpack("<lhb", payload)

سلسلة التنسيق هي العقد بين الطرفين. وأي عدم تطابق -- ترتيب بايتات خاطئ، أو أحجام أنواع خاطئة، أو ترتيب حقول خاطئ -- يُنتج قيمًا بلا معنى.

3.19.3. التخزين المؤقت والقراءات عالية المعدل

إن حلقة الاستقصاء التي تستدعي any() وread() تواكب معظم حركة المرور من تلقاء نفسها، طالما أن الحلقة الرئيسية تتكرر بالسرعة الكافية لتفريغ مخزن الاستقبال المؤقت قبل أن يمتلئ. ويبرز خياران في المُنشئ عندما يرتفع المعدل أو تكون الحلقة مشغولة.

يحدد rxbuf حجم مخزن RX المؤقت البرمجي. والقيمة الافتراضية بضع مئات من البايتات؛ وللمستشعرات التي تُصدر مئات البايتات لكل دفقة، أو عندما تؤدي الحلقة الرئيسية عملًا طويلًا بين عمليات الاستقصاء، يحافظ تكبير المخزن المؤقت على البايتات الواردة من السقوط بينما تكون الحلقة مشغولة في مكان آخر:

uart = UART(3, baudrate=115200, timeout=100, rxbuf=4096)

يُفقد أي شيء يصل بينما يكون المخزن المؤقت ممتلئًا؛ فحدّد حجم rxbuf لتغطية أطول فجوة بين عمليات التفريغ.

للمعدلات العالية المستدامة، تقرأ readinto() داخل مخزن مؤقت مُخصَّص مسبقًا بدلًا من إعادة كائن bytes جديد في كل استدعاء:

buf = bytearray(256)

while True:
    n = uart.readinto(buf)
    if n:
        process(buf, n)

لا يحدث أي تخصيص على مسار القراءة، وهو ما يهم عندما تكون الكومة مجزأة أو عندما يدفع زمن استجابة التخصيص بخلاف ذلك إلى تجاوز ميزانية التوقيت بين البايتات.