Suchcode

Steuere einen Servo-Motor aus großer Entfernung! Heltec WiFi LoRa 32 V3 Arduino Tutorial (TX)

Diese Lektion ist Teil von: Einführung in WiFi LoRa

Steuere einen Servo-Motor aus großer Entfernung! Heltec WiFi LoRa 32 V3 Arduino Tutorial (TX)

In diesem Leitfaden verwenden wir die genauen Skizzen unseres Heltec ESP32 LoRa V3 Servo-Projekts und erklären, wie sie funktionieren - kein zusätzlicher Code hinzugefügt. Sie erfahren, wie der Sender einen Drehgeber liest, den Winkel sichert und über LoRa sendet, und wie der Empfänger ihn entschlüsselt und einen Mikroservo antreibt. Alle Teile- und Code-Links sind unten, und wenn Sie über unsere Affiliate-Links bestellen, hilft uns das, weiterhin diese Tutorials zu erstellen.

Installation von Heltec ESP32-Boards

Fügen Sie diesen Pfad in die Einstellungen Ihrer Arduino IDE ein, wie im Video gezeigt:https://resource.heltec.cn/download/package_heltec_esp32_index.json

1. Transmitter (TX) Hardware und Einrichtung

Auf der TX-Seite benötigen Sie:

  • Heltec WiFi LoRa 32 V3 Platine (im Meshnology N33 Gehäuse, betrieben von einem 3000 mAh Akku)

  • Rotary-Encoder, verdrahtet an GPIO 6 (CLK), GPIO 5 (DT), GPIO 4 (SW)

  • OLED-Display über I²C (SDA= 4, SCL= 15)

Die Skizze beginnt damit, alles genau wie in einzuschließen und zu initialisieren.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;


Insetup(), der Code:

  • Leistungsanzeige, Schriftart einstellen

  • AnruferotaryEncoder.begin(),rotaryEncoder.setup(readEncoderISR),rotaryEncoder.setBoundaries(0, MAX_ANGLE, true)undrotaryEncoder.setAcceleration(20)

  • Setzt den Encoder zurück aufhomePosition

  • Initialisiert LoRa überMcu.begin(HELTEC_BOARD, SLOW_CLK_TPYE)und richtet einRadioEvents, Kanal und Parameter genau wie im bereitgestellten Entwurf.

2. Den Winkel sicher senden

Jeder Schleifenzyklus läuftrotary_loop(), welches:

  • Liest den Encoder im ISR.

  • WennservoAngelÄnderungen, verpackt sie in einen 16-Byte-Puffer, verschlüsselt mit AES-128encryptAES()Von der Skizze), und ruft

    cppCopyEditRadio.Send(data, sizeof(data));
    
    
  • Setslora_idle = falsebisOnTxDone()löscht und setzt es zurück.

3. Empfänger (RX) Hardware & Installation

Auf der RX-Seite benötigen Sie:

  • Heltec WiFi LoRa 32 V3 Board (gleiche Gehäuse/Batterie)

  • Mikro-Servo (z.B. SG90) an GPIO 6 (oder jedem getesteten PWM-Pin)

  • OLED-Display

Die Skizze inHeltec_ESP32_LoRa_V3_Sevo_RX.inobeginnt mit:

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;


Insetup(), it:

  • Powers on Vext für das Display/LoRa-Modul (VextON())

  • AnrufeRadio.Init(&RadioEvents)und konfiguriert RX mit denselben LoRa-Parametern

  • Befestigt den Servo mitmyservo.attach(servoPin, SERVO_DUTY_MIN, SERVO_DUTY_MAX)und zentriert es beihomePosition.

4. Empfang, Entschlüsselung und Steuerung des Servos

Der Kern ist derOnRxDone(uint8_t *payload, …)Rückruf:

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


Es entschlüsselt den 16-Byte-Block, konvertiert ihn in eine Ganzzahl und aktualisiert sofort den Servo.

5. PWM-Pin-Unterstützung & Servo-Abstimmung

Wir haben diese ESP32-Pins für PWM-Ausgang getestet und sie funktionieren alle zur Steuerung eines Mikro-Servos:

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


