كود البحث

تحكم في محرك سيرفو من مسافة بعيدة! درس Arduino لهيليك WiFi LoRa 32 V3 (TX)

هذه الدرس جزء من: مقدمة عن WiFi LoRa

تحكم في محرك سيرفو من مسافة بعيدة! درس Arduino لهيليك WiFi LoRa 32 V3 (TX)

في هذا الدليل، نأخذ الرسومات الدقيقة من مشروع السيرفو Heltec ESP32 LoRa V3 ونستعرض كيف تعمل - دون إضافة أي كود إضافي. ستتعلم كيف يقرأ جهاز الإرسال المشفر الدوار، ويؤمن تلك الزاوية ويرسلها عبر LoRa، وكيف يقوم جهاز الاستقبال بفك تشفيرها ويوجه سيرفو صغير. جميع روابط الأجزاء والكود أدناه، وإذا قمت بالطلب من خلال روابط الشركاء لدينا، فإن ذلك يساعدنا على الاستمرار في إنشاء هذه الدروس.

تثبيت لوحات هيلتيك ESP32

قم بإضافة هذا المسار في تفضيلات بيئة تطوير Arduino الخاصة بك كما هو موضح في الفيديو:https://resource.heltec.cn/download/package_heltec_esp32_index.json

1. أجهزة الإرسال (TX) والإعداد

على جانب TX تحتاج إلى:

  • لوحة Heltec WiFi LoRa 32 V3 (داخل غلاف Meshnology N33، مزودة ببطارية سعة 3000 مللي أمبير)

  • مُشَفِّر دوار موصول بـ GPIO 6 (CLK)، GPIO 5 (DT)، GPIO 4 (SW)

  • شاشة OLED على I²C (SDA= 4، SCL= 15)

الرسم يبدأ بتضمين وتهيئة كل شيء تمامًا كما فيHeltec_ESP32_LoRa_V3_Sevo_TX_AiRotaryEncoder.ino:

cppCopyEdit#include "AiEsp32RotaryEncoder.h"
#include "HT_SSD1306Wire.h"
#include "LoRaWan_APP.h"
#include "mbedtls/aes.h"
// …
static SSD1306Wire display(0x3c, 500000, SDA_OLED, SCL_OLED, GEOMETRY, RST_OLED);
AiEsp32RotaryEncoder rotaryEncoder = AiEsp32RotaryEncoder(
    PIN_A, PIN_B, SW_PIN, ROTARY_ENCODER_VCC_PIN, false, true, true);
const int homePosition = 90;
const int MAX_ANGLE    = 180;
int servoAngel = homePosition;


فيsetup(), الرمز:

  • تشغيل على الشاشة، تعيين الخط

  • مكالماتrotaryEncoder.begin(),rotaryEncoder.setup(readEncoderISR),rotaryEncoder.setBoundaries(0, MAX_ANGLE, true)وrotaryEncoder.setAcceleration(20)

  • يعيد تعيين المشفر إلىhomePosition

  • يبدأ تشغيل LoRa عبرMcu.begin(HELTEC_BOARD, SLOW_CLK_TPYE)ويعدّ إعدادRadioEvents، القناة، والمعايير تمامًا كما هو موضح في الرسم التخطيطي المقدم.

إرسال الزاوية بأمان

كل دورة حلقة تعملrotary_loop()، التي:

  • يقرأ المشفر في خدمة المقاطعة للوقت الحقيقي (ISR)

  • عندماservoAngelالتغييرات، ويضعها في مخزن بيانات بحجم 16 بايت، ويقوم بتشفيرها باستخدام AES-128encryptAES()من الرسم التخطيطي)، ويدعو

    cppCopyEditRadio.Send(data, sizeof(data));
    
    
  • مجموعاتlora_idle = falseحتىOnTxDone()يُشغّل ويعيد تعيينه.

3. معدات وترتيب جهاز الاستقبال (RX)

على جانب RX تحتاج إلى:

  • لوحة هيلتيك واي فاي لوارا 32 V3 (نفس العلبة/البطارية)

  • ميكرو سيرفو (مثل SG90) على GPIO 6 (أو أي دبوس PWM تم اختباره)

  • شاشة OLED

الرسم التخطيطي فيHeltec_ESP32_LoRa_V3_Sevo_RX.inoيبدأ بـ:

cppCopyEdit#include <ESP32Servo.h>
#include "HT_SSD1306Wire.h"
#include "LoRaWan_APP.h"
#include "mbedtls/aes.h"
// …
const int servoPin       = 6;
const int SERVO_DUTY_MIN = 400;  // us
const int SERVO_DUTY_MAX = 2400; // us
Servo    myservo;
int      servoAngel     = homePosition;


