Feather M0 LoRa 900 MHz

A Feather M0 microcontroller with a LoRa 900 MHz radio all within a single Feather package.  

Links:

Feather M0 LoRa 900 MHz guide

LoRa technology overview

Adafruit Feather + LoRa 900 MHz product ID 3178

M0 LoRa pinout

 

Hardware

 

Integrated Microcontroller + LoRa
LoRa Feather
M0
RFM95
Feather
32u4
RFM95
IRQ 3 7
CS 8 8
RST 4 4

The table above shows the hard wired pin assignments for the "M0 RFM95" and "32u4 RFM95".   Use this table for codes assignments when using the LoRa library, and to be aware of consumed pins.  

 

Firmware

The code below demonstrates sending a message via LoRa, and have it returned to the sender and confirmed.   The message in this example consists of an unsigned integer between 0 and 3300 (for 0 to 3300 mV), but it could be a larger value, or even a floating point value.   An Adafruit 128x32 mono OLED is used on each to see the values transmitted and received by each device.  

LoRa Sender


/*
  M0 RFM95 LoRa Sender
  
  Adafruit Feather M0 with built-in RFM95 LoRa 900 MHz
  AF product #3178

*/

// M0  (Feather M0)
const byte pin_LED = 13;
const byte pin_BATT = A7;

//////////////////////////////////////////////////////////////////////////////
// 10000 ms = 10 sec = 0.1 Hz 
// 1000 ms = 1 sec = 1 Hz
// 100 ms = 0.1 sec = 10 Hz
// 10 ms = 0.01 sec = 100 Hz
unsigned long timerInterval = 5000;  
unsigned long timerLast = 0;  // timer
//////////////////////////////////////////////////////////////////////////////
//  LoRa 900 MHz Radio
//  RadioHead library for LoRa Radio
//  http://www.airspayce.com/mikem/arduino/RadioHead/index.html
#include <RH_RF95.h>
// Pins on A,B,C,D,E vary by the microcontroller, so match up the pin position.
// M0: A=11; B=10; C=9; D=6; E=5
// Feather M0 with built-in LoRa 900 MHz (AF product #3178)
#define RFM95_CS 8
#define RFM95_RST 4
#define RFM95_INT 3
// Define frequency (set later)
#define RF95_FREQ 915.0
// Singleton instance of the radio driver
RH_RF95 rf95(RFM95_CS, RFM95_INT);
int16_t packetnum = 0;  // packet counter, we increment per xmission
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// NOTE: The FeatherWing OLED has an auto-reset chip, so you don't need to declare a reset pin.
// set OLED_RESET to -1 or dont pass it in, the reset button is hard-coded to the OLED reset
Adafruit_SSD1306 display(128, 32, &Wire);
const byte pin_Button_A = 9;
const byte pin_Button_B = 6;
const byte pin_Button_C = 5;
/////////////////////////////////////////////////////////////////////////////
// Show serial messages when DEBUG = true, otherwise minimize them.
// WARNING:   I experienced the Feather M0 waiting for the serial monitor to be
//            loaded from the IDE before the sketch would continue to run.  
//            For this reason, I am wrapping serial output around a DEBUG check.
#define DEBUG false
//////////////////////////////////////////////////////////////////////////////

byte cData[6];  // 5 character unsigned integer (0 to 32767) + null char
unsigned long successCount = 0;
boolean bStopOnRecvMsgErr = true;


void setup() {  
  pinMode(pin_LED, OUTPUT);
  
  #if DEBUG
  Serial.begin(9600);
  while (!Serial) {
    delay(1);
  }
  Serial.println("Serial ready");
  #endif

  randomSeed(analogRead(A0));

  //////////////////////////////////////////////////////////////////////////////
  // Feather OLED
  // by default, generate the high voltage from the 3.3v line internally.
  // initialize with the I2C addr 0x3C (for the 128x32)
  delay(100);  // Give the OLED driver time to bring it's internal charge up to voltage.
  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
    blinkLED(pin_LED);
    for(;;); // Don't proceed, loop forever
  }
  // Configure and clear the OLED
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setTextWrap(false);
  display.setCursor(0,0);
  display.print("LoRa");
  display.setCursor(0,16);
  display.print("Sender");
  display.display();

  //////////////////////////////////////////////////////////////////////////////
  //  LoRa 900 MHz Radio
  pinMode(RFM95_RST, OUTPUT);
  digitalWrite(RFM95_RST, HIGH);
  // manual reset
  digitalWrite(RFM95_RST, LOW);
  delay(10);
  digitalWrite(RFM95_RST, HIGH);
  delay(10);
 
  while (!rf95.init()) {
    display.clearDisplay();
    display.setTextSize(2);
    display.setCursor(0,0);
    display.print("ERROR");
    display.setCursor(0,16);
    display.print("rf95.init");
    display.display();
    while (1) blinkERR(pin_LED);
  }
  if (!rf95.setFrequency(RF95_FREQ)) {
    while (1) blinkERR(pin_LED);
  }
  rf95.setTxPower(23, false);
  //////////////////////////////////////////////////////////////////////////////

  blinkLED(pin_LED);
} // setup()


