NRF24L01+ Transceiver
- The nRF24L01+ is a single-chip 2.4GHz transceiver operating at the ISM frequency band at 2.400 – 2.4835GHz.
- The radio front end uses GFSK modulation.
- 126 RF channels.
- Supports an air data rate of 250 kbps, 1 Mbps and 2Mbps.
- Enhanced ShockBurstTM supports
- 1 to 32 bytes dynamic payload length.
- Automatic packet handling.
- Auto packet transaction handling.
- 6 data pipe MultiCeiverTM for 1:6 star networks.
- Host Interface – 4-pin hardware SPI.
- 3 separate 32 bytes TX and RX FIFOs.
- 2 devices is required in order to transmit or receive data with each other.
Linux device driver
Steps needed to write SPI client drivers.
- Declare device IDs supported by the driver using spi_device_id or of_device_id.
- Register the SPI client driver with SPI core or OF core with MODULE_DEVICE_TABLE().
- Implementing probe and remove functions, and filling a struct spi_driver structure.
- Implementing init and exit functions and registering with module_init() and module_exit().
Steps performed in the driver init.
- Dynamically reserve a major and a range of minors with alloc_chrdev_region().
- Create a class for our device with class_create(), visible as /sys/class/transceiver.
- Register the SPI device driver using spi_register_driver().
Steps performed in the driver probe.
- Initializing regmap and implementing custom read/write functions.
- Reading device address to make sure communication with the device is successful.
- Allocating memory for the device’s private data structure.
- Register the device with the system using device_register().
- Initialize GPIO’s for CE and IRQ. Configure interrupt by registering an interrupt handler.
- Defining two wait queues for the Tx thread.
- Initialize a kernel FIFO for handling Tx data.
- Creating worker threads for handling ISR and Rx data.
- Creating a sub-device and character file for pipe0 with a unique minor number.
- Defining open, read, write, and poll file operations for the character file.
- Initialize a kernel FIFO for handling pipe0 Rx data.
- Defining two wait queues for the Rx thread.
- Configuring NRF24l01p device with following
- 2-byte CRC.
- Auto Retransmission Delay – 4000us, Auto Retransmit Count – 15.
- Power 2dBm and data rate 2mbps.
- Enable auto ack.
- Enable dynamic payload.
- Enable Pipe0 for receiving.
- Flush RX and Tx FIFO.
- Power up.
- After configuration NRF24l01p will be in receive mode.
- Creating and waking the kernel thread to handle Tx data.
PTX operation
- If there is a packet present in the TX FIFO the nRF24L01+ enters TX mode and transmits the packet.
- The nRF24L01+ enters RX mode to receive an ACK packet after receiving TX_DS IRQ is asserted.
Steps performed in Tx thread.
static int nrf24l01p_tx_thread(void *data)
{
struct nrf24l0_data *nrf24l01p = data;
struct nrf24l0_tx_data txdata;
struct nrf24l0_pipe *pipe0;
int ret;
while(true) {
//Wait for data from user space.
wait_event_interruptible(nrf24l01p->tx_wait_queue,
kthread_should_stop() ||
(!nrf24l01p->rx_active && !kfifo_is_empty(&nrf24l01p->tx_fifo)));
if (kthread_should_stop())
return 0;
//Mutex to protect Tx FIFO
if (mutex_lock_interruptible(&nrf24l01p->tx_fifo_mutex))
continue;
//Get data from TX FIFO
ret = kfifo_out(&nrf24l01p->tx_fifo, &txdata, sizeof(txdata));
if (ret != sizeof(txdata)) {
dev_err(&nrf24l01p->dev, "get tx_data from fifo failed\n");
continue;
}
mutex_unlock(&nrf24l01p->tx_fifo_mutex);
pipe0 = txdata.pipe0;
//Disable device
nrf24_ce_pin(nrf24l01p, 0);
//Set TX mode
ret = nrf24l01p_set_mode(nrf24l01p, NRF24_MODE_TX);
if (ret) {
dev_err(&nrf24l01p->dev, "Mode set to Tx failed\n");
goto gotoRX;
}
//Write data into FIFO
ret = nrf24l01p_write_tx_pload(nrf24l01p, txdata.pload, txdata.size);
if (ret < 0) {
dev_err(&nrf24l01p->dev,
"write TX PLOAD failed (%d)\n",
ret);
goto gotoRX;
}
//Enable device
nrf24_ce_pin(nrf24l01p, 1);
//Wait for ack
nrf24l01p->tx_done = false;
wait_event_interruptible(nrf24l01p->tx_done_wait_queue,
(nrf24l01p->tx_done ||
kthread_should_stop()));
if (kthread_should_stop())
return 0;
pipe0->write_done = true;
//Wake up write fops to wait for next Tx data
wake_up_interruptible(&pipe0->write_wait_queue);
gotoRX:
//Again go back to Rx mode
if (kfifo_is_empty(&nrf24l01p->tx_fifo) || nrf24l01p->rx_active) {
//enter Standby-I
nrf24_ce_pin(nrf24l01p, 0);
ret = nrf24l01p_set_mode(nrf24l01p, NRF24_MODE_RX);
if (ret < 0) {
dev_err(&nrf24l01p->dev,
"Set rx mode failed (%d)\n",
ret);
}
nrf24_ce_pin(nrf24l01p, 1);
}
}
return 0;
}
PRX operation
- In nRF24L01+ enters RX mode and starts searching for packets
- If the packet is new the payload is made available in the RX FIFO and the RX_DR IRQ is asserted.
- Then transmitter is acknowledged with an ACK packet.
Steps performed by RX worker
static void nrf24_rx_work_handler(struct work_struct *work)
{
struct nrf24l0_data *nrf24l01p;
u8 payload[NRF24L0P_PLOAD_MAX];
struct nrf24l0_pipe *pipe0;
int length;
nrf24l01p = container_of(work, struct nrf24l0_data, rx_work);
pipe0 = nrf24l01p->pipe0;
//Check for data in device FIFO
while(!nrf24l01p_is_rx_fifo_empty(nrf24l01p))
{
memset(payload, 0, NRF24L0P_PLOAD_MAX);
//Read the data from FIFO
length = nrf24l01_get_rx_payload(nrf24l01p, payload);
if (length < 0) {
dev_info(&nrf24l01p->dev, "payload invalid\n");
}
nrf24l01p->rx_active = false;
//Mutex to protect FIFO
if (mutex_lock_interruptible(&pipe0->rx_fifo_mutex))
return;
//Put the data into Kernel FIFO
kfifo_in(&pipe0->rx_fifo, payload, length);
mutex_unlock(&pipe0->rx_fifo_mutex);
//Wake up read file operation
wake_up_interruptible(&pipe0->read_wait_queue);
}
}
- When RX_DR IRQ asserts the ISR thread schedule the Rx worker thread.
- The RX worker thread will check FIFO status.
- If the FIFO has data then it will read the FIFO contents and write into kernel FIFO.
- Then unblocks the read file operations so that received data is presented to the userspace.
File operation write
static ssize_t nrf24l01p_write(struct file *filp,
const char __user *buf,
size_t size,
loff_t *f_pos)
{
struct nrf24l0_data *nrf24l01p;
struct nrf24l0_pipe *pipe0;
struct nrf24l0_tx_data txdata;
pipe0 = filp->private_data;
nrf24l01p = to_nrf24_device(pipe0->dev->parent);
txdata.size = min_t(size_t, size, NRF24L0P_PLOAD_MAX);
txdata.pipe0 = pipe0;
memset(txdata.pload, 0, NRF24L0P_PLOAD_MAX);
//Copy data from userspace to kernel buffer
if (copy_from_user(txdata.pload, buf, txdata.size)) {
dev_err(&nrf24l01p->dev,"%s\n","Data copy error\n");
goto exit_lock;
}
//Mutex to protect kernel FIFO
if (mutex_lock_interruptible(&nrf24l01p->tx_fifo_mutex))
goto exit_lock;
//Put the data into kernel FIFO
if (kfifo_in(&nrf24l01p->tx_fifo, &txdata, sizeof(txdata)) != sizeof(txdata)) {
dev_err(&nrf24l01p->dev,"%s\n","Fifo write error\n");
goto exit_kfifo;
}
mutex_unlock(&nrf24l01p->tx_fifo_mutex);
//Wakeup the Tx thread
wake_up_interruptible(&nrf24l01p->tx_wait_queue);
pipe0->write_done = false;
//Wait for ack
wait_event_interruptible(pipe0->write_wait_queue, pipe0->write_done);
return size;
exit_kfifo:
mutex_unlock(&nrf24l01p->tx_fifo_mutex);
exit_lock:
wake_up_interruptible(&nrf24l01p->tx_wait_queue);
return size;
}
- Wait for data from userspace then copy data from userspace to the kernel buffer.
- Write the copied data into kernel Tx FIFO.
- Wake up the Tx thread.
- Wait for ACK before receiving another data from userspace.
File operation read
static ssize_t nrf24l01p_read(struct file *filp,
char __user *buf,
size_t size,
loff_t *f_pos)
{
struct nrf24l0_data *nrf24l01p;
struct nrf24l0_pipe *pipe0;
unsigned int copied, n;
int ret;
pipe0 = filp->private_data;
nrf24l01p = to_nrf24_device(pipe0->dev->parent);
//Check for data in Rx kernel FIFO
if (kfifo_is_empty(&pipe0->rx_fifo)) {
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
//Wait for data in Rx FIFO
wait_event_interruptible(pipe0->read_wait_queue,
!kfifo_is_empty(&pipe0->rx_fifo));
}
//Mutex to protect FIFO
ret = mutex_lock_interruptible(&pipe0->rx_fifo_mutex);
if (ret)
return ret;
//Copy data from Rx FIFO to userspace buffer
n = kfifo_to_user(&pipe0->rx_fifo, buf, size, &copied);
mutex_unlock(&pipe0->rx_fifo_mutex);
return n ? n : copied;
}
- Check for data in the kernel Rx FIFO.
- Wait until data is present in Rx kernel FIFO.
- If data is available copy data from kernel Rx FIFO to the userspace buffer.
DTS changes for IMX6SX-based Udoo neo board
&ecspi5 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_spi4>;
status = "okay";
nrf24: nrf24_radio@0 {
compatible = "nordic,nrf24l01p";
reg = <0>;
spi-max-frequency = <5000000>;
pinctrl-names = "default";
pinctrl-0 = <&nrf24_gpios>;
interrupt-parent = <&gpio5>;
interrupts = <20 IRQ_TYPE_EDGE_FALLING>; /* falling edge */
irq-gpio = <&gpio5 20 0>;
ce-gpio = <&gpio1 8 0>;
status = "okay";
};
};
&iomuxc {
nrf24_gpios: nrf_gpios {
fsl,pins = <MX6SX_PAD_GPIO1_IO08__GPIO1_IO_8 0x3079>,
<MX6SX_PAD_RGMII2_TD2__GPIO5_IO_20 0xF879>;
};
pinctrl_spi4: spi4 {
fsl,pins =
<MX6SX_PAD_NAND_DATA01__ECSPI5_MOSI 0x100b1>,
<MX6SX_PAD_NAND_DATA00__ECSPI5_MISO 0x100b1>,
<MX6SX_PAD_NAND_DATA02__ECSPI5_SCLK 0x100b1>,
<MX6SX_PAD_NAND_DATA03__ECSPI5_SS0 0x100b1>;
};
};
Sample output
Device-1
root@udoo:/home/udoo/nrf24# ./chat
Usage:
1.By default application will be in receive
2.Enter data to transmit
Starting poll...
Embeddeddiaries
Transmitting data: Embeddeddiaries
Received data = Linux-kernel
Device-2
root@udoo:/home/udoo/nrf24# ./chat
Usage:
1.By default application will be in receive
2.Enter data to transmit
Starting poll...
Received data = Embeddeddiaries
Linux-kernel
Transmitting data: Linux-kernel