Help with FlexyPin RFM69CW Breakout Shield

I am working on an Arduino Uno Shield for testing RFM69CW radios with FlexyPins from Solder Party (FlexyPin | Solder Party). FlexyPins are designed to allow easy insertion and removal of castellated modules like the RFM69CW.

The FlexyPins are working great! My Uno shield is not. Any suggestions are most welcome. I am attaching the schematic and board below. The idea is to follow the example of the “Arduino Uno Connections” section of the documentation (RFM12B & RFM69CW Wireless Transceiver Modules — OpenEnergyMonitor 0.0.1 documentation). As well as the original emonTx Shield. In other words, power the transceiver with the +3v3 pin of the Arduino. Level-shift the MOSI, Clock, and Select connections using 4k7+10k voltage divers.

I am testing with the usual RF12 Demo sketch from JeeLib. I am powering everything with USB plugged into a Mac running Arduino IDE.

What I find is that the radio will not even initialize. I see “[RF12demo.14]” in the serial monitor, but no subsequent output.

I tried various things. Checked that I had good GND connection. Checked my soldering. Measured the output of the voltage dividers. Checked the continuity of my traces.

I also tried powering the transciever with the +5V and the VIN pin running through a couple different 3V3 voltage regulators I have around. This didn’t work, but while I was doing it I made a breadboard error and bypassed the regulator, powering the transceiver with the +5V pin. I expected this would fry the module, but instead it started working! It initialized and started to output packets from the various sensors in my home.

I had three copies of my PCB from OSHPark to try, so I soldered up a second unit, but this time I bypassed the voltage dividers. (I replaced the 4k7 resistors with jumper wires.) This unit works when powered from the 3V3 pin. Without the level-shifters the MOSI, Clock, and Select are exposed to the 5V logic of the Uno, but it works and hasn’t (yet) fried a radio.

I took the third PCB and soldered it up with voltage dividers, but this time I used 470 and 100 ohm resistors, thinking that I could supply the same voltage but more current to the transceiver. This unit does not work. It won’t initialize.

To summarize:

  • Powering from +3V3 pin and using level-shifting dividers: doesn’t work, but seems like it should.
  • Powering from +5V pin: works, but seems like it shouldn’t.
  • Powering from +3V3 pin and bypassing level-shifting dividers: works, but seems like it shouldn’t.

I appreciate any thoughts and suggestions. I am stuck.


RFM69_FlexyPin_Shield_0-2C.sch (334.3 KB)
RFM69_FlexyPin_Shield_0-2C.brd (79.0 KB)

With every respect, you seem to have done so many things wrong that it’s difficult to know where to start.

There’s nothing wrong there. (You did mean “dividers”?)

I don’t use a Mac, but that too should be OK.

Where does that come from - is it in the sketch you’re using? Is it before the call to rf12_initialize( ... )
If the sketch stops there, a likely cause is rf12_initialize is failing to return. To prove it, put a Serial.print("Init done") (or similar) statement immediately after - if this doesn’t appear, you’ll know for certain where the first problem is.
One likely cause is that JeeLib was written for the RFM12B. If you’re using an RF69CW, you must tell it so, BEFORE you #include <JeeLib.h> like this:

#define RF69_COMPAT 1
#include <JeeLib.h>

Do you have this in your sketch?

I strongly recommend you read the data sheets for the modules. While applying 5 V is permissible for the RFM12B, it’s strongly not recommended in normal operation. For the RFM69CW, the situation is a lot clearer: Under “Absolute Maximum Ratings” it lists “Supply Voltage Min: -0.5 V, Max: 3.9 V”. It might surprise you to learn they mean that. Putting more than 3.9 V on any pin is liable to cause permanent damage, as the data sheet warns: “Stresses above the values listed below may cause permanent device failure. Exposure to absolute maximum ratings for extended periods may affect device reliability.”

Just read again what I wrote above. You are likely to have caused permanent damage to the module.

What in heaven’s name made you think this might be a good idea? Do you not believe that Glyn knows what he’s doing when he designed the Shield? Do you not have the faintest idea of the input current that a digital input on this sort of device requires? When I learned about this sort of thing, the Internet didn’t exist (in the form it does today). All you need to do now to find out is a few keystrokes and you’ll be able to have a reasonable indication, if not an exact value.

