Shlok Guptavishal soni
Published © MIT

NrfHub- Emergency Response for Rural India

nRFHub, A LoRa-based emergency alert system ensuring rapid, inclusive, and reliable response to crises, even in GSM-deprived areas.

IntermediateProtipOver 2 days234

Things used in this project

Hardware components

Nordic Semiconductor nRF54L15 DK
×1
Semtech LoRa sx1278
×1
AMS1117-3.3
×1
AMS1117-5
×1
Solar 12 V 10
×1
Type-C Male to Solderless Terminal
×1
3.7 LiPo Battery
×1

Software apps and online services

nRF Connect SDK
Nordic Semiconductor nRF Connect SDK
VS Code
Microsoft VS Code

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free
Solder Paste, Rework
Solder Paste, Rework

Story

Read more

Schematics

nrf54l15 to LoRa sx1278

Code

main.c

C/C++
// SX1278 LoRa Driver for NRF54L15
// main.c

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(lora_sx1278, LOG_LEVEL_INF);

// SX1278 Register Addresses
#define REG_FIFO 0x00
#define REG_OP_MODE 0x01
#define REG_FRF_MSB 0x06
#define REG_FRF_MID 0x07
#define REG_FRF_LSB 0x08
#define REG_PA_CONFIG 0x09
#define REG_LNA 0x0c
#define REG_FIFO_ADDR_PTR 0x0d
#define REG_FIFO_TX_BASE_ADDR 0x0e
#define REG_FIFO_RX_BASE_ADDR 0x0f
#define REG_FIFO_RX_CURRENT_ADDR 0x10
#define REG_IRQ_FLAGS 0x12
#define REG_RX_NB_BYTES 0x13
#define REG_PKT_SNR_VALUE 0x19
#define REG_PKT_RSSI_VALUE 0x1a
#define REG_MODEM_CONFIG_1 0x1d
#define REG_MODEM_CONFIG_2 0x1e
#define REG_PREAMBLE_MSB 0x20
#define REG_PREAMBLE_LSB 0x21
#define REG_PAYLOAD_LENGTH 0x22
#define REG_MODEM_CONFIG_3 0x26
#define REG_FREQ_ERROR_MSB 0x28
#define REG_FREQ_ERROR_MID 0x29
#define REG_FREQ_ERROR_LSB 0x2a
#define REG_RSSI_WIDEBAND 0x2c
#define REG_DETECTION_OPTIMIZE 0x31
#define REG_DETECTION_THRESHOLD 0x37
#define REG_SYNC_WORD 0x39
#define REG_DIO_MAPPING_1 0x40
#define REG_VERSION 0x42
#define REG_PA_DAC 0x4d

// LoRa modes
#define MODE_LONG_RANGE_MODE 0x80
#define MODE_SLEEP 0x00
#define MODE_STDBY 0x01
#define MODE_TX 0x03
#define MODE_RX_CONTINUOUS 0x05
#define MODE_RX_SINGLE 0x06

// PA config
#define PA_BOOST 0x80

// IRQ masks
#define IRQ_TX_DONE_MASK 0x08
#define IRQ_PAYLOAD_CRC_ERROR_MASK 0x20
#define IRQ_RX_DONE_MASK 0x40

// Define SPI Pins according to the table
#define LORA_CS_PIN 5 // P2.05 - NSS (Chip Select)
// SCK (Clock) is on P2.01
// MOSI is on P2.02
// MISO is on P2.04
// These pins are configured by the SPI driver based on device tree

// Define GPIO pins for RESET and DIO0
#define LORA_RESET_PIN 3 // P3.3
#define LORA_DIO0_PIN 4	 // P3.4

// Define GPIO for the button
#define BUTTON_PIN 2     // Example: P1.2
#define BUTTON_PORT_NAME "GPIO_1"

// Define GPIO ports
#define CS_PORT_NAME "GPIO_2"
#define RESET_PORT_NAME "GPIO_3"
#define DIO0_PORT_NAME "GPIO_3"

// Define SPI bus
#define SPI_DEV_NAME "SPI_0"

// Message counter for button presses
static uint32_t button_press_count = 0;

static const struct device *spi_dev;
static const struct device *cs_gpio;
static const struct device *reset_gpio;
static const struct device *dio0_gpio;
static const struct device *button_gpio;

static struct spi_config spi_cfg = {
	.frequency = 8000000,
	.operation = SPI_WORD_SET(8) | SPI_TRANSFER_MSB | SPI_MODE_CPOL | SPI_MODE_CPHA,
};

