Random wanderings through Microsoft Azure esp. PaaS plumbing, the IoT bits, AI on Micro controllers, AI on Edge Devices, .NET nanoFramework, .NET Core on *nix and ML.NET+ONNX
My ABP implementation is based on my OTAA one so is pretty “nasty”. Again, I assumed that there would be no timeouts or failures and I only send one message BCD “48656c6c6f204c6f526157414e” (“hello LoRaWAN”) every 20 seconds.
STM32F691Discovery with EVB plugged into Arduino headers
I created a new ABP device
Things Network ABP configuration
Then I configured the RAK811 module for LoRaWAN
// Set the Working mode to LoRaWAN
bytesWritten = outputDataWriter.WriteString("at+set_config=lora:work_mode:0rn");
Debug.WriteLine($"TX: work_mode {outputDataWriter.UnstoredBufferLength} bytes to output stream.");
txByteCount = outputDataWriter.Store();
Debug.WriteLine($"TX: {txByteCount} bytes via {serialDevice.PortName}");
// Read the response
bytesRead = inputDataReader.Load(128);
if (bytesRead > 0)
{
string response = inputDataReader.ReadString(bytesRead);
Debug.WriteLine($"RX sync:{response}");
}
Then sequentially stepped through the necessary configuration to join the The Things Network(TTN) network
// Set the JoinMode to ABP
bytesWritten = outputDataWriter.WriteString($"at+set_config=lora:join_mode:1\r\n");
Debug.WriteLine($"TX: join_mode {outputDataWriter.UnstoredBufferLength} bytes to output stream.");
txByteCount = outputDataWriter.Store();
Debug.WriteLine($"TX: {txByteCount} bytes via {serialDevice.PortName}");
// Read the response
bytesRead = inputDataReader.Load(128);
if (bytesRead > 0)
{
String response = inputDataReader.ReadString(bytesRead);
Debug.WriteLine($"RX :{response}");
}
// Set the device address
bytesWritten = outputDataWriter.WriteString($"at+set_config=lora:dev_addr:{devAddress}\r\n");
Debug.WriteLine($"TX: dev_addr {outputDataWriter.UnstoredBufferLength} bytes to output stream.");
txByteCount = outputDataWriter.Store();
Debug.WriteLine($"TX: {txByteCount} bytes via {serialDevice.PortName}");
// Read the response
bytesRead = inputDataReader.Load(128);
if (bytesRead > 0)
{
String response = inputDataReader.ReadString(bytesRead);
Debug.WriteLine($"RX :{response}");
}
...
After making a few fixes to my code and tweaking some settings I could see data in the TTN Console.
ABP Device data uplink
The code is not suitable for production but it confirmed my software and hardware configuration worked.
In the Visual Studio 2019 debug output I could see the AT Command responses from were getting truncated in odd ways so I need to be careful how they are processed.
The thread '<No Name>' (0x2) has exited with code 0 (0x0).
devMobile.IoT.Rak811.NetworkJoinABP starting
COM5,COM6
TX: work_mode 32 bytes to output stream.
TX: 32 bytes via COM6
RX :UART1 work mode: RUI_UART_NORAMAL
Current work_mode:LoRaWAN, join_mode:ABP, Class: A
Initialization OK
TX: region 33 bytes to output stream.
TX: 33 bytes via COM6
RX :OK
TX: join_mode 32 bytes to output stream.
TX: 32 bytes via COM6
RX :OK
TX: dev_addr 38 bytes to output stream.
TX: 38 bytes via COM6
RX :OK
TX: nwks_key 62 bytes to output stream.
TX: 62 bytes via COM6
RX :OK
TX: apps_key 62 bytes to output stream.
TX: 62 bytes via COM6
RX :OK
TX: confirm 30 bytes to output stream.
TX: 30 bytes via COM6
RX :OK
TX: join 9 bytes to output stream.
TX: 9 bytes via COM6
TX: send 43 bytes to output stream.
TX: 43 bytes via COM6
TX: send 43 bytes to output stream.
TX: 43 bytes via COM6
RX :OK Jo
TX: send 43 bytes to output stream.
TX: 43 bytes via COM6
RX :in Su
TX: send 43 bytes to output stream.
TX: 43 bytes via COM6
RX :ccess
TX: send 43 bytes to output stream.
TX: 43 bytes via COM6
RX :
OK
TX: send 43 bytes to output stream.
TX: 43 bytes via COM6
RX :
OK
The next step is to get rework the code to process responses to the AT commands in a smarter way and extract error codes when an operation fails.
STM32F691Discovery with EVB plugged into Arduino headers
My Over the Air Activation(OTAA) implementation is pretty “nasty” I assumed that there would be no timeouts or failures and I only send one BCD message “48656c6c6f204c6f526157414e” which is “hello LoRaWAN”
I configured the RAK811 module for LoRaWAN
// Set the Working mode to LoRaWAN
bytesWritten = outputDataWriter.WriteString("at+set_config=lora:work_mode:0\r\n");
Debug.WriteLine($"TX: work_mode {outputDataWriter.UnstoredBufferLength} bytes to output stream.");
txByteCount = outputDataWriter.Store();
Debug.WriteLine($"TX: {txByteCount} bytes via {serialDevice.PortName}");
// Read the response
bytesRead = inputDataReader.Load(128);
if (bytesRead > 0)
{
string response = inputDataReader.ReadString(bytesRead);
Debug.WriteLine($"RX sync:{response}");
}
Then just sequentially stepped through the necessary configuration to join the TTN network
// Set the Region to AS923
bytesWritten = outputDataWriter.WriteString("at+set_config=lora:region:AS923\r\n");
Debug.WriteLine($"TX: region {outputDataWriter.UnstoredBufferLength} bytes to output stream.");
txByteCount = outputDataWriter.Store();
Debug.WriteLine($"TX: {txByteCount} bytes via {serialDevice.PortName}");
// Read the response
bytesRead = inputDataReader.Load(128);
if (bytesRead > 0)
{
String response = inputDataReader.ReadString(bytesRead);
Debug.WriteLine($"RX sync:{response}");
}
// Set the JoinMode
bytesWritten = outputDataWriter.WriteString($"at+set_config=lora:join_mode:0\r\n");
Debug.WriteLine($"TX: join_mode {outputDataWriter.UnstoredBufferLength} bytes to output stream.");
txByteCount = outputDataWriter.Store();
Debug.WriteLine($"TX: {txByteCount} bytes via {serialDevice.PortName}");
// Read the response
bytesRead = inputDataReader.Load(128);
if (bytesRead > 0)
{
String response = inputDataReader.ReadString(bytesRead);
Debug.WriteLine($"RX sync:{response}");
}
// OTAA set the devEUI
bytesWritten = outputDataWriter.WriteString($"at+set_config=lora:dev_eui:{devEui}\r\n");
Debug.WriteLine($"TX: dev_eui {outputDataWriter.UnstoredBufferLength} bytes to output stream.");
txByteCount = outputDataWriter.Store();
Debug.WriteLine($"TX: {txByteCount} bytes via {serialDevice.PortName}");
// Read the response
bytesRead = inputDataReader.Load(128);
if (bytesRead > 0)
{
String response = inputDataReader.ReadString(bytesRead);
Debug.WriteLine($"RX sync:{response}");
}
...
The code is not suitable for production but it confirmed my software and hardware configuration worked.
The thread '<No Name>' (0x2) has exited with code 0 (0x0).
devMobile.IoT.Rak811.NetworkJoinOTAA starting
COM5,COM6
TX: work_mode 32 bytes to output stream.
TX: 32 bytes via COM6
RX sync:UART1 work mode: RUI_UART_NORAMAL
Current work_mode:LoRaWAN, join_mode:OTAA, Class: A
Initialization OK
TX: region 33 bytes to output stream.
TX: 33 bytes via COM6
RX sync:OK
TX: join_mode 32 bytes to output stream.
TX: 32 bytes via COM6
RX sync:OK
TX: dev_eui 45 bytes to output stream.
TX: 45 bytes via COM6
RX sync:OK
TX: app_eui 45 bytes to output stream.
TX: 45 bytes via COM6
RX sync:OK
TX: app_key 61 bytes to output stream.
TX: 61 bytes via COM6
RX sync:OK
TX: confirm 30 bytes to output stream.
TX: 30 bytes via COM6
RX sync:OK
TX: join 9 bytes to output stream.
TX: 9 bytes via COM6
RX sync:
RX sync:
RX sync:
RX sync:
RX sync:OK Join Success
TX: send 43 bytes to output stream.
TX: 43 bytes via COM6
TX: send 43 bytes to output stream.
TX: 43 bytes via COM6
RX sync:OK
at+recv=0,-59,9,0
In the Visual Studio 2019 debug out put I could see messages getting sent and then after a short delay they were visible in the TTN console.
I then modified the confirmed flag and in the TTN console I could see how they were processed differently.
Confirmed messagesUnconfirmed messages
I could receive messages but as the RAK 811 module can be configured to be a Class C device there didn’t appear to be a way to receive a message without sending one which seemed a bit odd.
The next step is to get Authentication By Personalisation(ABP) working.
When writing communications libraries one of the first things I try and get working is a “factory reset”. At some stage I will misconfigure the device so badly that it won’t work anymore and having a way to return to the device to its original configuration is really useful.
//---------------------------------------------------------------------------------
// Copyright (c) June 2020, devMobile Software
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//---------------------------------------------------------------------------------
// nanoff --target ST_STM32F769I_DISCOVERY --update
//#define SERIAL_SYNC_READ
//#define HARDWARE_RESET
//#define SOFTWARE_RESTART
//#define DEVICE_STATUS
//#define LORA_STATUS
namespace devMobile.IoT.Rak811.FactoryReset
{
using System;
using System.Diagnostics;
using System.Threading;
using Windows.Devices.Gpio;
using Windows.Devices.SerialCommunication;
using Windows.Storage.Streams;
public class Program
{
private const string SerialPortId = "COM6";
public static void Main()
{
SerialDevice serialDevice;
Debug.WriteLine("devMobile.IoT.Rak811.FactoryReset starting");
Debug.WriteLine(Windows.Devices.SerialCommunication.SerialDevice.GetDeviceSelector());
try
{
#if HARDWARE_RESET
GpioPin resetPin = GpioController.GetDefault().OpenPin(PinNumber('J', 4));
resetPin.SetDriveMode(GpioPinDriveMode.Output);
resetPin.Write(GpioPinValue.Low);
#endif
serialDevice = SerialDevice.FromId(SerialPortId);
// set parameters
serialDevice.BaudRate = 9600;
serialDevice.Parity = SerialParity.None;
serialDevice.StopBits = SerialStopBitCount.One;
serialDevice.Handshake = SerialHandshake.None;
serialDevice.DataBits = 8;
serialDevice.ReadTimeout = new TimeSpan(0, 0, 30);
serialDevice.WriteTimeout = new TimeSpan(0, 0, 4);
DataWriter outputDataWriter = new DataWriter(serialDevice.OutputStream);
#if SERIAL_SYNC_READ
DataReader inputDataReader = new DataReader(serialDevice.InputStream);
#else
serialDevice.DataReceived += SerialDevice_DataReceived;
#endif
// set a watch char to be notified when it's available in the input stream
serialDevice.WatchChar = '\n';
while (true)
{
#if HARDWARE_RESET
resetPin.Write(GpioPinValue.High);
Thread.Sleep(10);
resetPin.Write(GpioPinValue.Low);
#endif
#if SOFTWARE_RESTART
uint bytesWritten = outputDataWriter.WriteString("at+set_config=device:restart\r\n");
Debug.WriteLine($"TX: {outputDataWriter.UnstoredBufferLength} bytes to output stream.");
// calling the 'Store' method on the data writer actually sends the data
uint txByteCount = outputDataWriter.Store();
Debug.WriteLine($"TX: {txByteCount} bytes via {serialDevice.PortName}");
#endif
#if DEVICE_STATUS
uint bytesWritten = outputDataWriter.WriteString("at+get_config=device:status\r\n");
Debug.WriteLine($"TX: {outputDataWriter.UnstoredBufferLength} bytes to output stream.");
// calling the 'Store' method on the data writer actually sends the data
uint txByteCount = outputDataWriter.Store();
Debug.WriteLine($"TX: {txByteCount} bytes via {serialDevice.PortName}");
#endif
#if LORA_STATUS
uint bytesWritten = outputDataWriter.WriteString("at+get_config=lora:status\r\n");
Debug.WriteLine($"TX: {outputDataWriter.UnstoredBufferLength} bytes to output stream.");
// calling the 'Store' method on the data writer actually sends the data
uint txByteCount = outputDataWriter.Store();
Debug.WriteLine($"TX: {txByteCount} bytes via {serialDevice.PortName}");
#endif
#if SERIAL_SYNC_READ
// June 2020 appears to be limited to 256 chars
uint bytesRead = inputDataReader.Load(50);
Debug.WriteLine($"RXs :{bytesRead} bytes read from {serialDevice.PortName}");
if (bytesRead > 0)
{
String response = inputDataReader.ReadString(bytesRead);
Debug.WriteLine($"RX sync:{response}");
}
#endif
Thread.Sleep(20000);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
private static void SerialDevice_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
switch (e.EventType)
{
case SerialData.Chars:
//Debug.WriteLine("RX SerialData.Chars");
break;
case SerialData.WatchChar:
Debug.WriteLine("RX: SerialData.WatchChar");
SerialDevice serialDevice = (SerialDevice)sender;
using (DataReader inputDataReader = new DataReader(serialDevice.InputStream))
{
inputDataReader.InputStreamOptions = InputStreamOptions.Partial;
// read all available bytes from the Serial Device input stream
uint bytesRead = inputDataReader.Load(serialDevice.BytesToRead);
Debug.WriteLine($"RXa: {bytesRead} bytes read from {serialDevice.PortName}");
if (bytesRead > 0)
{
String response = inputDataReader.ReadString(bytesRead);
Debug.WriteLine($"RX:{response}");
}
}
break;
default:
Debug.Assert(false, $"e.EventType {e.EventType} unknown");
break;
}
}
static int PinNumber(char port, byte pin)
{
if (port < 'A' || port > 'J')
throw new ArgumentException();
return ((port - 'A') * 16) + pin;
}
}
}
Initially I tried strobing D8 which is connected to the reset pin on the RAK811 module.
UART1 work mode: RUI_UART_NORAMAL
Current work_mode:LoRaWAN, join_mode:OTAA, Class: A
Initialization OK
I then used the RAK Serial Port Tool to see if the configuration had changed
OK Work Mode: LoRaWAN
Region: AS923
Send_interval: 600s
Auto send status: false.
Join_mode: OTAA
DevEui: ...
AppEui: ...
AppKey: ...
Class: A
Joined Network:false
IsConfirm: false
AdrEnable: true
EnableRepeaterSupport: false
RX2_CHANNEL_FREQUENCY: 923200000, RX2_CHANNEL_DR:2
RX_WINDOW_DURATION: 3000ms
RECEIVE_DELAY_1: 1000ms
RECEIVE_DELAY_2: 2000ms
JOIN_ACCEPT_DELAY_1: 5000ms
JOIN_ACCEPT_DELAY_2: 6000ms
Current Datarate: 2
Primeval Datarate: 2
ChannelsTxPower: 0
UpLinkCounter: 0
DownLinkCounter: 0
The device reset but the settings appear not to have returned to factory.
STM32F691Discovery with EVB connected with Jumpers
The STM32F691DISCOVERY board has an Arduino Uno R3 format socket which I wanted to be able to plug the EVB into. After removing R8,R17 & R19 I put the EVB on the STM32F691DISCOVERY and could still retrieve the RAK811 module version information.
STM32F691Discovery with EVB plugged into Arduino headers
The thread '<No Name>' (0x2) has exited with code 0 (0x0).
devMobile.IoT.Rfm9x.ShieldSerial starting
COM5,COM6
TX: 12 bytes to output stream.
TX: 12 bytes via COM6
RXs :19 bytes read from COM6
RX sync:OK V3.0.0.13.H.T3
TX: 12 bytes to output stream.
TX: 12 bytes via COM6
RXs :19 bytes read from COM6
RX sync:OK V3.0.0.13.H.T3
My first step was to check what serial ports were available (COM5 & COM6) on the STM32F691Discovery and what pins they were connected to. (COM6 Arduino D0 & D1). Then check that these would work with the EVB pin assignments.
RAK 811 EVB schematic pg1RAK 811 EVB schematic pg2
My first test was was a simple loopback based on the nanoFramework samples Serial Communications example.
STM32F691Discovery with jumper loopback
//---------------------------------------------------------------------------------
// Copyright (c) June 2020, devMobile Software
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//---------------------------------------------------------------------------------
//#define ESP32_WROOM //nanoff --target ESP32_WROOM_32 --serialport COM4 --update
//#define NETDUINO3_WIFI // nanoff --target NETDUINO3_WIFI --update
//#define MBN_QUAIL // nanoff --target MBN_QUAIL --update
//#define ST_NUCLEO64_F091RC // nanoff --target ST_NUCLEO64_F091RC --update
//#define ST_NUCLEO144_F746ZG //nanoff --target ST_NUCLEO144_F746ZG --update
#define ST_STM32F769I_DISCOVERY // nanoff --target ST_STM32F769I_DISCOVERY --update
namespace devMobile.IoT.Rak811.ShieldSerial
{
using System;
using System.Diagnostics;
using System.Threading;
using Windows.Devices.SerialCommunication;
using Windows.Storage.Streams;
#if ESP32_WROOM_32_LORA_1_CHANNEL
using nanoFramework.Hardware.Esp32;
#endif
public class Program
{
#if ESP32_WROOM
private const string SerialPortId = "";
#endif
#if NETDUINO3_WIFI
private const string SpiBusId = "";
#endif
#if MBN_QUAIL
private const string SpiBusId = "";
#endif
#if ST_NUCLEO64_F091RC
private const string SpiBusId = "";
#endif
#if ST_NUCLEO144_F746ZG
private const string SpiBusId = "";
#endif
#if ST_STM32F429I_DISCOVERY
private const string SpiBusId = "";
#endif
#if ST_STM32F769I_DISCOVERY
private const string SerialPortId = "COM6";
#endif
public static void Main()
{
SerialDevice serialDevice;
Debug.WriteLine("devMobile.IoT.Rfm9x.ShieldSerial starting");
Debug.WriteLine(Windows.Devices.SerialCommunication.SerialDevice.GetDeviceSelector());
try
{
// set GPIO functions for COM2 (this is UART1 on ESP32)
#if ESP32_WROOM
Configuration.SetPinFunction(Gpio.IO04, DeviceFunction.COM2_TX);
Configuration.SetPinFunction(Gpio.IO05, DeviceFunction.COM2_RX);
#endif
serialDevice = SerialDevice.FromId(SerialPortId);
// set parameters
serialDevice.BaudRate = 9600;
serialDevice.Parity = SerialParity.None;
serialDevice.StopBits = SerialStopBitCount.One;
serialDevice.Handshake = SerialHandshake.None;
serialDevice.DataBits = 8;
serialDevice.ReadTimeout = new TimeSpan(0, 0, 30);
serialDevice.WriteTimeout = new TimeSpan(0, 0, 4);
DataWriter outputDataWriter = new DataWriter(serialDevice.OutputStream);
#if SERIAL_SYNC_READ
DataReader inputDataReader = new DataReader(serialDevice.InputStream);
#else
serialDevice.DataReceived += SerialDevice_DataReceived;
#endif
// set a watch char to be notified when it's available in the input stream
serialDevice.WatchChar = '\n';
while (true)
{
uint bytesWritten = outputDataWriter.WriteString("at+version\r\n");
Debug.WriteLine($"TX: {outputDataWriter.UnstoredBufferLength} bytes to output stream.");
// calling the 'Store' method on the data writer actually sends the data
uint txByteCount = outputDataWriter.Store();
Debug.WriteLine($"TX: {txByteCount} bytes via {serialDevice.PortName}");
#if SERIAL_SYNC_READ
uint bytesRead = inputDataReader.Load(50);
Debug.WriteLine($"RXs :{bytesRead} bytes read from {serialDevice.PortName}");
if (bytesRead > 0)
{
String response = inputDataReader.ReadString(bytesRead);
Debug.WriteLine($"RX sync:{response}");
}
#endif
Thread.Sleep(20000);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
private static void SerialDevice_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
switch(e.EventType)
{
case SerialData.Chars:
//Debug.WriteLine("RX SerialData.Chars");
break;
case SerialData.WatchChar:
Debug.WriteLine("RX: SerialData.WatchChar");
SerialDevice serialDevice = (SerialDevice)sender;
using (DataReader inputDataReader = new DataReader(serialDevice.InputStream))
{
inputDataReader.InputStreamOptions = InputStreamOptions.Partial;
// read all available bytes from the Serial Device input stream
uint bytesRead = inputDataReader.Load(serialDevice.BytesToRead);
Debug.WriteLine($"RXa: {bytesRead} bytes read from {serialDevice.PortName}");
if (bytesRead > 0)
{
String response = inputDataReader.ReadString(bytesRead);
Debug.WriteLine($"RX:{response}");
}
}
break;
default:
Debug.Assert(false, $"e.EventType {e.EventType} unknown");
break;
}
}
}
}
After some tinkering I could successfully transmit and receive a string.
The next step was to connect my EVB and sent the AT Command to request the LoRaWAN module version information
STM32F691Discovery with EVB connected with Jumpers
The thread '<No Name>' (0x2) has exited with code 0 (0x0).
devMobile.IoT.Rfm9x.ShieldSerial starting
COM5,COM6
TX: 12 bytes to output stream.
TX: 12 bytes via COM6
RX: SerialData.WatchChar
RXa: 19 bytes read from COM6
RX:OK V3.0.0.13.H.T3
TX: 12 bytes to output stream.
TX: 12 bytes via COM6
RX: SerialData.WatchChar
RXa: 19 bytes read from COM6
RX:OK V3.0.0.13.H.T3
The response was the same as I got with the RAK Serial Port Tool which was positive.
Version number check with RAK Serial Port tool
I need to do some more digging into how serialDevice.WatchChar = ‘\n’ works for synchronous reads.
Plus removing R17 & R19 there is no interaction with D11 & D10 which are normally used by the Serial Peripheral Interface(SPI) port so I can plug the shield directly into the STM32F691Discovery board.
My plan was to get an initial version of the library working with the STM32F691Discovery, then port it to the Netduino 3 Wifi (possible serial port pin issues) , ST_NUCLEO144_F746ZG, and ST_NUCLEO64_F091RC (possible issues with available flash).
The first step was to figure out the configuration using the 00.Shield project. After some experimentation I figured out the SPI port connected to D10-D13 was SPI2 (SPI1 is connected to the MicroSD port)
//---------------------------------------------------------------------------------
// Copyright (c) April 2020, devMobile Software
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//---------------------------------------------------------------------------------
//#define ESP32_WROOM_32_LORA_1_CHANNEL //nanoff --target ESP32_WROOM_32 --serialport COM4 --update
#define NETDUINO3_WIFI // nanoff --target NETDUINO3_WIFI --update
//NOTE May 2020 ST_NUCLEO64_F091RC device doesn't work something broken in SPI configuration
//#define ST_NUCLEO64_F091RC // nanoff --target ST_NUCLEO64_F091RC --update
//#define ST_STM32F429I_DISCOVERY //nanoff --target ST_STM32F429I_DISCOVERY --update
//NOTE May 2020 ST_STM32F769I_DISCOVERY device doesn't work SPI2 mappings broken
//#define ST_STM32F769I_DISCOVERY // nanoff --target ST_STM32F769I_DISCOVERY --update
namespace devMobile.IoT.Rfm9x.ShieldSPI
{
using System;
using System.Diagnostics;
using System.Threading;
using Windows.Devices.Gpio;
using Windows.Devices.Spi;
#if ESP32_WROOM_32_LORA_1_CHANNEL
using nanoFramework.Hardware.Esp32;
#endif
public class Program
{
private const byte RegVersion = 0x42;
#if ESP32_WROOM_32_LORA_1_CHANNEL
private const string SpiBusId = "SPI1";
#endif
#if NETDUINO3_WIFI
private const string SpiBusId = "SPI2";
#endif
#if ST_NUCLEO64_F091RC
private const string SpiBusId = "SPI1";
#endif
#if ST_STM32F429I_DISCOVERY
private const string SpiBusId = "SPI5";
#endif
#if ST_STM32F769I_DISCOVERY
private const string SpiBusId = "SPI5";
#endif
public static void Main()
{
#if ESP32_WROOM_32_LORA_1_CHANNEL // No reset line for this device as it isn't connected on SX127X
int ledPinNumber = Gpio.IO17;
int chipSelectPinNumber = Gpio.IO16;
#endif
#if NETDUINO3_WIFI
int ledPinNumber = PinNumber('A', 10);
// Arduino D10->PB10
int chipSelectPinNumber = PinNumber('B', 10);
// Arduino D9->PE5
int resetPinNumber = PinNumber('E', 5);
#endif
#if ST_NUCLEO64_F091RC // No LED for this device as driven by D13 the SPI CLK line
// Arduino D10->PB6
int chipSelectPinNumber = PinNumber('B', 6);
// Arduino D9->PC7
int resetPinNumber = PinNumber('C', 7);
#endif
#if ST_STM32F429I_DISCOVERY // No reset line for this device as I didn't bother with jumper to SX127X pin
int ledPinNumber = PinNumber('G', 14);
int chipSelectPinNumber = PinNumber('C', 2);
#endif
#if ST_STM32F769I_DISCOVERY
int ledPinNumber = PinNumber('J', 5);
// Arduino D10->PA11
int chipSelectPinNumber = PinNumber('A', 11);
// Arduino D9->PH6
int resetPinNumber = PinNumber('H', 6);
#endif
Debug.WriteLine("devMobile.IoT.Rfm9x.ShieldSPI starting");
try
{
GpioController gpioController = GpioController.GetDefault();
#if NETDUINO3_WIFI|| ST_NUCLEO64_F091RC || ST_STM32F769I_DISCOVERY
// Setup the reset pin
GpioPin resetGpioPin = gpioController.OpenPin(resetPinNumber);
resetGpioPin.SetDriveMode(GpioPinDriveMode.Output);
resetGpioPin.Write(GpioPinValue.High);
#endif
#if ESP32_WROOM_32_LORA_1_CHANNEL || NETDUINO3_WIFI|| ST_STM32F429I_DISCOVERY || ST_STM32F769I_DISCOVERY
// Setup the onboard LED
GpioPin led = gpioController.OpenPin(ledPinNumber);
led.SetDriveMode(GpioPinDriveMode.Output);
#endif
#if ESP32_WROOM_32_LORA_1_CHANNEL
Configuration.SetPinFunction(nanoFramework.Hardware.Esp32.Gpio.IO12, DeviceFunction.SPI1_MISO);
Configuration.SetPinFunction(nanoFramework.Hardware.Esp32.Gpio.IO13, DeviceFunction.SPI1_MOSI);
Configuration.SetPinFunction(nanoFramework.Hardware.Esp32.Gpio.IO14, DeviceFunction.SPI1_CLOCK);
#endif
var settings = new SpiConnectionSettings(chipSelectPinNumber)
{
ClockFrequency = 500000,
Mode = SpiMode.Mode0,// From SemTech docs pg 80 CPOL=0, CPHA=0
SharingMode = SpiSharingMode.Shared,
};
using (SpiDevice device = SpiDevice.FromId(SpiBusId, settings))
{
Thread.Sleep(500);
while (true)
{
byte[] writeBuffer = new byte[] { RegVersion, 0x0 };
byte[] readBuffer = new byte[writeBuffer.Length];
device.TransferFullDuplex(writeBuffer, readBuffer);
Debug.WriteLine(String.Format("Register 0x{0:x2} - Value 0X{1:x2}", RegVersion, readBuffer[1]));
#if ESP32_WROOM_32_LORA_1_CHANNEL|| NETDUINO3_WIFI || ST_STM32F429I_DISCOVERY || ST_STM32F769I_DISCOVERY
led.Toggle();
#endif
Thread.Sleep(10000);
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
#if NETDUINO3_WIFI || ST_NUCLEO64_F091RC || ST_STM32F429I_DISCOVERY || ST_STM32F769I_DISCOVERY
static int PinNumber(char port, byte pin)
{
if (port < 'A' || port > 'J')
throw new ArgumentException();
return ((port - 'A') * 16) + pin;
}
#endif
}
}
In the Visual Studio output windows I could see the correct version register value
The thread '<No Name>' (0x2) has exited with code 0 (0x0).
devMobile.IoT.Rfm9x.ShieldSPI starting
Register 0x42 - Value 0X12
Register 0x42 - Value 0X12
...
After checking the configuration of the reset (D9) and interrupt (D2) pins in other test harness programs my final configuration for Rfm9xLoRaDevice client was
//---------------------------------------------------------------------------------
// Copyright (c) April/May 2020, devMobile Software
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//---------------------------------------------------------------------------------
//#define ADDRESSED_MESSAGES_PAYLOAD
//#define ESP32_WROOM_32_LORA_1_CHANNEL //nanoff --target ESP32_WROOM_32 --serialport COM4 --update
#define NETDUINO3_WIFI // nanoff --target NETDUINO3_WIFI --update
//#define ST_STM32F429I_DISCOVERY //nanoff --target ST_STM32F429I_DISCOVERY --update
namespace devMobile.IoT.Rfm9x.LoRaDeviceClient
{
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
#if ESP32_WROOM_32_LORA_1_CHANNEL
using nanoFramework.Hardware.Esp32;
#endif
using devMobile.IoT.Rfm9x;
class Program
{
private const double Frequency = 915000000.0;
#if ST_STM32F429I_DISCOVERY
private const string DeviceName = "Disco429";
private const string SpiBusId = "SPI5";
#endif
#if ESP32_WROOM_32_LORA_1_CHANNEL
private const string DeviceName = "ESP32";
private const string SpiBusId = "SPI1";
#endif
#if NETDUINO3_WIFI
private const string DeviceName = "N3W";
private const string SpiBusId = "SPI2";
#endif
#if ADDRESSED_MESSAGES_PAYLOAD
private const string DeviceName = "LoRaIoT1";
#endif
static void Main()
{
byte MessageCount = System.Byte.MaxValue;
#if ST_STM32F429I_DISCOVERY
int chipSelectPinNumber = PinNumber('C', 2);
int resetPinNumber = PinNumber('C', 3);
int interruptPinNumber = PinNumber('A', 4);
#endif
#if ESP32_WROOM_32_LORA_1_CHANNEL
int chipSelectPinNumber = Gpio.IO16;
int interruptPinNumber = Gpio.IO26;
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, interruptPinNumber);
#endif
#if NETDUINO3_WIFI
// Arduino D10->PB10
int chipSelectPinNumber = PinNumber('B', 10);
// Arduino D9->PE5
int resetPinNumber = PinNumber('E', 5);
// Arduino D2->PA3
int interruptPinNumber = PinNumber('A', 3);
#endif
#if ST_STM32F429I_DISCOVERY || NETDUINO3_WIFI
Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SpiBusId, chipSelectPinNumber, resetPinNumber, interruptPinNumber);
#endif
rfm9XDevice.Initialise(Frequency, paBoost: true);
#if DEBUG
rfm9XDevice.RegisterDump();
#endif
rfm9XDevice.OnReceive += Rfm9XDevice_OnReceive;
#if ADDRESSED_MESSAGES_PAYLOAD
rfm9XDevice.Receive(UTF8Encoding.UTF8.GetBytes(DeviceName));
#else
rfm9XDevice.Receive();
#endif
rfm9XDevice.OnTransmit += Rfm9XDevice_OnTransmit;
Thread.Sleep(10000);
while (true)
{
string messageText = string.Format("Hello from {0} ! {1}", DeviceName, MessageCount);
MessageCount -= 1;
byte[] messageBytes = UTF8Encoding.UTF8.GetBytes(messageText);
Debug.WriteLine(string.Format("{0}-TX {1} byte message {2}", DateTime.UtcNow.ToString("HH:mm:ss"), messageBytes.Length, messageText));
#if ADDRESSED_MESSAGES_PAYLOAD
rfm9XDevice.Send(UTF8Encoding.UTF8.GetBytes(HostName), messageBytes);
#else
rfm9XDevice.Send(messageBytes);
#endif
Thread.Sleep(10000);
}
}
private static void Rfm9XDevice_OnReceive(object sender, Rfm9XDevice.OnDataReceivedEventArgs e)
{
try
{
// Remove unprintable characters from messages
for (int index = 0; index < e.Data.Length; index++)
{
if ((e.Data[index] < 0x20) || (e.Data[index] > 0x7E))
{
e.Data[index] = 0x20;
}
}
string messageText = UTF8Encoding.UTF8.GetString(e.Data, 0, e.Data.Length);
#if ADDRESSED_MESSAGES_PAYLOAD
string addressText = UTF8Encoding.UTF8.GetString(e.Address, 0, e.Address.Length);
Debug.WriteLine(string.Format(@"{0}-RX From {1} PacketSnr {2} Packet RSSI {3}dBm RSSI {4}dBm ={5} ""{6}""", DateTime.UtcNow.ToString("HH:mm:ss"), addressText, e.PacketSnr, e.PacketRssi, e.Rssi, e.Data.Length, messageText));
#else
Debug.WriteLine(string.Format(@"{0}-RX PacketSnr {1} Packet RSSI {2}dBm RSSI {3}dBm ={4} ""{5}""", DateTime.UtcNow.ToString("HH:mm:ss"), e.PacketSnr, e.PacketRssi, e.Rssi, e.Data.Length, messageText));
#endif
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
private static void Rfm9XDevice_OnTransmit(object sender, Rfm9XDevice.OnDataTransmitedEventArgs e)
{
Debug.WriteLine(string.Format("{0}-TX Done", DateTime.UtcNow.ToString("HH:mm:ss")));
}
#if ST_STM32F429I_DISCOVERY || NETDUINO3_WIFI
static int PinNumber(char port, byte pin)
{
if (port < 'A' || port > 'J')
throw new ArgumentException();
return ((port - 'A') * 16) + pin;
}
#endif
}
}
The sample client could reliable send and receive messages.
The thread '<No Name>' (0x2) has exited with code 0 (0x0).
Register dump
Register 0x00 - Value 0X7A
Register 0x01 - Value 0X80
Register 0x02 - Value 0X1A
Register 0x03 - Value 0X0B
Register 0x04 - Value 0X00
Register 0x05 - Value 0X52
Register 0x06 - Value 0XE4
Register 0x07 - Value 0XC0
Register 0x08 - Value 0X00
Register 0x09 - Value 0XCF
Register 0x0A - Value 0X09
Register 0x0B - Value 0X2B
Register 0x0C - Value 0X20
Register 0x0D - Value 0X01
Register 0x0E - Value 0X80
Register 0x0F - Value 0X00
Register 0x10 - Value 0X00
Register 0x11 - Value 0X00
Register 0x12 - Value 0X00
Register 0x13 - Value 0X00
Register 0x14 - Value 0X00
Register 0x15 - Value 0X00
Register 0x16 - Value 0X00
Register 0x17 - Value 0X00
Register 0x18 - Value 0X10
Register 0x19 - Value 0X00
Register 0x1A - Value 0X00
Register 0x1B - Value 0X00
Register 0x1C - Value 0X00
Register 0x1D - Value 0X72
Register 0x1E - Value 0X70
Register 0x1F - Value 0X64
Register 0x20 - Value 0X00
Register 0x21 - Value 0X08
Register 0x22 - Value 0X01
Register 0x23 - Value 0XFF
Register 0x24 - Value 0X00
Register 0x25 - Value 0X00
Register 0x26 - Value 0X04
Register 0x27 - Value 0X00
Register 0x28 - Value 0X00
Register 0x29 - Value 0X00
Register 0x2A - Value 0X00
Register 0x2B - Value 0X00
Register 0x2C - Value 0X00
Register 0x2D - Value 0X50
Register 0x2E - Value 0X14
Register 0x2F - Value 0X45
Register 0x30 - Value 0X55
Register 0x31 - Value 0XC3
Register 0x32 - Value 0X05
Register 0x33 - Value 0X27
Register 0x34 - Value 0X1C
Register 0x35 - Value 0X0A
Register 0x36 - Value 0X03
Register 0x37 - Value 0X0A
Register 0x38 - Value 0X42
Register 0x39 - Value 0X12
Register 0x3A - Value 0X49
Register 0x3B - Value 0X1D
Register 0x3C - Value 0X00
Register 0x3D - Value 0XAF
Register 0x3E - Value 0X00
Register 0x3F - Value 0X00
Register 0x40 - Value 0X00
Register 0x41 - Value 0X00
Register 0x42 - Value 0X12
00:00:25-TX 20 byte message Hello from N3W ! 255
00:00:25-TX Done
00:00:35-TX 20 byte message Hello from N3W ! 254
00:00:35-TX Done
00:00:45-TX 20 byte message Hello from N3W ! 253
00:00:45-TX Done
00:00:46-RX PacketSnr 9.50 Packet RSSI -70dBm RSSI -110dBm =59 " LoRaIoT1Maduino2at 43.9,ah 75,wsa 1,wsg 2,wd 36.00,r 0.00,"
00:00:55-TX 20 byte message Hello from N3W ! 252
00:00:55-TX Done
00:01:05-TX 20 byte message Hello from N3W ! 251
00:01:05-TX Done
Overall the process was fairly painless and helped identify a bug in the configuration of the Mode register in one of the test harness applications.
For the final revision my nanoFrameworkSX127X Library test harness I checked interrupts were working for the interleaved transmission and reception of messages.
private void InterruptGpioPin_ValueChanged(object sender, GpioPinValueChangedEventArgs e)
{
if (e.Edge != GpioPinEdge.RisingEdge)
{
return;
}
byte irqFlags = this.RegisterReadByte(0x12); // RegIrqFlags
Console.WriteLine($"RegIrqFlags 0X{irqFlags:x2}");
if ((irqFlags & 0b01000000) == 0b01000000) // RxDone
{
Console.WriteLine("Receive-Message");
byte currentFifoAddress = this.RegisterReadByte(0x10); // RegFifiRxCurrent
this.RegisterWriteByte(0x0d, currentFifoAddress); // RegFifoAddrPtr
byte numberOfBytes = this.RegisterReadByte(0x13); // RegRxNbBytes
// Allocate buffer for message
byte[] messageBytes = this.RegisterRead(0X0, numberOfBytes);
// Remove unprintable characters from messages
for (int index = 0; index < messageBytes.Length; index++)
{
if ((messageBytes[index] < 0x20) || (messageBytes[index] > 0x7E))
{
messageBytes[index] = 0x20;
}
}
string messageText = UTF8Encoding.UTF8.GetString(messageBytes,0, messageBytes.Length);
Console.WriteLine($"Received {messageBytes.Length} byte message {messageText}");
}
if ((irqFlags & 0b00001000) == 0b00001000) // TxDone
{
this.RegisterWriteByte(0x01, 0b10000101); // RegOpMode set LoRa & RxContinuous
Console.WriteLine("Transmit-Done");
}
this.RegisterWriteByte(0x40, 0b00000000); // RegDioMapping1 0b00000000 DI0 RxReady & TxReady
this.RegisterWriteByte(0x12, 0xff);// RegIrqFlags
}
…
class Program
{
static void Main()
{
int SendCount = 0;
#if ST_STM32F429I_DISCOVERY
int chipSelectPinNumber = PinNumber('C', 2);
int resetPinNumber = PinNumber('C', 3);
int interruptPinNumber = PinNumber('A', 4);
#endif
#if ESP32_WROOM_32_LORA_1_CHANNEL
int chipSelectPinNumber = Gpio.IO16;
int interruptPinNumber = Gpio.IO26;
#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, interruptPinNumber);
#endif
#if ST_STM32F429I_DISCOVERY
Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SpiBusId, chipSelectPinNumber, resetPinNumber, interruptPinNumber);
#endif
Thread.Sleep(500);
// Put device into LoRa + Standby mode
rfm9XDevice.RegisterWriteByte(0x01, 0b10000000); // 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
// Interrupt on TxDone
rfm9XDevice.RegisterWriteByte(0x40, 0b01000000); // RegDioMapping1 0b00000000 DI0 TxDone
while (true)
{
// Set the Register Fifo address pointer
rfm9XDevice.RegisterWriteByte(0x0E, 0x00); // 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}");
rfm9XDevice.RegisterWriteByte(0x01, 0b10000011); // RegOpMode
Thread.Sleep(10000);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
The diagnostic output shows inbound and outbound messages
This code implements the reception of messages builds on my transmit basic sample. I had to add a simple for loop to replace un-printable characters in the received message with spaces as nanoFrameworkUTF8Encoding.UTF8.GetString was throwing exceptions.
STM32F429 Discovery+ Dragino LoRa shield with Armtronix device
The code now works on STM32F429 Discovery and ESP32 WROOM platforms. (manual update nanoFramework.Hardware.Esp32 NuGet reference required)
Sparkfun LoRa Gateway 1 Channel schematic
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.
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.
SX127X RegOpMode details
// 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…