فيsetup(), it:

  • يعمل على Vext لوحدة العرض/LoRa (VextON())

  • مكالماتRadio.Init(&RadioEvents)ويقوم بتكوين RX بنفس معلمات LoRa

  • يربط السيرفو بـmyservo.attach(servoPin, SERVO_DUTY_MIN, SERVO_DUTY_MAX)ويتمركزه فيhomePosition.

4. استلام وفك تشفير وتشغيل السيرفو

الجوهر هو الـOnRxDone(uint8_t *payload, …)استدعاء:

cppCopyEditdecryptAES((uint8_t*)rxpacket, userKey);
if (isNumber(rxpacket)) {
  servoAngel = atoi(rxpacket);
  myservo.write(servoAngel);
  delay(15);
}
Serial.println("Angle: " + String(servoAngel));
lora_idle = true;


يقوم بفك تشفير الكتلة التي حجمها 16 بايت، ويحولها إلى عدد صحيح، ويقوم بتحديث السيرفو على الفور.

5. دعم دبابيس PWM وضبط السيرفو

قمنا باختبار هذه الدبابيس ESP32 لإخراج PWM وجميعها تعمل على تشغيل سيرفو صغير:

CopyEdit1, 2, 3, 4, 5, 6, 19, 35, 36, 38, 39, 40, 41, 42, 45, 47, 48


