mirror of
https://github.com/Genaker/LoraSA.git
synced 2026-03-28 17:42:59 +01:00
@@ -111,7 +111,7 @@ HardwareSerial SerialGPS(GPS_RX_PIN, GPS_TX_PIN);
|
||||
#include "driver/gpio.h"
|
||||
#endif // ARDUINO_ARCH_ESP32
|
||||
|
||||
DISPLAY_MODEL *u8g2 = NULL;
|
||||
// DISPLAY_MODEL *u8g2 = NULL;
|
||||
static DevInfo_t devInfo;
|
||||
|
||||
#ifdef HAS_GPS
|
||||
@@ -530,6 +530,7 @@ void loopPMU()
|
||||
|
||||
bool beginDisplay()
|
||||
{
|
||||
/**
|
||||
Wire.beginTransmission(DISPLAY_ADDR);
|
||||
if (Wire.endTransmission() == 0)
|
||||
{
|
||||
@@ -552,7 +553,7 @@ bool beginDisplay()
|
||||
}
|
||||
|
||||
Serial.printf("Warning: Failed to find Display at 0x%0X address\n", DISPLAY_ADDR);
|
||||
return false;
|
||||
return false;*/
|
||||
}
|
||||
|
||||
bool beginSDCard()
|
||||
@@ -847,7 +848,7 @@ void printResult(bool radio_online)
|
||||
Serial.println((psramFound()) ? "+" : "-");
|
||||
|
||||
Serial.print("Display : ");
|
||||
Serial.println((u8g2) ? "+" : "-");
|
||||
// Serial.println((u8g2) ? "+" : "-");
|
||||
|
||||
#ifdef HAS_SDCARD
|
||||
Serial.print("Sd Card : ");
|
||||
@@ -866,7 +867,7 @@ void printResult(bool radio_online)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (u8g2)
|
||||
/*if (u8g2)
|
||||
{
|
||||
|
||||
u8g2->clearBuffer();
|
||||
@@ -895,6 +896,7 @@ void printResult(bool radio_online)
|
||||
|
||||
delay(2000);
|
||||
}
|
||||
*/
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -23,12 +23,12 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <SPI.h>
|
||||
#include <U8g2lib.h>
|
||||
// #include <U8g2lib.h>
|
||||
#include <Wire.h>
|
||||
#include <XPowersLib.h>
|
||||
|
||||
#ifndef DISPLAY_MODEL
|
||||
#define DISPLAY_MODEL U8G2_SSD1306_128X64_NONAME_F_HW_I2C
|
||||
// #define DISPLAY_MODEL U8G2_SSD1306_128X64_NONAME_F_HW_I2C
|
||||
#endif
|
||||
|
||||
#ifndef OLED_WIRE_PORT
|
||||
@@ -57,7 +57,7 @@ typedef struct
|
||||
uint8_t flashSpeed;
|
||||
} DevInfo_t;
|
||||
|
||||
void setupBoards(bool disable_u8g2 = false);
|
||||
void setupBoards(bool disable_u8g2 = true);
|
||||
|
||||
bool beginSDCard();
|
||||
|
||||
@@ -81,7 +81,7 @@ void loopPMU();
|
||||
extern XPowersLibInterface *PMU;
|
||||
extern bool pmuInterrupt;
|
||||
#endif
|
||||
extern DISPLAY_MODEL *u8g2;
|
||||
// extern DISPLAY_MODEL *u8g2;
|
||||
|
||||
#define U8G2_HOR_ALIGN_CENTER(t) ((u8g2->getDisplayWidth() - (u8g2->getUTF8Width(t))) / 2)
|
||||
#define U8G2_HOR_ALIGN_RIGHT(t) (u8g2->getDisplayWidth() - u8g2->getUTF8Width(t))
|
||||
|
||||
@@ -413,7 +413,7 @@
|
||||
#define __HAS_SENSOR__
|
||||
|
||||
#define PMU_WIRE_PORT Wire1
|
||||
#define DISPLAY_MODEL U8G2_SH1106_128X64_NONAME_F_HW_I2C
|
||||
// #define DISPLAY_MODEL U8G2_SH1106_128X64_NONAME_F_HW_I2C
|
||||
#define BOARD_VARIANT_NAME "T-Beam S3"
|
||||
|
||||
#elif defined(T_MOTION_S76G)
|
||||
@@ -545,7 +545,7 @@
|
||||
#define __HAS_SENSOR__
|
||||
|
||||
#define PMU_WIRE_PORT Wire
|
||||
#define DISPLAY_MODEL U8G2_SH1106_128X64_NONAME_F_HW_I2C
|
||||
// #define DISPLAY_MODEL U8G2_SH1106_128X64_NONAME_F_HW_I2C
|
||||
#define BOARD_VARIANT_NAME "T-Beam BPF"
|
||||
|
||||
#else
|
||||
|
||||
@@ -13,7 +13,7 @@ struct RadioModule
|
||||
virtual float getRSSI() = 0;
|
||||
};
|
||||
|
||||
#ifdef USING_SX1262
|
||||
#ifndef USING_SX1262_no
|
||||
struct SX1262Module : RadioModule
|
||||
{
|
||||
SX1262 *_radio;
|
||||
|
||||
@@ -25,7 +25,7 @@ uint16_t Scan::rssiMethod(float (*getRSSI)(void *), void *param, size_t samples,
|
||||
rssi = -65535;
|
||||
|
||||
uint16_t abs_rssi = abs(rssi);
|
||||
if (abs_rssi < max_signal)
|
||||
if (abs_rssi < max_signal && max_signal != 0)
|
||||
{
|
||||
max_signal = abs_rssi;
|
||||
}
|
||||
|
||||
202
platformio.ini
202
platformio.ini
@@ -20,6 +20,7 @@ default_envs = heltec_wifi_lora_32_V3
|
||||
; src_dir = trans_src
|
||||
; for ELRS-BOARDS
|
||||
; src_dir = elrs-boards
|
||||
;src_dir = radio
|
||||
|
||||
[env:heltec_wifi_lora_32_V3]
|
||||
platform = espressif32
|
||||
@@ -250,7 +251,162 @@ build_flags =
|
||||
-DARDUINO_LILYGO_T3_S3_V1_X
|
||||
-DARDUINO_USB_MODE=1
|
||||
|
||||
[env:lilygo-T3S3-v1-2-lr1121-900-compass]
|
||||
[env:lilygo-T3S3-v1-2-lr1121-900-radio-tx]
|
||||
platform = espressif32
|
||||
board = t3_s3_v1_x
|
||||
framework = arduino
|
||||
upload_speed = 921600
|
||||
monitor_speed = 115200
|
||||
board_build.f_cpu = 240000000
|
||||
board_build.filesystem = littlefs
|
||||
src_dir = radio
|
||||
lib_deps =
|
||||
ropg/Heltec_ESP32_LoRa_v3@^0.9.1
|
||||
RadioLib
|
||||
XPowersLib
|
||||
SPI
|
||||
https://github.com/bolderflight/sbus
|
||||
gitlab-airbornemint/Protothreads
|
||||
build_flags =
|
||||
-DMAIN_FILE="Tx.cpp"
|
||||
-DLILYGO
|
||||
-DT3_S3_V1_2_LR1121
|
||||
-DARDUINO_LILYGO_T3S3_LR1121
|
||||
-DESP32
|
||||
-DSAMPLES_RSSI=5
|
||||
-DUSING_LR1121
|
||||
-DINIT_FREQ=900
|
||||
-DFREQ_BEGIN=830
|
||||
-DFREQ_END=945
|
||||
-DFREQ_RX=2440
|
||||
-DARDUINO_ARCH_ESP32
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
-DARDUINO_LILYGO_T3_S3_V1_X
|
||||
-DARDUINO_USB_MODE=1
|
||||
-DLORA_RX=0
|
||||
-DLORA_TX=1
|
||||
|
||||
[env:lilygo-T3S3-v1-2-lr1121-900-radio-rx]
|
||||
platform = espressif32
|
||||
board = t3_s3_v1_x
|
||||
framework = arduino
|
||||
upload_speed = 921600
|
||||
monitor_speed = 115200
|
||||
board_build.f_cpu = 240000000
|
||||
board_build.filesystem = littlefs
|
||||
src_dir = radio
|
||||
lib_deps =
|
||||
ropg/Heltec_ESP32_LoRa_v3@^0.9.1
|
||||
RadioLib
|
||||
XPowersLib
|
||||
https://github.com/bolderflight/sbus
|
||||
gitlab-airbornemint/Protothreads
|
||||
build_flags =
|
||||
-DMAIN_FILE="Tx.cpp"
|
||||
-DLILYGO
|
||||
-DT3_S3_V1_2_LR1121
|
||||
-DARDUINO_LILYGO_T3S3_LR1121
|
||||
-DESP32
|
||||
-DSAMPLES_RSSI=5
|
||||
-DUSING_LR1121
|
||||
-DINIT_FREQ=900
|
||||
-DFREQ_BEGIN=830
|
||||
-DFREQ_END=945
|
||||
-DFREQ_RX=2440
|
||||
-DARDUINO_ARCH_ESP32
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
-DARDUINO_LILYGO_T3_S3_V1_X
|
||||
-DARDUINO_USB_MODE=1
|
||||
-DLORA_RX=1
|
||||
-DLORA_TX=0
|
||||
|
||||
[env:heltec-v3-900-radio-rx]
|
||||
platform = espressif32
|
||||
board = heltec_wifi_lora_32_V3
|
||||
framework = arduino
|
||||
upload_speed = 921600
|
||||
monitor_speed = 115200
|
||||
board_build.f_cpu = 240000000
|
||||
src_dir = radio
|
||||
lib_deps =
|
||||
ropg/Heltec_ESP32_LoRa_v3@^0.9.1
|
||||
RadioLib
|
||||
XPowersLib
|
||||
https://github.com/bolderflight/sbus
|
||||
mbed-takuhachisu/CRC8
|
||||
build_flags =
|
||||
-DMAIN_FILE="Tx.cpp"
|
||||
-DHELTEC_POWER_BUTTON
|
||||
-DHELTEC
|
||||
-DRADIOLIB_CHECK_PARAMS=0
|
||||
-DESP32
|
||||
-DARDUINO_ARCH_ESP32
|
||||
-DARDUINO_USB_CDC_ON_BOOT=0
|
||||
-DARDUINO_USB_MODE=1
|
||||
-DLORA_RX=1
|
||||
-DLORA_TX=0
|
||||
|
||||
[env:heltec-v3-900-radio-tx]
|
||||
platform = espressif32
|
||||
board = heltec_wifi_lora_32_V3
|
||||
framework = arduino
|
||||
upload_speed = 921600
|
||||
monitor_speed = 115200
|
||||
board_build.f_cpu = 240000000
|
||||
src_dir = radio
|
||||
lib_deps =
|
||||
ropg/Heltec_ESP32_LoRa_v3@^0.9.1
|
||||
RadioLib
|
||||
XPowersLib
|
||||
https://github.com/bolderflight/sbus
|
||||
mbed-takuhachisu/CRC8
|
||||
build_flags =
|
||||
-DMAIN_FILE="Tx.cpp"
|
||||
-DHELTEC_POWER_BUTTON
|
||||
-DHELTEC
|
||||
-DRADIOLIB_CHECK_PARAMS=0
|
||||
-DESP32
|
||||
-DARDUINO_ARCH_ESP32
|
||||
-DARDUINO_USB_CDC_ON_BOOT=0
|
||||
-DARDUINO_USB_MODE=1
|
||||
-DLORA_RX=0
|
||||
-DLORA_TX=1
|
||||
-DSERIAL_INPUT=1
|
||||
|
||||
[env:lilygo-T3S3-v1-2-sx1262-900-radio-rx]
|
||||
platform = espressif32
|
||||
board = t3_s3_v1_x
|
||||
framework = arduino
|
||||
upload_speed = 921600
|
||||
monitor_speed = 115200
|
||||
board_build.f_cpu = 240000000
|
||||
board_build.filesystem = littlefs
|
||||
src_dir = radio
|
||||
lib_deps =
|
||||
ropg/Heltec_ESP32_LoRa_v3@^0.9.1
|
||||
RadioLib
|
||||
XPowersLib
|
||||
duracopter/MAVLink v2 C library@^2.0
|
||||
build_flags =
|
||||
-DMAIN_FILE="Tx.cpp"
|
||||
-DLILYGO
|
||||
-DT3_S3_V1_2_SX1262
|
||||
-DARDUINO_LILYGO_T3S3_SX1262
|
||||
-DESP32
|
||||
-DSAMPLES_RSSI=5
|
||||
-DUSING_SX1262
|
||||
-DINIT_FREQ=900
|
||||
-DFREQ_BEGIN=830
|
||||
-DFREQ_END=945
|
||||
-DFREQ_RX=2440
|
||||
-DARDUINO_ARCH_ESP32
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
-DARDUINO_LILYGO_T3_S3_V1_X
|
||||
-DARDUINO_USB_MODE=1
|
||||
-DLORA_RX=1
|
||||
-DLORA_TX=0
|
||||
|
||||
[env:lilygo-T3S3-v1-2-lr1121-900-BT]
|
||||
platform = espressif32
|
||||
board = t3_s3_v1_x
|
||||
framework = arduino
|
||||
@@ -264,16 +420,48 @@ lib_deps =
|
||||
U8g2
|
||||
XPowersLib
|
||||
h2zero/NimBLE-Arduino
|
||||
https://github.com/Genaker/Adafruit_HMC5883_Unified
|
||||
build_flags =
|
||||
-lbt
|
||||
build_flags =
|
||||
-DLILYGO
|
||||
-DT3_S3_V1_2_LR1121
|
||||
-DARDUINO_LILYGO_T3S3_LR1121
|
||||
-DESP32
|
||||
-DSAMPLES_RSSI=5
|
||||
-DUSING_LR1121
|
||||
-DSCAN_RBW_FACTOR=2
|
||||
-DINIT_FREQ=900
|
||||
-DFREQ_BEGIN=830
|
||||
-DFREQ_END=945
|
||||
-DFREQ_RX=2440
|
||||
-DARDUINO_ARCH_ESP32
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
-DARDUINO_LILYGO_T3_S3_V1_X
|
||||
-DARDUINO_USB_MODE=1
|
||||
-DBT_MOBILE
|
||||
|
||||
[env:lilygo-T3S3-v1-2-lr1121-900-compass]
|
||||
platform = espressif32
|
||||
board = t3_s3_v1_x
|
||||
framework = arduino
|
||||
upload_speed = 1500000
|
||||
monitor_speed = 115200
|
||||
board_build.f_cpu = 240000000
|
||||
board_build.partitions = min_spiffs.csv
|
||||
board_build.filesystem = littlefs
|
||||
lib_deps =
|
||||
ropg/Heltec_ESP32_LoRa_v3@^0.9.1
|
||||
RadioLib
|
||||
XPowersLib
|
||||
h2zero/NimBLE-Arduino
|
||||
https://github.com/Genaker/Adafruit_HMC5883_Unified
|
||||
build_flags =
|
||||
-lbt
|
||||
-Og -ggdb3
|
||||
-DLILYGO
|
||||
-DT3_S3_V1_2_LR1121
|
||||
-DARDUINO_LILYGO_T3S3_LR1121
|
||||
-DESP32
|
||||
-DSAMPLES_RSSI=2
|
||||
-DUSING_LR1121
|
||||
-DSCAN_RBW_FACTOR=1
|
||||
-DINIT_FREQ=900
|
||||
-DFREQ_BEGIN=800
|
||||
-DFREQ_END=950
|
||||
@@ -284,9 +472,11 @@ build_flags =
|
||||
-DARDUINO_USB_MODE=1
|
||||
-DCOMPASS_ENABLED
|
||||
-DCOMPASS_FREQ=915
|
||||
-DCOMPASS_DEBUG
|
||||
-DCOMPASS_DEBUG2
|
||||
-DCOMPASS_RSSI
|
||||
-DBT_MOBILE
|
||||
-DBT_RSSI
|
||||
-DBT_NM
|
||||
|
||||
[env:lilygo-T3S3-v1-2-sx1280]
|
||||
platform = espressif32
|
||||
|
||||
297
radio/CRSF.h
Normal file
297
radio/CRSF.h
Normal file
@@ -0,0 +1,297 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
//
|
||||
// Standard Crossfire definitions:
|
||||
//
|
||||
#define CRSF_SYNC_BYTE 0xC8
|
||||
#define CRSF_FRAME_TYPE_CHANNELS 0x16
|
||||
#define CRSF_MAX_CHANNELS 16
|
||||
#define CRSF_BAUDRATE 420000 // standard CRSF baud rate
|
||||
|
||||
// Betaflight CRSF docs:
|
||||
// https://github.com/betaflight/betaflight/blob/4.5-maintenance/src/main/rx/crsf_protocol.h
|
||||
// Crosfire Protocol specs:
|
||||
// https://github.com/tbs-fpv/tbs-crsf-spec/blob/main/crsf.md#broadcast-frame
|
||||
|
||||
// The CRSF "RC Channels" frame for 16 channels is always 24 bytes total:
|
||||
// Byte[0] = 0xC8 (address)
|
||||
// Byte[1] = 22 (length from byte[2] to the last CRC byte)
|
||||
// Byte[2] = 0x16 (frame type for channels)
|
||||
// Byte[3..22] = 20 bytes of channel data (10 bits × 16 = 160 bits)
|
||||
// Byte[23] = CRC
|
||||
//
|
||||
// If type=0x16 and length=22, we interpret it as channel data (16ch, 10 bits each).
|
||||
|
||||
class CRSF2
|
||||
{
|
||||
public:
|
||||
// Pass in the HardwareSerial, plus the pins to use on ESP32
|
||||
// (rxPin and txPin). If you only do TX, you can set rxPin=-1 or vice versa.
|
||||
CRSF2(HardwareSerial &serialPort, int rxPin, int txPin, long baudRate = CRSF_BAUDRATE)
|
||||
: serialPort(serialPort), rxPin(rxPin), txPin(txPin), baudRate(baudRate)
|
||||
{
|
||||
}
|
||||
|
||||
void begin()
|
||||
{
|
||||
// Initialize the UART. If you want two-way, specify both rxPin, txPin.
|
||||
// Example: 8N1, no inversion, 20s timeout
|
||||
serialPort.begin(baudRate, SERIAL_8N1, rxPin, txPin, false, 20000UL);
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
// Send a 16-channel RC frame (10 bits per channel).
|
||||
// channelData[i] should be in the range [0..1023].
|
||||
// For typical servo-like values (1000..2000), you must scale them down if needed.
|
||||
// -------------------------------
|
||||
void sendRCFrame(const uint16_t *channelData)
|
||||
{
|
||||
constexpr size_t CHANNEL_BITS = 10;
|
||||
constexpr size_t CHANNEL_COUNT = CRSF_MAX_CHANNELS;
|
||||
constexpr size_t CHANNEL_BYTES =
|
||||
((CHANNEL_COUNT * CHANNEL_BITS) + 7) / 8; // => 20
|
||||
constexpr size_t FRAME_TYPE_SIZE = 1; // 1 byte for frame type (0x16)
|
||||
constexpr size_t CRC_SIZE = 1; // 1 byte for CRC
|
||||
constexpr size_t CRSF_PAYLOAD_LEN =
|
||||
FRAME_TYPE_SIZE + CHANNEL_BYTES + CRC_SIZE; // => 1 + 20 + 1 = 22
|
||||
constexpr size_t PACKET_SIZE = 2 + CRSF_PAYLOAD_LEN; // => 24 total
|
||||
|
||||
uint8_t packet[PACKET_SIZE];
|
||||
|
||||
// Address (sync) byte
|
||||
packet[0] = CRSF_SYNC_BYTE;
|
||||
// Length (22)
|
||||
packet[1] = CRSF_PAYLOAD_LEN;
|
||||
// Frame Type
|
||||
packet[2] = CRSF_FRAME_TYPE_CHANNELS;
|
||||
|
||||
// Pack channel data (16 × 10 bits = 160 bits => 20 bytes)
|
||||
uint32_t bitBuffer = 0;
|
||||
uint8_t bitCount = 0;
|
||||
size_t byteIndex = 3; // start filling after [0..2]
|
||||
|
||||
for (size_t i = 0; i < CHANNEL_COUNT; i++)
|
||||
{
|
||||
uint16_t val = channelData[i] & 0x03FF; // 10-bit mask
|
||||
bitBuffer |= (static_cast<uint32_t>(val) << bitCount);
|
||||
bitCount += CHANNEL_BITS; // +10
|
||||
|
||||
while (bitCount >= 8)
|
||||
{
|
||||
packet[byteIndex++] = static_cast<uint8_t>(bitBuffer & 0xFF);
|
||||
bitBuffer >>= 8;
|
||||
bitCount -= 8;
|
||||
}
|
||||
}
|
||||
|
||||
// If leftover bits remain, put them into the last byte
|
||||
if (bitCount > 0)
|
||||
{
|
||||
packet[byteIndex++] = static_cast<uint8_t>(bitBuffer & 0xFF);
|
||||
}
|
||||
|
||||
// Compute CRC over [Type + channel bytes] => 21 bytes
|
||||
const size_t CRC_LENGTH = CRSF_PAYLOAD_LEN - 1; // (22 - 1) = 21
|
||||
uint8_t crc = calculateCRC(&packet[2], CRC_LENGTH);
|
||||
// Place CRC at last byte (index 23)
|
||||
packet[PACKET_SIZE - 1] = crc;
|
||||
|
||||
// Send out the 24-byte CRSF RC frame
|
||||
serialPort.write(packet, PACKET_SIZE);
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
// Poll the serial port for incoming CRSF frames.
|
||||
// If we receive a valid 16-ch "Channel Data" frame (type=0x16),
|
||||
// decode the channels into the provided channelData[] array.
|
||||
// Returns 'true' if a new channel frame was successfully parsed.
|
||||
//
|
||||
// Call this in your loop() or in a periodic task.
|
||||
// -------------------------------
|
||||
bool receiveRCFrame(uint16_t *channelData)
|
||||
{
|
||||
// Run our state machine as long as there's data available
|
||||
while (serialPort.available() > 0)
|
||||
{
|
||||
uint8_t c = static_cast<uint8_t>(serialPort.read());
|
||||
|
||||
switch (rxState)
|
||||
{
|
||||
case WAIT_SYNC:
|
||||
if (c == CRSF_SYNC_BYTE)
|
||||
{
|
||||
rxPacket[0] = c;
|
||||
rxIndex = 1;
|
||||
rxState = WAIT_LEN;
|
||||
}
|
||||
break;
|
||||
|
||||
case WAIT_LEN:
|
||||
// The next byte after sync is "length"
|
||||
rxPacket[rxIndex++] = c;
|
||||
rxLength = c; // length should be the payload from [type..CRC]
|
||||
// Basic sanity check: max 64 or so. CRSF frames can vary, but let's be
|
||||
// safe.
|
||||
if (rxLength < 2 || rxLength > 64)
|
||||
{
|
||||
// Invalid length, reset parser
|
||||
rxState = WAIT_SYNC;
|
||||
rxIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Next step: read length more bytes
|
||||
rxState = WAIT_PAYLOAD;
|
||||
}
|
||||
break;
|
||||
|
||||
case WAIT_PAYLOAD:
|
||||
// Store the byte
|
||||
rxPacket[rxIndex++] = c;
|
||||
|
||||
// If we've reached the entire frame, parse
|
||||
if (rxIndex >= (2 + rxLength))
|
||||
{
|
||||
// We have [0]=0xC8, [1]=length, plus 'length' bytes
|
||||
// Check CRC, parse if valid
|
||||
bool ok = parseFrame(channelData);
|
||||
// Reset to look for the next frame
|
||||
rxState = WAIT_SYNC;
|
||||
rxIndex = 0;
|
||||
|
||||
if (ok)
|
||||
{
|
||||
// We got a valid channel frame
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false; // no new channel frame parsed
|
||||
}
|
||||
|
||||
private:
|
||||
HardwareSerial &serialPort;
|
||||
int rxPin;
|
||||
int txPin;
|
||||
long baudRate;
|
||||
|
||||
// ---- Receive State Machine Fields ----
|
||||
enum RxState
|
||||
{
|
||||
WAIT_SYNC,
|
||||
WAIT_LEN,
|
||||
WAIT_PAYLOAD
|
||||
} rxState = WAIT_SYNC;
|
||||
|
||||
uint8_t rxPacket[64]; // buffer for incoming frame
|
||||
size_t rxIndex = 0; // how many bytes we've stored
|
||||
uint8_t rxLength = 0; // length byte from the packet
|
||||
|
||||
// 8-bit CRC with polynomial 0xD5
|
||||
uint8_t calculateCRC(const uint8_t *data, size_t length)
|
||||
{
|
||||
uint8_t crc = 0;
|
||||
for (size_t i = 0; i < length; i++)
|
||||
{
|
||||
crc ^= data[i];
|
||||
for (uint8_t bit = 0; bit < 8; bit++)
|
||||
{
|
||||
if (crc & 0x80)
|
||||
{
|
||||
crc <<= 1;
|
||||
crc ^= 0xD5;
|
||||
}
|
||||
else
|
||||
{
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
// Attempt to parse the frame in rxPacket[].
|
||||
// If it's a valid 16-ch RC frame (type=0x16) and CRC checks out,
|
||||
// decode into channelData[].
|
||||
// Returns true on success, false otherwise.
|
||||
bool parseFrame(uint16_t *channelData)
|
||||
{
|
||||
// Basic layout:
|
||||
// rxPacket[0] = 0xC8
|
||||
// rxPacket[1] = length (n)
|
||||
// Then we have n bytes: [2..(1+n)]
|
||||
// The last of those n bytes is CRC.
|
||||
|
||||
uint8_t lengthField = rxPacket[1];
|
||||
// The "payload" = lengthField bytes from rxPacket[2]..rxPacket[1 + lengthField]
|
||||
// So the CRC is at rxPacket[1 + lengthField].
|
||||
uint8_t crc = rxPacket[1 + lengthField];
|
||||
|
||||
// Check CRC over the [Frame Type..(payload)] excluding the CRC byte
|
||||
// i.e. from rxPacket[2]..rxPacket[1 + lengthField - 1]
|
||||
size_t dataLen = lengthField - 1; // e.g. if length=22, dataLen=21
|
||||
uint8_t calcCRC = calculateCRC(&rxPacket[2], dataLen);
|
||||
|
||||
if (calcCRC != crc)
|
||||
{
|
||||
// CRC mismatch
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check frame type
|
||||
uint8_t frameType = rxPacket[2];
|
||||
if (frameType != CRSF_FRAME_TYPE_CHANNELS)
|
||||
{
|
||||
// Not a channel frame
|
||||
return false;
|
||||
}
|
||||
|
||||
// For a 16-ch frame, lengthField should be 22
|
||||
// but let's check if we want to be sure
|
||||
if (lengthField != 22)
|
||||
{
|
||||
// not a 16-ch RC frame
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we reach here, we have a valid 16-ch frame
|
||||
// Decode the 20 bytes of channel data
|
||||
// They live at rxPacket[3..22], which is 20 bytes
|
||||
decodeChannels(&rxPacket[3], channelData);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Decode 16 channels of 10 bits from the 20-byte payload
|
||||
// into channelData[16].
|
||||
void decodeChannels(const uint8_t *payload, uint16_t *channelData)
|
||||
{
|
||||
constexpr size_t CHANNEL_COUNT = CRSF_MAX_CHANNELS; // 16
|
||||
constexpr size_t CHANNEL_BITS = 10;
|
||||
|
||||
uint32_t bitBuffer = 0;
|
||||
uint8_t bitCount = 0;
|
||||
size_t bytePos = 0;
|
||||
|
||||
for (size_t i = 0; i < CHANNEL_COUNT; i++)
|
||||
{
|
||||
// accumulate bits until we have at least 10
|
||||
while (bitCount < CHANNEL_BITS)
|
||||
{
|
||||
bitBuffer |= (static_cast<uint32_t>(payload[bytePos++]) << bitCount);
|
||||
bitCount += 8;
|
||||
}
|
||||
// extract 10 bits
|
||||
uint16_t val = static_cast<uint16_t>(bitBuffer & 0x3FF); // 10-bit mask
|
||||
bitBuffer >>= CHANNEL_BITS;
|
||||
bitCount -= CHANNEL_BITS;
|
||||
|
||||
// store
|
||||
channelData[i] = val;
|
||||
}
|
||||
}
|
||||
};
|
||||
94
radio/FHSS.cpp
Normal file
94
radio/FHSS.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#include "FHSS.h"
|
||||
|
||||
float hopTable[MAX_HOP_CHANNELS];
|
||||
uint64_t packetNumber = 0;
|
||||
long int receivedPacketCounter = 0;
|
||||
|
||||
uint32_t syncWord = 98754386857476; // Example sync word
|
||||
// uint32_t syncWord = 0x1A2B3C4D; // Example sync word (can be any 32-bit value)
|
||||
|
||||
float maxWidthMHz = 5.0; // Max hopping width of 20 MHz
|
||||
float startFreq = LORA_BASE_FREQ - maxWidthMHz; // Start at 900 MHz
|
||||
float stepKHz = 10.0; // 10 kHz step size
|
||||
|
||||
int numChannels = 0;
|
||||
|
||||
// Get the next frequency from the hopping table
|
||||
int hopIndex = 0;
|
||||
unsigned long lastHopTime = 0;
|
||||
unsigned long dwellTime = 500; // 500ms dwell time
|
||||
float currentFreq = 999;
|
||||
|
||||
// Function to generate a frequency hopping table, adapting if channels are fewer
|
||||
int generateFrequencies(uint32_t syncWord, float startFreq, float stepKHz,
|
||||
float maxWidthMHz)
|
||||
{
|
||||
float stepMHz = stepKHz / 1000.0; // Convert kHz to MHz
|
||||
numChannels = int((maxWidthMHz * 1e3) /
|
||||
stepKHz); // Calculate number of channels within max width
|
||||
|
||||
// If fewer channels are available, adjust dynamically
|
||||
if (numChannels < 10)
|
||||
{ // Less than 10 channels is not good for FHSS
|
||||
Serial.println("Warning: Too few channels! FHSS may not work well.");
|
||||
numChannels = 10; // Ensure a minimum of 10 channels
|
||||
}
|
||||
|
||||
if (numChannels > MAX_HOP_CHANNELS)
|
||||
{
|
||||
Serial.println("Warning: Reducing channels to MAX_HOP_CHANNELS.");
|
||||
numChannels = MAX_HOP_CHANNELS; // Prevent overflow
|
||||
}
|
||||
|
||||
// Generate sequential frequencies within max width
|
||||
for (int i = 0; i < numChannels; i++)
|
||||
{
|
||||
hopTable[i] = startFreq + (i * stepMHz);
|
||||
}
|
||||
|
||||
// Shuffle using sync word (randomize the order)
|
||||
for (int i = 0; i < numChannels; i++)
|
||||
{
|
||||
syncWord = (syncWord * 1103515245 + 12345) & 0x7FFFFFFF;
|
||||
int swapIndex = syncWord % numChannels;
|
||||
|
||||
// Swap values
|
||||
float temp = hopTable[i];
|
||||
hopTable[i] = hopTable[swapIndex];
|
||||
hopTable[swapIndex] = temp;
|
||||
}
|
||||
|
||||
return numChannels; // Return actual number of channels generated
|
||||
}
|
||||
|
||||
// Function to print the generated table (for debugging)
|
||||
void printHopTable(int numChannels)
|
||||
{
|
||||
delay(100);
|
||||
Serial.println("------");
|
||||
Serial.println("Generated Frequency Hopping Table [" + String(numChannels) + "]:");
|
||||
/*for (int i = 0; i < numChannels; i++)
|
||||
{
|
||||
Serial.println(String(i) + ": " + hopTable[i] + " MHz\n");
|
||||
}*/
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
void updateFrequency()
|
||||
{
|
||||
unsigned long currentTime = millis();
|
||||
|
||||
if (currentTime - lastHopTime >= dwellTime)
|
||||
{
|
||||
if (hopIndex == numChannels)
|
||||
{
|
||||
hopIndex = 0;
|
||||
}
|
||||
hopIndex = hopIndex + 1;
|
||||
// packetNumber = hopIndex;
|
||||
currentFreq = hopTable[hopIndex];
|
||||
radio.setFrequency(hopTable[hopIndex]);
|
||||
|
||||
lastHopTime = currentTime;
|
||||
}
|
||||
}
|
||||
36
radio/FHSS.h
Normal file
36
radio/FHSS.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#include "config.h"
|
||||
#include <Arduino.h>
|
||||
#include <LiLyGo.h>
|
||||
#include <LoRaBoards.h>
|
||||
|
||||
#define SYNC_FREQUENCY 915.000
|
||||
|
||||
#define MAX_HOP_CHANNELS 5000 // 20 MHz range with 10 kHz step
|
||||
#define PACKET_SEND_DURATION 1 * 60 * 1000 // 1 minutes in milliseconds
|
||||
|
||||
extern float hopTable[MAX_HOP_CHANNELS];
|
||||
extern uint64_t packetNumber;
|
||||
extern long int receivedPacketCounter;
|
||||
|
||||
extern uint32_t syncWord; // Example sync word (can be any 32-bit value)
|
||||
extern int numChannels;
|
||||
|
||||
// Get the next frequency from the hopping table
|
||||
extern int hopIndex;
|
||||
extern unsigned long lastHopTime;
|
||||
extern unsigned long dwellTime; // 500ms dwell time
|
||||
extern float currentFreq;
|
||||
|
||||
extern uint32_t syncWord; // Example sync word
|
||||
extern float maxWidthMHz; // Max hopping width of 20 MHz
|
||||
extern float startFreq; // Start at 900 MHz
|
||||
extern float stepKHz; // 10 kHz step size
|
||||
|
||||
// Function to generate a frequency hopping table, adapting if channels are fewer
|
||||
int generateFrequencies(uint32_t syncWord, float startFreq, float stepKHz,
|
||||
float maxWidthMHz);
|
||||
|
||||
// Function to print the generated table (for debugging)
|
||||
void printHopTable(int numChannels);
|
||||
|
||||
void updateFrequency();
|
||||
125
radio/MavLink.h
Normal file
125
radio/MavLink.h
Normal file
@@ -0,0 +1,125 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
// Include MAVLink headers.
|
||||
// Adjust the include path to match your local mavlink library structure.
|
||||
#include "mavlink.h"
|
||||
|
||||
// Which UART on ESP32? For example, Serial2 can be mapped to pins:
|
||||
constexpr int RX_PIN = 16; // Flight Controller TX -> ESP32 RX
|
||||
constexpr int TX_PIN = 17; // Flight Controller RX <- ESP32 TX
|
||||
constexpr long MAVLINK_BAUD = 57600; // Common default for MAVLink
|
||||
|
||||
// Identify our local system/component, and the target FC system
|
||||
// (ArduPilot often uses sysid=1, compid=1, but it can vary).
|
||||
constexpr uint8_t MAV_COMP_ID_ONBOARD = 1; // or 190 for companion computer
|
||||
constexpr uint8_t MAV_SYS_ID_ONBOARD = 255; // e.g. 255 for companion
|
||||
constexpr uint8_t TARGET_SYS_ID = 1; // autopilot system ID
|
||||
constexpr uint8_t TARGET_COMP_ID = 1; // autopilot component ID
|
||||
|
||||
// We'll send 8 channels. MAVLink 2 can handle up to 18 channels in
|
||||
// set_rc_channels_override, but typical autopilots read at least 8 or 14 channels.
|
||||
static uint16_t rcChannels[8] = {
|
||||
1500, // Channel 1
|
||||
1500, // Channel 2
|
||||
1500, // Channel 3
|
||||
1500, // Channel 4
|
||||
1000, // Channel 5
|
||||
1000, // Channel 6
|
||||
1000, // Channel 7
|
||||
1000 // Channel 8
|
||||
};
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
delay(2000);
|
||||
Serial.println("MAVLink RC Override Example - ESP32");
|
||||
|
||||
// Initialize Serial2 on given pins
|
||||
Serial2.begin(MAVLINK_BAUD, SERIAL_8N1, RX_PIN, TX_PIN);
|
||||
|
||||
// (Optional) Wait a bit for the autopilot to boot, if needed
|
||||
delay(2000);
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
// For demonstration, let's do a small "animation" on channel 1
|
||||
// to show that the flight controller is indeed receiving changes.
|
||||
// We'll move channel 1 from 1000 to 2000 in steps.
|
||||
static int ch1Value = 1000;
|
||||
static int increment = 10;
|
||||
|
||||
ch1Value += increment;
|
||||
if (ch1Value > 2000)
|
||||
{
|
||||
ch1Value = 2000;
|
||||
increment = -10;
|
||||
}
|
||||
else if (ch1Value < 1000)
|
||||
{
|
||||
ch1Value = 1000;
|
||||
increment = 10;
|
||||
}
|
||||
|
||||
// Update channel 1 in our array
|
||||
rcChannels[0] = ch1Value;
|
||||
|
||||
// Send the MAVLink Set RC Channels Override message
|
||||
sendRCChannelsOverride();
|
||||
|
||||
// Send ~20 times per second
|
||||
delay(50);
|
||||
}
|
||||
|
||||
void sendRCChannelsOverride()
|
||||
{
|
||||
mavlink_message_t msg;
|
||||
uint8_t buf[MAVLINK_MAX_PACKET_LEN];
|
||||
|
||||
// Fill out the message:
|
||||
// mavlink_msg_set_rc_channels_override_pack(
|
||||
// uint8_t system_id,
|
||||
// uint8_t component_id,
|
||||
// mavlink_message_t* msg,
|
||||
// uint8_t target_system,
|
||||
// uint8_t target_component,
|
||||
// uint16_t chan1_raw,
|
||||
// uint16_t chan2_raw,
|
||||
// ...
|
||||
// uint16_t chan8_raw,
|
||||
// uint16_t chan9_raw,
|
||||
// ...
|
||||
// up to chan18_raw
|
||||
// )
|
||||
// For 8 channels, pass 0 for unused channels > 8.
|
||||
|
||||
mavlink_msg_set_rc_channels_override_pack(
|
||||
MAV_SYS_ID_ONBOARD, MAV_COMP_ID_ONBOARD, &msg, TARGET_SYS_ID, TARGET_COMP_ID,
|
||||
rcChannels[0], // chan1
|
||||
rcChannels[1], // chan2
|
||||
rcChannels[2], // chan3
|
||||
rcChannels[3], // chan4
|
||||
rcChannels[4], // chan5
|
||||
rcChannels[5], // chan6
|
||||
rcChannels[6], // chan7
|
||||
rcChannels[7], // chan8
|
||||
0, 0, 0, 0, 0, 0, 0, 0 // for chan9..chan16 or up to chan18
|
||||
);
|
||||
|
||||
// Encode the message into the send buffer
|
||||
uint16_t len = mavlink_msg_to_send_buffer(buf, &msg);
|
||||
|
||||
// Write it out the serial port to the FC
|
||||
Serial2.write(buf, len);
|
||||
|
||||
// For debugging
|
||||
Serial.print("Sent RC override: [");
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
Serial.print(rcChannels[i]);
|
||||
if (i < 7)
|
||||
Serial.print(", ");
|
||||
}
|
||||
Serial.println("]");
|
||||
}
|
||||
67
radio/RadioCommands.h
Normal file
67
radio/RadioCommands.h
Normal file
@@ -0,0 +1,67 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
uint16_t testChannels[16] = {1500, 2000, 1350, 1400, 1505, 1506, 1507, 1508,
|
||||
1509, 1510, 1511, 1512, 1513, 1514, 1515, 1516};
|
||||
|
||||
/*
|
||||
commands sending message comments
|
||||
-----------------------------------------------------
|
||||
roll rc 1 <value> // move left or right
|
||||
pitch rc 2 <value> // move forward or backwards
|
||||
yaw rc 4 <value> // turn left or right
|
||||
throttle rc 3 <value> // move up or down
|
||||
*/
|
||||
enum Command
|
||||
{
|
||||
HEART_BEAT = 0, // Corresponds to rc 0
|
||||
ROLL = 1, // Corresponds to rc 1
|
||||
PITCH = 2, // Corresponds to rc 2
|
||||
THROTTLE = 3, // Corresponds to rc 3
|
||||
YAW = 4, // Corresponds to rc 4
|
||||
//// ----- Not Assigned Yet -----
|
||||
AUX1 = 5, // Corresponds to rc 5
|
||||
AUX2 = 6, // Corresponds to rc 6
|
||||
AUX3 = 7, // Corresponds to rc 7
|
||||
AUX4 = 8, // Corresponds to rc 8
|
||||
AUX5 = 9, // Corresponds to rc 9
|
||||
AUX6 = 10 // Corresponds to rc 10
|
||||
};
|
||||
|
||||
// Create a map from Command to string
|
||||
std::unordered_map<Command, String> commandToStringMap = {{HEART_BEAT, "HEART_BEAT"},
|
||||
{ROLL, "ROLL"},
|
||||
{PITCH, "PITCH"},
|
||||
{YAW, "YAW"},
|
||||
{THROTTLE, "THROTTLE"}};
|
||||
|
||||
// Define the mapping table
|
||||
std::vector<std::pair<uint8_t, uint16_t>> channelValueMappingTable = {
|
||||
{0, 1300}, {1, 1325}, {2, 1350}, {3, 1375}, {4, 1400}, {5, 1425},
|
||||
{6, 1450}, {7, 1475}, {8, 1500}, {9, 1525}, {10, 1550}, {11, 1575},
|
||||
{12, 1600}, {13, 1625}, {14, 1650}, {15, 1675}};
|
||||
// 0 - 1300 0
|
||||
// 1300 - 1325 1
|
||||
// 1325 - 1350 2
|
||||
// 1350 - 1375 3
|
||||
// 1375 - 1400 4
|
||||
// 1400 - 1425 5
|
||||
// 1425 - 1450 6
|
||||
// 1450 - 1475 7
|
||||
// 1475 - 1500 8
|
||||
// 1500 - 1525 9
|
||||
// 1525 - 1550 10
|
||||
// 1550 - 1575 11
|
||||
// 1575 - 1600 12
|
||||
// 1600 - 1625 13
|
||||
// 1625 - 1650 14
|
||||
// 1650 - 1675 15
|
||||
|
||||
#define INIT_SBUS_ARRAY \
|
||||
{1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, \
|
||||
1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500}
|
||||
28
radio/Readme.txt
Normal file
28
radio/Readme.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
1. Wiring:
|
||||
Connect the SBUS output from your receiver to the appropriate UART (Universal Asynchronous Receiver-Transmitter) port on your flight controller. This is usually labeled as RX or SBUS on the flight controller.
|
||||
2. Betaflight Configuration:
|
||||
Connect to Betaflight Configurator: Use the Betaflight Configurator software to connect to your flight controller via USB.
|
||||
Ports Tab: In the Configurator, navigate to the "Ports" tab. Enable the UART port where your SBUS receiver is connected. Set the port to "Serial RX".
|
||||
Configuration Tab: Go to the "Configuration" tab. Under "Receiver", select "Serial-based receiver" and then choose "SBUS" from the protocol dropdown menu.
|
||||
Save and Reboot: After making these changes, save the configuration and reboot the flight controller.
|
||||
3. Testing:
|
||||
After configuration, test the setup by moving the sticks on your transmitter and observing the response in the Betaflight Configurator's "Receiver" tab. The channels should move according to your stick inputs.
|
||||
|
||||
|
||||
Steps to Connect to Betaflight via Browser
|
||||
1. Download Betaflight Configurator:
|
||||
Visit the Betaflight Configurator releases page on GitHub.
|
||||
https://github.com/betaflight/betaflight-configurator/releases
|
||||
Download the appropriate version for your operating system (Windows, macOS, or Linux).
|
||||
|
||||
go to Port -> select port -> check Serial RX
|
||||
|
||||
Go to
|
||||
|
||||
# SBUS tested
|
||||
|
||||
ESP32 39 -> SBUS(R2) Any of them
|
||||
GND -> G
|
||||
Article explains : https://speedybee.zendesk.com/hc/en-us/articles/19968381088795-How-to-set-up-your-SBUS-receiver-in-Betaflight-configurator-on-SpeedyBee-F405MINI-flight-controller
|
||||
|
||||
|
||||
63
radio/SBUS.h
Normal file
63
radio/SBUS.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#include "config.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
#define SBUS_CHANNELS 16
|
||||
#define SBUS_PACKET_SIZE 25
|
||||
|
||||
class SBUS2
|
||||
{
|
||||
public:
|
||||
SBUS2(HardwareSerial &serialPort) : sbusSerial(serialPort)
|
||||
{
|
||||
sbusSerial.begin(100000, SERIAL_8E2, -1, TXD1, true); // Inverted SBUS signal
|
||||
}
|
||||
|
||||
void send(uint16_t channels[SBUS_CHANNELS])
|
||||
{
|
||||
uint8_t sbusPacket[SBUS_PACKET_SIZE] = {0};
|
||||
|
||||
sbusPacket[0] = 0x0F; // SBUS Start Byte
|
||||
|
||||
// Correctly pack 16 channels (11-bit each) into 22 bytes
|
||||
uint16_t packedData[SBUS_CHANNELS] = {0};
|
||||
for (int i = 0; i < SBUS_CHANNELS; i++)
|
||||
{
|
||||
packedData[i] = channels[i] & 0x07FF; // Ensure 11-bit limit
|
||||
}
|
||||
|
||||
sbusPacket[1] = (packedData[0] & 0xFF);
|
||||
sbusPacket[2] = ((packedData[0] >> 8) | (packedData[1] << 3)) & 0xFF;
|
||||
sbusPacket[3] = ((packedData[1] >> 5) | (packedData[2] << 6)) & 0xFF;
|
||||
sbusPacket[4] = (packedData[2] >> 2) & 0xFF;
|
||||
sbusPacket[5] = ((packedData[2] >> 10) | (packedData[3] << 1)) & 0xFF;
|
||||
sbusPacket[6] = ((packedData[3] >> 7) | (packedData[4] << 4)) & 0xFF;
|
||||
sbusPacket[7] = ((packedData[4] >> 4) | (packedData[5] << 7)) & 0xFF;
|
||||
sbusPacket[8] = (packedData[5] >> 1) & 0xFF;
|
||||
sbusPacket[9] = ((packedData[5] >> 9) | (packedData[6] << 2)) & 0xFF;
|
||||
sbusPacket[10] = ((packedData[6] >> 6) | (packedData[7] << 5)) & 0xFF;
|
||||
sbusPacket[11] = (packedData[7] >> 3) & 0xFF;
|
||||
sbusPacket[12] = (packedData[8] & 0xFF);
|
||||
sbusPacket[13] = ((packedData[8] >> 8) | (packedData[9] << 3)) & 0xFF;
|
||||
sbusPacket[14] = ((packedData[9] >> 5) | (packedData[10] << 6)) & 0xFF;
|
||||
sbusPacket[15] = (packedData[10] >> 2) & 0xFF;
|
||||
sbusPacket[16] = ((packedData[10] >> 10) | (packedData[11] << 1)) & 0xFF;
|
||||
sbusPacket[17] = ((packedData[11] >> 7) | (packedData[12] << 4)) & 0xFF;
|
||||
sbusPacket[18] = ((packedData[12] >> 4) | (packedData[13] << 7)) & 0xFF;
|
||||
sbusPacket[19] = (packedData[13] >> 1) & 0xFF;
|
||||
sbusPacket[20] = ((packedData[13] >> 9) | (packedData[14] << 2)) & 0xFF;
|
||||
sbusPacket[21] = ((packedData[14] >> 6) | (packedData[15] << 5)) & 0xFF;
|
||||
sbusPacket[22] = (packedData[15] >> 3) & 0xFF;
|
||||
|
||||
// Flags: Failsafe & Frame Lost
|
||||
sbusPacket[23] = 0x00; // Set failsafe/lost frame bits here if needed
|
||||
|
||||
// End byte
|
||||
sbusPacket[24] = 0x00; // Must be 0x00 or 0x04 if failsafe active
|
||||
|
||||
// Send SBUS packet
|
||||
sbusSerial.write(sbusPacket, SBUS_PACKET_SIZE);
|
||||
}
|
||||
|
||||
private:
|
||||
HardwareSerial &sbusSerial;
|
||||
};
|
||||
381
radio/Tx.cpp
Normal file
381
radio/Tx.cpp
Normal file
@@ -0,0 +1,381 @@
|
||||
#include "USB-Serial.h"
|
||||
#include "config.h"
|
||||
#include "radio-protocol.h"
|
||||
#include "utility.h"
|
||||
#include <Arduino.h>
|
||||
#include <FreeRTOS.h>
|
||||
#include <cmath>
|
||||
#include <esp_system.h>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
// Support heltec boards
|
||||
#ifndef LILYGO
|
||||
#include <heltec_unofficial.h>
|
||||
#endif // end ifndef LILYGO
|
||||
|
||||
#if defined(LILYGO)
|
||||
// LiLyGO device does not support the auto download mode, you need to get into the
|
||||
// download mode manually. To do so, press and hold the BOOT button and then press the
|
||||
// RESET button once. After that release the BOOT button. Or OFF->ON together with BOOT
|
||||
|
||||
// Default LilyGO code
|
||||
#include <LoRaBoards.h>
|
||||
|
||||
// #include "utilities.h"
|
||||
// Our Code
|
||||
#include <LiLyGo.h>
|
||||
#endif // end LILYGO
|
||||
|
||||
#define RADIOLIB_GODMODE (1)
|
||||
#define RADIOLIB_CHECK_PARAMS (0)
|
||||
|
||||
#include <RadioLib.h>
|
||||
|
||||
// Define the UART ports and pins
|
||||
#define TXD1 39 // Transmit pin for Serial1
|
||||
#define RXD2 40 // Receive pin for Serial2
|
||||
|
||||
long int lastWriteTime = 0;
|
||||
|
||||
#if PROTOCOL == IBUS
|
||||
#include "IBUS.h"
|
||||
// Create an instance of the Ibus class
|
||||
Ibus ibus;
|
||||
// Test data list with all control values set to 1700
|
||||
uint8_t testControlValues[IBUS_CHANNELS_COUNT * 2];
|
||||
#endif
|
||||
|
||||
#if PROTOCOL == SBUS
|
||||
#include "SBUS.h"
|
||||
SBUS2 sbus(Serial1); // Use Serial1 for SBUS output
|
||||
#endif
|
||||
|
||||
#include "FHSS.h"
|
||||
|
||||
#if PROTOCOL == CRSF
|
||||
#include "CRSF.h"
|
||||
// CRSF crsf(Serial1, TXD1, TXD1, 420000); // Use Serial1, TX_PIN, RX_PIN, BAUD_RATE
|
||||
CRSF2 crsf(Serial1, -1, TXD1);
|
||||
#endif
|
||||
// Example usage
|
||||
|
||||
#include "radio.h"
|
||||
|
||||
#if RUN_TESTS
|
||||
void testMap11BitTo4Bit();
|
||||
void testMap4BitTo11Bit();
|
||||
#endif
|
||||
|
||||
long int startTime = 0;
|
||||
void setup()
|
||||
{
|
||||
|
||||
Serial.begin(115200);
|
||||
|
||||
// Initialize Serial1 for iBUS communication with a custom TX pin
|
||||
#if PROTOCOL == IBUS
|
||||
ibus.begin(Serial1, TXD1);
|
||||
ibus.enable();
|
||||
#endif
|
||||
|
||||
#if PROTOCOL == SBUS
|
||||
// clearSbusData();
|
||||
#if LORA_RX
|
||||
// sbusWrite.Begin();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if PROTOCOL == CRSF
|
||||
crsf.begin();
|
||||
#endif // end CRSF
|
||||
|
||||
#if RUN_TESTS
|
||||
testMap11BitTo4Bit();
|
||||
testMap4BitTo11Bit();
|
||||
#endif
|
||||
// testMap11BitTo4Bit();
|
||||
// Initialize SBUS communication
|
||||
|
||||
#if LORA_TX
|
||||
// sbusRead.Begin();
|
||||
#endif
|
||||
Serial.println("SBUS write and read are ready");
|
||||
|
||||
heltec_setup();
|
||||
startTime = millis();
|
||||
|
||||
numChannels = generateFrequencies(syncWord, startFreq, stepKHz, maxWidthMHz);
|
||||
|
||||
delay(100);
|
||||
Serial.println("------");
|
||||
Serial.println("Generated Frequency Hopping Table [" + String(numChannels) + "]:");
|
||||
for (int i = 0; i < numChannels; i++)
|
||||
{
|
||||
// Serial.println(String(i) + ": " + hopTable[i] + " MHz\n");
|
||||
}
|
||||
delay(1000);
|
||||
|
||||
printHopTable(numChannels); // Print the generated table
|
||||
|
||||
#ifdef LILYGO
|
||||
setupBoards(); // true for disable U8g2 display library
|
||||
delay(200);
|
||||
Serial.println("Setup LiLyGO board is done");
|
||||
display.println("Setup LiLyGO board is done");
|
||||
#endif
|
||||
/// beginGFSK
|
||||
if (radio.begin() == RADIOLIB_ERR_NONE)
|
||||
{
|
||||
Serial.println("LoRa Initialized");
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("LoRa Initialization Failed!");
|
||||
while (true)
|
||||
;
|
||||
}
|
||||
|
||||
// Setup Radio
|
||||
setupRadio();
|
||||
}
|
||||
|
||||
String toBinary(int num, int bitSize = 4);
|
||||
std::map<int, int> lastSerialCommands;
|
||||
void loop()
|
||||
{
|
||||
// Read serial input
|
||||
#if LORA_TX && SERIAL_INPUT
|
||||
Serial.println("Waiting for Serial Commands");
|
||||
while (true)
|
||||
{
|
||||
std::map<int, int> serialCommands = readSerialInput();
|
||||
if (serialCommands.empty())
|
||||
{
|
||||
// Serial.println("Waiting for Serial Commands");
|
||||
delay(20);
|
||||
}
|
||||
else
|
||||
{
|
||||
lastSerialCommands = serialCommands;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if PROTOCOL == IBUS
|
||||
uint32_t seed = esp_random() ^ millis();
|
||||
randomSeed(seed);
|
||||
String str = "";
|
||||
// Set all control values to 1700
|
||||
for (int i = 0; i < IBUS_CHANNELS_COUNT; i++)
|
||||
{
|
||||
uint16_t randomValue =
|
||||
random(1200, 1900); // Generate random values between 1200 and 1900
|
||||
str += String(randomValue) + ",";
|
||||
testControlValues[i * 2] = randomValue & 0xFF; // Low byte
|
||||
testControlValues[i * 2 + 1] = (randomValue >> 8) & 0xFF; // High byte
|
||||
}
|
||||
Serial.println("I-BUS:" + str);
|
||||
display.println("I-BUS:" + str);
|
||||
|
||||
ibus.setControlValuesList(testControlValues);
|
||||
ibus.sendPacket();
|
||||
delay(20);
|
||||
#endif // end IBUS
|
||||
|
||||
#if PROTOCOL == SBUS
|
||||
uint32_t seed = esp_random() ^ millis();
|
||||
|
||||
randomSeed(seed);
|
||||
// Set all control values to 1700
|
||||
|
||||
uint16_t sbusSend[16] = INIT_SBUS_ARRAY;
|
||||
|
||||
for (int i = 0; i < 12; i++)
|
||||
{
|
||||
uint16_t randomValue = random(1200, 1900);
|
||||
sbusSend[i] = randomValue; // map4BitTo11Bit(randomValue);
|
||||
}
|
||||
// writeSbusData(sbusSend);
|
||||
sbus.send(sbusSend);
|
||||
display.println("SBUS");
|
||||
Serial.println("SBUS: " + String(sbusSend[0]) + "," + String(sbusSend[1]) + "," +
|
||||
String(sbusSend[2]) + "," + String(sbusSend[3]) + "," +
|
||||
String(sbusSend[4]) + "," + String(sbusSend[5]) + "," +
|
||||
String(sbusSend[6]) + "," + String(sbusSend[7]) + "," +
|
||||
String(sbusSend[8]) + "," + String(sbusSend[9]) + "," +
|
||||
String(sbusSend[10]) + "," + String(sbusSend[11]));
|
||||
delay(30);
|
||||
// Read data for test purpose
|
||||
// readSbusData();
|
||||
#endif // end SBUS
|
||||
|
||||
#if PROTOCOL == CRSF
|
||||
display.println("CRSF");
|
||||
uint32_t seed = esp_random() ^ millis();
|
||||
|
||||
randomSeed(seed);
|
||||
// Set all control values to 1700
|
||||
|
||||
uint16_t sbusSend[16] = INIT_SBUS_ARRAY;
|
||||
// Example: Set channel values
|
||||
uint16_t channels[CRSF_MAX_CHANNELS] = {random(1200, 1900),
|
||||
random(1200, 1900),
|
||||
random(1200, 1900),
|
||||
random(1200, 1900),
|
||||
random(1200, 1900),
|
||||
random(1200, 1900),
|
||||
random(1200, 1900),
|
||||
1435,
|
||||
1450,
|
||||
1800,
|
||||
1300,
|
||||
1000,
|
||||
1500,
|
||||
2000,
|
||||
1200,
|
||||
1300};
|
||||
|
||||
crsf.sendRCFrame(channels);
|
||||
Serial.println("CRSF: " + String(channels[0]) + "," + String(channels[1]) + "," +
|
||||
String(channels[2]) + "," + String(channels[3]));
|
||||
delay(50);
|
||||
#endif // end CRSF
|
||||
|
||||
uint8_t cmd1 = 0; // Example command 1
|
||||
uint8_t val1 = 5; // Example value 1
|
||||
uint8_t cmd2 = 1; // Example command 2
|
||||
uint8_t val2 = 10; // Example value 2
|
||||
uint8_t val3 = 0, val4 = 0;
|
||||
uint8_t cmd3 = 2, cmd4 = 3;
|
||||
long int start = millis();
|
||||
#if LORA_FHSS
|
||||
updateFrequency();
|
||||
#endif
|
||||
#if LORA_TX
|
||||
// listen to the Sbus commands form the Ground station or RC
|
||||
// readSbusData();
|
||||
|
||||
val1 = convertTo4Bit(testChannels[4]);
|
||||
val2 = convertTo4Bit(testChannels[1]);
|
||||
// 4 byte packet
|
||||
val3 = convertTo4Bit(testChannels[2]);
|
||||
val4 = convertTo4Bit(testChannels[3]);
|
||||
if (val3 != 8 && val4 != 8)
|
||||
{
|
||||
radio.setBandwidth(62.5);
|
||||
// RC 1 1500
|
||||
sendLoRaRCPacket(cmd1, val1, cmd2, val2, cmd3, val3, cmd4, val4);
|
||||
delay(500);
|
||||
radio.setBandwidth(500);
|
||||
sendLoRaRCPacket(cmd1, val1, cmd2, val2, cmd3, val3, cmd4, val4);
|
||||
}
|
||||
else
|
||||
{
|
||||
radio.setBandwidth(62.5);
|
||||
sendLoRaRCPacket(cmd1, val1, cmd2, val2);
|
||||
delay(500);
|
||||
radio.setBandwidth(500);
|
||||
sendLoRaRCPacket(cmd1, val1, cmd2, val2);
|
||||
}
|
||||
|
||||
packetNumber++;
|
||||
|
||||
if (packetNumber % 10 == 0)
|
||||
{
|
||||
// 8 B
|
||||
uint8_t data8[8];
|
||||
data8[0] = (packetNumber >> 56) & 0xFF;
|
||||
data8[1] = (packetNumber >> 48) & 0xFF;
|
||||
data8[2] = (packetNumber >> 40) & 0xFF;
|
||||
data8[3] = (packetNumber >> 32) & 0xFF;
|
||||
data8[4] = (packetNumber >> 24) & 0xFF;
|
||||
data8[5] = (packetNumber >> 16) & 0xFF;
|
||||
data8[6] = (packetNumber >> 8) & 0xFF;
|
||||
data8[7] = packetNumber & 0xFF;
|
||||
packetNumber++;
|
||||
|
||||
Serial.println("Sending 8 byte packet Number(" + String(sizeof(data8)) + ")");
|
||||
sendLoRaPacket(data8, 8);
|
||||
heltec_delay(50);
|
||||
}
|
||||
|
||||
#endif
|
||||
#if LORA_RX
|
||||
if (packetReceived)
|
||||
{
|
||||
packetReceived = false;
|
||||
onReceive();
|
||||
}
|
||||
/*if (millis() - lastPacketTime > 15000)
|
||||
{ // No packet for 15s? Reset.
|
||||
Serial.println("[LoRa] No packets received for a while, restarting...");
|
||||
forceRestartLoRa();
|
||||
}*/
|
||||
// Serial.print("[LoRa] Current Status: ");
|
||||
#endif
|
||||
|
||||
long int end = millis();
|
||||
|
||||
#if LORA_TX && LORA_FHSS
|
||||
long int currentTime = millis();
|
||||
if (currentTime - startTime < PACKET_SEND_DURATION)
|
||||
{ // Check if within first 5 minutes
|
||||
char packetData[LORA_DATA_BYTE]; // 2-byte array
|
||||
|
||||
// Store packet number into 2 bytes (big-endian format)
|
||||
packetData[0] = (packetNumber >> 8) & 0xFF; // High byte
|
||||
packetData[1] = packetNumber & 0xFF; // Low byte
|
||||
radio.setSpreadingFactor(5);
|
||||
radio.setBandwidth(125.0);
|
||||
|
||||
radio.transmit((uint8_t *)packetData, 2); // Send exactly 2 bytes
|
||||
Serial.printf("Sent: %s on %.3f MHz\n", packetData, hopTable[hopIndex]);
|
||||
}
|
||||
radio.setSpreadingFactor(LORA_SF);
|
||||
radio.setBandwidth(LORA_BW);
|
||||
#endif
|
||||
|
||||
#if LORA_TX
|
||||
|
||||
#if LORA_FHSS
|
||||
Serial.printf("Hopping [%s] to: %.3f MHz\n", String(packetNumber), currentFreq);
|
||||
display.printf("FHSS: %.3fMHz\n", currentFreq);
|
||||
#endif
|
||||
Serial.println("Packet: " + String(packetSave));
|
||||
|
||||
display.println("Time in the Air: " + String((end - start)));
|
||||
Serial.println("Time in the Air: " + String((end - start)));
|
||||
|
||||
display.println("P:" + String(cmd1) + ":" + String(val1) + ":" + String(cmd2) + ":" +
|
||||
String(val2));
|
||||
display.println("BP:" + toBinary(cmd1) + ":" + toBinary(val1) + ":" + toBinary(cmd2) +
|
||||
":" + toBinary(val2));
|
||||
|
||||
Serial.println("P:" + String(cmd1) + ":" + String(val1) + ":" + String(cmd2) + ":" +
|
||||
String(val2));
|
||||
Serial.println("BP:" + toBinary(cmd1) + ":" + toBinary(val1) + ":" + toBinary(cmd2) +
|
||||
":" + toBinary(val2));
|
||||
|
||||
if (val3 != 8 && val4 != 8)
|
||||
{
|
||||
Serial.println("P2:" + String(cmd3) + ":" + String(val3) + ":" + String(cmd4) +
|
||||
":" + String(val4));
|
||||
Serial.println("BP2:" + toBinary(cmd3) + ":" + toBinary(val3) + ":" +
|
||||
toBinary(cmd4) + ":" + toBinary(val4));
|
||||
}
|
||||
// display.println("Packet Sent");
|
||||
// delay(1000);
|
||||
#endif
|
||||
}
|
||||
|
||||
// this function is called when a complete packet
|
||||
// is received by the module
|
||||
// IMPORTANT: this function MUST be 'void' type
|
||||
// and MUST NOT have any arguments!
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
ICACHE_RAM_ATTR
|
||||
#endif
|
||||
96
radio/USB-Serial.h
Normal file
96
radio/USB-Serial.h
Normal file
@@ -0,0 +1,96 @@
|
||||
#include <Arduino.h>
|
||||
#include <map>
|
||||
|
||||
std::map<int, int> readSerialInput();
|
||||
void dumpCommand(const std::map<int, int> &map);
|
||||
std::map<int, int> processSerialCommand(const String &input);
|
||||
|
||||
std::map<int, int> readSerialInput()
|
||||
{
|
||||
std::map<int, int> result;
|
||||
String input = "";
|
||||
if (Serial.available() > 0)
|
||||
{
|
||||
input = Serial.readStringUntil(
|
||||
'\n'); // Read the incoming data until a newline character
|
||||
input.trim(); // Remove any leading or trailing whitespace
|
||||
}
|
||||
|
||||
if (!input.isEmpty())
|
||||
{
|
||||
Serial.println("SERIAL Data: " + input);
|
||||
delay(1000);
|
||||
result = processSerialCommand(input);
|
||||
}
|
||||
|
||||
if (!result.empty())
|
||||
{
|
||||
Serial.println("The map contains data: ");
|
||||
dumpCommand(result);
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
return result; // Return an empty string if no data is available
|
||||
}
|
||||
|
||||
void dumpCommand(const std::map<int, int> &map)
|
||||
{
|
||||
for (const auto &pair : map)
|
||||
{
|
||||
Serial.println("Key: " + String(pair.first) + ", Value: " + String(pair.second));
|
||||
}
|
||||
}
|
||||
|
||||
std::map<int, int> processSerialCommand(const String &input)
|
||||
{
|
||||
const int MAX_KEYS = 6;
|
||||
|
||||
std::map<int, int> keyValuePairs;
|
||||
if (input.startsWith("RC "))
|
||||
{
|
||||
int key1 = -1, value1 = -1;
|
||||
int key2 = -1, value2 = -1;
|
||||
int key3 = -1, value3 = -1;
|
||||
int key4 = -1, value4 = -1;
|
||||
int key5 = -1, value5 = -1;
|
||||
int key6 = -1, value6 = -1;
|
||||
|
||||
char command[3];
|
||||
int parsed = sscanf(input.c_str(), "%s %d %d %d %d %d %d %d %d %d %d %d %d",
|
||||
command, &key1, &value1, &key2, &value2, &key3, &value3,
|
||||
&key4, &value4, &key5, &value5, &key6, &value6);
|
||||
|
||||
// Create arrays to hold keys and values
|
||||
int keys[MAX_KEYS] = {key1, key2, key3, key4, key5, key6};
|
||||
int values[MAX_KEYS] = {value1, value2, value3, value4, value5, value6};
|
||||
|
||||
if (parsed >= 3)
|
||||
{
|
||||
Serial.print("Received command: ");
|
||||
Serial.print(command);
|
||||
Serial.print(" ");
|
||||
for (int i = 0; i < parsed - 2; i++)
|
||||
{
|
||||
if (values[i] != -1)
|
||||
{
|
||||
keyValuePairs[keys[i]] = values[i];
|
||||
}
|
||||
Serial.print("Key[" + String(i) + "]: ");
|
||||
Serial.print(keys[i]);
|
||||
Serial.print(" Value[" + String(i) + "]: ");
|
||||
Serial.print(String(values[i]) + ", ");
|
||||
}
|
||||
Serial.println(" ");
|
||||
Serial.println(">--------------------------------<");
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("Invalid command format or out of range values.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("Invalid command prefix.");
|
||||
}
|
||||
return keyValuePairs;
|
||||
}
|
||||
75
radio/config.h
Normal file
75
radio/config.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#define RUN_TESTS 0
|
||||
|
||||
#define TXD1 39
|
||||
#define RXD2 40
|
||||
// SBUS packet structure
|
||||
#define SBUS_PACKET_SIZE 25
|
||||
|
||||
#ifndef LORA_SF
|
||||
// Sets LoRa spreading factor. Allowed values range from 5 to 12.
|
||||
#define LORA_SF 8 // For retranmission 6.
|
||||
#endif
|
||||
|
||||
#ifndef LORA_CR
|
||||
#define LORA_CR 8
|
||||
#endif
|
||||
|
||||
#ifndef DEBUG_RX
|
||||
#define DEBUG_RX 0
|
||||
#endif
|
||||
|
||||
#ifndef LORA_HEADER
|
||||
#define LORA_HEADER 0
|
||||
#endif
|
||||
|
||||
#ifndef LORA_FHSS
|
||||
#define LORA_FHSS 0
|
||||
#endif
|
||||
|
||||
#ifndef LORA_RX
|
||||
#define LORA_RX 0
|
||||
#endif
|
||||
|
||||
#ifndef LORA_TX
|
||||
#define LORA_TX 1
|
||||
#endif
|
||||
|
||||
#ifndef LORA_BASE_FREQ
|
||||
// Sets LoRa coding rate denominator. Allowed values range from 5 to 8.
|
||||
#define LORA_BASE_FREQ 915
|
||||
#endif
|
||||
|
||||
#ifndef LORA_BW
|
||||
// Sets LoRa bandwidth. Allowed values are 62.5, 125.0, 250.0 and 500.0 kHz. (default,
|
||||
// high = false)
|
||||
#define LORA_BW 62.5 // 31.25 // 125.0 // 62.5 // 31.25 // 500
|
||||
#endif
|
||||
|
||||
#ifndef LORA_DATA_BYTE
|
||||
#define LORA_DATA_BYTE 2
|
||||
#endif
|
||||
|
||||
#define SBUS 1
|
||||
#define IBUS 2
|
||||
#define CRSF 3 // Doesn't work
|
||||
|
||||
#ifndef PROTOCOL
|
||||
#define PROTOCOL CRSF // CRSF // IBUS // SBUS
|
||||
#endif
|
||||
|
||||
#ifndef SERIAL_INPUT
|
||||
#define SERIAL_INPUT 0
|
||||
#endif
|
||||
|
||||
#ifndef LORA_PREAMBLE
|
||||
// 8 is default
|
||||
#if LORA_SF == 6 || LORA_SF == 5
|
||||
#define LORA_PREAMBLE 8
|
||||
#elif LORA_SF == 7
|
||||
#define LORA_PREAMBLE 8
|
||||
#else
|
||||
#define LORA_PREAMBLE 10
|
||||
#endif
|
||||
#endif
|
||||
161
radio/iBUS.h
Normal file
161
radio/iBUS.h
Normal file
@@ -0,0 +1,161 @@
|
||||
#include <Arduino.h>
|
||||
#include <HardwareSerial.h>
|
||||
// Define constants for iBUS
|
||||
#define IBUS_BAUD_RATE 115200
|
||||
#define IBUS_SEND_INTERVAL_MS 20
|
||||
#define IBUS_CHANNELS_COUNT 14
|
||||
#define IBUS_PACKET_BYTES_COUNT ((IBUS_CHANNELS_COUNT * 2) + 4)
|
||||
|
||||
// Define the custom TX pin
|
||||
#define CUSTOM_TX_PIN 17 // Change this to your desired TX pin
|
||||
|
||||
// Define the Ibus class
|
||||
class Ibus
|
||||
{
|
||||
public:
|
||||
void begin(HardwareSerial &serial, int txPin);
|
||||
void loop();
|
||||
void enable();
|
||||
void disable();
|
||||
void readLoop();
|
||||
void sendPacket();
|
||||
bool unpackIbusData(uint8_t *packet);
|
||||
void setControlValue(uint8_t channel, uint8_t value);
|
||||
void setControlValuesList(uint8_t list[IBUS_CHANNELS_COUNT * 2]);
|
||||
|
||||
private:
|
||||
HardwareSerial *serial;
|
||||
uint8_t controlValuesList[IBUS_CHANNELS_COUNT * 2] = {0};
|
||||
bool isEnabled = false;
|
||||
unsigned long previousMillis = 0;
|
||||
unsigned long currentMillis = 0;
|
||||
|
||||
uint8_t *createPacket();
|
||||
};
|
||||
|
||||
// Implement the Ibus methods
|
||||
void Ibus::begin(HardwareSerial &serial, int txPin)
|
||||
{
|
||||
this->serial = &serial;
|
||||
this->serial->begin(IBUS_BAUD_RATE, SERIAL_8N1, -1, txPin); // Set custom TX pin
|
||||
}
|
||||
|
||||
void Ibus::loop()
|
||||
{
|
||||
if (this->isEnabled)
|
||||
{
|
||||
this->sendPacket();
|
||||
// this->readLoop();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t *Ibus::createPacket()
|
||||
{
|
||||
static uint8_t packetBytesList[IBUS_PACKET_BYTES_COUNT];
|
||||
packetBytesList[0] = 0x20;
|
||||
packetBytesList[1] = 0x40;
|
||||
uint_fast16_t checksum = 0xFFFF - 0x20 - 0x40;
|
||||
for (size_t i = 2; i < (IBUS_CHANNELS_COUNT * 2) + 2; i++)
|
||||
{
|
||||
packetBytesList[i] = this->controlValuesList[i - 2];
|
||||
checksum -= packetBytesList[i];
|
||||
}
|
||||
packetBytesList[IBUS_PACKET_BYTES_COUNT - 2] = lowByte(checksum);
|
||||
packetBytesList[IBUS_PACKET_BYTES_COUNT - 1] = highByte(checksum);
|
||||
return packetBytesList;
|
||||
}
|
||||
|
||||
void Ibus::sendPacket()
|
||||
{
|
||||
if (this->isEnabled)
|
||||
{
|
||||
uint8_t *packetBytesList = this->createPacket();
|
||||
for (size_t i = 0; i < IBUS_PACKET_BYTES_COUNT; i++)
|
||||
{
|
||||
this->serial->write(packetBytesList[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Ibus::enable() { this->isEnabled = true; }
|
||||
|
||||
void Ibus::disable() { this->isEnabled = false; }
|
||||
|
||||
void Ibus::setControlValuesList(uint8_t list[IBUS_CHANNELS_COUNT * 2])
|
||||
{
|
||||
for (size_t i = 0; i < (IBUS_CHANNELS_COUNT * 2); i++)
|
||||
{
|
||||
this->controlValuesList[i] = list[i];
|
||||
}
|
||||
}
|
||||
|
||||
void Ibus::setControlValue(uint8_t channel, uint8_t value)
|
||||
{
|
||||
this->controlValuesList[channel] = value;
|
||||
}
|
||||
|
||||
bool Ibus::unpackIbusData(uint8_t *packet)
|
||||
{
|
||||
// Verify start and length bytes
|
||||
if (packet[0] != 0x20 || packet[1] != 0x40)
|
||||
{
|
||||
return false; // Invalid packet
|
||||
}
|
||||
|
||||
// Calculate checksum
|
||||
uint_fast16_t checksum = 0xFFFF;
|
||||
for (int i = 0; i < IBUS_PACKET_BYTES_COUNT - 2; i++)
|
||||
{
|
||||
checksum -= packet[i];
|
||||
}
|
||||
|
||||
// Verify checksum
|
||||
uint_fast16_t receivedChecksum =
|
||||
packet[IBUS_PACKET_BYTES_COUNT - 2] | (packet[IBUS_PACKET_BYTES_COUNT - 1] << 8);
|
||||
if (checksum != receivedChecksum)
|
||||
{
|
||||
return false; // Checksum mismatch
|
||||
}
|
||||
|
||||
// Extract channel values
|
||||
for (int i = 0; i < IBUS_CHANNELS_COUNT; i++)
|
||||
{
|
||||
this->controlValuesList[i] = packet[2 + i * 2] | (packet[3 + i * 2] << 8);
|
||||
}
|
||||
|
||||
return true; // Successfully unpacked
|
||||
}
|
||||
|
||||
void Ibus::readLoop()
|
||||
{
|
||||
uint8_t ibusPacket[IBUS_PACKET_BYTES_COUNT];
|
||||
int packetIndex = 0;
|
||||
while (this->serial->available())
|
||||
{
|
||||
uint8_t byte = this->serial->read();
|
||||
|
||||
// Store byte in packet buffer
|
||||
ibusPacket[packetIndex++] = byte;
|
||||
|
||||
// Check if we have a full packet
|
||||
if (packetIndex == IBUS_PACKET_BYTES_COUNT)
|
||||
{
|
||||
if (unpackIbusData(ibusPacket))
|
||||
{
|
||||
Serial.println("iBUS Data Unpacked:");
|
||||
for (int i = 0; i < IBUS_CHANNELS_COUNT; i++)
|
||||
{
|
||||
Serial.print("Channel ");
|
||||
Serial.print(i + 1);
|
||||
Serial.print(": ");
|
||||
Serial.println(this->controlValuesList[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("Failed to unpack iBUS data.");
|
||||
}
|
||||
packetIndex = 0; // Reset for next packet
|
||||
}
|
||||
}
|
||||
}
|
||||
153
radio/radio-protocol.h
Normal file
153
radio/radio-protocol.h
Normal file
@@ -0,0 +1,153 @@
|
||||
#include <Arduino.h>
|
||||
#include <RadioCommands.h>
|
||||
|
||||
uint8_t convertTo4Bit(uint16_t value11Bit);
|
||||
int16_t map4BitTo11Bit(uint8_t value4Bit);
|
||||
uint8_t map11BitTo4Bit(uint16_t value11Bit);
|
||||
|
||||
uint8_t convertTo4Bit(uint16_t value11Bit) { return map11BitTo4Bit(value11Bit); }
|
||||
|
||||
uint8_t map11BitTo4Bit(uint16_t value11Bit)
|
||||
{
|
||||
// Initialize variables to track the closest match
|
||||
uint8_t closest4BitValue = 0;
|
||||
uint16_t smallestDifference = UINT16_MAX;
|
||||
|
||||
const auto &lastEntry = channelValueMappingTable.back();
|
||||
// Find the closest match in the table
|
||||
for (const auto &entry : channelValueMappingTable)
|
||||
{
|
||||
|
||||
uint8_t key = entry.first; // Access the key
|
||||
uint16_t value = entry.second; // Access the value
|
||||
if (value11Bit >= lastEntry.second)
|
||||
{
|
||||
closest4BitValue = lastEntry.first;
|
||||
break;
|
||||
}
|
||||
else if (value11Bit <= value)
|
||||
{
|
||||
closest4BitValue = key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return closest4BitValue;
|
||||
}
|
||||
|
||||
int16_t map4BitTo11Bit(uint8_t value4Bit)
|
||||
{
|
||||
// Iterate through the mapping table to find the corresponding 11-bit value
|
||||
for (const auto &entry : channelValueMappingTable)
|
||||
{
|
||||
uint8_t key = entry.first; // Access the 4-bit key
|
||||
uint16_t value = entry.second; // Access the 11-bit value
|
||||
|
||||
if (key == value4Bit)
|
||||
{
|
||||
return value; // Return the 11-bit value if the key matches
|
||||
}
|
||||
}
|
||||
|
||||
// If no match is found, return a default value or handle the error
|
||||
// For example, return 0 - 1500 or throw an exception
|
||||
return 1500; // Or handle the error as needed
|
||||
}
|
||||
|
||||
// Test function
|
||||
void testMap11BitTo4Bit()
|
||||
{
|
||||
Serial.println("Test Mapping 11-bit to 4-bit");
|
||||
|
||||
// Test cases: {input, expected_output}
|
||||
std::vector<std::pair<uint16_t, uint8_t>> testCases = {
|
||||
{1290, 0}, {1300, 0}, {1310, 1}, {1325, 1}, {1340, 2},
|
||||
{1500, 8}, {1600, 12}, {1700, 15}, {1675, 15}, {1800, 15}};
|
||||
// 0 - 1300 0
|
||||
// 1300 - 1325 1
|
||||
// 1325 - 1350 2
|
||||
// 1350 - 1375 3
|
||||
// 1375 - 1400 4
|
||||
// 1400 - 1425 5
|
||||
// 1425 - 1450 6
|
||||
// 1450 - 1475 7
|
||||
// 1475 - 1500 8
|
||||
// 1500 - 1525 9
|
||||
// 1525 - 1550 10
|
||||
// 1550 - 1575 11
|
||||
// 1575 - 1600 12
|
||||
// 1600 - 1625 13
|
||||
// 1625 - 1650 14
|
||||
// 1650 - 1675 15
|
||||
bool failed = false;
|
||||
|
||||
for (const auto &testCase : testCases)
|
||||
{
|
||||
uint16_t input = testCase.first;
|
||||
uint8_t expectedOutput = testCase.second;
|
||||
uint8_t actualOutput = map11BitTo4Bit(input);
|
||||
bool assert = actualOutput == expectedOutput;
|
||||
if (!assert)
|
||||
{
|
||||
Serial.print("Test Failed->");
|
||||
failed = true;
|
||||
}
|
||||
Serial.println("Test input " + String(input) + ": expected " +
|
||||
String(expectedOutput) + ", got " + String(actualOutput));
|
||||
delay(100);
|
||||
}
|
||||
if (failed)
|
||||
{
|
||||
Serial.println("Test Failed");
|
||||
delay(500);
|
||||
}
|
||||
}
|
||||
|
||||
// Test function
|
||||
void testMap4BitTo11Bit()
|
||||
{
|
||||
Serial.println("Testing map4BitTo11Bit");
|
||||
// Test cases: {input, expected_output}
|
||||
std::vector<std::pair<uint8_t, uint16_t>> testCases = {
|
||||
{0, 1300}, {0, 1300}, {1, 1325}, {1, 1325}, {2, 1350},
|
||||
{8, 1500}, {12, 1600}, {15, 1675}, {14, 1650}, {18, 1500}};
|
||||
|
||||
// 1300 0
|
||||
// 1325 1
|
||||
// 1350 2
|
||||
// 1375 3
|
||||
// 1400 4
|
||||
// 1425 5
|
||||
// 1450 6
|
||||
// 1475 7
|
||||
// 1500 8
|
||||
// 1525 9
|
||||
// 1550 10
|
||||
// 1575 11
|
||||
// 1600 12
|
||||
// 1625 13
|
||||
// 1650 14
|
||||
// 1675 15
|
||||
bool failed = false;
|
||||
|
||||
for (const auto &testCase : testCases)
|
||||
{
|
||||
uint16_t input = testCase.first;
|
||||
uint16_t expectedOutput = testCase.second;
|
||||
uint16_t actualOutput = map4BitTo11Bit(input);
|
||||
bool assert = actualOutput == expectedOutput;
|
||||
if (!assert)
|
||||
{
|
||||
Serial.print("Test Failed->");
|
||||
failed = true;
|
||||
}
|
||||
Serial.println("Test input " + String(input) + ": expected " +
|
||||
String(expectedOutput) + ", got " + String(actualOutput));
|
||||
delay(100);
|
||||
}
|
||||
if (failed)
|
||||
{
|
||||
Serial.println("Test Failed");
|
||||
delay(500);
|
||||
}
|
||||
}
|
||||
378
radio/radio.h
Normal file
378
radio/radio.h
Normal file
@@ -0,0 +1,378 @@
|
||||
#include "SSD1306Wire.h"
|
||||
#include <LiLyGo.h>
|
||||
#include <LoRaBoards.h>
|
||||
#include <RadioLib.h>
|
||||
|
||||
void onReceive();
|
||||
void onReceiveFlag(void);
|
||||
void forceRestartLoRa();
|
||||
|
||||
bool radioIsRX = false;
|
||||
unsigned long lastPacketTime = 0;
|
||||
long int packetN = 0;
|
||||
int packetSave = 0;
|
||||
bool packetReceived = false;
|
||||
// Function to send 2-byte LoRa packet OR 4-byte LoRa packet
|
||||
void sendLoRaRCPacket(uint8_t cmd1, uint8_t val1, uint8_t cmd2, uint8_t val2,
|
||||
uint8_t cmd3 = 0, uint8_t val3 = 0, uint8_t cmd4 = 0,
|
||||
uint8_t val4 = 0)
|
||||
{
|
||||
cmd1 &= 0x0F;
|
||||
val1 &= 0x0F;
|
||||
cmd2 &= 0x0F;
|
||||
val2 &= 0x0F;
|
||||
int packetSize = LORA_DATA_BYTE;
|
||||
if (cmd3 != 0 && val3 != 0)
|
||||
{
|
||||
cmd3 &= 0x0F;
|
||||
val3 &= 0x0F;
|
||||
cmd4 &= 0x0F;
|
||||
val4 &= 0x0F;
|
||||
packetSize = 4;
|
||||
}
|
||||
|
||||
Serial.printf("Sending LoRa Packet: CMD1=%d, VAL1=%d, CMD2=%d, VAL2=%d, ", cmd1, val1,
|
||||
cmd2, val2);
|
||||
if (packetSize == 4)
|
||||
{
|
||||
Serial.printf("CMD3=%d, VAL3=%d, CMD4=%d, VAL4=%d, ", cmd3, val3, cmd4, val4);
|
||||
}
|
||||
Serial.println();
|
||||
|
||||
uint8_t paddedData[packetSize] = {0}; // Initialize with zeros
|
||||
// P:2:5:4:10
|
||||
// 0010:0101:0100:1010
|
||||
// RX 11001110
|
||||
// 9546
|
||||
// 0010:0101:0100:1010
|
||||
uint16_t packet = (cmd1 << 12) | (val1 << 8) | (cmd2 << 4) | val2;
|
||||
uint16_t packet2 = 0;
|
||||
|
||||
if (packetSize == 4)
|
||||
{
|
||||
packet2 = (cmd3 << 12) | (val3 << 8) | (cmd4 << 4) | val4;
|
||||
}
|
||||
|
||||
packetSave = packet;
|
||||
if (packet2 != 0)
|
||||
{
|
||||
packetSave = (static_cast<uint32_t>(packet) << 16) | packet2;
|
||||
}
|
||||
|
||||
uint8_t data[packetSize] = {0};
|
||||
data[0] = (packet >> 8) & 0xFF; // High byte
|
||||
data[1] = packet & 0xFF; // Low byte
|
||||
|
||||
if (packetSize == 4)
|
||||
{
|
||||
data[2] = (packet2 >> 8) & 0xFF; // High byte
|
||||
data[3] = packet2 & 0xFF; // Low byte
|
||||
}
|
||||
|
||||
int len = sizeof(data);
|
||||
|
||||
Serial.println("Packet length: " + String(len));
|
||||
Serial.print("Sending Packet: ");
|
||||
Serial.print(data[0], BIN);
|
||||
Serial.print(" ");
|
||||
Serial.print(data[1], BIN);
|
||||
if (packetSize == 4)
|
||||
{
|
||||
Serial.print(" ");
|
||||
Serial.print(data[2], BIN);
|
||||
Serial.print(" ");
|
||||
Serial.print(data[3], BIN);
|
||||
}
|
||||
Serial.println();
|
||||
|
||||
// size_t dataSize = sizeof(data) / sizeof(data[0]);
|
||||
// memcpy(paddedData, data, min(dataSize, (size_t)LORA_DATA_BYTE));
|
||||
|
||||
int status = radio.transmit(data, sizeof(data));
|
||||
if (status == RADIOLIB_ERR_NONE)
|
||||
{
|
||||
Serial.println("LoRa Packet Sent!");
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("LoRa Transmission Failed.");
|
||||
}
|
||||
}
|
||||
|
||||
void sendLoRaPacket(uint8_t *packetData, int length)
|
||||
{
|
||||
// Size of pointer here not an array count.
|
||||
// int length = sizeof(packetData);
|
||||
String packetStr = "";
|
||||
|
||||
Serial.println("Packet length: " + String(length));
|
||||
|
||||
Serial.print("Sending Packet: ");
|
||||
for (size_t i = 0; i < length; i++)
|
||||
{
|
||||
Serial.print(packetData[i], BIN);
|
||||
Serial.print(" ");
|
||||
packetStr += String(packetData[i]);
|
||||
}
|
||||
Serial.println();
|
||||
display.println("P[" + String(length) + "]:" + packetStr);
|
||||
Serial.println("P[" + String(length) + "]:" + packetStr);
|
||||
|
||||
int status = radio.transmit(packetData, length);
|
||||
if (status == RADIOLIB_ERR_NONE)
|
||||
{
|
||||
Serial.println("LoRa Packet Sent!");
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("LoRa Transmission Failed.");
|
||||
}
|
||||
}
|
||||
|
||||
void onReceiveFlag(void) { packetReceived = true; }
|
||||
|
||||
void onReceive(void)
|
||||
{
|
||||
#if LORA_RX
|
||||
receivedPacketCounter++;
|
||||
size_t len = radio.getPacketLength(true);
|
||||
uint8_t data[len] = {0};
|
||||
Serial.println("[LoRa] onReceive(" + String(len) + ")");
|
||||
|
||||
#if DEBUG_RX
|
||||
Serial.println("[LoRa] onReceive");
|
||||
if (len > LORA_DATA_BYTE)
|
||||
{
|
||||
Serial.println("WARNING: Packet size is too large:" + String(len));
|
||||
}
|
||||
|
||||
Serial.println("[LoRa] Length: " + String(len));
|
||||
#endif
|
||||
int state = radio.readData(data, len);
|
||||
|
||||
if (state == RADIOLIB_ERR_NONE)
|
||||
{
|
||||
#if DEBUG_RX
|
||||
if (sizeof(data) != LORA_DATA_BYTE)
|
||||
{
|
||||
Serial.println("[LoRa] ERROR: Packet Length Mismatch!");
|
||||
}
|
||||
#endif
|
||||
// Packet size 4 processing
|
||||
if (len == 8)
|
||||
{
|
||||
uint64_t seqNum = (static_cast<uint64_t>(data[0]) << 56) |
|
||||
(static_cast<uint64_t>(data[1]) << 48) |
|
||||
(static_cast<uint64_t>(data[2]) << 40) |
|
||||
(static_cast<uint64_t>(data[3]) << 32) |
|
||||
(static_cast<uint64_t>(data[4]) << 24) |
|
||||
(static_cast<uint64_t>(data[5]) << 16) |
|
||||
(static_cast<uint64_t>(data[6]) << 8) |
|
||||
static_cast<uint64_t>(data[7]);
|
||||
#if DEBUG
|
||||
|
||||
Serial.println("Lost:" + String(seqNum) + "/" +
|
||||
String(receivedPacketCounter) + ":" +
|
||||
String(seqNum - receivedPacketCounter));
|
||||
#endif
|
||||
int lost = seqNum - receivedPacketCounter;
|
||||
display.println("Lost:" + String(seqNum) + "/" +
|
||||
String(receivedPacketCounter) + ":" + String(lost));
|
||||
}
|
||||
|
||||
// Check if data is actually zero
|
||||
else if (data[0] == 0 && data[1] == 0)
|
||||
{
|
||||
int length = len;
|
||||
#if DEBUG
|
||||
Serial.println("[LoRa] WARNING: Received 0 : 0. I don't know why");
|
||||
Serial.print("[LoRa Receiver] Packet length: ");
|
||||
|
||||
Serial.println(String(length));
|
||||
|
||||
// return; // Ignore this packet
|
||||
Serial.print("[LoRa Receiver] RSSI: ");
|
||||
|
||||
Serial.print(radio.getRSSI());
|
||||
// display.println("0-0");
|
||||
|
||||
Serial.print("[LoRa Receiver] SNR: ");
|
||||
Serial.print(radio.getSNR());
|
||||
Serial.println(" dB");
|
||||
#endif
|
||||
}
|
||||
else if (len == 2 || len == 4) //** ToDo: process length 4 */)
|
||||
{
|
||||
int length = len;
|
||||
#if DEBUG
|
||||
Serial.println("[LoRa Receiver] Packet Received!");
|
||||
Serial.print("[LoRa Receiver] Packet length: ");
|
||||
Serial.println(String(length));
|
||||
Serial.println("[LoRa Receiver] DATA: " + String(data[0]) + ":" +
|
||||
String(data[1]));
|
||||
#endif
|
||||
display.println("DATA[" + String(packetN) + "]: " + String(data[0]) + ":" +
|
||||
String(data[1]));
|
||||
if (len == 4)
|
||||
{
|
||||
display.println("DATA-4[" + String(packetN) + "]: " + String(data[2]) +
|
||||
":" + String(data[3]));
|
||||
}
|
||||
packetN++;
|
||||
// Decode received data
|
||||
uint16_t receivedPacket = (data[0] << 8) | data[1];
|
||||
uint8_t cmd1 = (receivedPacket >> 12) & 0x0F;
|
||||
uint8_t val1 = (receivedPacket >> 8) & 0x0F;
|
||||
uint8_t cmd2 = (receivedPacket >> 4) & 0x0F;
|
||||
uint8_t val2 = receivedPacket & 0x0F;
|
||||
|
||||
display.println("Data:" + String(cmd1) + ":" + String(val1) + ":" +
|
||||
String(cmd2) + ":" + (val2));
|
||||
|
||||
// If size is 4, decode another 16 bits
|
||||
uint8_t cmd3 = 0, val3 = 0, cmd4 = 0, val4 = 0;
|
||||
|
||||
if (len == 4)
|
||||
{
|
||||
uint16_t receivedPacket2 = (data[2] << 8) | data[3];
|
||||
uint8_t cmd3 = (receivedPacket2 >> 12) & 0x0F;
|
||||
uint8_t val3 = (receivedPacket2 >> 8) & 0x0F;
|
||||
uint8_t cmd4 = (receivedPacket2 >> 4) & 0x0F;
|
||||
uint8_t val4 = receivedPacket2 & 0x0F;
|
||||
|
||||
display.println("Data:" + String(cmd3) + ":" + String(val3) + ":" +
|
||||
String(cmd4) + ":" + (val4));
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
// Print received data
|
||||
Serial.print("[LoRa Receiver] Data: ");
|
||||
Serial.print("CMD1=");
|
||||
Serial.print(cmd1);
|
||||
Serial.print(", VAL1=");
|
||||
Serial.print(val1);
|
||||
Serial.print(", CMD2=");
|
||||
Serial.print(cmd2);
|
||||
Serial.print(", VAL2=");
|
||||
Serial.println(val2);
|
||||
Serial.print("[LoRa Receiver] Data: ");
|
||||
|
||||
// Print RSSI (Signal Strength)
|
||||
Serial.print("[LoRa Receiver] RSSI: ");
|
||||
Serial.print(radio.getRSSI());
|
||||
Serial.println(" dBm");
|
||||
|
||||
// Print SNR (Signal-to-Noise Ratio)
|
||||
Serial.print("[LoRa Receiver] SNR: ");
|
||||
Serial.print(radio.getSNR());
|
||||
Serial.println(" dB");
|
||||
#endif
|
||||
display.print("RSSI: " + String(radio.getRSSI()));
|
||||
display.println(" SNR: " + String(radio.getSNR()));
|
||||
}
|
||||
}
|
||||
else if (state == RADIOLIB_ERR_RX_TIMEOUT)
|
||||
{
|
||||
// No packet received
|
||||
Serial.println("[LoRa Receiver] No packet received.");
|
||||
}
|
||||
else if (state == RADIOLIB_ERR_CRC_MISMATCH)
|
||||
{
|
||||
// Packet received but corrupted
|
||||
Serial.println("[LoRa Receiver] Packet received but CRC mismatch!");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Other error
|
||||
Serial.print("[LoRa Receiver] Receive failed, error code: ");
|
||||
Serial.println(state);
|
||||
}
|
||||
// Restart LoRa receiver
|
||||
// radio.implicitHeader(LORA_DATA_BYTE);
|
||||
radio.startReceive();
|
||||
#endif
|
||||
}
|
||||
|
||||
void forceRestartLoRa()
|
||||
{
|
||||
Serial.println("[LoRa] Forcing Restart...");
|
||||
radio.standby();
|
||||
radio.reset();
|
||||
delay(100);
|
||||
radio.begin();
|
||||
radio.startReceive();
|
||||
lastPacketTime = millis(); // Reset timeout
|
||||
}
|
||||
|
||||
void initRadioSwitchTable()
|
||||
{
|
||||
#ifdef USING_LR1121 // USING_SX1262
|
||||
// LR1121
|
||||
// set RF switch configuration for Wio WM1110
|
||||
// Wio WM1110 uses DIO5 and DIO6 for RF switching
|
||||
static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5,
|
||||
RADIOLIB_LR11X0_DIO6, RADIOLIB_NC,
|
||||
RADIOLIB_NC, RADIOLIB_NC};
|
||||
|
||||
static const Module::RfSwitchMode_t rfswitch_table[] = {
|
||||
// mode DIO5 DIO6
|
||||
{LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW}},
|
||||
{LR11x0::MODE_TX, {LOW, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH}},
|
||||
{LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}},
|
||||
{LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE,
|
||||
};
|
||||
radio.setRfSwitchTable(rfswitch_dio_pins, rfswitch_table);
|
||||
|
||||
// LR1121 TCXO Voltage 2.85~3.15V
|
||||
radio.setTCXO(3.0);
|
||||
heltec_delay(500);
|
||||
#endif
|
||||
}
|
||||
|
||||
void setupRadio()
|
||||
{
|
||||
radio.setFrequency(LORA_BASE_FREQ);
|
||||
radio.setBandwidth(LORA_BW);
|
||||
radio.setSpreadingFactor(LORA_SF);
|
||||
#if LORA_HEADER
|
||||
// Some issue it receives 0 0
|
||||
radio.implicitHeader(LORA_DATA_BYTE);
|
||||
#else
|
||||
radio.explicitHeader();
|
||||
#endif
|
||||
radio.setCodingRate(LORA_CR);
|
||||
radio.setPreambleLength(LORA_PREAMBLE);
|
||||
radio.forceLDRO(true);
|
||||
radio.setCRC(2);
|
||||
#if LORA_TX
|
||||
radio.setOutputPower(22);
|
||||
display.println("Turn ON RX to pair you have 30 seconds");
|
||||
for (int t = 0; t < 30; t++)
|
||||
{
|
||||
display.print(".");
|
||||
delay(50);
|
||||
}
|
||||
display.println();
|
||||
#endif
|
||||
|
||||
initRadioSwitchTable();
|
||||
|
||||
#if LORA_RX
|
||||
radio.setPacketReceivedAction(onReceiveFlag);
|
||||
int state = radio.startReceive();
|
||||
if (state == RADIOLIB_ERR_NONE)
|
||||
{
|
||||
radioIsRX = true;
|
||||
Serial.println("Listening for LoRa Packets...");
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.print("Receiver failed to start, code: ");
|
||||
Serial.println(state);
|
||||
while (true)
|
||||
{
|
||||
delay(5);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
22
radio/utility.h
Normal file
22
radio/utility.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
String toBinary(int num, int bitSize)
|
||||
{
|
||||
if (num == 0)
|
||||
return "0";
|
||||
|
||||
String binary = "";
|
||||
while (num > 0)
|
||||
{
|
||||
binary = String(num % 2) + binary;
|
||||
num /= 2;
|
||||
}
|
||||
|
||||
// Pad with leading zeros to match `bitSize`
|
||||
while (binary.length() < bitSize)
|
||||
{
|
||||
binary = "0" + binary;
|
||||
}
|
||||
|
||||
return binary;
|
||||
}
|
||||
380
src/main.cpp
380
src/main.cpp
@@ -23,64 +23,6 @@
|
||||
|
||||
// #define HELTEC_NO_DISPLAY
|
||||
|
||||
#ifdef BT_MOBILE
|
||||
#include <NimBLEDevice.h>
|
||||
|
||||
#define SERVICE_UUID "00001234-0000-1000-8000-00805f9b34fb"
|
||||
#define CHARACTERISTIC_UUID "00001234-0000-1000-8000-00805f9b34ac"
|
||||
|
||||
NimBLEServer *pServer = nullptr;
|
||||
NimBLECharacteristic *pCharacteristic = nullptr;
|
||||
NimBLEAdvertising *pAdvertising = nullptr;
|
||||
|
||||
void initBT()
|
||||
{
|
||||
// Initialize BLE device
|
||||
NimBLEDevice::init("RSSI_Radar");
|
||||
|
||||
// Get and print the MAC address
|
||||
String macAddress = NimBLEDevice::getAddress().toString().c_str();
|
||||
Serial.println("Bluetooth MAC Address: " + macAddress);
|
||||
|
||||
// Create BLE server
|
||||
pServer = NimBLEDevice::createServer();
|
||||
|
||||
// Create a BLE service
|
||||
NimBLEService *pService = pServer->createService(SERVICE_UUID);
|
||||
|
||||
// Create a BLE characteristic
|
||||
pCharacteristic = pService->createCharacteristic(
|
||||
CHARACTERISTIC_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY);
|
||||
|
||||
// Start the service
|
||||
pService->start();
|
||||
|
||||
// Start advertising
|
||||
|
||||
pAdvertising = NimBLEDevice::getAdvertising();
|
||||
pAdvertising->addServiceUUID(SERVICE_UUID);
|
||||
pAdvertising->setName("ESP32_RSSI_Radar"); // Set the device name
|
||||
pAdvertising->setMinInterval(300);
|
||||
pAdvertising->setMaxInterval(350);
|
||||
// pAdvertising->setScanResponse(true); // Allow scan responses
|
||||
|
||||
pAdvertising->start();
|
||||
|
||||
Serial.println("BLE server started.");
|
||||
}
|
||||
|
||||
// Function to send RSSI and Heading Data
|
||||
void sendBTData(float heading, float rssi)
|
||||
{
|
||||
String data =
|
||||
"RSSI_HEADING: '{H:" + String(heading) + ",RSSI:-" + String(rssi) + "}'";
|
||||
Serial.println("Sending data: " + data);
|
||||
pCharacteristic->setValue(data.c_str()); // Set BLE characteristic value
|
||||
pCharacteristic->notify(); // Notify connected client
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#include "FS.h"
|
||||
#include <Arduino.h>
|
||||
#ifdef WEB_SERVER
|
||||
@@ -120,6 +62,223 @@ void sendBTData(float heading, float rssi)
|
||||
#include <scan.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef BT_MOBILE
|
||||
|
||||
bool deviceConnected = false;
|
||||
#define SERVICE_UUID "00001234-0000-1000-8000-00805f9b34fb"
|
||||
#define CHARACTERISTIC_UUID "00001234-0000-1000-8000-00805f9b34ac"
|
||||
|
||||
#ifndef BT_NM
|
||||
#include <BLE2902.h>
|
||||
#include <BLEDevice.h>
|
||||
#include <BLEServer.h>
|
||||
#include <BLEUtils.h>
|
||||
|
||||
BLEServer *pServer = NULL;
|
||||
BLECharacteristic *pCharacteristic = NULL;
|
||||
BLEAdvertising *pAdvertising = NULL;
|
||||
|
||||
class MyServerCallbacks : public BLEServerCallbacks
|
||||
{
|
||||
void onConnect(BLEServer *pServer) { deviceConnected = true; };
|
||||
|
||||
void onDisconnect(BLEServer *pServer)
|
||||
{
|
||||
deviceConnected = false;
|
||||
BLEDevice::startAdvertising(); // Restart advertising after disconnect
|
||||
}
|
||||
};
|
||||
|
||||
#else
|
||||
#include <NimBLEDevice.h>
|
||||
|
||||
NimBLEServer *pServer = nullptr;
|
||||
NimBLECharacteristic *pCharacteristic = nullptr;
|
||||
NimBLEAdvertising *pAdvertising = nullptr;
|
||||
|
||||
class BTServerCallbacks : public NimBLEServerCallbacks
|
||||
{
|
||||
public:
|
||||
void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo) override
|
||||
{
|
||||
deviceConnected = true;
|
||||
Serial.printf("Device Connected | Free Heap: %d kByte\n",
|
||||
ESP.getFreeHeap() / 1000);
|
||||
Serial.printf("Client address: %s\n", connInfo.getAddress().toString().c_str());
|
||||
|
||||
pServer->updateConnParams(connInfo.getConnHandle(), 24, 48, 0, 180);
|
||||
}
|
||||
|
||||
void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo,
|
||||
int reason) override
|
||||
{
|
||||
deviceConnected = false;
|
||||
Serial.println("Device Disconnected");
|
||||
NimBLEDevice::startAdvertising(); // Restart advertising
|
||||
}
|
||||
|
||||
void onMTUChange(uint16_t MTU, NimBLEConnInfo &connInfo) override
|
||||
{
|
||||
Serial.printf("MTU updated: %u for connection ID: %u\n", MTU,
|
||||
connInfo.getConnHandle());
|
||||
}
|
||||
} BTServerCallbacks;
|
||||
|
||||
class CharacteristicCallbacks : public NimBLECharacteristicCallbacks
|
||||
{
|
||||
void onRead(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) override
|
||||
{
|
||||
Serial.printf("%s : onRead(), value: %s\n",
|
||||
pCharacteristic->getUUID().toString().c_str(),
|
||||
pCharacteristic->getValue().c_str());
|
||||
}
|
||||
|
||||
void onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) override
|
||||
{
|
||||
Serial.printf("%s : onWrite(), value: %s\n",
|
||||
pCharacteristic->getUUID().toString().c_str(),
|
||||
pCharacteristic->getValue().c_str());
|
||||
}
|
||||
|
||||
void onStatus(NimBLECharacteristic *pCharacteristic, int code) override
|
||||
{
|
||||
#ifdef COMPASS_DEBUG
|
||||
Serial.printf("Notification/Indication return code: %d, %s\n", code,
|
||||
NimBLEUtils::returnCodeToString(code));
|
||||
#endif
|
||||
}
|
||||
|
||||
void onSubscribe(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo,
|
||||
uint16_t subValue) override
|
||||
{
|
||||
std::string str = "Client ID: ";
|
||||
str += connInfo.getConnHandle();
|
||||
str += " Address: ";
|
||||
str += connInfo.getAddress().toString();
|
||||
if (subValue == 0)
|
||||
{
|
||||
str += " Unsubscribed to ";
|
||||
}
|
||||
else if (subValue == 1)
|
||||
{
|
||||
str += " Subscribed to notifications for ";
|
||||
}
|
||||
else if (subValue == 2)
|
||||
{
|
||||
str += " Subscribed to indications for ";
|
||||
}
|
||||
else if (subValue == 3)
|
||||
{
|
||||
str += " Subscribed to notifications and indications for ";
|
||||
}
|
||||
str += std::string(pCharacteristic->getUUID());
|
||||
|
||||
Serial.printf("%s\n", str.c_str());
|
||||
}
|
||||
} chrCallbacks;
|
||||
|
||||
class DescriptorCallbacks : public NimBLEDescriptorCallbacks
|
||||
{
|
||||
void onWrite(NimBLEDescriptor *pDescriptor, NimBLEConnInfo &connInfo) override
|
||||
{
|
||||
std::string dscVal = pDescriptor->getValue();
|
||||
Serial.printf("Descriptor written value: %s\n", dscVal.c_str());
|
||||
}
|
||||
|
||||
void onRead(NimBLEDescriptor *pDescriptor, NimBLEConnInfo &connInfo) override
|
||||
{
|
||||
Serial.printf("%s Descriptor read\n", pDescriptor->getUUID().toString().c_str());
|
||||
}
|
||||
} dscCallbacks;
|
||||
|
||||
#endif
|
||||
|
||||
void initBT()
|
||||
{
|
||||
#ifdef BT_NM
|
||||
// Initialize BLE device
|
||||
NimBLEDevice::init("RSSI_Radar");
|
||||
|
||||
// Get and print the MAC address
|
||||
String macAddress = NimBLEDevice::getAddress().toString().c_str();
|
||||
Serial.println("Bluetooth MAC Address: " + macAddress);
|
||||
|
||||
// Create BLE server
|
||||
pServer = NimBLEDevice::createServer();
|
||||
|
||||
if (!pServer)
|
||||
{
|
||||
Serial.println("Failed to create BLE Server");
|
||||
}
|
||||
|
||||
pServer->setCallbacks(&BTServerCallbacks);
|
||||
|
||||
// Create a BLE service
|
||||
NimBLEService *pService = pServer->createService(SERVICE_UUID);
|
||||
|
||||
// Create a BLE characteristic
|
||||
pCharacteristic = pService->createCharacteristic(
|
||||
CHARACTERISTIC_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY);
|
||||
pCharacteristic->setCallbacks(&chrCallbacks);
|
||||
|
||||
// Start the service
|
||||
pService->start();
|
||||
|
||||
// esp_task_wdt_init(20, true); // Increase timeout to 10 seconds
|
||||
// esp_task_wdt_add(NULL);
|
||||
|
||||
// Start advertising
|
||||
pAdvertising = NimBLEDevice::getAdvertising();
|
||||
pAdvertising->addServiceUUID(SERVICE_UUID);
|
||||
pAdvertising->setName("ESP32_RSSI_Radar"); // Set the device name
|
||||
// pAdvertising->setMinInterval(300);
|
||||
// pAdvertising->setMaxInterval(350);
|
||||
pAdvertising->enableScanResponse(true);
|
||||
// pAdvertising->setScanResponse(true); // Allow scan responses
|
||||
|
||||
pAdvertising->start();
|
||||
|
||||
Serial.println("BLE server started.");
|
||||
#else
|
||||
BLEDevice::init("ESP32_RADAR");
|
||||
pServer = BLEDevice::createServer();
|
||||
pServer->setCallbacks(new MyServerCallbacks());
|
||||
|
||||
BLEService *pService = pServer->createService(SERVICE_UUID);
|
||||
|
||||
pCharacteristic = pService->createCharacteristic(
|
||||
CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ |
|
||||
BLECharacteristic::PROPERTY_WRITE |
|
||||
BLECharacteristic::PROPERTY_NOTIFY);
|
||||
|
||||
pCharacteristic->setValue("Hello from ESP32");
|
||||
pService->start();
|
||||
|
||||
pAdvertising = BLEDevice::getAdvertising();
|
||||
pAdvertising->addServiceUUID(SERVICE_UUID);
|
||||
pAdvertising->setScanResponse(true);
|
||||
pAdvertising->setMinInterval(300);
|
||||
pAdvertising->setMaxInterval(350);
|
||||
BLEDevice::startAdvertising();
|
||||
|
||||
Serial.println("BLE server is ready!");
|
||||
#endif
|
||||
}
|
||||
|
||||
// Function to send RSSI and Heading Data
|
||||
void sendBTData(float heading, float rssi)
|
||||
{
|
||||
String data =
|
||||
"RSSI_HEADING: '{H:" + String(heading) + ",RSSI:-" + String(rssi) + "}'";
|
||||
|
||||
#ifdef COMPASS_DEBUG
|
||||
Serial.println("Sending data: " + data);
|
||||
#endif
|
||||
pCharacteristic->setValue(data.c_str()); // Set BLE characteristic value
|
||||
pCharacteristic->notify(); // Notify connected client
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef LILYGO
|
||||
#include <heltec_unofficial.h>
|
||||
// This file contains a binary patch for the SX1262
|
||||
@@ -258,7 +417,7 @@ void displaySensorDetails(void)
|
||||
Serial.println(" uT");
|
||||
Serial.println("------------------------------------");
|
||||
Serial.println("");
|
||||
delay(500);
|
||||
heltec_delay(500);
|
||||
}
|
||||
|
||||
// Variables for dynamic calibration
|
||||
@@ -802,7 +961,7 @@ int16_t initForScan(float freq)
|
||||
|
||||
// LR1121 TCXO Voltage 2.85~3.15V
|
||||
radio.setTCXO(3.0);
|
||||
delay(1000);
|
||||
heltec_delay(1000);
|
||||
#else
|
||||
state = radio.beginFSK(freq);
|
||||
#endif
|
||||
@@ -824,7 +983,7 @@ A:
|
||||
Serial.print(F("Failed to start receive mode, error code: "));
|
||||
display.drawString(0, 64 - 10, "E:startReceive");
|
||||
display.display();
|
||||
delay(2000);
|
||||
heltec_delay(2000);
|
||||
Serial.println(state);
|
||||
gotoAcounter++;
|
||||
if (gotoAcounter < 5)
|
||||
@@ -876,7 +1035,7 @@ bool setFrequency(float curr_freq)
|
||||
Serial.println("E(" + String(state) +
|
||||
"):setFrequency:" + String(r.current_frequency));
|
||||
display.display();
|
||||
delay(2);
|
||||
// delay(2);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -949,6 +1108,7 @@ void init_radio()
|
||||
#ifdef USING_SX1262
|
||||
if (config.radio2.enabled && config.radio2.module.equalsIgnoreCase("SX1262"))
|
||||
{
|
||||
|
||||
radio2 = new SX1262Module(config.radio2);
|
||||
state = radio2->beginScan(CONF_FREQ_BEGIN, BANDWIDTH, RADIOLIB_SHAPING_NONE);
|
||||
if (state == RADIOLIB_ERR_NONE)
|
||||
@@ -1165,7 +1325,6 @@ void draw360Scale(int start = 0, int end = 360, int width = 128, int height = 64
|
||||
for (int x = 0; x <= width; x += stepPixel)
|
||||
{
|
||||
// int x = map(i, start, end, 0, scaleLength);
|
||||
Serial.println("Tick: " + String(x));
|
||||
if (x == 128)
|
||||
{
|
||||
x = x - 1;
|
||||
@@ -2223,15 +2382,21 @@ int max_rssi_x = 999;
|
||||
void doScan();
|
||||
|
||||
void reportScan();
|
||||
long calStart = 0;
|
||||
|
||||
#ifdef COMPASS_ENABLED
|
||||
float getCompassHeading()
|
||||
{
|
||||
/* code */
|
||||
if (calStart == 0)
|
||||
{
|
||||
calStart = millis();
|
||||
}
|
||||
|
||||
/* Get a new sensor event */
|
||||
sensors_event_t event2;
|
||||
mag.getEvent(&event2);
|
||||
sensors_event_t event3;
|
||||
mag.getEvent(&event3);
|
||||
|
||||
#ifdef COMPASS_DEBUG
|
||||
/* Display the results (magnetic vector values are in micro-Tesla (uT)) */
|
||||
@@ -2263,20 +2428,26 @@ float getCompassHeading()
|
||||
// Dynamicly Calibrated out
|
||||
|
||||
// Read raw magnetometer data
|
||||
float x = event2.magnetic.x;
|
||||
float y = event2.magnetic.y;
|
||||
float z = event2.magnetic.z;
|
||||
float x = (event2.magnetic.x + event3.magnetic.x) / 2;
|
||||
float y = (event2.magnetic.y + event3.magnetic.y) / 2;
|
||||
float z = (event2.magnetic.z + event3.magnetic.z) / 2;
|
||||
|
||||
// Update min/max values dynamically
|
||||
x_min = min(x_min, x);
|
||||
x_max = max(x_max, x);
|
||||
y_min = min(y_min, y);
|
||||
y_max = max(y_max, y);
|
||||
z_min = min(z_min, z);
|
||||
z_max = max(z_max, z);
|
||||
// Doing calibration first 1 minute
|
||||
if (millis() - calStart < 60000)
|
||||
{
|
||||
// Update min/max values dynamically
|
||||
x_min = min(x_min, x);
|
||||
x_max = max(x_max, x);
|
||||
y_min = min(y_min, y);
|
||||
y_max = max(y_max, y);
|
||||
z_min = min(z_min, z);
|
||||
z_max = max(z_max, z);
|
||||
}
|
||||
|
||||
#ifdef COMPASS_DEBUG
|
||||
Serial.println("x_min:" + String(x_min) + " x_max: " + String(x_max) +
|
||||
" y_min: " + String(y_min));
|
||||
#endif
|
||||
|
||||
// Calculate offsets and scales in real-time
|
||||
float x_offset = (x_max + x_min) / 2;
|
||||
@@ -2350,9 +2521,54 @@ void loop(void)
|
||||
doScan();
|
||||
reportScan();
|
||||
}
|
||||
|
||||
#ifdef BT_MOBILE
|
||||
#ifdef BT_RSSI
|
||||
while (true)
|
||||
{
|
||||
float startFreq = FREQ_BEGIN - 0.5; // Start 2 MHz left
|
||||
float endFreq = FREQ_END + 0.5; // End 2 MHz right
|
||||
float step = 0.5; // Step size in MHz
|
||||
float rssi = -122;
|
||||
float rssiMax = -999;
|
||||
|
||||
for (float freq = startFreq; freq <= endFreq; freq += step)
|
||||
{
|
||||
setFrequency(freq);
|
||||
// Serial.println("COMPASS FREQ SET: " + String(freq));
|
||||
|
||||
// heltec_delay(5);
|
||||
#ifdef USING_LR1121
|
||||
radio.getRssiInst(&rssi);
|
||||
#else
|
||||
rssi = getRssi(false);
|
||||
#endif
|
||||
if (rssi > rssiMax)
|
||||
{
|
||||
rssiMax = rssi;
|
||||
}
|
||||
Serial.println("RSSI: " + String(freq) + ":" + String(rssiMax));
|
||||
// String p = _scan_result_str(m.payload.dump, 10);
|
||||
|
||||
if (pServer && pServer->getConnectedCount())
|
||||
{
|
||||
String str = "RSSI:TEST";
|
||||
if (pCharacteristic)
|
||||
{
|
||||
pCharacteristic->setValue(
|
||||
str.c_str()); // Set BLE characteristic value
|
||||
pCharacteristic->notify(); // Notify connected client
|
||||
}
|
||||
delay(50);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef COMPASS_ENABLED
|
||||
#if defined(COMPASS_FREQ)
|
||||
delay(1000);
|
||||
// delay(1000);
|
||||
display.clear();
|
||||
#endif // COMPAS_FREQ
|
||||
// Redraw Chart scale line
|
||||
@@ -2383,22 +2599,22 @@ void loop(void)
|
||||
draw360Scale(0, 360, 128, 64);
|
||||
}
|
||||
|
||||
float rssi = -122;
|
||||
float rssiMax = -999;
|
||||
if (headingDegrees >= 0 && headingDegrees <= 360)
|
||||
{
|
||||
float startFreq = COMPASS_FREQ - 1.0; // Start 2 MHz left
|
||||
float endFreq = COMPASS_FREQ + 1.0; // End 2 MHz right
|
||||
float startFreq = COMPASS_FREQ - 0.5; // Start 2 MHz left
|
||||
float endFreq = COMPASS_FREQ + 0.5; // End 2 MHz right
|
||||
float step = 0.5; // Step size in MHz
|
||||
#ifdef COMPASS_RSSI
|
||||
draw360Scale(0, 360, 128, 64);
|
||||
float rssi = -122;
|
||||
float rssiMax = -999;
|
||||
for (int i = 0; i < SAMPLES_RSSI; i++)
|
||||
{
|
||||
|
||||
for (float freq = startFreq; freq <= endFreq; freq += step)
|
||||
{
|
||||
setFrequency(freq);
|
||||
// Serial.println("COMPASS FREQ SET: " + String(freq));
|
||||
draw360Scale(0, 360, 128, 64);
|
||||
|
||||
// heltec_delay(5);
|
||||
#ifdef USING_LR1121
|
||||
radio.getRssiInst(&rssi);
|
||||
@@ -2830,6 +3046,7 @@ void doScan()
|
||||
max_x_rssi[display_x] = max_rssi;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // SCAN_METHOD == METHOD_RSSI
|
||||
|
||||
// if this code is not executed LORA radio doesn't work
|
||||
@@ -3183,6 +3400,7 @@ void loraSendMessage(Message &msg)
|
||||
|
||||
void reportScan()
|
||||
{
|
||||
|
||||
if (!config.lora_enabled)
|
||||
return;
|
||||
|
||||
|
||||
@@ -171,11 +171,16 @@
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
let lineCoef = 2.5;
|
||||
// Draw data points
|
||||
dataPoints.forEach(({ angle, rssi }) => {
|
||||
const rad = (angle * Math.PI) / 180;
|
||||
const length = ((120 + rssi) / (radius / 2)) * radius; // Scale RSSI to fit within radar
|
||||
|
||||
//const length = (120 + rssi) / (radius / 2) * radius;
|
||||
const length = ((120 - 90) + (rssi + 90)) * lineCoef; // Scale RSSI to fit within radar
|
||||
if (length > radius) {
|
||||
length = radius;
|
||||
}
|
||||
console.log("Length: " + length);
|
||||
const x = centerX + length * Math.cos(rad);
|
||||
const y = centerY + length * Math.sin(rad);
|
||||
|
||||
@@ -224,7 +229,7 @@
|
||||
|
||||
// Draw current RSSI line in yellow
|
||||
const currentRad = (currentPoint.angle * Math.PI) / 180;
|
||||
const currentLength = ((120 + currentPoint.rssi) / (radius / 2)) * radius;
|
||||
const currentLength = ((120 + currentPoint.rssi) / (radius / 2)) * radius * 1.2;
|
||||
|
||||
const currentX = centerX + currentLength * Math.cos(currentRad);
|
||||
const currentY = centerY + currentLength * Math.sin(currentRad);
|
||||
@@ -420,7 +425,7 @@
|
||||
console.log(`Reconnecting to device: ${bluetoothDevice.name}`);
|
||||
const server = await bluetoothDevice.gatt.connect();
|
||||
console.log(`Reconnected to device: ${bluetoothDevice.name}`);
|
||||
deviceNameElem.textContent = bluetoothDevice.name;
|
||||
//deviceNameElem.textContent = bluetoothDevice.name;
|
||||
statusElem.textContent = "Reconnected";
|
||||
} catch (error) {
|
||||
console.error("Error reconnecting to Bluetooth device:", error);
|
||||
|
||||
303
web_app/spectr/index.html
Normal file
303
web_app/spectr/index.html
Normal file
@@ -0,0 +1,303 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Spectrum Analyzer</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
text-align: center;
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chart {
|
||||
height: 40vh;
|
||||
border: 1px solid white;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.waterfall {
|
||||
height: 40vh;
|
||||
border: 1px solid white;
|
||||
}
|
||||
|
||||
.console {
|
||||
height: 20vh;
|
||||
width: 100%;
|
||||
background-color: black;
|
||||
color: white;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
border: 1px solid white;
|
||||
}
|
||||
|
||||
.labels {
|
||||
position: absolute;
|
||||
left: -50px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.freq-labels {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
position: absolute;
|
||||
bottom: -20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
image-rendering: crisp-edges;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
background-color: white;
|
||||
color: black;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: gray;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Spectrum Analyzer</h1>
|
||||
<button id="simulationButton" onclick="toggleSimulation()">Start Simulation</button>
|
||||
<button onclick="connectBluetooth()">Connect Bluetooth</button>
|
||||
<div class="container">
|
||||
<div class="chart-container">
|
||||
<div class="labels" id="dbLabels"></div>
|
||||
<canvas id="chartCanvas" class="chart"></canvas>
|
||||
<div class="freq-labels" id="freqLabels"></div>
|
||||
</div>
|
||||
<canvas id="waterfallCanvas" class="waterfall"></canvas>
|
||||
<div class="console" id="consoleOutput"></div>
|
||||
</div>
|
||||
<script>
|
||||
const chartCanvas = document.getElementById('chartCanvas');
|
||||
const chartCtx = chartCanvas.getContext('2d');
|
||||
const waterfallCanvas = document.getElementById('waterfallCanvas');
|
||||
const waterfallCtx = waterfallCanvas.getContext('2d');
|
||||
const dbLabels = document.getElementById('dbLabels');
|
||||
const freqLabels = document.getElementById('freqLabels');
|
||||
const simulationButton = document.getElementById('simulationButton');
|
||||
|
||||
const scaleFactor = 1; //window.devicePixelRatio || 2;
|
||||
chartCanvas.width = chartCanvas.clientWidth * scaleFactor;
|
||||
chartCanvas.height = chartCanvas.clientHeight * scaleFactor;
|
||||
chartCtx.scale(scaleFactor, scaleFactor);
|
||||
|
||||
waterfallCanvas.width = waterfallCanvas.clientWidth * scaleFactor;
|
||||
waterfallCanvas.height = waterfallCanvas.clientHeight * scaleFactor;
|
||||
waterfallCtx.scale(scaleFactor, scaleFactor);
|
||||
|
||||
let RSSI = {};
|
||||
let spectrumData = Array.from({ length: 80 }, () => new Array(900).fill(0));
|
||||
let waterfallHistory = Array.from({ length: 80 }, () => new Array(900).fill("black"));
|
||||
let simulationInterval;
|
||||
let isSimulationRunning = false;
|
||||
let bluetoothDevice;
|
||||
let bluetoothCharacteristic;
|
||||
|
||||
async function connectBluetooth() {
|
||||
try {
|
||||
const bluetoothDevice = await navigator.bluetooth.requestDevice({
|
||||
acceptAllDevices: true,
|
||||
optionalServices: ['00001234-0000-1000-8000-00805f9b34fb']
|
||||
});
|
||||
|
||||
const server = await bluetoothDevice.gatt.connect();
|
||||
const service = await server.getPrimaryService('00001234-0000-1000-8000-00805f9b34fb');
|
||||
const bluetoothCharacteristic = await service.getCharacteristic('00001234-0000-1000-8000-00805f9b34ac');
|
||||
|
||||
// Save the device's ID to localStorage
|
||||
localStorage.setItem("bluetoothDeviceId", bluetoothDevice.id);
|
||||
|
||||
bluetoothCharacteristic.addEventListener('characteristicvaluechanged', handleBluetoothData)
|
||||
|
||||
await bluetoothCharacteristic.startNotifications();
|
||||
|
||||
//statusElem.textContent = "Connected";
|
||||
} catch (error) {
|
||||
console.error("Error scanning for devices:", error);
|
||||
alert("Could not connect to device.");
|
||||
// statusElem.textContent = "Connection Error";
|
||||
}
|
||||
}
|
||||
|
||||
function handleBluetoothData(event) {
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
const value = decoder.decode(event.target.value);
|
||||
console.log("Received data:", value); // For debugging
|
||||
console.log(value);
|
||||
parseBluetoothData(value);
|
||||
}
|
||||
|
||||
function parseBluetoothData(data) {
|
||||
let matches = data.match(/\((\d+),\s*(-?\d+)\)/g);
|
||||
if (!matches) return;
|
||||
|
||||
RSSI = {};
|
||||
matches.forEach(match => {
|
||||
let [_, freq, rssi] = match.match(/\((\d+),\s*(-?\d+)\)/);
|
||||
RSSI[freq] = { freq: parseInt(freq), rssi: parseInt(rssi) };
|
||||
});
|
||||
|
||||
spectrumData.pop();
|
||||
spectrumData.unshift(Object.values(RSSI).map(d => d.rssi));
|
||||
updateConsole();
|
||||
drawChart();
|
||||
drawWaterfall();
|
||||
}
|
||||
|
||||
function generateRSSI() {
|
||||
for (let freq = 100; freq < 1000; freq++) {
|
||||
RSSI[freq] = { freq, rssi: Math.random() * -120 - 30 };
|
||||
}
|
||||
spectrumData.pop();
|
||||
spectrumData.unshift(Object.values(RSSI).map(d => d.rssi));
|
||||
updateConsole();
|
||||
}
|
||||
|
||||
function getColor(rssi) {
|
||||
if (rssi > -40) return "red";
|
||||
if (rssi > -50) return "magenta";
|
||||
if (rssi > -60) return "yellow";
|
||||
if (rssi > -70) return "green";
|
||||
return "blue";
|
||||
}
|
||||
|
||||
function drawChart() {
|
||||
chartCtx.clearRect(0, 0, chartCanvas.width, chartCanvas.height);
|
||||
let keys = Object.keys(RSSI).map(Number);
|
||||
console.log("chartCanvas.height: " + chartCanvas.height);
|
||||
keys.forEach((freq, index) => {
|
||||
let rssi = RSSI[freq].rssi;
|
||||
let color = getColor(rssi);
|
||||
chartCtx.fillStyle = color;
|
||||
const x = ((freq - 100) / 900) * chartCanvas.width;
|
||||
let minRssi = -90;
|
||||
let maxRssi = -30;
|
||||
let rssiDiapason = Math.abs(minRssi) - Math.abs(maxRssi);
|
||||
let yCoef = (chartCanvas.height / rssiDiapason);
|
||||
|
||||
let y = 0;
|
||||
if (rssi < minRssi) {
|
||||
y = chartCanvas.height;
|
||||
} else {
|
||||
y = (rssiDiapason - (rssiDiapason - Math.abs(Math.abs(rssi) - Math.abs(maxRssi)))) * yCoef;
|
||||
// y = chartCanvas.height * 0.1;
|
||||
}
|
||||
console.log(freq + ":" + rssi + " Y height: " + y);
|
||||
console.log("Y height: " + chartCanvas.height + "Coef: " + yCoef);
|
||||
/* x - The x-axis coordinate of the rectangle's starting point.
|
||||
y - The y-axis coordinate of the rectangle's starting point.
|
||||
width - The rectangle's width. Positive values are to the right, and negative to the left.
|
||||
height - The rectangle's height. Positive values are down, and negative are up.
|
||||
*/
|
||||
|
||||
chartCtx.fillRect(x, chartCanvas.height, chartCanvas.width / keys.length, -chartCanvas.height + y);
|
||||
waterfallHistory[0][index] = color;
|
||||
});
|
||||
}
|
||||
|
||||
function drawWaterfall() {
|
||||
waterfallCtx.clearRect(0, 0, waterfallCanvas.width, waterfallCanvas.height);
|
||||
waterfallHistory.pop();
|
||||
waterfallHistory.unshift([...waterfallHistory[0]]);
|
||||
for (let y = 0; y < waterfallHistory.length; y++) {
|
||||
for (let x = 0; x < waterfallHistory[y].length; x++) {
|
||||
waterfallCtx.fillStyle = waterfallHistory[y][x];
|
||||
waterfallCtx.fillRect(
|
||||
(x / waterfallHistory[y].length) * waterfallCanvas.width,
|
||||
(y / waterfallHistory.length) * waterfallCanvas.height,
|
||||
waterfallCanvas.width / waterfallHistory[y].length,
|
||||
waterfallCanvas.height / waterfallHistory.length
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateScales() {
|
||||
dbLabels.innerHTML = "";
|
||||
for (let db = 30; db <= 80; db += 10) {
|
||||
let div = document.createElement("div");
|
||||
div.textContent = "-" + db + " dB";
|
||||
div.style.flexGrow = "1";
|
||||
dbLabels.appendChild(div);
|
||||
}
|
||||
|
||||
freqLabels.innerHTML = "";
|
||||
for (let freq = 100; freq <= 1000; freq += 100) {
|
||||
let div = document.createElement("div");
|
||||
div.textContent = freq + " MHz";
|
||||
freqLabels.appendChild(div);
|
||||
}
|
||||
}
|
||||
|
||||
function updateConsole() {
|
||||
const consoleOutput = document.getElementById('consoleOutput');
|
||||
consoleOutput.innerHTML = Object.values(RSSI)
|
||||
.map(entry => `Freq: ${entry.freq} MHz | RSSI: ${entry.rssi.toFixed(2)} dB`)
|
||||
.join('<br>');
|
||||
}
|
||||
|
||||
function startSimulation() {
|
||||
updateScales();
|
||||
simulationInterval = setInterval(() => {
|
||||
generateRSSI();
|
||||
drawChart();
|
||||
drawWaterfall();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function stopSimulation() {
|
||||
clearInterval(simulationInterval);
|
||||
simulationInterval = null;
|
||||
}
|
||||
|
||||
function toggleSimulation() {
|
||||
if (isSimulationRunning) {
|
||||
stopSimulation();
|
||||
simulationButton.textContent = "Start Simulation";
|
||||
} else {
|
||||
startSimulation();
|
||||
simulationButton.textContent = "Stop Simulation";
|
||||
}
|
||||
isSimulationRunning = !isSimulationRunning;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user