static struct spi_cs_control spi_cs = {
	.gpio = {
		.pin = LORA_CS_PIN,
	},
	.delay = 0,
};

// Function to write a register
static void write_register(uint8_t reg, uint8_t value)
{
	uint8_t buf[2] = {reg | 0x80, value}; // MSB set to 1 for write
	struct spi_buf tx_buf = {
		.buf = buf,
		.len = sizeof(buf)};
	struct spi_buf_set tx_bufs = {
		.buffers = &tx_buf,
		.count = 1};

	gpio_pin_set_dt(&spi_cs.gpio, 0); // Active low CS
	spi_write(spi_dev, &spi_cfg, &tx_bufs);
	gpio_pin_set_dt(&spi_cs.gpio, 1); // Deactivate CS
}

// Function to read a register
static uint8_t read_register(uint8_t reg)
{
	uint8_t cmd = reg & 0x7F; // MSB cleared for read
	uint8_t value;

	struct spi_buf tx_buf = {
		.buf = &cmd,
		.len = 1};
	struct spi_buf_set tx_bufs = {
		.buffers = &tx_buf,
		.count = 1};

	struct spi_buf rx_buf = {
		.buf = &value,
		.len = 1};
	struct spi_buf_set rx_bufs = {
		.buffers = &rx_buf,
		.count = 1};

	gpio_pin_set_dt(&spi_cs.gpio, 0); // Active low CS
	spi_transceive(spi_dev, &spi_cfg, &tx_bufs, &rx_bufs);
	gpio_pin_set_dt(&spi_cs.gpio, 1); // Deactivate CS

	return value;
}

// Function to reset the SX1278
static void reset_sx1278(void)
{
	gpio_pin_set(reset_gpio, LORA_RESET_PIN, 0);
	k_sleep(K_MSEC(1));
	gpio_pin_set(reset_gpio, LORA_RESET_PIN, 1);
	k_sleep(K_MSEC(10));
}

// Function to initialize the SX1278
static bool init_sx1278(void)
{
	// Reset the device
	reset_sx1278();

	// Check version (should be 0x12 for sx1278)
	uint8_t version = read_register(REG_VERSION);
	if (version != 0x12)
	{
		LOG_ERR("Device not found! Version: 0x%02x", version);
		return false;
	}

	LOG_INF("SX1278 found. Version: 0x%02x", version);

	// Put in sleep mode
	write_register(REG_OP_MODE, MODE_SLEEP);

	// Set to LoRa mode
	write_register(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_SLEEP);

	// Set frequency to 915 MHz
	uint64_t frf = ((uint64_t)915000000 << 19) / 32000000;
	write_register(REG_FRF_MSB, (uint8_t)(frf >> 16));
	write_register(REG_FRF_MID, (uint8_t)(frf >> 8));
	write_register(REG_FRF_LSB, (uint8_t)(frf >> 0));

	// Set base addresses
	write_register(REG_FIFO_TX_BASE_ADDR, 0);
	write_register(REG_FIFO_RX_BASE_ADDR, 0);

	// LNA settings
	write_register(REG_LNA, read_register(REG_LNA) | 0x03);

	// Set auto AGC
	write_register(REG_MODEM_CONFIG_3, 0x04);

	// Set output power to 17 dBm using PA_BOOST
	write_register(REG_PA_CONFIG, PA_BOOST | (17 - 2));

	// Set Spreading Factor (SF12), Bandwidth (125 kHz)
	write_register(REG_MODEM_CONFIG_2, (12 << 4) | 0x04);

	// Set error coding rate (4/5), explicit header mode
	write_register(REG_MODEM_CONFIG_1, 0x72);

	// Set preamble length (8)
	write_register(REG_PREAMBLE_MSB, 0x00);
	write_register(REG_PREAMBLE_LSB, 0x08);

	// Set sync word
	write_register(REG_SYNC_WORD, 0x34);

	// Enable CRC
	write_register(REG_MODEM_CONFIG_2, read_register(REG_MODEM_CONFIG_2) | 0x04);

	// Put in standby mode
	write_register(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_STDBY);

	return true;
}

