pdf-icon

Arduino Quick Start

2. Devices & Examples

6. Applications

Cap LoRa868 Arduino Tutorial

1. Preparation

Note
The TinyGPSPlus library needs to be downloaded from the GitHub version adapted for M5Stack devices: TinyGPSPlus - M5Stack GitHub. Do not download it from the Arduino Library Manager. (If you have any questions, please refer to this tutorial)

2. Compile & Upload

  • Set the power switch on the side of the Cardputer-Adv to the OFF position, then press and hold the G0 button before powering on the device, and release it after powering on from the back of the device. The device will then enter download mode, waiting for flashing.

  • Select the device port, click the compile/upload button in the upper left corner of the Arduino IDE, and wait for the program to be compiled and uploaded to the device.

3. Example Program

  • In this tutorial, the main control device used is the Cardputer-Adv, paired with the Cap LoRa868. The LoRa part of the Cap LoRa868 communicates via SPI, and the GPS part communicates via UART. Please modify the pin definitions in the program according to the actual circuit connection. After the devices are connected, the corresponding SPI IOs are G5 (NSS), G4 (IRQ), G3 (RST), G6 (BUSY), and the UART IOs are G15 (RX), G13 (TX).

The physical connection assembly is shown in the figure below:

LoRa Usage Example

Transmitter

cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
#include <M5Unified.h>
#include <RadioLib.h>

#define LORA_BW           125.0f // SX1262 bandwidth (kHz) - 125kHz is common for long-range
#define LORA_SF           12     // Spreading factor (6-12) - higher = longer range, slower data rate
#define LORA_CR           5      // Coding rate (5-8, represents 4/5 to 4/8) - higher = more error correction
#define LORA_FREQ         868.0  // Carrier frequency (MHz) - 868MHz is EU ISM band for SX1262
#define LORA_SYNC_WORD    0x34   // Sync word for packet recognition - must match between transmitter/receiver
#define LORA_TX_POWER     22     // Transmission power (dBm) - 22dBm is maximum for many regions
#define LORA_PREAMBLE_LEN 20     // Preamble length (symbols) - ensures receiver can detect packet start

//         SX1262 PIN         NSS,       IRQ,         RST,        BUSY 
SX1262 radio = new Module(GPIO_NUM_5, GPIO_NUM_4, GPIO_NUM_3, GPIO_NUM_6);

// Tracks the result of the last transmission attempt (error code from RadioLib)
int transmissionState = RADIOLIB_ERR_NONE;

// Special attribute for ESP8266/ESP32 to place ISR in RAM (faster interrupt response)
#if defined(ESP8266) || defined(ESP32)
ICACHE_RAM_ATTR
#endif
// Packet sent flag
volatile bool transmittedFlag = false;
// This function is called when a complete packet is transmitted by the module
// IMPORTANT: this function MUST be 'void' type and MUST NOT have any arguments!
void setFlag(void)
{
    // we sent a packet, set the flag
    transmittedFlag = true;
}

void setup()
{
    M5.begin();
    Serial.begin(115200);
    M5.Display.setFont(&fonts::FreeMonoBold9pt7b);

    // Init SX1262
    Serial.print(F("[LoRa] Initializing ... "));
    int state =
        radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, LORA_SYNC_WORD, LORA_TX_POWER, LORA_PREAMBLE_LEN, 3.0, true);
    if (state == RADIOLIB_ERR_NONE) {
        Serial.println(F("Init success!"));
    } else {
        Serial.print(F("Init failed, code: "));
        Serial.println(state);
        while (true) { delay(10); }
    }
    radio.setCurrentLimit(140); radio.setCurrentLimit(140);// Current range: 0-140mA , step 2.5mA

    // Register callback function after sending packet successfully
    radio.setPacketSentAction(setFlag);

    // Send first packet to enable flag
    Serial.print(F("[LoRa] Sending first packet... "));
    transmissionState = radio.startTransmit("Hello World!");
    M5.Display.setCursor(5,0);
    M5.Display.printf("Hello World!\n");
}

