.NET Core SX127X library Arduino LoRaSimpleNode & LoRaSimpleGateway

The LoRaSimpleNode and LoRaSimpleGateway samples shows how the receive and transmit IQ can be inverted.

LoRaSimpleNode

This sample uses all default settings except for frequency with InvertIQ enabled in receive more and disabled in Transmit mode

void loop() {
  if (runEvery(1000)) { // repeat every 1000 millis

    String message = "HeLoRa World! ";
    message += "I'm a Node! ";
    message += millis();

    LoRa_sendMessage(message); // send a message

    Serial.println("Send Message!");
  }
}

void LoRa_rxMode(){
  LoRa.enableInvertIQ();                // active invert I and Q signals
  LoRa.receive();                       // set receive mode
}

void LoRa_txMode(){
  LoRa.idle();                          // set standby mode
  LoRa.disableInvertIQ();               // normal mode
}

void LoRa_sendMessage(String message) {
  LoRa_txMode();                        // set tx mode
  LoRa.beginPacket();                   // start packet
  LoRa.print(message);                  // add payload
  LoRa.endPacket();                     // finish packet and send it
  LoRa_rxMode();                        // set rx mode
}

void onReceive(int packetSize) {
  String message = "";

  while (LoRa.available()) {
    message += (char)LoRa.read();
  }

  Serial.print("Node Receive: ");
  Serial.println(message);

}
Arduino Monitor displaying the output of the Arduino-LoRa Simple Node sample

In the Visual Studio output window I could see the received messages.

Loaded '/usr/lib/dotnet/shared/Microsoft.NETCore.App/5.0.4/Microsoft.Win32.Primitives.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
17:46:31-RX length 31 "HeLoRa World! I'm a Node! 69000" snr 10.3 packet rssi -57dBm rssi -98dBm 
17:46:32-RX length 31 "HeLoRa World! I'm a Node! 70000" snr 9.8 packet rssi -56dBm rssi -104dBm 
17:46:33-RX length 31 "HeLoRa World! I'm a Node! 71000" snr 10.0 packet rssi -57dBm rssi -104dBm 
17:46:34-RX length 31 "HeLoRa World! I'm a Node! 72000" snr 9.8 packet rssi -56dBm rssi -102dBm 
17:46:35-RX length 31 "HeLoRa World! I'm a Node! 73000" snr 9.8 packet rssi -59dBm rssi -102dBm 
17:46:36- Length 28 "Hello LoRa from .NET Core! 1"
17:46:36-TX Done
17:46:37-RX length 31 "HeLoRa World! I'm a Node! 75000" snr 9.3 packet rssi -58dBm rssi -102dBm 
17:46:38-RX length 31 "HeLoRa World! I'm a Node! 76000" snr 9.0 packet rssi -58dBm rssi -102dBm 
17:46:39-RX length 31 "HeLoRa World! I'm a Node! 77000" snr 9.8 packet rssi -59dBm rssi -104dBm 
17:46:40-RX length 31 "HeLoRa World! I'm a Node! 78000" snr 9.5 packet rssi -57dBm rssi -102dBm 
17:46:41-RX length 31 "HeLoRa World! I'm a Node! 79000" snr 9.5 packet rssi -55dBm rssi -102dBm 
17:46:42-RX length 31 "HeLoRa World! I'm a Node! 80000" snr 9.8 packet rssi -57dBm rssi -104dBm 
17:46:43-RX length 31 "HeLoRa World! I'm a Node! 81000" snr 9.5 packet rssi -58dBm rssi -104dBm 
17:46:44-RX length 31 "HeLoRa World! I'm a Node! 82000" snr 9.5 packet rssi -58dBm rssi -104dBm 
17:46:45-RX length 31 "HeLoRa World! I'm a Node! 83000" snr 9.0 packet rssi -58dBm rssi -94dBm 
17:46:46- Length 28 "Hello LoRa from .NET Core! 2"
17:46:46-TX Done
17:46:47-RX length 31 "HeLoRa World! I'm a Node! 85000" snr 9.0 packet rssi -58dBm rssi -104dBm 
17:46:48-RX length 31 "HeLoRa World! I'm a Node! 86000" snr 9.5 packet rssi -58dBm rssi -104dBm 
17:46:49-RX length 31 "HeLoRa World! I'm a Node! 87000" snr 9.5 packet rssi -58dBm rssi -102dBm 
17:46:50-RX length 30 "HeLoRa World! I'm a Node! 1000" snr 9.5 packet rssi -58dBm rssi -102dBm 
17:46:51-RX length 30 "HeLoRa World! I'm a Node! 2000" snr 9.5 packet rssi -58dBm rssi -104dBm 
17:46:52-RX length 30 "HeLoRa World! I'm a Node! 3000" snr 9.3 packet rssi -58dBm rssi -102dBm 
17:46:53-RX length 30 "HeLoRa World! I'm a Node! 4000" snr 9.5 packet rssi -58dBm rssi -102dBm 
17:46:54-RX length 30 "HeLoRa World! I'm a Node! 5000" snr 10.0 packet rssi -57dBm rssi -102dBm 
17:46:55-RX length 30 "HeLoRa World! I'm a Node! 6000" snr 10.0 packet rssi -57dBm rssi -102dBm 
17:46:56- Length 28 "Hello LoRa from .NET Core! 3"
17:46:56-TX Done
17:46:56-RX length 30 "HeLoRa World! I'm a Node! 7000" snr 9.8 packet rssi -57dBm rssi -104dBm 
17:46:57-RX length 30 "HeLoRa World! I'm a Node! 8000" snr 10.0 packet rssi -57dBm rssi -102dBm 
17:46:58-RX length 30 "HeLoRa World! I'm a Node! 9000" snr 9.8 packet rssi -57dBm rssi -104dBm 
17:46:59-RX length 31 "HeLoRa World! I'm a Node! 10000" snr 9.8 packet rssi -57dBm rssi -100dBm 
17:47:00-RX length 31 "HeLoRa World! I'm a Node! 11000" snr 9.8 packet rssi -57dBm rssi -99dBm 
17:47:01-RX length 31 "HeLoRa World! I'm a Node! 12000" snr 9.3 packet rssi -57dBm rssi -104dBm 
17:47:04-RX length 30 "HeLoRa World! I'm a Node! 1000" snr 9.5 packet rssi -57dBm rssi -100dBm 
17:47:05-RX length 30 "HeLoRa World! I'm a Node! 2000" snr 10.0 packet rssi -57dBm rssi -100dBm 
17:47:06- Length 28 "Hello LoRa from .NET Core! 4"
17:47:06-TX Done

