LoRaPHY project, based upon SX1276Lib, hardware with NUCLEO-F103EB/L053R8 and other boards as well as multiple Radio modules of SX1272/1276/1278. The code contains MACRO definitions and can be configured as TX/RX only, PINGPONG and consider to be a code base of RTTY.

Dependencies:   SX1276Lib mbed

About LoRaPHY

This project has two major goals:

  1. Setup and verify low cost LoRa RF shields for Arduino compatible boards, Nucleo/Freedom, as well as various mini-core-boards. Share common hardware as possible as we can.
  2. Design competitive MAC layer based upon LoRa PHY layer radio link (LoRaPHY), with modified ALOHA algorithm for CSMA/CA, RTS/CTS control packets.

The first goal is very important for an extremely budget limited project, including this project, 100% personal self-sponsored and no support at all.

The second goal is to ease the pain of developing without a LoRaWAN gateway at hand. Sometimes SX1278/1272 single band module can be used to build a simple LoRaWAN gateway, or even more generic gateway. Yeap, it is also related to the budget as well as network coverage in your region.

We may derivate extra hardware/firmware/software products beyond above goals.

- Generic USB/UART dongle for LoRa with SX1278/SX1272/SX1276; - Verify Semtech IEEE488 SCPI as genernal equipment for LoRa; - Data Link Layer and IPv6 over LoRaPHY and ALOHA MAC....... - Cloud services.

Hardware

We delivery LoRa shield for Arduino/Nucleo/Freedom boards based upon mature RF modules. In order to avoid sourcing issues, we selected carefully and added solder jumpers to support multiple modules, therefore the developers can get constant supplies. Up to now, we have verified several SX1278 modules, and we will move on to verified SX1276 and SX1272 to cover EU/US bands.

Since we also develop based upon these boards, we added solder jumper for 3V3 VCC, so you can measure the power consumption of RF module. Additionally, we added some test pins, so you can use a logic analyzer to probe the SPI bus . This interface is very handy, I have removed some firmware bugs with this interface.

In this next version, we added I2C EEPROM. However since we can use internal Flash ROM to emulate EEPROM and some parts even have on-chip NVM, so it is still under evaluation.

RFM100L Arduino shield

Fig1, RFM100L with NPLINK SX1278, can plug into Arduino/Nucleo/Freedom boards

mbed Logo

Fig 2, RFM100L with AT SX1278 Ra-01 and NUCLEO-F103RB

mbed Logo

Fig 3, Schematics of RFM100L, check out the solder jumpers.

And we will continue to introduce more adapters for pin to pin compatible modules, as well as breakout boards for mini boards. Keep tuned.

MAC & Higher Layers

Since LoRa RFIC is a half duplex design, so we can not use CSMA/CD (Conflict Detect), we can use CSMA/CA(Conflict Avoidance). We will leverage wideband RSSI and CAD preamble detection as Carrier Sensing method. This part will borrow from LoRaWAN codebase.

In order to avoid conflict, we use RTS/CTS with timestamp information before real packet transaction. So other nodes will know how long the channel will be occupied. Then we will define packet structure. And X.25 over LoRa, and then IP based upon X.25.......

More discussion on Stacks.

Firmware

Most of the LoRa applications follow LoRaWAN specifications. LoRaWAN is suitable for fixed location IoT DAQ system. In some applications like people positioning system, we may need different MAC layer design due to different topology, which is similiar to early age of radio communications, HAM or walkie talkie. Therefore peer to peer and competitive MAC layer is required, instead of hybird MAC like LoRaWAN, which is competitive for uplink and time scheduler for downlink.

LoRaWAN firmware

Since LoRaWAN is a major fork for device firmware, we will verified the LoRaWAN device stack on our hardware.

MODEM firmware

In order to make it a MODEM, we will define AT command set to interface via UART/USB CDC. To make it easy to develop LoRa in a regular PC or Raspberry Pi/Android/Router.

SCPI firmware

We can reuse the same hardware for testing purposes by using Semtech's SCPI project.

Host Software