void loop() {

  // Send a message at a random interval between 500 and 5000 ms
  if (timerLast > millis())  timerLast = millis();
  if ((millis() - timerLast) > timerInterval) {
    digitalWrite(pin_LED, HIGH);
    
    // void BuildRandomUnsignedIntChar(byte *arr, int len, int lower, int upper)
    BuildRandomUnsignedIntChar(cData, sizeof(cData), 0, 3300);

    display.clearDisplay();
    display.setTextSize(2);
    display.setCursor(0,0);
    for (int i=0; i<sizeof(cData); i++) {
      display.write(cData[i]);
    }    
    display.display();

    // Send a message to rf95_server
    rf95.send(cData, sizeof(cData));  

    // Wait for a reply
    rf95.waitPacketSent(5000);
    uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
    uint8_t len = sizeof(buf);
    if (rf95.waitAvailableTimeout(1000)) { 
      // Should be a reply message for us now   
      if (rf95.recv(buf, &len)) {
        // Compare the received data buff to the data sent arrToEncrypt
        int matches = 0;
        for(int i=0; i<sizeof(cData); i++){
          if(buf[i]==cData[i]){     
            matches++;  
          } 
        }
        if (matches == sizeof(cData)) {
          successCount++;
          digitalWrite(pin_LED, LOW); 
          display.setCursor(0,16);
          display.print("RSSI ");
          display.print(rf95.lastRssi(), DEC);
          display.display();          
        } else {
          display.setCursor(0,16);
          display.print("CRC ERROR");
          display.display();          
          if (bStopOnRecvMsgErr == true) while (1) blinkERR(pin_LED);
        }
      } else {
        // Receive failed
        display.setCursor(0,16);
        display.print("RCV ERROR");
        display.display();          
        if (bStopOnRecvMsgErr == true) while (1) blinkERR(pin_LED);
      }
    } 
    timerInterval = random(1,500);  
    timerLast = millis();
  } // timer
  
} // loop()


void blinkLED(byte ledPIN){
  //  consumes 300 ms.
  for(int i = 5; i>0; i--){
    digitalWrite(ledPIN, HIGH);
    delay(30);
    digitalWrite(ledPIN, LOW);
    delay(30);
  }    
} // blinkLED()


void blinkERR(byte ledPIN){
  // S-O-S
  const int S = 150, O = 300;
  for(int i = 3; i>0; i--){
    digitalWrite(ledPIN, HIGH);
    delay(S);
    digitalWrite(ledPIN, LOW);
    delay(S);
  }    
  delay(200);
  for(int i = 3; i>0; i--){
    digitalWrite(ledPIN, HIGH);
    delay(O);
    digitalWrite(ledPIN, LOW);
    delay(O);
  }    
  delay(200);
  for(int i = 3; i>0; i--){
    digitalWrite(ledPIN, HIGH);
    delay(S);
    digitalWrite(ledPIN, LOW);
    delay(S);
  }    
  delay(200);
} // blinkERR()


//////////////////////////////////////////////////////////////////////////////

void BuildRandomUnsignedIntChar(byte *arr, int len, int lower, int upper) {
  // Works on Feather M0 (ATSAMD21 Cortex M0)
  for (int i=0; i<len; i++) {
    arr[i] = 0x20;  // space character
  }
  int iRnd = random(lower,upper);
  char cInt[len-1];
  sprintf(cInt, "%02u", iRnd);
  for (int i=0; i<strlen(cInt); i++) {
    arr[i] = cInt[i];
  } 
  arr[4] = 0x00; // null character
} // BuildRandomUnsignedIntChar()


 

LoRa Receiver


/*
  M0 RFM95 LoRa Receiver
  
  Adafruit Feather M0 Basic with the 
  FeatherWing RFM95 LoRa 900 MHz stacked on top of it.  

  COM9

*/

// M0  (Feather M0)
const byte pin_LED = 13;
const byte pin_BATT = A7;
//////////////////////////////////////////////////////////////////////////////
//  LoRa 900 MHz Radio
//  RadioHead library for LoRa Radio
//  http://www.airspayce.com/mikem/arduino/RadioHead/index.html
//#include <SPI.h>
#include <RH_RF95.h>
// Pins on A,B,C,D,E vary by the microcontroller, so match up the pin position.
// M0: A=11; B=10; C=9; D=6; E=5
// Feather M0
#define RFM95_INT  6   // "D"   alternative is 3
#define RFM95_CS  10   // "B"   alternative is 8
#define RFM95_RST 11   // "A"   alternative is 4
// Define the frequency
#define RF95_FREQ 915.0
// Singleton instance of the radio driver
RH_RF95 rf95(RFM95_CS, RFM95_INT);
//////////////////////////////////////////////////////////////////////////////
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// NOTE: The FeatherWing OLED has an auto-reset chip, so you don't need to declare a reset pin.
// set OLED_RESET to -1 or dont pass it in, the reset button is hard-coded to the OLED reset
Adafruit_SSD1306 display(128, 32, &Wire);
const byte pin_Button_A = 9;
const byte pin_Button_B = 6;
const byte pin_Button_C = 5;
//////////////////////////////////////////////////////////////////////////////
// Show serial messages when DEBUG = true, otherwise minimize them.
// WARNING:   I experienced the Feather M0 waiting for the serial monitor to be
//            loaded from the IDE before the sketch would continue to run.  
//            For this reason, I am wrapping serial output around a DEBUG check.
#define DEBUG false
//////////////////////////////////////////////////////////////////////////////
boolean bStopOnError = true;
char msg[RH_RF95_MAX_MESSAGE_LEN];