// Function to send data
static void sx1278_send(uint8_t *data, uint8_t length)
{
	// Set to standby mode
	write_register(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_STDBY);

	// Set payload length
	write_register(REG_PAYLOAD_LENGTH, length);

	// Set FIFO pointer
	write_register(REG_FIFO_ADDR_PTR, 0);

	// Write data to FIFO
	for (int i = 0; i < length; i++)
	{
		write_register(REG_FIFO, data[i]);
	}

	// Start transmission
	write_register(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_TX);

	// Wait for TX done
	while ((read_register(REG_IRQ_FLAGS) & IRQ_TX_DONE_MASK) == 0)
	{
		k_sleep(K_MSEC(10));
	}

	// Clear IRQ flags
	write_register(REG_IRQ_FLAGS, IRQ_TX_DONE_MASK);

	LOG_INF("Data sent!");
}

// Function to send data when button is pressed
static void send_button_data(void)
{
	char message[64];
	snprintf(message, sizeof(message), "Button pressed! Count: %lu", button_press_count);
	
	LOG_INF("Button press detected! Sending data packet #%lu via LoRa", button_press_count);
	LOG_INF("Message content: \"%s\"", message);
	
	// Send the data via LoRa
	sx1278_send((uint8_t *)message, strlen(message));
}

// DIO0 interrupt handler
static void dio0_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
	LOG_INF("Interrupt received on DIO0");

	// Handle interrupt (RX/TX completion)
	uint8_t irq_flags = read_register(REG_IRQ_FLAGS);
	write_register(REG_IRQ_FLAGS, irq_flags); // Clear IRQ flags

	if (irq_flags & IRQ_RX_DONE_MASK)
	{
		LOG_INF("Packet received!");

		// Handle received data if needed
		uint8_t len = read_register(REG_RX_NB_BYTES);
		uint8_t rx_data[256];

		write_register(REG_FIFO_ADDR_PTR, read_register(REG_FIFO_RX_CURRENT_ADDR));

		for (int i = 0; i < len; i++)
		{
			rx_data[i] = read_register(REG_FIFO);
		}

		LOG_HEXDUMP_INF(rx_data, len, "Received data:");
	}
}

// Button interrupt handler
static void button_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
	// Debounce
	k_sleep(K_MSEC(50));
	
	// Check if button is still pressed after debounce
	if (gpio_pin_get(button_gpio, BUTTON_PIN) == 1) {
		button_press_count++;
		send_button_data();
	}
}

static struct gpio_callback dio0_cb;
static struct gpio_callback button_cb;

int main(void)
{
	int ret;

	LOG_INF("Starting SX1278 LoRa Driver with Button Support");

	// Get SPI device
	spi_dev = device_get_binding(SPI_DEV_NAME);
	if (!spi_dev)
	{
		LOG_ERR("SPI device not found");
		return -1;
	}

	// Setup CS GPIO
	cs_gpio = device_get_binding(CS_PORT_NAME);
	if (!cs_gpio)
	{
		LOG_ERR("CS GPIO device not found");
		return -1;
	}

	ret = gpio_pin_configure(cs_gpio, LORA_CS_PIN, GPIO_OUTPUT_ACTIVE);
	if (ret < 0)
	{
		LOG_ERR("Error configuring CS pin");
		return ret;
	}

	spi_cs.gpio.port = cs_gpio;
	spi_cfg.cs = spi_cs;

	// Setup RESET GPIO
	reset_gpio = device_get_binding(RESET_PORT_NAME);
	if (!reset_gpio)
	{
		LOG_ERR("RESET GPIO device not found");
		return -1;
	}

	ret = gpio_pin_configure(reset_gpio, LORA_RESET_PIN, GPIO_OUTPUT_ACTIVE);
	if (ret < 0)
	{
		LOG_ERR("Error configuring RESET pin");
		return ret;
	}

	// Setup DIO0 GPIO for interrupts
	dio0_gpio = device_get_binding(DIO0_PORT_NAME);
	if (!dio0_gpio)
	{
		LOG_ERR("DIO0 GPIO device not found");
		return -1;
	}

	ret = gpio_pin_configure(dio0_gpio, LORA_DIO0_PIN, GPIO_INPUT);
	if (ret < 0)
	{
		LOG_ERR("Error configuring DIO0 pin");
		return ret;
	}

	ret = gpio_pin_interrupt_configure(dio0_gpio, LORA_DIO0_PIN, GPIO_INT_EDGE_RISING);
	if (ret < 0)
	{
		LOG_ERR("Error configuring DIO0 interrupt");
		return ret;
	}

	gpio_init_callback(&dio0_cb, dio0_callback, BIT(LORA_DIO0_PIN));
	gpio_add_callback(dio0_gpio, &dio0_cb);

	// Setup Button GPIO
	button_gpio = device_get_binding(BUTTON_PORT_NAME);
	if (!button_gpio)
	{
		LOG_ERR("Button GPIO device not found");
		return -1;
	}

	ret = gpio_pin_configure(button_gpio, BUTTON_PIN, GPIO_INPUT | GPIO_PULL_DOWN);
	if (ret < 0)
	{
		LOG_ERR("Error configuring button pin");
		return ret;
	}

	ret = gpio_pin_interrupt_configure(button_gpio, BUTTON_PIN, GPIO_INT_EDGE_RISING);
	if (ret < 0)
	{
		LOG_ERR("Error configuring button interrupt");
		return ret;
	}

	gpio_init_callback(&button_cb, button_callback, BIT(BUTTON_PIN));
	gpio_add_callback(button_gpio, &button_cb);

	LOG_INF("Button configured on P%s.%d", BUTTON_PORT_NAME, BUTTON_PIN);

	// Initialize SX1278
	if (!init_sx1278())
	{
		LOG_ERR("Failed to initialize SX1278");
		return -1;
	}

	LOG_INF("SX1278 initialized successfully");
	LOG_INF("Press the button to send data via LoRa");

	// Send initial test message
	uint8_t test_data[] = "System initialized and ready!";
	sx1278_send(test_data, sizeof(test_data) - 1);

	// Put into receive mode
	write_register(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_RX_CONTINUOUS);

	while (1)
	{
		k_sleep(K_SECONDS(1));
	}

	return 0;
}