The software running on host will be written in Python as well as Java, including CPython/Jython, Java for Android and Processing.

Bugs

Please refer Bugs for detail information.

Ordering

There are three products available right now:

- RFM100L, NPLINK/AiThinker SX1278 modules, Arduino Shield - RFM110L, HPD/HopeRF SX1272/1276/1278 modules, Arduino Shield, with 24CL64 FeRAM, uFL/SMA connector. - RFM1100A, clone version of Adafruit LoRa module, with uFL/SMA connector and modified BOM for LDO. - LRD110L, HPD/HopeRF SX1272/1276/1278 modules with STM32F103C8/CB mini board, the lowest cost dongle.

So far our first batch (20170201) of prototypes are only available via taobao.com in China. But we will arrange channels on tindie soon.

Please contact me (allankliu(at)163.com) if you are interested in this project.

main.cpp

Committer:
allankliu
Date:
2017-02-22
Revision:
0:90252f6ec3d0

File content as of revision 0:90252f6ec3d0:

#include "mbed.h"
#include "main.h"
#include "sx1276-hal.h"
#include "debug.h"

/* Set this flag to '1' to display debug messages on the console */
#define DEBUG_MESSAGE   0

/* Set this flag to '1' to use the LoRa modulation or to '0' to use FSK modulation */
#define USE_MODEM_LORA  1
#define USE_MODEM_FSK   !USE_MODEM_LORA

//#define RF868  1

#ifdef RF868

    #define RF_FREQUENCY                                    868000000 // Hz
    #define TX_OUTPUT_POWER                                 14        // 14 dBm

#else

    #define RF_FREQUENCY                                    433000000 // Hz
    #define TX_OUTPUT_POWER                                 14        // 26 dBm

#endif

#if USE_MODEM_LORA == 1
    #define LORA_BANDWIDTH  0
    #define LORA_SPREADING_FACTOR   10
    #define LORA_CODINGRATE     2

    //#define LORA_BANDWIDTH                              2         // [0: 125 kHz,
                                                                  //  1: 250 kHz,
                                                                  //  2: 500 kHz,
                                                                  //  3: Reserved]
    //#define LORA_SPREADING_FACTOR                       7         // [SF7..SF12]
    //#define LORA_CODINGRATE                             1         // [1: 4/5,
                                                                  //  2: 4/6,
                                                                  //  3: 4/7,
                                                                  //  4: 4/8]
    #define LORA_PREAMBLE_LENGTH                        8         // Same for Tx and Rx
    #define LORA_SYMBOL_TIMEOUT                         5         // Symbols
    #define LORA_FIX_LENGTH_PAYLOAD_ON                  false
    #define LORA_FHSS_ENABLED                           false  
    #define LORA_NB_SYMB_HOP                            4     
    #define LORA_IQ_INVERSION_ON                        false
    #define LORA_CRC_ENABLED                            true
    
#elif USE_MODEM_FSK == 1

    #define FSK_FDEV                                    25000     // Hz
    #define FSK_DATARATE                                19200     // bps
    #define FSK_BANDWIDTH                               50000     // Hz
    #define FSK_AFC_BANDWIDTH                           83333     // Hz
    #define FSK_PREAMBLE_LENGTH                         5         // Same for Tx and Rx
    #define FSK_FIX_LENGTH_PAYLOAD_ON                   false
    #define FSK_CRC_ENABLED                             true
    
#else
    #error "Please define a modem in the compiler options."
#endif

#define RX_TIMEOUT_VALUE                                3500000   // in us
#define BUFFER_SIZE                                     32        // Define the payload size here

#if( defined ( TARGET_KL25Z ) || defined ( TARGET_LPC11U6X ) )
DigitalOut led(LED2);
#else
//DigitalOut led(LED1);
//DigitalOut led2(LED2);
//DigitalOut led3(LED3);
//DigitalOut led4(LED4);
DigitalOut debugled(D6);
DigitalOut debugled2(D7);

#endif

DigitalIn role(USER_BUTTON);  // PC_13 in Mini 103RC mini board
Timer tx_tmr;

