حسّاسات ومشغّلات شائعة
نعرف الآن الأطراف والنواقل والمكتبات. حان وقت القطع التي نجدها في كل مشروع. لكل قطعة نتبع القالب نفسه: كيف تعمل، كيف نوصلها، الشيفرة، ثم المصيدة التي يقع فيها أغلب من يبدأ بها. وفي الآخر نجمعها في نظام صغير.
- نقرأ الحرارة والرطوبة من 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 – חיישני I2C
المبدأ: هذان الحسّاسان يعملان على الناقل الذي عرفناه في الفصل 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.