CMakeLists.txt

Plain text
# CMakeLists.txt
cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(lora_sx1278)

target_sources(app PRIVATE src/main.c)

Prj.conf

Plain text
# prj.conf

# Enable SPI
CONFIG_SPI=y

# Enable GPIO
CONFIG_GPIO=y

# Enable logging
CONFIG_LOG=y
CONFIG_LOG_DEFAULT_LEVEL=3
CONFIG_LOG_BACKEND_UART=y
CONFIG_SERIAL=y

# Enable console
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y

# Stack size
CONFIG_MAIN_STACK_SIZE=2048

# Other system settings
CONFIG_HEAP_MEM_POOL_SIZE=4096

.overlay file (optional)

Plain text
this is optional file, we have done using registers method where registers are used to perform but using this file and copying the sample 'LoRa send' will get you code running.
/* Device tree overlay for LoRa SX1278 module using SPI21 */
&spi21 {
	compatible = "nordic,nrf-spim";
	status = "okay";
	pinctrl-0 = <&spi21_default>;
	pinctrl-1 = <&spi21_sleep>;
	pinctrl-names = "default", "sleep";
	cs-gpios = <&gpio0 7 GPIO_ACTIVE_LOW>;

	/* Define the LoRa radio node with properties required by the SX1278 driver */
	lora_radio: radio@0 {
		compatible = "semtech,sx1278";
		reg = <0>;
		spi-max-frequency = <8000000>; /* 8 MHz SPI clock */
		/* Additional GPIOs for radio control */
		reset-gpios = <&gpio0 11 GPIO_ACTIVE_LOW>;
		dio0-gpios = <&gpio0 06 <(GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>;
		/* Uncomment and configure if your driver supports more DIOs:
		dio1-gpios = <&gpio0 22 GPIO_ACTIVE_HIGH>;
		dio2-gpios = <&gpio0 23 GPIO_ACTIVE_HIGH>;
		*/
	};
};

/* SPI pin configuration for spi21 */
&pinctrl {
	spi21_default: spi21_default {
		group1 {
			psels = <NRF_PSEL(SPIM_SCK, 0, 10)>,
					<NRF_PSEL(SPIM_MOSI, 0, 08)>,
					<NRF_PSEL(SPIM_MISO, 0, 09)>;
		};
	};

	spi21_sleep: spi21_sleep {
		group1 {
			psels = <NRF_PSEL(SPIM_SCK, 1, 11)>,
					<NRF_PSEL(SPIM_MOSI, 1, 13)>,
					<NRF_PSEL(SPIM_MISO, 1, 14)>;
			low-power-enable;
		};
	};
};

Credits

Shlok Gupta
1 project • 5 followers
Just another developer
Contact
vishal soni
8 projects • 10 followers
Engineer ,Electronic Enthusiast
Contact

Comments

Please log in or sign up to comment.