אפיק 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 (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 (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 (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 (Touch Screen XPT2046)
על הרבה ממסכים אלה יש שכבת מגע התנגדותית. כשלוחצים, שתי שכבות נוגעות זו בזו ונוצר מחלק מתח, ומקום הלחיצה הוא המתח. הרכיב XPT2046 הוא ממיר A/D של 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 (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 | Serial Peripheral Interface | ناقل SPI |
| קו שעון | 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 מקומות ימינה?