Community
OpenEnergyMonitor

OpenEnergyMonitor Community

ATSAMD21G18 with RFM69 - help with compatibility

Tags: #<Tag:0x00007f6810998098> #<Tag:0x00007f68109cbf88> #<Tag:0x00007f68109cbe98>

Hi - just wondering if someone can help set the record straight / point me in the right direction here.
For background I have some oldish emonPi kit successfully working with the RFM12 devices, logging to an old model B pi with a gateway. A few years on and a move I’m expanding the the system to include (hopefully) many more sensors.

I have recently purchased a couple of Adafruit M0 feathers with RFM69 on board I also have a new pi (2) with the RFM69 module.

The pi with the RFM69 is working fine… it’s also picking up the existing RFM12.
The feathers can be setup in tx and rx and they work ok.

What I can’t do is compile any of the sketches for the Feather.
My assumption is I can’t compile as the emon sketches use the jeelib library and they aren’t compatible with the SAMD boards?
If I compile using the LowPowerLabs library then the Pi won’t decode anything despite it being same group / frequency as the radio is packetised differently between Jeelib and LowPowerLabs?

I understand Jeelib isn’t implementing all the features of RFM69 (e.g. encryption) but LowPowerLabs do…

I would really appreciate if someone could confirm this logic is correct?
I so (and second ask) what would be the best outcome? My assumption would be write additional support for SAMD with Jeelib?

Many thanks. John

Yes, that is the case.

Sorry, but I’m not familiar with the SAMD, so I can’t help directly. But if your Adafruit module is transmit-only, then I do have transmit-only code (extracted from JeeLib) that should be easy to port.

Why can’t you compile the sketches, i.e. what are the error messages?

Would there be any issues because the SAMD board uses an ARM microcontroller vice a 328?

Hi the sketches don’t compile easily - avr/sleep.h doesn’t appear to be compatible also avr/wdt.h - largely (I guess) because these are for avr not SAMD and RFM69.h / jeelib.h are making calls using these libs??

btw the devices will work in TX and RX - I proved they were working with a simple TX prog on one and RX on another - the code needs to be adapted for the lowpowerlab library rather than jeelib - sadly adafruit test code uses Jeelib which of course doesn’t compile! If you have some code you can share that would be great - I’m interested in trying to fi the problem so any pointers would help.

Take a look at:

It is transmit only and JeeLib-compatible.

You’ll need to look at the interface between the processor and the RFM module (SPI library) and the functions readReg( ), writeReg( ), select( ) and unselect( ). If you’re not interested in power saving, you’re probably not wanting to sleep. Other than that, I don’t think there’s much that you need to change.

Many thanks Robert I’ll take a look at this, thanks for the pointers on the library too…

Different set of compilation errors here. Hooking back to the adafruit library. Would this be an indicator to look at the SPI library?

