Add nRF24 receive message buffering (#538)

* Add critical section support

Allows defining a code block which is executed as a critical section.
Supports AVR, ESP8266 & SAMD.

* Add receive message buffering

When MY_RF24_IRQ_PIN is defined (and connected), a buffer will be used
to retrieve & store messages received by nRF24 as fast as possible.
This reduces the chance of losing messages due to a busy
microcontroller.
nRF24 SPI rx message transfer is running from interrupt context when
MY_RF24_IRQ_PIN is defined. If this transfer takes too long, serial
characters received will get lost. Therefore serial rx is enabled during
SPI transfer of nRF messages.

Limitations: Only AVR/SAMD & nRF24L01+ combinations can buffer rx
messages.
nRF24L01+ IRQ pin should be connected to AVR.
Tested on W5100- & Serial-gateway.

SAMD has not been tested yet!

* Fix Doxygen warnings

* Fix more Doxygen warnings
This commit is contained in:
Embedded Innovation
2016-08-02 22:20:25 +02:00
committed by Olivier
parent 4695cc71dd
commit 99973a346e
12 changed files with 483 additions and 9 deletions
+196
View File
@@ -0,0 +1,196 @@
/*
CircularBuffer - An Arduino circular buffering library for arbitrary types.
Created by Ivo Pullens, Emmission, 2014-2016 -- www.emmission.nl
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file CircularBuffer.h
*
* Circular buffering for arbitrary types.
*/
#ifndef CircularBuffer_h
#define CircularBuffer_h
/**
* The circular buffer class.
* Pass the datatype to be stored in the buffer as template parameter.
*/
template <class T> class CircularBuffer
{
public:
/**
* Constructor
* @param buffer Preallocated buffer of at least size records.
* @param size Number of records available in the buffer.
*/
CircularBuffer(T* buffer, const uint8_t size )
: m_size(size), m_buff(buffer)
{
clear();
}
/**
* Clear all entries in the circular buffer.
*/
void clear(void)
{
MY_CRITICAL_SECTION
{
m_front = 0;
m_fill = 0;
}
}
/**
* Test if the circular buffer is empty.
* @return True, when empty.
*/
inline bool empty(void) const
{
bool empty;
MY_CRITICAL_SECTION
{
empty = !m_fill;
}
return empty;
}
/**
* Test if the circular buffer is full.
* @return True, when full.
*/
inline bool full(void) const
{
bool full;
MY_CRITICAL_SECTION
{
full = m_fill == m_size;
}
return full;
}
/**
* Return the number of records stored in the buffer.
* @return number of records.
*/
inline uint8_t available(void) const
{
MY_CRITICAL_SECTION
{
return m_fill;
}
}
/**
* Aquire unused record on front of the buffer, for writing.
* After filling the record, it has to be pushed to actually
* add it to the buffer.
* @return Pointer to record, or NULL when buffer is full.
*/
T* getFront(void) const
{
MY_CRITICAL_SECTION
{
if (!full()) return get(m_front);
}
return static_cast<T*>(NULL);
}
/**
* Push record to front of the buffer.
* @param record Record to push. If record was aquired previously (using getFront) its
* data will not be copied as it is already present in the buffer.
* @return True, when record was pushed successfully.
*/
bool pushFront(T* record)
{
MY_CRITICAL_SECTION
{
if (!full())
{
T* f = get(m_front);
if (f != record)
*f = *record;
m_front = (m_front+1) % m_size;
m_fill++;
return true;
}
}
return false;
}
/**
* Aquire record on back of the buffer, for reading.
* After reading the record, it has to be pop'ed to actually
* remove it from the buffer.
* @return Pointer to record, or NULL when buffer is empty.
*/
T* getBack(void) const
{
MY_CRITICAL_SECTION
{
if (!empty()) return get(back());
}
return static_cast<T*>(NULL);
}
/**
* Remove record from back of the buffer.
* @return True, when record was pop'ed successfully.
*/
bool popBack(void)
{
MY_CRITICAL_SECTION
{
if (!empty())
{
m_fill--;
return true;
}
}
return false;
}
protected:
/**
* Internal getter for records.
* @param idx Record index in buffer.
* @return Ptr to record.
*/
inline T * get(const uint8_t idx) const
{
return &(m_buff[idx]);
}
/**
* Internal getter for index of last used record in buffer.
* @return Index of last record.
*/
inline uint8_t back(void) const
{
return (m_front - m_fill + m_size) % m_size;
}
const uint8_t m_size; //!< Total number of records that can be stored in the buffer.
T* const m_buff; //!< Ptr to buffer holding all records.
volatile uint8_t m_front; //!< Index of front element (not pushed yet).
volatile uint8_t m_fill; //!< Amount of records currently pushed.
};
#endif // CircularBuffer_h
+63
View File
@@ -25,6 +25,10 @@
LOCAL uint8_t MY_RF24_BASE_ADDR[MY_RF24_ADDR_WIDTH] = { MY_RF24_BASE_RADIO_ID };
LOCAL uint8_t MY_RF24_NODE_ADDRESS = AUTO;
#ifdef MY_RF24_IRQ_PIN
LOCAL RF24_receiveCallbackType RF24_receiveCallback = NULL;
#endif
LOCAL void RF24_csn(bool level) {
digitalWrite(MY_RF24_CS_PIN, level);
}
@@ -86,6 +90,10 @@ LOCAL uint8_t RF24_getStatus(void) {
return RF24_spiByteTransfer( NOP );
}
LOCAL uint8_t RF24_getFifoStatus(void) {
return RF24_readByteRegister(FIFO_STATUS);
}
LOCAL void RF24_setChannel(uint8_t channel) {
RF24_writeByteRegister(RF_CH, channel);
}
@@ -253,14 +261,69 @@ LOCAL bool RF24_sanityCheck(void) {
return status;
}
#ifdef MY_RF24_IRQ_PIN
LOCAL void RF24_irqHandler( void )
{
if (RF24_receiveCallback)
{
// Will stay for a while (several 100us) in this interrupt handler. Any interrupts from serial
// rx coming in during our stay will not be handled and will cause characters to be lost.
// As a workaround we re-enable interrupts to allow nested processing of other interrupts.
// Our own handler is disconnected to prevent recursive calling of this handler.
#ifdef MY_GATEWAY_SERIAL
detachInterrupt(digitalPinToInterrupt(MY_RF24_IRQ_PIN));
interrupts();
#endif
// Read FIFO until empty.
// Procedure acc. to datasheet (pg. 63):
// 1.Read payload, 2.Clear RX_DR IRQ, 3.Read FIFO_status, 4.Repeat when more data available.
// Datasheet (ch. 8.5) states, that the nRF de-asserts IRQ after reading STATUS.
// Start checking if RX-FIFO is not empty, as we might end up here from an interrupt
// for a message we've already read.
while (!(RF24_getFifoStatus() & _BV(0))) {
RF24_receiveCallback(); // Must call RF24_readMessage(), which will clear RX_DR IRQ !
}
// Restore our interrupt handler.
#ifdef MY_GATEWAY_SERIAL
noInterrupts();
attachInterrupt(digitalPinToInterrupt(MY_RF24_IRQ_PIN), RF24_irqHandler, FALLING);
#endif
} else {
// clear RX interrupt
RF24_setStatus(_BV(RX_DR));
}
}
LOCAL void RF24_registerReceiveCallback( RF24_receiveCallbackType cb )
{
MY_CRITICAL_SECTION {
RF24_receiveCallback = cb;
}
}
#endif
LOCAL bool RF24_initialize(void) {
// Initialize pins
pinMode(MY_RF24_CE_PIN,OUTPUT);
pinMode(MY_RF24_CS_PIN,OUTPUT);
#ifdef MY_RF24_IRQ_PIN
pinMode(MY_RF24_IRQ_PIN,INPUT);
#endif
// Initialize SPI
_SPI.begin();
RF24_ce(LOW);
RF24_csn(HIGH);
#ifdef MY_RF24_IRQ_PIN
// assure SPI can be used from interrupt context
// Note: ESP8266 & SoftSPI currently do not support interrupt usage for SPI,
// therefore it is unsafe to use MY_RF24_IRQ_PIN with ESP8266/SoftSPI!
_SPI.usingInterrupt(digitalPinToInterrupt(MY_RF24_IRQ_PIN));
// attach interrupt
attachInterrupt(digitalPinToInterrupt(MY_RF24_IRQ_PIN), RF24_irqHandler, FALLING);
#else
(void)RF24_getFifoStatus; // prevent 'defined but not used' warning
#endif
// CRC and power up
RF24_setRFConfiguration(MY_RF24_CONFIGURATION | _BV(PWR_UP) ) ;
// settle >2ms
+17 -1
View File
@@ -62,7 +62,11 @@
#endif
// RF24 settings
#define MY_RF24_CONFIGURATION (uint8_t) (RF24_CRC_16 << 2)
#ifdef MY_RF24_IRQ_PIN
#define MY_RF24_CONFIGURATION (uint8_t) ((RF24_CRC_16 << 2) | (1 << MASK_TX_DS) | (1 << MASK_MAX_RT))
#else
#define MY_RF24_CONFIGURATION (uint8_t) (RF24_CRC_16 << 2)
#endif
#define MY_RF24_FEATURE (uint8_t)( _BV(EN_DPL) | _BV(EN_ACK_PAY) )
#define MY_RF24_RF_SETUP (uint8_t)( ((MY_RF24_DATARATE & 0b10 ) << 4) | ((MY_RF24_DATARATE & 0b01 ) << 3) | (MY_RF24_PA_LEVEL << 1) ) + 1 // +1 for Si24R1
@@ -222,6 +226,7 @@ LOCAL uint8_t RF24_RAW_writeByteRegister(uint8_t cmd, uint8_t value);
LOCAL void RF24_flushRX(void);
LOCAL void RF24_flushTX(void);
LOCAL uint8_t RF24_getStatus(void);
LOCAL uint8_t RF24_getFifoStatus(void);
LOCAL void RF24_openWritingPipe(uint8_t recipient);
LOCAL void RF24_startListening(void);
LOCAL void RF24_stopListening(void);
@@ -248,4 +253,15 @@ LOCAL void RF24_setPipeLSB(uint8_t pipe, uint8_t LSB);
LOCAL void RF24_setStatus(uint8_t status);
LOCAL void RF24_enableFeatures(void);
#ifdef MY_RF24_IRQ_PIN
typedef void (*RF24_receiveCallbackType)(void);
/**
* Register a callback, which will be called (from interrupt context) for every message received.
* @note When a callback is registered, it _must_ retrieve the message from the nRF24
* by calling RF24_readMessage(). Otherwise the interrupt will not get deasserted
* and message reception will stop.
*/
LOCAL void RF24_registerReceiveCallback( RF24_receiveCallbackType cb );
#endif
#endif // __RF24_H__