LoRaSimpleGateway

The SimpleGateway uses all the same settings but with InvertIQ enabled in Transmit mode and disabled in Receive mode

#include <SPI.h>              // include libraries
#include <LoRa.h>

const long frequency = 915E6;  // LoRa Frequency

const int csPin = 10;          // LoRa radio chip select
const int resetPin = 9;        // LoRa radio reset
const int irqPin = 2;          // change for your board; must be a hardware interrupt pin

void setup() {
  Serial.begin(9600);                   // initialize serial
  while (!Serial);

  LoRa.setPins(csPin, resetPin, irqPin);

  if (!LoRa.begin(frequency)) {
    Serial.println("LoRa init failed. Check your connections.");
    while (true);                       // if failed, do nothing
  }

  Serial.println("LoRa init succeeded.");
  Serial.println();
  Serial.println("LoRa Simple Gateway");
  Serial.println("Only receive messages from nodes");
  Serial.println("Tx: invertIQ enable");
  Serial.println("Rx: invertIQ disable");
  Serial.println();

  LoRa.onReceive(onReceive);
  LoRa_rxMode();
}

void loop() {
  if (runEvery(5000)) { // repeat every 5000 millis

    String message = "HeLoRa World! ";
    message += "I'm a Gateway! ";
    message += millis();

    LoRa_sendMessage(message); // send a message

    Serial.println("Send Message!");
  }
}

void LoRa_rxMode(){
  LoRa.disableInvertIQ();               // normal mode
  LoRa.receive();                       // set receive mode
}

void LoRa_txMode(){
  LoRa.idle();                          // set standby mode
  LoRa.enableInvertIQ();                // active invert I and Q signals
}

void LoRa_sendMessage(String message) {
  LoRa_txMode();                        // set tx mode
  LoRa.beginPacket();                   // start packet
  LoRa.print(message);                  // add payload
  LoRa.endPacket();                     // finish packet and send it
  LoRa_rxMode();                        // set rx mode
}

void onReceive(int packetSize) {
  String message = "";

  while (LoRa.available()) {
    message += (char)LoRa.read();
  }

  Serial.print("Gateway Receive: ");
  Serial.println(message);
}

Arduino Monitor displaying the output of the Arduino-LoRa Simple Gateway sample

In the Visual Studio output window I could see messages getting transmitted with sent confirmations.

