NRF24L01+ Driver : Part 4 : The Header File


Sorry about the code formatting and spacing , the website theme completely messes with spacing and makes the code look awful and misaligned specially in the #define lines

Now it is time to start the drive code implementation. I have written this so that essentially it can work with any microcontroller as long some hardware specific functions are written and passed to the driver. I have designed it largely based around pointers for both data buffers and functions where ever it occurred to me.

The fist thing I did was make a header and C file that will be the heart and soul of my driver. The header file will contain all the register and bit definitions found in the datasheet. I also added some offset defines so that I can get to a certain register just by passing the pipe number to the functions. Some of these functions are not used at all by the driver but for my own purposes during debugging and writing of the driver, however I have left them there should you, or I,  want to play around with the driver and develop it further.

Now let me introduce the Header file section by section and at the end I will post it in its entirety. 

#include  <stdint.h>
#include <stdbool.h>

These are the only two inclusions that are needed are the Standard integer and Standard bool headers. 
Next up are the defines for all the commands which are only 12 and an additional byte for the ACTIVATE command. 

//-----------| NRF COMMANDS |----------
#define R_REGISTER 0x00  //must be ORed with a registr address to get correct command value / but since its zero then it really does nothing
#define W_REGISTER 0x20  //must be ORed with a register address to get correct command value
#define R_RX_PAYLOAD 0x61
#define W_TX_PAYLOAD 0xA0
#define FLUSH_TX 0xE1
#define FLUSH_RX 0xE2
#define REUSE_TX_PL 0xE3 //unused as of now 
#define R_RX_PL_WID 0x60
#define W_ACK_PAYLOAD 0xA8 //unused as of now
#define NOP 0xFF
#define W_TX_PAYLOAD_NACK 0xB0 // will implement soon
#define ACTIVATE 0x50
#define ACTIVATE_BYTE        0x73  // the activate command must be followed by this byte

-----------------------------------------------------------------------------------------------------------------------------

Now here are all the defines for the register addresses. Nothing magical here so not much to explain, just copied the address and name from datasheet and made a define to match.

//-----------| NRF REGISTERS |----------
#define REGISTER_MASK 0x1F
#define NRF_CONFIG  0x00
#define EN_AA       0x01
#define EN_RXADDR   0x02
#define SETUP_AW    0x03
#define SETUP_RETR  0x04
#define RF_CH       0x05
#define RF_SETUP    0x06
#define NRF_STATUS  0x07
#define OBSERVE_TX  0x08
#define CD          0x09
#define RX_ADDR_P0  0x0A
#define RX_ADDR_P1  0x0B
#define RX_ADDR_P2  0x0C
#define RX_ADDR_P3  0x0D
#define RX_ADDR_P4  0x0E
#define RX_ADDR_P5  0x0F
#define TX_ADDR     0x10
#define RX_PW_P0    0x11
#define RX_PW_P1    0x12
#define RX_PW_P2    0x13
#define RX_PW_P3    0x14
#define RX_PW_P4    0x15
#define RX_PW_P5    0x16
#define FIFO_STATUS 0x17
#define DYNPD       0x1C
#define FEATURE     0x1D


-----------------------------------------------------------------------------------------------------------------------------

Next is the bit definitions where applicable, some registers like RX_ADDR registers and Payload width registers use all bits in the register as a value so each single bit does not have a definition so there is no need for one either. But here are the register where each bit has a different purpose. No magic here either.

