Is there a way to establish a LoRa connection between a SX1262 868M LoRa Hat and an ESP32 WiFi LoRa V3 from Heltec?

275 views Asked by At

I have the SX1262 868M LoRa Hat that comes along with test code. This sums up the function call to initialize the device.

node = sx126x.sx126x(serial_num = "/dev/ttyS0", freq=868, addr=0, power=22, rssi=True, air_speed=2400, relay=False)

Now, I am trying to set this Hat as a receiver (the code already does that), and the ESP32 (which also is equipped with an SX1262) as the sender with this code:

LoRaTransmitter.h

#include "LoRaWan_APP.h"
#include "Arduino.h"

class LoRaTransmitter {
  private:
    RadioEvents_t RadioEvents;
    static LoRaTransmitter* instance;
    bool txDone;
    LoRaTransmitter();

  public:
    static LoRaTransmitter* getInstance();
    void send(const char *message);
    void send(const String &message);
    static void OnTxDone(void);
    static void OnTxTimeout(void);
};

LoRaTransmitter.cpp


#include "LoRaTransmitter.h"

#define RF_FREQUENCY 868000000 // Hz
#define TX_OUTPUT_POWER 22 // dBm
#define LORA_BANDWIDTH 2 // [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3: Reserved]
#define LORA_SPREADING_FACTOR 7 // [SF7..SF12]
#define LORA_CODINGRATE 1 // [1: 4/5, 2: 4/6, 3: 4/7, // 4: 4/8]
#define LORA_PREAMBLE_LENGTH 4 // Same for Tx and Rx
#define LORA_SYMBOL_TIMEOUT 0 // Symbols
#define LORA_FIX_LENGTH_PAYLOAD_ON false
#define LORA_IQ_INVERSION_ON false
#define BUFFER_SIZE 512

LoRaTransmitter* LoRaTransmitter::instance;

LoRaTransmitter::LoRaTransmitter() {
  Mcu.begin();
  RadioEvents.TxDone = OnTxDone;
  RadioEvents.TxTimeout = OnTxTimeout;
  Radio.Init(&RadioEvents);
  Radio.SetChannel(RF_FREQUENCY);
  Radio.SetTxConfig(
    MODEM_LORA, TX_OUTPUT_POWER, 0, LORA_BANDWIDTH,
    LORA_SPREADING_FACTOR, LORA_CODINGRATE,
    LORA_PREAMBLE_LENGTH, LORA_FIX_LENGTH_PAYLOAD_ON,
    true, 0, 0, LORA_IQ_INVERSION_ON, 3000);
  txDone = true;
}

LoRaTransmitter* LoRaTransmitter::getInstance() {
  if (instance == nullptr)
    instance = new LoRaTransmitter();
  return instance;
}

void LoRaTransmitter::send(const char *message) {
  if (txDone) {
    txDone = false;
    Serial.println("Sending message: " + String(message) + ", Length: " + String(strlen(message)));
    char packet[BUFFER_SIZE];
    strncpy(packet, message, BUFFER_SIZE);
    Radio.Send((uint8_t *)packet, strlen(packet));
  }
  Radio.IrqProcess();
}

void LoRaTransmitter::send(const String &message) {
  send(message.c_str());
}

void LoRaTransmitter::OnTxDone(void) {
  Serial.print("TX done......");
  instance->txDone = true;
}

void LoRaTransmitter::OnTxTimeout(void) {
  Radio.Sleep();
  Serial.print("TX Timeout......");
}

Now, I'll also copy there the setup class that I used for the Hat:

import RPi.GPIO as GPIO
import serial
import time
class sx126x:
    M0 = 22
    M1 = 27
    # if the header is 0xC0, then the LoRa register settings don't get lost when it powers off, and 0xC2 will be lost.
    # cfg_reg = [0xC0, 0x00, 0x09, 0x00, 0x00, 0x00, 0x62, 0x00, 0x17, 0x43, 0x00, 0x00]
    cfg_reg = [0xC2, 0x00, 0x09, 0x00, 0x00, 0x00, 0x62, 0x00, 0x12, 0x43, 0x00, 0x00]
    get_reg = bytes(12)
    rssi = False
    addr = 65535
    serial_n = ""
    addr_temp = 0
    # start frequency of the two LoRa modules
    # E22-400T22S           E22-900T22S
    # 410~493MHz      or    850~930MHz
    start_freq = 850
    # offset between start and end frequency of the two LoRa modules
    # E22-400T22S           E22-900T22S
    # 410~493MHz      or    850~930MHz
    offset_freq = 18
    # power = 22
    # air_speed =2400
    SX126X_UART_BAUDRATE_1200 = 0x00
    SX126X_UART_BAUDRATE_2400 = 0x20
    SX126X_UART_BAUDRATE_4800 = 0x40
    SX126X_UART_BAUDRATE_9600 = 0x60
    SX126X_UART_BAUDRATE_19200 = 0x80
    SX126X_UART_BAUDRATE_38400 = 0xA0
    SX126X_UART_BAUDRATE_57600 = 0xC0
    SX126X_UART_BAUDRATE_115200 = 0xE0
    SX126X_PACKAGE_SIZE_240_BYTE = 0x00
    SX126X_PACKAGE_SIZE_128_BYTE = 0x40
    SX126X_PACKAGE_SIZE_64_BYTE = 0x80
    SX126X_PACKAGE_SIZE_32_BYTE = 0xC0
    SX126X_Power_22dBm = 0x00
    SX126X_Power_17dBm = 0x01
    SX126X_Power_13dBm = 0x02
    SX126X_Power_10dBm = 0x03
    lora_air_speed_dic = {
        1200:0x01,
        2400:0x02,
        4800:0x03,
        9600:0x04,
        19200:0x05,
        38400:0x06,
        62500:0x07
    }
    lora_power_dic = {
        22:0x00,
        17:0x01,
        13:0x02,
        10:0x03
    }
    lora_buffer_size_dic = {
        240:SX126X_PACKAGE_SIZE_240_BYTE,
        128:SX126X_PACKAGE_SIZE_128_BYTE,
        64:SX126X_PACKAGE_SIZE_64_BYTE,
        32:SX126X_PACKAGE_SIZE_32_BYTE
    }

    def __init__(self, serial_num, freq, addr, power, rssi, air_speed=2400, net_id=0, buffer_size = 240, crypt=0, relay=False, lbt=False, wor=False):
        self.rssi = rssi
        self.addr = addr
        self.freq = freq
        self.serial_n = serial_num
        self.power = power
        # Initial the GPIO for M0 and M1 Pin
        GPIO.setmode(GPIO.BCM)
        GPIO.setwarnings(False)
        GPIO.setup(self.M0, GPIO.OUT)
        GPIO.setup(self.M1, GPIO.OUT)
        GPIO.output(self.M0, GPIO.LOW)
        GPIO.output(self.M1, GPIO.HIGH)
        # The hardware UART of Pi3B+, Pi4B is /dev/ttyS0
        self.ser = serial.Serial(serial_num, 9600)
        self.ser.flushInput()
        self.set(freq, addr, power, rssi, air_speed, net_id, buffer_size, crypt, relay, lbt, wor)

    def set(self, freq, addr, power, rssi, air_speed=2400, \
            net_id=0, buffer_size = 240, crypt=0, \
            relay=False, lbt=False, wor=False):
        self.send_to = addr
        self.addr = addr
        # We should pull up the M1 pin when setting the module
        GPIO.output(self.M0, GPIO.LOW)
        GPIO.output(self.M1, GPIO.HIGH)
        time.sleep(0.1)
        low_addr = addr & 0xff
        high_addr = addr >> 8 & 0xff
        net_id_temp = net_id & 0xff
        if freq > 850:
            freq_temp = freq - 850
            self.start_freq = 850
            self.offset_freq = freq_temp
        elif freq >410:
            freq_temp = freq - 410
            self.start_freq  = 410
            self.offset_freq = freq_temp
        air_speed_temp = self.lora_air_speed_dic.get(air_speed, None)
        # if air_speed_temp != None
        buffer_size_temp = self.lora_buffer_size_dic.get(buffer_size, None)
        # if air_speed_temp != None:
        power_temp = self.lora_power_dic.get(power, None)
        #if power_temp != None:
        if rssi:
            # enable printing rssi value
            rssi_temp = 0x80
        else:
            # disable printing rssi value
            rssi_temp = 0x00
        # get crypt
        l_crypt = crypt & 0xff
        h_crypt = crypt >> 8 & 0xff
        if relay==False:
            self.cfg_reg[3] = high_addr
            self.cfg_reg[4] = low_addr
            self.cfg_reg[5] = net_id_temp
            self.cfg_reg[6] = self.SX126X_UART_BAUDRATE_9600 + air_speed_tempreadingto read noise rssi value when add 0x20 as follows
            self.cfg_reg[7] = buffer_size_temp + power_temp + 0x20
            self.cfg_reg[8] = freq_temp
            # it will output a packet rssi value following received message
            # when enabling the eighth bit with 06H register(rssi_temp = 0x80)
            self.cfg_reg[9] = 0x43 + rssi_temp
            self.cfg_reg[10] = h_crypt
            self.cfg_reg[11] = l_crypt
        else:
            self.cfg_reg[3] = 0x01
            self.cfg_reg[4] = 0x02
            self.cfg_reg[5] = 0x03
            self.cfg_reg[6] = self.SX126X_UART_BAUDRATE_9600 + air_speed_temp
            # it will enable to read noise rssi value when add 0x20 as follow
            self.cfg_reg[7] = buffer_size_temp + power_temp + 0x20
            self.cfg_reg[8] = freq_temp
            # it will output a packet rssi value following received message
            # when enabling the
            self.cfg_reg[9] = 0x03 + rssi_temp
            self.cfg_reg[10] = h_crypt
            self.cfg_reg[11] = l_crypt
        self.ser.flushInput()
        for i in range(2):
            self.ser.write(bytes(self.cfg_reg))
            r_buff = 0
            time.sleep(0.2)
            if self.ser.inWaiting() > 0:
                time.sleep(0.1)
                r_buff = self.ser.read(self.ser.inWaiting())
                if r_buff[0] == 0xC1:
                    pass
                    # print("parameters setting is :", end='')
                    # for i in self.cfg_reg:
                        # print(hex(i), end=' ')
                    # print('\r\n')
                    # print("parameters return is  :", end='')
                    # for i in r_buff:
                        # print(hex(i), end=' ')
                    # print('\r\n')
                else:
                    pass
                    #print("parameters setting fail :", r_buff)
                break
            else:
                print("setting fail, setting again")
                self.ser.flushInput()
                time.sleep(0.2)
                print('\x1b[1A', end='\r')
                if i == 1:
                    print("setting fail, Press Esc to Exit and run again")
                    # time.sleep(2)
                    # print('\x1b[1A', end='\r')
        GPIO.output(self.M0, GPIO.LOW)
        GPIO.output(self.M1, GPIO.LOW)
        time.sleep(0.1)

    def get_settings(self):
        # the pin M1 of lora HAT must be high when entering setting mode and getting parameters
        GPIO.output(M1, GPIO.HIGH)
        time.sleep(0.1)
        # send command to get setting parameters
        self.ser.write(bytes([0xC1, 0x00, 0x09]))
        if self.ser.inWaiting() > 0:
            time.sleep(0.1)
            self.get_reg = self.ser.read(self.ser.inWaiting())
        # check the return characters from hat and print the setting parameters
        if self.get_reg[0] == 0xC1 and self.get_reg[2] == 0x09:
            fre_temp = self.get_reg[8]
            addr_temp = self.get_reg[3] + self.get_reg[4]
            air_speed_temp = self.get_reg[6] & 0x03
            power_temp = self.get_reg[7] & 0x03
            print("Frequence is {0}.125MHz.", fre_temp)
            print("Node address is {0}.", addr_temp)
            print("Air speed is {0} bps"+ lora_air_speed_dic.get(None, air_speed_temp))
            print("Power is {0} dBm" + lora_power_dic.get(None, power_temp))
            GPIO.output(M1, GPIO.LOW)