// Counter to keep track of transmitted packets
int count = 0;

void loop()
{
    if (transmittedFlag) {
        // reset flag
        transmittedFlag = false;

        if (transmissionState == RADIOLIB_ERR_NONE) {
            // packet was successfully sent
            Serial.println(F("Transmission finished!"));
            M5.Display.println("Send sucessfully!");

            // NOTE: when using interrupt-driven transmit method,
            //       it is not possible to automatically measure
            //       transmission data rate using getDataRate()

        } else {
            Serial.print(F("Send failed, code: "));
            Serial.println(transmissionState);
            M5.Display.print("\nSend failed\ncode:");
            M5.Display.println(transmissionState);
        }

        // Clean up after transmission is finished
        // This will ensure transmitter is disabled,
        // RF switch is powered down etc.
        radio.finishTransmit();

        // Wait a second before transmitting again
        delay(1000);

        Serial.printf("[LoRa] Sending #%d packet ... ", count);
        // You can transmit C-string or Arduino string up to 256 characters long
        String str        = "Cap LoRa868#" + String(count++);
        transmissionState = radio.startTransmit(str);
        M5.Display.clear();
        M5.Display.setCursor(0,5);
        M5.Display.printf("[LoRa]\nSending #%d packet\n......\n", count);

        // You can also transmit byte array up to 256 bytes long
        /*
          byte byteArr[] = {0x01, 0x23, 0x45, 0x67,
                            0x89, 0xAB, 0xCD, 0xEF};
          transmissionState = radio.startTransmit(byteArr, 8);
        */
    }
}

Receiver

cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
#include <M5Unified.h>
#include <RadioLib.h>

#define LORA_BW           125.0f // LoRa bandwidth (kHz) - 125kHz is common for long-range
#define LORA_SF           12     // Spreading factor (6-12) - higher = longer range, slower data rate
#define LORA_CR           5      // Coding rate (5-8, represents 4/5 to 4/8) - higher = more error correction
#define LORA_FREQ         868.0  // Carrier frequency (MHz) - 868MHz is EU ISM band for LoRa
#define LORA_SYNC_WORD    0x34   // Sync word for packet recognition - must match between transmitter/receiver
#define LORA_TX_POWER     22     // Transmission power (dBm) - 22dBm is maximum for many regions
#define LORA_PREAMBLE_LEN 20     // Preamble length (symbols) - ensures receiver can detect packet start

//         SX1262 PIN         NSS,       IRQ,         RST,        BUSY 
SX1262 radio = new Module(GPIO_NUM_5, GPIO_NUM_4, GPIO_NUM_3, GPIO_NUM_6);

#if defined(ESP8266) || defined(ESP32)
ICACHE_RAM_ATTR
#endif
// Packet received flag
volatile bool receivedFlag = false;
// This function is called when a complete packet is received by the module
// IMPORTANT: This function MUST be 'void' type and MUST NOT have any arguments!
void setFlag(void)
{
    receivedFlag = true;
}

void setup()
{
    M5.begin();
    Serial.begin(115200);
    M5.Display.setFont(&fonts::FreeMonoBold9pt7b);

    // Init SX1262
    Serial.print(F("[LoRa] Initializing ... "));
    int state = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, LORA_SYNC_WORD, LORA_TX_POWER, LORA_PREAMBLE_LEN, 3.0, true);
    if (state == RADIOLIB_ERR_NONE) {
        Serial.println(F("Init success!"));
    } else {
        Serial.print(F("Init failed, code: "));
        Serial.println(state);
        while (true) { delay(10); }
    }
    radio.setCurrentLimit(140);// Current range: 0-140mA , step 2.5mA

    // Register callback function after receiving packet successfully
    radio.setPacketReceivedAction(setFlag);

    // Start listening for LoRa packets
    Serial.print(F("[LoRa] Starting to listen ... "));
    state = radio.startReceive();
    if (state == RADIOLIB_ERR_NONE) {
        Serial.println(F("Listen successfully!"));
    } else {
        Serial.print(F("Listen failed, code: "));
        Serial.println(state);
        while (true) { delay(10); }
    }

    // If needed, 'listen' mode can be disabled by calling any of the following methods:
    // radio.standby()
    // radio.sleep()
    // radio.transmit();
    // radio.receive();
    // radio.scanChannel();
}

