RFM69 C++ driver library for STM32 (and other controllers)

I recently developed a protocol agnostic driver library for HopeRF’s RFM69 modules. Protocol agnostic means that you get full control over the module and the data packets that you want to send or receive. You can use this library for receiving packets from existing commercial devices like temperature sensors, or you can set up your own RF network using your own protocol.

The library is written in C++ and targets the STM32 microcontroller family. But it can be easily ported to other devices by changing very little code.

Features

  • Protocol agnostic
  • Support for RFM69W and RFM69HW devices (maximum output power +20 dBm)
  • Only SPI signals are mandatory
  • CSMA/CA algorithm can be enabled (channel free detection)
  • Easily portable to other controllers (default: STM32)
  • No register handling necessary
  • Read RSSI values after reception of a packet
  • OOK mode supported (default: FSK)
  • Continuous data mode supported (default: packet oriented)
  • Written in C++
  • Polling based library

You can find the MIT licensed library in my GitHub repository.

Enjoy! 😉

By André Heßling

I am an electronic engineer living in Voerde, Germany.

23 comments

  1. Hello Andres,
    I have a STM32 Nucleo F4 series. Could you please provide a schema how to connect the RFM69HW (433Mhz) on it?

    1. Hi mosix,

      the RFM69 module has a very simple pinout. I can show you an extract of one of my schematics.
      See here:

      RFM69 to microcontroller connection

      A few notes:
      The S1-* nets connect to SPI1 of the STM32 in my case. Just select a (free) SPI on your Nucleo board.
      S1-NSS needs an external or internal pull-up resistor. The same applies to RF-RES which is the reset line of the module.
      The other connections are not mandatory, their use depend on the application you want to develop (see: datasheet).

  2. Hi there, I’m new to STM32 and I was wondering how to get this code uploaded to my Maple Mini STM32 board, what IDE do you use?

    1. Hi Dan,

      I am using Eclipse and the GCCARM Embedded Toolchain for my projects, both very powerful and available for free.

      If you are new to the STM32 world, maybe consider using the CooCox IDE (with GCCARM). I haven’t used it myself but I heard that the IDE should be beginner-friendly.
      There is also a blog called “MCU on eclipse”, which could give you a good start if you decide for Eclipse.

      Due to lack of time I cannot give you a tutorial of how to write a simple program for the STM32 microcontrollers and use my library, sorry.
      If you are able to write simple programs (like blink an LED, do something with SPI), the next step in including my library should be quite easy.

      But I hope this helps nonetheless.

      Kind regards,
      André

  3. Excellent work. I am getting an issue though. I am trying to test it by sending and receiving through two different RFM69 modules attached to the same STM32 discovery board at different SPIs (SPI2 for sending, SPI3 for receiving). I used the code that you had provided in the GitHub repo’s readme for testing Tx and Rx. I am not receiving any packets though. I can confirm that the SPIs are working by reading register 0x10 and get a value of 36 from both modules.

    My code in main.cpp is like so:

    mstimer_init();
    RF_Begin();
    while(1){
    RF_SendTest();
    char* value = RF_RecvTest();
    }

    Function RF_Begin() looks like so:
    spiRF.setPrescaler(SPI_BaudRatePrescaler_2);
    spiRF.init();
    spiRFRx.setPrescaler(SPI_BaudRatePrescaler_2);
    spiRFRx.init();
    // setup RFM69 and optional reset
    GPIO_InitTypeDef RFResetPin;
    RFResetPin.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_1;
    RFResetPin.GPIO_Mode = GPIO_Mode_OUT;
    RFResetPin.GPIO_OType = GPIO_OType_PP;
    GPIO_Init(GPIOA,&RFResetPin);
    rfm69.setResetPin(GPIOA, GPIO_Pin_2);
    rfm69rx.setResetPin(GPIOA,GPIO_Pin_1);
    rfm69.reset();
    rfm69rx.reset();
    // init RF module and put it to sleep
    rfm69.init();
    rfm69.sleep();
    rfm69rx.init();
    rfm69rx.setMode(RFM69_MODE_RX);
    //rfm69rx.sleep();
    // set output power
    rfm69.setPowerDBm(10); // +10 dBm
    rfm69rx.setPowerDBm(10); // +10 dBm
    // enable CSMA/CA algorithm
    rfm69.setCSMA(false);
    rfm69rx.setCSMA(false);

    And the RFM objects are declared at global level like so:
    // setup SPI and RF
    SPI spiRF(SPI2);
    RFM69 rfm69(&spiRF, GPIOB, GPIO_Pin_11, false); // false = RFM69W, true = RFM69HW
    SPI spiRFRx(SPI3);
    RFM69 rfm69rx(&spiRFRx,GPIOB,GPIO_Pin_10,false);

    I debugged into the RX function and it seems the FIFO ready flag is not being set. Inside the _receive function, the second “if” statement that checks for this flag fails because reading 0x28 register returns 0x60 instead of 0x04. I tried to bypass it but it ends up picking up gibberish. What could be wrong?

    1. Hi Azmath,

      thank you for your comment.

      Everything seems reasonable so far.

      If this is really your main loop you are showing, try adding a delay (as a start >100 ms) because otherwise you would be sending permanently without pause. I don’t know if this could be a problem.

      When you connect two RF modules to one discovery board, I assume that the distance between them is quite low (a few centimeters maybe). You should therefore limit the output power to maybe 0 dBm or even lower. Otherwise you could overdrive the input stage which might result in your problem.
      Register value 0x60 in register 0x28 means that there was received something (FIFO not empty) but probably not a complete or invalid packet because the CRC check failed (no payload ready flag).

      You could also try lowering the SPI speed of both SPI interfaces. Even if the transfer essentially seems to work (confirmed by reading register 0x10), there may be problems writing or reading other registers.

      Since you are able to debug, please also check the return code of the function “waitForPacketSent” which is called in the send function (this is not done in my library at the moment). Maybe the sending part of your system goes into a timeout for whatever reason.

      If none of the above works, please also provide the following information:
      1. What STM32 are you using?
      2. Is this really a RFM69W or maybe a RFM69HW?
      3. What happens in RF_SendTest?
      4. What happens in RF_RecvTest?
      5. Try small payloads at the beginning (< 10 bytes). 6. Try enhancing the function "setCustomConfig" or even "writeRegister" so that the register contents are read back and verified. 7. Maybe connect a logic analyser and verify that the sending and receiving part is handled correctly. Things to look for: Clock too fast, chip select released/acquired too early/late. I hope this helped! Kind regards, André

      1. Thanks for replying! It was the distance being too short I guess. I used a different RF transmitter (from a different device) and it receives fine! Thanks a lot!

  4. Hi Andres,

    I recently got a pair to RMF69HCW radios. The datasheet states the its capable of up to 300kbps but it seams that the highest data transfer rate achievable is only about 16kbps. I’m trying to find a site that pushed this radio to is limits and details the settings used to achieve it. I’m trying to transfer a large amount of data as fast as possible between 2 nodes in a network. I’ve searched high and low to try to figure this one out.

    Any help would be greatly appreciated.

    Regards,

    Sidney

    1. Hi Sidney,

      I haven’t used these modules for speeds greater than 57,6 kbps myself, but this baudrate works quite well in my setup.
      These are the settings I use in this constellation (just copied from one of my projects):


      static const uint8_t rfm69_ismmod_config[][2] =
      {
      {0x02, 0x00}, // RegDataModul: Packet mode, FSK, no shaping
      {0x03, 0x02}, // RegBitrateMsb: 57,6 kbps
      {0x04, 0x2C}, // RegBitrateLsb
      {0x05, 0x03}, // RegFdevMsb: 50 kHz
      {0x06, 0x33}, // RegFdevLsb
      {0x19, 0x42}, // RegRxBw: 125 kHz
      {0x2C, 0x00}, //RegPreambleMsb
      {0x2D, 0x03}, //RegPreambleLsb, 3 bytes preamble
      {0x2E, 0x88}, //enable Sync.Word, 1+1=2 bytes
      {0x2F, 0x41}, //SyncWord = 48h
      {0x30, 0x48}, //SyncWord = 48h
      {0x37, 0xD0}, //RegPacketConfig1, CRC calculation, variable packet length, whitening
      {0x38, 0x40}, //RegPayloadLength, max 64 bytes payload
      {0x3C, 0x8F}, //RegFiFoThresh, TxStart on FifoNotEmpty, 15 bytes (default)
      {0x58, 0x2D}, //RegTestLna, increase sensitivity with LNA (Note: consumption also increase!)
      {0x01, 0x04} //Enter standby mode
      };

      There are some things to consider when trying to push RF modules to its limits:

      • Increasing the bitrate leads to lower ranges. You have to compensate by increasing the output power. At the same time, don’t expect successful transmissions when both modules are too close together and the output power is too high.
      • You have to increase the receiver bandwidth according to the bitrate. This leads to worse signal-to-noise ratios which can be a real problem when there are many transmitters in the area.
      • The 868 MHz band should be better than 433 MHz because of fewer transmitters (in most areas).
      • Always enable data whitening (this is done in my example) for DC-free transmissions. This gives the receiver the chance to keep it’s synchronisation to the bitstream over long sequences of ones and zeroes.
      • Pay attention to the constraint: F_dev + BR/2 < 500 kHz (cf. datasheet p. 20).
      • The FSK demodulator works best with modulation indexes between 0.5 and 10 (cf. p. 29).
      • The bitrate must be smaller than two times the receiver bandwidth (cf. p. 26).
      • You could try to use modulation shaping (in this case a gaussian filter) to reduce the bandwidth usage (register 0x2, ModulationShaping).
      • Try to keep the packets as small as possible to reduce the chance that packets get corrupted due to bit errors.
      • Don’t forget that you are probably not the only transmitter in the frequency band. Implementing a “listen-before-talk” algorithm (CSMA/CA) might work wonders because it makes no sense to transmit on a channel if there is another transmitter currently active. The packet will probably be corrupted on the receiving side.

      I hope this helps in achieving your goal. Good luck!

      Kind regards,
      André

  5. Loointi the registersking for info on how to set frequency, Im using an arduino – would it be possible to use your library in conjunction with the arduino IDE to set frequency or can I set it with a calculation and a few hex pokes

    1. Hi Mike,

      in theory this library should be compatible with Arduino because it is standard C++ code. But I have never done anything with Arduino, so I cannot give you any support on this subject.

      Regarding the setting of the frequency… This is quite easy, look at the source code of rfm69.cpp in the function RFM69::setFrequency. There are 3 registers (0x07-0x09) which are responsible for setting the carrier frequency.
      So even when you do not use this library, you can easily adopt the function to your own needs or to another library if this particular function is missing.

      I hope this helps!

      Kind regards,
      André

  6. Hi André,

    I’m working with nRF52 and I’m using Nordic SDK and plan to use your library to make my device dual band (BLE + 433MHz radio). I believe I should use the SPI library that is defined by Nordic SDK, however I don’t know how to merge your library with Nordic DK. Do you have any insight in this?

    Thanks,
    Samuel

    1. Hi Samuel,

      it actually does not matter which SPI library you use as long as you present this library a common interface.
      If you look at the constructor of my RFM69 class, you notice that it depends on a “SPIBase” class. This is an abstract class (“interface class”) which is defined in spibase.hpp. You have to write a C++ class which implements these abstract functions and matches your hardware.
      The hardware-dependent code is not really scope of this library. I merely included some example implementations for the STM32L1 and STM32F0 devices in my library, copied from my other projects.

      I haven’t done anything with the nRF52 yet. Probably the easiest way to get this library to work with the nRF52 is to write a wrapper class which inherits from the SPIBase class. In this new wrapper class, you call the SPI functions from the Nordic SDK, which hopefully should exist.

      I hope this helps!

      Kind regards,
      André

  7. Hi André,

    I’m trying to get a RFM69 and a NRF905 to talk to each other. I’ve been trying since a week without any success. Each RF module is wired up to it’s own STM32F401. I’m using your RFM69-STM32-master library as a starting point. For the nrf905 I wrote my own class. Although I’ve been reading the 2 datasheets back and forth I can’t come up with a set of configurations.
    So I wonder has anybody a configuration for the RFM69 which makes it understand what a NRF905 is sending (and vice versa). The configuration for the NRF905 is relatively simple which means there aren’t too many choices.
    My setup is this (config reg 0 … config reg 9)
    {0x75,0x0e,0x33,0x18,0x18,0xCC,0xCC,0xCC,0xCC,0x18}
    which means:
    868.2 MHz
    3 byte address for RX and TX
    24 bytes payload for RX and TX
    address 0xCC 0xCC 0xCC
    CRC off

    Now I’m looking for a configuration which puts the RFM69 in a compatible mode. (Or any other compatible setups.)

    BTW: André, you wrote in one of your first posts:
    “S1-NSS needs an external or internal pull-up resistor. The same applies to RF-RES which is the reset line of the module.”
    I don’t understand this. Both are inputs to the RFM69 and are driven from DigitalOut pins of the STM32. So I don’t see what pull-up resistors should accomplish. ??

    Any help is greatly appreciated!

    Ronald

    1. Hi Ronald,

      sorry, I cannot help you with the problem regarding the NRF905 because I haven’t done anything with this module.
      I can only comment to the pull-up resistors:
      Technically you are right. The lines can be actively driven via a push-pull output stage of the controller and you should be fine. But in many cases, reset lines and chip select lines are pulled up externally so that the device doesn’t do anything “stupid” during the power-on phase of the circuit when the main controller is not yet ready. This becomes essentially important when there are more devices connected to the same SPI bus. Often there are also weak internal pull-ups integrated in the IC so that the external ones become redundant.

      Kidn regards,
      André

      1. Thanks for the quick answer, André !

        Do you happen to know an other place/forum where they deal with both RFM69 and NRF905 ?

        Cheers,
        Ronald

  8. Hi André.. I´m Eugenio from Argentina.. thanx for your work.. I´m trying to set the fequency in 425Mhz and read RSSI values to compare with a value saved in a variable in an infinity loop, if the RSSI value readed is higher than the value saved, then, a D output must be set to High .. I used RFM69CW-433S2 join to STM32F030… how can I do that? I can´t find any example to be modified.. thanx again!

Leave a Reply to Mitek Cancel reply

Your email address will not be published. Required fields are marked *