Loaded '/usr/lib/dotnet/shared/Microsoft.NETCore.App/5.0.4/Microsoft.Win32.Primitives.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
17:51:39-RX length 34 "HeLoRa World! I'm a Gateway! 10000" snr 9.3 packet rssi -59dBm rssi -102dBm 
17:51:39- Length 28 "Hello LoRa from .NET Core! 1"
17:51:39-TX Done
17:51:44-RX length 34 "HeLoRa World! I'm a Gateway! 15000" snr 9.3 packet rssi -58dBm rssi -102dBm 
17:51:49-RX length 34 "HeLoRa World! I'm a Gateway! 20000" snr 9.3 packet rssi -59dBm rssi -100dBm 
17:51:49- Length 28 "Hello LoRa from .NET Core! 2"
17:51:49-TX Done
17:51:54-RX length 34 "HeLoRa World! I'm a Gateway! 25000" snr 9.0 packet rssi -58dBm rssi -102dBm 
17:51:59-RX length 34 "HeLoRa World! I'm a Gateway! 30000" snr 9.3 packet rssi -58dBm rssi -100dBm 
17:51:59- Length 28 "Hello LoRa from .NET Core! 3"
17:51:59-TX Done
17:52:04-RX length 34 "HeLoRa World! I'm a Gateway! 35000" snr 9.3 packet rssi -60dBm rssi -104dBm 
17:52:09-RX length 34 "HeLoRa World! I'm a Gateway! 40000" snr 9.5 packet rssi -59dBm rssi -104dBm 
17:52:09- Length 28 "Hello LoRa from .NET Core! 4"
17:52:09-TX Done
17:52:14-RX length 34 "HeLoRa World! I'm a Gateway! 45000" snr 9.5 packet rssi -59dBm rssi -102dBm 
17:52:19-RX length 34 "HeLoRa World! I'm a Gateway! 50000" snr 9.3 packet rssi -60dBm rssi -104dBm 
17:52:19- Length 28 "Hello LoRa from .NET Core! 5"
17:52:19-TX Done
17:52:24-RX length 34 "HeLoRa World! I'm a Gateway! 55000" snr 9.8 packet rssi -60dBm rssi -102dBm 
17:52:29-RX length 34 "HeLoRa World! I'm a Gateway! 60000" snr 9.3 packet rssi -60dBm rssi -104dBm 
17:52:29- Length 28 "Hello LoRa from .NET Core! 6"
17:52:29-TX Done
17:52:34-RX length 34 "HeLoRa World! I'm a Gateway! 65000" snr 9.0 packet rssi -60dBm rssi -102dBm 
17:52:39-RX length 34 "HeLoRa World! I'm a Gateway! 70000" snr 9.3 packet rssi -60dBm rssi -102dBm 
17:52:39- Length 28 "Hello LoRa from .NET Core! 7"
17:52:39-TX Done
17:52:44-RX length 34 "HeLoRa World! I'm a Gateway! 75000" snr 8.8 packet rssi -58dBm rssi -102dBm 
17:52:49-RX length 34 "HeLoRa World! I'm a Gateway! 80000" snr 9.0 packet rssi -59dBm rssi -102dBm 
17:52:49- Length 28 "Hello LoRa from .NET Core! 8"
17:52:49-TX Done
17:52:54-RX length 34 "HeLoRa World! I'm a Gateway! 85000" snr 9.8 packet rssi -60dBm rssi -102dBm 
17:52:59-RX length 34 "HeLoRa World! I'm a Gateway! 90000" snr 9.0 packet rssi -60dBm rssi -102dBm 
17:52:59- Length 28 "Hello LoRa from .NET Core! 9"
17:52:59-TX Done
17:53:04-RX length 34 "HeLoRa World! I'm a Gateway! 95000" snr 9.3 packet rssi -59dBm rssi -100dBm 
17:53:09-RX length 35 "HeLoRa World! I'm a Gateway! 100000" snr 9.0 packet rssi -59dBm rssi -102dBm 
17:53:09- Length 29 "Hello LoRa from .NET Core! 10"
17:53:09-TX Done
17:53:14-RX length 35 "HeLoRa World! I'm a Gateway! 105000" snr 9.5 packet rssi -56dBm rssi -102dBm 
17:53:19-RX length 35 "HeLoRa World! I'm a Gateway! 110000" snr 9.3 packet rssi -59dBm rssi -102dBm 
17:53:19- Length 29 "Hello LoRa from .NET Core! 11"
17:53:19-TX Done

I then modified the SX127X.NetCore SX127XLoRaDeviceClient adding even more conditional compile options for the LoRaSampleNode and LoRaSampleGateway samples.