Für einen Standard SG90 verwendet unser Code einen Pulsbereich von400 µs(0°) zu2400 µs(180°), was eine sanfte, volle Bewegung ohne Ruckeln ergibt.

6. Schaltplan

Unten finden Sie Platzhalter, in die Sie Ihre TX- und RX-Schemata einfügen können:

Helte_Wifi_LoRA_Rotary_Encoder
Helte_Wifi_LoRA mit Batterie

Code & Affiliate-Links

Alle oben genannten Skizzen sind im Abschnitt "Code & Ressourcen" unten zum Download verfügbar. Wenn Sie dies selbst bauen möchten, ziehen Sie bitte in Betracht, Ihr Heltec LoRa32 V3-Modul, das Meshnology N33-Gehäuse, den Drehgeber und den SG90-Servo über unsere Affiliate-Links zu kaufen. Es kostet Sie nichts extra und hilft uns, weiterhin kostenlose Tutorials wie dieses zu erstellen!


Video-Kapitel zur Referenz

  • 00:00 Einführung & Übersicht

  • 00:05 Fernbedienungskonzepte

  • 00:19 Grundlagen der LoRa-Kommunikation

  • 00:23 Hardware-Vorschau

  • 00:28 Gehäuse- & Batterie-Präsentation

  • 01:03 Modulmerkmale

  • 01:42 Spezifikationen & Konnektivität

  • 02:54 Servoantrieb aktivieren

  • 03:05 Verdrahtung & Pinbelegung

  • 09:35 Antennenplatzierung

  • 11:04 Fallmontage

  • 29:26 Skizzen hochladen

  • 35:09 Reichweitentest 1,2 km

  • 36:38 Reichweitentest 1,4 km

  • 38:41 Leistungszusammenfassung

  • 43:04 Schlussfolgerung & Unterstützung

