بايثون بالقدر الذي يحتاجه المتحكّم
هذا ليس كتابا في لغة بايثون. نأخذ هنا ما يحتاج إليه بقية الكتاب فقط، وكل مثال مأخوذ من عالم الجهد والحسّاس. ونعرف في النهاية أين تختلف مايكروبايثون عن بايثون الحاسوب، لأن أخطاء كثيرة تأتي من هناك.
- نستعمل المتغيّرات والأنواع والحساب، ونطبع الأعداد بتنسيق واضح.
- نقرأ نصا من المستخدم ونحوّله إلى عدد، ونعالج الخطأ إذا لم يكن عددا.
- نبني القرار بالشرط، والتكرار بالحلقة، ونجمع القراءات في قائمة.
- ننظّم الشيفرة في دوال وأصناف ومكتبات، ونحسب حساب الذاكرة والدقة العشرية.
2.1 المتغيّرات والأنواع – משתנים וטיפוסים (Variables and Types)
المتغيّر (משתנה) اسم نربطه بقيمة. لا نعلن نوعه مسبقا، بل يحدّده المفسّر من القيمة نفسها. الأنواع (טיפוסים) التي نحتاج إليها أربعة:
| النوع | مثال | نستعمله لـ |
|---|---|---|
int عدد صحيح (שלם) | 20000 | قراءة المحوّل، العدّادات، أرقام الأطراف |
float عدد عشري (ממשי) | 3.3 | الجهد والحرارة |
str نص (מחרוזת) | "pot" | الأسماء والرسائل |
bool قيمة منطقية (בוליאני) | True | نتيجة كل مقارنة |
type يكشف نوع كل منها.raw = 20000 vref = 3.3 name = "pot" active = raw > 1000 print(name, raw, active) print(type(vref), type(name))
pot 20000 True <class 'float'> <class 'str'>
2.2 الحساب – חשבון (Arithmetic)
| العملية | معناها | مثال من عالمنا |
|---|---|---|
+ - * | جمع وطرح وضرب | ضرب القراءة في جهد المرجع |
/ | قسمة، والنتيجة float دائما | الجهد من القراءة |
// | قسمة صحيحة | نسبة مئوية بلا كسور |
% | باقي القسمة | تبديل حالة كل خطوتين |
** | رفع إلى قوة | عدد الخطوات 2n |
raw = 20000
volts = raw * 3.3 / 65535
print("volts = %.3f" % volts)
print("7 / 2 = %.1f" % (7 / 2))
print("7 // 2 =", 7 // 2)
print("7 % 2 =", 7 % 2)
print("2 ** 12 =", 2 ** 12)volts = 1.007 7 / 2 = 3.5 7 // 2 = 3 7 % 2 = 1 2 ** 12 = 4096
raw * 3.3 / 65535 يطبّق الضرب أولا. أما raw // 65535 * 3.3 فيعطي صفرا لكل قراءة أقل من 65535، لأن القسمة الصحيحة تُسقط الكسر.17 // 5 و17 % 5 و17 / 5؟2.3 الطباعة والتنسيق – הדפסה ותבניות (Printing and Formatting)
القراءة الخام لا تعني شيئا لمن يراقب الشاشة، ولذلك نطبع بتنسيق. لدينا طريقتان، وكلاهما يعمل في مايكروبايثون: التنسيق بالعلامة % والنص المنسَّق f"...".
%.2f عدد عشري بمنزلتين، و%d عدد صحيح.volts = 1.0071
temp = 24.5
raw = 20000
print("V = %.2f" % volts)
print("T = %.1f C, raw = %d" % (temp, raw))
print(f"V = {volts:.2f}")
print(f"T = {temp:.1f} C, raw = {raw}")V = 1.01 T = 24.5 C, raw = 20000 V = 1.01 T = 24.5 C, raw = 20000
2.4 القراءة من المستخدم والتحويل – קלט והמרה (input, int, float, split)
الأمر input يطبع سؤالا وينتظر حتى يكتب المستخدم سطرا ويضغط Enter في نافذة Shell. والأهم: يعيد دائما نصا، حتى لو كتبنا عددا. لذلك نحوّله بـint أو float قبل أي حساب. وإذا كتبنا عددين في سطر واحد، فالأمر split يقطع النص إلى قائمة أجزاء. الشيفرة التالية لم تُشغَّل في بناء الكتاب، لأنه لا يملك لوحة مفاتيح، ولذلك لا نعرض لها شاشة:
text = input("Voltage in volts: ")
volts = float(text)
print("raw =", int(volts * 65535 / 3.3))
line = input("R1 R2 in ohms: ")
parts = line.split()
r1 = float(parts[0])
r2 = float(parts[1])
print("Vout = %.2f V" % (3.3 * r2 / (r1 + r2)))على اللوحة يظهر كل سؤال وينتظر، فنكتب الجواب، ويطبع البرنامج النتيجة. والتحويل نفسه لا يحتاج إلى لوحة مفاتيح، ولذلك نجرّبه فعلا على نص ثابت:
input.line = "10000 4700"
parts = line.split()
print(parts)
r1 = float(parts[0])
r2 = float(parts[1])
print("Vout = %.2f V" % (3.3 * r2 / (r1 + r2)))['10000', '4700'] Vout = 1.06 V
وما الذي يحدث إذا كان النص لا يصلح للتحويل؟ هذا أول خطأ نراه، وهو حقيقي:
int لا يقبل نقطة عشرية.text = "3.3"
print("%.1f" % float(text))
n = int(text)
print(n)3.3 ValueError: invalid syntax for integer with base 10: '3.3'
السطر الأخير اسمه استثناء (חריגה): البرنامج توقف هنا، واسم الخطأ في أول السطر. نقرأ هذا السطر دائما قبل أن نعدّل شيئا. ولكي لا يتوقف برنامج على كتابة خاطئة، نضع التحويل داخل try:
text = "abc"
try:
volts = float(text)
print("%.2f" % volts)
except ValueError:
print("not a number:", text)not a number: abc
12 في input. ما نوع القيمة التي حصلنا عليها؟ وما نتيجة n * 2 قبل التحويل؟2.5 الشرط – תנאים (if / elif / else)
المتحكّم موجود ليقرّر: هل الجهد مرتفع؟ هل الباب مفتوح؟ الشرط (תנאי) ينفّذ كتلة من الأسطر إذا كانت المقارنة صحيحة، وكتلة الأسطر تُعرف بالإزاحة (הזחה) إلى الداخل. نقارن بـ== و!= و< و>، ونربط الشروط بـand وor وnot.
volts = 2.7
if volts > 3.0:
print("HIGH")
elif volts > 1.0:
print("MID")
else:
print("LOW")
print("in range:", 1.0 <= volts <= 3.0)
print("not low:", not volts < 1.0)MID in range: True not low: True
= تعطي قيمة، وعلامة == تقارن. والإزاحة جزء من اللغة: سطر أزيح خطأً يغيّر معنى البرنامج، أو يوقفه. نستعمل دائما 4 مسافات.2.6 الحلقات – לולאות (for / while)
الحلقة (לולאה) تكرّر كتلة من الأسطر. الحلقة for تمر على مجموعة معروفة، وrange(3) يعطي 0 و1 و2، دون 3. أما while فتكرّر ما دام الشرط صحيحا، وتناسب الحالات التي لا نعرف فيها عدد المرات مسبقا. هنا ينخفض الجهد 20% في كل خطوة، ونسأل: كم خطوة حتى يقل عن فولط واحد؟
for n in range(3):
raw = n * 20000
print(n, raw, "%.2f V" % (raw * 3.3 / 65535))
volts = 3.3
steps = 0
while volts > 1.0:
volts = volts * 0.8
steps += 1
print("steps:", steps, "volts: %.2f" % volts)0 0 0.00 V 1 20000 1.01 V 2 40000 2.01 V steps: 6 volts: 0.87
وداخل أي حلقة، الأمر continue يتخطّى الدورة الحالية، وbreak يخرج من الحلقة كلها.
الحلقة التي لا تنتهي:
كل برنامج متحكّم تقريبا مبني على حلقة while True لا تتوقف: نقرأ، نحسب، نقرّر، ننفّذ، ثم نعود.
analog_in نشرحه في الفصل 6، والطرف في الفصل 3.import time
from machine import Pin
import board
pot = board.analog_in(board.POT)
led = Pin(board.LED, Pin.OUT)
while True:
volts = pot.read_u16() * 3.3 / 65535
if volts > 2.0:
led.on()
state = "ON"
else:
led.off()
state = "OFF"
print("%.2f V LED %s" % (volts, state))
time.sleep(1)0.50 V LED OFF 1.50 V LED OFF 2.50 V LED ON 3.00 V LED ON ...
النقاط في آخر الشاشة تعني أن المحاكاة أوقفت البرنامج. أما اللوحة الحقيقية فتواصل حتى نضغط Ctrl-C.
2.7 القوائم – רשימות (Lists)
القائمة (רשימה) تجمع قراءات كثيرة تحت اسم واحد، وكل عنصر فيها له فهرس (אינדקס) يبدأ من الصفر. ومن النهاية نعدّ بالسالب: temps[-1] هو العنصر الأخير.
temps = [21.5, 22.0, 23.5, 22.5]
print("first %.1f, last %.1f" % (temps[0], temps[-1]))
temps.append(24.0)
print("count:", len(temps))
print("max %.1f, min %.1f" % (max(temps), min(temps)))
print("avg %.2f" % (sum(temps) / len(temps)))
for t in temps[1:3]:
print("%.1f" % t)first 21.5, last 22.5 count: 5 max 24.0, min 21.5 avg 22.70 22.0 23.5
IndexError. والقائمة التي تكبر بلا حد تملأ الذاكرة الصغيرة حتما، ولذلك نحدّد طولها، كما نفعل في 2.10.2.8 الدوال – פונקציות (Functions)
الدالة (פונקציה) تعطي اسما لمجموعة أسطر، فنكتبها مرة ونستعملها كثيرا. تأخذ وسائط (פרמטרים)، وتعيد نتيجة بالأمر return. ويمكن أن يكون للوسيط قيمة مفترضة، وأن تعيد الدالة أكثر من قيمة.
vref له قيمة مفترضة، وlimits تعيد قيمتين.def to_volts(raw, vref=3.3):
return raw * vref / 65535
def limits(values):
return min(values), max(values)
print("%.2f" % to_volts(20000))
print("%.2f" % to_volts(20000, 5.0))
low, high = limits([1.2, 0.9, 1.7])
print("low %.1f, high %.1f" % (low, high))1.01 1.53 low 0.9, high 1.7
المتغيّر الذي نعطيه قيمة داخل الدالة يبقى داخلها. وإذا أردنا تعديل متغيّر من خارجها، نكتب في أولها global count. سنحتاج إلى هذا في المقاطعات في الفصل 5.
2.9 القواميس باختصار – מילונים (Dictionaries)
القاموس (מילון) يربط اسما بقيمة، فنصل إلى القيمة بالاسم وليس بالموقع. نلقاه في الإعدادات وفي بيانات JSON في الفصل 12.
get لا يتوقف إذا غاب المفتاح، بل يعيد قيمة بديلة.limit = {"name": "battery", "low": 1.0, "high": 3.0}
print(limit["name"], "%.1f" % limit["high"])
limit["unit"] = "V"
print(limit.get("unit"), limit.get("max", "missing"))
print(sorted(limit))battery 3.0 V missing ['high', 'low', 'name', 'unit']
sorted كما في البرنامج.2.10 صنف صغير – מחלקה קטנה (A Small Class)
الصنف (מחלקה) يجمع البيانات مع الدوال التي تعمل عليها، والكائن (אובייקט) نسخة منه لها بياناتها. والدالة __init__ تعمل عند إنشاء كل كائن، وself هو الكائن نفسه. وهذا ما نفعله حين نكتب Pin(2): ننشئ كائنا من صنف جاهز. مثالنا هو المتوسّط المتحرّك (ממוצע נע): نضيف الجديد ونرمي الأقدم، فلا تطول القائمة أبدا، ويخفّ أثر قراءة شاذة مثل 400. ولكل حسّاس كائنه وقائمته.
class Average:
def __init__(self, size):
self.size = size
self.values = []
def add(self, x):
self.values.append(x)
if len(self.values) > self.size:
self.values.pop(0)
return sum(self.values) / len(self.values)
pot = Average(3)
ldr = Average(2)
for raw in [100, 110, 90, 400, 105]:
print(raw, "%.1f" % pot.add(raw))
print("ldr %.1f" % ldr.add(500))100 100.0 110 105.0 90 100.0 400 200.0 105 198.3 ldr 500.0
2.11 الاستيراد – ייבוא (import)
المكتبة (ספרייה) ملف فيه دوال جاهزة. نستوردها كلها، أو نأخذ منها اسما واحدا. وأي ملف نكتبه ونحفظه في المجلد lib يصبح مكتبة. هذه مكتبة صغيرة كتبناها:
# convert.py - small unit conversions, the same on every board.
# (c) 2026 Fahed Ghanem - MIT License - fahed-lab.com
VREF = 3.3
def to_volts(raw, vref=VREF):
return raw * vref / 65535
def to_percent(raw):
return raw * 100 // 65535import math
from math import sqrt
import convert
print("%.3f %.3f" % (math.pi, sqrt(2)))
print("%.2f V" % convert.to_volts(20000))
print(convert.to_percent(32768), "percent")3.142 1.414 1.01 V 50 percent
time.py أو math.py. يأخذ المفسّر ملفنا أولا، فتتوقف كل البرامج التي تحتاج إلى الأصلية.2.12 أين تختلف عن بايثون الحاسوب؟ – הבדלים ממחשב (Differences from a PC)
الذاكرة قليلة:
كل ما نُنشئه يأخذ من الذاكرة، والأمر gc.mem_free() يعطي ما تبقى منها. لا نطبع هنا عددا، لأن عدد المُحاكاة لا يعني شيئا عن لوحتكم؛ جرّبوه عليها.
import gc
gc.collect()
before = gc.mem_free()
data = [0] * 1000
after = gc.mem_free()
print("the list took memory:", after < before)the list took memory: True
الدقة العشرية:
على ESP32 وPico W يخزّن البرنامج الثابت القياسي العدد العشري في 32 بتّا، بدقة نحو 7 أرقام. أما في المحاكاة وفي بايثون الحاسوب فـ64 بتّا. ولذلك نطبع بتنسيق، ولا نقارن عددين عشريين بـ==:
total = 0.0
for i in range(10):
total += 0.1
print("exactly 1.0:", total == 1.0)
print("close to 1.0:", abs(total - 1.0) < 0.001)exactly 1.0: False close to 1.0: True
الثابت const:
القيمة الصحيحة التي لا تتغيّر نعلنها ثابتا بالأمر const من المكتبة micropython، فيعوّضها المفسّر بقيمتها مسبقا. وهذا المثال يحسب أيضا بأعداد صحيحة فقط، بالميلي فولط:
from micropython import const
_VREF_MV = const(3300)
_FULL = const(65535)
def to_mv(raw):
return raw * _VREF_MV // _FULL
print(to_mv(20000), "mV")1007 mV
ليست كل المكتبات موجودة:
مكتبات بايثون الضخمة لا تعمل هنا، واستيرادها يوقف البرنامج بالخطأ ImportError. نعرف ما هو متوفّر على لوحتنا بالأمر help("modules") في السطر التفاعلي، وبعض الناقص يُثبَّت كما في الفصل 11. والعكس صحيح: time.sleep_ms وmachine غير موجودين في بايثون الحاسوب. ولمن يريد التعمّق في اللغة نفسها، يمكن الرجوع إلى كتاب بايثون في موقع fahed-lab.com عند توفّره.
أهمّ أفكار الفصل:
inputيعيد نصا دائما، ونحوّله قبل الحساب.- سطر الخطأ الأخير يخبرنا باسم الخطأ: نقرؤه قبل أن نعدّل.
- كل برنامج متحكّم تقريبا حلقة: نقرأ، نحسب، نقرّر، ننفّذ.
- القائمة لها طول محدود دائما، لأن الذاكرة صغيرة.
- العدد العشري يُطبع بتنسيق، ولا يُقارن بـ
==. والزمن يُخزَّن عددا صحيحا.
أوامر الفصل:
| الأمر | عمله |
|---|---|
| int() float() str() | تحويل بين الأنواع. |
| "%.2f" % x f"{x:.2f}" | طباعة بمنزلتين عشريتين. |
| input() line.split() | قراءة سطر وقطعه. |
| try / except ValueError | معالجة الخطأ دون توقف. |
| a.append(x) a.pop(0) len(a) | إضافة، حذف الأول، الطول. |
| gc.mem_free() const() | الذاكرة الحرة؛ إعلان ثابت. |
قاموس المصطلحات – מילון מונחים:
| بالعربية | بالعبرية | English |
|---|---|---|
| المتغيّر | משתנה | Variable |
| عدد صحيح / عدد عشري | מספר שלם / ממשי | Integer / Float |
| النص | מחרוזת | String |
| الحلقة | לולאה | Loop |
| الإزاحة | הזחה | Indentation |
| القائمة | רשימה | List |
| الصنف | מחלקה | Class |
| الكائن | אובייקט | Object |
| الاستثناء | חריגה | Exception |
| المتوسّط المتحرّك | ממוצע נע | Moving Average |
3 و2 و3.4."1212"، لأن ضرب النص يكرره.أسئلة للتمرين:
3.3، "3.3"، 33، 3 > 1؟float، str، int، bool.range(2, 5)؟ وما الفهرس السالب للعنصر الأول في قائمة من 4 عناصر؟-4.def percent(raw):
return raw * 100 // 65535
for raw in (0, 32768, 65535):
print(raw, percent(raw))"12,30,18" من حسّاس عبر خط تسلسلي. اجمعوا الأعداد الثلاثة.line = "12,30,18"
total = 0
for part in line.split(","):
total += int(part)
print("total:", total)float فيتوقف بعد ساعات عن الزيادة بانتظام. لماذا؟ وما العلاج؟time.ticks_ms().