add library to control nodric nRF24L01(+) transceivers

This commit is contained in:
King Kévin 2016-01-18 15:52:02 +01:00
parent c25693e14c
commit f42fcc3b8f
2 changed files with 529 additions and 0 deletions

427
lib/nrf24.c Normal file
View File

@ -0,0 +1,427 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/* Copyright (c) 2015 King Kévin <kingkevin@cuvoodoo.info> */
/* This library allows to communicate with a nordic semiconductor nRF24L01 2.4GHz single chip transceiver
* this library uses the SPI bus and requires interrupts to handle the IRQ
*/
#include <stdint.h> // Standard Integer Types
#include <stdlib.h> // General utilities
#include <stdbool.h> // Boolean
#include <string.h> // Memory utilities
#include <avr/io.h> // AVR device-specific IO definitions
#include <avr/interrupt.h> // Interrupts
#include <spi.h> // SPI functions
#include <nrf24.h> // nRF24L01 configuration
#include <stdio.h> // Standard IO facilities
#include <avr/pgmspace.h> // Program Space Utilities
#if defined(IRQ_DDR) && defined(IRQ_IO)
/* if the nRF24L01 is initialized with interrupts, watch for nrf_activity to go true
* once true, check if transmission or reception happened using nrf24_activity(), then nrf24_tx_activity() or nrf24_rx_activity()
* you can then check is transmission succeeded using nrf24_tx_succeeded()
* or get the received data using nrf24_rx_data()
* set it to false after checks are done
*/
volatile bool nrf24_flag = false;
#endif
/* stay in RX mode to receive data */
bool rx = false;
/* initialize the nRF24L01 */
void nrf24_init()
{
// configure SPI
spi_init(); // SPI is used to communicate with the nRF24L01
// configure IO
CE_DDR |= (1<<CE_IO); // set CE as output
CE_PORT &= ~(1<<CE_IO); // disable CE (TR/RX)
// initialise variables
rx = false;
// power up device
// use spi_transfer_blocking in case global interrupt has not been enabled
uint8_t cmd[2]; // to send commands
cmd[0] = 0x00+0x00; // R_REGISTER command + CONFIG register
spi_transfer_blocking(cmd,sizeof(cmd)); // read configuration
#if defined(IRQ_DDR) && defined(IRQ_IO)
cmd[1] &= ~(0x70); // enable interrupt by clearing MASK_RX_DR, MASK_TX_DS, MASK_MAX_RT
#else
cmd[1] |= (0x70); // disable interrupt by setting MASK_RX_DR, MASK_TX_DS, MASK_MAX_RT
#endif
cmd[1] |= (1<<1); // set PWR_UP to power up
cmd[1] &= ~(1<<2); // set CRC to 1 byte
//cmd[1] |= (1<<2); // set CRC to 2 byte
cmd[0] = 0x20+0x00; // W_REGISTER command + CONFIG register
spi_transfer_blocking(cmd,sizeof(cmd)); // write configuration
// flush TX FIFO
cmd[0] = 0x00+0x00; // R_REGISTER command + CONFIG register
spi_transfer_blocking(cmd,sizeof(cmd)); // read config
cmd[0] = 0x20+0x00; // W_REGISTER command + CONFIG register
cmd[1] &= ~(1<<0); // set PRIM_RX to 0 (PTX)
spi_transfer_blocking(cmd,sizeof(cmd)); // write config to got in PTX mode
cmd[0] = 0xe1; // FLUSH_TX
spi_transfer_blocking(cmd,1); // flush TX FIFO
// flush RX FIFO
cmd[0] = 0x00+0x00; // R_REGISTER command + CONFIG register
spi_transfer_blocking(cmd,sizeof(cmd)); // read config
cmd[0] = 0x20+0x00; // W_REGISTER command + CONFIG register
cmd[1] |= (1<<0); // set PRIM_RX to 1 (PRX)
spi_transfer_blocking(cmd,sizeof(cmd)); // write config to got in PRX mode
cmd[0] = 0xe2; // FLUSH_RX
spi_transfer_blocking(cmd,1); // flush RX FIFO
// clear interrupts
cmd[0] = 0x20+0x07; // W_REGISTER command + STATUS register
cmd[1] = 0x70; // clear interrupts
spi_transfer_blocking(cmd,sizeof(cmd)); // clear interrupts
// enable auto acknowledge
cmd[0] = 0x20+0x01; // W_REGISTER command + EN_AA register
cmd[1] = 0x3f; // enable auto for all channels
spi_transfer_blocking(cmd,sizeof(cmd)); // write auto acknowledge
// enable data pipes 0 (to enable acknowledgement) and 1 (to receive)
cmd[0] = 0x20+0x02; // W_REGISTER command + EN_RXADDR register
cmd[1] = 0x03; // enable RX pipes 0 and 1
spi_transfer_blocking(cmd,sizeof(cmd)); // enable pipes
// enable dynamic payload
cmd[0] = 0x20+0x1d; // W_REGISTER command + FEATURE register
cmd[1] = 0x04; // set EN_DPL
spi_transfer_blocking(cmd,sizeof(cmd)); // enable dynamic payload
cmd[0] = 0x20+0x1c; // W_REGISTER command + DYNPD register
cmd[1] = 0x3f; // set DPL_Px
spi_transfer_blocking(cmd,sizeof(cmd)); // enable dynamic payload on all pipes
// set address width to 5 bytes
cmd[0] = 0x20+0x03; // W_REGISTER command + SETUP_AW register
cmd[1] = 0x03; // set to 5 bytes
spi_transfer_blocking(cmd,sizeof(cmd)); // set address width
// set RF: data rate, power
cmd[0] = 0x00+0x06; // R_REGISTER command + RF_SETUP register
spi_transfer_blocking(cmd,sizeof(cmd)); // read RF setup
cmd[0] = 0x20+0x06; // W_REGISTER command + RF_SETUP register
cmd[1] &= ~(1<<3); // set RF_DR to 1 Mbps
//cmd[1] |= (1<<3); // set RF_DR to 2 Mbps
cmd[1] |= (1<<2)|(1<<1); // set RF_PWR to 0 dBm (max)
spi_transfer_blocking(cmd,sizeof(cmd)); // set RF: data rate, power
// set re-transmit properties
cmd[0] = 0x20+0x04; // W_REGISTER command + SETUP_RETR register
cmd[1] = 0x1f; // set ARD to 500uS and ARC to 15
spi_transfer_blocking(cmd,sizeof(cmd)); // set re-transmit properties
// enable interrupt
#if defined(IRQ_IO)
IRQ_DDR &= ~(1<<IRQ_IO); // set IRQ as input
#if defined(IRQ_INT) && (IRQ_INT==1) && (IRQ_IO==PD2) // INT0 interrupt
EIMSK |= (1<<INT0); // enable interrupt for IRQ pin
EICRA &= ~(1<<ISC00); // interrupt on falling edge
EICRA |= (1<<ISC01); // interrupt on falling edge
#elif defined(IRQ_INT) && (IRQ_INT==1) && (IRQ_IO==PD3) // INT1 interrupt
EIMSK |= (1<<INT1); // enable interrupt for IRQ pin
EICRA &= ~(1<<ISC10); // interrupt on falling edge
EICRA |= (1<<ISC11); // interrupt on falling edge
#else // PCINT interrupt
PCICR |= (1<<IRQ_PCIE); // enable interrupt for IRQ port
IRQ_PCMSK |= (1<<IRQ_IO); // enable interrupt for IRQ pin
#endif
nrf24_flag = false;
#endif
}
/* register names */
const char reg_00[] PROGMEM = "CONFIG";
const char reg_01[] PROGMEM = "EN_AA";
const char reg_02[] PROGMEM = "EN_RXADDR";
const char reg_03[] PROGMEM = "SETUP_AW";
const char reg_04[] PROGMEM = "SETUP_RETR";
const char reg_05[] PROGMEM = "RF_CH";
const char reg_06[] PROGMEM = "RF_SETUP";
const char reg_07[] PROGMEM = "STATUS";
const char reg_08[] PROGMEM = "OBSERVE_TX";
const char reg_09[] PROGMEM = "CD";
const char reg_10[] PROGMEM = "RX_ADDR_P0";
const char reg_11[] PROGMEM = "RX_ADDR_P1";
const char reg_12[] PROGMEM = "RX_ADDR_P2";
const char reg_13[] PROGMEM = "RX_ADDR_P3";
const char reg_14[] PROGMEM = "RX_ADDR_P4";
const char reg_15[] PROGMEM = "RX_ADDR_P5";
const char reg_16[] PROGMEM = "TX_ADDR";
const char reg_17[] PROGMEM = "RX_PW_P0";
const char reg_18[] PROGMEM = "RX_PW_P1";
const char reg_19[] PROGMEM = "RX_PW_P2";
const char reg_20[] PROGMEM = "RX_PW_P3";
const char reg_21[] PROGMEM = "RX_PW_P4";
const char reg_22[] PROGMEM = "RX_PW_P5";
const char reg_23[] PROGMEM = "FIFO_STATUS";
/* register names table */
PGM_P const reg_names[] PROGMEM = {
reg_00,
reg_01,
reg_02,
reg_03,
reg_04,
reg_05,
reg_06,
reg_07,
reg_08,
reg_09,
reg_10,
reg_11,
reg_12,
reg_13,
reg_14,
reg_15,
reg_16,
reg_17,
reg_18,
reg_19,
reg_20,
reg_21,
reg_22,
reg_23
};
/* register size */
const uint8_t reg_sizes[] PROGMEM = {1,1,1,1,1,1,1,1,1,1,5,5,1,1,1,1,5,1,1,1,1,1,1,1};
/* dump config */
void nrf24_dump()
{
uint8_t cmd[6]; // SPI nFR24L01 command
uint8_t reg_size; // the register size
char* str; // to print strings
for (uint8_t i=0; i<sizeof(reg_sizes); i++) { // go through registers
cmd[0] = 0x00+i; // R_REGISTER command + register
reg_size = pgm_read_byte(&(reg_sizes[i]));
spi_transfer_wait(cmd,1+reg_size); // read register
str = malloc(strlen_PF((uint_farptr_t)pgm_read_word(&(reg_names[i]))));
strcpy_PF(str, (uint_farptr_t)pgm_read_word(&(reg_names[i])));
printf(str);
free(str);
printf(": ");
for (uint8_t j=0; j<reg_size; j++) {
printf("%02x ",cmd[1+j]);
}
printf("\n");
}
}
/* set the 5 bytes TX address
* it will be used as the destination address of the data to transmit
* this address will also be used (in RX_ADDR_P0) to receive the ack
*/
void nrf24_set_tx_addr(uint8_t* addr)
{
uint8_t cmd[6]; // command
cmd[0] = 0x20+0x10; // W_REGISTER command + TX_ADDR register
memcpy(&cmd[1],addr,sizeof(cmd)-1); // copy address
spi_transfer_wait(cmd,sizeof(cmd)); // write TX_ADDR
cmd[0] = 0x20+0x0A; // W_REGISTER command + RX_ADDR_P0 register
memcpy(&cmd[1],addr,sizeof(cmd)-1); // copy address (since it has been overwritten)
spi_transfer_wait(cmd,sizeof(cmd)); // write RX_ADDR_P0
}
/* set the 5 bytes RX address
* it will be used as the address of the receiving device
* this address will be used in RX_ADDR_P1
*/
void nrf24_set_rx_addr(uint8_t* addr)
{
uint8_t cmd[6]; // command
cmd[0] = 0x20+0x0B; // W_REGISTER command + RX_ADDR_P1 register
memcpy(&cmd[1],addr,sizeof(cmd)-1); // copy address
spi_transfer_wait(cmd,sizeof(cmd)); // write RX_ADDR_P1
}
/* set the RF channel to transmit on */
void nrf24_set_rf_channel(uint8_t channel)
{
if (channel>125) {
return;
}
/* verify there is space in the FIFO */
uint8_t cmd[2]; // command
cmd[0] = 0x20+0x05; // W_REGISTER command + RF_CH register
cmd[1] = channel; // set channel
spi_transfer_wait(cmd,sizeof(cmd)); // write RF_CH
}
/* transmit <payload> with of <length> bytes (max 32 bytes)
* returns false if input is corrupted or FIFO is already full
* returns true after payload is written
*/
bool nrf24_transmit(uint8_t* payload, uint8_t length)
{
if (payload==NULL || length==0 || length>32) { // ensure data and size is valid
return false;
}
/* check if TX FIFO is full */
uint8_t cmd[2]; // command
cmd[0] = 0x00+0x17; // R_REGISTER command + FIFO_STATUS register
spi_transfer_wait(cmd,sizeof(cmd)); // read FIFO status
if (cmd[1]&(1<<5)) { // TX_FULL
return false;
}
/* go into TX mode */
cmd[0] = 0x00+0x00; // R_REGISTER command + CONFIG register
spi_transfer_wait(cmd,sizeof(cmd)); // read config
cmd[0] = 0x20+0x00; // W_REGISTER command + CONFIG register
cmd[1] &= ~(1<<0); // set PRIM_RX to 0 (PTX)
spi_transfer_wait(cmd,sizeof(cmd)); // write config to got in PTX mode
/* write TX payload */
uint8_t* tx = calloc(length+1,sizeof(uint8_t));
tx[0] = 0xa0; // W_TX_PAYLOAD command
memcpy(&tx[1],payload,length); // copy data
spi_transfer_wait(tx,length+1); // write TX payload
/* start transmission */
CE_PORT |= (1<<CE_IO); // enable CE to start transmission
// clean memory (the payload transfer is complete at this point)
free(tx);
tx = NULL;
return true;
}
/* put device into RX mode or disable it */
void nrf24_enable_rx(bool enable)
{
rx = enable; // remember to stay in RX mode
if (rx) {
uint8_t cmd[2]; // SPI command
cmd[0] = 0x00+0x00; // R_REGISTER command + CONFIG register
spi_transfer_wait(cmd,sizeof(cmd)); // read config
cmd[0] = 0x20+0x00; // W_REGISTER command + CONFIG register
cmd[1] |= (1<<0); // set PRIM_RX to 1 (PRX)
spi_transfer_wait(cmd,sizeof(cmd)); // write config to got in PRX mode
CE_PORT |= (1<<CE_IO); // enable CE to start receiving
} else {
uint8_t cmd[2]; // SPI command
cmd[0] = 0x00+0x00; // R_REGISTER command + CONFIG register
spi_transfer_wait(cmd,sizeof(cmd)); // read config
if (cmd[1]&(1<<0)) { // currently in RX mode
CE_PORT &= ~(1<<CE_IO); // disable CE to go to sleep
} // do not disable if in TX mode
}
}
/* handle IRQ interrupt */
#if defined(IRQ_IO)
#if defined(IRQ_INT) && (IRQ_INT==1) && (IRQ_IO==PD2) // INT0 interrupt
ISR(INT0_vect)
#elif defined(IRQ_INT) && (IRQ_INT==1) && (IRQ_IO==PD3) // INT1 interrupt
ISR(INT1_vect)
#else // PCINT interrupt
ISR(IRQ_PCINT_vect)
#endif
{ /* RX or TX succeeded or failed */
nrf24_flag = true; // warn user
}
#endif
/* check the current status of the device
* return nrf24_activity_t bits correspoinding to the activies
* call it after an interrupt
* puts device back in PRX mode if transmission completed and rx has been enabled
* puts device back to sleep if transmission completed and rx has not been enabled
*/
uint8_t nrf24_activity()
{
uint8_t to_return = 0; // is there activity
uint8_t cmd[2]; // SPI nFR24L01 command
// get status
cmd[0] = 0xff; // NOP to read STATUS
spi_transfer_wait(cmd,1); // read STATUS register
// clear receive interrupt
to_return = cmd[0]&(0x70); // copy RX_DR, TS_DS, MAX_RT
if ((cmd[0]&0x0e)<0x0b) { // payload is available based on RX_P_NO
to_return |= RX_AVAILABLE; // warn user
}
if (cmd[0]&(0x70)) { // activity cause by interrupt
cmd[0] = 0x20+0x07; // W_REGISTER command + STATUS register
cmd[1] = 0x70; // clear interrupts
spi_transfer_wait(cmd,2); // clear interrupt
}
if (to_return&TX_FAILED) { // transmission failed based on MAX_RT
cmd[0] = 0xe1; // FLUSH_TX command
spi_transfer_wait(cmd,1); // flush failed TX_FIFO
}
// verify is there is still data to transmit
cmd[0] = 0x00+0x17; // R_REGISTER command + FIFO_STATUS register
spi_transfer_wait(cmd,2); // read FIFO_STATUS register
if (!(cmd[1]&(1<<4))) { // check TX_EMPTY, if not empty put is TX mode
cmd[0] = 0x00+0x00; // R_REGISTER command + CONFIG register
spi_transfer_wait(cmd,2); // read config
cmd[0] = 0x20+0x00; // W_REGISTER command + CONFIG register
cmd[1] &= ~(1<<0); // set PRIM_RX to 0 (PTX)
spi_transfer_wait(cmd,2); // write config to got in PTX mode
CE_PORT |= (1<<CE_IO); // enable CE to start transmission
} else if (rx) { // go in RX mode if enables (and TX FIFO is empty)
cmd[0] = 0x00+0x00; // R_REGISTER command + CONFIG register
spi_transfer_wait(cmd,2); // read config
cmd[0] = 0x20+0x00; // W_REGISTER command + CONFIG register
cmd[1] |= (1<<0); // set PRIM_RX to 1 (PRX)
spi_transfer_wait(cmd,2); // write config to got in PRX mode
CE_PORT |= (1<<CE_IO); // enable CE to start receiving
} else { // nothing to send, and RX is not enabled
CE_PORT &= ~(1<<CE_IO); // disable CE to go to sleep
}
return to_return;
}
/* verify if data is available in the RX FIFO */
bool nrf24_data_available()
{
uint8_t cmd[2]; // SPI nFR24L01 command
cmd[0] = 0x00+0x17; // R_REGISTER command + FIFO_STATUS register
spi_transfer_wait(cmd,sizeof(cmd)); // read STATUS register
return !(cmd[1]&0x01); // not RX FIFO empty
}
/* writes to received payload in provided <payload> pointer
* returns size of received data
* returns 0 if no data is available
* write maximum <size> in <payload> (max payload size is 32)
*/
uint8_t nrf24_rx_payload(uint8_t* payload, uint8_t size)
{
uint8_t to_return = 0; // the size of the data
uint8_t cmd[2]; // SPI nFR24L01 command
cmd[0] = 0x60; // R_RX_PL_WID command
spi_transfer_wait(cmd,2); // get size (and STATUS)
if ((cmd[0]&0x0e)==0x0e) { // RX_FIFO empty based on RX_P_NO
to_return = 0; // no data is available
} else if (cmd[1]>32) { // something is wrong
cmd[0] = 0xe2; // FLUSH_RX command
spi_transfer_wait(cmd,1); // flush, as suggested by the note
to_return = 0;
} else { // data is available
to_return = cmd[1];
if (to_return>size) { // avoid buffer overflow
to_return = size;
}
// go into RX mode
cmd[0] = 0x00+0x00; // R_REGISTER command + CONFIG register
spi_transfer_wait(cmd,2); // read config
cmd[0] = 0x20+0x00; // W_REGISTER command + CONFIG register
cmd[1] |= (1<<0); // set PRIM_RX to 1 (PRX)
spi_transfer_wait(cmd,2); // write config to got in PRX mode
// read RX payload
uint8_t* data = calloc(to_return+1,sizeof(uint8_t)); // re-allocated memory to get data
data[0] = 0x61; // R_RX_PAYLOAD command
spi_transfer_wait(data,to_return+1); // read FIFO (automatically deleted)
memcpy(payload,&data[1],to_return); // copy payload
free(data); // clean memory
data = NULL; // clean memory
}
return to_return;
}

