[solved] attiny13a and nrf24L01+


I found a very interesting article about attiny13a using an nRF24L01+ module. Unfortunately, I don’t understand what code the author actually was using with the attiny13a. A questions in his comments in regards to the attiny13a was left unanswered.

Any chance someone has realized a project using these two components and can help me?

I think I need:

  1. A software SPI that fits the attiny13a
  2. nrf24 implementation that can be used with an attiny13a

I couldn’t find neither one that would compile. I tried to use some SoftwareSPI libs (e.g. TinySoftwareSPI) but without success. Hard to believe that I have to invent the wheel again. :confused:

Thanks & best

I think you will be very hard pressed to make that work within the Arduino IDE. 1k of flash is really painful.

He indicated in the comments that he develops in Atmel Studio, not Arduino (and I got the impression from his wording that he looks down on losers like you and I who are too stupid to use Atmel Studio instead of Arduino - which isn't an unreasonable view to hold, frankly).

Anyway, my guess would be he was doing everything by hand to make it fit, rather than using a nice NRF24 library. I mean, he implemented his own weirdo 2-pin SPI.

I have been using attiny13A for some time now to make small cheap attiny13 nrf sensors, with a bit of work i have managed to get the code size down to 620 bytes, reading adc from thermistor and voltage divider(to get battery voltage) and transmitting with nrf and sleeping on both.
My latest project was to squeeze in reading from a dht22 and battery voltage, that took some work i can say…

Here is the code for thermistor/voltage:
its really bare bone with direct port manipulation and some other tricks stolen from other code here and there.

As you can see i skipped miso so you can only write to nrf and not read anything from it.
The code are still messy but it is what i have for now.

For the reciever side you can use rf24 library just make that side match the settings in TX_mode()

As for arduino attiny13 core i recommend MCUdudes MicroCore: GitHub - MCUdude/MicroCore: An light-weight Arduino hardware package for ATtiny13

You have the 2 needed files added below this:
Oh and one more thing to run the code as it is you need a hvsp programmer or you can change the pins and skip reading the voltage… good luck

#include "API.H"

#include <avr/sleep.h>

ISR(WDT_vect) {}

#define CE 2
#define CSN 5
#define SPI_SCK 1
#define SPI_MOSI 0

#define cbi(x,y)    x&= ~(1<<y)
#define sbi(x,y)    x|= (1<<y)

#define SPI_DDR (*((&SPI_PORT) -1))
#define SPI_PIN (*((&SPI_PORT) -2))

#define adc_int 3
#define adc_stand 2
byte adc_internalRead=0;

struct dataStruct{
  int16_t adcc_int;
  int16_t adcc_stand; 
  uint32_t counter;
  byte id; 
#define TX_ADR_WIDTH    5   // 5 unsigned chars TX(RX) address width
#define TX_PLOAD_WIDTH  sizeof(myData)   // 32 unsigned chars TX payload

}; // Define a static TX address

byte tx_buf[TX_PLOAD_WIDTH] = {0};

int main(void){

 // analogReference(INTERNAL);
  // WDTCR |= (1<<WDP3);//4s
   WDTCR |= (1 << WDP3) | (1 << WDP0); // 8s
 // WDTCR |= (0<<WDP3) | (1<<WDP2) | (1<<WDP1)| (0<<WDP0); // (1<<WDP2) | (1<<WDP0);

   WDTCR |= (1<<WDTIE);

   sei(); // Enable global interrupts 

   // Use the Power Down sleep mode


SPI_DDR |= ((1<<SPI_SCK) | (1<<SPI_MOSI)| (1<<CE)| (1<<CSN));

  sbi (SPI_PORT, CSN);                          // Initialize IO port

   TX_Mode();                      // set TX mode


while(1) {

 ADCSRA |= (1<<ADEN); //adc on

SPI_RW_Reg(WRITE_REG + CONFIG, 0x0e); //pwr upp
delayMicroseconds(2000);//wait for power upp
SPI_RW_Reg(FLUSH_TX,0);//kill old data
memcpy(tx_buf, &myData, TX_PLOAD_WIDTH);
  sbi (SPI_PORT, CE); 
 // delayMicroseconds(80);    

  myData.adcc_int = analogRead(adc_int);
  myData.adcc_stand = analogRead(adc_stand);



cbi (SPI_PORT, CE);

SPI_RW_Reg(WRITE_REG + CONFIG, 0x00);   //pwr down
ADCSRA &= ~(1<<ADEN);

 * Function: SPI_RW();
 * Description:
 * Writes one unsigned char to nRF24L01, and return the unsigned char read
 * from nRF24L01 during write, according to SPI protocol
void SPI_RW(byte data)
    byte i = 8;

        SPI_PORT &= ~(1<<SPI_MOSI);     // clr mosi
        if (data & 0x80) SPI_PIN = (1<<SPI_MOSI);
        SPI_PIN = (1<<SPI_SCK);         // clk hi
        data <<= 1;
        SPI_PIN = (1<<SPI_SCK);         // clk lo



 * Function: SPI_RW_Reg();
 * Description:
 * Writes value 'value' to register 'reg'
void SPI_RW_Reg(byte reg, byte value)

   cbi (SPI_PORT, CSN);     
  SPI_RW(reg);                   // select register
  SPI_RW(value);                          // ..and write value to it..
   sbi (SPI_PORT, CSN);      

 * Function: SPI_Write_Buf();
 * Description:
 * Writes contents of buffer '*pBuf' to nRF24L01
 * Typically used to write TX payload, Rx/Tx address
void SPI_Write_Buf(byte reg, byte *pBuf, byte bytes)
  byte i;
cbi (SPI_PORT, CSN);      
 // digitalWrite(CSNq, 0);                  // Set CSN low, init SPI tranaction
  SPI_RW(reg);             // Select register to write to and read status unsigned char
  for(i=0;i<bytes; i++)             // then write all unsigned char in buffer(*pBuf)
 sbi (SPI_PORT, CSN);     
  //digitalWrite(CSNq, 1);                   // Set CSN high again

 * Function: TX_Mode();
 * Description:
 * This function initializes one nRF24L01 device to
 * TX mode, set TX address, set RX address for auto.ack,
 * fill TX payload, select RF channel, datarate & TX pwr.
 * PWR_UP is set, CRC(2 unsigned chars) is enabled, & PRIM:TX.
 * ToDo: One high pulse(>10us) on CE will now send this
 * packet and expext an acknowledgment from the RX device.
void TX_Mode(void)
  //digitalWrite(CEq, 0);
  //SPI_RW_Reg(WRITE_REG + SETUP_AW,0X01);//3 byte adress
  SPI_Write_Buf(WRITE_REG + TX_ADDR, TX_ADDRESS, TX_ADR_WIDTH);    // Writes TX_Address to nRF24L01
  SPI_Write_Buf(WRITE_REG + RX_ADDR_P0, TX_ADDRESS, TX_ADR_WIDTH); // RX_Addr0 same as TX_Adr for Auto.Ack

  SPI_RW_Reg(WRITE_REG + EN_AA, 0x00);      // Enable Auto.Ack:Pipe0
  SPI_RW_Reg(WRITE_REG + EN_RXADDR, 0x01);  // Enable Pipe0
  SPI_RW_Reg(WRITE_REG + SETUP_RETR, 0x01); // 500us + 86us, 10 retrans...
  SPI_RW_Reg(WRITE_REG + RF_CH, 40);        // Select RF channel 40
 // SPI_RW_Reg(WRITE_REG + RF_SETUP, 0x27);   // TX_PWR:6dBm, Datarate:2Mbps, LNA:HCURR
  SPI_RW_Reg(WRITE_REG + RF_SETUP, 0x27);   // TX_PWR:-6dBm, Datarate:2Mbps, LNA:HCURR
 // SPI_RW_Reg(WRITE_REG + RF_SETUP, 0x26);   // TX_PWR:0dBm, Datarate:2Mbps, LNA:HCURR
  //SPI_RW_Reg(WRITE_REG + CONFIG, 0x0e);     // Set PWR_UP bit, enable CRC(2 unsigned chars) & Prim:TX. MAX_RT & TX_DS enabled..
   SPI_RW_Reg(WRITE_REG + 0x1C,0x01);     //dynamic payload pip0
 // SPI_RW_Reg(WRITE_REG + 0x1D, 0x04);     //dyn payload feature
  SPI_RW_Reg(WRITE_REG + 0x1D, 0x05);     //dyn payload feature +noacc pay
 // digitalWrite(CEq, 1);

API.H (2.65 KB)

attiny13_tmp_inst_1_ute_1.ino (5.76 KB)

Hi all,

Thank you for your help.
Firstly, let me clarify one thing: Nerd Ralph wrote "The result is that I was able to control a nRF24l01+ module using just two pins on an ATtiny13a." (first paragraph, line 3)

Secondly, I have a bunch of attiny13a so I want to put them to good use :wink: And, swe-dude, that is what I aimed at, thanks a lot for the code!
I saved the ino and h file into a new subfolder of the sketches folder and tried to compile your code but failed.

wiring.c.o (symbol from plugin): In function `__vector_8':

(.text+0x0): multiple definition of `__vector_8'

sketch\attiny13_tmp_inst_1_ute_1.ino.cpp.o (symbol from plugin):(.text+0x0): first defined here

collect2.exe: error: ld returned 1 exit status

exit status 1

I'm running Arduino IDE 1.6.9 and MiniCore 1.0.3. I don't understand the error message at all :confused: Sorry to bother you but any idea what went wrong?

you need a hvsp programmer or you can change the pins and skip reading the voltage

Is this because you have to disable the RESET pin (PB5) to use it and lose the ability to SPI-program? Would be fun enough to use one GPIO. However, maybe I can use your code and tweak it for Ralph Nerd's 2-pin SPI :slight_smile: I'd be happy to share any results in the end.

Thanks again and best

If you are playing with t13 i guess you mean microcore and not minicore… anyway you need to take a look in core_settings.h in microcore folder

// If you're not using millis(), you can save about 100b by commenting this out.
// Note that the millis() interrupt is based on the watch dog timer, and will interrupt
// every 16th ms (which is very little. This means the millis() function will not be 
// accurate down to 1 ms, but will increase with steps of 16.

Microcore uses wdt for millis so you need to either //#define ENABLE_MILLIS
or remove ISR(WDT_vect) {} from the code.
I would go with the first one, and while you are there maybe //#define SAFEMODE too that will give you smaller and faster running code.

I did try the 2 pin nrf thing from Nerd Ralph but the sleep current are terrible with the mosi/miso multiplex so that one is out for long time battery sensors,

Another tip the csn/sck multiplex with capacitor and resistor, I never got that one to work long time. it always stopped working after 1000-70000 transmissions or so.
I went with the earlier cap res diode one instead, that one have been stable for a year+

For minimal sleep current on transmitter when i need more pins i use a 3 pin variant ce ,sck/csn multiplex and mosi.

If you are planning to make a few projects with t13 you really need to build a hvsp programmer, this one have worked perfect for me:ScratchMonkey Users Manual

Below you have the code i used as a starting point for all the variants of nrf multiplex i tried, some copy and paste from Nerd Ralph´s code and i think you will be all set.
Happy coding.

NRF24L01_Demo_For_Arduino_v11 (2).zip (8.73 KB)

Dear swe-dude,

Thank you very very much! I would have never made the link between the error message reported above and #define ENABLE_MILLIS
Now, your first code example did compile without problems.
I did not try your second code-set yet. However, using the first example (changing TX_ADDRESS, PINs and channel) did not work with my receivers.
As soon I have time, I’ll have a look at your second code-set.

Thanks again & best!

Hi swe-dude,

I tried to get the tiny13a using the nrf24 but still no luck. Would be great if you have some thoughts on that.
All is wired as configured (checked with DMM). I’ve added a LED to the unused PIN (3) to check that the sketch runs fine. It does work, the tiny13a goes to sleep, wakes up etc.
I think there is a mismatch between the sender and receiver sketch because no data is received.
I changed some register values in your routine to match my needs (four lines marked with ***):

void TX_Mode(void)
  //digitalWrite(CEq, 0);
  //SPI_RW_Reg(WRITE_REG + SETUP_AW,0X01);//3 byte adress
  SPI_Write_Buf(WRITE_REG + TX_ADDR, TX_ADDRESS, TX_ADR_WIDTH);    // Writes TX_Address to nRF24L01
  SPI_Write_Buf(WRITE_REG + RX_ADDR_P0, TX_ADDRESS, TX_ADR_WIDTH); // RX_Addr0 same as TX_Adr for Auto.Ack

  SPI_RW_Reg(WRITE_REG + EN_AA, 0x00);      // Enable Auto.Ack:Pipe0
  SPI_RW_Reg(WRITE_REG + EN_RXADDR, 0x01);  // Enable Pipe0
  //SPI_RW_Reg(WRITE_REG + SETUP_RETR, 0x01); // 500us + 86us, 10 retrans...
  SPI_RW_Reg(WRITE_REG + SETUP_RETR, 0x0f); // 250us + 86us, 15retrans... ***
  SPI_RW_Reg(WRITE_REG + RF_CH, 83);        // Select RF channel 83 ***
 // SPI_RW_Reg(WRITE_REG + RF_SETUP, 0x27);   // TX_PWR:6dBm, Datarate:2Mbps, LNA:HCURR
  //SPI_RW_Reg(WRITE_REG + RF_SETUP, 0x27);   // TX_PWR:-6dBm, Datarate:2Mbps, LNA:HCURR
SPI_RW_Reg(WRITE_REG + RF_SETUP, 0x2e);   // TX_PWR:-6dBm, Datarate:250kbps, LNA:HCURR ***
 // SPI_RW_Reg(WRITE_REG + RF_SETUP, 0x26);   // TX_PWR:0dBm, Datarate:2Mbps, LNA:HCURR
  //SPI_RW_Reg(WRITE_REG + CONFIG, 0x0e);     // Set PWR_UP bit, enable CRC(2 unsigned chars) & Prim:TX. MAX_RT & TX_DS enabled..
   SPI_RW_Reg(WRITE_REG + 0x1C,0x01);     //dynamic payload pip0
  SPI_RW_Reg(WRITE_REG + 0x1D, 0x04);     //dyn payload ure ***
  //SPI_RW_Reg(WRITE_REG + 0x1D, 0x05);     //dyn payload feature +noacc pay
 // digitalWrite(CEq, 1);

The tiny13a is running at 9.6 MHz, just in case that matters.

My receiver sketch works when using a second Nano and the same nrf24 Module that is attached to the tiny13a

// SimpleRx - the slave or the receiver

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

#define CE_PIN   9
#define CSN_PIN 10

#define CHANNEL 83
const byte addr[5] = {
char dataReceived[9];

RF24 radio(CE_PIN, CSN_PIN);


void setup() {

    Serial.println("Simple nrf Receiver");
    Serial.print("listen on ");
    for(byte b=0;b<5;b++) Serial.print(addr[b],HEX);
    Serial.print(", using channel ");
    radio.setDataRate( RF24_250KBPS );
    radio.openReadingPipe(0, addr);


byte ackResponse = 0;

void loop() {
    byte pipe;
    if ( radio.available(&pipe) ) {
        radio.read( &dataReceived, sizeof(dataReceived) );
        Serial.print("Data [");
        Serial.print("] received on pipe [");
        for(byte b=0;b<5;b++) Serial.print(addr[b],HEX);

Thanks in advance & best

Ok i can see a few things that might cause problems
SPI_RW_Reg(WRITE_REG + EN_AA, 0x00); // Enable Auto.Ack:Pipe0
This one disables aa i forgot to change the text to //Disable Auto.Ack:Pipe0 (sorry…)
(0x01 enables aa)
SPI_RW_Reg(WRITE_REG + RF_SETUP, 0x2e);(00101110)

You have bit 3 “RF_DR_HIGH” and bit 5 “RF_DR_LOW” set to 1, not sure what Reserved does but it probably wont work.
‘00’ – 1Mbps
‘01’ – 2Mbps
‘10’ – 250kbps
‘11’ – Reserved
RF_SETUP, 0x27 are for 250kbps full power, not 2Mbps one more thing i forgot to change.
I grabbed the first attiny13 nrf code that i knew was working that i could find and forgot not all have corrected comments…

One more tip, for some changes in the nrf24l01 settings to take effect you might need to completely power off the nrf.

Fixing that might make it work but i cant test it right now.
But just in case it don’t work here is the settings and file that match the settings in the original file i uploaded

#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"
#include "printf.h"

// Hardware configuration

// Set up nRF24L01 radio on SPI bus plus pins 7 & 8

//RF24 radio(7,8);
RF24 radio(9,10);

#define ADR_WIDTH    5   // 5 unsigned chars TX(RX) address width
#define PLOAD_WIDTH  32  // 32 unsigned chars TX payload
#include <SimpleTimer.h> // here is the SimpleTimer library
SimpleTimer timer; // Create a Timer object called "timer"!
unsigned long good=0;
unsigned long bad=0;
uint8_t pip=0;

uint8_t pload_width_now=0;
byte newdata=0;

unsigned char ADDRESS2[1]= {0xb2};	
unsigned char ADDRESS3[1]= {0xb3};	
unsigned char ADDRESS4[1]= {0xb4};	
unsigned char ADDRESS5[1]= {0xb5};	

unsigned char ADDRESS1[ADR_WIDTH]  = 
}; // Define a static TX address

unsigned char ADDRESS0[ADR_WIDTH]  = 
}; // Define a static TX address


byte signal_lv=0;

struct dataStruct{
  int adcc_volt; 
  int adcc_tmp; 
  unsigned long counter;

unsigned char rx_buf[PLOAD_WIDTH]= {0};
unsigned char tx_buf[PLOAD_WIDTH]= {0};

void setup(void)


  // Setup and configure rf radio

  // enable dynamic payloads
 // radio.enableAckPayload();
  // optionally, increase the delay between retries & # of retries
  radio.setAutoAck(0);                    // Ensure autoACK is off
 // radio.setRetries(15,15);  // Max delay between retries & number of retries
  radio.setCRCLength(RF24_CRC_16 );

  timer.setInterval(50L, RXX);  

void loop(void)
    timer.run(); // Initiates SimpleTimer

RF24_mine_Nano_3_forum.zip (2.31 KB)

Thank you very much for your help!
I already had a look at the nrf24L01+ (plus!) data sheet but obviously I overlooked some things. So thanks for pointing that out!
In my data sheet on page 58 of 78 there is written:

RF_DR_LOW 5 0 R/W Set RF Data Rate to 250kbps. See RF_DR_HIGH for encoding.
RF_DR_HIGH Select between the high speed data rates. This bit is don’t care if RF_DR_LOW is set

Looks like I misunderstood something because I read this as "if bit 5 == 1 then bit 3 == ignored". Nevertheless I changed bit 3 to 0.
Btw. why is your first bit (bit 0) set to 1? The data sheet says it's "obsolete". I'll test 0x26 first and, if this does not work, I'll go with 0x27.

One more tip, for some changes in the nrf24l01 settings to take effect you might need to completely power off the nrf.

Good to know, thanks. So, after flashing the attiny13a I'll cut the power completely.

Fixing that might make it work but i cant test it right now.

No problem, I'm so glad you helped! I'll get back here to report the results.


Btw. why is your first bit (bit 0) set to 1? The data sheet says it's "obsolete". I'll test 0x26 first and, if this does not work, I'll go with 0x27

On a genuine nrf chip you are right bit 0 does nothing but those are rare.. on most clones thou setting the 0 bit gives a bit more power.

Another tip,
This is also one of the few ways to test if the nrf24l01 are a clone or not(that i know of..) power the transmitter with a big cap 500uf+ and see how many transmissions you get. if you get the same with 0x26 and 0x27 you probably have a genuine nrf... for me that was 1 per 10...

Dear swe-dude, thanks for your tip!
Btw. the good news is, I do receive data sent by the attiny13a! :smiley: Your ATTiny13A sketch works great :slight_smile: Looks like radio.enableDynamicPayloads(); did the trick. radio.setAutoAck(0); does not make a difference.
That's really great! Thank you very much, again :slight_smile: From here I can build some funny things.

Side note: When first testing, radio.printDetails() printed zero-values for the properties (which were correctly printed). Some testing revealed that one of the USB ports of my USB hub seems to be faulty. But I don't understand why this command does not work but everything else like SPI programming does. Maybe a power issue?


Sorry to revive an old thread, but did you ever get this working? Would you happen to have the final code? I'm trying to use the code you posted and when I look at the logic analyzer the attiny seems to be sending the right spi code but the NRF isn't transmitting anything.