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);
}

Update 2014/06/12:
I've noticed that power usage on the NRF modules shoots up if I leave any inputs (CSN, SCK, MOSI) floating.  With my circuit above CSN will never float, but SCK and MOSI should be pulled high or low when not in use, especially for battery-powered devices.

29 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
    2. Hi, great concept. I was also trying to come up with a solution to multiplex pins on my ATTiny85 to use the nRF24L01+. I was going to multiplex SCK and CE with a low-pass filter on CE so one could keep SCK high when idle, and the cap would keep CE high during transfers. Your solution would free another pin though as compared to my idea which still has a discrete CSN pin.

      Have you confirmed that the PTX and PRX modes both work properly using your circuit? According to the datasheets, one must make a low-hi-low pulse of >= 10us to initiate a transmission.. I'm curious as to how keeping CE tied high still allows one to transmit. How is the transfer initiated (is it by totally powering down the nRF24L01+ with a PWR_UP = 0 command, followed by a PWR_UP = 1, or is it sufficient to just set PRIM_RX = 0, with CE already high, after loading the TX FIFO)?

      The state diagrams in the datasheet, and support replies I have gotten from Nordic Semi, suggest that the low-hi CE transition is essential to initiating TX mode.


      Delete
    3. Tieing CE high isn't my idea - I saw it on another NRF example circuit when I was researching how to use them. It's been a few months since I was playing with them, but what I remember is you HAVE to toggle CSN - when tied low, it would only respond to the first command. After I wrote aspspi I was able to figure out that it is only after CSN goes high that the NRF processes the SPI command. Not only that, but for a register write, you can include a bunch of junk bytes between the command and the data you want to write, so for a single byte register it will only write the last byte of the SPI sequence.
      I agree the NRF datasheet can be a bit confusing as to the combinations of CE, CSN and register settings. If you read the datasheet from beginning to end (perhaps more than once) you can figure it out. After I figured out CE can be tied high and CSN has to be toggled, I re-read the manual and realized 6.1.6 does confirm what I found. It specifies a 10us high pulse to empty one level of the TX fifo (normal mode) or hold CE high to empty all levels of the TX fifo.
      Its also clear from the table (15) that for power down mode the state of CE doesn't matter - only the PWR_UP register has to be set to 0.
      p.s. it's even possible to toggle CSN with just a resistor and capacitor attached to SCK. SCK needs to be held low for a longer period, so I think it's easier with a diode.

      Delete
    4. Aha, I see what you mean - Table 15, "nRF24L01+ main modes" makes it all clear.

      So in the state diagram, if CE is held high the device can still go into Standby-II mode whenever there's no TX FIFO data, which saves almost as much power as Standby-I (not a big deal). Thanks!

      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
  10. Thanks Ralph for sharing.

    I am just starting out trying to it to work on my digispark. Which pins did you use when you tested it with your digi? I am trying to figure out which pins on the nrf24l01 to solder to the pins of the digi,

    Thank you in advance

    ReplyDelete
    Replies
    1. I used jumper wires between the header pins instead of soldering - much easier to fix if you make a wrong connection.
      Connect PB0(DI) to MISO, PB1(DO) to MOSI, and PB2 to SCK.

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

    ReplyDelete
  12. Hello,
    I have found these library for the Attiny85, https://github.com/stanleyseow/attiny-nRF24L01, and I was wondering if it works the same with your circuit and changing the Mirf.cpp lines (in that case Mirf85.cpp).

    I have uploaded the example sketch that comes with that library (attiny-nRF24L01) and I get the led flashing (the one between CE and Vcc) but I am not sure if it is sending anything.

    ReplyDelete
    Replies
    1. Yes, that's the link that is in the first paragraph of my post.

      Delete
  13. Hello Ralph,

    Thanks a lot for your hack, I tried to tie the CE PIN to VCC but the modules don't work anymore. I didn't use your hack for the CSN PIN (I am using a ATMEGA328, i just needed one more pin for my project), so the CSN pin is still used as before (on D7).

    I tried the mirf library, and the maniacbug's one. The nRF won't send any data with either library (I commented out all references CE pin in the code).

    Can you help me ? Did anyone encounter this problem ?

    Thanks in advance.

    Arnaud.

    ReplyDelete
    Replies
    1. The CE pin can be used to control the transmit. When it's tied high, PWR_UP must be set in order to transmit (see my discussion above with DangerRuss).
      I have only tried the version of the Mirf library modified to use the ATtiny's USI instead of SPI. I have been planning to test the modules with an Attiny88 which has hardware SPI, but haven't got around to it yet.

      Delete