# the data format is as follows
# "node address, frequency, payload"
# "20, 868, Hello World"

    def send(self, data):
        GPIO.output(self.M1, GPIO.LOW)
        GPIO.output(self.M0, GPIO.LOW)
        time.sleep(0.1)
        self.ser.write(data)
        # if self.rssi == True:
            # self.get_channel_rssi()
        time.sleep(0.1)

    def receive(self):
        if self.ser.inWaiting() > 0:
            time.sleep(0.5)
            r_buff = self.ser.read(self.ser.inWaiting())
            print("receive message from node address with frequence\033[1;32m %d, %d.125MHz\033[0m"%((r_buff[0]<<8)+r_buff[1], r_buff[2]+self.start_freq), end='\r\n', flush = True)
            print("message is "+str(r_buff[3:-1]), end='\r\n')
            # print the rssi
            if self.rssi:
                # print('\x1b[3A', end='\r')
                print("the packet rssi value: -{0}dBm".format(256-r_buff[-1:][0]))
                self.get_channel_rssi()
            else:
                pass
                #print('\x1b[2A', end='\r')

    def get_channel_rssi(self):
        GPIO.output(self.M1, GPIO.LOW)
        GPIO.output(self.M0, GPIO.LOW)
        time.sleep(0.1)
        self.ser.flushInput()
        self.ser.write(bytes([0xC0, 0xC1, 0xC2, 0xC3, 0x00, 0x02]))
        time.sleep(0.5)
        re_temp = bytes(5)
        if self.ser.inWaiting() > 0:
            time.sleep(0.1)
            re_temp = self.ser.read(self.ser.inWaiting())
        if re_temp[0] == 0xC1 and re_temp[1] == 0x00 and re_temp[2] == 0x02:
            print("the current noise rssi value: -{0}dBm".format(256-re_temp[3]))
            # print("the last receive packet rssi value: -{0}dBm".format(256-re_temp[4]))
        else:
            # pass
            print("receive rssi value fail")
            # print("receive rssi value fail: ", re_temp)

