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.
- 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)
- 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.
- 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.
- 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.
- 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.
- cmd_get_payload_width : Used to retrieve the width of the current received payload when using dynamic payload.
- cmd_get_pipe_num_current_pl : this will return the pipe number for which the current received payload was intended for
- 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.
- 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
- 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)
- 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
Comments