void loop()
{
    if (receivedFlag) {
        // reset flag
        receivedFlag = false;

        // Read received data as an Arduino String
        String str;
        int state = radio.readData(str);

        // Read received data as byte array
        /*
          byte byteArr[8];
          int numBytes = radio.getPacketLength();
          int state = radio.readData(byteArr, numBytes);
        */

        if (state == RADIOLIB_ERR_NONE) {
            // Packet was successfully received
            Serial.println(F("[LoRa] Received packet:"));
            M5.Display.clear();
            M5.Display.setCursor(0,5);
            M5.Display.printf("[LoRa]\nReceived packet:\n");

            // Data of the packet
            Serial.print(F("[LoRa] Data:\t\t"));
            Serial.println(str);
            M5.Display.printf("Data: %s\n", str.c_str());

            // RSSI (Received Signal Strength Indicator)
            Serial.print(F("[LoRa] RSSI:\t\t"));
            Serial.print(radio.getRSSI());
            Serial.println(F(" dBm"));
            M5.Display.printf("RSSI: %0.2f dBm\n", radio.getRSSI());

            // SNR (Signal-to-Noise Ratio)
            Serial.print(F("[LoRa] SNR:\t\t"));
            Serial.print(radio.getSNR());
            Serial.println(F(" dB"));
            M5.Display.printf("SNR:  %0.2f dB\n", radio.getSNR());

            // Frequency error
            Serial.print(F("[LoRa] Frequency error:\t"));
            Serial.print(radio.getFrequencyError());
            Serial.println(F(" Hz"));
            M5.Display.printf("Freq err: %0.2f Hz\n", radio.getFrequencyError());

        } else if (state == RADIOLIB_ERR_CRC_MISMATCH) {
            // Packet was received, but is malformed
            Serial.println(F("CRC error!"));

        } else {
            Serial.print(F("Receive failed, code: "));
            Serial.println(state);
        }
    }
}

The above example’s effect: the sending end will send a string containing a counter every second; the receiving end will print the received string and display RSSI, SNR, and frequency error information.

  • Transmitter serial output:
[LoRa] Sending #39 packet ... Transmission finished!
  • Receiver serial output:
[LoRa] Received packet:
[LoRa] Data:            Cap LoRa868#38
[LoRa] RSSI:            -7.00 dBm
[LoRa] SNR:             4.50 dB
[LoRa] Frequency error: -779.60 Hz

GPS Usage Example

  • The GPS module of this device supports multiple satellite systems, including GPS, GLONASS, GALILEO, BDS, and QZSS. The example provides a function setSatelliteMode() to modify the satellite system, with GLONASS as the default. When modified, the prefix displayed on the screen will also change accordingly to help identify the current satellite system.
cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
#include "M5Unified.h"
#include "M5GFX.h"
#include "MultipleSatellite.h"

static const int RXPin = 15, TXPin = 13;
static const uint32_t GPSBaud = 115200;
MultipleSatellite gps(Serial1, GPSBaud, SERIAL_8N1, RXPin, TXPin);

// Variable to track current satellite system mode (default: GLONASS)
satellite_mode_t currentMode = SATELLITE_MODE_GLONASS;

// Function prototype for displaying information
void displayInfo(void);

/**
 * Get the prefix string for different satellite systems
 * @param mode Current satellite system mode
 * @return Corresponding prefix string for the satellite system
 */
