Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Raspberry pi pico 2w
120 min
Share

التحكم فى المحركات من خلال الحاسوب باستخدام لوحة الراسبيرى باى بيكو

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

Project Video

Overview

Getting the Items

Raspberry Pi Pico 2 wireless
Get Item
Motor Driver L293D Chip
Get Item
SG90 Servo -Positional Rotation
Get Item
Small Brushed DC Motor (5V 16500 RPM) with jumper wires
Get Item
Full-size Breadboard
Get Item
Jumper Wires - Male to Male (40 Pack)
Get Item
Jumper Wires – Male to Female (40 Pack)
Get Item

Steps

Wiring it Up

قم بتوصيل الأسلاك بين محرك التيار المستمر وقائد المحركات ومحرك السيرفو ولوحة راسبيرى باى بيكو 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

Coding

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

'''

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()

Testing it Out

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

Resources

No items found.