RF24G with SPI

Here are some functions I use for the RF-24G (shockburst mode) connected to an AVR with SPI. The SPI connection is described in:

http://www.nordicsemi.no/files/Product/ … RF2401.pdf

The code is for avr-gcc. I use it with mega32 and mega48 parts (both at 8Mhz and with the SPI clock at 1Mhz).

The ‘trf24g_config.h’ file is where the wanted configuration and pins to use must be specified (the file below is what I use on a mega32).

/Lars

trf24g.h:

/*
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
 * Lars Jonsson (User name Lajon at www.avrfreaks.net) wrote this file.  
 * As long as you retain this notice you can do whatever you want with this
 * stuff. If we meet some day, and you think this stuff is worth it, you can
 * buy me a beer in return.   Lars Jonsson
 * ----------------------------------------------------------------------------
 *
 */
#ifndef TRF24G_H
#define TRF24G_H

#include <inttypes.h>
#include <stdbool.h>

extern void trf24gInit(void);
extern bool trf24gReceive(void *buf);
extern void trf24gSend(void *buf);
extern void trf24gReconfigure(uint8_t channel, bool rx);
#define TRF24G_TIME_ON_AIR ((1 + 3 * (1-TRF24G_1MBPS)) * 8 * \
	(1 + TRF24G_ADDR_SIZE + TRF24G_PAYLOAD_SIZE + TRF24G_CRC_SIZE))
#endif

trf24g_config.h:

/*
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
 * Lars Jonsson (User name Lajon at www.avrfreaks.net) wrote this file.  
 * As long as you retain this notice you can do whatever you want with this
 * stuff. If we meet some day, and you think this stuff is worth it, you can
 * buy me a beer in return.   Lars Jonsson
 * ----------------------------------------------------------------------------
 *
 */
#ifndef TRF24G_CONFIG_H
#define TRF24G_CONFIG_H

#define TRF24G_CHANNEL 2
#define TRF24G_RX 0 // 0 or 1
#define TRF24G_ADDR_SIZE 3 // 1-5
#define TRF24G_ADDR1 0, 0,'L',0x55,0xAF // 5 bytes
#define TRF24G_RX2_EN 0 // 0 or 1
#define TRF24G_ADDR2  0,0,0,0,0 // 5 bytes
#define TRF24G_CRC_SIZE 2 // 0,1,2
#define TRF24G_PAYLOAD_SIZE 12
#define TRF24G_1MBPS 0 // 1== 1MBs 0==250Kbs

#define TRF24G_CONTROL_PORT  PORTD
#define TRF24G_CONTROL_DDR   DDRD
#define TRF24G_CS_MASK	  (1<<5)
#define TRF24G_CE_MASK	  (1<<4)

#define TRF24G_DR1_PIN	  PIND
#define TRF24G_DR1_MASK 	  (1<<2) 

#define TRF24G_SPI_DDR	  DDRB
#define TRF24G_SPI_SCK_MASK  (1<<7)
#define TRF24G_SPI_MOSI_MASK (1<<5)

#endif

trf24g.c:

/*
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
 * Lars Jonsson (User name Lajon at www.avrfreaks.net) wrote this file.  
 * As long as you retain this notice you can do whatever you want with this
 * stuff. If we meet some day, and you think this stuff is worth it, you can
 * buy me a beer in return.   Lars Jonsson
 * ----------------------------------------------------------------------------
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <avr/io.h>
#include <avr/signal.h>
#include <avr/interrupt.h>
#include "config.h"
#include <avr/delay.h>
#include "trf24g_config.h"


#define TRF24G_START_CONFIG() TRF24G_CONTROL_PORT |= TRF24G_CS_MASK; _delay_us(5)
#define TRF24G_END_CONFIG()   TRF24G_CONTROL_PORT &= (uint8_t)~TRF24G_CS_MASK;

#define TRF24G_ACTIVE()   TRF24G_CONTROL_PORT |= TRF24G_CE_MASK; _delay_us(5)
#define TRF24G_INACTIVE() TRF24G_CONTROL_PORT &= ~TRF24G_CE_MASK;

static uint8_t trf24gConfig[15] = {
	/*  0 */ TRF24G_PAYLOAD_SIZE * 8,	// DATA2_W
	/*  1 */ TRF24G_PAYLOAD_SIZE * 8,	// DATA1_W
	/*  2 */ 0,0,0,0,0,					// ADDR2 not used
	/*  7 */ TRF24G_ADDR1,				// ADDR1
	/* 12 */ ((TRF24G_ADDR_SIZE * 8) << 2) | 
	         ((TRF24G_CRC_SIZE == 0) ? 0b00 : ((TRF24G_CRC_SIZE == 1) ? 0b01 : 0b11)), 
	/* 13 */ (0 << 7) |					// RX2 active or not (not for now)
			 (1 << 6) | 				// Shockburst (always)
			 (TRF24G_1MBPS << 5) |		// Speed
			 (0b011 << 2) |         	// 16MHz always
			 0b11,                    	// Full power always
	/* 14 */ (TRF24G_CHANNEL << 1) | TRF24G_RX
 };
 #define ADDR1 7
 
static inline uint8_t spiSend(uint8_t d)
{
	SPDR = d;
	while(!(SPSR & (1<<SPIF)))
		;
	return SPDR;
}

void trf24gInit(void)
{
	uint8_t i;
	
	// DDR for trf24g control, CS and CE are outputs
	TRF24G_CONTROL_DDR |= TRF24G_CS_MASK|TRF24G_CE_MASK;
    // DDR for SPI, MOSI and SCK are outputs
	TRF24G_SPI_DDR |= TRF24G_SPI_MOSI_MASK|TRF24G_SPI_SCK_MASK;
	
	// SPI master, trailing edge setup 
	SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0); // /8 clock (8Mhz/8 = 1Mhz)
    SPSR |= (1<<SPI2X);
	
	//SPCR = (1<<SPE)|(1<<MSTR)|(0<<SPR0); // /4 clock (works also)
	
	// >3ms startup
	_delay_ms(3);
	TRF24G_START_CONFIG();
	for(i = 0; i < sizeof(trf24gConfig); i++) {
		spiSend(trf24gConfig[i]);
	}
	TRF24G_END_CONFIG();
#if TRF24G_RX
	TRF24G_ACTIVE();
#endif
}

void trf24gReconfigure(uint8_t channel, bool rx)
{
	uint8_t cfg = channel << 1;
	cfg |= rx;
	
	TRF24G_INACTIVE();
	TRF24G_START_CONFIG();
	spiSend(cfg);
	TRF24G_END_CONFIG();
	if (rx) {
		TRF24G_ACTIVE();
	}
}

bool trf24gReceive(void *p)
{	
	uint8_t dr = TRF24G_DR1_PIN & TRF24G_DR1_MASK;
	if (dr) {
		uint8_t n = TRF24G_PAYLOAD_SIZE;
		uint8_t *buf = (uint8_t*)p;
		do {
			*buf++ = spiSend(0);
		} while(--n);
	}
	return dr;
}

void trf24gSend(void *p)
{
	uint8_t n = TRF24G_ADDR_SIZE;
	uint8_t *buf = (uint8_t*)p;
	uint8_t *a = trf24gConfig + ADDR1 + 5 - TRF24G_ADDR_SIZE;
	
	TRF24G_ACTIVE();
	do {
		spiSend(*a++);
	} while(--n);
	n = TRF24G_PAYLOAD_SIZE;
	do {
		spiSend(*buf++);
	} while(--n); 
	TRF24G_INACTIVE();
}

Important note from davids@oz.net about SPI configuration:

On the Mega8 at least you have to set the !SS pin as an output or put an external pullup on it. If you don’t when you set the SPI master mode if !SS ever goes low the chip will drop out of master mode (a bus collision mechanisum). By setting it an output you avoid this problem.

/Lars

I couldn’t get this project to compile with Win AVR. Where is config.h?

Yes, I forgot to make a comment about config.h. It is where I place a define like:

#define F_CPU 8E6