#define UART_ENABLE

#ifdef UART_ENABLE
//Serial pc(USBTX, USBRX);
Serial pc(SERIAL_TX, SERIAL_RX);
#endif

Ticker nwk_ticker;

bool channelOccupied = false;

#define NWK_SLEEP  0
#define NWK_TX     1
#define NWK_TX_NOK 2
#define NWK_RX_OK  3
#define NWK_RX_NOK 4   

//#define PINGPONG

void nwk_toggle()
{
    debugled = !debugled;
    debugled2 = !debugled2;
}

void nwk_setmode(uint8_t mode)
{
    switch(mode){
        case NWK_TX:
        case NWK_RX_OK:
            nwk_ticker.attach(&nwk_toggle, 1);
            break;
        case NWK_TX_NOK:
        case NWK_RX_NOK:
            nwk_ticker.attach(&nwk_toggle, 0.1);
            break;
        case NWK_SLEEP:
        default:
            debugled = 0;
            debugled2 = 0;
            nwk_ticker.detach();
            break;
    }
}

/*
 *  Global variables declarations
 */
typedef enum
{
    ST_LOWPOWER = 0,
    ST_IDLE,
    
    ST_RX,
    ST_RX_TIMEOUT,
    ST_RX_ERROR,
    
    ST_TX,
    ST_TX_TIMEOUT,
    
    ST_CAD,
    ST_CAD_DONE
}AppStates_t;

volatile AppStates_t State = ST_LOWPOWER;

/*!
 * Radio events function pointer
 */
static RadioEvents_t RadioEvents;

/*
 *  Global variables declarations
 */
SX1276MB1xAS Radio( NULL );

const uint8_t PingMsg[] = "PING";
const uint8_t PongMsg[] = "PONG";
const uint8_t TestMsg[] = "LoRa Test";

uint16_t BufferSize = BUFFER_SIZE;
uint8_t Buffer[BUFFER_SIZE];

//int16_t RssiValue = 0.0;
//int8_t SnrValue = 0.0;

int16_t RssiValue = 0;
int8_t SnrValue = 0;

#define REG_SIZE 0x70 // see below
#define REG_IDX_SIZE 39

static int my_strncmp(const char *, const char *, int);
static void my_strcpy(char * , const char *);

int my_strncmp(const char * s1, const char * s2, int size)
{
    int n = size;
    for ( ; n > 0; s1++, s2++, --n)
    if (*s1 != *s2)
        return ((*(unsigned char *)s1 < *(unsigned char *)s2) ? -1 : +1);
    else if (*s1 == '\0')
        return 0;
    return 0;    
}

void my_strcpy(char * s1, const char * s2)
{
    char *s = s1;
    while ((*s++ = *s2++) != 0)
    ;
}

void Sender ( void )
{
    int i;
    my_strcpy( ( char* )Buffer, ( char* )PingMsg );
    for( i = 4; i < BufferSize; i++ ){
        Buffer[i] = i - 4;
    }
    Radio.Send( Buffer, BufferSize );
}