775-Secure LoRa Servo Angle Transmitter (TX) with Rotary Encoder - Heltec V3
Sprache: C++
/*
 * Datei: Heltec_ESP32_LoRa_V3_Sevo_TX_AiRotaryEncoder.ino  
 * geschrieben am 24. Jun, 2025 von Ahmad Shamshiri  
 * 
 * =====================================================================  
 * ARDUINO CODE BESCHREIBUNG: SICHERES LoRa SERVO STEUERUNGSSYSTEM (TX)  
 * =====================================================================  
 * 
 * HARDWAREKOMPONENTEN:  
 * -------------------  
 * - Hauptsteuerung: Heltec WiFi LoRa 32 V3  
 * - Gehäuse: Meshnology N33 Gehäuse mit 3000mAh Batterie  
 * - Eingabe: Drehgeber mit Druckknopf  
 * - Rückmeldung: Eingebaute OLED-Anzeige  
 * - Ausgabe: Servomotor + LoRa-Drahtlosübertragung  
 * 
 * SYSTEMFUNKTIONALITÄT:  
 * -------------------  
 * [1] DREHGEBERSTEUERUNG:  
 * - Im Uhrzeigersinn/Gegen den Uhrzeigersinn Drehung passt den Zielwinkel an (0°-180°)  
 * - Echtzeitanzeige des Winkels auf dem OLED-Bildschirm  
 * - Druckknopf bringt den Servomotor in die Home-Position (Standard: 90°)  
 * 
 * [2] SICHERE DRAHTLOSÜBERTRAGUNG:  
 * - Alle Winkelwerte werden vor der LoRa-Übertragung verschlüsselt  
 * - Kommando für die Home-Position wird als spezielles sicheres Paket übertragen  
 * - Verwendet das 433MHz LoRa-Band für zuverlässige Kommunikation  
 * 
 * [3] ENERGIEVERWALTUNG:  
 * - Optimiert für den Batteriebetrieb (3000mAh)  
 * - Energiesparmodi zwischen den Übertragungen  
 * 
 * FÜR VOLLSTÄNDIGE EINRICHTUNGSANLEITUNGEN:  
 * Bitte sehen Sie sich das Tutorial-Video an: https://youtu.be/EPynuJ7sasY  
 * =====================================================================  
 * 
 * Vollständige Videoerklärung ansehen: https://youtu.be/EPynuJ7sasY  
 * Ressourcenseite: https://robojax.com/T635  
 * 
 * HAFTUNGSAUSSCHLUSS:  
 * Dieser Code wird "WIE BESEHEN" ohne jegliche Gewährleistung bereitgestellt. Der Autor  
 * kann nicht für Schäden haftbar gemacht werden, die aus der Nutzung dieses Codes entstehen.  
 * 
 * LIZENZ:  
 * Dieses Werk ist unter der GNU General Public License v3.0 lizenziert.  
 * Genehmigungen, die über den Umfang dieser Lizenz hinausgehen, sind möglicherweise auf Robojax.com erhältlich.  
 * 
 * TEILENBEDINGUNGEN:  
 * Sie dürfen diesen Code für nicht-kommerzielle Zwecke teilen, kopieren und modifizieren,  
 * VORAAUSGESEZT, dass Sie:  
 * 1. Dieses gesamte Kommentarblock mit dem Originalcode intakt halten  
 * 2. Den Originallink von Robojax.com einfügen  
 * 3. Den YouTube-Tutorial-Link (falls zutreffend) beibehalten  
 * 4. Alle vorgenommenen Änderungen deutlich angeben  
 * 
 * Originaltutorial unter: https://robojax.com/T635  
 * YouTube-Video: 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); // addr , freq , i2c-Gruppe , Auflösung , rst


const int TX_POWER = 2; // dBm von 2 bis 20. Bei batteriebetriebener Versorgung ist 2 bis 14 dBm die beste Option.
const int MAX_ANGLE = 180; // Am häufigsten ist 180, aber Sie können es nach Bedarf einstellen.

String labelAngle = "Angle";
const int homePosition = 90; // startposition


 // endcoder
const int SW_PIN = 4; // Definieren Sie einen PIN für den Drehschalter.
const int PIN_A  = 6;
const int PIN_B  = 5;
const int ANGLE_STEP  = 6;
const bool debug= false; // Um Debug-Daten im seriellen Monitor auszugeben, setzen Sie es auf wahr, andernfalls auf falsch.

int servoAngel = homePosition;
int oldAngleValue = servoAngel;
#include "mbedtls/aes.h" // zur Sicherung von Daten
#include <cstring> // Für memset, memcpy
mbedtls_aes_context aes;
const char *userKey = "hyhT676#h~_1a"; // Sicherheitsschlüssel.


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

 // Statt hier zu ändern, ändern Sie lieber die Zahlen oben.
AiEsp32RotaryEncoder rotaryEncoder = AiEsp32RotaryEncoder(
            PIN_A,
            PIN_B,
            SW_PIN,
            ROTARY_ENCODER_VCC_PIN,
            ANGLE_STEP);



#define RF_FREQUENCY                                915432000 // Hz

#define TX_OUTPUT_POWER                             TX_POWER // dBm von 2 bis 20. bei betrieb über Batterie 2 bis 14 dBm

#define LORA_BANDWIDTH                              0 // [0: 125 kHz,
 // 1: 250 kHz,
 // 2: 500 kHz,
 // 3: Reserviert
#define LORA_SPREADING_FACTOR                       7 // [SF7..SF12]
#define LORA_CODINGRATE                             1 // [1: 4/5,
 // 2: 4/6,
 // 3: 4/7,
 // 4: 4/8]
#define LORA_PREAMBLE_LENGTH                        8 // Gleich für Tx und Rx
#define LORA_SYMBOL_TIMEOUT                         0 // Symbole
#define LORA_FIX_LENGTH_PAYLOAD_ON                  false
#define LORA_IQ_INVERSION_ON                        false


#define RX_TIMEOUT_VALUE                            1000
#define BUFFER_SIZE                                 64 // Definieren Sie hier die Payload-Größe.

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(); // Prototypfunktion: Drehgeber
void IRAM_ATTR readEncoderISR(); // Prototypfunktion: Drehgeber
void rotary_onButtonClick(); // Prototypfunktion: Drehgeber

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

  VextON();
  delay(100);

 // wir müssen den Drehgeber initialisieren
	rotaryEncoder.begin();
	rotaryEncoder.setup(readEncoderISR);
	bool circleValues = false;
	rotaryEncoder.setBoundaries(0, MAX_ANGLE, circleValues); // minValue, maxValue, circleValues wahr|falsch (wenn max zu min und umgekehrt)
/*
 * Rotationsbeschleunigung eingeführt am 25.2.2021.  
 * Falls der auszuwählende Bereich riesig ist, zum Beispiel - wähle einen Wert zwischen 0 und 1000 und wir möchten 785.  
 * Ohne Beschleunigung benötigt man lange, um zu dieser Zahl zu gelangen.  
 * Mit Beschleunigung gilt: Je schneller du drehst, desto schneller wird der Wert steigen.  
 * Zum Feineinstellen langsamer werden.
 */
 // rotaryEncoder.deaktiviereBeschleunigung(); //Beschleunigung ist jetzt standardmäßig aktiviert - deaktivieren, wenn du sie nicht benötigst
	rotaryEncoder.setAcceleration(20); // oder setzen Sie den Wert - größere Zahl = mehr Beschleunigung; 0 oder 1 bedeutet deaktivierte Beschleunigung
  rotaryEncoder.reset(homePosition); // Heimposition setzen

 // Die Initialisierung der Benutzeroberfläche wird auch das Display initialisieren.
  display.init();
  display.setFont(ArialMT_Plain_10);
 // LoRa-Zeug
  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(); // Anzeige vor neuem Inhalt löschen

 // Winkel
    display.setTextAlignment(TEXT_ALIGN_LEFT);

 // Zeile 2: Temperaturwert in 24pt Schriftgröße
    display.setFont(ArialMT_Plain_24);

 // Format
    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(); // Update OLED
}



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