const char* getSatPrefix(satellite_mode_t mode) {
    switch (mode) {
        case SATELLITE_MODE_GPS:      return "GPS_Sat";   // GPS satellite prefix
        case SATELLITE_MODE_BDS:      return "BDS_Sat";   // BeiDou satellite prefix
        case SATELLITE_MODE_GLONASS:  return "GLN_Sat";   // GLONASS satellite prefix
        case SATELLITE_MODE_GALILEO:  return "GAL_Sat";   // Galileo satellite prefix
        case SATELLITE_MODE_QZSS:     return "QZS_Sat";   // QZSS satellite prefix
        default:                      return "Unknown";   // Default for unknown systems
    }
}

void setup() {
    M5.begin();               
    Serial.begin(115200);    
    M5.Display.setFont(&fonts::FreeMonoBold9pt7b);
    gps.begin();              // Initialize GPS module
    // Set GPS to factory start mode
    gps.setSystemBootMode(BOOT_FACTORY_START);
    
    // Print initialization information to serial monitor
    Serial.println(F("<----------Cap LoRa868 Example---------->"));
    Serial.print(F("Testing TinyGPSPlus library v. "));
    Serial.println(TinyGPSPlus::libraryVersion());
    String version = gps.getGNSSVersion();
    Serial.printf("GNSS SW=%s\r\n", version.c_str());
    delay(1000);  // Short delay for initialization

    displayInfo();  // Initial display of information
}

void loop() {
    gps.updateGPS();  // Update GPS data
    displayInfo();    // Update display with new data
    delay(100);       
}

void displayInfo(void) {
    Serial.println("=========================================");
    Serial.print(F("Location: "));

    Serial.printf("satellites:%d\n", gps.satellites.value());
    String gps_mode = gps.getSatelliteMode(); 
    Serial.printf("GNSS Mode:%s\r\n", gps_mode.c_str());

    // Get appropriate satellite prefix based on current mode
    const char* satPrefix = getSatPrefix(currentMode);

    // Check if location data has been updated
    if (gps.location.isUpdated()) {
        auto latitude = gps.location.lat();
        auto longitude = gps.location.lng();
        // Print latitude and longitude to serial with 6 decimal places
        Serial.print(latitude, 6); Serial.print(F(","));
        Serial.print(longitude, 6);Serial.print(F("\n"));

        M5.Display.fillRect(0, 0, 240, 135, TFT_BLACK);  
        M5.Display.setCursor(0, 0);                   
        M5.Display.printf("%s: \nSat: %d\nLat: %.6f\nlng: %.6f\n", satPrefix,
                          (uint8_t)gps.satellites.value(),
                          latitude,
                          longitude);
    } else {
        // If no valid location data, display placeholders
        M5.Display.fillRect(0, 0, 240, 135, TFT_BLACK);
        M5.Display.setCursor(0, 0);
        M5.Display.printf("%s\n", satPrefix);
        M5.Display.print("Sat: ----\nLat: ----\nLng: ----\n");
        Serial.print(F("LOCATION INVALID\n"));  // Indicate invalid data on serial
    }

    Serial.print(F("Date/Time: "));
    // Check if date data has been updated
    if (gps.date.isUpdated()) {
        auto month = gps.date.month();
        auto day = gps.date.day();
        auto year = gps.date.year();
        // Print date to serial (month/day/year)
        Serial.printf("%d/%d/%d ", month, day, year);

        M5.Display.fillRect(0, 80, 128, 128, TFT_BLACK);
        M5.Display.setCursor(0, 80);
        M5.Display.printf("Date: %d/%d/%d\n", month, day, year);
    } else {
        Serial.print(F("DATE INVALID"));  // Indicate invalid date
    }

    // Check if time data has been updated
    if (gps.time.isUpdated()) {
        auto hour = gps.time.hour();
        auto minute = gps.time.minute();
        auto sec = gps.time.second();
        auto centisec = gps.time.centisecond();
        // Print time to serial with leading zeros where necessary (HH:MM:SS.CS)
        if (hour < 10) Serial.print(F("0"));
        Serial.print(hour);Serial.print(F(":"));
        if (minute < 10) Serial.print(F("0"));
        Serial.print(minute);Serial.print(F(":"));
        if (sec < 10) Serial.print(F("0"));
        Serial.print(sec);Serial.print(F("."));
        if (centisec < 10) Serial.print(F("0"));
        Serial.print(centisec);

        M5.Display.fillRect(0, 128, 128, 60, TFT_BLACK);
        M5.Display.setCursor(0, 96);
        M5.Display.printf("Time: %02d:%02d:%02d.%02d\n", hour, minute, sec, centisec);
    } else {
        Serial.print(F("TIME INVALID"));  // Indicate invalid time
    }
    Serial.println();
    delay(1000);  // Delay to stabilize display updates
} 

