חיישנים ומפעילים נפוצים
כבר הכרנו את ההדקים, האפיקים והספריות. עכשיו מגיע תורם של הרכיבים שנמצאים בכל פרויקט. לכל רכיב אותה תבנית: איך הוא עובד, איך מחברים אותו, הקוד, ואז המלכודת שרוב המתחילים נופלים בה. בסוף נחבר אותם למערכת קטנה.
- נקרא טמפרטורה ולחות מ-DHT11/22 בלי לעבור את מגבלת קצב הקריאה.
- נדליק רצועת NeoPixel ונחשב את הזרם שלה לפני החיבור.
- נפעיל ממסר דרך טרנזיסטור בבטחה, ונבין את תפקיד דיודת החופש.
- נקרא מגע ב-ESP32 וג׳ויסטיק דרך שני ממירים.
- נשתמש בחיישני I2C בעזרת ספרייה, ונבנה מערכת של חיישן, סף ותצוגה.
| הרכיב | סוגו | איך מחברים | סעיף |
|---|---|---|---|
| DHT11 / DHT22 | טמפרטורה ולחות | הדק ספרתי אחד | 13.1 |
| HC-SR04 | מרחק | שני הדקים ספרתיים, פולס | 13.2 ו-4.5 |
| NeoPixel | מפעיל: אור צבעוני | הדק ספרתי אחד | 13.3 |
| ממסר | מפעיל: מתג לעומס נפרד | הדק ספרתי וטרנזיסטור | 13.4 |
| TouchPad | מגע | הדק אחד, ESP32 בלבד | 13.5 |
| KY-023 | מיקום וכיוון | שני ממירים והדק ספרתי | 13.6 |
| BMP280 / AHT20 | לחץ, טמפרטורה ולחות | I2C | 13.7 |
13.1 טמפרטורה ולחות: DHT11 ו-DHT22
העיקרון: בתוך היחידה יש חיישן ובקר קטן שמודדים ושולחים את התוצאה כמספרים דרך חוט נתונים אחד. מצדנו זה הדק ספרתי רגיל, בלי ממיר. המודול dht הוא חלק מהקושחה בשני הלוחות.
| DHT11 | DHT22 | |
|---|---|---|
| תחום הטמפרטורה (טיפוסי) | 0 – 50 °C | −40 – 80 °C |
| תחום הלחות (טיפוסי) | 20 – 90 % | 0 – 100 % |
| הקריאה המהירה ביותר לפי התיעוד | פעם בשנייה | פעם בשתי שניות |
import board
import dht
import time
from machine import Pin
sensor = dht.DHT22(Pin(board.DHT))
while True:
try:
sensor.measure()
t = sensor.temperature()
h = sensor.humidity()
print("%.1f C %.1f %%" % (t, h))
except OSError:
print("no answer from the sensor")
time.sleep(2)24.0 C 55.0 % 24.5 C 56.0 % 25.0 C 57.0 % ...
import board
import dht
import time
from machine import Pin
sensor = dht.DHT22(Pin(board.DHT))
while True:
try:
sensor.measure()
t = sensor.temperature()
h = sensor.humidity()
print("%.1f C %.1f %%" % (t, h))
except OSError:
print("no answer from the sensor")
time.sleep(2)no answer from the sensor ...
measure() היא שקוראת מן החיישן, ואילו temperature() מחזירה את הערך האחרון. מי ששוכח את measure יראה את אותו מספר לנצח. ואם קוראים מהר יותר מן המגבלה, התוצאה עלולה להיות לא מדויקת או שהמדידה תיכשל.אם כמה חלקים בתוכנית מבקשים את הטמפרטורה בקצב גבוה, שמים את המגבלה במחלקה ששומרת את הקריאה האחרונה:
import board
import dht
import time
from machine import Pin
class Climate:
def __init__(self, pin, min_ms=2000):
self.sensor = dht.DHT22(Pin(pin))
self.min_ms = min_ms
self.last = None
def read(self):
now = time.ticks_ms()
if (self.last is None
or time.ticks_diff(now, self.last) >= self.min_ms):
self.sensor.measure()
self.last = now
return self.sensor.temperature()
room = Climate(board.DHT)
for _ in range(5):
print("%.1f C" % room.read())
time.sleep(1)24.0 C 24.0 C 26.0 C 26.0 C 28.0 C
13.2 מרחק: HC-SR04
את החיישן הזה הסברנו בסעיף 4.5: העיקרון, מחלק המתח על ECHO והספרייה hcsr04.py. לחיבור חוזרים לשם, וכאן משתמשים בספרייה עם סף:
None אם לא חזר הד.import board
import time
from hcsr04 import HCSR04
NEAR_CM = 30
sensor = HCSR04(board.TRIG, board.ECHO)
for _ in range(3):
d = sensor.distance_cm()
if d is None:
print("no echo")
else:
print("%.1f cm" % d, "NEAR" if d < NEAR_CM else "far")
time.sleep(1)20.0 cm NEAR no echo 100.0 cm far ...
None לפני ההשוואה: None < 30 היא שגיאה שעוצרת את התוכנית. בד או משטח משופע עשויים שלא להחזיר הד, ולכן אסור להסיק מ-None שהדרך פנויה.13.3 הרצועה הצבעונית: NeoPixel (NeoPixel, WS2812)
העיקרון: בכל נורית ברצועה יש שבב קטן. הנתונים נכנסים לנורית הראשונה, והיא לוקחת לעצמה שלושה בתים: אדום, ירוק וכחול, ומעבירה את השאר לנורית הבאה. לכן הדק אחד מספיק לכל הרצועה. הצבעים משתנים רק ב-write().
זה יותר ממה שמותר לדרוש מהדק ההזנה של הלוח, ולכן משתמשים במקור חיצוני.
import board
import neopixel
import time
from machine import Pin
COUNT = 8
GREEN = (0, 40, 0)
OFF = (0, 0, 0)
strip = neopixel.NeoPixel(Pin(board.NEOPIXEL, Pin.OUT), COUNT)
pot = board.analog_in(board.POT)
while True:
lit = pot.read_u16() * (COUNT + 1) // 65536
for i in range(COUNT):
strip[i] = GREEN if i < lit else OFF
strip.write()
print("lit:", lit)
time.sleep(1)lit: 0 lit: 2 lit: 5 lit: 8 [neo] 000000 000000 000000 000000 000000 000000 000000 000000 002800 002800 000000 000000 000000 000000 000000 000000 002800 002800 002800 002800 002800 000000 000000 000000 002800 002800 002800 002800 002800 002800 002800 002800 ...
בחרנו בצבע (0, 40, 0) ולא ב-255: הבהירות מספיקה, והזרם קטן יותר. בערך טיפוסי של כ-20 mA לצבע אחד בבהירות מרבית, זרם שמונה הנוריות יורד לכ-25 mA.
13.4 ממסר: מתג לעומס נפרד (Relay)
העיקרון: בממסר יש סליל שמושך מגע בכוח מכני. צד הסליל וצד המגע אינם מחוברים חשמלית, ולכן זרם קטן שולט במעגל אחר שנפרד לגמרי. אבל זרם הסליל גדול ממה שההדק יכול לתת, ולכן צריך טרנזיסטור, כמו בסעיף 7.8.
הדיודה שמחוברת לסליל היא דיודת חופש: הסליל צובר אנרגיה, ובניתוק המתח שלו קופץ קפיצה שעלולה להרוס את הטרנזיסטור, והדיודה מעבירה את הזרם הזה עד שהוא דועך. אי אפשר בלעדיה.
הזרם גדול בהרבה מהמינימום, ולכן הטרנזיסטור פועל כמתג סגור. ואנחנו נשארים בטווח שההדק נותן בדרך כלל.
התוכנית היא הדק מוצא רגיל. כאן משתמשים ב-board.LED_EXT, כי זה הדק פנוי. בין ההפעלה לכיבוי קובעים שני ספים ולא אחד, כדי שהמגע לא יקשקש בכל פעם שהטמפרטורה מתנדנדת סביב הסף:
import board
import dht
import time
from machine import Pin
ON_ABOVE = 28
OFF_BELOW = 26
relay = Pin(board.LED_EXT, Pin.OUT, value=0)
sensor = dht.DHT22(Pin(board.DHT))
while True:
sensor.measure()
t = sensor.temperature()
if t >= ON_ABOVE:
relay.value(1)
elif t <= OFF_BELOW:
relay.value(0)
print("%.1f C relay=%d" % (t, relay.value()))
time.sleep(2)25.0 C relay=0 27.0 C relay=0 28.5 C relay=1 27.0 C relay=1 26.0 C relay=0 27.0 C relay=0 ...
13.5 מגע: TouchPad (Capacitive Touch)
העיקרון: כל גוף מוליך משנה את הקיבול החשמלי בהדק, והשבב מודד זאת. לכן מספיקים חוט וחתיכת נחושת, בלי לחצן. המחלקה TouchPad קיימת ב-ESP32 בלבד, וחלק מהדקיה: 0, 2, 4, 12–15, 27, 32, 33. ב-Pico W היא לא קיימת.
import time
from machine import Pin, TouchPad
pad = TouchPad(Pin(14))
limit = pad.read() // 2
print("limit:", limit)
while True:
value = pad.read()
print(value, "touched" if value < limit else "free")
time.sleep(1)limit: 600 1200 free 1180 free 60 touched 55 touched 1190 free ...
13.6 ג׳ויסטיק: KY-023 (Joystick)
העיקרון: המודול KY-023 הוא שני פוטנציומטרים מאונכים ולחצן. את המתח של כל ציר קוראים בממיר, כמו בפרק 6. במצב מרכז המתח הוא בערך חצי ממתח ההזנה, כלומר מספר הקרוב ל-32768.
import board
import time
from machine import Pin
CENTER = 32768
DEAD = 6000
x_axis = board.analog_in(board.POT)
y_axis = board.analog_in(board.LDR)
button = Pin(board.BTN, Pin.IN, Pin.PULL_UP)
def axis(adc):
d = adc.read_u16() - CENTER
return 0 if abs(d) < DEAD else d
def direction(dx, dy):
if dx == 0 and dy == 0:
return "center"
if abs(dx) >= abs(dy):
return "right" if dx > 0 else "left"
return "up" if dy > 0 else "down"
while True:
name = direction(axis(x_axis), axis(y_axis))
print(name, "pressed" if button.value() == 0 else "")
time.sleep(1)center right up left center pressed ...
DEAD.DEAD = 6000 שווה בערך ל-0.3 V. ייתכן גם שכיוון הציר הפוך, בהתאם לאופן שבו המודול מורכב, ולכן בודקים אותו והופכים את הסימן.13.7 חיישני I2C: BMP280 ו-AHT20
העיקרון: שני החיישנים האלה עובדים על האפיק שהכרנו בפרק 9. BMP280 מודד לחץ וטמפרטורה, ו-AHT20 מודד טמפרטורה ולחות. הכתובות שלהם שונות: 0x76 או 0x77 בראשון, ו-0x38 בשני, ולכן אפשר לחבר אותם יחד לאותם שני חוטים.
כתבנו לכל אחד מהם ספרייה קצרה: common/bmp280.py ו-common/aht20.py, והן נמצאות עם ספריות הספר. הספרייה BMP280 מיישמת את נוסחאות התיקון מגיליון הנתונים של היצרן, כי לכל שבב יש מקדמי כיול משלו, ולכן היא קוראת אותם קודם ורק אחר כך מחשבת.
import board
from bmp280 import BMP280
i2c = board.i2c()
print("found:", [hex(a) for a in i2c.scan()])
sensor = BMP280(i2c)
t, p = sensor.read()
print("%.2f C" % t)
print("%.1f hPa" % (p / 100))found: ['0x76'] 25.08 C 1006.5 hPa
import board
from aht20 import AHT20
sensor = AHT20(board.i2c())
t, h = sensor.read()
print("%.1f C %.1f %%" % (t, h))התוכנית של AHT20 לבדה לא הורצה בבניית הספר, כי למדמה אין את השבב הזה, והיא עברה הידור בלבד. לכן נבדוק אותה קודם על הלוח.
i2c.scan(). מספרים עשרוניים על הלוח פחות מדויקים מאשר במדמה, ולכן לא קוראים את הספרות האחרונות של הלחץ.13.8 פרויקט: חלון חממה
עכשיו מחברים חיישן, שני ספים, תצוגה ומפעיל. החממה פותחת את החלון שלה אם הטמפרטורה עולה, והצבע מראה את המצב. את הסרבו מכירים מסעיף 7.4, יחד עם הספרייה servo.py.
import board
import dht
import neopixel
import time
from machine import Pin
from servo import Servo
WARM = 26
HOT = 30
GREEN, AMBER, RED = (0, 40, 0), (40, 20, 0), (40, 0, 0)
sensor = dht.DHT22(Pin(board.DHT))
strip = neopixel.NeoPixel(Pin(board.NEOPIXEL, Pin.OUT), 8)
window = Servo(board.pwm(board.SERVO, 50))
def decide(t):
if t >= HOT:
return RED, 90
if t >= WARM:
return AMBER, 45
return GREEN, 0
while True:
try:
sensor.measure()
except OSError:
print("sensor error")
time.sleep(2)
continue
t = sensor.temperature()
color, angle = decide(t)
strip.fill(color)
strip.write()
window.angle(angle)
print("%.1f C window %d" % (t, angle))
time.sleep(2)24.0 C window 0 27.0 C window 45 31.0 C window 90 28.0 C window 45 24.0 C window 0 [neo] 002800 002800 002800 002800 002800 002800 002800 002800 281400 281400 281400 281400 281400 281400 281400 281400 280000 280000 280000 280000 280000 280000 280000 280000 281400 281400 281400 281400 281400 281400 281400 281400 002800 002800 002800 002800 002800 002800 002800 002800 ...
WARM ו-HOT מקובץ הגדרות.13.9 בחירת חיישן
| רוצים למדוד | בוחרים | שואלים לפני הקנייה |
|---|---|---|
| טמפרטורה ולחות בחדר | DHT22 או AHT20 | האם איטיות הקריאה מספיקה? האם יש לי אפיק I2C? |
| לחץ וגובה יחסי | BMP280 | האם זה BMP או BME? מה המתח שלו? |
| אור בלי דיוק רב | נגד תלוי אור, פרק 6 | האם מחלק מתח מספיק? |
| מרחק של כמה מטרים | HC-SR04 | האם המתח הוא 5 V? האם המשטח מחזיר את הקול? |
| זרימת מים | חיישן עם מאוורר שנותן פולסים | כמה פולסים לליטר לפי גיליון הנתונים? סופרים אותם בפסיקה, פרק 5 |
| מגע בלי לחצן | TouchPad | האם הלוח הוא ESP32? |
| מיקום ביד | פוטנציומטר או KY-023 | כמה ממירים פנויים? |
הכלל: מתחילים ממה שבאמת צריך: התחום, מהירות הקריאה, מתח העבודה ואופן החיבור. כל מספר לוקחים מגיליון הנתונים ולא מאתר המכירה.
עיקרי הפרק:
- לכל רכיב: העיקרון, החיבור, הקוד, המלכודת.
- DHT22: קודם
measure, רק אחת לשתי שניות, ועםtry. - רצועה ארוכה לא מוזנת מהלוח, ומחשבים את הזרם שלה לפני החיבור.
- ממסר דרך טרנזיסטור, ותמיד עם דיודת חופש, ובניסויים שלנו אין מתח רשת.
- בודקים את המתח בכל הדק: 3.3 V הוא הגבול, גם אם כתוב 5 V על המודול.
- מפרידים את ההחלטה מהחומרה, וכך קל לבדוק את המערכת.
סיכום הכללים בפרק:
| הכלל | הנוסחה |
|---|---|
| זרם הרצועה | I = N · ILED |
| זרם בסיס מינימלי | IB,min = IChFE |
| זרם הבסיס | IB = Vpin − VBERB |
פקודות הפרק:
| פקודה | מה היא עושה |
|---|---|
| dht.DHT22(Pin(n)), dht.DHT11(Pin(n)) | חיישן טמפרטורה ולחות. |
| sensor.measure() | מדידה חדשה. אחריה temperature() ו-humidity(). |
| neopixel.NeoPixel(Pin(n, Pin.OUT), count) | רצועה של count נוריות. |
| strip[i] = (r, g, b), strip.fill(c) | צבע של נורית אחת או של כולן, בזיכרון. |
| strip.write() | שליחת הצבעים לרצועה. |
| TouchPad(Pin(n)).read() | מדידת מגע, ב-ESP32 בלבד. |
| BMP280(i2c).read() | טמפרטורה ולחץ, מ-bmp280.py. |
| AHT20(i2c).read() | טמפרטורה ולחות, מ-aht20.py. |
מילון מונחים:
| עברית | English | ערבית |
|---|---|---|
| ממסר | Relay | المرحّل |
| מגע | Contact | الملامس |
| חממה | Greenhouse | الدفيئة |
| אזור מת | Dead Zone | المنطقة الميتة |
| לחות | Humidity | الرطوبة |
| לחץ אטמוספרי | Atmospheric Pressure | الضغط الجوي |
| ג׳ויסטיק | Joystick | عصا التحكّم |
שאלות לתרגול:
measure() ל-temperature()?strip.write().TouchPad לא עובדת ב-Pico W?LED_EXT כל עוד הלחצן לחוץ.Pin(board.LED_EXT, Pin.OUT), ובלולאה: led.value(1 - button.value()).decide ב-greenhouse.py שני ספים לכל מצב, כדי שהחלון לא יתנדנד.lit = COUNT - d * COUNT // 100 בין 0 ל-100 ס״מ, עם בדיקת None והגבלת התוצאה בין 0 ל-COUNT.