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);
}
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);
}
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);
}
}
}