Alora RFM1262 – LoRa Mesh Network

TheSoftware

Part 4 – The other stuff

There are a few more SW modules involved in this tutorial, but I will only go into details for the setup() and loop() functions.
Other functions, like the display support, BLE UART output and my logging functions are relatively simple and you can check how they work in the source codes.

The setup()

As in all Arduino applications, setup() is called once after a fresh start or reboot of the device. Here we define out unique device ID, which will be used to identify the device in the Mesh network, initialize BLE and display (if attached) and initialize the Alora RFM1262 LoRa hardware.

Creating the node ID.

Each node in the Mesh network needs a unique identifier. The easiest way to create such an identifier is to use the MAC address of the device. The library that I use under the hood for the LoRa communication (SX126x-Arduino) has a function called BoardGetUniqueId, that I use for this purpose. The function returns an byte array of 8 bytes that is different for each device. This byte array is then used to create the node ID.

// Create node ID
uint8_t deviceMac[8];

BoardGetUniqueId(deviceMac);

deviceID += (uint32_t)deviceMac[2] << 24;
deviceID += (uint32_t)deviceMac[3] << 16;
deviceID += (uint32_t)deviceMac[4] << 8;
deviceID += (uint32_t)deviceMac[5];

myLog_n("Mesh NodeId = %08lX", deviceID);

Next is the initialization of the display and BLE UART functions

#ifdef HAS_DISPLAY
	initDisplay();
#endif

	// Initialize BLE
	initBLE();

As you can see I use #define compiler directives to integrate or skip certain parts of the code.

Last step is the initialization of the LoRa transceiver HW and start the Mesh network handler.

Depending on the used HW the LoRa transceiver initialization is slightly different. If the target HW is an ESP32 or an Adafruit nRF52 Feather board, the LoRa library needs to know the assignment for the GPIOs that are connecting the ESP32 or nRF52 with the SX1262 transceiver. If the target HW is the ISP4520 module the initialization is much easier, because the connections are predefined in the library.

	// Initialize the LoRa
#if defined(ESP32) || defined(ADAFRUIT)
	// Define the HW configuration between MCU and SX126x
	hwConfig.CHIP_TYPE = SX1262_CHIP;		  // eByte E22 module with an SX1262
	hwConfig.PIN_LORA_RESET = PIN_LORA_RESET; // LORA RESET
	hwConfig.PIN_LORA_NSS = PIN_LORA_NSS;	 // LORA SPI CS
	hwConfig.PIN_LORA_SCLK = PIN_LORA_SCLK;   // LORA SPI CLK
	hwConfig.PIN_LORA_MISO = PIN_LORA_MISO;   // LORA SPI MISO
	hwConfig.PIN_LORA_DIO_1 = PIN_LORA_DIO_1; // LORA DIO_1
	hwConfig.PIN_LORA_BUSY = PIN_LORA_BUSY;   // LORA SPI BUSY
	hwConfig.PIN_LORA_MOSI = PIN_LORA_MOSI;   // LORA SPI MOSI
	hwConfig.RADIO_TXEN = RADIO_TXEN;		  // LORA ANTENNA TX ENABLE
	hwConfig.RADIO_RXEN = RADIO_RXEN;		  // LORA ANTENNA RX ENABLE
	hwConfig.USE_DIO2_ANT_SWITCH = true;	  // Example uses an eByte E22 module which uses RXEN and TXEN pins as antenna control
	hwConfig.USE_DIO3_TCXO = true;			  // Example uses an eByte E22 module which uses DIO3 to control oscillator voltage
	hwConfig.USE_DIO3_ANT_SWITCH = false;	 // Only Insight ISP4520 module uses DIO3 as antenna control

	if (lora_hardware_init(hwConfig) != 0)
	{
		myLog_e("Error in hardware init");
	}
#else // ISP4520
	if (lora_isp4520_init(SX1262) != 0)
	{
		myLog_e("Error in hardware init");
	}
#endif

Then we tell the Mesh network handler which function should be called if LoRa data was received.

MeshEvents.DataAvailable = OnLoraData;

The Mesh handler is running as a independent task in the background, handling the LoRa communication (sending and receiving) without interaction with the loop() task. The OnLoraData() function will be called from the Mesh handler if new data is available from the LoRa Mesh network.

See also  LED, Resistor, and Switch in Series: A Quick Guide

Last thing here is to start the Mesh handler. As the nRF52 has less available heap memory, the maximum number of nodes is limited to 30. On an ESP32, we can set this to the maximum possible number of 48 nodes.

	// Initialize the LoRa Mesh
#ifdef ESP32
	initMesh(&MeshEvents, 48);
#else
	initMesh(&MeshEvents, 30);
#endif

The OnLoraData callback function

Whenever a LoRa data package was received, this function is called with a pointer to the data, the size of the data and some transmission parameters.

Usually you would transfer the received data into a buffer and inform the loop() function about it. Then data handling would be done inside the loop().

But to keep the tutorial simple, we just print out the received data over the Serial port.

/**
 * Callback after a LoRa package was received
 * @param payload
 * 			Pointer to the received data
 * @param size
 * 			Length of the received package
 * @param rssi
 * 			Signal strength while the package was received
 * @param snr
 * 			Signal to noise ratio while the package was received
 */
void OnLoraData(uint8_t *rxPayload, uint16_t rxSize, int16_t rxRssi, int8_t rxSnr)
{
	Serial.println("-------------------------------------");
	Serial.println("Got");
	for (int idx = 0; idx < rxSize; idx++)
	{
		Serial.printf("%02X ", rxPayload[idx]);
	}
	Serial.printf("\n\n%s\n", rxPayload);
	Serial.println("-------------------------------------");
#if defined(HAS_DISPLAY) || defined(RED_ESP)
	digitalWrite(LED_BUILTIN, LOW);
#else
	digitalWrite(LED_BUILTIN, HIGH);
#endif
#ifdef ESP32
	ledOffTick.detach();
	ledOffTick.once(1, ledOff);
#else
	timer.attachInterrupt(&ledOff, 1000 * 1000); // microseconds
#endif
}

