Thursday, January 9, 2014

nrf24l01+ control with 3 ATtiny85 pins

Nrf24l01+ modules are a cheap and low-power option for MCU wireless communication.  Libraries are available for Arduino, and for arduino compatible MCUs like the ATTiny85.  Controlling the nrf modules usually requires power plus 5 pins - CE, CSN, SCK, MOSI, & MISO.  With pin-limited MCUs like the ATtiny85, 5 pins is a lot to tie up.  On something like the Digispark, with PB3 and PB4 hard-wired to USB+ and USB-, using the nrf24l01+ modules might seem impossible.  Another issue is that although the nrf inputs are 5v tolerant, Vcc must be between 1.9 and 3.6V.  I've designed a simple solution to provide 3V power as well as control the modules with just 3 of the pins on the ATtiny85.

For powering the nrf24l01+ module, a 3.3v regulator could be used, but a cheaper and simpler way is to drop a 5V supply to 3V using a 20mA-rated red led.  Most red LEDs have a forward voltage drop between 1.8 and 2.2V, leaving 3.2-2.8V for the nrf, which is well within the 1.9-3.6V requirement.

Controlling CE without using a pin on the AVR is also easy: just tie it high.  In my setup function, I set Mirf.cePin = 7 as a dummy since the tiny85 only has pins 0-6.  I later commented out the digitalWrite calls in Nrf24l::ceHi() and ceLow(), and removed the Mirf.cePin line from setup() which cut down on the size of my compiled sketch.

I initially thought the CSN line could be tied low, but when I tried it my test sketch was not getting a valid status back from the nrf module.  I also found section 8.3.1 of the datasheet: "Every new command must be started by a high to low transition on CSN."  So in order to control the nrf with just 3 pins, CSN needs to be multiplexed with one (or more) of SCK, MOSI, or MISO.  After a few different ideas, I came up with this circuit:
When SCK on the ATtiny85 goes high for several microseconds, C1 will charge through R1 and bring CSN high.  If SCK is brought low for several microseconds before being used to clock the SPI data, C1 will discharge through D1 and bring CSN low.  High pulses on SCK of less than a few microseconds during communication with the nrf won't last long enough to charge C1, so CSN will stay low.

To support the multiplexed SCK/CSN, I modified Mirf.cpp as follows:
void Nrf24l::csnHi(){
PORTB |= (1<<PINB2); // SCK->CSN HIGH
delayMicroseconds(64);  // allow csn to settle
}

void Nrf24l::csnLow(){
PORTB &= ~(1<<PINB2);  // SCK->CSN LOW
delayMicroseconds(8);  // allow csn to settle
}

The circuit still worked with a 32us delay in csnHi and 8us in csnLow, but I doubled those values to have a good safety margin.  The delays could be reduced with a smaller capacitor for C1.  Going lower than .01uF could risk CSN going high during the high clock pulses of SCK.

When connecting the nrf module to a tiny85, connect MISO(pin7) on the module to MOSI/DI(PB0), and not MISO/DI(PB1).  Here's the connections required:
nrf module  ATtiny85 pin
SCK(5)      PB2 (physical pin 7)
MOSI(6)     PB1 (physical pin 6)
MISO(7)     PB0 (physical pin 5)

I also changed TinyDebugSerial.h to define TINY_DEBUG_SERIAL_BIT 5, and connected pb5 to the Rx line of my ttl serial module.

Finally, here's my test sketch.  When it runs, it reports a status of 'E', which is the reset value of the status register according to the datasheet.  If you connect things wrong it will usually report 0 or FF.
#include <SPI85.h>
#include <Mirf.h>
#include <MirfHardwareSpi85Driver.h>

void setup()
{
  Serial.begin(115200);

  Mirf.spi = &MirfHardwareSpi85;
  Mirf.init();
}

void loop()
{
  uint8_t nrfStatus;
  delay(3000);
  Serial.print("\nMirf status: ");
  nrfStatus = Mirf.getStatus();
  // do back-to-back getStatus to test if CSN goes high
  nrfStatus = Mirf.getStatus();
  Serial.print(nrfStatus, HEX);
}