int messageCount = 1;

sX127XDevice.Initialise(
		SX127XDevice.RegOpModeMode.ReceiveContinuous,
		915000000.0,
		powerAmplifier: SX127XDevice.PowerAmplifier.PABoost,
		// outputPower: 5, outputPower: 20, outputPower:23,
		//powerAmplifier: SX127XDevice.PowerAmplifier.Rfo,	
		//outputPower:-1, outputPower: 14,
#if LORA_SENDER // From the Arduino point of view
		rxDoneignoreIfCrcMissing: false
#endif
#if LORA_RECEIVER // From the Arduino point of view, don't actually need this as already inverted
		invertIQTX: true
#endif

#if LORA_SET_SYNCWORD
		syncWord: 0xF3,
		invertIQTX: true,
		rxDoneignoreIfCrcMissing: false
#endif
#if LORA_SET_SPREAD
		spreadingFactor: SX127XDevice.RegModemConfig2SpreadingFactor._256ChipsPerSymbol,
		invertIQTX: true,
		rxDoneignoreIfCrcMissing: false
#endif
#if LORA_SIMPLE_NODE // From the Arduino point of view
		invertIQTX: false,
		rxDoneignoreIfCrcMissing: false
#endif
#if LORA_SIMPLE_GATEWAY // From the Arduino point of view
		invertIQRX: true,
		rxDoneignoreIfCrcMissing: false
#endif
		);

#if DEBUG
		sX127XDevice.RegisterDump();
#endif

#if !LORA_RECEIVER
		sX127XDevice.OnReceive += SX127XDevice_OnReceive;
		sX127XDevice.Receive();
#endif
#if !LORA_SENDER
		sX127XDevice.OnTransmit += SX127XDevice_OnTransmit;
#endif

#if LORA_SENDER
		Thread.Sleep(-1);
#else
		Thread.Sleep(5000);
#endif

		while (true)
		{
			string messageText = "Hello LoRa from .NET Core! " + messageCount.ToString();

			byte[] messageBytes = UTF8Encoding.UTF8.GetBytes(messageText);

			Console.WriteLine($"{DateTime.Now:HH:mm:ss}- Length {messageBytes.Length} \"{messageText}\"");

			messageCount += 1;

			sX127XDevice.Send(messageBytes);

			Thread.Sleep(10000);
		}
	}

Summary

While testing the LoRaReceiver sample I found a problem with how my code managed the transmit power by accidentally commenting out the “paBoost: true” parameter of the initialise method. When I did this the Seeeduino V4.2 and Dragino Shield stopped receiving messages.

I had assumed a user could configure the the output power using the initialise method but that was difficult/possible. After some digging I found that I needed to use RegPAConfigPADac and PABoost (I need to find a device which uses RFO for testing). So I removed several of the configuration parameters from the Intialise method and replaced them with one called outputPower. I then re-read the SX127X data sheet and had a look at some other libraries.

The Arduino-LoRa code has SetPower

void LoRaClass::setTxPower(int level, int outputPin)
{
  if (PA_OUTPUT_RFO_PIN == outputPin) {
    // RFO
    if (level < 0) {
      level = 0;
    } else if (level > 14) {
      level = 14;
    }

    writeRegister(REG_PA_CONFIG, 0x70 | level);
  } else {
    // PA BOOST
    if (level > 17) {
      if (level > 20) {
        level = 20;
      }

      // subtract 3 from level, so 18 - 20 maps to 15 - 17
      level -= 3;

      // High Power +20 dBm Operation (Semtech SX1276/77/78/79 5.4.3.)
      writeRegister(REG_PA_DAC, 0x87);
      setOCP(140);
    } else {
      if (level < 2) {
        level = 2;
      }
      //Default value PA_HF/LF or +17dBm
      writeRegister(REG_PA_DAC, 0x84);
      setOCP(100);
    }

    writeRegister(REG_PA_CONFIG, PA_BOOST | (level - 2));
  }
}

The AdaFruit version of RadioHead library has SetTxPower which has been “tweaked”