which is needed by the avr-libc delay functions. You can provide that define in the makefile also if you prefer (i.e., a -DF_CPU= added to CFLAGS).

/Lars

I’ve got a doubt about (:wink:) the code. I’m not very familiar with C programming.

What I don’t now is the meaning of:

cfg=channel<<1

The problem is not with that instruction, but with the operand (<<). What does it mean?It’s a shift?

THX

Hi, It’s me again!!

Lajon: colud you explian me what you do in “trf24gSend”. I can’t understand it… above all because of the variables with the *

And then you transmit *a++ . What’s that??:?: (you increase *a and transmit it?? why do you increase *a??)

Hope I’m not bothering.

Thanks again

SebaS:
I’ve got a doubt about (:wink:) the code. I’m not very familiar with C programming.

What I don’t now is the meaning of:

cfg=channel<<1

The problem is not with that instruction, but with the operand (<<). What does it mean?It’s a shift?

THX

Yes, it’s standard C for a left shift.

Leon

SebaS:
Hi, It’s me again!!

Lajon: colud you explian me what you do in “trf24gSend”. I can’t understand it… above all because of the variables with the *

And then you transmit *a++ . What’s that??:?: (you increase *a and transmit it?? why do you increase *a??)

Hope I’m not bothering.

Thanks again

You need to learn C, try reading K&R.

Leon

Lars,

It will not compile for me. I get the following:

vr-gcc -g -Wall -Os -mmcu=atmega32 -Wl -o trf24g.elf trf24g.o