19 comments:

  1. Brilliant! This opens up all sorts of possibilities. Need to resurrect that tray of ATtiny chips ...

    ReplyDelete
  2. Very Clever, i was just thinking the same of dropping off some wires, but, i was so lazy to think of it :-)

    I will try your solution with my digispark

    ReplyDelete
    Replies
    1. I tested the circuit with a digispark, so you should have no problems.

      Delete
    2. I've duplicated this circuit with a digispark. I'll update after end to end testing.

      Delete
  3. It's probably worth mentioning that tying CE high throws away the ability to switch to low-power TX modes, which may be a problem if you're running on batteries. Otherwise, clever hack!

    ReplyDelete
    Replies
    1. I don't think low power modes are completely lost. Looking at the datasheet, and from my testing it can go into low power mode with CE high. The side benefit of using the red LED to drop the voltage is that it is a simple power usage indicator. When first powered up the module uses ~10mA and the LED glows bright. After initialization with nothing to transmit and rx mode off, the LED is quite dim, indicating well under 1mA of power consumption. Going from memory, I think there are a few low power modes, so possibly the lowest power mode may require CE low.

      Delete
  4. Absolutely !!! Fantastic Thanks for Sharing Ralph....

    ReplyDelete
  5. I'm sure it's an stupid question, but PB0 (physical pin 5) it's the only one in Attiny with HW PWM and I need to use it in one of my projects involving attiny plus nrf24l01.
    Is it possible to use physical pin 2 or 3 in attiny to connect MISO(7) from the nrf24l01 ?

    Very ingenious trick by the way! Be sure I'll be using it !

    ReplyDelete
    Replies
    1. Pin 5 is USI data in, but if you use bitbang (software) SPI instead of USI, then you should be fine.

      Delete
  6. This comment has been removed by a blog administrator.

    ReplyDelete
  7. Have you transmitted anything between two modules? I'm interested in getting one up and talking to another Arduino but I'm not getting anywhere. I got the E status back but I haven't been able to communicate with another module. Any help would be great.

    Thanks.
    Steve

    ReplyDelete
    Replies
    1. Did you make sure they're both set to the same channel?
      If you use the red LED to drop the power like I show in the circuit, it gets quite dim when the NRF is not receiving because power use drops from ~10mA in active receive or transmit mode to <1mA idle. The sample sketch I posted does not put the module in receive mode. If you've think you've it set to receive mode but the LED is not glowing bright, then it probably isn't really in receive mode.

      Delete
    2. All I have managed to do was get the E status from the sketch you included. I'm not very familiar with the MIRF library and haven't been able to get it to run on my mega with any reliability let alone transmit from the attiny. There has to be something basic I'm doing wrong. I have gotten the nrf library to work and transmit.

      Delete
    3. You won't be able to get the same library working for an ATMega and an ATtinyx5 since the the x5 doesn't have hardware SPI - the SPI communications is done using USI instead.
      Since you're getting the E status result back, the communication is working, so it's just a matter of programming the nrf it to transmit. At a minimum that means you need to write the tx payload (W_TX_PAYLOAD command), and set the PWR_UP of register address 0 to 1. See sections 8.3 and 9 of the datasheet for more details.

      Delete
  8. Hey Ralph,

    I had downloaded the attiny core from here: https://code.google.com/p/arduino-tiny/ and the mirf/spi85 libraries here: https://github.com/samuelclay/doormonitor

    everything compiles fine before modifying the .cpp file, but when i do your modification, I receive the folllowing errors:

    /Applications/Arduino.app/Contents/Resources/Java/libraries/Mirf/Mirf.cpp:288: error: stray '\302' in program
    /Applications/Arduino.app/Contents/Resources/Java/libraries/Mirf/Mirf.cpp:288: error: stray '\240' in program
    /Applications/Arduino.app/Contents/Resources/Java/libraries/Mirf/Mirf.cpp:293: error: stray '\302' in program
    /Applications/Arduino.app/Contents/Resources/Java/libraries/Mirf/Mirf.cpp:293: error: stray '\240' in program
    /Applications/Arduino.app/Contents/Resources/Java/libraries/Mirf/Mirf.cpp:294: error: stray '\302' in program
    /Applications/Arduino.app/Contents/Resources/Java/libraries/Mirf/Mirf.cpp:294: error: stray '\240' in program

    ReplyDelete
    Replies
    1. Those look like unicode character escapes. I'd look at it in an editor like vim to see where you have accidentally inserted any non-ascii characters.

      Delete
  9. This comment has been removed by the author.

    ReplyDelete