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! 😉

18 Replies to “RFM69 C++ driver library for STM32 (and other controllers)”

  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é

Leave a Reply

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