Do you mean powering everything at 3.3 V, or the Arduino at 5 V and the RFM69CW at 3.3 V. ?
I think it should work, after all, you’re following a proven design - so your next move should have been to look for something else that might be wrong.

You need to ask yourself: For how long will it continue? Bear in mind the warning on the data sheet.

My best first suggestion is scrap those 3 modules, they’re likely to be seriously stressed and liable to fail at some time in the near future, even if they appear to be working now.
The second is while you’re waiting, read and understand the documentation for the parts you’re using and for the software you’re using.
And the third is don’t “Poke an’ 'ope.” Follow engineering and scientific knowledge and employ a purely logic-driven approach.

If you want a more accessible library to see exactly how the RFM12B and RFM69CW are initialised and driven for transmit only, look at my rfmTxLib: (61.3 KB)

Lots of opportunities to learn something then, I suppose!

Yes. Typo.

Yes it comes from the sketch, and yes it happens before the call to rf12_initialize. The sketch is the RFM12Pi V2 with RFM69CW Firmware (below).

//RFM12Pi V2 with RFM69CW Firmware
//Based on JCW RF12 Demo:
//Edited for RFM12Pi and emonPi operation June 2014 by Glyn Hudson and Trystan Lea

// V1.4 Jan 17
// Compile with platformIO and relase with GH releases using TravisCI

// V1.3 July 15 - add RF trace mode debug and fix node ID isse, merge pb66 pull requests

// Version V1.0 - Nov 2014
// * 433Mhz default frequency, 15 default node ID

// V0.9 June 2014
// * 210 default network group
// * activity LED to light on startup and each time packet is received


/// @dir RF12demo
/// Configure some values in EEPROM for easy config of the RF12 later on.
// 2009-05-06 <[email protected]>

// this version adds flash memory support, 2009-11-19
// Adding frequency features, author JohnO, 2013-09-05
// Major EEPROM format change, refactoring, and cleanup for v12, 2014-02-13

#define RF69_COMPAT 1 // define this to use the RF69 driver i.s.o. RF12

#include <JeeLib.h>
#include <util/crc16.h>
#include <avr/eeprom.h>
#include <avr/pgmspace.h>
#include <util/parity.h>

#define MAJOR_VERSION RF12_EEPROM_VERSION // bump when EEPROM layout changes
#define MINOR_VERSION 2                   // bump on other non-trivial changes
#define VERSION "[RF12demo.14]"           // keep in sync with the above

#define SERIAL_BAUD 38400   // max baud for 8Mhz RFM12Pi
#define DATAFLASH   0       // set to 0 for non-JeeLinks, else 4/8/16 (Mbit)
#define LED_PIN     9       // activity LED, comment out to disable

/// Save a few bytes of flash by declaring const if used more than once.
const char INVALID1[] PROGMEM = "\rInvalid\n";
const char INITFAIL[] PROGMEM = "config save failed\n";

  byte trace_mode = 0;

#define _receivePin 8
static int _bitDelay;
static char _receive_buffer;
static byte _receive_buffer_index;

