Nrf24l01 Tutorial 1 Pic18
Nrf24l01 Tutorial 1 Pic18
Nrf24l01 Tutorial 1 Pic18
Introduction
Hopefully youve made it through Tutorial 0 and now you have a pretty good grasp on the basic operation of the nRF24L01. In this tutorial, well actually get into doing some hardware and software instead of a bunch of boring old reading so you can get a nifty little RF link up and running.
To program these little gems, Im using the PIC-MCP-USB, also from Sparkfun. It runs around $92, but it is powered from the USB port and can do in-circuit serial programming (ICSP), which is really nice. The unit can also be seen in Figure 1.
As I mentioned, you must connect one of your microcontrollers to your computer over a serial port (RS232). You will need some type of level conversion for this to work, and I personally use MAX3232 chip, which allows the user to run at 3.3V or 5V and still get a working RS232 link. Since the PIC-P40 development board has this circuit built-in, I simply wired it to the PICs USART (more on connections later). I have also included information on connecting such a circuit up if you dont have one on your board (see the Pin Connections to the MAX3232 Level Converter section for details).
(2) CE -> uC pin PORTC.1, configured as GPIO/input (3) CSN -> uC pin PORTC.2, configured as GPIO/output (4) SCK -> uC pin PORTC.3/SCK, configured as SCK(SPI)/output (5) MOSI -> uC pin PORTC.5/SDO, configured as SDO(SPI)/output (6) MISO -> uC pin PORTC.4/SDI, configured as SDI(SPI)/input (7) IRQ -> uC pin PORTB.0, configured as GPIO/input (8) GND -> common digital ground I apologize for the ASCII wiring hardware connections. I dont have a circuit CAD program handy, but since there arent a terribly large amount of pins to connect, I think that you should probably be able to handle the task.
can use whatever programmer you like to program the PIC (I personally use the PICMCP-USB from Olimex). If you would like make changes to the code and compile the tutorial yourself, simply press the Build All button in MPLAB. If you have all of the necessary tools working properly, you should have a new set of .hex files in the project directory that you can upload.
NRF24L01.h and .c
Since the source files in the nRF24L01 library are pretty well commented, I dont really want to use this space to write redundant information. Rather, what I do want to do here is to give you an outline of the normal procedures of using my library in your own projects. There are a few prerequisites that you need for this library to work. First you need a function that will delay a specified number of microseconds. Also, you need a function to operate the SPI bus. This function takes a byte as an argument, sends that byte over SPI, receives a byte from the 24L01 over SPI, and then returns this received byte. Also, there is a section in the header file where you must make defines for the IO control registers and pin masks for each of the IO pins that go to the 24L01 (CSN, CE, and IRQ). When you get the support code working and move onto your actual useful code, you will start off by initializing the nRF24L01. I have included three initialize functions, and they serve different purposes and are targeted for different users. The first and most useful is nrf24l01_initialize(). It allows you to initialize the values to every writeable register in the 24L01. It also allows you to set whether or not you set the CE pin when the 24L01 is set up as a receiver. To get a simple connection up and running, use the nrf24l01_initialize_debug() function. It limits you to using pipe 0, but gives you the options of selecting RX/TX, payload width, and Enhanced Shockburst. The third init function is nrf24l01_initialize_debug_lite(). It doesnt initialize all of the registers, but instead sets up only the CONFIG and RX_PW_P0 registers and leaves all other registers in whatever state theyre already in (Enhanced Shockburst is left on). Both of the debug versions of the init functions set CE if the device is going to be a receiver. After the init function has been called, the device will either be a receiver or a transmitter. If the device is a receiver and CE is high, then you will be monitoring
packets 130 uS after the CE pin was set (this is important to remember for a transmitter that is sending packets to it). At this point, you have two options in your microcontroller: interrupts and polling. If you are using interrupts, then you will more than likely put a call to nrf24l01_read_rx_payload()in your interrupt service routine. If you are polling, you should first check the function nrf24l01_irq_pin_active() and then check the function irq_rx_dr_active() to see if you have received a packet. Then you would call nrf24l01_read_rx_paylaod() to get the packet. Once you read the packet in either mode, you should call nrf24l01_irq_clear_rx_ds() or nrf24l01_irq_clear_all() to clear the RX_DS interrupt. In the case that you are using more than one pipe, once you receive a packet, you can check the status register to see what pipe it was received on. The easiest way to do this is to set a variable equal to the nrf24l01_read_rx_payload() function because this function returns the status register at the time of the payload read. Then take you take this variable and give it to the nrf24l01_get_pipe_from_status() function as an argument to find what pipe you received your packet on. Remember that once you read the packet out of the FIFO, you wont be able to get the pipe it came on. It is important to do this either before you call nrf24l01_read_rx_payload() or use the return value of the function as was just described. If the 24L01 was made a TX after initialization, then you have a slightly different road to follow. In normal operation, when you decide you want to send a packet, you call the nrf24l01_write_tx_payload() function. If you are using a very high data rate, you should check the to make sure the TX FIFO isnt full before you send the payload over SPI (this is available in both the STATUS and FIFO_STATUS registers). Once you send the payload, you wait for the TX_DS interrupt to go active before you send another packet, which is similar to waiting for RX_DR with an RX. If youre using the Enhanced Shockburst feature, you also need to watch for the MAX_RT interrupt in case your packet never makes it to the receiver. You also need to clear the interrupts when you get them, just as with an RX. Just as a hint, remember that anytime a receiver calls the nrf24l01_read_rx_payload() function, it needs up to 130 uS to come back to active mode before it starts monitoring for packets again. You need to take this into account with your transmitter and allow for a 130 uS delay when appropriate or else you *may* lose packets. I have written code before that didnt have the delay and still worked. In my experience I have found that this delay seems to be more important to include when you are using Enhanced Shockburst than regular Shockburst. If in some time during your wireless transceiving you need to change a device from a TX to an RX or vice versa, there are also functions for this. These are, surprisingly enough, nrf24l01_set_as_tx() and nrf24l01_set_as_rx(). There are also parameterized versions of these functions that allow you to change the contents of the whole CONFIG register. Many devices will want to conserve battery power and keep the 24L01 powered down. The functions nrf24l01_power_up() and nrf24l01_power_down() make easy work of this task by keeping all of the registers in their current state except for the PWR_UP bit in the CONFIG register. They also do all of the timing and other operations for you. As with the mode change functions, these functions also have parameterized versions that allow you to change the entire CONFIG register.
A very useful function for debugging is the nrf24l01_get_all_registers() function. For this guy, you simply pass it an array of 36 bytes and it will read every register in the 24L01. I use it very often when I am writing new code for the library and trying to debug new functions to make sure they are actually doing what I want them to do. The only thing to remember with this function is that it gets all five of the three multi-byte registers (TX_ADDR, RX_ADDR_P0, and RX_ADDR_P1). Most of the other functions in the nRF24L01 library have somewhat more sparse uses, so I wont be discussing them in this tutorial. All of the functions are well commented in the .c file, so feel free to look there to see what each function does and if it will solve a specific problem for you.
Now we wait until the packet has been sent. This is done by first monitoring the IRQ pin itself with the nrf24l01_irq_pin_active() function, and then when this function is true, we check to see if the TX_DS interrupt is active. If both of these are true, we know that we have sent the packet and we can then clear the interrupts with the nrf24l01_clear_all() function. Next, we want to get back the character that we just sent to the other 24L01 over RF. Therefore, we call the nrf24l01_set_to_rx() function to change the local 24L01 to a receiver. This function has one argument, and it is true to set the device into RX mode (CE high) and false to set the device to standby mode (CE low). In this call, we will put the device into RX mode so it will monitor the air. The next thing we must do is wait until the RX_DR interrupt is active. However, we dont want to wait forever if the packet doesnt make it back to us. Therefore, I have put in a for loop that will allow the unit to check 25,000 times to see if the interrupt is active (I dont know how long this goes, but I am guessing on the order of milliseconds). If we successfully read the IRQ pin and the RX_DS interrupt both being active, we read the payload, put the character into the data variable, and then exit out of the for loop. If we reach the last iteration and we still havent received the character, we set data to a question mark to indicate that the character was not received. Next up, we once again clear our interrupts and then print what we set the data variable to be in the previous for loop to the screen. Next, we need to wait on the remote unit to get back into active RX mode. This is the purpose of the 130 uS delay (see Table 13 in the 24L01 datasheet). Finally, we toggle the status of the on-board LED to show that all the data operations for this particular character session have been completed. We then go back to the beginning of the loop to check for USART characters again. The other function of interest is the Initialize() function. This function sets up the IO pins in the PIC18F452, as well as opens the USART and SPI sections for communications. Finally, it sets up the 24L01 using the nrf24l01_initialize_debug() function.
As mentioned before, the Initialize() routine in this file is slightly different from that in maintutorial1local.c. There is no call to OpenUSART(), because we are not using USART for this node. The other difference is the call to nrf24l01_initialize_debug(). Here, the first argument is false to tell the 24L01 to be a TX.
Figure 3. Setting up HyperTerminal to send CR/LF pairs when pressing the Enter key
Concluding Remarks
At this point, you should have a working link that you can use in future tutorials and in your own projects. You should also now have a basic understanding of the flow of the more important functions in the nRF24L01 library. If you have any questions, feel free to email me at brennen@diyembedded.com.
Disclaimer: The author provides no guarantees, warrantees, or promises, implied or otherwise. By using the software in this tutorial, you agree to indemnify the author of any damages incurred by using it. You also agree to indemnify the author against any personal injury that may come about using this tutorial. The plain English version Im doing you a favor by trying to help you out, so take some responsibility and dont sue me!