void RH_RF95::setTxPower(int8_t power, bool useRFO)
{
    // Sigh, different behaviours depending on whther the module use PA_BOOST or the RFO pin
    // for the transmitter output
    if (useRFO)
    {
	if (power > 14)
	    power = 14;
	if (power < -1)
	    power = -1;
	spiWrite(RH_RF95_REG_09_PA_CONFIG, RH_RF95_MAX_POWER | (power + 1));
    }
    else
    {
	if (power > 23)
	    power = 23;
	if (power < 5)
	    power = 5;

	// For RH_RF95_PA_DAC_ENABLE, manual says '+20dBm on PA_BOOST when OutputPower=0xf'
	// RH_RF95_PA_DAC_ENABLE actually adds about 3dBm to all power levels. We will us it
	// for 21, 22 and 23dBm
	if (power > 20)
	{
	    spiWrite(RH_RF95_REG_4D_PA_DAC, RH_RF95_PA_DAC_ENABLE);
	    power -= 3;
	}
	else
	{
	    spiWrite(RH_RF95_REG_4D_PA_DAC, RH_RF95_PA_DAC_DISABLE);
	}

	// RFM95/96/97/98 does not have RFO pins connected to anything. Only PA_BOOST
	// pin is connected, so must use PA_BOOST
	// Pout = 2 + OutputPower.
	// The documentation is pretty confusing on this topic: PaSelect says the max power is 20dBm,
	// but OutputPower claims it would be 17dBm.
	// My measurements show 20dBm is correct
	spiWrite(RH_RF95_REG_09_PA_CONFIG, RH_RF95_PA_SELECT | (power-5));
    }
}

The LoRa Shield Arduino library has two methods setPower(char p) and setPowerNum(uint8_t pow)

/*
 Function: Sets the signal power indicated as input to the module.
 Returns: Integer that determines if there has been any error
   state = 2  --> The command has not been executed
   state = 1  --> There has been an error while executing the command
   state = 0  --> The command has been executed with no errors
   state = -1 --> Forbidden command for this protocol
 Parameters:
   pow: power option to set in configuration. The input value range is from 
   0 to 14 dBm.
*/
int8_t SX1278::setPowerNum(uint8_t pow)
{
  byte st0;
  int8_t state = 2;
  byte value = 0x00;

  #if (SX1278_debug_mode > 1)
	  Serial.println();
	  Serial.println(F("Starting 'setPower'"));
  #endif

  st0 = readRegister(REG_OP_MODE);	  // Save the previous status
  if( _modem == LORA )
  { // LoRa Stdby mode to write in registers
	  writeRegister(REG_OP_MODE, LORA_STANDBY_MODE);
  }
  else
  { // FSK Stdby mode to write in registers
	  writeRegister(REG_OP_MODE, FSK_STANDBY_MODE);
  }
  
  if ( (pow >= 2) && (pow <= 20) )
  { // Pout= 17-(15-OutputPower) = OutputPower+2
	  if ( pow <= 17 ) {
		writeRegister(REG_PA_DAC, 0x84);
	  	pow = pow - 2;
	  } else { // Power > 17dbm -> Power = 20dbm
		writeRegister(REG_PA_DAC, 0x87);
		pow = 15;
	  }
	  _power = pow;
  }
  else
  {
	  state = -1;
	  #if (SX1278_debug_mode > 1)
		  Serial.println(F("## Power value is not valid ##"));
		  Serial.println();
	  #endif
  }

  writeRegister(REG_PA_CONFIG, _power);	// Setting output power value
  value = readRegister(REG_PA_CONFIG);

  if( value == _power )
  {
	  state = 0;
	  #if (SX1278_debug_mode > 1)
		  Serial.println(F("## Output power has been successfully set ##"));
		  Serial.println();
	  #endif
  }
  else
  {
	  state = 1;
  }

  writeRegister(REG_OP_MODE, st0);	// Getting back to previous status
  return state;
}

The SEMTECH library(V2.1.0) manages sleeping the device, reading the existing configuration and updating it as required which was a bit more functionality that I wanted.