int main() 
{
    uint8_t i;
    uint8_t regval;
    bool isMaster = true;
    int begin, end;

    uint32_t rand;
    debugled = 1;
    debugled2 = 1;

    isMaster = role.read();
    
    //debug( "\n\n\r     SX1276 Ping Pong Demo Application \n\n\r" );

    // Initialize Radio driver
    RadioEvents.TxDone = OnTxDone;
    RadioEvents.RxDone = OnRxDone;
    RadioEvents.RxError = OnRxError;
    RadioEvents.TxTimeout = OnTxTimeout;
    RadioEvents.RxTimeout = OnRxTimeout;
    Radio.Init( &RadioEvents );

    #ifdef UART_ENABLE
    regval = Radio.Read(REG_VERSION);
    pc.printf("%s", TestMsg);
    pc.printf("IC Version: %02X\r\n", regval);
    regval = Radio.Read(REG_OPMODE);
    pc.printf("OPMODE: %02X\r\n", regval);
    #endif

    debugled = 0;
    debugled2 = 0;
    
    // verify the connection with the board
    while( Radio.Read( REG_VERSION ) == 0x00  )
    {
        //debug( "Radio could not be detected!\n\r", NULL );
        wait( 1 );
    }
            
    //debug_if( ( DEBUG_MESSAGE & ( Radio.DetectBoardType( ) == SX1276MB1LAS ) ) , "\n\r > Board Type: SX1276MB1LAS < \n\r" );
    //debug_if( ( DEBUG_MESSAGE & ( Radio.DetectBoardType( ) == SX1276MB1MAS ) ) , "\n\r > Board Type: SX1276MB1MAS < \n\r" );
    
    Radio.SetChannel( RF_FREQUENCY ); 

#if USE_MODEM_LORA == 1
    
    //debug_if( LORA_FHSS_ENABLED, "\n\n\r             > LORA FHSS Mode < \n\n\r");
    //debug_if( !LORA_FHSS_ENABLED, "\n\n\r             > LORA Mode < \n\n\r");
    
    Radio.SetTxConfig( MODEM_LORA, TX_OUTPUT_POWER, 0, LORA_BANDWIDTH,
                         LORA_SPREADING_FACTOR, LORA_CODINGRATE,
                         LORA_PREAMBLE_LENGTH, LORA_FIX_LENGTH_PAYLOAD_ON,
                         LORA_CRC_ENABLED, LORA_FHSS_ENABLED, LORA_NB_SYMB_HOP, 
                         LORA_IQ_INVERSION_ON, 2000000 );
    
    Radio.SetRxConfig( MODEM_LORA, LORA_BANDWIDTH, LORA_SPREADING_FACTOR,
                         LORA_CODINGRATE, 0, LORA_PREAMBLE_LENGTH,
                         LORA_SYMBOL_TIMEOUT, LORA_FIX_LENGTH_PAYLOAD_ON, 0,
                         LORA_CRC_ENABLED, LORA_FHSS_ENABLED, LORA_NB_SYMB_HOP, 
                         LORA_IQ_INVERSION_ON, true );
                         
#elif USE_MODEM_FSK == 1

    //debug("\n\n\r              > FSK Mode < \n\n\r");
    Radio.SetTxConfig( MODEM_FSK, TX_OUTPUT_POWER, FSK_FDEV, 0,
                         FSK_DATARATE, 0,
                         FSK_PREAMBLE_LENGTH, FSK_FIX_LENGTH_PAYLOAD_ON,
                         FSK_CRC_ENABLED, 0, 0, 0, 2000000 );
    
    Radio.SetRxConfig( MODEM_FSK, FSK_BANDWIDTH, FSK_DATARATE,
                         0, FSK_AFC_BANDWIDTH, FSK_PREAMBLE_LENGTH,
                         0, FSK_FIX_LENGTH_PAYLOAD_ON, 0, FSK_CRC_ENABLED,
                         0, 0, false, true );
                         
#else

#error "Please define a modem in the compiler options."

#endif
     
    //debug_if( DEBUG_MESSAGE, "Starting Ping-Pong loop\r\n" ); 
        
    rand = Radio.Random();
    rand = rand%1000;
    wait_ms((uint16_t)rand);
    
#ifdef PINGPONG
    Radio.Rx( RX_TIMEOUT_VALUE );
    
   
    while( 1 )
    {
        switch( State )
        {
        case ST_RX:
            if( isMaster == true )
            {
                if( BufferSize > 0 )
                {
                    #ifdef UART_ENABLE
                    pc.printf("Received message:\r\n");
                    pc.printf("%s",Buffer);
                    #endif
                    //if( strncmp( ( const char* )Buffer, ( const char* )PongMsg, 4 ) == 0 )
                    if( my_strncmp( ( const char* )Buffer, ( const char* )PongMsg, 4 ) == 0 )
                    {
                        nwk_setmode(NWK_NORMAL);
                        //debug( "...Pong\r\n" );
                        // Send the next PING frame            
                        my_strcpy( ( char* )Buffer, ( char* )PingMsg );
                        //_strcpy( ( char* )Buffer, ( char* )PingMsg );
                        // We fill the buffer with numbers for the payload 
                        for( i = 4; i < BufferSize; i++ )
                        {
                            Buffer[i] = i - 4;
                        }
                        wait_ms( 10 ); 
                        Radio.Send( Buffer, BufferSize );
                    }
                    else if( my_strncmp( ( const char* )Buffer, ( const char* )PingMsg, 4 ) == 0 )
                    { // A master already exists then become a slave
                        debug( "...Ping\r\n" );
                        //nwk_setmode(NWK_NORMAL);
                        //led = !led;
                        isMaster = false;
                        // Send the next PONG frame            
                        my_strcpy( ( char* )Buffer, ( char* )PongMsg );
                        // We fill the buffer with numbers for the payload 
                        for( i = 4; i < BufferSize; i++ )
                        {
                            Buffer[i] = i - 4;
                        }
                        wait_ms( 10 ); 
                        Radio.Send( Buffer, BufferSize );
                    }
                    else // valid reception but neither a PING or a PONG message
                    {    // Set device as master ans start again
                        isMaster = true;
                        Radio.Rx( RX_TIMEOUT_VALUE );
                    }    
                }
            }
            else
            {
                if( BufferSize > 0 )
                {
                    #ifdef UART_ENABLE
                    pc.printf("Received message:\r\n");
                    pc.printf("%s",Buffer);
                    #endif
                    
                    if( my_strncmp( ( const char* )Buffer, ( const char* )PingMsg, 4 ) == 0 )
                    {
                        nwk_setmode(NWK_NORMAL);
                        //led = !led;
                        //debug( "...Ping\r\n" );
                        // Send the reply to the PING string
                        my_strcpy( ( char* )Buffer, ( char* )PongMsg );
                        // We fill the buffer with numbers for the payload 
                        for( i = 4; i < BufferSize; i++ )
                        {
                            Buffer[i] = i - 4;
                        }
                        wait_ms( 10 );  
                        Radio.Send( Buffer, BufferSize );
                    }
                    else // valid reception but not a PING as expected
                    {    // Set device as master and start again
                        isMaster = true;
                        Radio.Rx( RX_TIMEOUT_VALUE );
                    }    
                }
            }
            State = ST_LOWPOWER;
            break;
        case ST_TX:    
            //nwk_setmode(NWK_NORMAL);
            //led = !led; 
            if( isMaster == true )  
            {
                //debug( "Ping...\r\n" );
            }
            else
            {
                //debug( "Pong...\r\n" );
            }
            Radio.Rx( RX_TIMEOUT_VALUE );
            State = ST_LOWPOWER;
            break;
        case ST_RX_TIMEOUT:
            //debugled = !debugled;
            //debugled2 = !debugled2;
            nwk_setmode(NWK_ERR);
            if( isMaster == true )
            {
                // Send the next PING frame
                my_strcpy( ( char* )Buffer, ( char* )PingMsg );
                for( i = 4; i < BufferSize; i++ )
                {
                    Buffer[i] = i - 4;
                }
                wait_ms( 10 ); 
                Radio.Send( Buffer, BufferSize );
            }
            else
            {
                Radio.Rx( RX_TIMEOUT_VALUE );  
            }             
            State = ST_LOWPOWER;
            break;
        case ST_RX_ERROR:
            // We have received a Packet with a CRC error, send reply as if packet was correct
            nwk_setmode(NWK_ERR);
            if( isMaster == true )
            {
                // Send the next PING frame
                my_strcpy( ( char* )Buffer, ( char* )PingMsg );
                for( i = 4; i < BufferSize; i++ )
                {
                    Buffer[i] = i - 4;
                }
                wait_ms( 10 );  
                Radio.Send( Buffer, BufferSize );
            }
            else
            {
                // Send the next PONG frame
                my_strcpy( ( char* )Buffer, ( char* )PongMsg );
                for( i = 4; i < BufferSize; i++ )
                {
                    Buffer[i] = i - 4;
                }
                wait_ms( 10 );  
                Radio.Send( Buffer, BufferSize );
            }
            State = ST_LOWPOWER;
            break;
        case ST_TX_TIMEOUT:
            nwk_setmode(NWK_ERR);
            Radio.Rx( RX_TIMEOUT_VALUE );
            State = ST_LOWPOWER;
            break;
        case ST_CAD:
            break;
        case ST_CAD_DONE:
            if(channelOccupied){
                State = ST_LOWPOWER;
            }else{
                // start to transmit here.
            }
            break;
        case ST_LOWPOWER:
            break;
        default:
            nwk_setmode(0xFF);
            State = ST_LOWPOWER;
            break;
        }    
    }

#else
    
    if(isMaster == true){
        tx_tmr.start();
        begin = tx_tmr.read_ms();
    }else{
        Radio.Rx( RX_TIMEOUT_VALUE );
    }
    
    while(1){
        switch(State){
        case ST_RX:
            //nwk_setmode(NWK_RX_OK);
            Radio.Rx( RX_TIMEOUT_VALUE );
            State = ST_LOWPOWER;
#ifdef UART_ENABLE
            //pc.printf("\r\nRX OK\tRSSI:%f  SNR:%f \r\n",float(RssiValue),float(SnrValue));
            pc.printf("\r\nRX OK\tRSSI:%d  SNR:%d \r\n",RssiValue,SnrValue);
            pc.printf("\r\nRSSI: %02X SNR: %01X\r\n", RssiValue, SnrValue);
#endif      
            break;
        case ST_TX:
            nwk_setmode(NWK_TX);
            State = ST_LOWPOWER;
#ifdef UART_ENABLE
            pc.printf("\r\nTX Done.\r\n");
#endif                
            break;
        case ST_RX_TIMEOUT:
        case ST_RX_ERROR:
            //nwk_setmode(NWK_RX_NOK);
            Radio.Rx( RX_TIMEOUT_VALUE );
            State = ST_LOWPOWER;
#ifdef UART_ENABLE
            pc.printf("\r\nRX TIMEOUT or ERROR.\r\n");
#endif                
            break;
        case ST_TX_TIMEOUT:
            State = ST_LOWPOWER;
            break;
        case ST_LOWPOWER:
            if (isMaster){
                end = tx_tmr.read_ms();
                if ((end-begin) >= 2000){
                    Sender();
                    tx_tmr.stop();
                    tx_tmr.start();
                    begin = tx_tmr.read_ms();
                }
            }
            break;
        default:
            State = ST_LOWPOWER;
            break;
        }
    }
#endif
    
}


