Skip to main content

STM32 LoRa Integration

Introduction

LoRa (Long Range) is a wireless communication technology designed for low-power, long-range connectivity, making it ideal for Internet of Things (IoT) applications. When combined with STM32 microcontrollers, developers can create powerful IoT solutions that communicate over distances of several kilometers while maintaining battery life for years.

In this tutorial, we'll explore how to integrate LoRa modules with STM32 microcontrollers, configure the necessary hardware connections, and write firmware to establish reliable LoRa communication.

What is LoRa?

LoRa is a physical layer protocol that defines the radio modulation technique for long-range communication. It uses a chirp spread spectrum (CSS) modulation that provides excellent resistance to interference while maximizing range and minimizing power consumption.

Key characteristics of LoRa include:

  • Range: 2-15 km in urban areas, up to 45 km in rural areas
  • Low power consumption (ideal for battery-powered devices)
  • Relatively low data rates (0.3 kbps to 50 kbps)
  • Operation in license-free ISM radio bands (usually 868 MHz in Europe, 915 MHz in North America)

LoRaWAN is a higher-level network protocol built on top of LoRa that defines how devices communicate with gateways connected to the internet.

Required Hardware

To follow this tutorial, you'll need:

  1. An STM32 development board (we'll use the Nucleo-F446RE in our examples)
  2. A LoRa transceiver module (such as the SX1276/SX1278)
  3. Jumper wires
  4. Breadboard (optional)
  5. USB cable for programming the STM32
  6. Antennas for the LoRa modules

Hardware Connection

The SX1276/78 LoRa module communicates with the STM32 microcontroller using the SPI protocol, plus a few additional pins for interrupt and control signals.

Here's the typical connection diagram:

Specific pin connections for the Nucleo-F446RE and SX1276:

STM32 PinSX1276 PinFunction
PA5SCKSPI Clock
PA6MISOSPI Master In Slave Out
PA7MOSISPI Master Out Slave In
PB6NSS/CSSPI Chip Select
PC7DIO0Interrupt
PA8RESETModule Reset
3.3V3.3VPower
GNDGNDGround

Software Requirements

For this tutorial, we'll use:

  1. STM32CubeIDE for project creation and coding
  2. STM32CubeMX for peripheral configuration
  3. A LoRa library for STM32 (we'll provide a custom library)

Step 1: Setting Up the STM32CubeIDE Project

Let's start by creating a new project and configuring the required peripherals:

  1. Open STM32CubeIDE and create a new STM32 project

  2. Select your board or microcontroller (in our case, NUCLEO-F446RE)

  3. Name your project, e.g., "STM32_LoRa_Integration"

  4. In the pinout configuration:

    • Enable SPI1 in Full-Duplex Master mode
    • Configure PA5, PA6, and PA7 for SPI1
    • Configure PB6 as GPIO_Output for NSS
    • Configure PC7 as GPIO_Input with EXTI for DIO0 interrupt
    • Configure PA8 as GPIO_Output for RESET
  5. In the Clock Configuration, set the system clock to 84 MHz

  6. Generate the code to create your project

Step 2: Creating a LoRa Library

To interact with the LoRa module efficiently, we'll create a simple library. First, create two new files in your project: lora.h and lora.c.

Here's the content for lora.h:

c
#ifndef LORA_H
#define LORA_H

#include "main.h"
#include <stdbool.h>
#include <string.h>

// LoRa module commands
#define LORA_REG_FIFO 0x00
#define LORA_REG_OP_MODE 0x01
#define LORA_REG_FRF_MSB 0x06
#define LORA_REG_FRF_MID 0x07
#define LORA_REG_FRF_LSB 0x08
#define LORA_REG_PA_CONFIG 0x09
#define LORA_REG_PA_RAMP 0x0A
#define LORA_REG_OCP 0x0B
#define LORA_REG_LNA 0x0C
#define LORA_REG_FIFO_ADDR_PTR 0x0D
#define LORA_REG_FIFO_TX_BASE_ADDR 0x0E
#define LORA_REG_FIFO_RX_BASE_ADDR 0x0F
#define LORA_REG_FIFO_RX_CURRENT_ADDR 0x10
#define LORA_REG_IRQ_FLAGS 0x12
#define LORA_REG_RX_NB_BYTES 0x13
#define LORA_REG_PKT_SNR_VALUE 0x19
#define LORA_REG_PKT_RSSI_VALUE 0x1A
#define LORA_REG_MODEM_CONFIG_1 0x1D
#define LORA_REG_MODEM_CONFIG_2 0x1E
#define LORA_REG_SYMB_TIMEOUT_LSB 0x1F
#define LORA_REG_PREAMBLE_MSB 0x20
#define LORA_REG_PREAMBLE_LSB 0x21
#define LORA_REG_PAYLOAD_LENGTH 0x22
#define LORA_REG_MODEM_CONFIG_3 0x26
#define LORA_REG_FREQ_ERROR_MSB 0x28
#define LORA_REG_FREQ_ERROR_MID 0x29
#define LORA_REG_FREQ_ERROR_LSB 0x2A
#define LORA_REG_RSSI_WIDEBAND 0x2C
#define LORA_REG_DETECTION_OPTIMIZE 0x31
#define LORA_REG_INVERTIQ 0x33
#define LORA_REG_DETECTION_THRESHOLD 0x37
#define LORA_REG_SYNC_WORD 0x39
#define LORA_REG_INVERTIQ2 0x3B
#define LORA_REG_DIO_MAPPING_1 0x40
#define LORA_REG_VERSION 0x42
#define LORA_REG_PA_DAC 0x4D

// LoRa operation modes
#define LORA_MODE_SLEEP 0x00
#define LORA_MODE_STDBY 0x01
#define LORA_MODE_TX 0x03
#define LORA_MODE_RX_CONTINUOUS 0x05
#define LORA_MODE_RX_SINGLE 0x06

// LoRa module configuration structure
typedef struct {
SPI_HandleTypeDef* hspi;
GPIO_TypeDef* nss_port;
uint16_t nss_pin;
GPIO_TypeDef* reset_port;
uint16_t reset_pin;
GPIO_TypeDef* dio0_port;
uint16_t dio0_pin;
uint8_t spreading_factor; // 6-12
uint8_t bandwidth; // 0:125kHz, 1:250kHz, 2:500kHz
uint8_t coding_rate; // 1:4/5, 2:4/6, 3:4/7, 4:4/8
uint32_t frequency; // Hz
uint8_t power; // dBm
uint8_t tx_buffer[256];
uint8_t rx_buffer[256];
} LoRa_t;

// Function prototypes
void LoRa_Init(LoRa_t* lora);
void LoRa_Reset(LoRa_t* lora);
uint8_t LoRa_ReadRegister(LoRa_t* lora, uint8_t reg);
void LoRa_WriteRegister(LoRa_t* lora, uint8_t reg, uint8_t value);
void LoRa_SetMode(LoRa_t* lora, uint8_t mode);
void LoRa_SetFrequency(LoRa_t* lora, uint32_t frequency);
void LoRa_SetPower(LoRa_t* lora, uint8_t power);
void LoRa_SetSpreadingFactor(LoRa_t* lora, uint8_t sf);
void LoRa_SetBandwidth(LoRa_t* lora, uint8_t bw);
void LoRa_SetCodingRate(LoRa_t* lora, uint8_t cr);
void LoRa_EnableCRC(LoRa_t* lora);
void LoRa_DisableCRC(LoRa_t* lora);
void LoRa_Send(LoRa_t* lora, uint8_t* data, uint8_t size);
uint8_t LoRa_Receive(LoRa_t* lora, uint8_t* data);
int16_t LoRa_GetRSSI(LoRa_t* lora);
int8_t LoRa_GetSNR(LoRa_t* lora);
void LoRa_PrintConfig(LoRa_t* lora);

#endif /* LORA_H */

And here's the content for lora.c:

c
#include "lora.h"

// Helper functions
static void LoRa_NSS_Low(LoRa_t* lora) {
HAL_GPIO_WritePin(lora->nss_port, lora->nss_pin, GPIO_PIN_RESET);
}

static void LoRa_NSS_High(LoRa_t* lora) {
HAL_GPIO_WritePin(lora->nss_port, lora->nss_pin, GPIO_PIN_SET);
}

// Initialize the LoRa module
void LoRa_Init(LoRa_t* lora) {
// Reset the module
LoRa_Reset(lora);

// Check if we can communicate with the module
uint8_t version = LoRa_ReadRegister(lora, LORA_REG_VERSION);
if (version != 0x12) {
// Error: Could not communicate with the LoRa module
while (1);
}

// Put module in sleep mode to configure it
LoRa_SetMode(lora, LORA_MODE_SLEEP);

// Set LoRa mode (instead of FSK)
LoRa_WriteRegister(lora, LORA_REG_OP_MODE, 0x80);

// Configure the module based on structure settings
LoRa_SetFrequency(lora, lora->frequency);
LoRa_SetPower(lora, lora->power);
LoRa_SetSpreadingFactor(lora, lora->spreading_factor);
LoRa_SetBandwidth(lora, lora->bandwidth);
LoRa_SetCodingRate(lora, lora->coding_rate);

// Set preamble length to 8
LoRa_WriteRegister(lora, LORA_REG_PREAMBLE_MSB, 0x00);
LoRa_WriteRegister(lora, LORA_REG_PREAMBLE_LSB, 0x08);

// Set LoRa sync word (0x34 is for LoRaWAN, 0x12 is for private networks)
LoRa_WriteRegister(lora, LORA_REG_SYNC_WORD, 0x12);

// Enable CRC
LoRa_EnableCRC(lora);

// Configure FIFO
LoRa_WriteRegister(lora, LORA_REG_FIFO_TX_BASE_ADDR, 0x00);
LoRa_WriteRegister(lora, LORA_REG_FIFO_RX_BASE_ADDR, 0x00);

// Set module to standby mode
LoRa_SetMode(lora, LORA_MODE_STDBY);
}

// Reset the LoRa module
void LoRa_Reset(LoRa_t* lora) {
// Set RESET pin low
HAL_GPIO_WritePin(lora->reset_port, lora->reset_pin, GPIO_PIN_RESET);
HAL_Delay(1);

// Set RESET pin high
HAL_GPIO_WritePin(lora->reset_port, lora->reset_pin, GPIO_PIN_SET);
HAL_Delay(10);
}

// Read a register from the LoRa module
uint8_t LoRa_ReadRegister(LoRa_t* lora, uint8_t reg) {
uint8_t data = 0;

LoRa_NSS_Low(lora);

// Send address with MSB set to 0 for read
uint8_t address = reg & 0x7F;
HAL_SPI_Transmit(lora->hspi, &address, 1, HAL_MAX_DELAY);

// Read the data
HAL_SPI_Receive(lora->hspi, &data, 1, HAL_MAX_DELAY);

LoRa_NSS_High(lora);

return data;
}

// Write to a register on the LoRa module
void LoRa_WriteRegister(LoRa_t* lora, uint8_t reg, uint8_t value) {
LoRa_NSS_Low(lora);

// Send address with MSB set to 1 for write
uint8_t address = reg | 0x80;
HAL_SPI_Transmit(lora->hspi, &address, 1, HAL_MAX_DELAY);

// Send the data
HAL_SPI_Transmit(lora->hspi, &value, 1, HAL_MAX_DELAY);

LoRa_NSS_High(lora);
}

// Set the operating mode of the LoRa module
void LoRa_SetMode(LoRa_t* lora, uint8_t mode) {
// Read current value, mask off mode bits, and set new mode
uint8_t current = LoRa_ReadRegister(lora, LORA_REG_OP_MODE);
current = (current & 0xF8) | mode;
LoRa_WriteRegister(lora, LORA_REG_OP_MODE, current);
}

// Set the frequency of the LoRa module
void LoRa_SetFrequency(LoRa_t* lora, uint32_t frequency) {
lora->frequency = frequency;

// Calculate the frequency setting
// FREQ = (F(Hz) * 2^19) / F(XOSC)
// F(XOSC) = 32 MHz
uint64_t frf = ((uint64_t)frequency << 19) / 32000000;

// Write the frequency registers
LoRa_WriteRegister(lora, LORA_REG_FRF_MSB, (frf >> 16) & 0xFF);
LoRa_WriteRegister(lora, LORA_REG_FRF_MID, (frf >> 8) & 0xFF);
LoRa_WriteRegister(lora, LORA_REG_FRF_LSB, frf & 0xFF);
}

// Set the transmit power of the LoRa module
void LoRa_SetPower(LoRa_t* lora, uint8_t power) {
lora->power = power;

// Power is limited to 2-20 dBm
if (power < 2) power = 2;
if (power > 20) power = 20;

if (power > 17) {
// High power settings
LoRa_WriteRegister(lora, LORA_REG_PA_DAC, 0x87);
power = power - 5;
} else {
// Normal power settings
LoRa_WriteRegister(lora, LORA_REG_PA_DAC, 0x84);
}

// Configure the PA_CONFIG register
// Power = 10.8 + 0.6 * (power - 2)
// PA_BOOST pin is used (not RFO)
LoRa_WriteRegister(lora, LORA_REG_PA_CONFIG, 0x80 | (power - 2));
}

// Set the spreading factor
void LoRa_SetSpreadingFactor(LoRa_t* lora, uint8_t sf) {
lora->spreading_factor = sf;

// SF range is 6-12
if (sf < 6) sf = 6;
if (sf > 12) sf = 12;

// For SF6, special settings are required
if (sf == 6) {
LoRa_WriteRegister(lora, LORA_REG_DETECTION_OPTIMIZE, 0xC5);
LoRa_WriteRegister(lora, LORA_REG_DETECTION_THRESHOLD, 0x0C);
} else {
LoRa_WriteRegister(lora, LORA_REG_DETECTION_OPTIMIZE, 0xC3);
LoRa_WriteRegister(lora, LORA_REG_DETECTION_THRESHOLD, 0x0A);
}

// Update the register with new SF value
uint8_t config2 = LoRa_ReadRegister(lora, LORA_REG_MODEM_CONFIG_2);
config2 = (config2 & 0x0F) | ((sf << 4) & 0xF0);
LoRa_WriteRegister(lora, LORA_REG_MODEM_CONFIG_2, config2);
}

// Set the bandwidth
void LoRa_SetBandwidth(LoRa_t* lora, uint8_t bw) {
lora->bandwidth = bw;

// BW values: 0:125kHz, 1:250kHz, 2:500kHz
if (bw > 2) bw = 2;

// Update the register with new BW value
uint8_t config1 = LoRa_ReadRegister(lora, LORA_REG_MODEM_CONFIG_1);
config1 = (config1 & 0x0F) | ((bw << 4) & 0xF0);
LoRa_WriteRegister(lora, LORA_REG_MODEM_CONFIG_1, config1);
}

// Set the coding rate
void LoRa_SetCodingRate(LoRa_t* lora, uint8_t cr) {
lora->coding_rate = cr;

// CR values: 1:4/5, 2:4/6, 3:4/7, 4:4/8
if (cr < 1) cr = 1;
if (cr > 4) cr = 4;

// Update the register with new CR value
uint8_t config1 = LoRa_ReadRegister(lora, LORA_REG_MODEM_CONFIG_1);
config1 = (config1 & 0xF1) | ((cr << 1) & 0x0E);
LoRa_WriteRegister(lora, LORA_REG_MODEM_CONFIG_1, config1);
}

// Enable CRC checking
void LoRa_EnableCRC(LoRa_t* lora) {
uint8_t config2 = LoRa_ReadRegister(lora, LORA_REG_MODEM_CONFIG_2);
config2 |= 0x04; // Set bit 2
LoRa_WriteRegister(lora, LORA_REG_MODEM_CONFIG_2, config2);
}

// Disable CRC checking
void LoRa_DisableCRC(LoRa_t* lora) {
uint8_t config2 = LoRa_ReadRegister(lora, LORA_REG_MODEM_CONFIG_2);
config2 &= ~0x04; // Clear bit 2
LoRa_WriteRegister(lora, LORA_REG_MODEM_CONFIG_2, config2);
}

// Send data over LoRa
void LoRa_Send(LoRa_t* lora, uint8_t* data, uint8_t size) {
// Record the data and size
memcpy(lora->tx_buffer, data, size);

// Set the module to standby mode
LoRa_SetMode(lora, LORA_MODE_STDBY);

// Configure DIO0 to interrupt on TX Done
LoRa_WriteRegister(lora, LORA_REG_DIO_MAPPING_1, 0x40); // DIO0 = 01 (TX Done)

// Set FIFO pointer to beginning of TX section
LoRa_WriteRegister(lora, LORA_REG_FIFO_ADDR_PTR, 0x00);

// Write data to FIFO
LoRa_NSS_Low(lora);
uint8_t addr = LORA_REG_FIFO | 0x80;
HAL_SPI_Transmit(lora->hspi, &addr, 1, HAL_MAX_DELAY);
HAL_SPI_Transmit(lora->hspi, data, size, HAL_MAX_DELAY);
LoRa_NSS_High(lora);

// Set payload size
LoRa_WriteRegister(lora, LORA_REG_PAYLOAD_LENGTH, size);

// Start transmission
LoRa_SetMode(lora, LORA_MODE_TX);

// Wait for transmission to complete (DIO0 will go high)
while (HAL_GPIO_ReadPin(lora->dio0_port, lora->dio0_pin) == GPIO_PIN_RESET);

// Clear IRQ flags
LoRa_WriteRegister(lora, LORA_REG_IRQ_FLAGS, 0xFF);
}

// Receive data over LoRa
uint8_t LoRa_Receive(LoRa_t* lora, uint8_t* data) {
// Set the module to standby mode
LoRa_SetMode(lora, LORA_MODE_STDBY);

// Configure DIO0 to interrupt on RX Done
LoRa_WriteRegister(lora, LORA_REG_DIO_MAPPING_1, 0x00); // DIO0 = 00 (RX Done)

// Set module to continuous receive mode
LoRa_SetMode(lora, LORA_MODE_RX_CONTINUOUS);

// Wait for reception to complete (DIO0 will go high)
while (HAL_GPIO_ReadPin(lora->dio0_port, lora->dio0_pin) == GPIO_PIN_RESET);

// Clear IRQ flags
LoRa_WriteRegister(lora, LORA_REG_IRQ_FLAGS, 0xFF);

// Get payload length
uint8_t len = LoRa_ReadRegister(lora, LORA_REG_RX_NB_BYTES);

// Get the address of the last packet
uint8_t addr = LoRa_ReadRegister(lora, LORA_REG_FIFO_RX_CURRENT_ADDR);

// Set FIFO address pointer
LoRa_WriteRegister(lora, LORA_REG_FIFO_ADDR_PTR, addr);

// Read the data from FIFO
LoRa_NSS_Low(lora);
uint8_t reg = LORA_REG_FIFO;
HAL_SPI_Transmit(lora->hspi, &reg, 1, HAL_MAX_DELAY);
HAL_SPI_Receive(lora->hspi, data, len, HAL_MAX_DELAY);
LoRa_NSS_High(lora);

// Return to standby mode
LoRa_SetMode(lora, LORA_MODE_STDBY);

// Return the number of bytes received
return len;
}

// Get the RSSI (Received Signal Strength Indicator) of the last received packet
int16_t LoRa_GetRSSI(LoRa_t* lora) {
int16_t rssi = LoRa_ReadRegister(lora, LORA_REG_PKT_RSSI_VALUE);

// Adjust RSSI based on frequency
if (lora->frequency < 868E6) {
// If frequency < 868 MHz (915 MHz in the Americas)
rssi = rssi - 164;
} else {
// If frequency > 868 MHz (868 MHz in Europe)
rssi = rssi - 157;
}

return rssi;
}

// Get the SNR (Signal-to-Noise Ratio) of the last received packet
int8_t LoRa_GetSNR(LoRa_t* lora) {
int8_t snr = (int8_t)LoRa_ReadRegister(lora, LORA_REG_PKT_SNR_VALUE);
snr = snr / 4;
return snr;
}

// Print the current LoRa configuration (useful for debugging)
void LoRa_PrintConfig(LoRa_t* lora) {
// This function is a placeholder - implement based on your debug output method
// You could use printf or uart transmission to output this information

// Example of what could be printed:
// printf("LoRa Configuration:\r
");
// printf("Frequency: %lu Hz\r
", lora->frequency);
// printf("Power: %d dBm\r
", lora->power);
// printf("Spreading Factor: SF%d\r
", lora->spreading_factor);
// printf("Bandwidth: %d kHz\r
", lora->bandwidth == 0 ? 125 : (lora->bandwidth == 1 ? 250 : 500));
// printf("Coding Rate: 4/%d\r
", lora->coding_rate + 4);
}

Step 3: Using the LoRa Library in the Main Program

Let's create a simple application that sends and receives data using our LoRa library. Modify your main.c file to include:

c
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "spi.h"
#include "gpio.h"
#include "lora.h"
#include <stdio.h>
#include <string.h>

/* Private variables ---------------------------------------------------------*/
LoRa_t myLora;
uint8_t rxBuffer[256];
char message[100];
int messageCounter = 0;

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void SendMessage(void);
void ReceiveMessage(void);

/* Main function -------------------------------------------------------------*/
int main(void)
{
/* MCU Configuration--------------------------------------------------------*/
HAL_Init();
SystemClock_Config();

/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_SPI1_Init();

/* Configure the LoRa module */
myLora.hspi = &hspi1;
myLora.nss_port = GPIOB;
myLora.nss_pin = GPIO_PIN_6;
myLora.reset_port = GPIOA;
myLora.reset_pin = GPIO_PIN_8;
myLora.dio0_port = GPIOC;
myLora.dio0_pin = GPIO_PIN_7;

// Communication parameters
myLora.frequency = 868000000; // 868 MHz
myLora.power = 17; // 17 dBm
myLora.spreading_factor = 7; // SF7
myLora.bandwidth = 0; // 125 kHz
myLora.coding_rate = 1; // 4/5

// Initialize the LoRa module
LoRa_Init(&myLora);

while (1)
{
// Send a message
SendMessage();
HAL_Delay(5000); // Wait 5 seconds

// Try to receive a message
ReceiveMessage();
HAL_Delay(100); // Wait a bit
}
}

/* Send a message using LoRa */
void SendMessage(void)
{
// Create a message with a counter
sprintf(message, "STM32 LoRa Test Message #%d", messageCounter++);

// Send the message
LoRa_Send(&myLora, (uint8_t*)message, strlen(message));
}

/* Receive a message using LoRa */
void ReceiveMessage(void)
{
// Set LoRa to receive mode for a short time
uint8_t len = LoRa_Receive(&myLora, rxBuffer);

if (len > 0)
{
// We received something - null-terminate the string
rxBuffer[len


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)