المداخل التماثلية وملاءمة الإشارة
معظم الكمّيات في العالم الحقيقي تماثلية: الحرارة، الضوء، الضغط والجهد تتغير بشكل مستمر. المتحكّم لا يفهم إلا الأعداد، ولذلك يحوّل الجهد في مدخله التماثلي إلى عدد صحيح. في هذا الفصل نتعرّف على المحوّل التماثلي-الرقمي، نقرأ مدخلا تماثليا في البرنامج، نحوّل مقاومة الحسّاس إلى جهد بمُجزِّء الجهد، نخفّف الضجيج بالمتوسّط في البرنامج، وفي النهاية نحوّل القراءة إلى وحدات يفهمها المستخدم، مثل °C و cm.
- نشرح أخذ العيّنات والتكميم، ونحسب Nmax ودقّة التمييز للوحة Uno وللوحة ESP32.
- نحوّل القيمة الخام إلى جهد بالصيغة V = N·VrefNmax وبالعكس، ونتجنّب أخطاء الحساب بالأعداد الصحيحة.
- نوصل حسّاسا مقاوما في مُجزِّء جهد ونختار له مقاومة ثابتة ملائمة.
- نصغّر جهدا أكبر من جهد المرجع بمُجزِّء جهد قبل أن نقرأه.
- نخفّف الضجيج بحساب متوسّط عدّة قراءات في البرنامج.
- نحوّل القراءة إلى وحدات هندسية حسب مدى الحسّاس في ورقة البيانات، بالتناسب أو بالدالة map().
- نقارن بين Uno و ESP32 والراسبيري باي: المحوّل ومدى القيم ودقّة التمييز، ونقرأ حسّاسا في الراسبيري باي ببايثون.
הממיר התקבילי-ספרתי – المحوّل التماثلي-الرقمي (Analog-to-Digital Converter)
الحسّاس التماثلي يعطي جهدا يمكن أن يأخذ أي قيمة داخل مداه، مثلا 1.37 V أو 1.3712 V. أما المتحكّم فيعمل بالأعداد الثنائية فقط. الجسر بينهما هو وحدة داخل المتحكّم تُسمّى المحوّل التماثلي-الرقمي.
يعمل المحوّل بخطوتين:
- أخذ العيّنات (דגימה): يقيس المحوّل الجهد في لحظات منفصلة فقط، لا بشكل مستمر. في لوحة Uno تستغرق القراءة الواحدة نحو 100 µs ، أي نحو 10,000 قراءة في الثانية على الأكثر (حسب توثيق Arduino).
- التكميم (קוונטיזציה): يقرّب المحوّل كل عيّنة إلى أقرب «درجة» من عدد محدود من الدرجات. لذلك نرى في الخرج درجا وليس خطا مستمرا.
عدد البتات وجهد المرجع ودقّة التمييز – מספר סיביות, מתח ייחוס והפרדה:
عدد الدرجات يحدّده عدد البتات n (מספר סיביות): محوّل من n بتات يعطي 2n قيمة، من 0 حتى Nmax. أكبر جهد يقيسه المحوّل هو جهد المرجع Vref (מתח ייחוס)، والمسافة بين درجتين متجاورتين هي دقّة التمييز (רזולוציה, הפרדה): أصغر تغيّر في الجهد يميّزه المحوّل. القيمة الخام تتناسب مع الجهد: 0 V يعطي N = 0 ، و Vref يعطي N = Nmax.
| الرمز | المعنى | وحدة القياس |
|---|---|---|
| n , N | عدد بتات المحوّل · القيمة الخام (من 0 حتى Nmax) | — |
| V , Vref | الجهد في المدخل · جهد المرجع (أكبر جهد يقيسه المحوّل) | [V] |
| Δ V | دقّة التمييز: عرض درجة واحدة | [V] |
| اللوحة | n | Nmax | Vref | دقّة التمييز Δ V |
|---|---|---|---|---|
| Arduino Uno (ATmega328P) | 10 | 1023 | 5 V | 4.88 mV |
| ESP32 | 12 | 4095 | 3.3 V | 0.806 mV |
لحساب القيمة الخام المتوقّعة لجهد معيّن نعكس الصيغة: N = V·NmaxVref ، ونأخذ الجزء الصحيح. ورقة بيانات ATmega328P تكتب 2n في المقام بدل Nmax ؛ الفرق لا يتعدّى قيمة خام واحدة، ونستعمل في الكتاب Nmax لأنه يعطي Vref بالضبط عند Nmax.
(أ) ما هو الجهد في المدخل؟
(ب) ما هي القيمة الخام التي نتوقّعها لجهد 1.2 V ؟
(أ) نعوّض في الصيغة:
(ب) نعكس الصيغة ونأخذ الجزء الصحيح:
تغيير جهد المرجع: في لوحة Uno نختار المرجع بالأمر analogReference(): DEFAULT يعني 5 V ، و INTERNAL مرجع داخلي 1.1 V (قيمة نموذجية حسب ورقة البيانات)، و EXTERNAL الجهد الموصول بالطرف AREF. مع 1.1 V تصبح Δ V = 1.11024 = 1.07 mV ، وهذا مفيد لحسّاس جهده صغير، لكن كل جهد فوق 1.1 V يُقرأ 1023.
קריאת מבוא תקבילי בתוכנית – قراءة مدخل تماثلي في البرنامج (analogRead)
في لوحة Uno ستة مداخل تماثلية، من A0 حتى A5. الأمر analogRead(A0) يُرجع القيمة الخام بين 0 و 1023. لا نحتاج إلى pinMode() للقراءة التماثلية. نبدأ بأبسط حسّاس تماثلي: المقاومة المتغيّرة (البوتنشيومتر)، وهي مُجزِّء جهد نغيّر نسبته باليد.
الدالة map() وفخّ الأعداد الصحيحة – הפונקציה map() ומלכודת המספרים השלמים:
الدالة map(x, inMin, inMax, outMin, outMax) تنقل عددا من مدى إلى مدى آخر بخط مستقيم. لكنها تحسب بأعداد صحيحة فقط وتحذف الكسر (لا تقرّب)، ولذلك هي ملائمة للنسبة المئوية أو للزاوية، وغير ملائمة عندما نحتاج إلى كسور. البرنامج التالي يقرأ البوتنشيومتر ويحسب الجهد بثلاث طرق:
const int POT = A0;
void setup() { Serial.begin(9600); }
void loop() {
int n = analogRead(POT); // 0..1023
long vMap = map(n, 0, 1023, 0, 5); // whole volts only
long mV = map(n, 0, 1023, 0, 5000); // millivolts, integer
float v = n * 5.0 / 1023.0; // float math
Serial.print("N = "); Serial.print(n);
Serial.print(" map V = "); Serial.print(vMap);
Serial.print(" mV = "); Serial.print(mV);
Serial.print(" V = "); Serial.println(v, 3);
delay(500);
}N = 0 map V = 0 mV = 0 V = 0.000 N = 100 map V = 0 mV = 488 V = 0.489 N = 511 map V = 2 mV = 2497 V = 2.498 N = 1000 map V = 4 mV = 4887 V = 4.888 N = 1023 map V = 5 mV = 5000 V = 5.000
في السطر N = 1000 تُرجع map(n, 0, 1023, 0, 5) العدد 4 ، والجهد الحقيقي 4.888 V : خطأ 0.89 V ، أي نحو 18% من المدى! الحساب الصحيح هو السطر n * 5.0 / 1023.0: النقطة العشرية تجعل الحساب بأعداد float. لو كتبنا n * 5 / 1023 لكانت النتيجة أعدادا صحيحة من 0 حتى 5 فقط.
חיישן התנגדותי במחלק מתח – الحسّاس المقاوم في مُجزِّء الجهد (Resistive Sensor in a Divider)
حسّاسات كثيرة تغيّر مقاومتها وليس جهدها: المقاومة الحرارية (NTC)، المقاومة الضوئية (LDR)، وحسّاس الانحناء. المحوّل يقيس جهدا فقط، ولذلك نحوّل المقاومة إلى جهد بأبسط دائرة: مُجزِّء الجهد. تحويل إشارة الحسّاس إلى جهد يلائم مدخل المتحكّم يُسمّى ملاءمة الإشارة (התאמת אותות).
| الرمز | المعنى | وحدة القياس |
|---|---|---|
| Vout | الجهد في نقطة الوصل، إلى المدخل التماثلي | [V] |
| RS , RF | مقاومة الحسّاس (في الأسفل) · المقاومة الثابتة (في الأعلى) | [Ω] |
كيف نختار المقاومة الثابتة؟ قاعدة عملية: نختار RF قريبة من مقاومة الحسّاس في منتصف مدى القياس. هكذا يتغيّر Vout بأكبر قدر تقريبا، ولا يلتصق بـ 0 V أو بـ 5 V. لذلك نصل المقاومة الحرارية «10 kΩ» (هذه مقاومتها في 25 °C) بمقاومة ثابتة 10 kΩ.
ارتفعت الحرارة فصغرت مقاومة NTC ، ولذلك صغر الجهد وصغرت القيمة الخام.
البرنامج التالي يقرأ A0 ، يحسب الجهد كما في البند 4.2، ثم يحسب منه مقاومة الحسّاس. السطر الذي يحسب rs هو قانون مُجزِّء الجهد بعد أن نعزل منه RS. إذا قرأ المحوّل 1023 فالأرجح أن الحسّاس مفصول.
const int SENSOR = A0;
const float VCC = 5.0;
const float R_FIXED = 10000.0; // ohm, top resistor
void setup() { Serial.begin(9600); }
void loop() {
int n = analogRead(SENSOR);
if (n >= 1023) {
Serial.println("sensor open?");
} else {
float v = n * VCC / 1023.0;
float rs = R_FIXED * v / (VCC - v); // sensor at the bottom
Serial.print("N = "); Serial.print(n);
Serial.print(" V = "); Serial.print(v, 2);
Serial.print(" Rs = "); Serial.print(rs / 1000.0, 2);
Serial.println(" kOhm");
}
delay(1000);
}N = 788 V = 3.85 Rs = 33.53 kOhm N = 511 V = 2.50 Rs = 9.98 kOhm N = 270 V = 1.32 Rs = 3.59 kOhm
الأسطر الثلاثة تقابل المقاومة الحرارية في 0 °C ، 25 °C و 50 °C. القيمة المحسوبة قريبة جدا من المقاومة الحقيقية؛ الفرق الصغير سببه التكميم. نتعرّف على المقاومة الحرارية أكثر في الفصل 5.
تصغير جهد كبير بمُجزِّء الجهد – הקטנת מתח גבוה במחלק מתח:
لقياس جهد أكبر من Vref ، مثلا مُركِّم 12 V ، نصغّره بمُجزِّء جهد حتى لا يتجاوز 5 V في أسوأ الحالات. مع R1 = 22 kΩ و R2 = 10 kΩ نحسب النسبة وأكبر جهد نستطيع قياسه:
في البرنامج نحسب الجهد v في A0 كما في البند 4.2، ثم «نُلغي» مُجزِّء الجهد بالضرب في R1 + R2R2 :
float vBat = v * (22.0 + 10.0) / 10.0; // volts at the battery
רעש ומיצוע בתוכנה – الضجيج والمتوسّط في البرنامج (Noise and Averaging)
حتى عندما لا تتغيّر الكمّية المقيسة، تقفز القيمة الخام بضع درجات إلى الأعلى والأسفل. هذا هو الضجيج (רעש): إشارات غير مرغوب فيها تأتي من أسلاك طويلة، من محرّكات قريبة، من شبكة 50 Hz ، ومن المحوّل نفسه. تقليل الضجيج يُسمّى الترشيح (סינון). أبسط ترشيح نكتبه في البرنامج: نقرأ المدخل عدّة مرات متتالية ونحسب المتوسّط. الضجيج عشوائي، فيه قيم فوق القيمة الحقيقية وقيم تحتها، ولذلك يُلغي بعضها بعضا في المجموع.
const int SENSOR = A0;
const int COUNT = 8;
void setup() { Serial.begin(9600); }
void loop() {
int raw = analogRead(SENSOR); // one single reading
long sum = raw;
for (int i = 1; i < COUNT; i++) {
sum += analogRead(SENSOR);
}
float avg = sum / (float)COUNT; // average of 8 readings
Serial.print("raw = "); Serial.print(raw);
Serial.print(" avg = "); Serial.println(avg, 1);
delay(500);
}raw = 516 avg = 511.4 raw = 522 avg = 513.1 raw = 506 avg = 511.9 raw = 519 avg = 511.6 raw = 524 avg = 513.5
في هذه التجربة جهد ثابت يعطي N = 512. القراءات المفردة تتراوح بين 500 و 524 بسبب الضجيج، أما المتوسّطات فتبقى بين 511.4 و 513.5 فقط. الثمن: كل نتيجة تحتاج إلى ثماني قراءات. لحسّاس حرارة بطيء نستطيع أن نجمع قراءات كثيرة؛ لزرّ أو لحسّاس حماية سريع نجمع قراءات قليلة.
המרה ליחידות הנדסיות – التحويل إلى وحدات هندسية (Converting to Engineering Units)
القيمة الخام N لا تعني شيئا للمستخدم. نريد أن نعرض وحدات هندسية (יחידות הנדסיות): °C ، cm ، bar. ورقة بيانات الحسّاس تعطي مدى القياس والجهد الذي يقابله: أصغر قيمة في المدى تعطي أصغر جهد، وأكبر قيمة تعطي أكبر جهد. في الحسّاس الخطّي العلاقة بينهما خط مستقيم، ولذلك يكفي تناسب بسيط. الطريق كله ثلاث خطوات:
- نقرأ القيمة الخام N بالأمر analogRead().
- نحوّل القيمة الخام إلى جهد: V = N·VrefNmax (البند 4.1).
- نحوّل الجهد إلى الكمّية المقيسة بالتناسب، حسب ورقة البيانات.
نحوّل القيمة الخام إلى جهد، ثم الجهد إلى درجات بالتناسب:
التحويل بالدالة map() – המרה בעזרת map():
عندما يقابل مدى الحسّاس كل مدى المحوّل، أي من 0 V حتى 5 V ، يكفي سطر واحد بالدالة map(): نكتب فيها القيمة الخام، ثم مدى المحوّل (0 و 1023)، ثم أصغر قيمة وأكبر قيمة في مدى الحسّاس. مثال: حسّاس مستوى تماثلي يُخرج 0 V والخزان فارغ، و 5 V عندما يصل الماء إلى 200 cm (هذا مثال للتوضيح؛ المدى الحقيقي نقرؤه في ورقة بيانات الحسّاس). البرنامج يحسب الارتفاع ونسبة الامتلاء:
const int LEVEL = A0;
const int H_MAX = 200; // cm at 5 V, from the datasheet
void setup() { Serial.begin(9600); }
void loop() {
int n = analogRead(LEVEL);
long h = map(n, 0, 1023, 0, H_MAX); // whole cm
long fill = map(n, 0, 1023, 0, 100); // percent
Serial.print("N = "); Serial.print(n);
Serial.print(" h = "); Serial.print(h);
Serial.print(" cm fill = "); Serial.print(fill);
Serial.println(" %");
delay(1000);
}N = 0 h = 0 cm fill = 0 % N = 256 h = 50 cm fill = 25 % N = 512 h = 100 cm fill = 50 % N = 1023 h = 200 cm fill = 100 %
إذا كان خرج الحسّاس من 1 V حتى 5 V لنفس المدى، نكتب في map() بدل الصفر القيمة الخام لجهد 1 V ، وهي N = 204 (الخط المتقطّع في الرسم):
long h = map(n, 204, 1023, 0, H_MAX); // sensor output 1..5 V
(أ) ماذا تُرجع map(n, 0, 1023, 0, 6) ؟
(ب) ما هو الضغط الدقيق؟
שלושה לוחות, שלושה ממירים – ثلاث لوحات، ثلاثة محوّلات (Uno, ESP32 and Raspberry Pi)
الحسّاس نفسه يتّصل بثلاث لوحات شائعة في المختبر: Arduino Uno و ESP32 والراسبيري باي (רספברי פיי). اللوحة تحدّد ثلاثة أمور: أيّ جهد مسموح على الطرف، وهل يوجد محوّل تماثلي-رقمي أصلا، وكم بتّا فيه. والفرق الأكبر: الراسبيري باي لا يحتوي على محوّل تماثلي-رقمي في أيّ طراز، ولذلك يقرأ الحسّاس التماثلي فقط عبر محوّل خارجي، مثل الدائرة المتكاملة MCP3008 (عشرة بتات، ثماني قنوات، تتّصل بناقل SPI).
| Arduino Uno | ESP32 | Raspberry Pi | |
|---|---|---|---|
| جهد الأطراف | 5 V | 3.3 V — لا يتحمّل 5 V | 3.3 V — لا يتحمّل 5 V |
| المحوّل التماثلي-الرقمي | مدمج، 10 بتات | مدمج، 12 بتّا | غير موجود — فقط MCP3008 خارجي (10 بتات) |
| مدى القيم الخام | 0 حتى 1023 | 0 حتى 4095 | 0 حتى 1023 (عبر MCP3008) |
| جهد المرجع | 5 V | نحو 3.3 V | 3.3 V |
| دقّة التمييز | 4.88 mV | 0.81 mV | 3.22 mV |
| لغة البرمجة | أردوينو (C++) | أردوينو أو مايكروبايثون | بايثون |
نستعمل الصيغة نفسها في الثلاث: N = V·NmaxVref ، ونأخذ الجزء الصحيح:
الجهد نفسه يعطي ثلاثة أعداد مختلفة، لأنّ عدد البتات وجهد المرجع مختلفان. لذلك نكتب في كل برنامج Nmax و Vref الصحيحين للّوحة.
الحسّاسات في الراسبيري باي ببايثون – חיישנים ברספברי פיי בפייתון:
في الراسبيري باي لا نكتب برنامج أردوينو، بل برنامجا بلغة بايثون. الحسّاسات الرقمية تتّصل مباشرة بأطراف GPIO (بجهد 3.3 V)، والتماثلية عبر MCP3008. نعرض أربعة أمثلة قصيرة لحسّاسات من هذا الكتاب. المكتبة gpiozero موجودة في نظام Raspberry Pi OS ، ومكتبة DHT22 نثبّتها مرّة واحدة بالأمر:
pip install adafruit-circuitpython-dht
from gpiozero import Button
from signal import pause
btn = Button(17) # GPIO17, internal pull-up
btn.when_pressed = lambda: print("pressed")
btn.when_released = lambda: print("released")
pause()from gpiozero import MCP3008
from time import sleep
lm35 = MCP3008(channel=0) # SPI, Vref = 3.3 V
while True:
n = lm35.raw_value # 0 .. 1023
v = n * 3.3 / 1023
t = v / 0.010 # 10 mV per degree C
print("N = %d V = %.3f V T = %.1f C" % (n, v, t))
sleep(1)import time
import board
import adafruit_dht
dht = adafruit_dht.DHT22(board.D27) # GPIO27
while True:
try:
print("T = %.1f C RH = %.1f %%" % (dht.temperature, dht.humidity))
except RuntimeError:
pass # a missed reading: try again
time.sleep(2)import glob, time
dev = glob.glob("/sys/bus/w1/devices/28-*/temperature")[0]
while True:
with open(dev) as f:
t = int(f.read()) / 1000 # millidegrees C
print("T = %.2f C" % t)
time.sleep(1)ملخّص قوانين الفصل:
| القانون | الصيغة |
|---|---|
| أكبر قيمة خام | Nmax = 2n − 1 |
| دقّة التمييز | Δ V = Vref2n |
| من القيمة الخام إلى الجهد | V = N·VrefNmax |
| من الجهد إلى القيمة الخام | N = V·NmaxVref |
| مُجزِّء الجهد، الحسّاس في الأسفل | Vout = VCC·RSRF + RS |
| LM35: من الجهد إلى الحرارة | T = V10 mV/°C |
قاموس المصطلحات – מילון מונחים:
| بالعربية | بالعبرية (كما تظهر في الامتحان) | English |
|---|---|---|
| المحوّل التماثلي-الرقمي | ממיר תקבילי-ספרתי (ADC) | Analog-to-digital converter |
| أخذ العيّنات | דגימה | Sampling |
| التكميم | קוונטיזציה | Quantization |
| عدد البتات | מספר סיביות | Number of bits |
| القيمة الخام | ערך גולמי | Raw value (count) |
| جهد المرجع | מתח ייחוס | Reference voltage |
| دقّة التمييز | רזולוציה (הפרדה) | Resolution |
| مُجزِّء الجهد | מחלק מתח | Voltage divider |
| ملاءمة الإشارة | התאמת אותות | Signal conditioning |
| الضجيج | רעש | Noise |
| الترشيح | סינון | Filtering |
| مدى القياس | תחום מדידה | Measuring range |
| الوحدات الهندسية | יחידות הנדסיות | Engineering units |
أسئلة للتمرين:
(أ) ما هو Nmax ؟
(ب) ما هي دقّة التمييز؟
(ج) ما هي القيمة الخام لجهد 2 V ؟
(أ) n / 1023 * 5
(ب) map(n, 0, 1023, 0, 5)
(ج) n * 5.0 / 1023.0 مع ثلاث خانات عشرية؟
(أ) هل يكبر Vout أم يصغر مقارنة بـ 25 °C ؟
(ب) احسبوا Vout والقيمة الخام.
(أ) ما هو الجهد في A0 ؟
(ب) ما هي القيمة الخام؟
(ج) ما هو أكبر جهد نستطيع قياسه بهذا المُجزِّء؟
(أ) اكتبوا سطر map() الملائم.
(ب) ماذا يُرجع؟
(ج) ما هي الحرارة الدقيقة؟
(أ) ما هي القيمة الخام عند 1 V وعند 5 V ؟
(ب) اكتبوا سطر map() الملائم.
(ج) ما هي الحرارة عندما N = 614 ؟