Comments: SparkFun SAMD21 Pro RF Hookup Guide


Looking for answers to technical questions?

We welcome your comments and suggestions below. However, if you are looking for solutions to technical questions please see our Technical Assistance page.

  • Member #1601910 / about 4 years ago * / 2

    I made a very minor modification to the example code referenced in the Hookup guide. I simply moved the transmit message part into an interrupt routine to send message when a button is pushed. The radio seems to stick in the tx mode(tx led is on) and nothing is received from the other board(or sent from this test board). To get the test board un-hung I have to reset it. I found that removing the rf95.waitPacketSent(); line helps but can still hang if I initiate another interrupt(button push) too soon after the first. However, if I leave the rf95.waitPacketSent(); in the code it will hang for sure every time and message is not sent. Can someone explain what is happening? I am not new to electronics, but am new to arduino and also I only do limited amount of coding. See my sketch below. Thank you.

    /* Both the TX and RX ProRF boards will need a wire antenna. We recommend a 3" piece of wire. This example is a modified version of the example provided by the Radio Head Library which can be found here: */

    #include <SPI.h>
    //Radio Head Library:
    #include <RH_RF95.h> 
    #define interruptPin 2 //Pin we are going to use to wake up the Arduino
    // We need to provide the RFM95 module's chip select and interrupt pins to the
    // rf95 instance below.On the SparkFun ProRF those pins are 12 and 6 respectively.
    RH_RF95 rf95(12, 6);
    int LED = 13; //Status LED is on pin 13
    int packetCounter = 0; //Counts the number of packets sent
    long timeSinceLastPacket = 0; //Tracks the time stamp of last packet received
    // The broadcast frequency is set to 921.2, but the SADM21 ProRf operates
    // anywhere in the range of 902-928MHz in the Americas.
    // Europe operates in the frequencies 863-870, center frequency at 868MHz.
    // This works but it is unknown how well the radio configures to this frequency:
    //float frequency = 864.1; 
    float frequency = 921.2; //Broadcast frequency
    void setup()
      pinMode(LED, OUTPUT);
      pinMode(interruptPin, INPUT_PULLUP);
      // It may be difficult to read serial messages on startup. The following line
      // will wait for serial to be ready before continuing. Comment out if not needed.
     // while(!SerialUSB); 
      SerialUSB.println("RFM Client!"); 
      //Initialize the Radio.
      if (rf95.init() == false){
        SerialUSB.println("Radio Init Failed - Freezing");
        while (1);
        //An LED inidicator to let us know radio initialization has completed. 
        SerialUSB.println("Transmitter up!"); 
        digitalWrite(LED, HIGH);
        digitalWrite(LED, LOW);
      // Set frequency
       // The default transmitter power is 13dBm, using PA_BOOST.
       // If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then 
       // you can set transmitter powers from 5 to 23 dBm:
       // Transmitter power can range from 14-20dbm.
       rf95.setTxPower(14, false);
    void loop()
    attachInterrupt(digitalPinToInterrupt(interruptPin), wakeUp, FALLING); // tried CHANGE, FALLING too
                      digitalWrite(LED, HIGH);
                       digitalWrite(LED, LOW);
     //void wakeUp();
    void wakeUp()
      digitalWrite(LED, LOW);
      SerialUSB.println("Sending message");
      //Send a message to the other radio
      uint8_t toSend[] = "EVENT DETECTED!";
      //sprintf(toSend, "Hi, my counter is: %d", packetCounter++);
      rf95.send(toSend, sizeof(toSend));
      rf95.waitPacketSent(); // <-----program hangs here every time, "Event Detected" message not sent unless I remove this line.

    • santaimpersonator / about 4 years ago / 1

      Hi there, it sounds like you are looking for technical assistance. Please use the link in the banner above, to get started with posting a topic in our forums. Our technical support team will do their best to assist you. (*I should note that custom code consultation is usually outside the scope of our support team.)

  • pubdc / about 2 years ago / 1

    There is a new LMIC library (MCCI), one has to edit a config file for the frequency plan selection "Arduino\libraries\MCCI_LoRaWAN_LMIC_library\project_config\lmic_project_config.h". I struggled until I located that...

    • Can you post an actual working example? Ever since TTN switched to "The Things Stack" all examples have stopped working, this guide is terribly outdated and just as you have discovered - the LMIC has changed. It seems nobody at SparkFun is interested to support this board with the new TTN way of doing things and up until stumbling on your post - I was ready to toss the board. Thank you

      • pubdc / about 2 years ago * / 1

        Not sure where you are getting stuck so hard to address the relevant point. You can still pretty much follow the guide, (add Arduino SAMD board definitions, add Sparkfun Board Definitions) except when it comes to installing the LMIC you go for "MCCI LoraWAN LMIC library" by Terry Moore. This will create some folders in yr Arduino directory, find this file "Arduino\libraries\MCCI_LoRaWAN_LMIC_library\project_config\lmic_project_config.h and select the appropriate frequency for your region, comment out the others (Its EU868 for me). Then back to the guide. static const u1_t PROGMEM APPEUI[8]={ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; //For TTN issued EUIs the last bytes should be 0xD5, 0xB3,0x70. static const u1_t PROGMEM DEVEUI[8]={ 0xZZ, 0xZZ, 0xZZ, 0xZZ, 0xZZ, 0xD5, 0xB3, 0x70 }; the "ZZ" you will need to replace with whatever key your device has in TTN, you can toggle the key format until it shows the D5-B3-70 at the end. I played with the SparkFun BME sensor and the below worked fine for me. Hope this helps ! ps Will post code in a separate reply, struggling with formatting

        • pubdc / about 2 years ago * / 1
          #include <lmic.h>
          #include <hal/hal.h>
          #include <SPI.h>
          // BME280 stuff ------------------------------
          #include <Wire.h>
          #include "SparkFunBME280.h"
          BME280 mySensor;
          // BME280 stuff ------------------------------
          # define FILLMEIN 0
          # warning "You must replace the values marked FILLMEIN with real values from the TTN control panel!"
          # define FILLMEIN (#dont edit this, edit the lines that use FILLMEIN)
          // fetch yr key on TTN, 00 is fine here
          static const u1_t PROGMEM APPEUI[8]={ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
          void os_getArtEui (u1_t* buf) { memcpy_P(buf, APPEUI, 8);}
          // fetch yr key on TTN, this one always ends D5-B3-70
          static const u1_t PROGMEM DEVEUI[8]={ 0xZZ, 0xZZ, 0xZZ, 0xZZ, 0xZZ, 0xD5, 0xB3, 0x70 };
          void os_getDevEui (u1_t* buf) { memcpy_P(buf, DEVEUI, 8);}
          // fetch yr key on TTN
          static const u1_t PROGMEM APPKEY[16] = { 0xZZ, 0xZZ, 0xZZ, 0xZZ, 0xZZ, 0xZZ, 0xZZ, 0xZZ, 0xZZ, 0xZZ, 0xZZ, 0xZZ, 0xZZ, 0xZZ, 0xZZ, 0xZZ };
          void os_getDevKey (u1_t* buf) {  memcpy_P(buf, APPKEY, 16);}
          //the hello world is from the tutorial, I commented this out and instead push a sensor value down below
          //static uint8_t mydata[] = "Hello, world!";
          static osjob_t sendjob;
          // Schedule TX every this many seconds (might become longer due to duty
          // cycle limitations).
          const unsigned TX_INTERVAL = 60;
          // Pin mapping for SparkFun SAMD21 Pro RF
          const lmic_pinmap lmic_pins = {
              .nss = 12,
              .rxtx = LMIC_UNUSED_PIN,
              .rst = 7,
              .dio = {6, 10, 11},
          void printHex2(unsigned v) {
              v &= 0xff;
              if (v < 16)
              SerialUSB.print(v, HEX);
          void onEvent (ev_t ev) {
              SerialUSB.print(": ");
              switch(ev) {
                  case EV_SCAN_TIMEOUT:
                  case EV_BEACON_FOUND:
                  case EV_BEACON_MISSED:
                  case EV_BEACON_TRACKED:
                  case EV_JOINING:
                  case EV_JOINED:
                        u4_t netid = 0;
                        devaddr_t devaddr = 0;
                        u1_t nwkKey[16];
                        u1_t artKey[16];
                        LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
                        SerialUSB.print("netid: ");
                        SerialUSB.println(netid, DEC);
                        SerialUSB.print("devaddr: ");
                        SerialUSB.println(devaddr, HEX);
                        SerialUSB.print("AppSKey: ");
                        for (size_t i=0; i<sizeof(artKey); ++i) {
                          if (i != 0)
                        SerialUSB.print("NwkSKey: ");
                        for (size_t i=0; i<sizeof(nwkKey); ++i) {
                                if (i != 0)
                      // Disable link check validation (automatically enabled
                      // during join, but because slow data rates change max TX
                // size, we don't use it in this example.
                  || This event is defined but not used in the code. No
                  || point in wasting codespace on it.
                  || case EV_RFU1:
                  ||     SerialUSB.println(F("EV_RFU1"));
                  ||     break;
                  case EV_JOIN_FAILED:
                  case EV_REJOIN_FAILED:
                  case EV_TXCOMPLETE:
                      SerialUSB.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
                      if (LMIC.txrxFlags & TXRX_ACK)
                        SerialUSB.println(F("Received ack"));
                      if (LMIC.dataLen) {
                        SerialUSB.print(F("Received "));
                        SerialUSB.println(F(" bytes of payload"));
                      // Schedule next transmission
                      os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
                  case EV_LOST_TSYNC:
                  case EV_RESET:
                  case EV_RXCOMPLETE:
                      // data received in ping slot
                  case EV_LINK_DEAD:
                  case EV_LINK_ALIVE:
                  || This event is defined but not used in the code. No
                  || point in wasting codespace on it.
                  || case EV_SCAN_FOUND:
                  ||    SerialUSB.println(F("EV_SCAN_FOUND"));
                  ||    break;
                  case EV_TXSTART:
                  case EV_TXCANCELED:
                  case EV_RXSTART:
                      /* do not print anything -- it wrecks timing */
                  case EV_JOIN_TXCOMPLETE:
                      SerialUSB.println(F("EV_JOIN_TXCOMPLETE: no JoinAccept"));
                      SerialUSB.print(F("Unknown event: "));
                      SerialUSB.println((unsigned) ev);
          void do_send(osjob_t* j){
          //reading the BME280 sensor here, pushing to serial for bugtracking
              float val = mySensor.readFloatPressure() ;
              SerialUSB.print("pressure : ");
          // convert pressure to just 2 bytes. this works but can be improved upon
              int valbyte = (val - 95000)/20;
              SerialUSB.print("press adj : ");
          // create array and store the pressure in 2 bytes
              byte mydata[2];
              mydata[0] = highByte(valbyte);
              mydata[1] = lowByte(valbyte);
          //decode the value here, same decoder on the other end in TTN
              int decoded = ((mydata[0] << 8) + mydata[1])*20 + 95000;
              SerialUSB.print("bytes decoded : ");
              // Check if there is not a current TX/RX job running
              if (LMIC.opmode & OP_TXRXPEND) {
                  SerialUSB.println(F("OP_TXRXPEND, not sending"));
              } else {
                  // Prepare upstream data transmission at the next possible time. -- here is where the 2 bytes get sent over LoRa
                  LMIC_setTxData2(1, mydata, sizeof(mydata), 0);
                  SerialUSB.println(F("Packet queued"));
              // Next TX is scheduled after TX_COMPLETE event.
          void setup() {
          //BME280 stuff-------------------
             if (mySensor.beginI2C() == false) //Begin communication over I2C
              SerialUSB.println("The sensor did not respond. Please check wiring.");
              delay(1000); //Freeze
          //BME280 stuff-------------------
            // line added to halt execution until serial port is online
              #ifdef VCC_ENABLE
              // For Pinoccio Scout boards
              pinMode(VCC_ENABLE, OUTPUT);
              digitalWrite(VCC_ENABLE, HIGH);
              // LMIC init
              // Reset the MAC state. Session and pending data transfers will be discarded.
              // Start job (sending automatically starts OTAA too)
          void loop() {
          • pubdc / about 2 years ago / 1

            in ttn, added a Javascript decoder :

            function decodeUplink(input) {
              var data = {};
              data.b0 = input.bytes[0];
              data.b1 = input.bytes[1];
              data.pressure = ((input.bytes[0] << 8) + input.bytes[1])*20 + 95000;
              var warnings = [];
              if (data.pressure < 990) {
                warnings.push("it's low pressure");
              if (data.pressure > 1030) {
                warnings.push("it's high pressure");
              return {
                data: data,
                warnings: warnings

            ... and then watch as the messages come through

  • Did anyone notice that the screnshot of how to copy the various keys from TTN's console shown in this guide is totally misleading? The picture shows the keys in MSB while they should be in LSB except for the APPKEY[16] which should be MSB

  • paulohm / about 4 years ago / 1

    seems to me that my device is entering sleep mode once I unplug it from the usb host. where can I find more info about disabling this?

    • santaimpersonator / about 4 years ago / 1

      Hi there, it sounds like you are looking for technical assistance. Please use the link in the banner above, to get started with posting a topic in our forums. Our technical support team will do their best to assist you.

      That being said, I didn't notice anything in this tutorial that would put your microcontroller into sleep mode. Maybe it is something in your code?

  • Member #731462 / about 5 years ago / 1

    Note: VDDCORE is tied to 3.3V on this board, which causes it to use too much current (~60mA awake, ~30mA asleep). Cut the trace from SAMD21G pin 43 to get much lower power usage (13mA awake, ~110uA sleep is achievable). SparkFun, if you revise the board, please fix this. Your other SAMD21 boards don't have VDDCORE pulled high.

    • Member #1583315 / about 4 years ago / 1

      Hi I cut the 3.3V line to pin 43 but the chip stopped working. Is there something missing?

      I tried to disable brown out detection but no hope.

      • santaimpersonator / about 4 years ago / 1

        Hi there, it sounds like you are looking for technical assistance. Unfortunately, as noted in the guide, we will not be helping customers with this modification as it does require more experienced technical skills; and those with the minimum skill level, would be able to figure out how to perform the modification without our assistance.

        That being said, feel free use the link in the banner above, to get started with posting a topic in our forums to get help from the community.

  • Member #367155 / about 5 years ago / 1

    I am following the instructions on this but my Arduino will not compile the line:

    // LoRaWAN end-device address (DevAddr) static const u4_t DEVADDR = { 0x26, 0x02, 0x1B, 0xE0 };

    I get an error: "scalar object 'DEVADDR' requires one element in initializer"

    Any ideas?

    • santaimpersonator / about 5 years ago / 1

      Hi there, it sounds like you are looking for technical assistance. Please use the link in the banner above, to get started with posting a topic in our forums. Our technical support team will do their best to assist you.

      Otherwise, it looks like you are trying to give the variable an array, which doesn't work with the defined data type (an unassigned integer).

  • Member #1507835 / about 5 years ago / 1

    Santa Claus, It sounds like Member #355246 is stating that he gets the LoRa functionality when the jumpers are open. Member #255246, is this correct?

  • Member #355246 / about 5 years ago / 1

    Can you explain what do the LoRaWAN jumper really do? I ask because they don't seem to make a difference in some preliminary tests (in fact, we lost the ability to send data to TTN when they're closed, but anecdotally, we can when the jumpers are cleared).

    • Member #731462 / about 5 years ago / 1

      It seems that the jumpers connect DIO1 and DIO2 to the microcontroller, which is needed for use with LoRaWAN libraries like LMIC. For point-to-point LoRa communication, like what's provided with RadioHead libraries, those are not needed. RadioHead is currently only configured for LoRa usage with this radio module (but not LoRaWAN, if I understand correctly). So either way, you will use LoRa, but connection to TTN for instance probably won't work without those jumpers closed.

    • santaimpersonator / about 5 years ago / 1

      The information for those jumpers is actually covered in the Hardware Overview section. Closing the jumpers changes the board from low power point-to-point functionality and configures the RFM95W Radio Module for LoRaWAN functionality.

If you've found an issue with this tutorial content, please send us your feedback!