ناقل I2C
في الفصل السابق كان كل سلكين يصلان جهازين فقط. لكن المشروع الحقيقي فيه ساعة وشاشة ومحوّل وحسّاس، وأطراف اللوحة محدودة. هنا يأتي ناقل يكفيه سلكان لعشرات الشرائح، ولكل شريحة عنوان. نفهم ما يجري على السلكين بتّا بتّا، ثم نكتب بأنفسنا مكتبات ثلاث شرائح وشاشتين، ونجمعها كلها على ناقل واحد.
- نشرح دور SDA وSCL ومقاومتي الرفع، ونحسب حدود قيمتهما.
- نقرأ معاملة على الناقل: البداية والعنوان والإقرار والبيانات والتوقف.
- نمسح الناقل ونستخرج عنوان أي شريحة، ونعالج شريحة لا تجيب.
- نوسّع أطراف اللوحة بالشريحة PCF8574، فنضيء 4 ثنائيات باعثة للضوء عبر طرفين فقط.
- نقرأ الساعة DS1307 ونضبطها، ونتعامل مع الترميز BCD.
- نكتب مكتبة لشاشة LCD ومكتبة لشاشة OLED.
9.1 ما هو ناقل I2C؟ – מאפייני האפיק (Bus Features)
الناقل I2C سلكان فقط: سلك البيانات SDA وسلك الساعة SCL. عليهما يتصل جهاز رئيسي (Master, בקר ראשי) هو المتحكّم عندنا، وأجهزة تابعة (Slave, עבד) كثيرة. الرئيسي وحده يولّد الساعة ويبدأ كل حديث، وكل تابع يبقى صامتا حتى يسمع عنوانه (כתובת).
| UART | I2C | |
|---|---|---|
| عدد الأسلاك | TX، RX، أرضي | SDA، SCL، أرضي |
| الساعة | لا يوجد سلك: غير متزامن | على SCL: متزامن |
| عدد الأجهزة | طرفان | كثيرة، كل منها بعنوان من 7 بتّات |
| الاتجاه | الاتجاهان معا | اتجاه واحد في كل لحظة |
| السرعة الشائعة | حتى 115200 bit/s | 100 kHz أو 400 kHz |
| الإقرار | لا يوجد | بتّ إقرار بعد كل بايت |
عدد العناوين يبدو كبيرا، 27 = 128، لكن بعضها محجوز. والمسافة قصيرة: الناقل صُمّم للعمل داخل الجهاز، على اللوحة نفسها أو بأسلاك قصيرة.
9.2 لماذا مقاومتا رفع؟ – נגדי משיכה (Pull-up Resistors)
لا توجد شريحة على الناقل تدفع السلك إلى الأعلى. كل شريحة تستطيع شيئا واحدا: أن تصل السلك بالأرضي، أو أن تتركه. هذا ما نسمّيه المصرف المفتوح (ניקוז פתוח, Open Drain). فمن يرفع السلك حين يتركه الجميع؟ مقاومة الرفع (נגד משיכה, Pull-up) الموصولة بالتغذية. والفائدة كبيرة: لو أخطأت شريحتان وتكلّمتا معا، فلا تتصادم إحداهما بالأخرى، بل يبقى السلك منخفضا ولا يحترق شيء.
كم تكون قيمتها؟ لها حدّان. مقاومة صغيرة جدا تحمّل الشريحة تيارا لا تستطيع سحبه. ومقاومة كبيرة جدا تبطئ صعود السلك، لأن للسلك سعة C_b. وتعطي مواصفات الناقل UM10204 هاتين العلاقتين:
| الرمز | المعنى | وحدة القياس |
|---|---|---|
| VOL | أعلى جهد مسموح للمستوى المنخفض، 0.4 V | V |
| IOL | تيار السحب، 3 mA في السرعتين | A |
| الرمز | المعنى | وحدة القياس |
|---|---|---|
| tr | زمن الصعود، حدّه 1000 ns عند 100 kHz و300 ns عند 400 kHz | s |
| Cb | سعة الناقل، حدّها 400 pF | F |
الزمن أكبر من 300 ns، فهذه القيمة لا تصلح عند 400 kHz بهذه السعة، وتصلح عند 100 kHz.
9.3 معاملة على الناقل – התחלה, Acknowledge וסיום (A Bus Transaction)
القاعدة الأساسية: SDA يتغيّر فقط حين يكون SCL منخفضا، ويُقرأ حين يكون مرتفعا. لهذه القاعدة استثناءان، وهما بالضبط ما يحدّد حدود الحديث:
- شرط البداية (תנאי התחלה): يهبط SDA وSCL مرتفع. ترى كل الشرائح ذلك فتنتبه.
- بايت العنوان: سبعة بتّات عنوان، يتبعها بتّ الاتجاه: 0 للكتابة، و1 للقراءة. والبتّ الأعلى وزنا أولا، عكس UART.
- الإقرار (Acknowledge, אישור): بعد كل بايت يترك المرسل السلك، فيسحبه المستقبل إلى الأسفل لنبضة ساعة واحدة: «وصلني». إذا بقي مرتفعا فلا أحد أجاب.
- البيانات: بايت بعد بايت، وبعد كل بايت إقرار.
- شرط التوقف (תנאי עצירה): يصعد SDA وSCL مرتفع، فيتحرّر الناقل.
نزيح العنوان بتّا واحدا إلى اليسار ليترك مكانا لبتّ الاتجاه.
أي 0x4E للكتابة و0x4F للقراءة.
0x27، وتضيف الوحدة بتّ الاتجاه وحدها. لكن بعض أوراق البيانات تكتب العنوان بثمانية بتّات، 0x4E مثلا. فإذا لم يطابق المسح، فهذا أول ما نفحصه.كل بايت 8 نبضات وإقرار، أي 9 نبضات.
9.4 الناقل في مايكروبايثون والمسح – כתובת וסריקת האפיק (Scanning the Bus)
نعرف من الفصل 6 الدالة board.i2c()، وهي تضبط الناقل على 400 kHz. وهذا مناسب للمحوّل ADS1115. أما PCF8574 وDS1307 فتحدّد أوراق بياناتهما 100 kHz فقط. لذلك ننشئ الناقل في هذا الفصل بأنفسنا، بسرعة أبطأ شريحة عليه، وبأسماء الأطراف من board. وهذا السطر يعمل على اللوحتين:
from machine import I2C, Pin
import board
i2c = I2C(0, scl=Pin(board.SCL), sda=Pin(board.SDA), freq=100000)
found = i2c.scan()
print("found", len(found), "chips")
for addr in found:
print("0x%02X" % addr)found 3 chips 0x20 0x48 0x68
| الأمر | عمله |
|---|---|
| I2C(0, scl=Pin(n), sda=Pin(n), freq=100000) | ينشئ الناقل رقم 0 على هذين الطرفين. |
| i2c.scan() | يجرّب كل العناوين، ويعيد قائمة من أجاب بإقرار. |
| i2c.writeto(addr, data) | بداية، عنوان، بايتات، توقف. |
| i2c.readfrom(addr, n) | بداية، عنوان، ثم يقرأ n بايتات، توقف. |
وماذا لو لم تجب الشريحة؟ يتوقف writeto بخطأ من نوع OSError، وبرنامج بلا معالجة يقف هنا. ونلتقطه هكذا:
from machine import I2C, Pin
import board
i2c = I2C(0, scl=Pin(board.SCL), sda=Pin(board.SDA), freq=100000)
try:
i2c.writeto(0x27, bytes([0]))
print("ACK from 0x27")
except OSError:
print("no answer from 0x27")
print("the program goes on")no answer from 0x27 the program goes on
| العرض | السبب الأرجح |
|---|---|
| القائمة فارغة | لا مقاومتي رفع، أو SDA وSCL مبدّلان، أو لا تغذية للشريحة. |
| العنوان غير المتوقع | أطراف العنوان موصولة بطريقة أخرى، أو نسخة أخرى من الشريحة. |
| كل العناوين تجيب، أو المسح يتوقف | أحد السلكين موصول بالأرضي. |
| تعمل أحيانا فقط | سلك طويل، أو سعة كبيرة وزمن صعود طويل، أو سرعة أعلى مما تحتمله الشريحة. |
9.5 توسيع الأطراف: PCF8574 – התנסות ב-PCF8574 (I/O Expander)
الشريحة PCF8574 موسّع أطراف (מרחיב הדקים): ثمانية أطراف P0–P7 نكتبها ونقرؤها عبر الناقل. لا مسجّلات: نكتب بايتا فتتغيّر الأطراف الثمانية، ونقرأ بايتا فنعرف حالتها.
العنوان 0x20 زائد الثلاثة بتّات A2 A1 A0 التي نحدّدها بالأسلاك، فنصل إلى ثماني شرائح على الناقل نفسه. وللنسخة PCF8574A عناوين أخرى تبدأ من 0x38.
أطراف هذه الشريحة شبه ثنائية الاتجاه: تستطيع سحب تيار كبير إلى الأسفل، قد يصل حسب ورقة البيانات إلى 25 mA، لكنها في المستوى المرتفع تعطي تيارا ضعيفا جدا. فنوصل الثنائي الباعث للضوء بين التغذية والطرف، فيضيء عند 0، بعكس ما اعتدناه.
نبدأ بالأمرين الأساسيين بلا مكتبة، مع زرّ على P4 يسحبه إلى الأسفل:
from machine import I2C, Pin
import board
i2c = I2C(0, scl=Pin(board.SCL), sda=Pin(board.SDA), freq=100000)
i2c.writeto(0x20, bytes([0b11111110]))
value = i2c.readfrom(0x20, 1)[0]
print("P7..P0 = {:08b}".format(value))P7..P0 = 11101110 [pcf] fe
Pin.IN هنا: لا مسجّل اتجاه في هذه الشريحة.والآن نكتب صنفا صغيرا، ونلاحظ أنه لا يستورد machine: يعطيه البرنامج الناقل جاهزا، كما في ads1115.py.
# pcf8574.py - driver for the PCF8574 eight-line I2C port expander.
# ESP32 and Pico: pass a machine.I2C. Raspberry Pi 4/5: pass a pi_i2c.PiI2C.
# (c) 2026 Fahed Ghanem - MIT License - fahed-lab.com
class PCF8574:
def __init__(self, i2c, addr=0x20):
self.i2c = i2c
self.addr = addr
self.write(0xFF)
def write(self, value):
self.state = value & 0xFF
self.i2c.writeto(self.addr, bytes([self.state]))
def read(self):
return self.i2c.readfrom(self.addr, 1)[0]
def set_pin(self, n, level):
if level:
self.write(self.state | (1 << n))
else:
self.write(self.state & ~(1 << n))
def get_pin(self, n):
return (self.read() >> n) & 1الدالة set_pin تغيّر طرفا واحدا، لكن الشريحة لا تفهم إلا بايتا كاملا. لذلك تحفظ المكتبة آخر قيمة في self.state فلا تغيّر بقية الأطراف. والآن ضوء يتجوّل على الثنائيات الأربعة:
from machine import I2C, Pin
from pcf8574 import PCF8574
import board
import time
i2c = I2C(0, scl=Pin(board.SCL), sda=Pin(board.SDA), freq=100000)
pcf = PCF8574(i2c, 0x20)
for n in (0, 1, 2, 3, 2, 1, 0):
pcf.write(~(1 << n))
print("LED", n, "on")
time.sleep(0.25)
pcf.write(0xFF)LED 0 on LED 1 on LED 2 on LED 3 on LED 2 on LED 1 on LED 0 on [pcf] ff fe fd fb f7 fb fd fe ff
from machine import I2C, Pin
from pcf8574 import PCF8574
import board
import time
i2c = I2C(0, scl=Pin(board.SCL), sda=Pin(board.SDA), freq=100000)
pcf = PCF8574(i2c, 0x20)
while True:
pressed = pcf.get_pin(4) == 0
pcf.set_pin(0, 0 if pressed else 1)
print("pressed" if pressed else "released")
time.sleep(1)released pressed pressed released ...
9.6 ساعة الزمن الحقيقي DS1307 – התנסות ב-DS1307 (Real-Time Clock)
ساعة المتحكّم تتوقف عند انقطاع التغذية. أما ساعة الزمن الحقيقي (שעון זמן אמת, RTC) فشريحة تعدّ الثواني بلا توقف، ببلّورة صغيرة وبطارية. وعنوان DS1307 هو 0x68 دائما.
داخل الشريحة سبعة مسجّلات بالترميز العشري الثنائي (BCD): كل خانة عشرية في أربعة بتّات. فالثانية 45 تُخزَّن 0100 0101، أي 0x45، وليس 45 بالثنائي العادي. وهكذا يقرأ الإنسان القيمة مباشرة في الست عشري.
العشرات 3 في الأربعة العليا، والآحاد 7 في الأربعة الدنيا، فالبايت 0011 0111، أي 0x37. وهو 55 بالعشري.
البايت 0x59: الأربعة العليا 5 والأربعة الدنيا 9، أي 5 · 10 + 9 = 59.
| المسجّل | المعنى | ملاحظة |
|---|---|---|
| 0 | الثواني | البتّ 7 هو CH: عند 1 تتوقف الساعة |
| 1 | الدقائق | |
| 2 | الساعات | البتّ 6: 0 لنظام 24 ساعة |
| 3 | يوم الأسبوع | من 1 إلى 7، ومعناه نحن نحدّده |
| 4 | يوم الشهر | |
| 5 | الشهر | |
| 6 | السنة | من 00 إلى 99 |
القراءة من شريحة فيها مسجّلات خطوتان: نكتب رقم المسجّل، فيصبح مؤشّر الشريحة عليه، ثم نقرأ، فيتقدّم المؤشّر وحده. وهذا هو البرنامج بلا مكتبة:
from machine import I2C, Pin
import board
i2c = I2C(0, scl=Pin(board.SCL), sda=Pin(board.SDA), freq=100000)
regs = bytes([0, 0x30, 0x59, 0x09, 0x01, 0x02, 0x11, 0x26])
i2c.writeto(0x68, regs)
i2c.writeto(0x68, bytes([0]))
data = i2c.readfrom(0x68, 7)
print(" ".join("%02X" % b for b in data))30 59 09 01 02 11 26
# ds1307.py - driver for the DS1307 real-time clock, 24-hour mode.
# ESP32 and Pico: pass a machine.I2C. Raspberry Pi 4/5: pass a pi_i2c.PiI2C.
# (c) 2026 Fahed Ghanem - MIT License - fahed-lab.com
def to_bcd(x):
return ((x // 10) << 4) | (x % 10)
def from_bcd(b):
return (b >> 4) * 10 + (b & 0x0F)
class DS1307:
def __init__(self, i2c, addr=0x68):
self.i2c = i2c
self.addr = addr
def set_time(self, year, month, day, hour, minute, second, weekday=1):
values = (second, minute, hour, weekday, day, month, year - 2000)
regs = [to_bcd(v) for v in values]
self.i2c.writeto(self.addr, bytes([0] + regs))
def now(self):
self.i2c.writeto(self.addr, bytes([0]))
r = self.i2c.readfrom(self.addr, 7)
return (2000 + from_bcd(r[6]), from_bcd(r[5]), from_bcd(r[4]),
from_bcd(r[2] & 0x3F), from_bcd(r[1]),
from_bcd(r[0] & 0x7F))from machine import I2C, Pin
from ds1307 import DS1307
import board
import time
i2c = I2C(0, scl=Pin(board.SCL), sda=Pin(board.SDA), freq=100000)
rtc = DS1307(i2c)
rtc.set_time(2026, 11, 2, 9, 59, 58)
for i in range(4):
y, mo, d, h, mi, s = rtc.now()
print("%04d-%02d-%02d %02d:%02d:%02d" % (y, mo, d, h, mi, s))
time.sleep(1)2026-11-02 09:59:58 2026-11-02 09:59:59 2026-11-02 10:00:00 2026-11-02 10:00:01
set_time تصفّره. ولا نستدعي set_time عند كل تشغيل: فهذا يعيد الساعة إلى الوقت نفسه كل مرة. نضبطها مرة، ثم نحذف السطر أو نجعله شرطا، كما في تمرين آخر الفصل.والفرق عن ساعة machine.RTC التي في المتحكّم: تلك تتوقف أو تضيع مع انقطاع التغذية، وهذه تبقى بفضل البطارية. وهناك طريقة ثالثة حين تتوفر الشبكة: أخذ الوقت منها.
9.7 شاشة LCD عبر جسر PCF8574 – תצוגת LCD 16x2 (Text Display)
شاشة النص 16×2 مبنية حول المتحكّم HD44780، وتحتاج في الأصل إلى ستة أطراف على الأقل. والجسر (גשר) الصغير المثبّت خلفها ما هو إلا PCF8574: يحوّل ما نكتبه على الناقل إلى أطراف الشاشة. فكل ما نحتاجه هو سلكا الناقل. وعنوان الجسر الشائع 0x27، أو 0x3F مع PCF8574A، والمسح يخبرنا.
الجسر الشائع يصل الشاشة بأربعة بتّات بيانات فقط. فكل بايت نقسمه إلى نصفين: الأربعة العليا أولا، ثم الدنيا. والطرف E هو أمر «خذ الآن»: تقرأ الشاشة البيانات عند هبوطه. والطرف RS يفصل الأمر (0) عن الحرف (1).
A، وقيمته 0x41، والإضاءة تعمل؟في كل بايت: نصف الحرف في البتّات العليا، مع RS = 1 وLight = 1، وE مرفوع ثم منخفض.
النصف الأعلى 4: 0x4D ثم 0x49.
النصف الأدنى 1: 0x1D ثم 0x19.
نرى أولا كيف نستعمل المكتبة i2c_lcd.py، ثم ننظر في داخلها:
from machine import I2C, Pin
from i2c_lcd import LCD
import board
import time
i2c = I2C(0, scl=Pin(board.SCL), sda=Pin(board.SDA), freq=100000)
lcd = LCD(i2c, 0x27)
lcd.line(0, "Hello, I2C")
for n in range(1, 4):
lcd.line(1, "Count: %d" % n)
time.sleep(1)
print("done")done [LCD] |Hello, I2C | |Count: 3 |
والدالة line تكتب سطرا كاملا وتملأ الباقي بفراغات، بدلا من clear. لماذا؟ لأن المسح يُطفئ الشاشة لحظة فترتجف. والسرعة مهمّة هنا أيضا:
الحرف أمران writeto، في كل منهما عنوان وبايتان، أي 6 بايتات، بـ 9 نبضات لكل بايت.
وهذه المكتبة i2c_lcd.py كاملة، كما هي على اللوحة، وبعدها شرح أوامرها:
# i2c_lcd.py - HD44780 text display behind a PCF8574 I2C bridge.
# Bridge lines: P0=RS P1=RW P2=E P3=light P4..P7=D4..D7.
# ESP32 and Pico: pass a machine.I2C. Raspberry Pi 4/5: pass a pi_i2c.PiI2C.
# (c) 2026 Fahed Ghanem - MIT License - fahed-lab.com
import time
RS = 0x01
ENABLE = 0x04
LIGHT = 0x08
ROW_START = (0x00, 0x40, 0x14, 0x54)
class LCD:
def __init__(self, i2c, addr=0x27, cols=16):
self.i2c = i2c
self.addr = addr
self.cols = cols
self.light = LIGHT
time.sleep(0.05)
for high in (0x30, 0x30, 0x30, 0x20):
self._nibble(high, 0)
time.sleep(0.005)
self._send(0x28, 0)
self._send(0x0C, 0)
self._send(0x06, 0)
self.clear()
def _nibble(self, high, rs):
out = high | rs | self.light
self.i2c.writeto(self.addr, bytes([out | ENABLE, out]))
def _send(self, value, rs):
self._nibble(value & 0xF0, rs)
self._nibble((value << 4) & 0xF0, rs)
def clear(self):
self._send(0x01, 0)
time.sleep(0.002)
def move(self, col, row):
self._send(0x80 | (ROW_START[row] + col), 0)
def write(self, text):
for ch in text:
self._send(ord(ch), RS)
def line(self, row, text):
self.move(0, row)
self.write((text + " " * self.cols)[:self.cols])
def backlight(self, on):
self.light = LIGHT if on else 0
self.i2c.writeto(self.addr, bytes([self.light]))| الأمر | عمله |
|---|---|
| (0x30, 0x30, 0x30, 0x20) | الشاشة تستيقظ في وضع 8 بتّات، وهذا التسلسل من ورقة بياناتها ينقلها إلى وضع 4 بتّات. |
| self._send(0x28, 0) | أمر: 4 بتّات وسطران. |
| self._send(0x0C, 0) | أمر: الشاشة تعمل، بلا مؤشّر. |
| self._send(0x06, 0) | أمر: يتقدّم الموضع يمينا بعد كل حرف. |
| self._send(0x01, 0) | أمر المسح، وهو بطيء، فننتظر بعده. |
| 0x80 | ... | أمر تحديد الموضع. والسطر الثاني يبدأ عند 0x40. |
9.8 شاشة OLED ووحدة framebuf – תצוגת OLED (Graphic Display)
شاشة OLED الصغيرة، 128×64 نقطة بالشريحة SSD1306، تتحكّم بكل نقطة. وعنوانها الشائع 0x3C، وتعمل بسرعة 400 kHz، فيكفينا هنا board.i2c(). والفكرة: نرسم في الذاكرة، ثم نرسل الصورة كلها مرة واحدة. وفي مايكروبايثون وحدة جاهزة للرسم في الذاكرة، هي framebuf. فتكفينا مكتبة قصيرة تنقل الذاكرة إلى الشاشة:
# oled.py - SSD1306 graphic display, 128 x 64, on I2C. MicroPython only.
# Drawing commands come from framebuf: text, line, rect, fill, pixel.
# (c) 2026 Fahed Ghanem - MIT License - fahed-lab.com
import framebuf
WIDTH = 128
HEIGHT = 64
SETUP = (0xAE, 0x20, 0x00, 0xA1, 0xC8, 0xA8, HEIGHT - 1, 0xDA, 0x12,
0x81, 0x7F, 0x8D, 0x14, 0xAF)
class OLED(framebuf.FrameBuffer):
def __init__(self, i2c, addr=0x3C):
self.i2c = i2c
self.addr = addr
self.data = bytearray(1 + WIDTH * HEIGHT // 8)
self.data[0] = 0x40
pixels = memoryview(self.data)[1:]
super().__init__(pixels, WIDTH, HEIGHT, framebuf.MONO_VLSB)
for byte in SETUP:
self._command(byte)
def _command(self, byte):
self.i2c.writeto(self.addr, bytes([0x00, byte]))
def show(self):
for byte in (0x21, 0, WIDTH - 1, 0x22, 0, HEIGHT // 8 - 1):
self._command(byte)
self.i2c.writeto(self.addr, self.data)ورث الصنف OLED من framebuf.FrameBuffer كل أوامر الرسم. وكل كتابة تبدأ ببايت تحكّم: 0x00 للأوامر، و0x40 للبيانات. ولذلك خصّصنا له أول بايت في الذاكرة، فلا ننسخ الصورة عند الإرسال.
framebuf هنا حقيقية، وطبعنا الزاوية العليا من الذاكرة بعد الرسم.from oled import OLED
import board
screen = OLED(board.i2c(), 0x3C)
screen.fill(0)
screen.text("OK", 0, 0)
screen.rect(0, 12, 128, 20, 1)
screen.show()
for y in range(8):
row = ""
for x in range(16):
row += "#" if screen.pixel(x, y) else "."
print(row)..####...##..##. .##..##..##.##.. .##..##..####... .##..##..###.... .##..##..####... .##..##..##.##.. ..####...##..##. ................
| الأمر | عمله |
|---|---|
| screen.fill(0) | يمسح الذاكرة. |
| screen.text("OK", x, y) | نص بخط 8×8 نقاط. |
| screen.pixel(x, y, 1) | يرسم نقطة. وبلا القيمة الثالثة يقرؤها. |
| screen.line(x1, y1, x2, y2, 1) | خط. |
| screen.rect(x, y, w, h, 1) | مستطيل. |
| screen.show() | يرسل الذاكرة إلى الشاشة. دونه لا يظهر شيء. |
9.9 كل الشرائح على ناقل واحد – קשר ל-ADS1115 (Everything on One Bus)
في الفصل 6 وصلنا المحوّل ADS1115 بالناقل نفسه، ومكتبته كانت مكتوبة بالطريقة التي نكتب بها هنا. فلنجمع المحوّل والساعة والشاشة بثلاثة كائنات تتشارك ناقلا واحدا. ولا نغيّر شيئا في المكتبات، والسرعة هي سرعة أبطأ شريحة:
from machine import I2C, Pin
from ads1115 import ADS1115
from ds1307 import DS1307
from i2c_lcd import LCD
import board
import time
i2c = I2C(0, scl=Pin(board.SCL), sda=Pin(board.SDA), freq=100000)
adc = ADS1115(i2c)
rtc = DS1307(i2c)
lcd = LCD(i2c)
rtc.set_time(2026, 11, 2, 9, 30, 0)
while True:
y, mo, d, h, mi, s = rtc.now()
volts = adc.read_volts(0)
lcd.line(0, "%02d:%02d:%02d" % (h, mi, s))
lcd.line(1, "A0 = %.3f V" % volts)
print("%02d:%02d:%02d %.3f V" % (h, mi, s, volts))
time.sleep(1)09:30:00 0.500 V 09:30:01 1.250 V 09:30:02 2.500 V [LCD] |09:30:02 | |A0 = 2.500 V | ...
والعنوان وحده هو ما يفصل بين الشرائح، فلا يتشارك جهازان العنوان نفسه. فإذا احتجنا إلى جهازين متطابقين بعنوان ثابت، نستعمل ناقلا ثانيا I2C(1) على طرفين آخرين.
9.10 على Raspberry Pi 4/5 – ב-Raspberry Pi
المكتبات الثلاث pcf8574.py وds1307.py وi2c_lcd.py لا تطلب من الناقل إلا writeto وreadfrom. لذلك تعمل على الباي دون تغيير، بالملف pi_i2c.py الذي كتبناه في الفصل 6، وبالتحضير نفسه. أما oled.py فلا تعمل، لأن framebuf جزء من مايكروبايثون وليست في بايثون العادي.
import وPiI2C() هما الفرق الوحيد عن برنامج المتحكّم.import time
from pcf8574 import PCF8574
from pi_i2c import PiI2C
pcf = PCF8574(PiI2C(), 0x20)
for n in range(4):
pcf.write(~(1 << n))
print("LED", n, "on")
time.sleep(0.25)
pcf.write(0xFF)LED 0 on LED 1 on LED 2 on LED 3 on
أهمّ أفكار الفصل:
- سلكان لعشرات الشرائح: الرئيسي يولّد الساعة، وكل تابع يجيب حين يسمع عنوانه.
- الشرائح تسحب الخط فقط، ومقاومتا الرفع هما اللتان ترفعانه. دونهما لا ناقل.
- البداية والتوقف هما التغيّر الوحيد في SDA وSCL مرتفع، والإقرار بعد كل بايت يخبرنا أن الطرف الآخر موجود.
- نبدأ كل شريحة جديدة بالمسح، ونجعل السرعة سرعة أبطأ شريحة.
- PCF8574 يضيء عند 0، ولقراءة طرف نكتب فيه 1 أولا.
- DS1307 يخزّن بـBCD، وشاشة النص تأخذ كل بايت نصفين.
- مكتبة لا تطلب من الناقل إلا
writetoوreadfromتعمل على كل اللوحات.
ملخّص قوانين الفصل:
| القانون | الصيغة |
|---|---|
| مقاومة الرفع الدنيا | Rmin = VDD − VOLIOL |
| زمن الصعود | tr = 0.8473 · R · Cb |
| بايت العنوان | b = 2 · A + d |
أوامر الفصل:
| الأمر | عمله |
|---|---|
| I2C(0, scl=Pin(n), sda=Pin(n), freq=f) | ينشئ الناقل، على اللوحتين. |
| i2c.scan() | قائمة العناوين التي أجابت. |
| i2c.writeto(addr, data) | كتابة بايتات. |
| i2c.readfrom(addr, n) | قراءة بايتات. |
| PCF8574(i2c, addr) | موسّع الأطراف: write، read، set_pin، get_pin. |
| DS1307(i2c) | الساعة: set_time، now. |
| LCD(i2c, addr) | شاشة النص: line، move، write، clear، backlight. |
| OLED(i2c, addr) | شاشة الرسم: كل أوامر framebuf، ثم show. |
قاموس المصطلحات – מילון מונחים:
| بالعربية | بالعبرية | English |
|---|---|---|
| الجهاز الرئيسي | בקר ראשי | Master |
| الجهاز التابع | עבד | Slave |
| المصرف المفتوح | ניקוז פתוח | Open Drain |
| مقاومة الرفع | נגד משיכה | Pull-up Resistor |
| شرط البداية | תנאי התחלה | Start Condition |
| شرط التوقف | תנאי עצירה | Stop Condition |
| الإقرار | אישור | Acknowledge |
| العنوان | כתובת | Address |
| موسّع الأطراف | מרחיב הדקים | I/O Expander |
| ساعة الزمن الحقيقي | שעון זמן אמת | Real-Time Clock |
| الترميز العشري الثنائي | קוד BCD | Binary-Coded Decimal |
| الجسر | גשר | Bridge |
| التباين | ניגודיות | Contrast |
أسئلة للتمرين:
from machine import I2C, Pin
from pcf8574 import PCF8574
import board
import time
i2c = I2C(0, scl=Pin(board.SCL), sda=Pin(board.SDA), freq=100000)
pcf = PCF8574(i2c, 0x20)
for n in range(16):
pcf.write(~n)
time.sleep(0.5)
print("done")True إذا كانت الساعة تعدّ، بفحص البتّ CH. واستعملوها لضبط الوقت مرة واحدة فقط.def running(rtc):
rtc.i2c.writeto(rtc.addr, bytes([0]))
return not rtc.i2c.readfrom(rtc.addr, 1)[0] & 0x80if not running(rtc): rtc.set_time(...).writeto(addr, bytes([0]))؟ وماذا يحدث دونه؟