This tutorial is part of: คู่มือการใช้งาน WiFi LoRa 32
วิดีโอทั้งหมดที่เกี่ยวข้องกับ Heltec WiFi LoRa 32 จะถูกจัดอยู่ในกลุ่มนี้ ลิงก์ไปยังวิดีโออื่นๆ อยู่ด้านล่างบทความนี้
ควบคุมมอเตอร์เซอร์โวจากระยะไกล! บทช่วยสอน Heltec WiFi LoRa 32 V3 Arduino (TX)
ในคู่มือนี้ เราจะนำสเก็ตช์ต้นฉบับจากโปรเจกต์ Heltec ESP32 LoRa V3 เซอร์โวของเรามาอธิบายวิธีการทำงานทีละขั้นตอน—โดยไม่มีการเพิ่มโค้ดเพิ่มเติม คุณจะได้เรียนรู้ว่าตัวส่งสัญญาณอ่านโรตารีเอนโค้ดเดอร์อย่างไร เข้ารหัสและส่งมุมนั้นผ่าน LoRa อย่างปลอดภัยอย่างไร และตัวรับสัญญาณถอดรหัสและขับไมโครเซอร์โวอย่างไร ลิงก์ชิ้นส่วนและโค้ดทั้งหมดอยู่ด้านล่าง และหากคุณสั่งซื้อผ่านลิงก์พันธมิตรของเรา จะช่วยให้เราสร้างบทเรียนเหล่านี้ต่อไปได้
การติดตั้งบอร์ด Heltec ESP32
เพิ่มพาธนี้ลงในการตั้งค่าของ Arduino IDE ตามที่แสดงในวิดีโอ:https://resource.heltec.cn/download/package_heltec_esp32_index.json
1. ฮาร์ดแวร์และการตั้งค่าตัวส่งสัญญาณ (TX)
ฝั่ง TX คุณต้องมี:
-
บอร์ด Heltec WiFi LoRa 32 V3 (ในเคส Meshnology N33 จ่ายไฟด้วยแบตเตอรี่ 3000 mAh)
-
โรตารีเอนโค้ดเดอร์เชื่อมต่อกับ 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ช่องสัญญาณ และพารามิเตอร์เหมือนกับในสเก็ตช์ที่ให้ไว้ทุกประการ
2. การส่งมุมอย่างปลอดภัย
ทุก ๆ รอบลูปจะเรียกใช้ rotary_loop() ซึ่ง:
-
อ่านเอนโค้ดเดอร์ใน ISR
-
เมื่อ
servoAngelเปลี่ยนแปลง จะบรรจุลงในบัฟเฟอร์ขนาด 16 ไบต์ เข้ารหัสด้วย AES-128 (encryptAES()จากสเก็ตช์) และเรียกcppCopyEditRadio.Send(data, sizeof(data)); -
ตั้งค่า
lora_idle = falseจนกว่าOnTxDone()จะทำงานและรีเซ็ตค่า
3. ฮาร์ดแวร์และการตั้งค่าตัวรับสัญญาณ (RX)
ฝั่ง RX คุณต้องมี:
-
บอร์ด Heltec WiFi LoRa 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() จะ:
-
เปิดไฟ 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 µs (0°) ถึง 2400 µs (180°) ซึ่งให้การหมุนเต็มช่วงที่ราบรื่นโดยไม่มีการกระตุก
6. แผนผังการเดินสายไฟ
ด้านล่างเป็นตำแหน่งที่คุณสามารถวางแผนผัง TX และ RX ของคุณได้:


โค้ดและลิงก์พันธมิตร
สเก็ตช์ทั้งหมดข้างต้นสามารถดาวน์โหลดได้ในส่วน "โค้ดและทรัพยากร" ด้านล่าง หากคุณต้องการสร้างด้วยตัวเอง โปรดพิจารณาซื้อโมดูล Heltec LoRa32 V3, เคส Meshnology N33, โรตารีเอนโคเดอร์ และเซอร์โว SG90 ผ่านลิงก์พันธมิตรของเรา ซึ่งไม่มีค่าใช้จ่ายเพิ่มเติมสำหรับคุณ และช่วยให้เราสร้างบทเรียนฟรีแบบนี้ต่อไปได้!
บทวิดีโอสำหรับอ้างอิง
-
00:00 บทนำและภาพรวม
-
00:05 แนวคิดการควบคุมระยะไกล
-
00:19 พื้นฐานการสื่อสาร LoRa
-
00:23 ตัวอย่างฮาร์ดแวร์
-
00:28 การแสดงเคสและแบตเตอรี่
-
01:03 คุณสมบัติของโมดูล
-
01:42 ข้อมูลจำเพาะและการเชื่อมต่อ
-
02:54 การจ่ายไฟให้เซอร์โว
-
03:05 การเดินสายไฟและขาสัญญาณ
-
09:35 การวางตำแหน่งเสาอากาศ
-
11:04 การประกอบเคส
-
29:26 การอัปโหลดสเก็ตช์
-
35:09 ทดสอบระยะ 1.2 กม.
-
36:38 ทดสอบระยะ 1.4 กม.
-
38:41 สรุปประสิทธิภาพ
-
43:04 บทสรุปและการสนับสนุน
This tutorial is part of: คู่มือการใช้งาน WiFi LoRa 32
- ใช้ Heltec WiFi LoRa 32 V3 เพื่อส่งอุณหภูมิโดยใช้ DHT22 ไปยังระยะ 1.4 กิโลเมตร
- 13 Miles 20km with NO WiFi? How LoRa Sent Voltage Across Crazy Distances! (Heltec WiFi LoRa 32 V3)
- เปิดใช้งานอุปกรณ์จากระยะทาง 13 ไมล์ (21 กิโลเมตร) – โปรเจกต์ LoRa นอกระบบสุดยอดพร้อม WiFi LoRa 32!
- ระบบแจ้งเตือนประตูระยะไกลจากระยะทาง 13 ไมล์ (21 กิโลเมตร) ด้วย LoRa – นอกระบบโครงข่าย! (Heltec WiFi LoRa 32 V3)
- โปรเจกต์รีเลย์ระยะไกล DIY: ระยะทาง 13 ไมล์ ไม่ใช้ Wi-Fi/ไม่ใช้ซิม Heltec LoRa 32 โมดูล
- How to Use the Heltec LoRa CubeCell Development Board HTCC-AB01
/*
File: Heltec_ESP32_LoRa_V3_Sevo_TX_AiRotaryEncoder.ino
written on 24 Jun, 2025 by Ahmad Shamshiri
* =====================================================================
* ARDUINO CODE DESCRIPTION: SECURE LoRa SERVO CONTROL SYSTEM (TX)
* =====================================================================
*
* HARDWARE COMPONENTS:
* -------------------
* - Main Controller: Heltec WiFi LoRa 32 V3
* - Enclosure: Meshnology N33 case with 3000mAh battery
* - Input: Rotary encoder with push-button
* - Feedback: Built-in OLED display
* - Output: Servo motor + LoRa wireless transmission
*
* SYSTEM FUNCTIONALITY:
* -------------------
* [1] ROTARY ENCODER CONTROL:
* - Clockwise/Counter-clockwise rotation adjusts target angle (0°-180°)
* - Real-time angle display on OLED screen
* - Push-button returns servo to Home position (default: 90°)
*
* [2] SECURE WIRELESS TRANSMISSION:
* - All angle values encrypted before LoRa transmission
* - Home position command transmitted as special secure packet
* - Uses 433MHz LoRa band for reliable communication
*
* [3] POWER MANAGEMENT:
* - Optimized for battery operation (3000mAh)
* - Low-power modes between transmissions
*
* FOR COMPLETE SETUP INSTRUCTIONS:
* Please watch the tutorial video at: https://youtu.be/EPynuJ7sasY
* =====================================================================
Watch full video explaination: https://youtu.be/EPynuJ7sasY
Resources page: https://robojax.com/T635
* DISCLAIMER:
* This code is provided "AS IS" without warranty of any kind. The author
* shall not be held liable for any damages arising from the use of this code.
*
* LICENSE:
* This work is licensed under the GNU General Public License v3.0
* Permissions beyond the scope of this license may be available at Robojax.com
*
* SHARING TERMS:
* You are free to share, copy and modify this code for non-commercial purposes
* PROVIDED you:
* 1. Keep this entire comment block intact with the original code
* 2. Include the original Robojax.com link
* 3. Keep the YouTube tutorial link (if applicable)
* 4. Clearly indicate any modifications made
*
* Original tutorial at: 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 group , resolution , rst
const int TX_POWER = 2;//dBm from 2 to 20. when powered via battery 2 to 14dBm is the best option
const int MAX_ANGLE = 180;//the most common is 180, but you can set it as needed
String labelAngle = "Angle";
const int homePosition = 90; //initial position
//endcoder
const int SW_PIN = 4;//define a pin for rotary encode switch
const int PIN_A = 6;
const int PIN_B = 5;//
const int ANGLE_STEP = 6;//
const bool debug= false;//to print debug data in serial moinitor set it to true, else false
int servoAngel = homePosition;
int oldAngleValue = servoAngel;
#include "mbedtls/aes.h"//for securing data
#include <cstring> // For memset, memcpy
mbedtls_aes_context aes;
const char *userKey = "hyhT676#h~_1a"; //Security key.
#include "LoRaWan_APP.h"
#include "AiEsp32RotaryEncoder.h"
#include "Arduino.h"
#define ROTARY_ENCODER_VCC_PIN -1
//instead of changing here, rather change numbers above
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 from 2 to 20. when powered via battery 2 to 14dBm
#define LORA_BANDWIDTH 0 // [0: 125 kHz,
// 1: 250 kHz,
// 2: 500 kHz,
// 3: Reserved]
#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 // Same for Tx and Rx
#define LORA_SYMBOL_TIMEOUT 0 // Symbols
#define LORA_FIX_LENGTH_PAYLOAD_ON false
#define LORA_IQ_INVERSION_ON false
#define RX_TIMEOUT_VALUE 1000
#define BUFFER_SIZE 64 // Define the payload size here
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();//prototyp function: rotary encoder
void IRAM_ATTR readEncoderISR();//prototyp function: rotary encoder
void rotary_onButtonClick();//prototyp function: rotary encoder
void setup() {
Serial.begin(115200);
Serial.println();
VextON();
delay(100);
//we must initialize rotary encoder
rotaryEncoder.begin();
rotaryEncoder.setup(readEncoderISR);
bool circleValues = false;
rotaryEncoder.setBoundaries(0, MAX_ANGLE, circleValues); //minValue, maxValue, circleValues true|false (when max go to min and vice versa)
/*Rotary acceleration introduced 25.2.2021.
* in case range to select is huge, for example - select a value between 0 and 1000 and we want 785
* without accelerateion you need long time to get to that number
* Using acceleration, faster you turn, faster will the value raise.
* For fine tuning slow down.
*/
//rotaryEncoder.disableAcceleration(); //acceleration is now enabled by default - disable if you dont need it
rotaryEncoder.setAcceleration(20); //or set the value - larger number = more accelearation; 0 or 1 means disabled acceleration
rotaryEncoder.reset(homePosition); //set home position
// Initialising the UI will init the display too.
display.init();
display.setFont(ArialMT_Plain_10);
//LoRa stuff
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(); // Clear display before new content
// Line 1: Text: Angle
display.setTextAlignment(TEXT_ALIGN_LEFT);
// Line 2: Temperature value in 24pt font
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 default OFF
{
pinMode(Vext,OUTPUT);
digitalWrite(Vext, HIGH);
}
void sendData()
{
String txData = String(servoAngel) ;
uint8_t data[BUFFER_SIZE];
memset(data, 0, sizeof(data)); // Zero-padding
strncpy((char*)data, txData.c_str(), sizeof(data) - 1); // Copy string safely
encryptAES(data, userKey); // Encrypt before sending
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;//keep record of angle change
}
Radio.IrqProcess( );
}
void loop() {
rotary_loop();
// clear the display
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;
}
/**
* Converts a user-provided plaintext key into a fixed-length 16-byte (128-bit)
* or 32-byte (256-bit) key.
*/
void processKey(const char *userKey, uint8_t *processedKey, size_t keySize) {
memset(processedKey, 0, keySize); // Fill with zeros
size_t len = strlen(userKey);
if (len > keySize) len = keySize; // Truncate if too long
memcpy(processedKey, userKey, len); // Copy valid key part
}
/**
* Encrypts a 16-byte (one block) message using AES-128.
*/
void encryptAES(uint8_t *data, const char *key) {
uint8_t processedKey[16]; // 128-bit key
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);
}
/**
* Decrypts a 16-byte (one block) message using AES-128.
*/
void decryptAES(uint8_t *data, const char *key) {
uint8_t processedKey[16]; // 128-bit key
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;
//ignore multiple press in that time 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()
{
//dont print anything unless value changed
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 Links
Common Course Files
แหล่งข้อมูลและเอกสารอ้างอิง
-
ภายนอกHeltec WiFi Kit 32 website linkheltec.org
-
ภายนอก
-
ภายนอกPurchase a WiFi LoRa 32 V3 from AliExpresss.click.aliexpress.com
-
ภายนอกPurchase a WiFi LoRa 32 V3 from AliExpress-2s.click.aliexpress.com
-
ภายนอก
ไฟล์📁
ไลบรารี Arduino (zip)
-
Robojax_HeltecLoRa32V3
Robojax_HeltecLoRa32V3.zip0.09 MB