I imagine the two devices should be able to communicate, due to the fact that in the end they are the same. Does someone have an idea of why the receiver HAT does not receive any message from the ESP32 LoRa sender?

1

There are 1 answers

0
dda On BEST ANSWER

I imagine the two devices should be able to communicate, due to the fact that in the end they are the same.

But that's the issue here. While they are, at the core, the same – two SX1262 – they are fundamentally not. One, the ebyte crap, is a managed LoRa module, that you access through UART, and have very little control over. The other one is a "real" SX1262 that you manage yourself, and have control over, via SPI.

One of the things that you should have noticed is that the settings between both are VERY different. The main differences between the ebyte and normal SX1262 modules are:

  • the "airspeed" setting, which is not a LoRa setting.
  • the fact that you can't set the SF value with the ebyte.
  • the addr address setting in the ebyte? WTH is that?
  • frequencies are integer multiples of MHz in the ebyte. There's no way to set it to say 868.125.
  • There are only 3 BW values for the ebyte instead of 10.

The 2 first points, which are I believe related, make it near impossible to use ebyte modules with normal LoRa modules. Their firmware seems to derive an SF value from that "air speed" thing. I wasn't able to match these airspeeds to BW / SF / CR values. For instance, looking at the LoRa calculator, the very common settings of SF 10, BW 7, CR 4/5 give you a data rate of 977 bauds per second. There's no way to twist these setting into something that nears 2,400.

enter image description here

I read somewhere of a guy who managed to make an ebyte communicate with an SX1276. Can't remember where or how, but it sounded complicated. Your best guess is to either stay within the ebyte ecosystem (which is crap anyway), or work with decent products, either SPI or UART-managed. There's a lot out there, my favorites being RAKwireless (for quality) and Heltec (for the cheaper stuff).