//---------| NRF BIT Definitions |---------------
//CONFIG Register : CONFIG
#define MASK_RX_DR  6
#define MASK_TX_DS  5
#define MASK_MAX_RT 4
#define EN_CRC      3
#define CRCO        2
#define PWR_UP      1
#define PRIM_RX     0
//ENABLE AUTO ACK Register : EN_AA
#define ENAA_P5     5
#define ENAA_P4     4
#define ENAA_P3     3
#define ENAA_P2     2
#define ENAA_P1     1
#define ENAA_P0     0
//Enabled RX Addresses Register : EN_RXADDR
#define ERX_P5      5
#define ERX_P4      4
#define ERX_P3      3
#define ERX_P2      2
#define ERX_P1      1
#define ERX_P0      0
//Setup of Address Widths Register : SETUP_AW
#define AW          0
//Setup of Automatic Retransmission Register : SETUP_RETR
#define ARD         4
#define ARC         0
//RF Channel Register :RF_CH
#define RF_CH_BITS  0
//RF Setup Register : RF_SETUP
#define PLL_LOCK    4
#define RF_DR       3
#define RF_PWR      6
#define LNA_HCURR   0
//STATUS register : STATUS
#define RX_DR       6
#define TX_DS       5
#define MAX_RT      4
#define RX_P_NO     1
#define TX_FULL     0
//Transmit observe register : OBSERVE_TX
#define PLOS_CNT    4
#define ARC_CNT     0
// CD
#define CD_BIT      0
//FIFO Status Register : FIFO_STATUS
#define TX_REUSE    6
#define FIFO_FULL   5
#define TX_EMPTY    4
#define RX_FULL     1
#define RX_EMPTY    0
//Enable dynamic payload length Register : DYNPD
#define DPL_P5     5
#define DPL_P4     4
#define DPL_P3     3
#define DPL_P2     2
#define DPL_P1     1
#define DPL_P0     0
//Feature Register : FEATURE
#define EN_DPL     2
#define EN_ACK_PAY  1
#define EN_DYN_ACK  0


-----------------------------------------------------------------------------------------------------------------------------

Now I have made some custom defines because it just easier to remember I will explain them below 

//---------------Custom Defines -----------------------
//I like this naming convention better and can apply to all the enable registers
#define PIPE_0 0x00 
#define PIPE_1 0x01
#define PIPE_2 0x02
#define PIPE_3 0x03
#define PIPE_4 0x04
#define PIPE_5 0x05
//some offsets that make fr more readability
#define RX_ADDR_OFFSET  0x0A
#define RX_PW_OFFSET    0x11
//SETUP_AW byte codes
#define THREE_BYTES 0b01
#define FOUR_BYTES  0b10
#define FIVE_BYTES  0b11
#define ENCODING_SCHEME_1_BYTE  0x00
#define ENCODING_SCHEME_2_BYTE  0x01

#define DUMMYBYTE  0xF1

I have defined a much more simple PIPE_# naming convention to use on registers to enable address or enable auto ack , or dynamic payload. If you notice all those registers the bit position matches the pipe number, but they have names like ENAA_P1 and ERX_P1 and DPL_P1 , but they all are defined to be 1 so might as well make it easier and just say PIPE_1 that way in my driver when I tell it to enable stuff PIPE_1 it can just refer to everything as PIPE_1 when it needs to figure out which PIPE to enable Dynamic Payload or RX address for etc...

Now the offset defines are to calculate the register addresses for the RX_ADDR registers this is where you write the address for a specific Pipe.  In my driver I will have a function where you tell it to set the address for a specific PIPE and you tell it what address you want and what Pipe number you want that address for and that is its, it will calculate which RX_ADDR register it goes into. 