Since the GPS of this product uses a built-in antenna without an external one, please try to use it in open outdoor areas such as playgrounds or rooftops. The positioning wait time is relatively long, about a few minutes, so please be patient while the device searches for satellites and obtains coordinates.

  • Serial output:
=========================================
Location: satellites:22
GNSS Mode:GLN_Sat
22.687541,113.772193
Date/Time: 9/5/2025 03:00:24.00

Using LoRa Communication to Transmit GPS Information

Transmitter

cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
#include <M5Unified.h>
#include <RadioLib.h>
#include "M5GFX.h"
#include "MultipleSatellite.h"

// LoRa configuration parameters
#define LORA_BW           125.0f // Bandwidth (kHz)
#define LORA_SF           12     // Spreading factor (6-12)
#define LORA_CR           5      // Coding rate (5-8)
#define LORA_FREQ         868.0  // Frequency (MHz)
#define LORA_SYNC_WORD    0x34   // Sync word
#define LORA_TX_POWER     22     // Transmission power (dBm)
#define LORA_PREAMBLE_LEN 20     // Preamble length

// LoRa pin definition
SX1262 radio = new Module(GPIO_NUM_5, GPIO_NUM_4, GPIO_NUM_3, GPIO_NUM_6);

// GPS configuration parameters
static const int RXPin = 15, TXPin = 13;
static const uint32_t GPSBaud = 115200;
MultipleSatellite gps(Serial1, GPSBaud, SERIAL_8N1, RXPin, TXPin);

// Global variables
int transmissionState = RADIOLIB_ERR_NONE;
volatile bool transmittedFlag = false;
int count = 0;
satellite_mode_t currentMode = SATELLITE_MODE_GLONASS;
float lastLat = 0.0f, lastLng = 0.0f;
int lastSatellites = 0;
String lastDate = "", lastTime = "";

// LoRa transmission completion callback function
#if defined(ESP8266) || defined(ESP32)
ICACHE_RAM_ATTR
#endif
void setFlag(void) {
    transmittedFlag = true;
}

// Function to get satellite system prefix
const char* getSatPrefix(satellite_mode_t mode) {
    switch (mode) {
        case SATELLITE_MODE_GPS:      return "GPS_Sat";
        case SATELLITE_MODE_BDS:      return "BDS_Sat";
        case SATELLITE_MODE_GLONASS:  return "GLN_Sat";
        case SATELLITE_MODE_GALILEO:  return "GAL_Sat";
        case SATELLITE_MODE_QZSS:     return "QZS_Sat";
        default:                      return "Unknown";
    }
}