void OnTxDone( void )
{
    Radio.Sleep( );
    State = ST_TX;    
    //debug_if( DEBUG_MESSAGE, "> OnTxDone\n\r" );
}

void OnRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr)
{
    Radio.Sleep( );
    BufferSize = size;
    memcpy( Buffer, payload, BufferSize );
    RssiValue = rssi;
    SnrValue = snr;
    State = ST_RX;
    nwk_setmode(NWK_RX_OK);
    //debug_if( DEBUG_MESSAGE, "> OnRxDone\n\r" );
}

void OnTxTimeout( void )
{
    Radio.Sleep( );
    State = ST_TX_TIMEOUT;
    //debug_if( DEBUG_MESSAGE, "> OnTxTimeout\n\r" );
}

void OnRxTimeout( void )
{
    Radio.Sleep( );
    Buffer[ BufferSize ] = 0;
    State = ST_RX_TIMEOUT;
    nwk_setmode(NWK_RX_NOK);
    //debug_if( DEBUG_MESSAGE, "> OnRxTimeout\n\r" );
}

void OnRxError( void )
{
    Radio.Sleep( );
    State = ST_RX_ERROR;
    //debug_if( DEBUG_MESSAGE, "> OnRxError\n\r" );
}

// Added by allankliu
void OnCadDone( bool channelActivityDetected )
{
    Radio.Sleep();
    channelOccupied = channelActivityDetected;
    State = ST_CAD_DONE;
}