

في هذا الفيديو سنتعلم كيفية التحكم في محرك تيار مستمر ومحرك سيرفو من خلال برنامج يعمل على الحاسوب، بحيث يمكن التحكم في تحريك محرك سيرفو إلى أي زاوية، وأيضًا التحكم في محرك تيار مستمر بحيث يمكن التحكم في السرعة واتجاه الدوران وإيقاف وتشغيل المحرك.
قم بتوصيل الأسلاك بين محرك التيار المستمر وقائد المحركات ومحرك السيرفو ولوحة راسبيرى باى بيكو 2W كما فى الصورة التى فى الأسفل.

التوصيلات من لوحة راسبيرى باى بيكو 2W :
• منفذ الVBUS بلوحة راسبيرى باى بيكو 2W بالمنافذ الموجبة بلوحة التجارب
• منفذ الGND بلوحة راسبيرى باى بيكو 2 W المنافذ السالبة بلوحة التجارب
التوصيلات من قائد المحركات L293D :
• منفذ VCC 1 ومنفذ VCC 2 لقائد المحركات ←المنافذ الموجبة للوحة التجارب
• منافذ ال gnd لقائد المحركات ← المنافذ السالبة للوحة التجارب
• منفذ ENABLE 1,2 لقائد المحركات ←منفذ رقم 16 في لوحة راسبيرى باى بيكو 2W
• منفذ 1 Input لقائد المحركات ←منفذ رقم 14 في لوحة راسبيرى باى بيكو 2W
• منفذ 2 Input لقائد المحركات ←منفذ رقم 15 في لوحة راسبيرى باى بيكو 2W
التوصيلات من محرك التيار المستمر:
• احد اطراف المحرك ← منفذ Output1 بقائد المحركات
• الطرف الاخر للمحرك ←منفذ Output2 بقائد المحركات
التوصيلات من محرك السيرفو:
• المنفذ الموجب لمحرك السيرفو ← منفذ الVBUS بلوحة راسبيرى باى بيكو 2W
• المنفذ السالب لمحرك السيرفو ← منفذ الGND بلوحة راسبيرى باى بيكو 2W
• منفذ الإشارة لمحرك السيرفو ← منفذ رقم 17 في لوحة راسبيرى باى بيكو 2W
وظيفة النص البرمجي التالي هي التحكم في محرك تيار مستمر ومحرك سيرفو من خلال برنامج يعمل على الحاسوب، بحيث يمكن التحكم في تحريك محرك سيرفو إلى أي زاوية، وأيضًا التحكم في محرك تيار مستمر بحيث يمكن التحكم في السرعة واتجاه الدوران وإيقاف وتشغيل المحرك.
'''
Voltaat Learn (http://learn.voltaat.com)
Link to the full tutorial:
Tutorial: Controlling motors via computer using a Raspberry Pi Pico board.
The function of this code is to control a DC motor and a servo motor through
a computer program, so that the servo motor can be moved to any angle, and also
the DC motor can be controlled so that the speed, direction of rotation, and
stopping and starting of the motor can be controlled.
Note: You can use this sketch with any Raspberry Pi Pico.
'''
from machine import Pin, PWM
import utime
import sys
import uselect
import ujson as json
# تعريف منافذ محرك DC مع L293D
IN1 = Pin(14, Pin.OUT) # التحكم في الاتجاه 1
IN2 = Pin(15, Pin.OUT) # التحكم في الاتجاه 2
ENA = PWM(Pin(16)) # التحكم في السرعة (PWM)
ENA.freq(1000) # تردد PWM = 1kHz
# تعريف منافذ محرك السيرفو
SERVO_PIN = Pin(17)
servo = PWM(SERVO_PIN)
servo.freq(50) # تردد السيرفو القياسي 50Hz
# تعريف منافذ LED للإشارة
LED_PIN = Pin(25, Pin.OUT) # LED المدمج في Pico
STATUS_LED = Pin(6, Pin.OUT) # LED إضافي للإشارة (اختياري)
# حالة النظام
class SystemState:
def __init__(self):
self.motor_running = False
self.motor_speed = 0
self.motor_direction = "forward"
self.servo_angle = 90
self.last_command_time = utime.ticks_ms()
self.command_count = 0
self.error_count = 0
self.system_start_time = utime.ticks_ms()
self.connection_active = True
# إنشاء حالة النظام
state = SystemState()
# إعدادات النظام
CONFIG_FILE = "config.json"
DEFAULT_CONFIG = {
"max_speed": 100,
"min_speed": 0,
"servo_min": 0,
"servo_max": 180,
"auto_save": True
}
def load_config():
"""تحميل إعدادات النظام"""
try:
with open(CONFIG_FILE, 'r') as f:
return json.load(f)
except:
return DEFAULT_CONFIG
def save_config(config):
"""حفظ إعدادات النظام"""
try:
with open(CONFIG_FILE, 'w') as f:
json.dump(config, f)
return True
except:
return False
config = load_config()
def init_system():
"""تهيئة النظام"""
# إيقاف جميع المحركات
IN1.off()
IN2.off()
ENA.duty_u16(0)
# ضبط السيرفو على الوضع الأوسط
set_servo_angle(90)
# تشغيل LED للإشارة
LED_PIN.on()
STATUS_LED.off()
# رسالة بدء التشغيل
print("\n" + "="*50)
print("نظام التحكم في المحركات - Raspberry Pi Pico")
print("الإصدار: 2.0 المحسن")
print("="*50)
print(f"وقت البدء: {utime.ticks_ms()} ms")
print(f"الإعدادات: {config}")
print("="*50)
print("جاهز لاستقبال الأوامر...")
print("اكتب 'HELP' لرؤية الأوامر المتاحة")
print("="*50 + "\n")
def set_motor_speed(speed):
"""ضبط سرعة محرك DC (0-100%)"""
state.motor_speed = max(config["min_speed"], min(config["max_speed"], speed))
pwm_value = int(state.motor_speed * 655.35) # تحويل 0-100 إلى 0-65535
ENA.duty_u16(pwm_value)
# وميض LED عند تغيير السرعة
if speed > 0:
STATUS_LED.on()
else:
STATUS_LED.off()
return f"Speed: {state.motor_speed}%"
def set_motor_direction(direction):
"""ضبط اتجاه دوران محرك DC"""
state.motor_direction = direction.lower()
if state.motor_direction == "forward":
IN1.on()
IN2.off()
direction_text = "أمامي"
elif state.motor_direction == "backward":
IN1.off()
IN2.on()
direction_text = "خلفي"
else:
return f"Error: Invalid direction '{direction}'"
return f"Direction: {direction_text}"
def motor_on():
"""تشغيل محرك DC"""
if not state.motor_running:
state.motor_running = True
set_motor_speed(state.motor_speed)
LED_PIN.toggle() # وميض LED المدمج
return "Motor: ON"
return "Motor: Already ON"
def motor_off():
"""إيقاف محرك DC"""
if state.motor_running:
state.motor_running = False
ENA.duty_u16(0)
LED_PIN.toggle() # وميض LED المدمج
return "Motor: OFF"
return "Motor: Already OFF"
def set_servo_angle(angle):
"""ضبط زاوية محرك السيرفو"""
min_angle = config["servo_min"]
max_angle = config["servo_max"]
angle = max(min_angle, min(max_angle, angle))
state.servo_angle = angle
# تحويل الزاوية إلى دورة عمل
min_duty = 1638 # 0.5ms / 20ms * 65535
max_duty = 8192 # 2.5ms / 20ms * 65535
duty = int(min_duty + (angle / 180) * (max_duty - min_duty))
servo.duty_u16(duty)
return f"Servo: {angle}°"
def get_system_status():
"""الحصول على حالة النظام"""
uptime = utime.ticks_diff(utime.ticks_ms(), state.system_start_time) // 1000
status = {
"motor_running": state.motor_running,
"motor_speed": state.motor_speed,
"motor_direction": state.motor_direction,
"servo_angle": state.servo_angle,
"uptime_seconds": uptime,
"command_count": state.command_count,
"error_count": state.error_count,
"connection_active": state.connection_active
}
return json.dumps(status)
def run_test_sequence():
"""تشغيل تسلسل اختباري"""
results = []
# اختبار LED
LED_PIN.on()
results.append("LED: ON")
utime.sleep_ms(200)
LED_PIN.off()
results.append("LED: OFF")
utime.sleep_ms(200)
# اختبار المحرك DC
results.append(motor_on())
utime.sleep_ms(500)
for speed in [30, 60, 90]:
results.append(set_motor_speed(speed))
utime.sleep_ms(300)
results.append(set_motor_direction("backward"))
utime.sleep_ms(500)
results.append(set_motor_direction("forward"))
utime.sleep_ms(300)
results.append(motor_off())
utime.sleep_ms(200)
# اختبار السيرفو
for angle in [0, 45, 90, 135, 180]:
results.append(set_servo_angle(angle))
utime.sleep_ms(150)
results.append(set_servo_angle(90))
return "\n".join(results)
def process_command(command):
"""معالجة الأوامر الواردة"""
command = command.strip().upper()
state.last_command_time = utime.ticks_ms()
state.command_count += 1
try:
if command == "MOTOR_ON":
return motor_on()
elif command == "MOTOR_OFF":
return motor_off()
elif command.startswith("SPEED:"):
try:
speed = int(command.split(":")[1])
return set_motor_speed(speed)
except:
state.error_count += 1
return "Error: Invalid speed format. Use SPEED:0-100"
elif command.startswith("DIR:"):
direction_cmd = command.split(":")[1]
direction_map = {"FWD": "forward", "REV": "backward"}
if direction_cmd in direction_map:
return set_motor_direction(direction_map[direction_cmd])
else:
state.error_count += 1
return "Error: Invalid direction. Use FWD or REV"
elif command.startswith("SERVO:"):
try:
angle = int(command.split(":")[1])
return set_servo_angle(angle)
except:
state.error_count += 1
return "Error: Invalid angle format. Use SERVO:0-180"
elif command == "STATUS":
return get_system_status()
elif command == "HELP":
help_text = """Available Commands:
MOTOR_ON - تشغيل المحرك DC
MOTOR_OFF - إيقاف المحرك DC
SPEED:0-100 - ضبط السرعة (0-100%)
DIR:FWD/REV - ضبط الاتجاه (أمامي/خلفي)
SERVO:0-180 - ضبط زاوية السيرفو
STATUS - حالة النظام
TEST - تشغيل تسلسل اختباري
CONFIG - عرض الإعدادات
RESET - إعادة تعيين النظام
HELP - عرض هذه المساعدة
READY - اختبار جاهزية النظام
PING - اختبار الاتصال
GET_STATS - إحصائيات النظام
CALIBRATE_SERVO - معايرة السيرفو
EMERGENCY_STOP - إيقاف طارئ
SERVO_SWEEP - مسح كامل للسيرفو
MOTOR_RAMP - زيادة تدريجية في السرعة"""
return help_text
elif command == "TEST":
return run_test_sequence()
elif command == "CONFIG":
return f"Current config: {config}"
elif command == "RESET":
init_system()
return "System reset complete"
elif command == "READY" or command == "TEST":
return "PICO_READY - Motor Control System Active"
elif command == "PING":
return f"PONG - Uptime: {utime.ticks_diff(utime.ticks_ms(), state.system_start_time) // 1000}s"
elif command == "SAVE_CONFIG":
if save_config(config):
return "Config saved successfully"
else:
state.error_count += 1
return "Error: Failed to save config"
elif command == "GET_STATS":
stats = {
"commands_processed": state.command_count,
"errors": state.error_count,
"uptime_seconds": utime.ticks_diff(utime.ticks_ms(), state.system_start_time) // 1000,
"last_command_time": state.last_command_time,
"current_speed": state.motor_speed,
"current_angle": state.servo_angle,
"motor_state": "ON" if state.motor_running else "OFF"
}
return json.dumps(stats)
elif command == "CALIBRATE_SERVO":
# تسلسل معايرة السيرفو
results = []
for angle in [0, 90, 180, 90]:
results.append(set_servo_angle(angle))
utime.sleep_ms(500)
return "Calibration complete\n" + "\n".join(results)
elif command == "EMERGENCY_STOP":
# إيقاف طارئ
results = []
results.append(motor_off())
results.append(set_servo_angle(90))
ENA.duty_u16(0)
LED_PIN.off()
STATUS_LED.off()
return "EMERGENCY STOP ACTIVATED\n" + "\n".join(results)
elif command == "SERVO_SWEEP":
# مسح كامل للسيرفو
results = []
for angle in range(0, 181, 10):
results.append(set_servo_angle(angle))
utime.sleep_ms(50)
for angle in range(180, -1, -10):
results.append(set_servo_angle(angle))
utime.sleep_ms(50)
results.append(set_servo_angle(90))
return "Servo sweep complete\n" + "\n".join(results[:5]) + "\n..." # عرض أول 5 نتائج فقط
elif command == "MOTOR_RAMP":
# زيادة تدريجية في سرعة المحرك
results = []
results.append(motor_on())
for speed in range(0, 101, 10):
results.append(set_motor_speed(speed))
utime.sleep_ms(200)
for speed in range(100, -1, -10):
results.append(set_motor_speed(speed))
utime.sleep_ms(200)
results.append(motor_off())
return "Motor ramp test complete\n" + "\n".join(results[:5]) + "\n..."
elif command.startswith("SET_CONFIG:"):
try:
# تحديث الإعدادات ديناميكياً
config_str = command.split(":", 1)[1]
new_config = json.loads(config_str)
config.update(new_config)
return f"Config updated: {config}"
except:
state.error_count += 1
return "Error: Invalid config format"
else:
state.error_count += 1
return f"Error: Unknown command '{command}'. Type HELP for available commands."
except Exception as e:
state.error_count += 1
return f"Error processing command: {str(e)}"
def check_for_input(poll_obj, timeout_ms=0):
"""التحقق من وجود بيانات للإدخال باستخدام uselect"""
try:
# استخدام poll للتحقق من وجود بيانات
events = poll_obj.poll(timeout_ms)
return len(events) > 0
except:
return False
def handle_connection():
"""إدارة الاتصال ومعالجة الأوامر"""
buffer = ""
heartbeat_interval = 5000 # 5 ثواني
last_heartbeat = utime.ticks_ms()
# إنشاء كائن poll لمراقبة الإدخال
poll_obj = uselect.poll()
poll_obj.register(sys.stdin, uselect.POLLIN)
print("Starting connection handler...")
while True:
try:
# التحقق من وجود بيانات للإدخال باستخدام poll
if check_for_input(poll_obj, 0):
try:
# قراءة حرف واحد
char = sys.stdin.read(1)
if char == '\n' or char == '\r':
if buffer:
# معالجة الأمر الكامل
print(f"Command received: {buffer}")
response = process_command(buffer)
print(f"Response: {response}")
buffer = ""
elif char:
buffer += char
except Exception as e:
print(f"Error reading input: {e}")
buffer = ""
# إرسال نبضة حياة (Heartbeat) دورية
current_time = utime.ticks_ms()
if utime.ticks_diff(current_time, last_heartbeat) > heartbeat_interval:
# إرسال حالة النظام تلقائياً
if state.connection_active:
status = get_system_status()
print(f"HEARTBEAT: {status}")
# وميض LED للإشارة إلى النشاط
LED_PIN.toggle()
last_heartbeat = current_time
# فحص المهلة (Timeout) - إذا مر وقت طويل دون أوامر
idle_time = utime.ticks_diff(current_time, state.last_command_time)
if idle_time > 30000: # 30 ثانية
# إعادة تعيين المحرك إذا كان متوقفاً
if not state.motor_running and state.motor_speed > 0:
state.motor_speed = 0
ENA.duty_u16(0)
# تأخير قصير لتجنب الاستخدام المفرط للمعالج
utime.sleep_ms(10)
except KeyboardInterrupt:
print("\nKeyboard interrupt received. Shutting down...")
cleanup()
break
except Exception as e:
print(f"Error in connection handler: {e}")
utime.sleep_ms(100)
def cleanup():
"""تنظيف الموارد عند الخروج"""
print("\nCleaning up resources...")
# إيقاف جميع المحركات
motor_off()
set_servo_angle(90)
# إطفاء جميع الـ LEDs
LED_PIN.off()
STATUS_LED.off()
# إغلاق PWM
ENA.deinit()
servo.deinit()
print("Cleanup complete. Goodbye!")
print("="*50)
def startup_sequence():
"""تسلسل بدء التشغيل للإشارة إلى جاهزية النظام"""
print("Running startup sequence...")
# وميض LED المدمج 3 مرات
for _ in range(3):
LED_PIN.on()
utime.sleep_ms(200)
LED_PIN.off()
utime.sleep_ms(200)
# تحريك السيرفو سريعاً
for angle in [45, 90, 135, 90]:
set_servo_angle(angle)
utime.sleep_ms(150)
# تشغيل وإيقاف المحرك بسرعة
motor_on()
set_motor_speed(30)
utime.sleep_ms(300)
motor_off()
# تشغيل LED الإضافي
STATUS_LED.on()
utime.sleep_ms(500)
STATUS_LED.off()
print("Startup sequence complete. System is ready.")
def main():
"""الدالة الرئيسية"""
try:
# تهيئة النظام
init_system()
# تشغيل تسلسل بداية تشغيل قصير
startup_sequence()
# بدء معالجة الاتصال
print("System ready. Waiting for commands...")
# تشغيل حلقة معالجة الأوامر
handle_connection()
except KeyboardInterrupt:
print("\nProgram interrupted by user")
cleanup()
except Exception as e:
print(f"Fatal error in main: {e}")
cleanup()
# تشغيل البرنامج
if __name__ == "__main__":
main()
import tkinter as tk
from tkinter import ttk, messagebox
import serial
import serial.tools.list_ports
import threading
import time
class MotorControlApp:
def __init__(self, root):
self.root = root
self.root.title("نظام التحكم في المحركات - Raspberry Pi Pico")
# تحديد الحد الأدنى والأقصى لحجم النافذة
self.MIN_WIDTH = 650
self.MIN_HEIGHT = 750
self.MAX_WIDTH = 900
self.MAX_HEIGHT = 900
# تعيين حجم ابتدائي مناسب
initial_width = 700
initial_height = 800
# تعيين حجم النافذة
self.root.geometry(f"{initial_width}x{initial_height}")
# منع التصغير تحت الحجم المحدد
self.root.minsize(self.MIN_WIDTH, self.MIN_HEIGHT)
self.root.maxsize(self.MAX_WIDTH, self.MAX_HEIGHT)
# إضافة خاصية تغيير الحجم (يمكن التكبير ولكن ضمن الحدود المحددة)
self.root.resizable(True, True)
# مركزية النافذة على الشاشة
self.center_window()
# متغيرات الاتصال
self.serial_port = None
self.connected = False
# متغيرات التحكم
self.dc_speed = tk.IntVar(value=0)
self.dc_direction = tk.StringVar(value="forward")
self.dc_running = tk.BooleanVar(value=False)
self.servo_angle = tk.IntVar(value=90)
# إنشاء الواجهة
self.create_widgets()
# البحث عن المنافذ المتاحة
self.find_available_ports()
# ربط حدث تغيير حجم النافذة
self.root.bind("<Configure>", self.on_window_resize)
def center_window(self):
"""مركزية النافذة على الشاشة"""
screen_width = self.root.winfo_screenwidth()
screen_height = self.root.winfo_screenheight()
x = (screen_width - self.MIN_WIDTH) // 2
y = (screen_height - self.MIN_HEIGHT) // 2
self.root.geometry(f"{self.MIN_WIDTH}x{self.MIN_HEIGHT}+{x}+{y}")
def on_window_resize(self, event):
"""التعامل مع تغيير حجم النافذة"""
# الحصول على الحجم الحالي
width = self.root.winfo_width()
height = self.root.winfo_height()
# التأكد من عدم تجاوز الحدود الدنيا
if width < self.MIN_WIDTH or height < self.MIN_HEIGHT:
# إعادة الحجم إلى الحد الأدنى إذا كان أصغر
self.root.geometry(f"{max(width, self.MIN_WIDTH)}x{max(height, self.MIN_HEIGHT)}")
# تحديث تخطيط الواجهة إذا لزم الأمر
self.update_layout()
def update_layout(self):
"""تحديث تخطيط الواجهة بناءً على حجم النافذة"""
# يمكن إضافة منطق لتعديل التخطيط بناءً على الحجم
pass
def create_widgets(self):
# إطار العنوان
title_frame = tk.Frame(self.root, bg="#2c3e50", height=80)
title_frame.pack(fill=tk.X)
title_frame.pack_propagate(False)
title_label = tk.Label(title_frame, text="نظام التحكم في المحركات",
font=("Arial", 20, "bold"), fg="white", bg="#2c3e50")
title_label.pack(pady=20)
# إطار الاتصال
connection_frame = tk.LabelFrame(self.root, text="إعدادات الاتصال",
font=("Arial", 12, "bold"), padx=15, pady=15)
connection_frame.pack(fill=tk.X, padx=20, pady=10, ipadx=5, ipady=5)
# إنشاء شبكة داخل إطار الاتصال
connection_grid = tk.Frame(connection_frame)
connection_grid.pack(fill=tk.X, expand=True)
# صف 1: اختيار المنفذ
port_label = tk.Label(connection_grid, text="اختر المنفذ:", font=("Arial", 10))
port_label.grid(row=0, column=0, sticky=tk.W, pady=5, padx=(0, 10))
self.port_combo = ttk.Combobox(connection_grid, state="readonly", width=35)
self.port_combo.grid(row=0, column=1, padx=10, pady=5, sticky=tk.W+tk.E)
refresh_btn = tk.Button(connection_grid, text="تحديث المنافذ",
command=self.find_available_ports,
bg="#3498db", fg="white", width=12)
refresh_btn.grid(row=0, column=2, padx=5, pady=5, sticky=tk.W)
# صف 2: أزرار الاتصال
self.connect_btn = tk.Button(connection_grid, text="اتصال",
command=self.connect_serial,
bg="#27ae60", fg="white", width=10)
self.connect_btn.grid(row=1, column=1, padx=5, pady=10, sticky=tk.W)
disconnect_btn = tk.Button(connection_grid, text="قطع الاتصال",
command=self.disconnect_serial,
bg="#e74c3c", fg="white", width=10)
disconnect_btn.grid(row=1, column=1, padx=(120, 0), pady=10, sticky=tk.W)
# حالة الاتصال
self.connection_status = tk.Label(connection_grid, text="غير متصل",
fg="red", font=("Arial", 10, "bold"))
self.connection_status.grid(row=1, column=0, sticky=tk.W, padx=5, pady=10)
# جعل الأعمدة قابلة للتوسع
connection_grid.columnconfigure(1, weight=1)
# إطار محرك DC
dc_frame = tk.LabelFrame(self.root, text="التحكم في محرك DC",
font=("Arial", 12, "bold"), padx=15, pady=15)
dc_frame.pack(fill=tk.X, padx=20, pady=10, ipadx=5, ipady=5)
# التحكم في التشغيل/الإيقاف
onoff_frame = tk.Frame(dc_frame)
onoff_frame.pack(fill=tk.X, pady=10, padx=10)
tk.Label(onoff_frame, text="الحالة:", font=("Arial", 10)).pack(side=tk.LEFT, padx=(0, 10))
self.dc_on_btn = tk.Button(onoff_frame, text="تشغيل",
command=self.dc_motor_on,
bg="#2ecc71", fg="white", width=10)
self.dc_on_btn.pack(side=tk.LEFT, padx=5)
self.dc_off_btn = tk.Button(onoff_frame, text="إيقاف",
command=self.dc_motor_off,
bg="#e74c3c", fg="white", width=10)
self.dc_off_btn.pack(side=tk.LEFT, padx=5)
# التحكم في الاتجاه
direction_frame = tk.Frame(dc_frame)
direction_frame.pack(fill=tk.X, pady=10, padx=10)
tk.Label(direction_frame, text="الاتجاه:", font=("Arial", 10)).pack(side=tk.LEFT, padx=(0, 10))
forward_btn = tk.Radiobutton(direction_frame, text="أمامي",
variable=self.dc_direction,
value="forward", command=self.update_dc_direction)
forward_btn.pack(side=tk.LEFT, padx=10)
backward_btn = tk.Radiobutton(direction_frame, text="خلفي",
variable=self.dc_direction,
value="backward", command=self.update_dc_direction)
backward_btn.pack(side=tk.LEFT, padx=10)
# التحكم في السرعة
speed_frame = tk.Frame(dc_frame)
speed_frame.pack(fill=tk.X, pady=15, padx=10)
speed_label_frame = tk.Frame(speed_frame)
speed_label_frame.pack(side=tk.LEFT, padx=(0, 10))
tk.Label(speed_label_frame, text="السرعة:", font=("Arial", 10)).pack(anchor=tk.W)
self.speed_label = tk.Label(speed_label_frame, text="0%",
font=("Arial", 10, "bold"), fg="#2980b9")
self.speed_label.pack(anchor=tk.W)
scale_frame = tk.Frame(speed_frame)
scale_frame.pack(side=tk.LEFT, fill=tk.X, expand=True)
self.speed_scale = tk.Scale(scale_frame, from_=0, to=100,
orient=tk.HORIZONTAL,
variable=self.dc_speed,
length=300,
command=self.update_dc_speed,
sliderlength=20)
self.speed_scale.pack(fill=tk.X, expand=True)
# إطار محرك السيرفو
servo_frame = tk.LabelFrame(self.root, text="التحكم في محرك السيرفو",
font=("Arial", 12, "bold"), padx=15, pady=15)
servo_frame.pack(fill=tk.X, padx=20, pady=10, ipadx=5, ipady=5)
# التحكم في الزاوية
angle_frame = tk.Frame(servo_frame)
angle_frame.pack(fill=tk.X, pady=15, padx=10)
angle_label_frame = tk.Frame(angle_frame)
angle_label_frame.pack(side=tk.LEFT, padx=(0, 10))
tk.Label(angle_label_frame, text="الزاوية:", font=("Arial", 10)).pack(anchor=tk.W)
self.angle_label = tk.Label(angle_label_frame, text="90°",
font=("Arial", 10, "bold"), fg="#9b59b6")
self.angle_label.pack(anchor=tk.W)
scale_frame_servo = tk.Frame(angle_frame)
scale_frame_servo.pack(side=tk.LEFT, fill=tk.X, expand=True)
self.angle_scale = tk.Scale(scale_frame_servo, from_=0, to=180,
orient=tk.HORIZONTAL,
variable=self.servo_angle,
length=300,
command=self.update_servo_angle,
sliderlength=20)
self.angle_scale.pack(fill=tk.X, expand=True)
# أزرار زوايا محددة
preset_frame = tk.Frame(servo_frame)
preset_frame.pack(fill=tk.X, pady=15, padx=10)
tk.Label(preset_frame, text="زوايا سريعة:", font=("Arial", 10)).pack(side=tk.LEFT, padx=(0, 10))
angles = [0, 45, 90, 135, 180]
for angle in angles:
btn = tk.Button(preset_frame, text=f"{angle}°",
command=lambda a=angle: self.set_servo_angle(a),
bg="#9b59b6", fg="white", width=6,
activebackground="#8e44ad", activeforeground="white")
btn.pack(side=tk.LEFT, padx=3)
# إطار المعلومات
info_frame = tk.LabelFrame(self.root, text="معلومات النظام",
font=("Arial", 12, "bold"), padx=15, pady=15)
info_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=10, ipadx=5, ipady=5)
# إطار قابل للتمرير للمعلومات
info_canvas = tk.Canvas(info_frame, bg="#f8f9fa", highlightthickness=0)
scrollbar = tk.Scrollbar(info_frame, orient="vertical", command=info_canvas.yview)
scrollable_frame = tk.Frame(info_canvas, bg="#f8f9fa")
scrollable_frame.bind(
"<Configure>",
lambda e: info_canvas.configure(scrollregion=info_canvas.bbox("all"))
)
info_canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
info_canvas.configure(yscrollcommand=scrollbar.set)
info_text = """
تعليمات الاستخدام:
1. قم بتوصيل Raspberry Pi Pico بالكمبيوتر عبر كابل USB
2. اختر المنفذ المناسب من القائمة المنسدلة
3. اضغط على زر "اتصال" لتأسيس الاتصال
4. استخدم عناصر التحكم للتحكم في المحركات
توصيل المحرك DC مع L293D:
• IN1 → GPIO 2 (الدبوس رقم 4)
• IN2 → GPIO 3 (الدبوس رقم 5)
• ENA → GPIO 4 (الدبوس رقم 6) - PWM
• VCC → 5V من Pico
• GND → GND من Pico
توصيل محرك السيرفو (مثل SG90 أو MG90S):
• الإشارة → GPIO 5 (الدبوس رقم 7)
• VCC → 5V من Pico (الدبوس رقم 40)
• GND → GND من Pico (الدبوس رقم 38)
ملاحظات هامة:
• تأكد من أن Raspberry Pi Pico مبرمج بـ MicroPython
• تأكد من تثبيت المكتبات المطلوبة على الكمبيوتر
• عند قطع الاتصال، اضغط على زر "قطع الاتصال" أولاً
حالة النظام:
• اللون الأخضر: متصل
• اللون الأحمر: غير متصل
• الشريط السفلي: يعرض آخر عملية تمت
إجراءات الأمان:
• تأكد من أن مصدر الطاقة كافٍ للمحركات
• تجنب تحميل المحركات فوق طاقتها
• افصل المحركات عند عدم الاستخدام
"""
self.info_label = tk.Label(scrollable_frame, text=info_text,
justify=tk.LEFT, font=("Arial", 9),
bg="#f8f9fa", padx=10, pady=10)
self.info_label.pack(fill=tk.BOTH, expand=True)
# تعبئة إطار المعلومات
info_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# إطار حالة الإرسال
status_frame = tk.Frame(self.root, bg="#34495e", height=50)
status_frame.pack(fill=tk.X, side=tk.BOTTOM)
status_frame.pack_propagate(False)
self.status_label = tk.Label(status_frame, text="جاهز للاتصال...",
bg="#34495e", fg="white",
font=("Arial", 10))
self.status_label.pack(expand=True, fill=tk.BOTH, padx=20)
# شريط حالة حجم النافذة
size_frame = tk.Frame(self.root, bg="#2c3e50", height=25)
size_frame.pack(fill=tk.X, side=tk.BOTTOM)
size_frame.pack_propagate(False)
self.size_label = tk.Label(size_frame,
text=f"الحجم الحالي: {self.MIN_WIDTH}x{self.MIN_HEIGHT} (الحد الأدنى)",
bg="#2c3e50", fg="#ecf0f1",
font=("Arial", 8))
self.size_label.pack(side=tk.RIGHT, padx=10)
# تحديث تسمية الحجم عند تغيير حجم النافذة
self.root.bind("<Configure>", self.update_size_label)
def update_size_label(self, event=None):
"""تحديث تسمية حجم النافذة"""
width = self.root.winfo_width()
height = self.root.winfo_height()
self.size_label.config(text=f"الحجم: {width}x{height} (الحد الأدنى: {self.MIN_WIDTH}x{self.MIN_HEIGHT})")
def find_available_ports(self):
"""البحث عن المنافذ التسلسلية المتاحة"""
ports = serial.tools.list_ports.comports()
port_list = [f"{port.device} - {port.description}" for port in ports]
self.port_combo['values'] = port_list
if port_list:
self.port_combo.set(port_list[0])
self.update_status(f"تم العثور على {len(port_list)} منفذ/منافذ")
else:
self.port_combo.set("")
self.update_status("لم يتم العثور على منافذ متاحة", "warning")
def connect_serial(self):
"""الاتصال بالمنفذ التسلسلي"""
port = self.port_combo.get()
if not port:
self.update_status("الرجاء اختيار منفذ أولاً", "error")
messagebox.showerror("خطأ", "الرجاء اختيار منفذ أولاً!")
return
# استخراج اسم المنفذ فقط (قبل الواصلة)
port_name = port.split(" - ")[0] if " - " in port else port
try:
self.serial_port = serial.Serial(port_name, 9600, timeout=1)
time.sleep(2) # انتظار تهيئة الاتصال
self.connected = True
self.connect_btn.config(state=tk.DISABLED, bg="#95a5a6")
self.connection_status.config(text="متصل ✓", fg="#2ecc71")
self.update_status(f"تم الاتصال بـ {port_name}", "success")
# بدء خيط لقراءة البيانات من المنفذ التسلسلي
self.read_thread = threading.Thread(target=self.read_serial, daemon=True)
self.read_thread.start()
except Exception as e:
self.update_status(f"فشل الاتصال: {str(e)}", "error")
messagebox.showerror("خطأ في الاتصال", f"فشل الاتصال: {str(e)}")
def disconnect_serial(self):
"""قطع الاتصال بالمنفذ التسلسلي"""
if self.serial_port and self.serial_port.is_open:
self.serial_port.close()
self.connected = False
self.connect_btn.config(state=tk.NORMAL, bg="#27ae60")
self.connection_status.config(text="غير متصل ✗", fg="#e74c3c")
self.update_status("تم قطع الاتصال", "info")
def read_serial(self):
"""قراءة البيانات من المنفذ التسلسلي"""
while self.connected:
try:
if self.serial_port and self.serial_port.in_waiting > 0:
data = self.serial_port.readline().decode().strip()
if data:
self.root.after(0, self.update_status, f"Pico: {data}", "info")
except:
pass
time.sleep(0.1)
def send_command(self, command):
"""إرسال أمر إلى Raspberry Pi Pico"""
if self.connected and self.serial_port:
try:
self.serial_port.write(f"{command}\n".encode())
self.update_status(f"تم إرسال الأمر: {command}", "success")
except Exception as e:
self.update_status(f"خطأ في الإرسال: {str(e)}", "error")
else:
self.update_status("غير متصل بـ Raspberry Pi Pico!", "warning")
messagebox.showwarning("تحذير", "غير متصل بـ Raspberry Pi Pico!")
def update_status(self, message, msg_type="info"):
"""تحديث حالة النظام مع ألوان مختلفة"""
colors = {
"info": ("#3498db", "white"),
"success": ("#27ae60", "white"),
"warning": ("#f39c12", "white"),
"error": ("#e74c3c", "white")
}
color, text_color = colors.get(msg_type, ("#3498db", "white"))
# تغيير لون الخلفية مؤقتاً
self.status_label.config(text=message, bg=color, fg=text_color)
# العودة إلى اللون الأصلي بعد 3 ثواني (للرسائل المؤقتة)
if msg_type in ["success", "warning", "error"]:
self.root.after(3000, lambda: self.status_label.config(bg="#34495e", fg="white"))
def dc_motor_on(self):
"""تشغيل محرك DC"""
self.dc_running.set(True)
self.send_command(f"MOTOR_ON")
self.update_status("محرك DC قيد التشغيل", "success")
def dc_motor_off(self):
"""إيقاف محرك DC"""
self.dc_running.set(False)
self.send_command(f"MOTOR_OFF")
self.update_status("محرك DC متوقف", "info")
def update_dc_speed(self, value):
"""تحديث سرعة محرك DC"""
self.speed_label.config(text=f"{value}%")
if self.dc_running.get():
self.send_command(f"SPEED:{value}")
self.update_status(f"السرعة: {value}%", "info")
def update_dc_direction(self):
"""تحديط اتجاه محرك DC"""
direction = self.dc_direction.get()
direction_code = "FWD" if direction == "forward" else "REV"
self.send_command(f"DIR:{direction_code}")
direction_text = "أمامي" if direction == "forward" else "خلفي"
self.update_status(f"اتجاه محرك DC: {direction_text}", "info")
def update_servo_angle(self, angle):
"""تحديث زاوية محرك السيرفو"""
self.angle_label.config(text=f"{angle}°")
self.send_command(f"SERVO:{angle}")
self.update_status(f"زاوية السيرفو: {angle}°", "info")
def set_servo_angle(self, angle):
"""ضبط زاوية محددة لمحرك السيرفو"""
self.servo_angle.set(angle)
self.angle_label.config(text=f"{angle}°")
self.send_command(f"SERVO:{angle}")
self.update_status(f"تم ضبط زاوية السيرفو إلى {angle}°", "success")
def main():
root = tk.Tk()
# إضافة أيقونة للتطبيق (إذا كانت موجودة)
try:
root.iconbitmap("motor_icon.ico") # يمكنك تغيير المسار أو حذف السطر
except:
pass
app = MotorControlApp(root)
root.mainloop()
if __name__ == "__main__":
main()
بعد رفع الكود البرمجي على لوحة راسبيرى باى بيكو 2W سوف تظهر لك نافذة التحكم، قم باختيار المنفذ المتصل به لوحة الراسبيرى باى بيكو بالحاسوب، ومن ثم قم بالتحكم في محرك السيرفو بتحريكه إلى أي زاوية تريدها، ويمكنك أيضًا التحكم في سرعة واتجاه دوران وإيقاف وتشغيل محرك التيار المستمر.













