Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Odd problem when works with other LoRa library. #347

Closed
xg590 opened this issue Aug 11, 2021 · 7 comments
Closed

Odd problem when works with other LoRa library. #347

xg590 opened this issue Aug 11, 2021 · 7 comments
Labels
question Generic question about code or usage resolved Issue was resolved (e.g. bug fixed, or feature implemented)

Comments

@xg590
Copy link

xg590 commented Aug 11, 2021

I am using a MicroPython LoRa library (Raspberry Pi Pico + Adafruit RFM95W (SX1276) ) to receive message from RadioLib (ESP32 with on-board SX1276).

The problem is I can only get 4 characters ("WWII" or "1945" or "look") reliably and more characters sent from RadioLib are either random characters or garbage.

If two tested LoRa systems both run RadioLib or both run the MicroPython library, everything works perfectly.

To Reproduce

#include <RadioLib.h> 
SX1276 radio = new Module(RADIO_CS_PIN, RADIO_DI0_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
void setup()
{
    initBoard(); delay(1500); 
                        //   freq,    bw, sf,  cr,          syncWord,  power, preambleLength,   gain
    int state = radio.begin(  915, 125.0,  12,  8,  SX127X_SYNC_WORD,      4,              8,     0); 
}
 
void loop()
{    
    int state = radio.transmit("1956abc");   
    delay(5000);
}

MicroPython

import time, urandom
from machine import SPI, Pin 
####################
#                  #
#     1.Enable     #
#                  #
####################
# RFM95W         Pico GPIO
LoRa_MISO_Pin  = 16
LoRa_CS_Pin    = 17
LoRa_SCK_Pin   = 18
LoRa_MOSI_Pin  = 19
LoRa_G0_Pin    = 20 # DIO0_Pin
LoRa_RST_Pin   = 21
LoRa_EN_Pin    = 22

# Enable LoRa Module
lora_en_pin = Pin(LoRa_EN_Pin, Pin.OUT)
lora_en_pin.on()
time.sleep(0.01)

# Reset LoRa Module
lora_rst_pin = Pin(LoRa_RST_Pin, Pin.OUT)
lora_rst_pin.off()
time.sleep(0.01)
lora_rst_pin.on()
time.sleep(0.01)

####################
#                  #
#      2.SPI       #
#                  #
####################
'''
We command LoRa module to perform Tx/Rx operations via the SPI interface.
We disable SPI communication first to ensure it only happends when we need.
Define communication functions read and write.
The SPI comm is enabled temporarily for reading and writing and disabled thereafter.
'''
# Disable SPI communication with the LoRa module
lora_cs_pin  = Pin(LoRa_CS_Pin, Pin.OUT)
lora_cs_pin.on() # Release board from SPI Bus by bringing it into high impedance status.
time.sleep(0.01)

# SPI communication
# See datasheet: Device support SPI mode 0 (polarity & phase = 0) up to a max of 10MHz.
spi = SPI(0, baudrate=10_000_000, polarity=0, phase=0,
          sck=Pin(LoRa_SCK_Pin), mosi=Pin(LoRa_MOSI_Pin), miso=Pin(LoRa_MISO_Pin)) # We are using 0/first/default SPI

def write(reg, data):
    if type(data) == int:
        data = [data]
    elif type(data) == bytes:
        data = [p for p in data]
    elif type(data) == str:
        data = [ord(s) for s in data]
    lora_cs_pin.value(0) # Bring the CS pin low to enable communication
    spi.write(bytearray([reg | 0x80] + data))
    lora_cs_pin.value(1) # release the bus.

def read(reg=None, length=1):
    lora_cs_pin.value(0)
    # https://docs.micropython.org/en/latest/library/machine.SPI.html#machine-softspi
    if length == 1:
        data = spi.read(length+1, reg)[1]
    else:
        data = spi.read(length+1, reg)[1:]
    lora_cs_pin.value(1)
    return data

####################
#                  #
#      3.Lora      #
#                  #
####################
'''
Choose LoRa mode and Test write/read functions
Set bandwidth, coding rate, header mode, spreading factor, CRC, and etc.
'''
RegOpMode         = 0x01
Mode_SLEEP        = 0b00000000
LongRangeMode     = 0b1
# Choose LoRa (instead of FSK) mode for SX1276 and put the module in sleep mode
write(RegOpMode, Mode_SLEEP | LongRangeMode << 7)
time.sleep(0.1)
# Check if mode is set
assert read(RegOpMode) == (Mode_SLEEP | LongRangeMode << 7), "LoRa initialization failed"
 
# See 4.4. LoRa Mode Register Map
RegModemConfig1      = 0x1d
Bw                   = {'125KHz':0b0111, '500kHz':0b1001}
CodingRate           = {5:0b001, 6:0b010, 7:0b011, 8:0b100}
ImplicitHeaderModeOn = {'Implicit':0b1, 'Explicit':0b0}
write(RegModemConfig1, Bw['125KHz'] << 4 | CodingRate[8] << 1 | ImplicitHeaderModeOn['Explicit'])

# More parameters
RegModemConfig2  = 0x1e
RegModemConfig3  = 0x26
SpreadingFactor  = {7:0x7, 9:0x9, 12:0xC}
TxContinuousMode = {'normal':0b0, 'continuous':0b1}
RxPayloadCrcOn   = {'disable':0b0, 'enable':0b1}
write(RegModemConfig2, SpreadingFactor[12] << 4 | TxContinuousMode['normal'] << 3 | RxPayloadCrcOn['enable'] << 2 | 0x00) # Last 0x00 is SymbTimeout(9:8)
write(RegModemConfig3, 0x04) # 0x04 is SymbTimeout(7:0)

# Preamble length
RegPreambleMsb    = 0x20 # Size of preamble
RegPreambleLsb    = 0x21
write(RegPreambleMsb, 0x0) # Preamble can be (2<<15)kb long, much longer than payload
write(RegPreambleLsb, 0x8) # but we just use 8-byte preamble

# See 4.1.4. Frequency Settings
RegFrfMsb         = 0x06
RegFrfMid         = 0x07
RegFrfLsb         = 0x08
FXOSC = 32e6 # Freq of XOSC
FSTEP = FXOSC / (2**19)
Frf = int(915e6 / FSTEP)
write(RegFrfMsb, (Frf >> 16) & 0xff)
write(RegFrfMid, (Frf >>  8) & 0xff)
write(RegFrfLsb,  Frf        & 0xff)
  
####################
#                  #
#    Interrupt     #
#                  #
####################
'''
# This section is optional for Tx.
# It enable an interrupt when Tx is done.
'''
RegDioMapping1    = 0x40
DioMapping = {
    'Dio0' : {
                 'RxDone'           : 0b00 << 6,
                 'TxDone'           : 0b01 << 6,
                 'CadDone'          : 0b10 << 6
             },
    'Dio1' : {
                 'RxTimeout'        : 0b00 << 4,
                 'FhssChangeChannel': 0b01 << 4,
                 'CadDetected'      : 0b10 << 4
             },
    'Dio2' : {},
    'Dio3' : {},
    'Dio4' : {},
    'Dio5' : {},
}
write(RegDioMapping1, DioMapping['Dio0']['RxDone'])  # Configure Pin Dio0 so that this pin interrupts on TxDone, see Table 18 DIO Mapping LoRa ® Mode
 
RegPktSnrValue = 0x19 
RegPktRssiValue = 0x1A
RegRssiValue    = 0x1B


# Reg and value
RegFifoAddrPtr       = 0x0d
RegFifo              = 0x00
RegPayloadLength     = 0x22
RegFifoRxCurrentAddr = 0x10
RegRxNbBytes         = 0x13 # Number of received bytes
RegPktSnrValue       = 0x19
RegPktRssiValue      = 0x1a 
RegVersion           = 0x42

RegIrqFlags = 0x12
IrqFlags = {
    'RxTimeout'        : 0b1 << 7,
    'RxDone'           : 0b1 << 6,
    'PayloadCrcError'  : 0b1 << 5,
    'ValidHeader'      : 0b1 << 4,
    'TxDone'           : 0b1 << 3,
    'CadDone'          : 0b1 << 2,
    'FhssChangeChannel': 0b1 << 1,
    'CadDetected'      : 0b1 << 0
}
def _handler(pin):
    irq_flags = read(RegIrqFlags) 
    write(RegIrqFlags, 0xff) # write anything could clear all types of interrupt flags  
    if irq_flags & IrqFlags['RxDone'] and irq_flags & IrqFlags['ValidHeader'] : 
        PacketSnr  = read(RegPktSnrValue)
        SNR = PacketSnr / 4
        PacketRssi = read(RegPktRssiValue) 
        #Rssi = read(RegRssiValue) 
        if SNR < 0:
            RSSI = -157 + PacketRssi + SNR
        else:
            RSSI = -157 + 16 / 15 * PacketRssi 
        RSSI = round(RSSI, 2) # Table 7 Frequency Synthesizer Specification 
        print('SNR: {}, RSSI: {}'.format(SNR, RSSI))

        write(RegFifoAddrPtr, read(RegFifoRxCurrentAddr)) 
        packet = read(RegFifo, read(RegRxNbBytes)) 
        print('Payload: {}\n'.format(packet) ) 

    else: 
        for i, j in IrqFlags.items():
            if irq_flags & j:
                print(i) 
      
lora_irq_pin = Pin(LoRa_G0_Pin, Pin.IN)
lora_irq_pin.irq(handler=_handler, trigger=Pin.IRQ_RISING)

''' # interrupt flag mask: use to deactive a particular interrupt
RegIrqFlagsMask = 0x11;
IrqFlagsMask = {
    'RxTimeoutMask'        : 0b1 << 7,
    'RxDoneMask'           : 0b1 << 6,
    'PayloadCrcErrorMask'  : 0b1 << 5,
    'ValidHeaderMask'      : 0b1 << 4,
    'TxDoneMask'           : 0b1 << 3,
    'CadDoneMask'          : 0b1 << 2,
    'FhssChangeChannelMask': 0b1 << 1,
    'CadDetectedMask'      : 0b1 << 0
}
write(RegIrqFlagsMask, IrqFlagsMask['TxDoneMask'])  #  This will deactivate interrupt on TxDone.
'''

####################
#                  #
#       4.Rx       #
#                  #
####################

### Register Value
Mode = { # see Table 16 LoRa ® Operating Mode Functionality
  'CAD'          : 0b00000111, 
  'STANDBY'      : 0b00000001,
  'TX'           : 0b00000011,
  'RXCONTINUOUS' : 0b00000101 
} 
'''
SX1276 has a 256 byte memory area as the FIFO buffer for Tx/Rx operations.
How do we know which area is for Tx and which is for Rx.
We must set the base addresses RegFifoTxBaseAddr and RegFifoRxBaseAddr independently.
Since SX1276 work in a half-duplex manner, we better set both base addresses
at the bottom (0x00) of the FIFO buffer so that we can buffer 256 byte data
during transmition or reception.
'''
RegFifoTxBaseAddr = 0x0e
RegFifoRxBaseAddr = 0x0f
Fifo_Bottom       = 0x00 # We choose this value to max buffer we can write (then send out)
write(RegFifoTxBaseAddr, Fifo_Bottom)
write(RegFifoRxBaseAddr, Fifo_Bottom)

write(RegOpMode, Mode['STANDBY'])  # Request Standby mode so SX1276 performs reception initialization.   
# Receive Data
write(RegOpMode, Mode['RXCONTINUOUS'])          # Request Standby mode so SX1276 send out payload  

Expected behavior
I expect 1956abc but get 1956\x16pI

Additional info:
Arduino core: [ESP32 1.0.6]
Arduino IDE version [1.8.15]
RadioLib version [4.5.0]
MicroPython: rp2-pico-20210618-v1.16.uf2

@jgromes
Copy link
Owner

jgromes commented Aug 12, 2021

From the description it looks like some configuration inconsistency between RadioLib and uPython. What happens if the direction of the transmission is reversed, i.e. uPython -> RadioLib?

@xg590
Copy link
Author

xg590 commented Aug 13, 2021

I just prepared four systems: Two ESP32 with RadioLib and Two Raspberry Pi Pico with MicroPython.
When they run simultaneously, each pair can communicate internally. Only MicroPython Code responds to RadioLib code (ValidHead) whereas RadioLib is tone-deaf to MicroPython code.

  • Can you explain to me how RadioLib handles packet header? Is header explicit or implicit?
  • Which packet-related parameters were set by RadioLib for SX1276? Bandwidth, coding rate or so? I want to check if I missed sth.
  • Is FHSS to be blamed? Does RadioLib use FHSS?

@jgromes
Copy link
Owner

jgromes commented Aug 13, 2021

To answer your questions:

  1. For SX1276 in LoRa mode, explicit header is used by default. There's also a header integrity check:

// check packet header integrity
if(_crcEnabled && (_mod->SPIgetRegValue(SX127X_REG_HOP_CHANNEL, 6, 6)) == 0) {
// CRC is disabled according to packet header and enabled according to user
// most likely damaged packet header
clearIRQFlags();
return(ERR_LORA_HEADER_DAMAGED);
}

  1. See the default configuration Wiki page.

  2. RadioLib doesn't use frequency hopping - it's not implemented and disabled by default.

RadioLib is tone-deaf to MicroPython code

What does it mean exactly? Does it not receive the transmission at all? Does it return some error code? Does it return mangled data ...?

@xg590
Copy link
Author

xg590 commented Aug 13, 2021

When I enable RxPayloadCrcOn Tx side, RadioLib always throws out ERR_CRC_MISMATCH. When I disable it, RadioLIb sometime does print the message. Msg again begins with four and only four correct characters. And other time, I still get ERR_CRC_MISMATCH.
What kind of mis-configuration could lead to this weird problem.

@jgromes
Copy link
Owner

jgromes commented Aug 14, 2021

I was taking a look at the Python code, I noticed this:

write(RegModemConfig3, 0x04) # 0x04 is SymbTimeout(7:0)

You don't seem to be enabling low data optimization. As per SX1276 datasheet recommendation, RadioLib enables this automatically for LoRa settings that produce symbols longer than 16 ms. Your configuration SF12/BW 125 kHz will produce 32.77 ms symbols, so you should enable that on the uPython side.

Alternatively, you can disable it on RadioLib side by calling forceLDRO(false)

@xg590
Copy link
Author

xg590 commented Aug 14, 2021

[Thumbs up] Thanks for you magical tip :) It works after I enable LowDataRateOptimize!
From erratic

write(RegModemConfig3, 0x04) 

To awesome

LowDataRateOptimize = {'Disabled':0b0, 'Enabled':0b1}
AgcAutoOn = {'register LnaGain':0b0, 'internal AGC loop':0b1}
write(RegModemConfig3, LowDataRateOptimize['Enabled'] << 3 | AgcAutoOn['internal AGC loop'] << 2)  

@xg590 xg590 closed this as completed Aug 14, 2021
@jgromes jgromes added question Generic question about code or usage resolved Issue was resolved (e.g. bug fixed, or feature implemented) labels Aug 14, 2021
@jgromes
Copy link
Owner

jgromes commented Aug 14, 2021

Glad it's working now!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Generic question about code or usage resolved Issue was resolved (e.g. bug fixed, or feature implemented)
Projects
None yet
Development

No branches or pull requests

2 participants