void SX1276LoRaSetRFPower( int8_t power )
{
    SX1276Read( REG_LR_PACONFIG, &SX1276LR->RegPaConfig );
    SX1276Read( REG_LR_PADAC, &SX1276LR->RegPaDac );
    
    if( ( SX1276LR->RegPaConfig & RFLR_PACONFIG_PASELECT_PABOOST ) == RFLR_PACONFIG_PASELECT_PABOOST )
    {
        if( ( SX1276LR->RegPaDac & 0x87 ) == 0x87 )
        {
            if( power < 5 )
            {
                power = 5;
            }
            if( power > 20 )
            {
                power = 20;
            }
            SX1276LR->RegPaConfig = ( SX1276LR->RegPaConfig & RFLR_PACONFIG_MAX_POWER_MASK ) | 0x70;
            SX1276LR->RegPaConfig = ( SX1276LR->RegPaConfig & RFLR_PACONFIG_OUTPUTPOWER_MASK ) | ( uint8_t )( ( uint16_t )( power - 5 ) & 0x0F );
        }
        else
        {
            if( power < 2 )
            {
                power = 2;
            }
            if( power > 17 )
            {
                power = 17;
            }
            SX1276LR->RegPaConfig = ( SX1276LR->RegPaConfig & RFLR_PACONFIG_MAX_POWER_MASK ) | 0x70;
            SX1276LR->RegPaConfig = ( SX1276LR->RegPaConfig & RFLR_PACONFIG_OUTPUTPOWER_MASK ) | ( uint8_t )( ( uint16_t )( power - 2 ) & 0x0F );
        }
    }
    else
    {
        if( power < -1 )
        {
            power = -1;
        }
        if( power > 14 )
        {
            power = 14;
        }
        SX1276LR->RegPaConfig = ( SX1276LR->RegPaConfig & RFLR_PACONFIG_MAX_POWER_MASK ) | 0x70;
        SX1276LR->RegPaConfig = ( SX1276LR->RegPaConfig & RFLR_PACONFIG_OUTPUTPOWER_MASK ) | ( uint8_t )( ( uint16_t )( power + 1 ) & 0x0F );
    }
    SX1276Write( REG_LR_PACONFIG, SX1276LR->RegPaConfig );
    LoRaSettings.Power = power;
}

All the of the examples I looked at were different and some had manual tweaks, others I have not included were just wrong. I have based my beta version on a hybrid of the Arduino-LoRa, RadioHead and Semtech libraries. I need to test my code and confirm that I have the limits and offsets correct for the PABoost and RFO modes.

// RegPaDac more power
[Flags]
public enum RegPaDac
{
	Normal = 0b01010100,
	Boost = 0b01010111,
}
private const byte RegPaDacPABoostThreshold = 20;

// Validate the OutputPower
if (powerAmplifier == PowerAmplifier.Rfo)
{
	if ((outputPower < OutputPowerRfoMin) || (outputPower > OutputPowerRfoMax))
	{
		throw new ArgumentException($"outputPower must be between {OutputPowerRfoMin} and {OutputPowerRfoMax}", nameof(outputPower));
	}
}
if (powerAmplifier == PowerAmplifier.PABoost)
{
	if ((outputPower < OutputPowerPABoostMin) || (outputPower > OutputPowerPABoostMax))
	{
		throw new ArgumentException($"outputPower must be between {OutputPowerPABoostMin} and {OutputPowerPABoostMax}", nameof(outputPower));	
	}
}

if (( powerAmplifier != PowerAmplifierDefault) || (outputPower != OutputPowerDefault))
{
	byte regPAConfigValue = RegPAConfigMaxPowerMax;

	if (powerAmplifier == PowerAmplifier.Rfo)
	{
		regPAConfigValue |= RegPAConfigPASelectRfo;

		regPAConfigValue |= (byte)(outputPower + 1);

		this.WriteByte((byte)Registers.RegPAConfig, regPAConfigValue);
	}

	if (powerAmplifier == PowerAmplifier.PABoost)
	{
		regPAConfigValue |= RegPAConfigPASelectPABoost;

		if (outputPower > RegPaDacPABoostThreshold)
		{
			this.WriteByte((byte)Registers.RegPaDac, (byte)RegPaDac.Boost);

			regPAConfigValue |= (byte)(outputPower - 8);

			this.WriteByte((byte)Registers.RegPAConfig, regPAConfigValue);
		}
		else
		{
			this.WriteByte((byte)Registers.RegPaDac, (byte)RegPaDac.Normal);

			regPAConfigValue |= (byte)(outputPower - 5);

			this.WriteByte((byte)Registers.RegPAConfig, regPAConfigValue);
		}
	}
}

.NET Core SX127X library Arduino LoRaSender & LoRaReceiver

The arduino-LoRa library comes with a number of samples showing how to use its functionality. The LoRaSender and LoRaReceiver samples show the bare minimum of code required to send and receive messages.

LoRaSender

This sample uses all default settings except for frequency

#include <SPI.h>
#include <LoRa.h>

int counter = 0;

void setup() {
  Serial.begin(9600);
  while (!Serial);

  Serial.println("LoRa Sender");

  if (!LoRa.begin(915E6)) {
    Serial.println("Starting LoRa failed!");
    while (1);
  }
  
  delay(5000);  
}

void loop() {
  Serial.print("Sending packet: ");
  Serial.println(counter);

  // send packet
  LoRa.beginPacket();
  LoRa.print("hello ");
  LoRa.print(counter);
  LoRa.endPacket();

  counter++;

  delay(5000);
}