102
lib/nrf24.h Normal file
View File

@ -0,0 +1,102 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/* Copyright (c) 2015 King Kévin <kingkevin@cuvoodoo.info> */
/* This library allows to communicate with a nordic semiconductor nRF24L01 2.4GHz single chip transceiver */
/* Chip Enable pin to activate TX/RX */
#define CE_IO PB1
#define CE_PORT PORTB
#define CE_DDR DDRB
#define CE_PIN PINB
/* Interrupt ReQuest pin to activate TX/RX */
// comment out IRQ_IO if you don't want to use interrupts
#define IRQ_IO PD2
#define IRQ_PORT PORTD
#define IRQ_DDR DDRD
#define IRQ_PIN PIND
#define IRQ_PCIE PCIE2
#define IRQ_PCMSK PCMSK2
#define IRQ_PCINT_vect PCINT2_vect
// if you use PD2 or PD3 you can use the INT0 or INT1 interrupt so to not take over a whole PORT PCINT interrupt
#if defined(IRQ_IO) && ((IRQ_IO==PD2)||(IRQ_IO==PD3))
// comment or set to 0 if you prefer to use PCINT interrupt
// define and set to 1 if you prefer to use INT interrupt
#define IRQ_INT 1
#endif
#if defined(IRQ_DDR) && defined(IRQ_IO)
/* if the nRF24L01 is initialized with interrupts, watch for nrf_activity to go true
* once true, check if transmission or reception happened using nrf24_activity(), then nrf24_tx_activity() or nrf24_rx_activity()
* you can then check is transmission succeeded using nrf24_tx_succeeded()
* or get the received data using nrf24_rx_data()
* set it to false after checks are done
*/
extern volatile bool nrf24_flag;
#endif
/* initialize the nRF24L01 */
void nrf24_init();
/* set the 5 bytes TX address
* it will be used as the destination address of the data to transmit
* this address will also be used (in RX_ADDR_P0) to receive the ack
*/
void nrf24_set_tx_addr(uint8_t* addr);
/* set the 5 bytes RX address
* it will be used as the address of the receiving device
* this address will be used in RX_ADDR_P1
*/
void nrf24_set_rx_addr(uint8_t* addr);
/* set the RF channel to transmit on */
void nrf24_set_rf_channel(uint8_t channel);
/* transmit <payload> with of <length> bytes (max 32 bytes)
* returns false if input is corrupted or FIFO is already full
* returns true after payload is written
*/
bool nrf24_transmit(uint8_t* payload, uint8_t length);
/* put device into RX mode or disable it */
void nrf24_enable_rx(bool enable);
/* handle IRQ interrupt */
#if defined(IRQ_IO)
#if defined(IRQ_INT) && (IRQ_INT==1) && (IRQ_IO==PD2) // INT0 interrupt
ISR(INT0_vect);
#elif defined(IRQ_INT) && (IRQ_INT==1) && (IRQ_IO==PD3) // INT1 interrupt
ISR(INT1_vect);
#else // PCINT interrupt
ISR(IRQ_PCINT_vect);
#endif
#endif
/* check the current status of the device
* return nrf24_activities bits correspoinding to the activies
* call it after an interrupt
* puts device back in PRX mode if transmission completed and rx has been enabled
* puts device back to sleep if transmission completed and rx has not been enabled
*/
enum nrf24_activities {
TX_SUCCEEDED = (1<<5),
TX_FAILED = (1<<4),
RX_RECEIVED = (1<<6),
RX_AVAILABLE = (1<<3)
};
uint8_t nrf24_activity();
/* writes to received payload in provided <payload> pointer
* returns size of received data
* returns 0 if no data is available
* write maximum <size> in <payload> (max payload size is 32)
*/
uint8_t nrf24_rx_payload(uint8_t* payload, uint8_t size);
/* verify if data is available in the RX FIFO */
bool nrf24_data_available();
/* dump config */
void nrf24_dump();