C:/WinAVR/bin/…/lib/gcc/avr/3.4.3/…/…/…/…/avr/lib/avr5/crtm32.o(.init9+0x0): undefined reference to `main’

make.exe: *** [trf24g.elf] Error 1

Process Exit Code: 2

I can not find the undefined reference to ‘main’. This is just beyond my understanding of gcc and its libraries. Do you know?

You haven’t provided a main function.

Leon

Indeed, the code above does not provide a main program. It can be very simple if you just plan to have a one way transmisson. So a sender main() can look like this:

   trf24gInit(); 
   while(1) { 
       char buffer[TRF24G_PAYLOAD_SIZE]; 
       /* put something in the message buffer here */ 
       trf24gSend(buffer); 
       _delay_ms((TRF24G_TIME_ON_AIR + 999) / 1000); /* round up to ms */ 
   }

The sender trf24g_config.h should have:

#define TRF24G_RX 0

A receiver main() would be:

    trf24gInit(); 
    while(1) { 
        char buffer[TRF24G_PAYLOAD_SIZE]; 
        if (trf24gReceive(buffer)) { 
            /* Do something with the message here */ 
        } 
    }

The receiver trf24g_config.h should have:

#define TRF24G_RX 1

and the rest of the configuration defines must match the sender (except the PORT, PIN, DDR and mask #defines which must match the hardware).

/Lars

I see. Now, it makes sense.

JEL

Sebas: *a++ increments a and transmits *a. It’s a post-increment so it trasmits *a and then increments a to point to the next thing. *++a would increment a and then transmit the thing a points to after the increment.

This only works because ++ has a higher precedence than *.

Lars,

First, let me thank you for writing and distributing this excellent code for the 2401. Studying it has been an education in C programming. Once I understood it, I have set out to adapt it to my application. In so doing I have run into a perplexing problem that I hoped you might be willing to hazard an opinion on.

My application is a remote controller. It repeatedly sends two 8 bit bytes that represent the position of seven switches on the controller. The transmitter uses a tiny2313 running code that bit bangs the data to the MiRF. It works fine. The receiever uses an M32. I wrote code for the receiver using a bit banging method that also works. However, I wanted to switch to using your SPI driven software because I want two way capability.

The problem is this. When I put your software into the M32 in receive mode only (my code is driving the transmitter) I see my data appear on MISO. However, the voltage of the individual bits is 100mv instead of 3.3v. All circuit components have a stable 3.3v vcc. When I look at the clock, it is 3.3v, same for CS, CE, DR1 and the configuration word when it goes from the MOSI to the MiRF via the SPI. MISO is also 3.3 volts between successive bytes. However, when I look at the individual data bits on my DSO, they are 100mv. If I put my original code for the receiver back into the very same circuit, the data looks normal. This must have something to do with the SPI, but I cannot figure out what is causing it.

The circuit consists of the power supply, the M32 and the MiRF and a MAX3232. I have driven SS high externally. MISO and MOSI are both connected to the DATA pin without resistors. CS and CE are connected to PD4 and PD5. CLK is connected to PB7, DR1 to PD2. I am running WINAVR GCC with AVRStudio 4.12 and JTAGICE

I have a DSO screen shot that shows what I have described, but I can’t post it.

Any ideas of what is happening would be greatly appreciated.

Jim Lake

I have determined that the problem is caused by connecting MOSI and MISO to the DATA pin. By disconnecting MOSI after the config is done, I get normal data. Clearly, they need to be isolated. I added the pair of resistors suggested by Nordic, and I get some strangely shaped data, but I think that is caused by capacitance on the breadboard.

Hi!

Lajon, I’ve got a question for you, since you achived the goal we are all lookign for…

Finally, a couple of months ago, I got them to work. But There’s something I can’t undestrand. I’m working with the BUS at 20MHz and the SPI baud rate is set to 1Mbps. That works fine, it also works fine at 10Mbps… Don’t look me like that I can’t believe it neither, but what sourpsise me the most is that it won’t work if the SPI baud rate is lower than 1Mbps. :shock:

I measured the Speed with a FLUKE Scope (wich is able to do that automatically) and those are the real speeds.

Any idea of what’s going? I mean, the data sheet says that the minimum input clock high is 500ns so the MAX freq is 1Mbps… wich means that at 1Mbps it would be at the edge, it would work fine at a lower speed and it won’t likely work at a higher one.

/SebaS

Sorry, I mean 500ns ------> 2Mbps

/SebaS

I’m kind of getting use to answer my own questions… Not because you guys don’t reply (you actually do it very often, almost every time :wink: )

I’ve just learn something that’s not in the data sheet or at least I couldn’t be able to find it.

Last time I’ve said that this module won’t work with the SPI speed lower than 1Mbps, and it could work fine with 10Mbps :shock: . Amazing, rigth?.. Almost freaky…

Well after hours, weeks and months of researching and testing I thought it could be a goog idea :idea: to set up a delay of 3ms after I’m done sending the config word and before setting CS low. And EUREKA!!! :smiley:

Now I’m working at any speed!!! from 10k to 10M!!!

I had a delay of a few micro-sec, then I increase it to 1ms and I were able to work at 250Kbps, but not lower, then I set 3ms and worked fine.

That idea came because I suppose it makes sense to have a delay before setting CS low, some kind of remain-high time. But I didn’t find it in the DS.

But what is still odd is the fact that this time deppend on the communication speed. :shock:

What’s your experience with that?

SebaS, just did a quick test with slower SPI speeds. I did not need any additional delays to make it work. Strange that you need that. I tried 62.5KHz and 7.8KHz. I do have the 3ms delay before doing anything that is needed (TPd2a in the data sheet).

That 10MHz speed you can have also sounds strange, fastest I got working was 2MHz and that is I belive outside the spec.

Sorry, I mean 500ns ------> 2Mbps

No I think you got it right the first time, minimum clock high (Thmin) is 500ns so the complete clock cycle should be >= 1000ns so <= 1MHz.

/Lars

I am having an odd problem with this code. I am using it in a controller that sends an 8 bit word (0x01 to 0x80) representing the position of 8 switches. I use a pair of MiRF’s. When I single step thru the code on AVRStudio with a JTAG ICE, I receive the correct bits at the receiver. However, when I run at full speed, only 5 of the switches work correctly. For two of the remaining 3, I get a 0 and for the other one, I get a 1 where I should get an 0x40. I have slowed it down by inserting a one second delay in the main loop, same behavior.

I am fresh out of ideas. I would highly appreciate any ideas, or any further troubleshooting strategies. I can post the code.

I have a UART on the board.

Jim Lake