Arduino-LoRa library LoRaSender monitor output

In the Visual Studio output window I could see the received messages including a “corrupted” one which was displayed because the SX127XLoRaDeviceClient couldn’t force Cyclic Redundancy Check(CRC)s.

Loaded '/usr/lib/dotnet/shared/Microsoft.NETCore.App/5.0.4/Microsoft.Win32.Primitives.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
17:08:14-RX length 108 "hello 7" snr 9.3 packet rssi -63dBm rssi -102dBm 
17:08:24-RX length 108 "hello 0" snr 9.5 packet rssi -64dBm rssi -104dBm 
17:08:29-RX length 108 "hello 1" snr 9.3 packet rssi -64dBm rssi -102dBm 
17:08:34-RX length 108 "hello 2" snr 9.5 packet rssi -64dBm rssi -102dBm 
17:08:39-RX length 108 "hello 3" snr 8.5 packet rssi -61dBm rssi -104dBm 
17:08:44-RX length 108 "hello 4" snr 8.5 packet rssi -62dBm rssi -104dBm 
17:08:49-RX length 108 "hello 5" snr 9.3 packet rssi -64dBm rssi -104dBm 
17:08:54-RX length 108 "hello 6" snr 9.3 packet rssi -64dBm rssi -102dBm 
17:08:59-RX length 108 "hello 7" snr 9.3 packet rssi -64dBm rssi -102dBm 
17:09:04-RX length 108 "hello 8" snr 9.3 packet rssi -64dBm rssi -100dBm 
17:09:09-RX length 108 "hello 9" snr 9.3 packet rssi -64dBm rssi -102dBm 
17:09:14-RX length 108 "hello 10" snr 8.8 packet rssi -58dBm rssi -102dBm 
17:09:19-RX length 108 "hello 11" snr 9.3 packet rssi -60dBm rssi -104dBm 
17:09:24-RX length 108 "hello 12" snr 9.5 packet rssi -59dBm rssi -104dBm 
17:09:29-RX length 108 "hello 13" snr 9.0 packet rssi -60dBm rssi -102dBm 
17:09:34-RX length 108 "hello 14" snr 9.5 packet rssi -59dBm rssi -105dBm 
17:09:39-RX length 108 "hello 15" snr 9.0 packet rssi -57dBm rssi -102dBm 
17:09:44-RX length 108 "hello 16" snr 9.3 packet rssi -61dBm rssi -104dBm 
17:09:49-RX length 108 "hello 17" snr 9.5 packet rssi -61dBm rssi -104dBm 
17:09:54-RX length 108 "hello 18" snr 9.0 packet rssi -59dBm rssi -104dBm 
17:09:59-RX length 108 "hello 19" snr 9.3 packet rssi -61dBm rssi -102dBm 
17:10:04-RX length 108 "hello 20" snr 9.0 packet rssi -59dBm rssi -104dBm 
17:10:09-RX length 108 "hello 21" snr 9.3 packet rssi -61dBm rssi -102dBm 
17:10:14-RX length 108 "hello 22" snr 9.5 packet rssi -60dBm rssi -102dBm 
17:10:19-RX length 108 "hello 23" snr 9.3 packet rssi -60dBm rssi -104dBm 
17:10:24-RX length 108 "hello 24" snr 9.3 packet rssi -60dBm rssi -103dBm 
17:10:26-RX length 212 "�Q�Ԕv&G=����[9Y���
2S��ᒵ��O*�Ϥ��X��p쏊��" snr 50.8 packet rssi -104dBm rssi -102dBm 
17:10:30-RX length 108 "hello 25" snr 9.5 packet rssi -60dBm rssi -102dBm 
17:10:35-RX length 108 "hello 26" snr 9.3 packet rssi -60dBm rssi -104dBm 

LoRaReceiver

This sample also uses all default settings except of the frequency

#include <SPI.h>
#include <LoRa.h>

void setup() {
  Serial.begin(9600);
  while (!Serial);

  Serial.println("LoRa Receiver");

  if (!LoRa.begin(915E6)) {
    Serial.println("Starting LoRa failed!");
    while (1);
  }
}

void loop() {
  // try to parse packet
  int packetSize = LoRa.parsePacket();
  if (packetSize) {
    // received a packet
    Serial.print("Received packet '");

    // read packet
    while (LoRa.available()) {
      Serial.print((char)LoRa.read());
    }

    // print RSSI of packet
    Serial.print("' with RSSI ");
    Serial.println(LoRa.packetRssi());
  }
}
Arduino-LoRa library LoRaReceiver monitor output