// TODO: replace with code from the std avr libc library:
void whackDelay (word delay) {
  byte tmp=0;

  asm volatile("sbiw      %0, 0x01 \n\t"
  "ldi %1, 0xFF \n\t"
  "cpi %A0, 0xFF \n\t"
  "cpc %B0, %1 \n\t"
  "brne .-10 \n\t"
  "+r" (delay), "+a" (tmp)
  "0" (delay)

ISR (PCINT0_vect) {
  char i, d = 0;
  if (digitalRead(_receivePin))       // PA2 = Jeenode DIO2
    return;             // not ready!
  whackDelay(_bitDelay - 8);
  for (i=0; i<8; i++) {
    whackDelay(_bitDelay*2 - 6);    // digitalread takes some time
    if (digitalRead(_receivePin)) // PA2 = Jeenode DIO2
      d |= (1 << i);
  if (_receive_buffer_index)
  _receive_buffer = d;                // save data
  _receive_buffer_index = 1;  // got a byte

static byte inChar () {
  byte d;
  if (! _receive_buffer_index)
    return -1;
  d = _receive_buffer; // grab first and only byte
  _receive_buffer_index = 0;
  return d;

static unsigned long now () {
  // FIXME 49-day overflow
  return millis() / 1000;

static void activityLed (byte on) {
#ifdef LED_PIN
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, on);

static void printOneChar (char c) {

/// @details
/// For the EEPROM layout, see
/// Useful url:

// RF12 configuration area
typedef struct {
  byte nodeId;            // used by rf12_config, offset 0
  byte group;             // used by rf12_config, offset 1
  byte format;            // used by rf12_config, offset 2
byte hex_output   :
  2;   // 0 = dec, 1 = hex, 2 = hex+ascii
byte collect_mode :
  1;   // 0 = ack, 1 = don't send acks
byte quiet_mode   :
  1;   // 0 = show all, 1 = show only valid packets
byte spare_flags  :
  word frequency_offset;  // used by rf12_config, offset 4
  byte pad[RF12_EEPROM_SIZE-8];
  word crc;

static RF12Config config;
static char cmd;
static word value;
static byte stack[RF12_MAXDATA+4], top, sendLen, dest;
static byte testCounter;

static void showNibble (byte nibble) {
  char c = '0' + (nibble & 0x0F);
  if (c > '9')
    c += 7;

static void showByte (byte value) {
  if (config.hex_output) {
    showNibble(value >> 4);
    Serial.print((word) value);

static void showString (PGM_P s) {
  for (;;) {
    char c = pgm_read_byte(s++);
    if (c == 0)
    if (c == '\n')

static word calcCrc (const void* ptr, byte len) {
  word crc = ~0;
  for (byte i = 0; i < len; ++i)
    crc = _crc16_update(crc, ((const byte*) ptr)[i]);
  return crc;

static void loadConfig () {
  // eeprom_read_block(&config, RF12_EEPROM_ADDR, sizeof config);
  // this uses 166 bytes less flash than eeprom_read_block(), no idea why
  for (byte i = 0; i < sizeof config; ++ i)
    ((byte*) &config)[i] = eeprom_read_byte(RF12_EEPROM_ADDR + i);

static void saveConfig () {
  config.format = MAJOR_VERSION;
  config.crc = calcCrc(&config, sizeof config - 2);
  // eeprom_write_block(&config, RF12_EEPROM_ADDR, sizeof config);
  // this uses 170 bytes less flash than eeprom_write_block(), no idea why
  eeprom_write_byte(RF12_EEPROM_ADDR, ((byte*) &config)[0]);
  for (byte i = 0; i < sizeof config; ++ i)
    eeprom_write_byte(RF12_EEPROM_ADDR + i, ((byte*) &config)[i]);

  if (rf12_configSilent())

static byte bandToFreq (byte band) {
  return band == 4 ? RF12_433MHZ : band == 8 ? RF12_868MHZ : band == 9 ? RF12_915MHZ : 0;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// OOK transmit code

#if RF69_COMPAT // not implemented in RF69 compatibility mode
static void fs20cmd(word house, byte addr, byte cmd) {
static void kakuSend(char addr, byte device, byte on) {

// Turn transmitter on or off, but also apply asymmetric correction and account
// for 25 us SPI overhead to end up with the proper on-the-air pulse widths.
// With thanks to JGJ Veken for his help in getting these values right.
static void ookPulse(int on, int off) {
  delayMicroseconds(on + 150);
  delayMicroseconds(off - 200);

static void fs20sendBits(word data, byte bits) {
  if (bits == 8) {
    data = (data << 1) | parity_even_bit(data);
  for (word mask = bit(bits-1); mask != 0; mask >>= 1) {
    int width = data & mask ? 600 : 400;
    ookPulse(width, width);

static void fs20cmd(word house, byte addr, byte cmd) {
  byte sum = 6 + (house >> 8) + house + addr + cmd;
  for (byte i = 0; i < 3; ++i) {
    fs20sendBits(1, 13);
    fs20sendBits(house >> 8, 8);
    fs20sendBits(house, 8);
    fs20sendBits(addr, 8);
    fs20sendBits(cmd, 8);
    fs20sendBits(sum, 8);
    fs20sendBits(0, 1);

static void kakuSend(char addr, byte device, byte on) {
  int cmd = 0x600 | ((device - 1) << 4) | ((addr - 1) & 0xF);
  if (on)
    cmd |= 0x800;
  for (byte i = 0; i < 4; ++i) {
    for (byte bit = 0; bit < 12; ++bit) {
      ookPulse(375, 1125);
      int on = bitRead(cmd, bit) ? 1125 : 375;
      ookPulse(on, 1500 - on);
    ookPulse(375, 375);
    delay(11); // approximate


// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// DataFlash code

#include "dataflash.h"
#else // DATAFLASH

#define df_present() 0
#define df_initialize()
#define df_dump()
#define df_replay(x,y)
#define df_erase(x)
#define df_wipe()
#define df_append(x,y)


// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

const char helpText1[] PROGMEM =
"Available commands:\n"
"  <nn> i     - set node ID (standard node ids are 1..30)\n"
"  <n> b      - set MHz band (4 = 433, 8 = 868, 9 = 915)\n"
"  <nnnn> o   - change frequency offset within the band (default 1600)\n"
"               96..3903 is the range supported by the RFM12B\n"
"  <nnn> g    - set network group (RFM12 only allows 212, 0 = any)\n"
"  <n> c      - set collect mode (advanced, normally 0)\n"
"  t          - broadcast max-size test packet, request ack\n"
"  ...,<nn> a - send data packet to node <nn>, request ack\n"
"  ...,<nn> s - send data packet to node <nn>, no ack\n"
"  <n> q      - set quiet mode (1 = don't report bad packets)\n"
"  <n> x      - set reporting format (0: decimal, 1: hex, 2: hex+ascii)\n"
"  <nnn> y    - enable signal strength trace mode, default:0 (disabled)\n"
"               sample interval <nnn> secs/100 (0.01s-2.5s) eg 10y=0.1s\n"
"  123 z      - total power down, needs a reset to start up again\n"
"Remote control commands:\n"
"  <hchi>,<hclo>,<addr>,<cmd> f     - FS20 command (868 MHz)\n"
"  <addr>,<dev>,<on> k              - KAKU command (433 MHz)\n"

const char helpText2[] PROGMEM =
"Flash storage (JeeLink only):\n"
"    d                                  - dump all log markers\n"
"    <sh>,<sl>,<t3>,<t2>,<t1>,<t0> r    - replay from specified marker\n"
"    123,<bhi>,<blo> e                  - erase 4K block\n"
"    12,34 w                            - wipe entire flash memory\n"

static void showHelp () {
  if (df_present())
  showString(PSTR("Current configuration:\n"));

static void handleInput (char c) {
  if ('0' <= c && c <= '9') {
    value = 10 * value + c - '0';

  if (c == ',') {
    if (top < sizeof stack)
      stack[top++] = value; // truncated to 8 bits
    value = 0;

  if ('a' <= c && c <= 'z') {
    showString(PSTR("> "));
    for (byte i = 0; i < top; ++i) {
      Serial.print((word) stack[i]);

  // keeping this out of the switch reduces code size (smaller branch table)
  if (c == '>') {
    // special case, send to specific band and group, and don't echo cmd
    // input: band,group,node,header,data...
    stack[top++] = value;
    // TODO: frequency offset is taken from global config, is that ok?
    rf12_initialize(stack[2], bandToFreq(stack[0]), stack[1],
    rf12_sendNow(stack[3], stack + 4, top - 4);
  else if (c > ' ') {
    switch (c) {

    case 'i': // set node id
      if ((value > 0) && (value < 31)) {
        config.nodeId = (config.nodeId & 0xE0) + (value & 0x1F);

    case 'b': // set band: 4 = 433, 8 = 868, 9 = 915
      value = bandToFreq(value);
      if (value) {
        config.nodeId = (value << 6) + (config.nodeId & 0x3F);
        config.frequency_offset = 1600;

    case 'o':
      { // Increment frequency within band
        // Stay within your country's ISM spectrum management guidelines, i.e.
        // allowable frequencies and their use when selecting operating frequencies.
        if ((value > 95) && (value < 3904)) { // supported by RFM12B
          config.frequency_offset = value;

    case 'g': // set network group = value;

    case 'c': // set collect mode (off = 0, on = 1)
      config.collect_mode = value;

    case 't': // broadcast a maximum size test packet, request an ack
      cmd = 'a';
      sendLen = RF12_MAXDATA;
      dest = 0;
      for (byte i = 0; i < RF12_MAXDATA; ++i)
        stack[i] = i + testCounter;
      showString(PSTR("test "));
      showByte(testCounter); // first byte in test buffer

    case 'a': // send packet to node ID N, request an ack
    case 's': // send packet to node ID N, no ack
      cmd = c;
      sendLen = top;
      dest = value;

    case 'f': // send FS20 command: <hchi>,<hclo>,<addr>,<cmd>f
      rf12_initialize(0, RF12_868MHZ, 0);
      fs20cmd(256 * stack[0] + stack[1], stack[2], value);

    case 'k': // send KAKU command: <addr>,<dev>,<on>k
      rf12_initialize(0, RF12_433MHZ, 0);
      kakuSend(stack[0], stack[1], value);

    case 'z': // put the ATmega in ultra-low power mode (reset needed)
      if (value == 123) {
        showString(PSTR(" Zzz...\n"));

    case 'q': // turn quiet mode on or off (don't report bad packets)
      config.quiet_mode = value;

    case 'x': // set reporting mode to decimal (0), hex (1), hex+ascii (2)
      config.hex_output = value;

    case 'v': //display the interpreter version and configuration

    case 'l': // turn activity LED on or off

    case 'd': // dump all log markers
      if (df_present())

    case 'r': // replay from specified seqnum/time marker
      if (df_present()) {
        word seqnum = (stack[0] << 8) | stack[1];
        long asof = (stack[2] << 8) | stack[3];
        asof = (asof << 16) | ((stack[4] << 8) | value);
        df_replay(seqnum, asof);

    case 'e': // erase specified 4Kb block
      if (df_present() && stack[0] == 123) {
        word block = (stack[1] << 8) | value;

    case 'w': // wipe entire flash memory
      if (df_present() && stack[0] == 12 && value == 34) {
    case 'y': // turn signal strength trace mode on or off (rfm69 only)
      trace_mode = value;


  value = top = 0;

static void displayASCII (const byte* data, byte count) {
  for (byte i = 0; i < count; ++i) {
    printOneChar(' ');
    char c = (char) data[i];
    printOneChar(c < ' ' || c > '~' ? '.' : c);

static void displayVersion () {

void setup () {
  delay(100); // shortened for now. Handy with JeeNode Micro V1 where ISP
  // interaction can be upset by RF12B startup process.


  if (rf12_configSilent()) {
  else {
    memset(&config, 0, sizeof config);
   config.nodeId = 0x4F;                           // RFM12Pi - 433 MHz, node 15 = 0xD2;                           // RFM12Pi - default group 210
    config.frequency_offset = 1600;
    config.quiet_mode = true;   // Default flags, quiet on


  delay(1000);        //rfm12pi keep LED for for 1s to show it's working at startup

void loop () {
  if (Serial.available())

  if (trace_mode == 0) {
  if (rf12_recvDone()) {
    byte n = rf12_len;
    if (rf12_crc == 0)
    else {
      if (config.quiet_mode)
      showString(PSTR(" ?"));
      if (n > 20) // print at most 20 bytes if crc is wrong
        n = 20;
    if (config.hex_output)
    if ( == 0) {
      showString(PSTR(" G"));
    printOneChar(' ');
    showByte(rf12_hdr & 0x1F);
    for (byte i = 0; i < n; ++i) {
      if (!config.hex_output)
        printOneChar(' ');
    // display RSSI value after packet data
    showString(PSTR(" ("));
    if (config.hex_output)
    showString(PSTR(") "));

    if (config.hex_output > 1) { // also print a line as ascii
      showString(PSTR("ASC "));
      if ( == 0) {
        showString(PSTR(" II "));
      printOneChar(rf12_hdr & RF12_HDR_DST ? '>' : '<');
      printOneChar('@' + (rf12_hdr & RF12_HDR_MASK));
      displayASCII((const byte*) rf12_data, n);

    if (rf12_crc == 0) {

      if (df_present())
        df_append((const char*) rf12_data - 2, rf12_len + 2);

      if (RF12_WANTS_ACK && (config.collect_mode) == 0) {
        showString(PSTR(" -> ack\n"));
        rf12_sendStart(RF12_ACK_REPLY, 0, 0);

  if (cmd && rf12_canSend()) {

    showString(PSTR(" -> "));
    Serial.print((word) sendLen);
    showString(PSTR(" b\n"));
    byte header = cmd == 'a' ? RF12_HDR_ACK : 0;
    if (dest)
      header |= RF12_HDR_DST | dest;
    rf12_sendStart(header, stack, sendLen);
    cmd = 0;

  } else {
      byte y = (RF69::rssi>>1);
      for (byte i = 0; i < (100-y); ++i) {
      for (byte i = 0; i < (y); ++i) {
          printOneChar(' ');



I do. (The RFM12Pi V2 with RFM69CW Firmware I am using does.)

I can’t claim to have read every page, but I spent a good amount of time reading about the voltage ratings and the level-shifting necessary to use the modules with a 5V-logic device such as an Uno. There are a lot of warnings around the internet about this, and I had every intent of heeding them.

That may be true. If so, I am out about $1.50 US.

I did a very careful study of Glyn’s designs for the Sheild, starting from the earliest v1 with through-hole resistors. I also read as many blog posts from Jean-Claude Wippler as I could find from the era when he was developing JeeNodes, especially his first RFM12B Board. I believe Glyn and JCW know what they’re doing, which is why my design tries to stay true to theirs.

When my board didn’t work I started searching on the internet for reasons it might not. I ran into a lot of forum posts that claimed the built-in 3V3 voltage regulator on the Arduino Uno does not supply enough current to the RFM transceiver. That didn’t make sense to me, because the emonTx Shield works, and it uses that 3V3 voltage source, but it was worth exploring. So I moved to a breadboard and connected VDD of the RFM69CW to a 3V3 voltage regulator, which in turn I connected to +5V. This I saw RCW do with his JeeNodes, so it seemed a valid thing to try. I tried a L4931-3.3 and then a LD1117-3.3. This didn’t help.

While I was doing this I accidentally connected VDD to +5V because of a breadboard mistake. That’s when I noticed the radio initialize and start reporting packets. That was unexpected. What I expected based on the datasheets and thoughtful designs of experts was blue smoke.

I then tried the lower-value voltage divider as an attempt to see if that made a difference. My thought was that if my setup was already low on available mA for the module, further restricting it with larger-value resistors could be contributing to the problem. Honestly this was a bit of a poke and hope. And it didn’t pan out. But again, I’m doing this to learn.

I mean powering the Arduino at 5V and the RFM69CW at 3.3V. And I agree. It is a proven design. It should work. I am stuck.

The only thing that is all that different in my design is that the FlexyPins are the “last mile” of every connection. Could that be the difference? Could they cause the behavior I am seeing?

When you’ve ruled out all possible explanations, it’s time to consider the impossible ones.

Yes, that could be a problem - but you should be able to feel if there’s enough contact pressure on each one (each that matters - many are unused). If you use my library and it’s a RFM69CW, only GND, 3.3 V, NSS, SCK, MOSI & MISO are used.

Is there solder on either the module or the pins where they make contact? If so, that’s bad - it should be gold on gold.

What do you have in the way of test equipment and instrumentation?

NSS (selects the module) should be high except when transferring data, SCK is the data clock pulses and MOSI is the data in from the Arduino, MISO is data out received by the module. Even if you’ve only got a multimeter, you might be able to see a change on SCK and MOSI as it tries to initialise the module.

How does that work? Most SPI slaves require that to go low before they’ll look at the bus signals. Without that they typically assume the transaction is for some other device on the bus and so ignore it.

You’re right as usual - I was looking at the start of the init( ) function, not at the actual function that does the writing.
Edited above.

It does seem to be making good contact. And this is a new radio - no solder to get in the way. Here’s an attempt at a photo.

I believe that is true of the older “JeeLib Classic” library I’m using as well. That’s been my experience. (I’ve read a lot about your work on continuous monitoring for the emonPi and the rfmTxLib library with native rfm69 packet format support, but haven’t had the opportunity to try it yet. (Since my house is full of OEM devices running the classic library, the classic sketch gives me immediate feedback when it works - lots of packets are rolling in all the time.))

I have a Fluke 83 III multimeter, a Rigol DS1052E oscilloscope, and LowPowerLab CurrentRanger.

I’ll try to take a look at these with the oscilloscope.

But first I will shovel a lot of snow.

You need to use your 'scope to look first at NSS to ensure the module is selected, then at MOSI and MISO to see the data sent to the module and its response.

The problem with the ‘Classic’ library, as you’ve no doubt read, is it is almost completely interrupt-driven - because the RFM12B had only a 1-character buffer. The RFM69CW has a sensible buffer at around 66 bytes and so the host can leave all the processing to the RFM69CW and then extract the message (in receive mode) at its leisure (and much the same when transmitting). Interrupts are not required when you use the RFM69CW’s internal buffer - you either poll it and ask if it’s got a complete message, or feed it the message and tell it to get on with sending it.

The RFM69CW reference design in section 7.3 of the V1.1 data sheet has a 100nF decoupling capacitor to ground on the RFM69CW Vcc (+3.3V) pin.

The reference schematic has a note:

‘Note that all schematics shown in this section are full schematics, listing ALL required components, including decoupling capacitors.’

I can’t see an obvious capacitor on the RFM69CW Vcc pin in the photo of the board?

I’m not sure what difference a lack of the decoupling capacitor might make …

All the following measurements were done while using the JeeLib Classic RFM69Pi firmware I pasted in the thread above. Also bear in mind I have very little experience with oscilloscopes.

First I’m going to show you what I see in my scope when I’m using the PCB that bypasses the level-shifting dividers. (Which, as discussed before, somehow receives packets instead of going up in smoke).

Here is SEL. (This is the “bypass voltage divider” unit.)

Here is MOSI when a packet arrives. (This is the “bypass voltage divider” unit.)

Here is MOSI (CH1) and MISO (CH2) when a packet arrives. (This is the “bypass voltage divider” unit.)

Here is SEL (CH1) and MOSI (CH2) when a packet arrives. (This is the “bypass voltage divider” unit.)

Here is SEL (CH1) and MISO (CH2) when a packet arrives. (This is the “bypass voltage divider” unit.)

Now I’m going to show you what I see when testing the unit with the 4k7+10k voltage dividers. There are no “packet arriving” events, because the unit doesn’t get past the rf12_initialize step, so I’m just recording the ongoing pattern that I see, which I am guessing (but have not confirmed) is the MCU’s continuing attempts to initialize the transceiver.

Here is SEL. (This is the “voltage-divider” unit.)

Here is MOSI. (This is the “voltage-divider” unit.)

Here is SEL (CH1) with MOSI (CH2). (This is the “voltage-divider” unit.)

Here is SEL (CH1) with MISO (CH2). (This is the “voltage-divider” unit.)

I also measured the values of the voltage dividers with the Fluke 83. On SEL, for example, they are 4.69k and 9.79k. Between Arduino pin 13 and the the SEL FlexyPin is 4.69k. Between the SEL FlexyPin and Arduino GND pin is 14.42k (very close to 4.69k+9.79k=14.48k).

With Vpp of the Arduino around 5.12V, the voltage divider equation with a 4.69k+9.79k voltage divider would give 3.462V. The Vpp reading on the SEL pin using the oscilloscope is 4.16V. That doesn’t make sense to me.

One more measurement. The rise time on the SEL signal of the unit without the voltage divider resistors is under 10 ns. On the unit with voltage dividers in place the rise time is 150 ns.

Basic question: have you calibrated your 'scope? The calibration square wave is that tag on the front panel, bottom right. I suspect you’ll find an “auto-calibrate” function in the “Utility” menu.

Allied to the above, were you using ×1 probes or ×10?
The longer rise time is caused by the driving impedance (higher, about 3.2 kΩ) interacting with capacitance in the circuit it’s driving - in this case, that includes the capacitance of the probes and the input capacitance of your 'scope. Using ×10 probes will increase the impedance of the scope & leads by about 10 times, so I’d expect you to see a much faster rise time. I think you should repeat the second set with ×10 probes (many probes have a switch, set it to ×10).
In the absence of a ×10 probe, just use a high value resistor (10 MΩ) in series. Y-Calibration will be wrong, but the the wave shape should look more like the upper set of images.

Your break-out board will add a bit of capacitance, I wouldn’t expect it to be significant.

I did the probe compensation adjustment before I took those measurements. I set the probe to 10x and adjusted the trimmer capacitor to get the flattest square wave.

Just now I also checked with the Fluke multimeter that the voltage readings on the scope matched the voltage readings on the multimeter. I tested the 5V pin of the Arduino. One got 5.02 and the other 5.04.

The 10x.

The probes and everything else were the same in the upper set of images (with shorter rise-times) and the lower set (with longer rise-times). The only difference I am aware of was the presence of the resistors. This got me searching the internet with terms like “capacitance of carbon film resistors”. I ran into this articles like this one: And discussions like this one: 3.3V to 5V level shifter for high speed serial - #20 by skyjumper - Project Guidance - Arduino Forum. Also on Wikipedia’s article about voltage dividers (Voltage divider - Wikipedia) “If the input impedance is capacitive, a purely resistive divider will limit the data rate.” And this from Felix Rusu on a LowPowerLab forum post (RFM69HW - Arduino Uno) “The 5v-3.3v signal buffering is another conundrum that is not very straightforward especially for beginners.”

Still, the proven designs discussed above (emonTx Shield, RFM12B Board) worked using carbon film resistors, and I can’t understand what my breakout is doing differently that would cause it not to work.

I don’t have a 5 V Arduino, nor either an emonTx Shield or of course your breakout board, so I don’t think there’s much more I can do. What we need is @glyn.hudson or @TrystanLea to post pictures of the same waveforms from an Arduino with the emonTx Shield and an RFM69CW.

As a general comment, every single component you can buy has resistance, capacitance and inductance. It’s only the proportions that differ: for example a “resistor” will have a tiny series inductance and a tiny parallel capacitance, with the resistance almost completely dominating its characteristics in most applications. It’s only at very high frequencies that the other two come into play.

What you might need to do is the same as your 10× 'scope probe does: put a small capacitor in parallel with the 4.7 kΩ resistor to “sharpen up” the edges. And I mean small - the RC time constant needed looks to be around 200 ns, about 68 pF.

This works! I did not have 68 pF, but I had 22 and 100 pF ceramic capacitors on hand. I tried the 22 pF first. It works! Messages coming through!

I’ll hook it up to the oscilloscope and post some pictures of the edges.

Why does this work?

1 Like

Read up about the compensation adjustment on your 'scope probe. It’s exactly the same.

The 10 kΩ resistor is equivalent to the 'scope input impedance, along with whatever stray capacitance to ground there is on the breakout board and the RFM69CW itself (the trimmer in your probe), the 4.7 kΩ is equivalent to the series resistor in the probe, and your 22 pF capacitor is the equivalent of the capacitor across the series resistor in the probe.

1 Like

I will do so.

In the meantime, what aspect of my breakout’s layout requires this compensation? The proven designs do not. If I moved the resistors closer to the RF69CW, could that help? If I use narrower traces? I’m curious where the difference is coming from.

Here are some photos, as promised.

Rising edge of SEL. (Unit with 22 pF compensation.)

First 8.7 ns of rising edge of SEL. (Unit with 22 pF compensation.)

Full 142 ns of rising edge of SEL. (Unit with 22 pF compensation.)

SEL (CH1) with MISO (CH2). (Unit with 22 pF compensation.)

SEL (CH1) with MOSI (CH2). (Unit with 22 pF compensation.)

It might be worth putting the scope on the 3.3V supply to the RF module. If that is sagging (or not powered at all) those input pins (SCLK, MOSI, CS) will likely put a much bigger load on your 5V->3.3V dividers.

Vpp will include any ringing and noise. You’re probably more interested in the Vtop or Vamp measurements. Here’s how my Agilent scope defines the various voltage measurements:

Screenshot from 2023-02-12 11-26-10

The Top of a waveform is the mode (most common value) of the upper part of the
waveform, or if the mode is not well defined, the top is the same as Maximum.

The Base of a waveform is the mode (most common value) of the lower part of the
waveform, or if the mode is not well defined, the base is the same as Minimum.

The Amplitude of a waveform is the difference between its Top and Base values.

1 Like


I’m not sure how to tell you this…

Your voltage dividers aren’t voltage dividers. You’ve got the input pin from the Arduino and the output to the RFM swapped - the 10 kΩ resistor is loading the Arduino output and the 4.7 kΩ is directly in series with the signal path.

Or put another way, the 10 kΩ is connected to the wrong end of the 4.7 kΩ.

But I must say, you’ve been consistent. All three have the same error.

This at the very least explains your 4.2 V on the RFM pins.

It would be a big help if there was some visual contrast on those PCB drawings.

Compare your PCB with this:

1 Like

Ha! I was studying the world of compensation adjustments and realized the same thing. I made this image to explain my mistake, came here to post it and … you had already solved it.

So, here it is as another way of visualizing my mistake, for all to see. I repeated this mistake faithfully on all three level-shifters.