The loop()

As mentioned above, the loop() would normally include some data handling tasks, but in this simple tutorial we just print every 30 seconds the current list of known nodes to the Serial port (and display if available), select a random node and send a package to this node.

void loop()
{
	delay(30000);
	Serial.println("---------------------------------------------");
	if (xSemaphoreTake(accessNodeList, (TickType_t)1000) == pdTRUE)
	{
		numElements = numOfNodes();
		Serial.printf("%d nodes in the map\n", numElements + 1);
		Serial.printf("Node #01 id: %08X\n", deviceID);
		if (bleUARTisConnected) {
			char sendData[512] = {0};
			int sendLen = snprintf(sendData, 512, "%d nodes in the map\n", numElements + 1);
			bleUartWrite(sendData, sendLen);
			sendLen = snprintf(sendData, 512, "Node #01 id: %08X\n", deviceID);
			bleUartWrite(sendData, sendLen);
		}
#ifdef HAS_DISPLAY
		dispWriteHeader();
		char line[128];
		// sprintf(line, "%08X", deviceID);
		sprintf(line, "%02X%02X", (uint8_t)(deviceID >> 24), (uint8_t)(deviceID >> 16));
		dispWrite(line, 0, 11);
#endif
		for (int idx = 0; idx < numElements; idx++)
		{
			getNode(idx, nodeId[idx], firstHop[idx], numHops[idx]);
		}
		// Select random node to send a package
		getRoute(nodeId[random(0, numElements)], &routeToNode);
		// Release access to nodes list
		xSemaphoreGive(accessNodeList);
		// Prepare data
		outData.mark1 = 'L';
		outData.mark2 = 'o';
		outData.mark3 = 'R';
		if (routeToNode.firstHop != 0)
		{
			outData.dest = routeToNode.firstHop;
			outData.from = routeToNode.nodeId;
			outData.type = 2;
			Serial.printf("Queuing msg to hop to %08X over %08X\n", outData.from, outData.dest);
			if (bleUARTisConnected)
			{
				char sendData[512] = {0};
				int sendLen = snprintf(sendData, 512, "Queuing msg to hop to %08X over %08X\n", outData.from, outData.dest);
				bleUartWrite(sendData, sendLen);
			}
		}
		else
		{
			outData.dest = routeToNode.nodeId;
			outData.from = deviceID;
			outData.type = 1;
			Serial.printf("Queuing msg direct to %08X\n", outData.dest);
			if (bleUARTisConnected)
			{
				char sendData[512] = {0};
				int sendLen = snprintf(sendData, 512, "Queuing msg direct to %08X\n", outData.dest);
				bleUartWrite(sendData, sendLen);
			}
		}
		int dataLen = MAP_HEADER_SIZE + sprintf((char *)outData.data, ">>%08X<<", deviceID);
		// Add package to send queue
		if (!addSendRequest(&outData, dataLen))
		{
			Serial.println("Sending package failed");
			if (bleUARTisConnected)
			{
				char sendData[512] = {0};
				int sendLen = snprintf(sendData, 512, "Sending package failed\n");
				bleUartWrite(sendData, sendLen);
			}
		}

		// Display the nodes
		for (int idx = 0; idx < numElements; idx++)
		{
#ifdef HAS_DISPLAY
			if (firstHop[idx] == 0)
			{
				// sprintf(line, "%08X", nodeId[idx]);
				sprintf(line, "%02X%02X", (uint8_t)(nodeId[idx] >> 24), (uint8_t)(nodeId[idx] >> 16));
			}
			else
			{
				// sprintf(line, "%08X*", nodeId[idx]);
				sprintf(line, "%02X%02X*", (uint8_t)(nodeId[idx] >> 24), (uint8_t)(nodeId[idx] >> 16));
			}
			if (idx < 4)
			{
				dispWrite(line, 0, ((idx + 2) * 10) + 1);
			}
			else if (idx < 9)
			{
				dispWrite(line, 42, ((idx - 3) * 10) + 1);
			}
			else
			{
				dispWrite(line, 84, ((idx - 8) * 10) + 1);
			}
			
#endif
			if (firstHop[idx] == 0)
			{
				Serial.printf("Node #%02d id: %08X direct\n", idx + 2, nodeId[idx]);
				if (bleUARTisConnected)
				{
					char sendData[512] = {0};
					int sendLen = snprintf(sendData, 512, "Node #%02d id: %08LX direct\n", idx + 2, nodeId[idx]);
					bleUartWrite(sendData, sendLen);
				}
			}
			else
			{
				Serial.printf("Node #%02d id: %08X first hop %08X #hops %d\n", idx + 2, nodeId[idx], firstHop[idx], numHops[idx]);
				if (bleUARTisConnected)
				{
					char sendData[512] = {0};
					int sendLen = snprintf(sendData, 512, "Node #%02d id: %08X first hop %08X #hops %d\n", idx + 2, nodeId[idx], firstHop[idx], numHops[idx]);
					bleUartWrite(sendData, sendLen);
				}
			}
		}
#ifdef HAS_DISPLAY
		dispUpdate();
#endif
	}
	else
	{
		Serial.println("Could not access the nodes list");
	}

	Serial.println("---------------------------------------------");
}

And that’s it. If you have any questions, suggestions or problems with this tutorial, leave a message here or in my Github repository.