void VextOFF(void) // Vext Standard AUS
{
  pinMode(Vext,OUTPUT);
  digitalWrite(Vext, HIGH);
}

void sendData()
{

  String txData = String(servoAngel) ;

  uint8_t data[BUFFER_SIZE];
  memset(data, 0, sizeof(data)); // Null-Padding
  strncpy((char*)data, txData.c_str(), sizeof(data) - 1); // Sicheren Sie den Kopiervorgang.

  encryptAES(data, userKey); // Vor dem Senden verschlüsseln
  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; // Aufzeichnung der Winkeländerung führen
    }
    Radio.IrqProcess( );
}



void loop() {
  rotary_loop();
 // Anzeige löschen
  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;
}


/*
 * Konvertiert einen vom Benutzer bereitgestellten Klartextschlüssel in einen festen 16-Byte (128-Bit) oder 32-Byte (256-Bit) Schlüssel.
 */
void processKey(const char *userKey, uint8_t *processedKey, size_t keySize) {
    memset(processedKey, 0, keySize); // Mit Nullen auffüllen
    size_t len = strlen(userKey);
    if (len > keySize) len = keySize; // Abschneiden, wenn zu lang
    memcpy(processedKey, userKey, len); // Kopiere den gültigen Schlüsselteil
}

/*
 * Verschlüsselt eine 16-Byte (ein Block) Nachricht mit AES-128.
 */
void encryptAES(uint8_t *data, const char *key) {
    uint8_t processedKey[16]; // 128-Bit-Schlüssel
    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);
}

/*
 * Entschlüsselt eine 16-Byte (ein Block) Nachricht mit AES-128.
 */
void decryptAES(uint8_t *data, const char *key) {
    uint8_t processedKey[16]; // 128-Bit-Schlüssel
    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;
 // Ignoriere mehrfaches Drücken in diesem Zeitrahmen in Millisekunden.
	if (millis() - lastTimePressed < 500)
	{
		return;
	}
	lastTimePressed = millis();

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

void rotary_loop()
{
 // Drucke nichts aus, es sei denn, der Wert hat sich geändert.
	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

Ressourcen & Referenzen

Dateien📁

Keine Dateien verfügbar.