ناقل SPI
في الفصل السابق وصلنا عدة شرائح بسلكين، ودفعنا الثمن سرعة محدودة. لكن شاشة ملوّنة فيها عشرات آلاف النقاط لا تنتظر. في هذا الفصل نتعلّم ناقلا أبسط وأسرع: أسلاكه أكثر، ولا عناوين فيه، ويصل إلى ملايين البتّات في الثانية. نبدأ بمصفوفة ضوئية، ثم شاشة، ثم شاشة لمس، ونكتب بأنفسنا مكتبتين صغيرتين.
- نشرح أدوار الخطوط SCK وMOSI وMISO وCS، ونختار وضع الساعة الصحيح.
- نقارن SPI بـI2C، ونحسب زمن نقل صورة كاملة.
- نتحكّم في مصفوفة 8×8 عبر مسجّلات MAX7219، ونتحقّق من البايتات على الخطوط.
- نملأ مستطيلا على شاشة ILI9341، ونفهم دور الخط DC.
- نقرأ موضع اللمس من XPT2046 بأمر
write_readinto، ونشارك الناقل بين شريحتين.
10.1 أربعة خطوط بدل العناوين – ארבעה קווים במקום כתובות (Four Lines Instead of Addresses)
في I2C يتكلم الجميع على سلك بيانات واحد، ولكل شريحة عنوان. أما SPI فيحل المسألة بالأسلاك: خط لكل اتجاه، وخط خاص لكل شريحة يقول لها: «دورك الآن». والمتحكّم هو الذي يولّد الساعة دائما.
| الخط | الاتجاه | معناه |
|---|---|---|
SCK | من المتحكّم إلى الشريحة | نبضات الساعة (שעון). كل نبضة تنقل بتّا واحدا. |
MOSI | من المتحكّم إلى الشريحة | البيانات الخارجة من المتحكّم. |
MISO | من الشريحة إلى المتحكّم | البيانات الداخلة إلى المتحكّم. بعض الشرائح لا تحتاج إليه. |
CS | من المتحكّم إلى شريحة واحدة | اختيار الشريحة (בחירת רכיב). فعّال عند 0: تسمع الشريحة فقط حين يكون خطها منخفضا. |
10.2 الساعة ووضعها – השעון ומצבו (Clock Polarity and Phase)
داخل كل طرف من الطرفين مسجّل إزاحة. مع كل نبضة ساعة يخرج بتّ من المتحكّم إلى الشريحة، ويخرج في اللحظة نفسها بتّ من الشريحة إلى المتحكّم. فالنقل تبادل في الأصل: لا يوجد في SPI «كتابة» بلا «قراءة»، وإنما نتجاهل أحد الاتجاهين حين لا نحتاج إليه. ولا يحتوي الناقل على عناوين ولا على إشارة تأكيد، ولذلك هو سريع.
على الطرفين أن يتفقا على أمرين، يصفهما عددان في machine.SPI:
- قطبية الساعة (קוטביות)
polarity: مستوى خط الساعة وهو ساكن: 0 أو 1. - طور الساعة (מופע)
phase: هل نأخذ البتّ عند الحافة الأولى من النبضة (0) أم عند الثانية (1).
العددان معا يعطيان أربعة أوضاع. والوضع مكتوب في ورقة بيانات الشريحة، ولا نخمّنه. الشرائح الأربع في هذا الفصل تعمل على الوضع 0، وهو القيمة الافتراضية في machine.SPI: polarity=0, phase=0. ومعها تأتي قيمتان افتراضيتان أخريان: 8 بتّات في الكلمة، والبتّ الأعلى أولا.
كم يستغرق النقل؟ لأن كل نبضة تنقل بتّا واحدا، فالحساب مباشر:
| الرمز | المعنى | وحدة القياس |
|---|---|---|
| t | أقل زمن لنقل البيانات | s |
| N | عدد الوحدات المنقولة (نقاط أو بايتات) | — |
| b | عدد البتّات في الوحدة | — |
| f | تردد الساعة | Hz |
عدد الصور في الثانية هو مقلوب هذا الزمن: نحو 8.1. والوقت الحقيقي أطول، لأن البرنامج يحتاج أيضا إلى وقته.
10.3 مقارنة مع I2C – השוואה ל-I2C (SPI versus I2C)
| I2C | SPI | |
|---|---|---|
| عدد الأسلاك | 2، لأي عدد من الشرائح | 3 مشتركة، وخط CS لكل شريحة |
| اختيار الشريحة | عنوان يُرسل على الناقل | سلك خاص |
| الاتجاه | اتجاه واحد في كل لحظة | الاتجاهان معا |
| السرعة في هذا الكتاب | 400 kHz | 1–10 MHz |
| الحد الأعلى على ESP32 | — | حتى 80 MHz على الأطراف الافتراضية، حسب التوثيق |
| هل تجيب الشريحة؟ | نعم: بتّ تأكيد بعد كل بايت | لا: لا يوجد تأكيد |
| البحث عن الشرائح | i2c.scan() | لا يوجد |
| الاستعمال المناسب | حسّاسات بطيئة كثيرة | شاشات، بطاقات، بيانات كثيرة |
spi.write() ينجح دائما، حتى لو كان السلك مقطوعا. نكتشف الخطأ من سلوك الشريحة فقط. وهذا صحيح أيضا عن المُحاكاة: تسجّل ما يخرج على الخطوط، ولا توجد شريحة تستقبله.10.4 الناقل في مايكروبايثون – האפיק במיקרו-פייתון (SPI in MicroPython)
الأطراف والرقم الداخلي للناقل مختلفة بين اللوحتين، ولذلك أخفيناها في الملف board.py:
# board.py - ESP32 DevKit
from machine import ADC, I2C, PWM, SPI, UART, Pin, Timer
SCK = 18
MOSI = 23
MISO = 19
CS = 5
DC = 4
RST = 15
def spi(baudrate=1000000):
return SPI(2, baudrate, sck=Pin(SCK), mosi=Pin(MOSI), miso=Pin(MISO))وعلى Pico W الملف نفسه، مع SPI(0) والأطراف SCK = 18, MOSI = 19, MISO = 16, CS = 17, DC = 20, RST = 21.
خط CS ليس جزءا من الكائن SPI: هو طرف خرج عادي نتحكّم فيه بأنفسنا. نُنزله قبل النقل، ونرفعه بعده، وعند رفعه تُنفّذ أغلب الشرائح ما وصلها. هذا هو البرنامج الأول. طلبنا من المُحاكاة أن تعرض تغيّرات الأطراف، ثم البايتات على الناقل:
import board
from machine import Pin
spi = board.spi(1000000)
cs = Pin(board.CS, Pin.OUT, value=1)
cs.value(0)
spi.write(bytes([0x0A, 0x03]))
cs.value(1)
print('sent 2 bytes')sent 2 bytes [pins] 5 0 5 1 [spi] 0a 03
sent 2 bytes [pins] 17 0 17 1 [spi] 0a 03
نقرأ المُخرَج: القسم [pins] يعرض رقم الطرف ومستواه. نزل CS إلى 0 ثم صعد إلى 1، وبينهما خرج البايتان 0a 03. ولقراءة الاتجاهين معا نستعمل write_readinto. في المثال التالي نضع في العالم المُحاكى بايتين تعيدهما الشريحة:
import board
from machine import Pin
spi = board.spi(1000000)
cs = Pin(board.CS, Pin.OUT, value=1)
tx = bytes([0x12, 0x34])
rx = bytearray(2)
cs.value(0)
spi.write_readinto(tx, rx)
cs.value(1)
print('sent %02x %02x' % (tx[0], tx[1]))
print('received %02x %02x' % (rx[0], rx[1]))sent 12 34 received a1 b2 [spi] 12 34
| الأمر | عمله |
|---|---|
| board.spi(baud) | ناقل على أطراف اللوحة، بالتردد المطلوب. |
| spi.write(data) | يرسل البايتات، ويتجاهل ما يعود. |
| spi.read(n) | يقرأ n بايت، ويرسل أصفارا. |
| spi.write_readinto(tx, rx) | يرسل tx ويكتب ما يعود في rx. لهما الطول نفسه. |
| spi.init(baudrate=f) | يغيّر تردد الناقل وهو يعمل. |
board.py عتادي: ينفّذه جزء خاص من الشريحة. وفي مايكروبايثون أيضا machine.SoftSPI الذي يعمل على أي طرف، لكنه أبطأ، ولا نحتاج إليه في هذا الفصل.قبل أن نوصل شيئا، نفحص أمرا قد يوقعنا في خطأ. الاسمان DC وRST يقعان على ESP32 على طرفين نستعملهما لغرض آخر في الكتاب:
board.py نفسه.import board
print('DC =', board.DC, ' DHT =', board.DHT)
print('RST =', board.RST, ' NEOPIXEL =', board.NEOPIXEL)
print('shared:', board.DC == board.DHT or board.RST == board.NEOPIXEL)DC = 4 DHT = 4 RST = 15 NEOPIXEL = 15 shared: True
DC = 20 DHT = 10 RST = 21 NEOPIXEL = 7 shared: False
DC وDHT معا، والطرف 15 هو RST وNEOPIXEL معا. لا نستعمل الشاشة وحسّاس DHT أو شريط NeoPixel في دائرة واحدة على هذه الأطراف. في مشروع يحتاج إليها جميعا ننقل أحدها إلى طرف حر. وعلى Pico W لا يوجد هذا التعارض.10.5 مصفوفة 8×8 مع MAX7219 – מטריצה 8×8 עם MAX7219 (LED Matrix with MAX7219)
المصفوفة (מטריצה) فيها 64 ثنائيا باعثا للضوء، ولا يكفي عدد أطراف اللوحة لوصلها مباشرة. الشريحة MAX7219 تتولّى الأمر: تحتفظ بصورة المصفوفة في مسجّلات داخلية، وتمسح الصفوف بنفسها، ولا نرسل نحن سوى الصورة. الاتجاه هنا واحد، ولذلك لا نحتاج إلى MISO.
كل أمر للشريحة حزمة من 16 بتّا، أي بايتان: رقم المسجّل ثم قيمته. أهم المسجّلات:
| المسجّل | الاسم | القيم |
|---|---|---|
| 0x01 … 0x08 | الصفوف الثمانية | كل بتّ مضاء أو مطفأ |
| 0x09 | فك الترميز Decode Mode | 0 للمصفوفة، بلا فك ترميز |
| 0x0A | شدة الإضاءة Intensity | من 0 إلى 15 |
| 0x0B | عدد الصفوف الممسوحة Scan Limit | 7 لكل الصفوف |
| 0x0C | الإيقاف Shutdown | 0 إيقاف، 1 تشغيل |
| 0x0F | اختبار الإضاءة Display Test | 1 يضيء كل شيء، 0 عمل عادي |
نرسم الآن قلبا. كل صف بايت، وكل بتّ فيه نقطة مضاءة أو مطفأة:
نضع ما عرفناه في مكتبة صغيرة، كتبناها لهذا الكتاب. الناقل وخط CS يصلان إليها من الخارج، ولذلك لا تعرف شيئا عن اللوحة:
# max7219.py - driver for an 8x8 LED matrix with the MAX7219 chip.
# spi: a machine.SPI in mode 0. cs: an output Pin, active low.
# (c) 2026 Fahed Ghanem - MIT License - fahed-lab.com
REG_DIGIT0 = 0x01
REG_DECODE = 0x09
REG_INTENSITY = 0x0A
REG_SCAN = 0x0B
REG_SHUTDOWN = 0x0C
REG_TEST = 0x0F
class MAX7219:
def __init__(self, spi, cs, intensity=3):
self.spi = spi
self.cs = cs
self.cs.value(1)
self._send(REG_TEST, 0)
self._send(REG_DECODE, 0)
self._send(REG_SCAN, 7)
self.intensity(intensity)
self.clear()
self._send(REG_SHUTDOWN, 1)
def _send(self, reg, value):
self.cs.value(0)
self.spi.write(bytes([reg, value]))
self.cs.value(1)
def intensity(self, level):
if not 0 <= level <= 15:
raise ValueError('intensity is 0 to 15')
self._send(REG_INTENSITY, level)
def show(self, rows):
for i in range(8):
self._send(REG_DIGIT0 + i, rows[i])
def clear(self):
self.show(bytes(8))import board
from machine import Pin
from max7219 import MAX7219
HEART = (0x00, 0x66, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0x00)
spi = board.spi(1000000)
cs = Pin(board.CS, Pin.OUT, value=1)
matrix = MAX7219(spi, cs)
matrix.show(HEART)
print('heart sent')heart sent [spi] 0f 00 09 00 0b 07 0a 03 01 00 02 00 03 00 04 00 05 00 06 00 07 00 08 00 0c 01 01 00 02 66 03 ff 04 ff 05 7e 06 3c 07 18 08 00
نتحقّق من قائمة [spi] مقابل جدول المسجّلات: السطور الأربعة الأولى هي ضبط الاختبار وفك الترميز والمسح والشدة. ثم ثمانية أسطر تمسح الصفوف، ثم 0c 01 تشغّل الشريحة، وأخيرا ثمانية أسطر هي القلب: 02 66 يعني أن الصف الثاني قيمته 0x66. مسحنا المصفوفة قبل التشغيل حتى لا تظهر محتويات عشوائية عند بدء العمل.
الزمن هنا صغير جدا: ثمانية صفوف من 16 بتّا على 1 MHz تأخذ 128 µs. لذلك لا نشعر بأي انتظار مع المصفوفة. أما مع الشاشة فالقصة مختلفة.
10.6 شاشة ملوّنة ILI9341 – מסך גרפי ILI9341 (Graphic Display ILI9341)
الشريحة ILI9341 تقود شاشة من 240 · 320 عنصر صورة (פיקסל)، ونسمّي عنصر الصورة اختصارا النقطة. هنا يوجد نوعان من البايتات: أوامر وبيانات. والشريحة لا تستطيع أن تميّز بينهما بقراءة البايت نفسه، فنضيف خطا آخر:
والخط RST يعيد الشريحة إلى حالتها الابتدائية عند 0، ونحرّكه مرة واحدة عند البدء. وللشاشة خط LED للإضاءة الخلفية.
نرتّب الرسم في ثلاثة أوامر: نحدّد نافذة الأعمدة، ونافذة الصفوف، ثم نكتب الألوان، وتنتقل الشريحة وحدها من نقطة إلى التي تليها:
| الأمر | الاسم | البيانات |
|---|---|---|
| 0x11 | الخروج من النوم SLPOUT | بلا، ثم انتظار |
| 0x3A | صيغة النقطة COLMOD | 0x55: 16 بتّا لكل نقطة |
| 0x36 | اتجاه الرسم MADCTL | 0x48 في إعدادنا |
| 0x29 | تشغيل الشاشة DISPON | بلا |
| 0x2A | نافذة الأعمدة CASET | البداية والنهاية، كل منهما بايتان |
| 0x2B | نافذة الصفوف PASET | البداية والنهاية، كل منهما بايتان |
| 0x2C | كتابة الذاكرة RAMWR | لون كل نقطة، بايتان لكل نقطة |
اللون في 16 بتّا: 5 للأحمر، و6 للأخضر، و5 للأزرق. وهذا هو التنسيق RGB565. نأخذ البتّات العليا من كل لون ونضعها في مكانها. فاللون البرتقالي (255, 165, 0) يصبح 0xFD20.
المكتبة تكتفي بمهمة واحدة: ملء مستطيل. الحروف والخطوط والصور خارجها. وهذا مقصود: نقرأ كل سطر فيها ونفهمه:
# ili9341_mini.py - fill rectangles on an ILI9341 display. Nothing more.
# spi: a machine.SPI. cs, dc, rst: output Pins.
# (c) 2026 Fahed Ghanem - MIT License - fahed-lab.com
import struct
import time
SLPOUT = 0x11
DISPON = 0x29
CASET = 0x2A
PASET = 0x2B
RAMWR = 0x2C
MADCTL = 0x36
COLMOD = 0x3A
WIDTH = 240
HEIGHT = 320
CHUNK = 256
def rgb565(r, g, b):
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)class ILI9341:
def __init__(self, spi, cs, dc, rst):
self.spi = spi
self.cs = cs
self.dc = dc
self.cs.value(1)
rst.value(0)
time.sleep(0.01)
rst.value(1)
time.sleep(0.12)
self._cmd(SLPOUT)
time.sleep(0.12)
self._cmd(COLMOD, b'\x55')
self._cmd(MADCTL, b'\x48')
self._cmd(DISPON)
def _cmd(self, command, data=b''):
self.cs.value(0)
self.dc.value(0)
self.spi.write(bytes([command]))
if data:
self.dc.value(1)
self.spi.write(data)
self.cs.value(1) def fill_rect(self, x, y, w, h, color):
if x < 0 or y < 0 or w < 1 or h < 1:
raise ValueError('bad rectangle')
if x + w > WIDTH or y + h > HEIGHT:
raise ValueError('outside the screen')
self._cmd(CASET, struct.pack('>HH', x, x + w - 1))
self._cmd(PASET, struct.pack('>HH', y, y + h - 1))
left = w * h
n = min(left, CHUNK)
block = struct.pack('>H', color) * n
self.cs.value(0)
self.dc.value(0)
self.spi.write(bytes([RAMWR]))
self.dc.value(1)
while left >= n:
self.spi.write(block)
left -= n
if left:
self.spi.write(block[:2 * left])
self.cs.value(1)import board
from machine import Pin
from ili9341_mini import ILI9341, rgb565
spi = board.spi(10000000)
cs = Pin(board.CS, Pin.OUT, value=1)
dc = Pin(board.DC, Pin.OUT, value=0)
rst = Pin(board.RST, Pin.OUT, value=1)
lcd = ILI9341(spi, cs, dc, rst)
lcd.fill_rect(10, 20, 3, 2, rgb565(255, 0, 0))
print('filled', 3 * 2, 'pixels')filled 6 pixels [spi] 11 3a 55 36 48 29 2a 00 0a 00 0c 2b 00 14 00 15 2c f8 00 f8 00 f8 00 f8 00 f8 00 f8 00
نقرأ الأسطر الأخيرة: 2a هو أمر نافذة الأعمدة، ثم البيانات 00 0a 00 0c، أي من 10 إلى 12. ثم 2b والصفوف من 20 إلى 21. وبعد الأمر 2c يخرج اللون الأحمر f8 00 ست مرات، مرة لكل نقطة. الخط DC يكون 0 عند الأوامر و1 عند البيانات، حسب ما كتبناه في _cmd.
والآن الشاشة كاملة. نكتب اللون على دفعات من 256 نقطة حتى لا نحجز في الذاكرة 153600 بايت. ثم نخطئ عمدا في آخر سطر، لنرى أن المكتبة تحرس حدود الشاشة:
import board
from machine import Pin
from ili9341_mini import ILI9341, WIDTH, HEIGHT, rgb565
BAUD = 10000000
spi = board.spi(BAUD)
cs = Pin(board.CS, Pin.OUT, value=1)
dc = Pin(board.DC, Pin.OUT, value=0)
rst = Pin(board.RST, Pin.OUT, value=1)
lcd = ILI9341(spi, cs, dc, rst)
lcd.fill_rect(0, 0, WIDTH, HEIGHT, rgb565(0, 0, 255))
pixels = WIDTH * HEIGHT
print('pixels:', pixels)
print('at least %.1f ms on the wire' % (pixels * 16 / BAUD * 1000))
lcd.fill_rect(230, 0, 20, 10, rgb565(0, 0, 255))pixels: 76800 at least 122.9 ms on the wire ValueError: outside the screen
10.7 شاشة اللمس XPT2046 – מסך מגע XPT2046 (Touch Screen XPT2046)
فوق كثير من هذه الشاشات طبقة لمس مقاومة (מגע התנגדותי). عند الضغط تتلامس طبقتان فيتكوّن مجزّئ جهد، وموضع الضغط هو الجهد. الشريحة XPT2046 هي محوّل تماثلي رقمي من 12 بتّا يقيس هذا الجهد، ولها خط T_IRQ ينخفض عند اللمس.
القراءة تبادل حقيقي على الناقل. نرسل بايت أمر ثم بايتين فارغين، وفي هذين البايتين تعود القيمة. 0xD0 يقيس المحور X، و0x90 يقيس المحور Y. والنتيجة 12 بتّا تجلس في وسط البايتين، ولذلك نزيح 3 خانات إلى اليمين.
BTN.import time
import board
from machine import Pin
CMD_X = 0xD0
CMD_Y = 0x90
RAW_MIN = 200
RAW_MAX = 3900
spi = board.spi(1000000)
cs = Pin(board.CS, Pin.OUT, value=1)
irq = Pin(board.BTN, Pin.IN, Pin.PULL_UP)
def read_axis(cmd):
tx = bytes([cmd, 0, 0])
rx = bytearray(3)
cs.value(0)
spi.write_readinto(tx, rx)
cs.value(1)
return ((rx[1] << 8) | rx[2]) >> 3
def to_pixel(raw, size):
return (raw - RAW_MIN) * size // (RAW_MAX - RAW_MIN)
while True:
if irq.value() == 0:
x = read_axis(CMD_X)
y = read_axis(CMD_Y)
print('raw', x, y, ' pixel', to_pixel(x, 240), to_pixel(y, 320))
time.sleep(1)raw 2048 1000 pixel 119 69 raw 600 3000 pixel 25 242 ...
الثابتان RAW_MIN وRAW_MAX مثال فقط. كل شاشة تحتاج إلى معايرة (כיול) خاصة بها: نلمس زاويتين معروفتين، ونسجّل ما نقرؤه، ونكتب القيمتين في البرنامج.
10.8 شريحتان على ناقل واحد – שני רכיבים על אפיק אחד (Sharing the Bus)
الشاشة ولوحة اللمس على وحدة واحدة عادة، وتشتركان في الخطوط الثلاثة. يبقى أمران: لكل شريحة خط CS خاص بها، ولا ينزل إلا واحد منهما في كل لحظة. والشريحتان لا تتحملان السرعة نفسها: الشاشة على 10 MHz، أما اللمس فنقرأه على 1 MHz. لذلك نبدّل التردد قبل كل قراءة، ثم نعيده.
في board.py اسم واحد لخط CS. فنستعير للمس طرف TRIG، لأن الحسّاس فوق الصوتي غير موصول في هذه الدائرة. نرسم نقطة خضراء حيث لمس المستخدم:
import board
from machine import Pin
from ili9341_mini import ILI9341, rgb565
LCD_BAUD = 10000000
TOUCH_BAUD = 1000000
TOUCH_CS = board.TRIG
spi = board.spi(LCD_BAUD)
cs_lcd = Pin(board.CS, Pin.OUT, value=1)
cs_touch = Pin(TOUCH_CS, Pin.OUT, value=1)
dc = Pin(board.DC, Pin.OUT, value=0)
rst = Pin(board.RST, Pin.OUT, value=1)
lcd = ILI9341(spi, cs_lcd, dc, rst)
def read_axis(cmd):
rx = bytearray(3)
spi.init(baudrate=TOUCH_BAUD)
cs_touch.value(0)
spi.write_readinto(bytes([cmd, 0, 0]), rx)
cs_touch.value(1)
spi.init(baudrate=LCD_BAUD)
return ((rx[1] << 8) | rx[2]) >> 3
def scale(raw, size):
pos = (raw - 200) * size // 3700
return max(0, min(size - 5, pos))
x = scale(read_axis(0xD0), 240)
y = scale(read_axis(0x90), 320)
lcd.fill_rect(x, y, 5, 5, rgb565(0, 255, 0))
print('dot drawn at', x, y)dot drawn at 119 69
value=1 في السطر الذي ينشئ الطرف.10.9 قارئ البطاقات RC522 – קורא כרטיסים RC522 (RFID Reader RC522)
الوحدة RC522 تقرأ بطاقات لاسلكية قريبة (RFID) على تردد 13.56 MHz، وتتصل بالمتحكّم عبر SPI أيضا. بروتوكول البطاقات طويل ومعقّد، ولا فائدة من كتابته من الصفر. هذا هو الموقف الذي نستعمل فيه مكتبة من طرف ثالث.
RST على الطرف 15.المكتبة المعروفة هي micropython-mfrc522 من Stefan Wendler، برخصة MIT. لكن ملف الوصف فيها يقول إن صاحبها جرّبها على ESP8266 وWiPy فقط، لذلك نحتاج على لوحتينا إلى نسخة معدّلة، وعلينا فحص هذه النسخة كما نتعلم في الفصل التالي. هذا هو شكل البرنامج، بالأوامر التي تعرضها أمثلة المكتبة. هذا البرنامج لم يُشغَّل في بناء الكتاب، لأن المكتبة ليست جزءا من حزمتنا والبطاقات غير موجودة في المُحاكاة، وقد تختلف الأوامر قليلا من نسخة إلى أخرى:
import time
import board
from mfrc522 import MFRC522
rdr = MFRC522(board.SCK, board.MOSI, board.MISO, board.RST, board.CS)
while True:
stat, kind = rdr.request(rdr.REQIDL)
if stat == rdr.OK:
stat, uid = rdr.anticoll()
if stat == rdr.OK:
print('card', bytes(uid).hex())
time.sleep(0.5)أهمّ أفكار الفصل:
- SPI يبدّل العناوين بالأسلاك: ثلاثة خطوط مشتركة، وخط CS لكل شريحة.
- كل نبضة ساعة تنقل بتّا في الاتجاهين معا، ولذلك
write_readintoهو الأمر الأساسي. - الوضع مكتوب في ورقة البيانات. والخطأ فيه لا يعطي رسالة خطأ، وكذلك الشريحة المفصولة.
- الزمن يُحسب: عدد البتّات مقسوما على التردد. ومن هنا نعرف أن الشاشة الملوّنة تحتاج إلى SPI.
- مكتبة تأخذ الناقل والأطراف من الخارج لا تعرف اللوحة، وتعمل على كل لوحة دون تغيير.
- على ESP32 لا نجمع الشاشة مع DHT أو NeoPixel على الطرفين 4 و15.
ملخّص قوانين الفصل:
| القانون | الصيغة |
|---|---|
| زمن النقل | t = N · bf |
أوامر الفصل:
| الأمر | عمله |
|---|---|
| board.spi(baud) | إنشاء الناقل بالتردد المطلوب. |
| Pin(board.CS, Pin.OUT, value=1) | خط الاختيار، مرفوعا من البداية. |
| spi.write(data) | إرسال البايتات. |
| spi.write_readinto(tx, rx) | إرسال وقراءة معا. |
| spi.init(baudrate=f) | تغيير التردد بين شريحتين. |
| MAX7219(spi, cs) | مصفوفة 8×8؛ show(rows) وintensity(n) وclear(). |
| ILI9341(spi, cs, dc, rst) | الشاشة؛ fill_rect(x, y, w, h, color). |
| rgb565(r, g, b) | لون من ثلاثة بايتات إلى 16 بتّا. |
| framebuf.FrameBuffer(buf, w, h, fmt) | الرسم في الذاكرة؛ text وline وpixel. |
قاموس المصطلحات – מילון מונחים:
| بالعربية | بالعبرية | English |
|---|---|---|
| ناقل SPI | אפיק SPI | Serial Peripheral Interface |
| خط الساعة | קו שעון | Clock Line |
| اختيار الشريحة | בחירת רכיב | Chip Select |
| قطبية الساعة | קוטביות שעון | Clock Polarity |
| طور الساعة | מופע שעון | Clock Phase |
| مسجّل الإزاحة | אוגר הזזה | Shift Register |
| المصفوفة | מטריצה | Matrix |
| شدة الإضاءة | עוצמת הארה | Intensity |
| محوّل المستوى | ממיר רמות | Level Shifter |
| عنصر الصورة، النقطة | פיקסל | Pixel |
| أمر وبيانات | פקודה ונתונים | Command and Data |
| طبقة اللمس | שכבת מגע | Touch Layer |
| المعايرة | כיול | Calibration |
أسئلة للتمرين:
import board
from machine import Pin
from max7219 import MAX7219
X = (0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x81)
spi = board.spi(1000000)
cs = Pin(board.CS, Pin.OUT, value=1)
matrix = MAX7219(spi, cs)
matrix.show(X)
for row in X:
print('%02x' % row)framebuf، وتنسيقها MONO_HMSB يطابق المصفوفة: كل بايت صف، والبتّ الأعلى هو النقطة اليسرى. ارسموا بها الحرف A على المصفوفة.import framebuf
import board
from machine import Pin
from max7219 import MAX7219
spi = board.spi(1000000)
cs = Pin(board.CS, Pin.OUT, value=1)
matrix = MAX7219(spi, cs)
rows = bytearray(8)
fb = framebuf.FrameBuffer(rows, 8, 8, framebuf.MONO_HMSB)
fb.text('A', 0, 0, 1)
matrix.show(rows)
for row in rows:
line = ''
for bit in range(7, -1, -1):
line += '#' if row >> bit & 1 else '.'
print(line)...##... ..####.. .##..##. .######. .##..##. .##..##. .##..##. ........
import board
from machine import Pin
spi = board.spi(1000000)
cs = Pin(board.CS, Pin.OUT, value=1)
def send_far(reg, value):
cs.value(0)
spi.write(bytes([reg, value, 0x00, 0x00]))
cs.value(1)
send_far(0x0A, 8)
print('done')DC وDHT على الطرف 4. نترك board.DHT مكانه، وننشئ dc على طرف حر بدل board.DC. المكتبة تأخذ الأطراف من الخارج فلا تتغيّر.read_axis 3 خانات إلى اليمين؟