Transmit Basic Revisited
After finding some possible SPI library issues (April 2020) with my STM32F429 Discovery + Dragino LoRa shield for Arduino test rig I wanted to trial my code on another nanoFramework platform.
I had ordered a Sparkfun LoRa Gateway 1 Channel ESP32 for a LoRaWAN research project from a local supplier and an unexpected “bonus” was that the ESP32 WROOM platform is supported by the nanoFramework.

I am using this in conjunction with my Armtronix IA005 SX1276 loRa node and my STM32F429 Discovery + Dragino LoRa shield for Arduino test rig.

The code now works on STM32F429 Discovery and ESP32 WROOM platforms. (manual update nanoFramework.Hardware.Esp32 NuGet reference required)

One disadvantage of the SparkFun device is that the reset pin on the SX127X doesn’t appear to be connected to the ESP32 so I can’t factory reset the device in code.
//#define ST_STM32F429I_DISCOVERY //nanoff --target ST_STM32F429I_DISCOVERY --update #define ESP32_WROOM_32_LORA_1_CHANNEL //nanoff --target ESP32_WROOM_32 --serialport COM4 --update namespace devMobile.IoT.Rfm9x.TransmitBasic { using System; using System.Text; using System.Threading; using Windows.Devices.Gpio; using Windows.Devices.Spi; #if ESP32_WROOM_32_LORA_1_CHANNEL using nanoFramework.Hardware.Esp32; #endif public sealed class Rfm9XDevice { private SpiDevice rfm9XLoraModem; private const byte RegisterAddressReadMask = 0X7f; private const byte RegisterAddressWriteMask = 0x80; public Rfm9XDevice(string spiPort, int chipSelectPin, int resetPin) { var settings = new SpiConnectionSettings(chipSelectPin) { ClockFrequency = 1000000, //DataBitLength = 8, Mode = SpiMode.Mode0,// From SemTech docs pg 80 CPOL=0, CPHA=0 SharingMode = SpiSharingMode.Shared, }; rfm9XLoraModem = SpiDevice.FromId(spiPort, settings); // Factory reset pin configuration GpioController gpioController = GpioController.GetDefault(); GpioPin resetGpioPin = gpioController.OpenPin(resetPin); resetGpioPin.SetDriveMode(GpioPinDriveMode.Output); resetGpioPin.Write(GpioPinValue.Low); Thread.Sleep(10); resetGpioPin.Write(GpioPinValue.High); Thread.Sleep(10); } public Rfm9XDevice(string spiPort, int chipSelectPin) { var settings = new SpiConnectionSettings(chipSelectPin) { ClockFrequency = 1000000, Mode = SpiMode.Mode0,// From SemTech docs pg 80 CPOL=0, CPHA=0 SharingMode = SpiSharingMode.Shared, }; rfm9XLoraModem = SpiDevice.FromId(spiPort, settings); } public Byte RegisterReadByte(byte registerAddress) { byte[] writeBuffer = new byte[] { registerAddress &= RegisterAddressReadMask, 0x0 }; byte[] readBuffer = new byte[writeBuffer.Length]; rfm9XLoraModem.TransferFullDuplex(writeBuffer, readBuffer); return readBuffer[1]; } public ushort RegisterReadWord(byte address) { byte[] writeBuffer = new byte[] { address &= RegisterAddressReadMask, 0x0, 0x0 }; byte[] readBuffer = new byte[writeBuffer.Length]; rfm9XLoraModem.TransferFullDuplex(writeBuffer, readBuffer); return (ushort)(readBuffer[2] + (readBuffer[1] << 8)); } public byte[] RegisterRead(byte address, int length) { byte[] writeBuffer = new byte[length + 1]; byte[] readBuffer = new byte[writeBuffer.Length]; byte[] repyBuffer = new byte[length]; writeBuffer[0] = address &= RegisterAddressReadMask; rfm9XLoraModem.TransferFullDuplex(writeBuffer, readBuffer); Array.Copy(readBuffer, 1, repyBuffer, 0, length); return repyBuffer; } public void RegisterWriteByte(byte address, byte value) { byte[] writeBuffer = new byte[] { address |= RegisterAddressWriteMask, value }; byte[] readBuffer = new byte[writeBuffer.Length]; rfm9XLoraModem.TransferFullDuplex(writeBuffer, readBuffer); } public void RegisterWriteWord(byte address, ushort value) { byte[] valueBytes = BitConverter.GetBytes(value); byte[] writeBuffer = new byte[] { address |= RegisterAddressWriteMask, valueBytes[0], valueBytes[1] }; byte[] readBuffer = new byte[writeBuffer.Length]; rfm9XLoraModem.TransferFullDuplex(writeBuffer,readBuffer); } public void RegisterWrite(byte address, byte[] bytes) { byte[] writeBuffer = new byte[1 + bytes.Length]; byte[] readBuffer = new byte[writeBuffer.Length]; Array.Copy(bytes, 0, writeBuffer, 1, bytes.Length); writeBuffer[0] = address |= RegisterAddressWriteMask; rfm9XLoraModem.TransferFullDuplex(writeBuffer, readBuffer); } public void RegisterDump() { Console.WriteLine("Register dump"); for (byte registerIndex = 0; registerIndex <= 0x42; registerIndex++) { byte registerValue = this.RegisterReadByte(registerIndex); Console.WriteLine($"Register 0x{registerIndex:x2} - Value 0X{registerValue:x2}"); } } } class Program { #if ST_STM32F429I_DISCOVERY private const string SpiBusId = "SPI5"; #endif #if ESP32_WROOM_32_LORA_1_CHANNEL private const string SpiBusId = "SPI1"; #endif static void Main() { int SendCount = 0; #if ST_STM32F429I_DISCOVERY int chipSelectPinNumber = PinNumber('C', 2); int resetPinNumber = PinNumber('C', 3); #endif #if ESP32_WROOM_32_LORA_1_CHANNEL int chipSelectPinNumber = Gpio.IO16; #endif try { #if ESP32_WROOM_32_LORA_1_CHANNEL Configuration.SetPinFunction(Gpio.IO12, DeviceFunction.SPI1_MISO); Configuration.SetPinFunction(Gpio.IO13, DeviceFunction.SPI1_MOSI); Configuration.SetPinFunction(Gpio.IO14, DeviceFunction.SPI1_CLOCK); Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SpiBusId, chipSelectPinNumber); #endif #if ST_STM32F429I_DISCOVERY Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SpiBusId, chipSelectPinNumber, resetPinNumber); #endif Thread.Sleep(500); // Put device into LoRa + Standby mode rfm9XDevice.RegisterWriteByte(0x01, 0b10000001); // RegOpMode // Set the frequency to 915MHz byte[] frequencyWriteBytes = { 0xE4, 0xC0, 0x00 }; // RegFrMsb, RegFrMid, RegFrLsb rfm9XDevice.RegisterWrite(0x06, frequencyWriteBytes); // More power PA Boost rfm9XDevice.RegisterWriteByte(0x09, 0b10000000); // RegPaConfig rfm9XDevice.RegisterDump(); while (true) { rfm9XDevice.RegisterWriteByte(0x0E, 0x0); // RegFifoTxBaseAddress // Set the Register Fifo address pointer rfm9XDevice.RegisterWriteByte(0x0D, 0x0); // RegFifoAddrPtr string messageText = $"Hello LoRa {SendCount += 1}!"; // load the message into the fifo byte[] messageBytes = UTF8Encoding.UTF8.GetBytes(messageText); rfm9XDevice.RegisterWrite(0x0, messageBytes); // RegFifo // Set the length of the message in the fifo rfm9XDevice.RegisterWriteByte(0x22, (byte)messageBytes.Length); // RegPayloadLength Console.WriteLine($"Sending {messageBytes.Length} bytes message {messageText}"); /// Set the mode to LoRa + Transmit rfm9XDevice.RegisterWriteByte(0x01, 0b10000011); // RegOpMode // Wait until send done, no timeouts in PoC Console.WriteLine("Send-wait"); byte IrqFlags = rfm9XDevice.RegisterReadByte(0x12); // RegIrqFlags while ((IrqFlags & 0b00001000) == 0) // wait until TxDone cleared { Thread.Sleep(10); IrqFlags = rfm9XDevice.RegisterReadByte(0x12); // RegIrqFlags Console.WriteLine("."); } Console.WriteLine(""); rfm9XDevice.RegisterWriteByte(0x12, 0b00001000); // clear TxDone bit Console.WriteLine("Send-Done"); Thread.Sleep(10000); } } catch (Exception ex) { Console.WriteLine(ex.Message); } } #if ST_STM32F429I_DISCOVERY static int PinNumber(char port, byte pin) { if (port < 'A' || port > 'J') throw new ArgumentException(); return ((port - 'A') * 16) + pin; } #endif } }
When I initially ran the application in Visual Studio 2019 the text below was displayed in the output window.
Register dump Register 0x00 - Value 0X00 Register 0x01 - Value 0X80 Register 0x02 - Value 0X1A Register 0x03 - Value 0X0B Register 0x04 - Value 0X00 … Register 0x3E - Value 0X00 Register 0x3F - Value 0X00 Register 0x40 - Value 0X00 Register 0x41 - Value 0X00 Register 0x42 - Value 0X12 Sending 13 bytes message Hello LoRa 1! Send-wait . . . . . Send-Done Sending 13 bytes message Hello LoRa 2! Send-wait . . . . . Send-Done
I could the see the messages arriving at the Armtronix device in the Arduino monitor.
18:21:46.299 -> Sending HeLoRa World! 188 18:21:48.700 -> Message: p8V⸮⸮⸮⸮⸮Kg 18:21:48.700 -> Length: 13 18:21:48.734 -> FirstChar: 112 18:21:48.734 -> RSSI: -70 18:21:48.734 -> Snr: 9.50 18:21:48.769 -> 18:21:50.193 -> Message: Hello LoRa 10! 18:21:50.193 -> Length: 14 18:21:50.226 -> FirstChar: 72 18:21:50.226 -> RSSI: -49 18:21:50.226 -> Snr: 10.00 18:21:50.260 -> 18:21:56.652 -> Sending HeLoRa World! 190 18:21:58.765 -> Message: Hello LoRa 2! 18:21:58.765 -> Length: 13 18:21:58.798 -> FirstChar: 72 18:21:58.798 -> RSSI: -71 18:21:58.798 -> Snr: 9.75 18:21:58.832 -> 18:22:00.268 -> Message: Hello LoRa 11! 18:22:00.268 -> Length: 14 18:22:00.302 -> FirstChar: 72 18:22:00.302 -> RSSI: -49 18:22:00.302 -> Snr: 10.00 18:22:00.336 ->
The first message was getting corrupted (only when running in the debugger) which after some trial and error I think was most probably due to my RegOpMode register mode configuration.

// Put device into LoRa + Sleep mode rfm9XDevice.RegisterWriteByte(0x01, 0b10000000); // Put device into LoRa + Standby mode rfm9XDevice.RegisterWriteByte(0x01, 0b10000001);
After a couple of years and half a dozen platform ports still finding bugs in my samples…
Pingback: nanoFramework LoRa library Part5 | devMobile's blog