// Function to update display information
String displayInfo(void) {
    M5.Display.setTextColor(TFT_WHITE);
    Serial.println("\n=========================================");
    Serial.print(F("Location: "));
    // Update satellite count
    lastSatellites = gps.satellites.value();
    Serial.printf("satellites:%d\n", lastSatellites);
    String gps_mode = gps.getSatelliteMode(); 
    Serial.printf("GNSS Mode:%s\r\n", gps_mode.c_str());

    const char* satPrefix = getSatPrefix(currentMode);

    // Update location information
    if (gps.location.isUpdated()) {
        lastLat = gps.location.lat();
        lastLng = gps.location.lng();
        Serial.print(lastLat, 6); Serial.print(F(","));
        Serial.print(lastLng, 6);Serial.print(F("\n"));

        // Display GPS information
        M5.Display.fillRect(0, 0, 240, 135, TFT_BLACK);  
        M5.Display.setCursor(0, 0);                   
        M5.Display.printf("%s: \nSat: %d\nLat: %.6f\nLng: %.6f\n", 
                          satPrefix, lastSatellites, lastLat, lastLng);
    } else {
        M5.Display.fillRect(0, 0, 240, 135, TFT_BLACK);
        M5.Display.setCursor(0, 0);
        M5.Display.printf("%s\nSat: ----\nLat: ----\nLng: ----\n", satPrefix);
        Serial.print(F("LOCATION INVALID\n"));
    }

    // Update date information
    Serial.print(F("Date/Time: "));
    if (gps.date.isUpdated()) {
        lastDate = String(gps.date.month()) + "/" + 
                  String(gps.date.day()) + "/" + 
                  String(gps.date.year());
        Serial.print(lastDate);
        Serial.print(F(" "));

        M5.Display.setCursor(0, 75);
        M5.Display.printf("Date: %s\n", lastDate.c_str());
    } else {
        lastDate = "DATE INVALID";
        Serial.print(F("DATE INVALID "));
    }

    // Update time information
    if (gps.time.isUpdated()) {
        lastTime = (gps.time.hour() < 10 ? "0" : "") + String(gps.time.hour()) + ":" +
           (gps.time.minute() < 10 ? "0" : "") + String(gps.time.minute()) + ":" +
           (gps.time.second() < 10 ? "0" : "") + String(gps.time.second()) + "." +
           (gps.time.centisecond() < 10 ? "0" : "") + String(gps.time.centisecond());
        Serial.print(lastTime);

        M5.Display.printf("Time: %s\n", lastTime.c_str());
    } else {
        lastTime = "TIME INVALID";
        Serial.print(F("TIME INVALID"));
    }
    Serial.println();

    return String("Type:") + satPrefix + 
           " Count:" + String(count) + 
           " Sat:" + String(lastSatellites) + 
           " Lat:" + String(lastLat, 6) + 
           " Lng:" + String(lastLng, 6) + 
           " Date:" + lastDate + 
           " Time:" + lastTime;
}

void setup() {
    // Initialize M5 device
    M5.begin();
    Serial.begin(115200);
    M5.Display.setFont(&fonts::FreeMonoBold9pt7b);

    // Initialize LoRa module
    Serial.print(F("[LoRa] Initializing ... "));
    int state = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, 
                           LORA_SYNC_WORD, LORA_TX_POWER, LORA_PREAMBLE_LEN, 3.0, true);
    if (state == RADIOLIB_ERR_NONE) {
        Serial.println(F("Init success!"));
    } else {
        Serial.print(F("Init failed, code: "));
        Serial.println(state);
        while (true) { delay(10); }
    }
    radio.setCurrentLimit(140);
    radio.setPacketSentAction(setFlag);
    // Send first data packet
    Serial.print(F("[LoRa] Sending first packet... \n"));
    transmissionState = radio.startTransmit("GPS Initializing...");

    M5.Display.setCursor(5,0);
    M5.Display.printf("GPS Initializing...\n");
    // Initialize GPS module
    gps.begin();
    gps.setSystemBootMode(BOOT_FACTORY_START);
    Serial.println(F("<----------Cap LoRa868 Example---------->"));
    Serial.print(F("Testing TinyGPSPlus library v. "));
    Serial.println(TinyGPSPlus::libraryVersion());
    String version = gps.getGNSSVersion();
    Serial.printf("GNSS SW=%s\r\n", version.c_str());
    delay(1000);
}

