אפיק I2C
בפרק הקודם כל זוג חוטים חיבר שני התקנים בלבד. אבל בפרויקט אמיתי יש שעון, מסך, ממיר וחיישן, והדקי הלוח מוגבלים. כאן בא אפיק ששני חוטים מספיקים לו לעשרות שבבים, ולכל שבב יש כתובת. נבין מה קורה על שני החוטים סיבית אחרי סיבית, ואז נכתוב בעצמנו ספריות לשלושה שבבים ולשני מסכים, ונחבר את כולם לאפיק אחד.
- נסביר את תפקידם של SDA ו-SCL ושל נגדי המשיכה, ונחשב את גבולות ערכם.
- נקרא עסקה על האפיק: התחלה, כתובת, אישור, נתונים ועצירה.
- נסרוק את האפיק, נמצא את הכתובת של כל שבב, ונטפל בשבב שלא עונה.
- נרחיב את הדקי הלוח בעזרת השבב PCF8574 ונדליק 4 נוריות דרך שני הדקים בלבד.
- נקרא את השעון DS1307 ונכוונן אותו, ונתמודד עם הקידוד BCD.
- נכתוב ספרייה למסך LCD וספרייה למסך OLED.
9.1 מאפייני האפיק (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 (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 (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 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 (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
שלוש הספריות pcf8574.py, ds1307.py ו-i2c_lcd.py מבקשות מהאפיק רק writeto ו-readfrom. לכן הן עובדות ב-Pi בלי שינוי, עם הקובץ 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]))? ומה קורה בלעדיה?