void setup() {  
  pinMode(pin_LED, OUTPUT);

  #if DEBUG
  Serial.begin(9600);
  while (!Serial) {
    delay(1);
  }
  Serial.println("Serial ready");
  #endif

  //////////////////////////////////////////////////////////////////////////////
  // Feather OLED
  // by default, generate the high voltage from the 3.3v line internally.
  // initialize with the I2C addr 0x3C (for the 128x32)
  delay(100);  // Give the OLED driver time to bring it's internal charge up to voltage.
  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
    blinkERR(pin_LED);
    for(;;); // Don't proceed, loop forever
  }
  // Configure and clear the OLED
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setTextWrap(false);
  display.setCursor(0,0);
  display.print("LoRa");
  display.setCursor(0,16);
  display.print("Receiver");
  display.display();

  //////////////////////////////////////////////////////////////////////////////
  //  LoRa 900 MHz Radio
  pinMode(RFM95_RST, OUTPUT);
  delay(10);
  digitalWrite(RFM95_RST, HIGH);
  delay(10);
  // manual reset
  digitalWrite(RFM95_RST, LOW);
  delay(10);
  digitalWrite(RFM95_RST, HIGH);
  delay(10);
  // Initialize the LoRa driver 
  while (!rf95.init()) {
    display.clearDisplay();
    display.setTextSize(2);
    display.setCursor(0,0);
    display.print("ERROR");
    display.setCursor(0,16);
    display.print("rf95.init");
    display.display();
    while (1) blinkERR(pin_LED);
  }
  if (!rf95.setFrequency(RF95_FREQ)) {
    while (1) blinkERR(pin_LED);
  }
  rf95.setTxPower(23, false);
  //////////////////////////////////////////////////////////////////////////////

  blinkLED(pin_LED);
} // setup()


void loop() {
  
  if (rf95.available()) {
    // Should be a message for us now
    uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
    uint8_t len = sizeof(buf);
    if (rf95.recv(buf, &len))     {
      digitalWrite(pin_LED, HIGH);

      // Extract integer value from buff
      unsigned int iPos = 0;
      do {
        msg[iPos] = char(buf[iPos]);
        iPos++;        
      } while (buf[iPos] != 0x00);
      iPos++;
      msg[iPos] = buf[iPos];
      //long = atol(buf);
      int iVal = atoi(msg);

      display.clearDisplay();
      display.setTextSize(2);
      display.setCursor(0,0);
      display.print(iVal);
      display.display();

      // Send a reply back to the sender
      rf95.send(buf, sizeof(buf));
      rf95.waitPacketSent(5000);

      //display.setCursor(0,16);
      //display.print("RSSI ");
      //display.print(rf95.lastRssi(), DEC);
      //display.display();          

      digitalWrite(pin_LED, LOW);
    } else {
      display.setCursor(0,16);
      display.print("ERROR");
      display.display();          
      if (bStopOnError == true) 
        while (1) blinkERR(pin_LED);
    } // rf95.recv
  } // rf95.available()
  
} // loop()


void blinkLED(byte ledPIN){
  //  consumes 300 ms.
  for(int i = 5; i>0; i--){
    digitalWrite(ledPIN, HIGH);
    delay(30);
    digitalWrite(ledPIN, LOW);
    delay(30);
  }    
} //blinkLED()


void blinkERR(byte ledPIN){
  // S-O-S
  const int S = 150, O = 300;
  for(int i = 3; i>0; i--){
    digitalWrite(ledPIN, HIGH);
    delay(S);
    digitalWrite(ledPIN, LOW);
    delay(S);
  }    
  delay(200);
  for(int i = 3; i>0; i--){
    digitalWrite(ledPIN, HIGH);
    delay(O);
    digitalWrite(ledPIN, LOW);
    delay(O);
  }    
  delay(200);
  for(int i = 3; i>0; i--){
    digitalWrite(ledPIN, HIGH);
    delay(S);
    digitalWrite(ledPIN, LOW);
    delay(S);
  }    
  delay(200);
} // blinkERR()

 

 


Do you need help developing or customizing a IoT product for your needs?   Send me an email requesting a free one hour phone / web share consultation.  

 

The information presented on this website is for the author's use only.   Use of this information by anyone other than the author is offered as guidelines and non-professional advice only.   No liability is assumed by the author or this web site.