void loop() {
    // Handle LoRa transmission completion event
    if (transmittedFlag) {
        transmittedFlag = false;

        if (transmissionState == RADIOLIB_ERR_NONE) {
            // Update GPS data and display
            Serial.println(F("      Transmission finished!"));
            M5.Display.setTextColor(TFT_GREEN);
            M5.Display.println("  OK!");
        } else {
            Serial.print(F("Send failed, code: "));
            Serial.println(transmissionState);
            M5.Display.print("\nSend failed\ncode:");
            M5.Display.println(transmissionState);
        }

        radio.finishTransmit();
        delay(1000);
        gps.updateGPS();
        String gpsData = displayInfo();
        // Send GPS data
        Serial.printf("[LoRa] Sending GPS data:\n %s\n", gpsData.c_str());
        transmissionState = radio.startTransmit(gpsData);
        
        // Update display
        M5.Display.fillRect(0, 115, 240, 20, TFT_BLACK);
        M5.Display.setCursor(0,115);
        M5.Display.setTextColor(TFT_YELLOW);
        M5.Display.printf("Sending #%d... ", count++);
    }
}

Receiver

cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
#include <M5Unified.h>
#include <RadioLib.h>

#define LORA_BW           125.0f // LoRa bandwidth (kHz) - 125kHz is common for long-range
#define LORA_SF           12     // Spreading factor (6-12) - higher = longer range, slower data rate
#define LORA_CR           5      // Coding rate (5-8, represents 4/5 to 4/8) - higher = more error correction
#define LORA_FREQ         868.0  // Carrier frequency (MHz) - 868MHz is EU ISM band for LoRa
#define LORA_SYNC_WORD    0x34   // Sync word for packet recognition - must match between transmitter/receiver
#define LORA_TX_POWER     22     // Transmission power (dBm) - 22dBm is maximum for many regions
#define LORA_PREAMBLE_LEN 20     // Preamble length (symbols) - ensures receiver can detect packet start

//         SX1262 PIN         NSS,       IRQ,         RST,        BUSY 
SX1262 radio = new Module(GPIO_NUM_5, GPIO_NUM_4, GPIO_NUM_3, GPIO_NUM_6);

#if defined(ESP8266) || defined(ESP32)
ICACHE_RAM_ATTR
#endif
// Packet received flag
volatile bool receivedFlag = false;
// This function is called when a complete packet is received by the module
// IMPORTANT: This function MUST be 'void' type and MUST NOT have any arguments!
void setFlag(void)
{
    receivedFlag = true;
}

String Type = "Unknown";
String Count = "--";
String Sat = "--";
String Lat = "--";
String Lng = "--";
String Date = "Invalid";
String Time = "Invalid";

void gpsInfoParse(String str) {
   
    int pos = 0;
    while (pos < str.length()) {
        int newlinePos = str.indexOf(' ', pos);
        if (newlinePos == -1) newlinePos = str.length();
        
        String line = str.substring(pos, newlinePos);
        pos = newlinePos + 1;
        
        int fieldType = -1;
        if (line.startsWith("Type:")) fieldType = 0;
        else if (line.startsWith("Count:")) fieldType = 1;
        else if (line.startsWith("Sat:")) fieldType = 2;
        else if (line.startsWith("Lat:")) fieldType = 3;
        else if (line.startsWith("Lng:")) fieldType = 4;
        else if (line.startsWith("Date:")) fieldType = 5;
        else if (line.startsWith("Time:")) fieldType = 6;
        
        switch (fieldType) {
            case 0: Type = line.substring(5); break;// Type                
            case 1: Count = line.substring(6); break; // Count
            case 2: Sat = line.substring(4); break; // Sat
            case 3: Lat = line.substring(4); break; // Lat
            case 4: Lng = line.substring(4); break; // Lng
            case 5: Date = line.substring(5); break; // Date
            case 6: Time = line.substring(5); break; // Time
            default: break;
        }
    }
}