In the Visual Studio output window I could see messages getting transmitted with sent confirmations.

Loaded '/usr/lib/dotnet/shared/Microsoft.NETCore.App/5.0.4/Microsoft.Win32.Primitives.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
17:21:19- Length 28 "Hello LoRa from .NET Core! 1"
17:21:19-TX Done
17:21:29- Length 28 "Hello LoRa from .NET Core! 2"
17:21:29-TX Done
17:21:39- Length 28 "Hello LoRa from .NET Core! 3"
17:21:39-TX Done
17:21:49- Length 28 "Hello LoRa from .NET Core! 4"
17:21:49-TX Done
17:21:59- Length 28 "Hello LoRa from .NET Core! 5"
17:21:59-TX Done
17:22:09- Length 28 "Hello LoRa from .NET Core! 6"
17:22:09-TX Done
17:22:19- Length 28 "Hello LoRa from .NET Core! 7"
17:22:19-TX Done
17:22:29- Length 28 "Hello LoRa from .NET Core! 8"
17:22:29-TX Done
17:22:39- Length 28 "Hello LoRa from .NET Core! 9"
17:22:39-TX Done
17:22:49- Length 29 "Hello LoRa from .NET Core! 10"
17:22:49-TX Done
17:22:59- Length 29 "Hello LoRa from .NET Core! 11"
17:22:59-TX Done
17:23:09- Length 29 "Hello LoRa from .NET Core! 12"
17:23:09-TX Done
17:23:19- Length 29 "Hello LoRa from .NET Core! 13"
17:23:19-TX Done
17:23:29- Length 29 "Hello LoRa from .NET Core! 14"
17:23:29-TX Done
17:23:39- Length 29 "Hello LoRa from .NET Core! 15"
17:23:39-TX Done
17:23:49- Length 29 "Hello LoRa from .NET Core! 16"
17:23:49-TX Done
17:23:59- Length 29 "Hello LoRa from .NET Core! 17"
17:23:59-TX Done
17:24:09- Length 29 "Hello LoRa from .NET Core! 18"
17:24:09-TX Done

I modified the SX127X.NetCore SX127XLoRaDeviceClient adding a conditional compile options for each sample

static void Main(string[] args)	
{
	int messageCount = 1;

	sX127XDevice.Initialise(
			SX127XDevice.RegOpModeMode.ReceiveContinuous,
			915000000.0,
			powerAmplifier: SX127XDevice.PowerAmplifier.PABoost,
#if LORA_SENDER // From the Arduino point of view
			rxDoneignoreIfCrcMissing: false
#endif
#if LORA_RECEIVER // From the Arduino point of view, don't actually need this as already inverted
			invertIQTX: true
#endif

#if LORA_SET_SYNCWORD
			syncWord: 0xF3,
			invertIQTX: true,
			rxDoneignoreIfCrcMissing: false
#endif
#if LORA_SET_SPREAD
			spreadingFactor: SX127XDevice.RegModemConfig2SpreadingFactor._256ChipsPerSymbol,
			invertIQTX: true,
			rxDoneignoreIfCrcMissing: false
#endif
	);

#if DEBUG
	sX127XDevice.RegisterDump();
#endif

#if LORA_SENDER
	sX127XDevice.OnReceive += SX127XDevice_OnReceive;
	sX127XDevice.Receive();
#endif
#if LORA_RECEIVER
	sX127XDevice.OnTransmit += SX127XDevice_OnTransmit;
#endif

#if LORA_SENDER
	Thread.Sleep(-1);
#else
	Thread.Sleep(5000);
#endif

	while (true)
	{
		string messageText = "Hello LoRa from .NET Core! " + messageCount.ToString();

		byte[] messageBytes = UTF8Encoding.UTF8.GetBytes(messageText);

		Console.WriteLine($"{DateTime.Now:HH:mm:ss}- Length {messageBytes.Length} \"{messageText}\""); 

		sX127XDevice.Send(messageBytes);

		messageCount += 1;

		Thread.Sleep(10000);
	}
}

Summary

While testing the LoRaReceiver sample I found a problem with how my code managed the RegOpMode register LoRa status value. In previous versions of the code I used RegOpModeModeDefault to manage status when the ProcessTxDone(byte IrqFlags) method completed and Receive() was called.

I had assumed that that the device would always be set with SetMode(RegOpModeModeDefault) but RegOpModeModeDefault was always RegOpModeMode.Sleep.