/var/folders/84/st2hr6vs2vj9tv81zrd5_s600000gn/T/arduino_build_10092/core/core.a(main.cpp.o): In function `main':
../Arduino15/packages/adafruit/hardware/samd/1.0.13/cores/arduino/main.cpp:42: undefined reference to `setup'
../Arduino15/packages/adafruit/hardware/samd/1.0.13/cores/arduino/main.cpp:46: undefined reference to `loop'
collect2: error: ld returned 1 exit status
exit status 1
Error compiling for board Adafruit Feather M0 (Native USB Port).

I can’t tell from that what you real problem is.

I think you need to be starting from here. What were you doing (apart from using lowpowerlabs library) to get it to work? What I was suggesting by giving you rfm.ino was that all you needed to solve was the way to transfer data between the SAMD and the RFM, i.e. replace the SPI library calls with the equivalent for your SAMD, in those functions I mentioned. If you do that successfully, I’d expect it to work, once you’ve taken out the sleep bits.

May add to more confusion. This does compile ok (and work) with the caveat that the rfm69 library has the RFM69_OTA.* and RFM69_ATC.* files removed. Question is it possible to adapt. The code above doesn’t appear to call any other library’s so the format of the RF Packet is defined within this code alone? I have put known working code here https://github.com/cl0udb0y/AF_Feather_M0.git

Ah - sorry - missed this gem from you. I thought you were suggesting the .ino would compile ok for SAMD - so the error I posted was the result of just a straight compile of the sketch. It fails (seems to not be unique) with undefined references.

Am I correct in thinking your suggesting this ino will packetise the data correctly for jeelib (on the Pi) to decode. So what I need to do is take the working ino (which uses LowPowerLabs RFM69.h and .ccp) I have for the basic TX (on SAMD) and change the calls for these? based on this ino (which could be turned into a new fork RFM69.h and .cpp)
?? (sorry for all the q’s, as you would have guessed i’m not a programmer, but desperately trying to learn, and refresh my brain)

e.g. if I look at lowpowerlab RFM69.ccp and extract one function to ‘send’

void RFM69::send(uint8_t toAddress, const void* buffer, uint8_t bufferSize, bool requestACK)
{
  writeReg(REG_PACKETCONFIG2, (readReg(REG_PACKETCONFIG2) & 0xFB) | RF_PACKET2_RXRESTART); // avoid RX deadlocks
  uint32_t now = millis();
  while (!canSend() && millis() - now < RF69_CSMA_LIMIT_MS) receiveDone();
  sendFrame(toAddress, buffer, bufferSize, requestACK, false);
}

// to increase the chance of getting a packet across, call this function instead of send
// and it handles all the ACK requesting/retrying for you :)
// The only twist is that you have to manually listen to ACK requests on the other side and send back the ACKs
// The reason for the semi-automaton is that the lib is interrupt driven and
// requires user action to read the received data and decide what to do with it
// replies usually take only 5..8ms at [email protected]
bool RFM69::sendWithRetry(uint8_t toAddress, const void* buffer, uint8_t bufferSize, uint8_t retries, uint8_t retryWaitTime) {
  uint32_t sentTime;
  for (uint8_t i = 0; i <= retries; i++)
  {
    send(toAddress, buffer, bufferSize, true);
    sentTime = millis();
    while (millis() - sentTime < retryWaitTime)
    {
      if (ACKReceived(toAddress))
      {
        //Serial.print(" ~ms:"); Serial.print(millis() - sentTime);
        return true;
      }
    }
    //Serial.print(" RETRY#"); Serial.println(i + 1);
  }
  return false;
}

the equiv in the code you supplied would be

/    / transmit data via the RFM12
    void rfm_send(const byte *data, const byte size, const byte group, const byte node)
    {
      byte i = 0, next, txstate = 0;
      word crc = ~0;

      rfm_write(0x8228); // OPEN PA
      rfm_write(0x8238);

      digitalWrite(RFMSELPIN, LOW);
      SPI.transfer(0xb8); // tx register write command

      while (txstate < 13)
      {
        while (digitalRead(SDOPIN) == 0); // wait for SDO to go high
        switch (txstate)
        {
          case 0:
          case 1:
          case 2: next = 0xaa; txstate++; break;
          case 3: next = 0x2d; txstate++; break;
          case 4: next = group; txstate++; break;
          case 5: next = node; txstate++; break; // node ID
          case 6: next = size; txstate++; break;
          case 7: next = data[i++]; if (i == size) txstate++; break;
          case 8: next = (byte)crc; txstate++; break;
          case 9: next = (byte)(crc >> 8); txstate++; break;
          case 10:
          case 11:
          case 12: next = 0xaa; txstate++; break; // dummy bytes (if <3 CRC gets corrupted sometimes)
        }
        if ((txstate > 4) && (txstate < 9)) crc = _crc16_update(crc, next);
        SPI.transfer(next);
      }
      digitalWrite(RFMSELPIN, HIGH);

      rfm_write( 0x8208 ); // CLOSE PA
      rfm_write( 0x8200 ); // enter sleep
    }

No. What I’m doing is the core transmit loop that might be inside send(toAddress, buffer, bufferSize, true);
You didn’t catch the importance of the details in what I wrote earlier:

i.e. you need to replace the SPI calls with something that does the equivalent in your processor, which is send or receive a byte or a word on the serial lines that the RFM69 is connected to.

Thanks Robert, I appreciate your patience - I think I understand the concept, I’m not sure I understand how to change it, I’m struggling with how the libraries nest together and no doubt need to understand more of the basics.

When I look at the SPI library for SAMD - .h file below. What I don’t see (but it’s probably staring me in the face) is an obvious correlation with the functions e.g. writeReg() - it appears to me there is just a transfer data function. I see something similar in your code SPI.transfer(next); but also rfm_write( xxx ). I’m thinking rfm_write is also going to call the SPI function too but I need to find the other libraries which define that.

So in my SPI library where I have a function beginTransaction(SPISettings settings); I would need to change

From yours SPI.transfer(next); to SPI.beginTransaction(SPISettings settings); (assuming thats the correct function - but i doubt it is, hence not being able to correlate the readReg(), writeReg() etc.

/*
 * SPI Master library for Arduino Zero.
 * Copyright (c) 2015 Arduino LLC
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#ifndef _SPI_H_INCLUDED
#define _SPI_H_INCLUDED

#include <Arduino.h>

// SPI_HAS_TRANSACTION means SPI has
//   - beginTransaction()
//   - endTransaction()
//   - usingInterrupt()
//   - SPISetting(clock, bitOrder, dataMode)
#define SPI_HAS_TRANSACTION 1

#define SPI_MODE0 0x02
#define SPI_MODE1 0x00
#define SPI_MODE2 0x03
#define SPI_MODE3 0x01

#if defined(__SAMD21G18A__)
  // Even if not specified on the datasheet, the SAMD21G18A MCU
  // doesn't operate correctly with clock dividers lower than 4.
  // This allows a theoretical maximum SPI clock speed of 12Mhz
  #define SPI_MIN_CLOCK_DIVIDER 4
  // Other SAMD21xxxxx MCU may be affected as well
#else
  #define SPI_MIN_CLOCK_DIVIDER 2
#endif

class SPISettings {
  public:
  SPISettings(uint32_t clock, BitOrder bitOrder, uint8_t dataMode) {
    if (__builtin_constant_p(clock)) {
      init_AlwaysInline(clock, bitOrder, dataMode);
    } else {
      init_MightInline(clock, bitOrder, dataMode);
    }
  }

  // Default speed set to 4MHz, SPI mode set to MODE 0 and Bit order set to MSB first.
  SPISettings() { init_AlwaysInline(4000000, MSBFIRST, SPI_MODE0); }

  private:
  void init_MightInline(uint32_t clock, BitOrder bitOrder, uint8_t dataMode) {
    init_AlwaysInline(clock, bitOrder, dataMode);
  }

  void init_AlwaysInline(uint32_t clock, BitOrder bitOrder, uint8_t dataMode) __attribute__((__always_inline__)) {
    this->clockFreq = (clock >= (F_CPU / SPI_MIN_CLOCK_DIVIDER) ? F_CPU / SPI_MIN_CLOCK_DIVIDER : clock);

    this->bitOrder = (bitOrder == MSBFIRST ? MSB_FIRST : LSB_FIRST);

    switch (dataMode)
    {
      case SPI_MODE0:
        this->dataMode = SERCOM_SPI_MODE_0; break;
      case SPI_MODE1:
        this->dataMode = SERCOM_SPI_MODE_1; break;
      case SPI_MODE2:
        this->dataMode = SERCOM_SPI_MODE_2; break;
      case SPI_MODE3:
        this->dataMode = SERCOM_SPI_MODE_3; break;
      default:
        this->dataMode = SERCOM_SPI_MODE_0; break;
    }
  }

  uint32_t clockFreq;
  SercomSpiClockMode dataMode;
  SercomDataOrder bitOrder;

  friend class SPIClass;
};

class SPIClass {
  public:
  SPIClass(SERCOM *p_sercom, uint8_t uc_pinMISO, uint8_t uc_pinSCK, uint8_t uc_pinMOSI, SercomSpiTXPad, SercomRXPad);


  byte transfer(uint8_t data);
  uint16_t transfer16(uint16_t data);
  inline void transfer(void *buf, size_t count);

  // Transaction Functions
  void usingInterrupt(int interruptNumber);
  void beginTransaction(SPISettings settings);
  void endTransaction(void);

  // SPI Configuration methods
  void attachInterrupt();
  void detachInterrupt();

  void begin();
  void end();

  void setBitOrder(BitOrder order);
  void setDataMode(uint8_t uc_mode);
  void setClockDivider(uint8_t uc_div);

  private:
  void init();
  void config(SPISettings settings);

  SERCOM *_p_sercom;
  uint8_t _uc_pinMiso;
  uint8_t _uc_pinMosi;
  uint8_t _uc_pinSCK;

  SercomSpiTXPad _padTx;
  SercomRXPad _padRx;

  bool initialized;
  uint8_t interruptMode;
  char interruptSave;
  uint32_t interruptMask;
};

void SPIClass::transfer(void *buf, size_t count)
{
  // TODO: Optimize for faster block-transfer
  uint8_t *buffer = reinterpret_cast<uint8_t *>(buf);
  for (size_t i=0; i<count; i++)
    buffer[i] = transfer(buffer[i]);
}

#if SPI_INTERFACES_COUNT > 0
  extern SPIClass SPI;
#endif
#if SPI_INTERFACES_COUNT > 1
  extern SPIClass SPI1;
#endif
#if SPI_INTERFACES_COUNT > 2
  extern SPIClass SPI2;
#endif
#if SPI_INTERFACES_COUNT > 3
  extern SPIClass SPI3;
#endif
#if SPI_INTERFACES_COUNT > 4
  extern SPIClass SPI4;
#endif
#if SPI_INTERFACES_COUNT > 5
  extern SPIClass SPI5;
#endif

// For compatibility with sketches designed for AVR @ 16 MHz
// New programs should use SPI.beginTransaction to set the SPI clock
#if F_CPU == 48000000
  #define SPI_CLOCK_DIV2   6
  #define SPI_CLOCK_DIV4   12
  #define SPI_CLOCK_DIV8   24
  #define SPI_CLOCK_DIV16  48
  #define SPI_CLOCK_DIV32  96
  #define SPI_CLOCK_DIV64  192
  #define SPI_CLOCK_DIV128 255
#endif

#endif

OK, addressing the [quote=“cl0udb0y, post:13, topic:3297”]
I’m struggling with how the libraries nest together
[/quote]

question, and starting at the highest level:

I sent you a file containing two functions: one to set up the RFM module (called rfm_init( )) and one to give it the data and tell it to transmit it (rfm_send( )).
That’s the top level.

At the next level down, both send data to the RFM module by setting register values, and extract data by reading register values. The functions that do this are writeReg( ) and readReg( ). (There are also functions to select and de-select the module.) Those registers are inside the RFM module and their numbers (locations, addresses) and the values you put in there are defined in the RFM data sheet.

At the deepest level, you need a way to get the values onto the wires (MISO and MOSI) connecting the processor and the RFM module. That’s where the SPI library comes in.

Now what I cannot understand is why the SPI library doesn’t appear to want to do its stuff. As far as I can see, the interface (SPI.xxxxx) is common across the Arduino IDE to all devices that the IDE supports. Spelling it out, SPI.begin( ) should do the same for you as it does for me, as should all the other functions.

The only area where there might be a difference is in the settings for setting up the SPI interface in select( ):
SPI.setDataMode(SPI_MODE0);
SPI.setBitOrder(MSBFIRST);
SPI.setClockDivider(SPI_CLOCK_DIV4);

You have the correct pin for RFMSELPIN?
So as far as I can make out, if those are correct (or you copy the correct values from the sketch that did work for you), it should compile and work.

https://www.arduino.cc/en/reference/SPI could help.

Many thanks Robert - this does make sense to me, I’ll try and digest and implement and post back when I have success! (may take a few days)