لـ SG90 القياسي، يستخدم كودنا نطاق نبضات من400 ميكروثانية(0Ã'°) to2400 ميكروثانية(180 درجة)، مما يمنح حركة سلسة وكاملة دون اهتزاز.

6. مخطط الأسلاك

فيما يلي أماكن مخصصة حيث يمكنك وضع مخططات الإرسال والاستقبال الخاصة بك:

هيلت_واي فاي_لورا_مشفِّر دوار
هيلتي_Wifi_LoRA مع بطارية

رمز وروابط الشركاء

جميع الرسومات المذكورة أعلاه متاحة للتنزيل في قسم "الكود والموارد" أدناه. إذا كنت ترغب في بناء هذا بنفسك، يرجى النظر في شراء وحدة Heltec LoRa32 V3، وعلبة Meshnology N33، وم encoder دوار، ومحرك SG90 من خلال روابطنا التابعة. لا يكلفك ذلك شيئًا إضافيًا ويساعدنا على الاستمرار في إنشاء دروس مجانية مثل هذه!


فصول الفيديو للرجوع إليها

  • 00:00 المقدمة ونظرة عامة

  • مفاهيم التحكم عن بُعد

  • أساسيات الاتصال LoRa

  • 00:23 عينة الأجهزة

  • عرض الحالات والبطاريات

  • 01:03 ميزات الوحدة

  • 01:42 المواصفات والاتصال

  • :54 تشغيل السيرفو

  • 03:05 التوصيلات وتخطيط الدبابيس

  • 09:35 وضع الهوائي

  • 11:04 تجميع الحالة

  • :26 رفع الرسومات

  • 35:09 اختبار المدى 1.2 كم

  • :38 اختبار النطاق 1.4 كم

  • ملخص الأداء 38:41

  • :04 الخاتمة والدعم

775-Secure LoRa Servo Angle Transmitter (TX) with Rotary Encoder - Heltec V3
اللغة: C++
/*
 * ملف: Heltec_ESP32_LoRa_V3_Sevo_TX_AiRotaryEncoder.ino  
 * كتب في 24 يونيو، 2025 بواسطة أحمد شمشيري  
 * 
 * =====================================================================  
 * وصف كود أردوينو: نظام تحكم سيرفو لورا الآمن (TX)  
 * =====================================================================  
 * 
 * المكونات الصلبة:  
 * -------------------  
 * - المتحكم الرئيسي: Heltec WiFi LoRa 32 V3  
 * - العلبة: علبة Meshnology N33 مع بطارية 3000mAh  
 * - المدخل: مشفر دوار مع زر ضغط  
 * - التغذية الراجعة: شاشة OLED مدمجة  
 * - المخرج: محرك سيرفو + نقل لاسلكي عبر لورا  
 * 
 * وظائف النظام:  
 * -------------------  
 * [1] التحكم بواسطة المشفر الدوار:  
 * - دوران مع عقارب الساعة / ضد عقارب الساعة لضبط الزاوية المستهدفة (0°-180°)  
 * - عرض الزاوية في الوقت الفعلي على شاشة OLED  
 * - زر الضغط يعيد السيرفو إلى الوضع الرئيسى (افتراضي: 90°)  
 * 
 * [2] نقل لاسلكي آمن:  
 * - جميع قيم الزوايا مشفرة قبل نقلها عبر لورا  
 * - أمر الوضع الرئيسى يُنقل كحزمة آمنة خاصة  
 * - يستخدم نطاق لورا بتردد 433MHz لتوفير اتصالات موثوقة  
 * 
 * [3] إدارة الطاقة:  
 * - مُحسَّن لعملية البطارية (3000mAh)  
 * - أوضاع منخفضة الطاقة بين النقلات  
 * 
 * للحصول على تعليمات الإعداد الكاملة:  
 * يرجى مشاهدة الفيديو التعليمي على: https://youtu.be/EPynuJ7sasY  
 * =====================================================================  
 * 
 * شاهد الفيديو الكامل: https://youtu.be/EPynuJ7sasY  
 * صفحة الموارد: https://robojax.com/T635  
 * 
 * إخلاء المسؤولية:  
 * تم تقديم هذا الكود "كما هو" دون أي ضمان من أي نوع. لا يجوز تحميل المؤلف المسؤولية عن أي أضرار تنشأ عن استخدام هذا الكود.  
 * 
 * الرخصة:  
 * هذا العمل مرخص بموجب رخصة المشاع الإبداعي العامة GNU v3.0  
 * قد تتوفر إذنات تتجاوز نطاق هذه الرخصة على Robojax.com  
 * 
 * شروط المشاركة:  
 * يمكنك مشاركة ونسخ وتعديل هذا الكود لأغراض غير تجارية شريطة:  
 * 1. الاحتفاظ بهذا الملاحظ بالكامل مع الكود الأصلي  
 * 2. تضمين رابط Robojax.com الأصلي  
 * 3. الاحتفاظ برابط الفيديو التعليمي على يوتوب (إذا كان ذلك مناسبًا)  
 * 4. الإشارة بوضوح إلى أي تعديلات تم إجراؤها  
 * 
 * التعليمات الأصلية على: https://robojax.com/T635  
 * فيديو يوتيوب: https://youtu.be/EPynuJ7sasY
 */
#include <Wire.h>
#include "HT_SSD1306Wire.h"
#include "WiFi.h"
static SSD1306Wire  display(0x3c, 500000, SDA_OLED, SCL_OLED, GEOMETRY_128_64, RST_OLED); // عنوان ، تردد ، مجموعة I2C ، دقة ، إعادة تعيين


const int TX_POWER = 2; // ديسيبل ميلي واط من 2 إلى 20. عندما يتم تشغيله عبر البطارية، فإن 2 إلى 14 ديسيبل ميلي واط هو الخيار الأفضل.
const int MAX_ANGLE = 180; // الأكثر شيوعًا هو 180، ولكن يمكنك ضبطه حسب الحاجة

String labelAngle = "Angle";
const int homePosition = 90; // الموقع الابتدائي


 // مشفر
const int SW_PIN = 4; // حدد دبوس لمفتاح التشفير الدوار
const int PIN_A  = 6;
const int PIN_B  = 5;
const int ANGLE_STEP  = 6;
const bool debug= false; // لطباعة بيانات التصحيح في وحدة التحكم التسلسلية، قم بتعيينها إلى صحيح، وإلا إلى خطأ.

int servoAngel = homePosition;
int oldAngleValue = servoAngel;
#include "mbedtls/aes.h" // لتأمين البيانات
#include <cstring> // لـ memset، memcpy
mbedtls_aes_context aes;
const char *userKey = "hyhT676#h~_1a"; // مفتاح الأمان.


#include "LoRaWan_APP.h"
#include "AiEsp32RotaryEncoder.h"
#include "Arduino.h"
#define ROTARY_ENCODER_VCC_PIN -1

 // بدلاً من التغيير هنا، من الأفضل تغيير الأرقام أعلاه
AiEsp32RotaryEncoder rotaryEncoder = AiEsp32RotaryEncoder(
            PIN_A,
            PIN_B,
            SW_PIN,
            ROTARY_ENCODER_VCC_PIN,
            ANGLE_STEP);



#define RF_FREQUENCY                                915432000 // هرتز

#define TX_OUTPUT_POWER                             TX_POWER // دي بي إم من 2 إلى 20. عند التشغيل عبر البطارية من 2 إلى 14 دي بي إم

#define LORA_BANDWIDTH                              0 // 125 كيلو هرتز
 // 250 كيلوهرتز،
 // 2: 500 كيلوهرتز،
 // 3: محجوزة
#define LORA_SPREADING_FACTOR                       7 // [SF7..SF12]
#define LORA_CODINGRATE                             1 // 4/5
 // ٢: ٤/٦،
 // ٣: ٤/٧،
 // ٤: ٤/٨
#define LORA_PREAMBLE_LENGTH                        8 // نفس الشيء بالنسبة للإرسال والاستقبال
#define LORA_SYMBOL_TIMEOUT                         0 // رموز
#define LORA_FIX_LENGTH_PAYLOAD_ON                  false
#define LORA_IQ_INVERSION_ON                        false


#define RX_TIMEOUT_VALUE                            1000
#define BUFFER_SIZE                                 64 // حدد حجم الحمولة هنا

char txpacket[BUFFER_SIZE];
char rxpacket[BUFFER_SIZE];

double txNumber;

bool lora_idle=true;

static RadioEvents_t RadioEvents;
unsigned long lastTxTime = 0;
void OnTxDone( void );
void OnTxTimeout( void );
void decryptAES(uint8_t *data, const char *key);
void encryptAES(uint8_t *data, const char *key);
void processKey(const char *userKey, uint8_t *processedKey, size_t keySize);
void VextON(void);

void rotary_loop(); // وظيفة النموذج الأولي: مقياس دوار
void IRAM_ATTR readEncoderISR(); // وظيفة النموذج الأولي: مقياس دوار
void rotary_onButtonClick(); // وظيفة النموذج الأولي: مقياس دوار

void setup() {
  Serial.begin(115200);
  Serial.println();

  VextON();
  delay(100);

 // يجب علينا تهيئة مشفر التدوير
	rotaryEncoder.begin();
	rotaryEncoder.setup(readEncoderISR);
	bool circleValues = false;
	rotaryEncoder.setBoundaries(0, MAX_ANGLE, circleValues); // minValue، maxValue، circleValues صحيح|خطأ (عندما يصل الحد الأقصى إلى الحد الأدنى والعكس صحيح)
/*
 * تم تقديم تسارع دوار في 25.2.2021.  
 * في حال كان نطاق الاختيار كبيراً، على سبيل المثال - اختر قيمة بين 0 و 1000 ونريد 785  
 * بدون تسارع، ستحتاج وقتاً طويلاً للوصول إلى هذا الرقم  
 * باستخدام التسارع، كلما أسرعت في الدوران، كلما ارتفعت القيمة بسرعة.  
 * للتعديل الدقيق، أبطئ.
 */
 // rotaryEncoder.disableAcceleration(); // تسارُع مُفعل الآن بشكل افتراضي - قم بالتعطيل إذا لم تكن بحاجة إليه
	rotaryEncoder.setAcceleration(20); // أو اضبط القيمة - رقم أكبر = مزيد من التسارع؛ 0 أو 1 يعني تعطيل التسارع
  rotaryEncoder.reset(homePosition); // set home position

 // تهيئة واجهة المستخدم ستقوم أيضًا بتهيئة العرض.
  display.init();
  display.setFont(ArialMT_Plain_10);
 // أشياء لورا
  Mcu.begin(HELTEC_BOARD,SLOW_CLK_TPYE);

    txNumber=0;

    RadioEvents.TxDone = OnTxDone;
    RadioEvents.TxTimeout = OnTxTimeout;

    Radio.Init( &RadioEvents );
    Radio.SetChannel( RF_FREQUENCY );
    Radio.SetTxConfig( MODEM_LORA, TX_OUTPUT_POWER, 0, LORA_BANDWIDTH,
                                   LORA_SPREADING_FACTOR, LORA_CODINGRATE,
                                   LORA_PREAMBLE_LENGTH, LORA_FIX_LENGTH_PAYLOAD_ON,
                                   true, 0, 0, LORA_IQ_INVERSION_ON, 3000 );

}


void displayAngle() {
    display.clear(); // مسح العرض قبل المحتوى الجديد

 // خط 1: نص: زاوية
    display.setTextAlignment(TEXT_ALIGN_LEFT);

 // خط 2: قيمة درجة الحرارة بحجم خط 24 نقطة
    display.setFont(ArialMT_Plain_24);

 // تنسيق
    String angleString = String(servoAngel) + "°";

    display.setFont(ArialMT_Plain_16);
    display.drawString(0, 0, labelAngle);
    display.setFont(ArialMT_Plain_24);
    display.drawString(0, 15, angleString);

    display.display(); // تحديث OLED
}



void VextON(void)
{
  pinMode(Vext,OUTPUT);
  digitalWrite(Vext, LOW);
}

void VextOFF(void) // افتراضي Vext إيقاف
{
  pinMode(Vext,OUTPUT);
  digitalWrite(Vext, HIGH);
}

void sendData()
{

  String txData = String(servoAngel) ;

  uint8_t data[BUFFER_SIZE];
  memset(data, 0, sizeof(data)); // التعبئة بالصفر
  strncpy((char*)data, txData.c_str(), sizeof(data) - 1); // انسخ النص بأمان

  encryptAES(data, userKey); // قم بالتشفير قبل الإرسال
  if(lora_idle == true)
    {
 // delay(1000);
      Radio.Send(data,  sizeof(data));
      if(debug){
      Serial.print("Sending: ");
      Serial.println((char *)data);
      }
      lora_idle = false;
      oldAngleValue =servoAngel; // ابقِ سجلاً لتغيير الزاوية
    }
    Radio.IrqProcess( );
}



void loop() {
  rotary_loop();
 // امسح العرض
  display.clear();

  displayAngle();

  if(oldAngleValue != servoAngel)
  {
	  sendData();
  }
 // delay(100);

}


void OnTxDone( void )
{
        if(debug){
	        Serial.println("TX done......");
        }
	lora_idle = true;
}

void OnTxTimeout( void )
{
    Radio.Sleep( );
        if(debug){
	        Serial.println("TX Timeout......");
        }
    lora_idle = true;
}


/*
 * يقوم بتحويل مفتاح نصي مقدم من المستخدم إلى مفتاح ثابت الطول يبلغ 16 بايت (128 بت) أو 32 بايت (256 بت).
 */
void processKey(const char *userKey, uint8_t *processedKey, size_t keySize) {
    memset(processedKey, 0, keySize); // املأ بالصفر
    size_t len = strlen(userKey);
    if (len > keySize) len = keySize; // اختصر إذا كانت طويلة جدًا
    memcpy(processedKey, userKey, len); // نسخ جزء المفتاح الصحيح
}

/*
 * يقوم بتشفير رسالة بطول 16 بايت (كتلة واحدة) باستخدام AES-128.
 */
void encryptAES(uint8_t *data, const char *key) {
    uint8_t processedKey[16]; // مفتاح 128 بت
    processKey(key, processedKey, 16);

    mbedtls_aes_init(&aes);
    mbedtls_aes_setkey_enc(&aes, processedKey, 128);
    mbedtls_aes_crypt_ecb(&aes, MBEDTLS_AES_ENCRYPT, data, data);
    mbedtls_aes_free(&aes);
}

/*
 * يفك تشفير رسالة بطول 16 بايت (كتلة واحدة) باستخدام AES-128.
 */
void decryptAES(uint8_t *data, const char *key) {
    uint8_t processedKey[16]; // مفتاح 128 بت
    processKey(key, processedKey, 16);

    mbedtls_aes_init(&aes);
    mbedtls_aes_setkey_dec(&aes, processedKey, 128);
    mbedtls_aes_crypt_ecb(&aes, MBEDTLS_AES_DECRYPT, data, data);
    mbedtls_aes_free(&aes);
}



void rotary_onButtonClick()
{
	static unsigned long lastTimePressed = 0;
 // تجاهل الضغط المتعدد في تلك الفترة الزمنية بالم milliseconds
	if (millis() - lastTimePressed < 500)
	{
		return;
	}
	lastTimePressed = millis();

	        if(debug){
	          Serial.print("button pressed ");
	          Serial.print(millis());
	          Serial.println(" milliseconds after restart");
          }
}

void rotary_loop()
{
 // لا تطبع أي شيء ما لم يتغير القيمة
	if (rotaryEncoder.encoderChanged())
	{
		        if(debug){
	            Serial.print("Value: ");
		          Serial.println(rotaryEncoder.readEncoder());
            }
    servoAngel = rotaryEncoder.readEncoder();

	}
	if (rotaryEncoder.isEncoderButtonClicked())
	{
    rotaryEncoder.reset(homePosition);
    servoAngel = homePosition;
		rotary_onButtonClick();
	}
}

void IRAM_ATTR readEncoderISR()
{
	rotaryEncoder.readEncoder_ISR();
}

Common Course Files

الموارد والمراجع

ملفات📁

لا توجد ملفات متاحة.