הפעלת מסך oled קטנטן עם esp32

יצירת מערכת מולטימדיה שלמה בגודל של בול דואר עם מיקרובקר ובעלות של דולרים בודדים. וגם על הדרך נלמד על ת׳רדים

בפוסט הזה נלמד איך מפעילים מסך קטנטן (מונוכרומטי) עם 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 ו… נוכל לראות את הקוד בעיניים!

אם זה לא עובד:

  1. הריצו את הקוד שלכם באמולטור של ESP32. הנה קישור למערכת עם OLED שיש עליה כבר דרייבר. האם זה עובד?
  2. בדיקה והחלפה של הג׳מפרים.
  3. לפעמים המסך עצמו תקול 🙁

אם זה עובד – השמים הם הגבול. אפשר לצייר ולעשות דברים מאד מעניינים.

הנה דוגמה קטנה ומטופשת:

איך ציירתי את הלב? עם פייתון וקצת עזרה מצ׳אט 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.

חובה להכיר את המונחים האלו גם אם אתם סומכים על בינה מלאכותית שתבנה לכם את הקוד כי בחלק מהמודלים היא לא תציע את הפתרון הזה. כדאי להנחות אותה מראש, אם יש לנו כמה פעולות, להשתמש בת׳רדס.

הפוסט הבא יראה איך משתמשים בג׳ויסטיק אנלוגי.

פוסטים נוספים שכדאי לקרוא

מיקרו בקרים

חיבור מצלמה למיקרובקר

חיבור מצלמה למיקרו בקר ויצירה של מצלמת אבטחה מרחוק בעלות של 20 שקל.

גלילה לראש העמוד