void setup()
{
    M5.begin();
    Serial.begin(115200);
    M5.Display.setFont(&fonts::FreeMonoBold9pt7b);

    // Init SX1262
    Serial.print(F("[LoRa] Initializing ... "));
    int state = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, LORA_SYNC_WORD, LORA_TX_POWER, LORA_PREAMBLE_LEN, 3.0, true);
    if (state == RADIOLIB_ERR_NONE) {
        Serial.println(F("Init success!"));
    } else {
        Serial.print(F("Init failed, code: "));
        Serial.println(state);
        while (true) { delay(10); }
    }
    radio.setCurrentLimit(140);// Current range: 0-140mA , step 2.5mA

    // Register callback function after receiving packet successfully
    radio.setPacketReceivedAction(setFlag);

    // Start listening for LoRa packets
    Serial.print(F("[LoRa] Starting to listen ... "));
    state = radio.startReceive();
    if (state == RADIOLIB_ERR_NONE) {
        Serial.println(F("Listen successfully!"));
    } else {
        Serial.print(F("Listen failed, code: "));
        Serial.println(state);
        while (true) { delay(10); }
    }
}

void loop()
{
    if (receivedFlag) {
        // reset flag
        receivedFlag = false;

        // Read received data as an Arduino String
        String gpsDataStr;
        int state = radio.readData(gpsDataStr);

        if (state == RADIOLIB_ERR_NONE) {
            gpsInfoParse(gpsDataStr);
            // Packet was successfully received
            Serial.println(F("[LoRa] Received packet:"));
            M5.Display.clear();

            // Data of the packet
            Serial.print(F("[LoRa] Data:\t\t"));
            Serial.println(gpsDataStr);

            // RSSI (Received Signal Strength Indicator)
            Serial.print(F("[LoRa] RSSI:\t\t"));
            Serial.print(radio.getRSSI());
            Serial.println(F(" dBm"));

            // SNR (Signal-to-Noise Ratio)
            Serial.print(F("[LoRa] SNR:\t\t"));
            Serial.print(radio.getSNR());
            Serial.println(F(" dB"));

            // Frequency error
            Serial.print(F("[LoRa] Frequency error:\t"));
            Serial.print(radio.getFrequencyError());
            Serial.println(F(" Hz"));

            // Display GPS Information
            M5.Display.setCursor(0,5);
            M5.Display.setTextColor(TFT_BLUE);
            M5.Display.printf("Received packet: ");
            M5.Display.setTextColor(TFT_WHITE);
            M5.Display.printf("#%s\n", Count);
            M5.Display.setTextColor(TFT_YELLOW);
            M5.Display.printf("Type: %s\n", Type);
            M5.Display.printf("Sat: %s\n", Sat);
            M5.Display.printf("Lat: %s\n", Lat);
            M5.Display.printf("Lng: %s\n", Lng);
            M5.Display.printf("Date: %s\n", Date);
            M5.Display.printf("Time: %s\n", Time);
        } else if (state == RADIOLIB_ERR_CRC_MISMATCH) {
            // Packet was received, but is malformed
            Serial.println(F("CRC error!"));

        } else {
            Serial.print(F("Receive failed, code: "));
            Serial.println(state);
        }
    }
}

Since the GPS of this product uses a built-in antenna without an external one, please try to use the sending device in open outdoor areas such as playgrounds or rooftops. For first-time use, the wait time is relatively long, about a few minutes, so please be patient while the device searches for satellites and obtains coordinates.

The result of a successful run of the above example is as follows:

  • Transmitter serial output:
=========================================
Location: satellites:17
GNSS Mode:GLN_Sat
22.687502,113.772232
Date/Time: 9/5/2025 01:31:06.00
[LoRa] Sending GPS data:
 Type:GLN_Sat Count:120 Sat:17 Lat:22.687502 Lng:113.772232 Date:9/5/2025 Time:01:31:06.00
      Transmission finished!
  • Receiver serial output:
[LoRa] Received packet:
[LoRa] Data:            Type:GLN_Sat Count:120 Sat:17 Lat:22.687502 Lng:113.772232 Date:9/5/2025 Time:01:31:06.00
[LoRa] RSSI:            -2.00 dBm
[LoRa] SNR:             6.50 dB
[LoRa] Frequency error: -789.77 Hz
On This Page