Its easy because each RX_ADDR registers is 10 (0x0A) away from the PIPE number it corresponds to. The RX_ADDR register for Pipe 5 is located at RX_ADDR_P5 which is address 15. 
So as you can see the Pipe number plus 10... (5 + 10 = 15 )(PIPE_# + RX_ADDR_OFFSET)

The next 3 defines are to be used with the structure for telling it your desired address width, it just makes it easier to read than passing those random numbers.
Same applies for the encoding defines

And finally the DUMMYBYTE  is just a define used when I need to send a dummy byte via SPI because remember when you read from a register, you first send the command to read the register then you need to clock in some data so that the device can -send you the data you want to read, the data you send doesnt matter, what matters is that you are clocking the SPI ,  hence dummy byte,

-----------------------------------------------------------------------------------------------------------------------------

Check out all those function pointers below.

//functions pointers... duh
typedef void(*fptr)(); //you will use 4 of these
typedef void(*spiSend_ptr)(uint8_t data);
typedef void(*spiSednMultiByte_ptr)(uint8_t * data, uint32_t len, uint8_t *rx_buff);
typedef uint8_t(*spiRead_ptr)();

typedef void(*actAsRx_ptr)(bool state);
typedef void(*tx_data_ptr)(uint8_t *data, uint8_t len); 
typedef void(*tx_set_addr_ptr)(uint32_t addr_high, uint8_t addr_low, bool auto_ack); 
typedef void(*rx_data_ptr)(uint8_t *data, uint8_t len); 
typedef void(*rx_set_addr_ptr)(uint8_t rx_pipe, uint32_t addr_high, uint8_t addr_low); 
typedef uint8_t(*getStatus_ptr)();
typedef uint8_t(*getpayload_width_ptr)();
typedef uint8_t(*getpipe_current_pl_ptr)();

These functions pointers are to be used as elements in a structure hence why decided better to make them typedefs and just use the pointer names in the struct.  
The first 4 are functions you will write on your own, or copy mine if you are using an STM32. Those are for hardware specific functions concerning SPI communication.
  • An SPI send function to send one byte of data via SPI
  • An SPI send multiple bytes function, will be a copy of the previous SPI send function but inside a loop to send multiple times
  • And an SPI read function to read from SPI
  • Finally the fptr function pointer will be used for 4 functions. Those functions are just one line long used to toggle the pins.
    • CE toggle HIGH
    • CE toggle LOW
    • CSN toggle HIGH
    • CSN toggle LOW
The rest of the function pointers are for user functions to control the NRF they will also be used as structure elements and will be passed functions with similar names , read the names carefully and that should hint at what the function will do. 
I chose to use function pointers because that way you do not have to go into the driver and make any changes. All the settings and anything that is specific to your chip   can be done from outside the driver in your main application and thus making this driver a little more portable to any platform. Or so i think but shit im not genius here just a guy with a keyboard and a computer. 

-----------------------------------------------------------------------------------------------------------------------------

Holy COW get ready for this big ass structure. This is the heart and sole of the driver. The big momma! the Kobe versus Jordan , Steve Jobs  versus Bill Gates, Tupac versus Biggie, STM32 versus ARM....wait wtf?

typedef struct //user structure to setup and control the NRF24
{
//common settings
bool set_enable_crc;                 // true / false
uint8_t set_crc_scheme; // ENCODING_SCHEME_1_BYTE / ENCODING_SCHEME_2_BYTE
bool set_enable_auto_ack; // true / false
uint8_t set_rf_channel; // 0 - 126  (2.400GHz to 2.525GHz)(2400 + RF_CH)
uint8_t set_address_width; // THREE_BYTES / FOUR_BYTES / FIVE_BYTES
bool set_enable_dynamic_pl_width; // true / false

//rx settings
bool set_enable_rx_mode; // true / false 
uint8_t set_rx_pipe; // PIPE_1 / PIPE_2 / PIPE_3 .. . .. 
uint8_t set_rx_addr_byte_1; // low byte will go in RX_ADDR_P#
uint32_t set_rx_addr_byte_2_5; // high byte will go in Pipe 1
uint8_t set_payload_width; // 1 - 32 
bool set_enable_rx_dr_interrupt; // true / false

//tx settings
bool set_enable_tx_mode; // true / false
uint8_t set_tx_addr_byte_1; // low byte will go in TX_ADDR register
uint32_t set_tx_addr_byte_2_5; // high byte low byte will go in TX_ADDR register 
// and PIPE 0 if auto ack is enabled
bool set_enable_max_rt_interrupt; // true / false
bool set_enable_tx_ds_interrupt; // true / false
//commands : function points that can be used as function calls
fptr cmd_clear_interrupts;
getStatus_ptr cmd_get_status;
rx_set_addr_ptr cmd_set_rx_addr;
tx_set_addr_ptr cmd_set_tx_addr;
fptr            cmd_listen;  
getpayload_width_ptr       cmd_get_payload_width;
getpipe_current_pl_ptr     cmd_get_pipe_num_current_pl;
rx_data_ptr cmd_read_payload;
tx_data_ptr cmd_transmit;
actAsRx_ptr cmd_act_as_RX;
fptr                       cmd_flush_rx;
fptr                       cmd_flush_tx;

//hardware specific functions needed : user has no need to call these : for driver use only
spiSend_ptr spi_spiSend;
spiSednMultiByte_ptr spi_spiSendMultiByte;
spiRead_ptr spi_spiRead;

fptr pin_CE_HIGH;
fptr pin_CE_LOW;
fptr pin_CSN_HIGH;
fptr pin_CSN_LOW;
}CL_nrf24l01p_init_type;

OK lets analyze this bad boy part by part. First of all this is the structure you will make an instance of in your main application and set the things you want to set and all that good stuff. I have commented acceptable settings for each element. 

The common settings are settings required if it is acting as a transmitter or receiver. You can also have it act as both and set everything just make sure you only use one mode at a time and switch back and forth, ill explain that in a second.

The next section is RX settings where you set RX related stuff. The set_enable_rx_mode  is a boolean that will be true or false. This does not set it as a RX receiver but it only lets the driver  know to go ahead and set all the RX related settings in the device, if this is false then it will ignore all the RX setting even if you fill them out.

Recall the the device has a minimum of 3 bytes for address assigning even though PIPEs 2 ,3 ,4 ,5 only have 1 byte register for addresses, that is because it uses the HIGH bytes from PIPE 1. So in the address settings set_rx_addr_byte_1 will go in your desired PIPE and set_rx_addr_byte_2_5 will go into PIPE1 to complete your address , given your setting for set_address_width which takes the values commented next to it. You can also enable the data received interrupt.

The next section is the TX settings. The same logic applies with the enable setting. The address settings are also split into high and low bytes. If auto ack is enabled that means the same address will go in TX_ADDR and PIPE 0 because that is the PIPE the transmitter will listen on for the auto ack from the receiver. And you can also enabled the TX related interrupts.

The following section is the command section. These are the only commands that you should have to call from your main application unless you are doing some debugging. The names are pretty self explanatory but I will go through their functions anyways, and not to mention we will write the functions for these commands so you will know what they do anyways.
  1. cmd_clear_interrupts : This is a global interrupt clearing command. It just clears all interrupts whether they are RX or TX related. (remember I am talking about clearing interrupts for the NRF not your MCU)
  2. cmd_get_status : Returns the contents of the STATUS register, this is what will help you determine what interrupt fired, and take your action accordingly. Then after this you would call the previous command to clear it.
  3. cmd_set_rx_addr : It may be the case where you may need to change the address of a certain pipe for some reason and you could do so with this command.
  4. cmd_set_tx_addr : It may also be the case you are transmitting to multiple receivers , or maybe on receiver but multiple pipe addresses, so you will need to change the address of the current transmit packet constantly, you do so with this command. For this command and the previous one , they accept certain parameter and I will explore them when we write the functions that go with these commands.
  5. cmd_listen : Puts the NRF in a listening state when  acting as a receiver. It needs to be "listening" to receiver data, otherwise it will ignore the airways. 
  6. cmd_get_payload_width : Used to retrieve the width of the current received payload when using dynamic payload. 
  7. cmd_get_pipe_num_current_pl : this will return the pipe number for which the current received payload was intended for
  8. cmd_read_payload : Once data has arrived you use this command to retrieve your payload data.To retrieve the data the NRF has to stop listening and this command handles that. You need to call the listen command again to start listening if you wish to do so.
  9. cmd_transmit : This command is responsible for transmitting your payload to the address specified in the TX_ADDR , its parameters include a pointer to your data
  10. cmd_act_as_RX : This randomly placed command is the one that tells the NRF to act as a receiver (true)  or a transmitter (false)
  11. cmd_flush_rx cmd_flush_tx : These two commands clear the FIFOs for rx and tx , any unread payload data is erased.
The Next section are function pointers that are responsible for the SPI functions. Followed by a section for functions controlling the pins of the NRF which are  CE (Chip Enable) CSN (SPI slave select).

-----------------------------------------------------------------------------------------------------------------------------

So let me explain my thought process for the next part of the header. These function pointers that all point to functions written by the main application and not inside the header itself are all critical for the driver to work. The driver needs to use all these functions to do its job. In order for the internal functions of the driver to access these function pointers it needs to access this big ass structure of mine. So that means every inner function will need to be passed this big ass structure in order to access these function pointers.

Well I did not want to do that. So what I did was made a much smaller structure, a global one.  And that smaller structure only purpose is to also hold these function pointers. So when I initialize my NRF device , I pass these pointers from the big structure to the little one for internal use only. And here it is:

//this is an internal structure to control and call external functions, i find it much simpler
//and perhaps more efficient, otherwise I would need to pass my HUGE ASS main structure to all the internal
//functions. so i chose to do it this way.

typedef struct 
{
spiSend_ptr spiSend;
spiSednMultiByte_ptr spiSendMultiByte;
spiRead_ptr spiRead;

fptr NRF_CE_HIGH;
fptr NRF_CE_LOW;
fptr NRF_CSN_HIGH;
fptr NRF_CSN_LOW;

}NRF_type;
NRF_type NRF;

-----------------------------------------------------------------------------------------------------------------------------

Finally the header file ends with my function prototypes. None of these functions will be called from the user application unless you feel like messing around or exploring the registers. I will discuss their use as we begin to write them. They are all pretty small , 10 lines or less aside from the init function, in-fact most functions are helper functions for the init function. All this will be seen in future posts. 

//------------------------------------------------------------------------
//----------------|
Function Prototypes |----------------------------------
uint8_t  NRF_cmd_read_single_byte_reg(uint8_t reg); 
void NRF_cmd_read_multi_byte_reg(uint8_t reg, uint8_t numBytes, uint8_t *buff);
void NRF_cmd_write_5byte_reg(uint8_t reg, uint8_t value); 
void NRF_cmd_modify_reg(uint8_t reg, uint8_t bit, uint8_t state); 
void NRF_cmd_write_entire_reg(uint8_t reg, uint8_t value); 
uint8_t NRF_cmd_get_pipe_current_pl(void);
uint8_t  NRF_cmd_read_dynamic_pl_width(void);
uint8_t NRF_cmd_get_status(void);
 
void NRF_cmd_read_RX_PAYLOAD(uint8_t *data, uint8_t len);
void NRF_cmd_write_TX_PAYLOAD(uint8_t *data, uint8_t len);
void NRF_cmd_setup_addr_width(uint8_t width); //
void NRF_cmd_FLUSH_TX(void);
void NRF_cmd_FLUSH_RX(void);
void NRF_cmd_reuse_TX_PL(void);
void NRF_cmd_activate(void);
void NRF_cmd_listen(void);
void NRF_cmd_clear_interrupts(void);
void NRF_cmd_act_as_RX(bool state);
void NRF_set_tx_addr(uint32_t addr_high, uint8_t addr_low , bool auto_ack); 
void NRF_set_rx_addr(uint8_t rx_pipe, uint32_t addr_high, uint8_t addr_low);
void NRF_init(CL_nrf24l01p_init_type *nrf_type);

Here is the entire header file. sorry about the formating. The blog theme messes with it
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
#ifndef CL_nrf24l01p_H_
#define CL_nrf24l01p_H_

#include  <stdint.h>
#include <stdbool.h>


//-----------| NRF COMMANDS |----------
#define R_REGISTER			0x00  //must be ORed with a registr address to get correct command value / but since its zero then it really does nothing
#define W_REGISTER			0x20  //must be ORed with a register address to get correct command value
#define R_RX_PAYLOAD		0x61
#define W_TX_PAYLOAD		0xA0
#define FLUSH_TX			0xE1
#define FLUSH_RX			0xE2
#define REUSE_TX_PL			0xE3 //unused as of now 
#define R_RX_PL_WID			0x60
#define W_ACK_PAYLOAD		0xA8 //unused as of now
#define NOP					0xFF
#define W_TX_PAYLOAD_NACK	0xB0 // will implement soon

#define ACTIVATE			0x50
#define ACTIVATE_BYTE       0x73  // the activate command must be followed by this byte

//-----------| NRF REGISTERS |----------
#define REGISTER_MASK 0x1F
#define NRF_CONFIG  0x00
#define EN_AA       0x01
#define EN_RXADDR   0x02
#define SETUP_AW    0x03
#define SETUP_RETR  0x04
#define RF_CH       0x05
#define RF_SETUP    0x06
#define NRF_STATUS  0x07
#define OBSERVE_TX  0x08
#define CD          0x09
#define RX_ADDR_P0  0x0A
#define RX_ADDR_P1  0x0B
#define RX_ADDR_P2  0x0C
#define RX_ADDR_P3  0x0D
#define RX_ADDR_P4  0x0E
#define RX_ADDR_P5  0x0F
#define TX_ADDR     0x10
#define RX_PW_P0    0x11
#define RX_PW_P1    0x12
#define RX_PW_P2    0x13
#define RX_PW_P3    0x14
#define RX_PW_P4    0x15
#define RX_PW_P5    0x16
#define FIFO_STATUS 0x17
#define DYNPD       0x1C
#define FEATURE     0x1D


//---------| NRF BIT Definitions |---------------
//CONFIG Register : CONFIG
#define MASK_RX_DR  6
#define MASK_TX_DS  5
#define MASK_MAX_RT 4
#define EN_CRC      3
#define CRCO        2
#define PWR_UP      1
#define PRIM_RX     0
//ENABLE AUTO ACK Register : EN_AA
#define ENAA_P5     5
#define ENAA_P4     4
#define ENAA_P3     3
#define ENAA_P2     2
#define ENAA_P1     1
#define ENAA_P0     0
//Enabled RX Addresses Register : EN_RXADDR
#define ERX_P5      5
#define ERX_P4      4
#define ERX_P3      3
#define ERX_P2      2
#define ERX_P1      1
#define ERX_P0      0
//Setup of Address Widths Register : SETUP_AW
#define AW          0
//Setup of Automatic Retransmission Register : SETUP_RETR
#define ARD         4
#define ARC         0
//RF Channel Register :RF_CH
#define RF_CH_BITS  0
//RF Setup Register : RF_SETUP
#define PLL_LOCK    4
#define RF_DR       3
#define RF_PWR      6
#define LNA_HCURR   0
//STATUS register : STATUS
#define RX_DR       6
#define TX_DS       5
#define MAX_RT      4
#define RX_P_NO     1
#define TX_FULL     0
//Transmit observe register : OBSERVE_TX
#define PLOS_CNT    4
#define ARC_CNT     0
// CD
#define CD_BIT      0
//FIFO Status Register : FIFO_STATUS
#define TX_REUSE    6
#define FIFO_FULL   5
#define TX_EMPTY    4
#define RX_FULL     1
#define RX_EMPTY    0
//Enable dynamic payload length Register : DYNPD
#define DPL_P5	    5
#define DPL_P4	    4
#define DPL_P3	    3
#define DPL_P2	    2
#define DPL_P1	    1
#define DPL_P0	    0
//Feature Register : FEATURE
#define EN_DPL	    2
#define EN_ACK_PAY  1
#define EN_DYN_ACK  0


//---------------Custom Defines -----------------------
//I like this naming convention better and can apply to all the enable registers
#define PIPE_0	0x00 
#define PIPE_1	0x01
#define PIPE_2	0x02
#define PIPE_3	0x03
#define PIPE_4	0x04
#define PIPE_5	0x05

//some offsets that make fr more readability
#define RX_ADDR_OFFSET  0x0A
#define RX_PW_OFFSET    0x11

//SETUP_AW byte codes
#define THREE_BYTES 0b01
#define FOUR_BYTES  0b10
#define FIVE_BYTES  0b11

#define ENCODING_SCHEME_1_BYTE  0x00
#define ENCODING_SCHEME_2_BYTE  0x01


#define DUMMYBYTE  0xF1

//functions pointers... duh
typedef		void(*fptr)(); //you will use 4 of these
typedef		void(*spiSend_ptr)(uint8_t data);
typedef		void(*spiSednMultiByte_ptr)(uint8_t * data, uint32_t len, uint8_t *rx_buff);	
typedef		uint8_t(*spiRead_ptr)();

typedef		void(*actAsRx_ptr)(bool state);
typedef		void(*tx_data_ptr)(uint8_t *data, uint8_t len); 
typedef		void(*tx_set_addr_ptr)(uint32_t addr_high, uint8_t addr_low, bool auto_ack); 
typedef		void(*rx_data_ptr)(uint8_t *data, uint8_t len); 
typedef		void(*rx_set_addr_ptr)(uint8_t rx_pipe, uint32_t addr_high, uint8_t addr_low); 
typedef		uint8_t(*getStatus_ptr)();
typedef		uint8_t(*getpayload_width_ptr)();
typedef		uint8_t(*getpipe_current_pl_ptr)();


typedef struct //user structure to setup and control the NRF24
{
	//common settings
	bool		set_enable_crc;                 // true / false
	uint8_t		set_crc_scheme;					// ENCODING_SCHEME_1_BYTE / ENCODING_SCHEME_2_BYTE
	bool		set_enable_auto_ack;			// true / false
	uint8_t		set_rf_channel;					// 0 - 126  (2.400GHz to 2.525GHz)(2400 + RF_CH)
	uint8_t		set_address_width;				// THREE_BYTES / FOUR_BYTES / FIVE_BYTES
	bool		set_enable_dynamic_pl_width;	// true / false
	
	//rx settings
	bool		set_enable_rx_mode;				// true / false 
	uint8_t		set_rx_pipe;					// PIPE_1 / PIPE_2 / PIPE_3 .. . .. 
	uint8_t		set_rx_addr_byte_1;				// low byte will go in RX_ADDR_P#
	uint32_t	set_rx_addr_byte_2_5;			// high byte will go in Pipe 1
	uint8_t		set_payload_width;				// 1 - 32 
	bool		set_enable_rx_dr_interrupt;		// true / false
	
	//tx settings
	bool		set_enable_tx_mode;				// true / false
	uint8_t		set_tx_addr_byte_1;				// low byte will go in TX_ADDR register
	uint32_t	set_tx_addr_byte_2_5;			// high byte low byte will go in TX_ADDR register 
												// and PIPE 0 if auto ack is enabled	
	bool		set_enable_max_rt_interrupt;	// true / false
	bool		set_enable_tx_ds_interrupt;		// true / false	
	//commands : function points that can be used as function calls
	fptr					cmd_clear_interrupts;
	getStatus_ptr			cmd_get_status;
	rx_set_addr_ptr			cmd_set_rx_addr;
	tx_set_addr_ptr			cmd_set_tx_addr;
	fptr					cmd_listen;  
	getpayload_width_ptr    cmd_get_payload_width;
	getpipe_current_pl_ptr  cmd_get_pipe_num_current_pl;
	rx_data_ptr				cmd_read_payload;
	tx_data_ptr				cmd_transmit;	
	actAsRx_ptr				cmd_act_as_RX;
	fptr                    cmd_flush_rx;
	fptr                    cmd_flush_tx;	
	
	//hardware specific functions needed : user has no need to call these : for driver use only
	spiSend_ptr				spi_spiSend;
	spiSednMultiByte_ptr	spi_spiSendMultiByte;
	spiRead_ptr				spi_spiRead;
	
	fptr					pin_CE_HIGH;
	fptr					pin_CE_LOW;
	fptr					pin_CSN_HIGH;
	fptr					pin_CSN_LOW;
}CL_nrf24l01p_init_type;




//this is an internal structure to control and call external functions, i find it much simpler
//and perhaps more efficient, otherwise I would need to pass my HUGE ASS main structure to all the internal
//functions. so i chose to do it this way.
typedef struct 
{
	spiSend_ptr				spiSend;
	spiSednMultiByte_ptr	spiSendMultiByte;
	spiRead_ptr				spiRead;
	
	fptr					NRF_CE_HIGH;
	fptr					NRF_CE_LOW;
	fptr					NRF_CSN_HIGH;
	fptr					NRF_CSN_LOW;
	
}NRF_type;

NRF_type NRF;


//------------------------------------------------------------------------
//----------------| Function Prototypes |----------------------------------

uint8_t  NRF_cmd_read_single_byte_reg(uint8_t reg); 
void NRF_cmd_read_multi_byte_reg(uint8_t reg, uint8_t numBytes, uint8_t *buff);
void NRF_cmd_write_5byte_reg(uint8_t reg, uint8_t value); 
void NRF_cmd_modify_reg(uint8_t reg, uint8_t bit, uint8_t state); 
void NRF_cmd_write_entire_reg(uint8_t reg, uint8_t value); 

uint8_t NRF_cmd_get_pipe_current_pl(void);
uint8_t  NRF_cmd_read_dynamic_pl_width(void);
uint8_t NRF_cmd_get_status(void);
void NRF_cmd_write_TX_ADDR(uint8_t *addr, uint8_t len); 
void NRF_cmd_read_RX_PAYLOAD(uint8_t *data, uint8_t len);
void NRF_cmd_write_TX_PAYLOAD(uint8_t *data, uint8_t len);
void NRF_cmd_setup_addr_width(uint8_t width);
void NRF_cmd_FLUSH_TX(void);
void NRF_cmd_FLUSH_RX(void);
void NRF_cmd_reuse_TX_PL(void);
void NRF_cmd_activate(void);
void NRF_cmd_listen(void);
void NRF_cmd_clear_interrupts(void);
void NRF_cmd_act_as_RX(bool state);


void NRF_init(CL_nrf24l01p_init_type *nrf_type);
void NRF_set_tx_addr(uint32_t addr_high, uint8_t addr_low , bool auto_ack); 
void NRF_set_rx_addr(uint8_t rx_pipe, uint32_t addr_high, uint8_t addr_low);

#endif
Ok in the next post I will very briefly go through the SPI functions, though this is not an SPI tutorial so I will not explain the registers or how to set it up. I am also using the LL api for this but feel free to watch my SPI tutorial where I set it up via the register : HERE



<< PREVIOUS | NEXT >>












Comments

Share your comments with me

Archive

Contact Form

Send