בפוסט הזה נלמד איך מפעילים מסך קטנטן (מונוכרומטי) עם ESP32 כדי להציג פלט דיגיטלי באופן נעים ונחמד. אני יוצא מנקודת הנחה שקראתם את הפוסטים הקודמים שלי בנושא ESP32. אם לא – אני ממליץ בחום להתחיל עם הפוסט הראשון בסדרה שמסביר ממש ממש מאפס על איך נכנסים לעולם המופלא של ESP32.
המצרכים הם:
ESP32 – כאמור בכל עלי אקספרס הקרוב למקום מגוריכם.
כמה כבלים קטנים שנקראים ג׳מפרים
מסך OLED התומך ב-SSD1306 – ניתן לקניה בעלי אקספרס בעלות של שקלים בודדים.
הנה קישורים לקניית ה-ESP32, ה-OLED והג׳מפרים. הם קישורים בלי affiliate אז אני לא מקבל שקל על קניה דרכם. זה נטו על מנת לסייע. אם המוצר לא זמין, פשוט תחפשו בעלי אקספרס. יש מלא כאלו בזיל הזול.
אני גם נעזר, כפי שאפשר לראות בתמונות, ב-breadboard (הלוח הלבן, שכתבתי עליו בעבר) וב-ESP32 Development Board שעליו יושב ה-ESP32. אבל זה נטו לנוחות שלי וממש לא מחייב.
השלב הבא הוא לתכנן. בגדול מסכי SSD1306 מכילים 4 פינים.
VCC – מתחבר לפין של ה-3.3V
GND – מתחבר לפין גראונד
SCL – מתחבר לפין SCL
SDA – מתחבר לפין SDA
התקשורת נעשית באמצעות פרוטוקול I²C. לא צריך להכנס ללחץ, רק לשים לב היטב ב-pinout שלכם היכן הפינים האלו נמצאים. ב-ESP32 עם 30 פינים הם נמצאים ב-GPIO 22 – עבור SCL וב-GPIO 21 עבור SDA. בדיוק כמו בסכימה שצירפתי. נעזרתי ב-Wokwit כהרגלי שהוא כלי סופר נוח והקדשתי לו פוסט משל עצמו.
השלב הבא הוא לכתוב את הקוד, נפתח את Thonny הנאמן שלנו ונתחבר ל-ESP32. ניצור שם קובץ בשם ssd1306.py ונעתיק אליו את התוכן הזה – זה הדרייבר שאליו אנחנו מתחברים.
השלב הבא הוא ליצור את הקוד שממש מתחבר וכותב למסך. מדובר בקוד פשוט ממש:
from machine import Pin, SoftI2C
import ssd1306
from time import sleep
i2c = SoftI2C(scl=Pin(22), sda=Pin(21))
oled_width = 128
oled_height = 64
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)
oled.text('internet-israel!', 0, 0)
oled.text('I LOVE Python', 0, 10)
oled.show()
נשמור את הקוד ונלחץ על play ו… נוכל לראות את הקוד בעיניים!
אם זה לא עובד:
- הריצו את הקוד שלכם באמולטור של ESP32. הנה קישור למערכת עם OLED שיש עליה כבר דרייבר. האם זה עובד?
- בדיקה והחלפה של הג׳מפרים.
- לפעמים המסך עצמו תקול 🙁
אם זה עובד – השמים הם הגבול. אפשר לצייר ולעשות דברים מאד מעניינים.
הנה דוגמה קטנה ומטופשת:
איך ציירתי את הלב? עם פייתון וקצת עזרה מצ׳אט GPT:
from machine import Pin, SoftI2C
import ssd1306
from time import sleep
# ESP32 Pin assignment
i2c = SoftI2C(scl=Pin(22), sda=Pin(21))
oled_width = 128
oled_height = 64
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)
def draw_filled_heart(oled, x_offset, y_offset):
# Define heart shape with filling
heart_pixels = [
# Left side of the heart
(-1,2), (-2,1), (-3,1), (-4,2),
(-5,3), (-5,4), (-5,5), (-4,6), (-3,7),
(-2,8), (-1,8), (0,9),
# Right side of the heart
(1,2), (2,1), (3,1), (4,2),
(5,3), (5,4), (5,5), (4,6), (3,7),
(2,8), (1,8), (0,9),
]
for pixel in heart_pixels:
oled.pixel(x_offset + pixel[0], y_offset + pixel[1], 1)
# Clear display
oled.fill(0)
# Draw text and filled heart
oled.text('I', 5, 10)
draw_filled_heart(oled, 25, 8)
oled.text('Python', 40, 10)
oled.show()
אפשר להשתעשע עם כל מיני דברים אחרים – למשל להדפיס את מלחמת הכוכבים. איך? אני לא אדביק את הקוד כי באמת ידידנו GPT מדהים עם זה. פשוט לבקש ממנו את השאילתה הבאה:
I am using ESP32 with SSD1306 on micropython. Please create a code that
Create a starwars scrolling text from below to the top
תוכלו לקבל את הקוד ולעבוד איתו בדיוק כמוני.
אם אתם עובדים עם מינאטורות/צעצועים אז כמובן שהפוטנציאל פה הוא נפלא. אבל כשמחברים את זה לחיישנים למשל או לווב, זה חוסך לא מעט כאב ראש – כאן למשל אני מדפיס את כתובת ה-IP שאני מקבל כשאני מתחבר לרשת. מאד מאד שימושי כשאני מחבר חיישנים לרשת למשל.
from machine import Pin, SoftI2C
import ssd1306
import network
from time import sleep
# ESP32 Pin assignment
i2c = SoftI2C(scl=Pin(22), sda=Pin(21))
oled_width = 128
oled_height = 64
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)
# Assuming each line of log takes up 10 pixels vertically
LINE_HEIGHT = 10
MAX_LINES = oled_height // LINE_HEIGHT
logs = []
def display_logs():
"""Update the OLED with the latest logs."""
oled.fill(0) # Clear display
start_index = max(0, len(logs) - MAX_LINES) # Start from the end of logs
for i, log_line in enumerate(logs[start_index:]):
oled.text(log_line, 0, i * LINE_HEIGHT)
oled.show()
def add_log(log_message):
"""Add a new log message and update the OLED."""
logs.append(log_message)
display_logs()
# WiFi setup and connection
SSID = "MY NETWORK"
PASSWORD = "MY PASSWORD"
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
add_log("Connecting...")
wlan.connect(SSID, PASSWORD)
while not wlan.isconnected():
sleep(0.5)
ip_address = wlan.ifconfig()[0]
add_log(f"Got UIP")
add_log(ip_address)
else:
ip_address = wlan.ifconfig()[0]
add_log(f"Already connected! IP: {ip_address}")
add_log(ip_address)
זה באמת נהדר לכל מיני סטטוסים ובמיוחד ליחידות שלא נמצאות קרוב למחשב.
אבל רגע רגע רגע רגע – מה עם סאונד?
אפשר לייצר סאונד בכמה דרכים – למשל עם בלוטות׳, אבל אם אנחנו לא רוצים להסתבך יותר מדי וגם על הדרך לראות threads בעבודה – אנחנו ניקח רמקול שנקרא רמקול פיאזו. זה בעצם זמזם קטן ופרימיטיבי אבל שאנו יכולים לנגן איתו מנגנינות. אפשר לקנות אותו בכמה שקלים (מחפשים piezzo buzzer בעלי אקספרס) והוא גם נמצא בכל קיט סנסורים של ארדואינו. יש כאלו עם שלוש פינים ויש כאלו עם שניים.
בכאלו עם שני פינים – פין אחד הולך ל- GPIO ופין אחד הולך לגראונד.
בכאלו עם שלושה פינים – פין אחד הולך ל-GPIO, פין שני למתח 3V ופין אחד הולך לגראונד.
הנה הסרטוט של ESP32 מחובר גם ל-OLED וגם לבאזר:
הסרטוטים והתכנון הם חשובים כי בפועל כבר יש לנו שישה ג׳מפרים וזה נראה כבר מבולגן בצילום:
הבאזר עובד עם פין שתומך ב-Pulse-width modulation או יותר נכון PWM – פלט אנלוגי שמאפשר לנו לא רק להעביר מתח או לא (1 או 0) אלא סוג של עוצמה של מתח. כמעט כל הפינים ב-ESP32 תומכים ב-pwm ואחד מהם הוא GPIO13 שאליו אני מחבר את הג׳מפר מהפין המתאים של הבאזר.
איך מתקשרים עם הפין? בגדול עם שני פרמטרים – התדר (frequency) שקובע את הצליל ו-duty_cycle שקובע את הזמן שבו הוא מופעל (זה הסבר מאד שטחי אבל מספיק למטרתנו. הנה קוד לדוגמה שמראה איך אני משמיע צלילים עם באזר שמחובר ל-GPIO 13.
import machine
# Set up the GPIO pin for PWM output
pwm_pin = machine.Pin(13, machine.Pin.OUT)
# Create a PWM object on the specified pin
pwm = machine.PWM(pwm_pin)
# Set the PWM frequency and duty cycle
frequency = 1000 # Frequency in Hertz
duty_cycle = 512 # Duty cycle (0 to 1023)
pwm.freq(frequency) # Set the frequency
pwm.duty(duty_cycle) # Set the duty cycle (0 to 1023)
# You can also use pwm.deinit() to turn off the PWM when you're done
# pwm.deinit()
או קיי, אז מה השלב הבא? בעצם ליצור פונקיציה שמקבל תדירות וזמן ולנגן ולהעביר לה את המנגינה של מלחמת הכוכבים.
אך אם תנסו זאת בעצמכם או שתבקשו מצ׳אט ג׳יפיטי לעשות זאת, תגלו שהמנגינה מתחילה, מסתיימת ואז הטקסט מנגן. למה זה קורה? כי הקוד הוא בת׳רד אחד – הוא מריץ קודם את המנגינה ורק אחר כך את האנימציה. אני רוצה ששניהם ירוצו במקביל.
בשביל זה אנחנו צריכים לעבוד עם Threads – תהליכים מקבילים. עם מיקרופייתון זה קל ממש! יש מודול שנקרא threads_ שמאפשר לנו ליצור ת׳רדס ולהעביר להם את הפונקציה שתרוץ. הנה, כך נראה קוד מלא שגם מנגן וגם מריץ את האנימציה. הקוד המלא הוא:
from machine import Pin, SoftI2C
import ssd1306
import machine
import time
import _thread
# Set up the GPIO pin connected to the piezo buzzer
buzzer_pin = machine.Pin(13, machine.Pin.OUT)
# Define notes frequencies (in Hertz)
# Format: (note, duration)
notes = [
(440, 500), # A
(440, 500), # A
(440, 500), # A
(349, 350), # F
(523, 150), # C
(440, 500), # A
(349, 350), # F
(523, 150), # C
(440, 1000), # A
(659, 500), # E
(659, 500), # E
(659, 500), # E
(698, 350), # F#
(523, 150), # C
(415, 500), # Ab
(349, 350), # F
(523, 150), # C
(440, 1000), # A
]
# Function to play a note
def play_note(pin, freq, dur):
pwm = machine.PWM(pin)
pwm.freq(freq)
pwm.duty(512) # 50% duty cycle
time.sleep(dur / 1000) # Convert duration to seconds
pwm.deinit() # Turn off the PWM
time.sleep(0.05) # Add a small delay between notes
# Function to play a note
def play_note_thread(freq, dur):
play_note(buzzer_pin, freq, dur)
# Create a thread for music playback
def play_music_thread():
for note_freq, note_dur in notes:
play_note_thread(note_freq, note_dur)
# Text to scroll
star_wars_text = [
"A long time ago,",
"in a galaxy far,",
"far away...",
"",
"STAR WARS",
"",
"Episode IV",
"A NEW HOPE",
"",
"It is a period",
"of civil war. Rebel",
"spaceships, striking",
"from a hidden base, ",
"have won their first",
"victory against the",
"evil Galactic Empire.",
"",
"During the battle,",
"Rebel spies managed",
"to steal secret",
"plans to the",
"Empire's ultimate weapon",
"the DEATH STAR",
"an armored space station",
"with enough power to",
"destroy an entire planet.",
"",
"Pursued by the Empire’s",
"sinister agents, Princess",
"Leia races home aboard her",
"starship, custodian of the",
"stolen plans that can save",
"her people and restore",
"freedom to the galaxy..."
]
# ESP32 Pin assignment
i2c = SoftI2C(scl=Pin(22), sda=Pin(21))
oled_width = 128
oled_height = 64
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)
# Initial position
y_position = oled_height
# Start the music playback thread
_thread.start_new_thread(play_music_thread, ())
while True:
# Clear display
oled.fill(0)
# Draw each line of the text
for i, line in enumerate(star_wars_text):
y = y_position + (i * 10)
oled.text(line, 0, y)
# Display the frame
oled.show()
# Move the text up by 1 pixel
y_position -= 1
# When the last line of the text goes out of the display,
# reset y_position to start over
if y_position + len(star_wars_text) * 10 < 0:
y_position = oled_height
# Delay to control the speed of the scrolling
time.sleep(0.1)
ומי שרוצה לשחק איתו יכול בהחלט בדמו שלי ב-wokwi.
חובה להכיר את המונחים האלו גם אם אתם סומכים על בינה מלאכותית שתבנה לכם את הקוד כי בחלק מהמודלים היא לא תציע את הפתרון הזה. כדאי להנחות אותה מראש, אם יש לנו כמה פעולות, להשתמש בת׳רדס.
הפוסט הבא יראה איך משתמשים בג׳ויסטיק אנלוגי.
5 תגובות
היי רן. תודה על כל המדריכים!
האם צריך לקנות ג'מפרים זכר/נקבה, או קומבינציה אחרת?
אני קונה פשוט חבילה שמכילה את הכל: נקבה/נקבה, זכר/זכר ונקבה/זכר. זה עולה 17₪ בעלי אקספרס 🙂 הנה קישור למשהו שהזמנתי לא מזמן.
תודה על המדריך וחשוב לא פחות על המוטיבציה, ההכוונה והרעיון.
קנינו וכבר הספקנו לעשות כמה פרוייקטים קטנים ומגניבים. הרעיון הנוכחי של בן ה-6 הוא להכין מכשיר לשים אצל הסבתא שאליו הוא יוכל לשלוח הודעות דרך אתר יעודי באינטרנט, והיא תוכל לענות ע"י לחיצה על אחד משני כפתורים.
בעיה שיש לי ולא מצאתי פתרון ברשת (או בChat GPT) זה שהרכיב נתקע (אבל ממש) בכל נסיון לפעולת רשת. כרגע נוטה להאמין שהיחידה תקולה ואאלץ לקנות חדשה.
תודה! מה לגבי עברית על המסך הזה במיקרופייתון?
צריך דרייבר לזה 🙂 האמת שזה רעיון לפרויקט קטן ונחמד.