A Feather M0 microcontroller with a LoRa 900 MHz radio all within a single Feather package.
Adafruit Feather + LoRa 900 MHz product ID 3178
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.
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.
/*
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()
/*
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.