From 4cf0fb0c5adc305d40547f42366161e809122b94 Mon Sep 17 00:00:00 2001 From: TMRh20 Date: Tue, 18 Mar 2014 07:07:03 -0600 Subject: [PATCH] High Speed Data Transfers/Optimization Changes: - Enable PTX at radio begin, in standby-I mode - Enable PTX at radio stopListening(); in standy-I mode - Radio startListening - removed delay. What is there to wait for when the radio will just be listening anyway? - Radio powerUp - radio will need to be powered up after configuration - radio will not power down unless instructed to - need to issue radio.txStandBy(); when finished transmitting to engage standby-I mode - Added txStandBy() to drop to standBy-I mode if not switching to RX mode - Radio Write: Sped up - radio previously did not use standby mode effectively, after TX sending of each packet, the radio is powered down introducing a 1.5ms delay in addition to the 130us delay from standby. - No observe-tx code, a simple NOP cmd is sent to get the status info - No millis() timeout, hanging condition should only occur because of faulty code - Simplified whatHappened() code - Flush TX FIFO if MAX_RT met as lib does not support re-transmit instead of flush every time. - Remove ack size monitoring - No powerdown needed, - Added writeClear() function for writes that need the interrupt flags cleared (ack payloads, etc). Same as write() otherwise - Radio startWrite: Sped up - Enable PTX mode is now done in stopListening() code. Enter TX mode by writing CE high. This brings the data transfer capabilities up to a reasonable level - No need to power up, already in Standby-I mode - No need for delayMicroseconds() in write functon. - No need for delay after writing CE high - Does not enter Standby-I mode be default. It is up to the MCU to call the new txStandBy() function to enter standby-I mode from PTX if not switching back to PRX - Radio available: - Now checks the FIFO buffer instead of the interrupt flag (no missed data) - Moved clearing of interrupt flags to Read() - Radio read: - No longer returns any value - use available to check for more data - Radio isAckPayloadAvailable() now checks the FIFO directly - Radio getDynamicPayloadSize() - Per the datasheet, added fix to flush corrupted packets --- RF24.cpp | 200 +++++++++++++++++++++++++------------------------------ RF24.h | 54 ++++++++------- 2 files changed, 121 insertions(+), 133 deletions(-) diff --git a/RF24.cpp b/RF24.cpp index 9471583..c875fc9 100644 --- a/RF24.cpp +++ b/RF24.cpp @@ -103,9 +103,9 @@ uint8_t RF24::write_payload(const void* buf, uint8_t len) uint8_t data_len = min(len,payload_size); uint8_t blank_len = dynamic_payloads_enabled ? 0 : payload_size - data_len; - + //printf("[Writing %u bytes %u blanks]",data_len,blank_len); - + csn(LOW); status = SPI.transfer( W_TX_PAYLOAD ); while ( data_len-- ) @@ -126,9 +126,9 @@ uint8_t RF24::read_payload(void* buf, uint8_t len) uint8_t data_len = min(len,payload_size); uint8_t blank_len = dynamic_payloads_enabled ? 0 : payload_size - data_len; - + //printf("[Reading %u bytes %u blanks]",data_len,blank_len); - + csn(LOW); status = SPI.transfer( R_RX_PAYLOAD ); while ( data_len-- ) @@ -239,7 +239,7 @@ void RF24::print_address_register(const char* name, uint8_t reg, uint8_t qty) /****************************************************************************/ RF24::RF24(uint8_t _cepin, uint8_t _cspin): - ce_pin(_cepin), csn_pin(_cspin), wide_band(true), p_variant(false), + ce_pin(_cepin), csn_pin(_cspin), wide_band(true), p_variant(false), payload_size(32), ack_payload_available(false), dynamic_payloads_enabled(false), pipe0_reading_address(0) { @@ -299,7 +299,7 @@ static const char rf24_pa_dbm_e_str_0[] PROGMEM = "PA_MIN"; static const char rf24_pa_dbm_e_str_1[] PROGMEM = "PA_LOW"; static const char rf24_pa_dbm_e_str_2[] PROGMEM = "LA_MED"; static const char rf24_pa_dbm_e_str_3[] PROGMEM = "PA_HIGH"; -static const char * const rf24_pa_dbm_e_str_P[] PROGMEM = { +static const char * const rf24_pa_dbm_e_str_P[] PROGMEM = { rf24_pa_dbm_e_str_0, rf24_pa_dbm_e_str_1, rf24_pa_dbm_e_str_2, @@ -366,14 +366,14 @@ void RF24::begin(void) { p_variant = true ; } - + // Then set the data rate to the slowest (and most reliable) speed supported by all // hardware. setDataRate( RF24_1MBPS ) ; // Initialize CRC and request 2-byte (16bit) CRC setCRCLength( RF24_CRC_16 ) ; - + // Disable dynamic payloads, to match dynamic_payloads_enabled setting write_register(DYNPD,0); @@ -389,6 +389,11 @@ void RF24::begin(void) // Flush buffers flush_rx(); flush_tx(); + + //Enable PTX, do not write CE high so radio will remain in standby I mode ( 130us max to transition to RX or TX instead of 1500us from powerUp ) + write_register(CONFIG, ( read_register(CONFIG) ) & ~_BV(PRIM_RX) ); + + } /****************************************************************************/ @@ -409,8 +414,6 @@ void RF24::startListening(void) // Go! ce(HIGH); - // wait for the radio to come up (130us actually only needed) - delayMicroseconds(130); } /****************************************************************************/ @@ -420,6 +423,8 @@ void RF24::stopListening(void) ce(LOW); flush_tx(); flush_rx(); + write_register(CONFIG, ( read_register(CONFIG) ) & ~_BV(PRIM_RX) ); + } /****************************************************************************/ @@ -431,6 +436,7 @@ void RF24::powerDown(void) /****************************************************************************/ +//Power up now. Radio will not power down unless instructed by MCU for config changes etc. void RF24::powerUp(void) { write_register(CONFIG,read_register(CONFIG) | _BV(PWR_UP)); @@ -438,85 +444,69 @@ void RF24::powerUp(void) /******************************************************************/ -bool RF24::write( const void* buf, uint8_t len ) +//Similar to the previous write, clears the interrupt flags +bool RF24::writeClear( const void* buf, uint8_t len ) { - bool result = false; + //Start Writing + startWrite(buf,len); - // Begin the write - startWrite(buf,len); + //Wait until complete or failed + //ACK payloads that are handled improperly will cause this to hang + //If autoAck is ON, a payload has to be written prior to reading a payload, else write after reading a payload + while( ! ( get_status() & ( _BV(TX_DS) | _BV(MAX_RT) ))) { } - // ------------ - // At this point we could return from a non-blocking write, and then call - // the rest after an interrupt + uint8_t status = write_register(STATUS,_BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT) ); - // Instead, we are going to block here until we get TX_DS (transmission completed and ack'd) - // or MAX_RT (maximum retries, transmission failed). Also, we'll timeout in case the radio - // is flaky and we get neither. - - // IN the end, the send should be blocking. It comes back in 60ms worst case, or much faster - // if I tighted up the retry logic. (Default settings will be 1500us. - // Monitor the send - uint8_t observe_tx; - uint8_t status; - uint32_t sent_at = millis(); - const uint32_t timeout = 500; //ms to wait for timeout - do - { - status = read_register(OBSERVE_TX,&observe_tx,1); - IF_SERIAL_DEBUG(Serial.print(observe_tx,HEX)); + //Max retries exceeded + if( status & _BV(MAX_RT)){ + //Flush the FIFO since this is a single-packet-write function, and FIFO is NOT cleared when MAX_RT is reached + flush_tx(); } - while( ! ( status & ( _BV(TX_DS) | _BV(MAX_RT) ) ) && ( millis() - sent_at < timeout ) ); - - // The part above is what you could recreate with your own interrupt handler, - // and then call this when you got an interrupt - // ------------ - - // Call this when you get an interrupt - // The status tells us three things - // * The send was successful (TX_DS) - // * The send failed, too many retries (MAX_RT) - // * There is an ack packet waiting (RX_DR) - bool tx_ok, tx_fail; - whatHappened(tx_ok,tx_fail,ack_payload_available); - - //printf("%u%u%u\r\n",tx_ok,tx_fail,ack_payload_available); - - result = tx_ok; - IF_SERIAL_DEBUG(Serial.print(result?"...OK.":"...Failed")); - - // Handle the ack packet - if ( ack_payload_available ) - { - ack_payload_length = getDynamicPayloadSize(); - IF_SERIAL_DEBUG(Serial.print("[AckPacket]/")); - IF_SERIAL_DEBUG(Serial.println(ack_payload_length,DEC)); - } - - // Yay, we are done. - - // Power down - powerDown(); - - // Flush buffers (Is this a relic of past experimentation, and not needed anymore??) - flush_tx(); - - return result; + //TX OK 1 or 0 + return status & _BV(TX_DS);//tx_ok; } + /****************************************************************************/ -void RF24::startWrite( const void* buf, uint8_t len ) +//For general use, the flags are not important to clear +bool RF24::write( const void* buf, uint8_t len ) { - // Transmitter power-up - write_register(CONFIG, ( read_register(CONFIG) | _BV(PWR_UP) ) & ~_BV(PRIM_RX) ); - delayMicroseconds(150); + //Start Writing + startWrite(buf,len); - // Send the payload - write_payload( buf, len ); + //Wait until complete or failed + //ACK payloads that are handled improperly will cause this to hang + //If autoAck is ON, a payload has to be written prior to reading a payload, else write after reading a payload + uint8_t status; + while( ! ( status = get_status() & ( _BV(TX_DS) | _BV(MAX_RT) ))) { } - // Allons! - ce(HIGH); - delayMicroseconds(15); - ce(LOW); + //Max retries exceeded + if( status & _BV(MAX_RT)){ + //Flush the FIFO since this is a single-packet-write function, and FIFO is NOT cleared when MAX_RT is reached + flush_tx(); + } + //TX OK 1 or 0 + return status & _BV(TX_DS);//tx_ok; +} + + +/****************************************************************************/ + +//Per the documentation, we want to set PTX Mode when not listening. Then all we do is write data and set CE high +//In this mode, if we can keep the FIFO buffers loaded, packets will transmit immediately (no 130us delay) +//Otherwise we enter Standby-II mode, which is still faster than standby mode +//Also, we remove the need to keep writing the config register over and over and delaying for 150 us each time if sending a stream of data + +void RF24::startWrite( const void* buf, uint8_t len ){ //TMRh20 + + write_payload( buf,len); + ce(HIGH); + +} + + +void RF24::txStandBy(){ + ce(LOW); } /****************************************************************************/ @@ -530,6 +520,7 @@ uint8_t RF24::getDynamicPayloadSize(void) result = SPI.transfer(0xff); csn(HIGH); + if(result > 32) { flush_rx(); return 0; } return result; } @@ -544,45 +535,35 @@ bool RF24::available(void) bool RF24::available(uint8_t* pipe_num) { - uint8_t status = get_status(); - // Too noisy, enable if you really want lots o data!! - //IF_SERIAL_DEBUG(print_status(status)); + //Check the FIFO buffer to see if data is waitng to be read - bool result = ( status & _BV(RX_DR) ); + if (!( read_register(FIFO_STATUS) & _BV(RX_EMPTY) )){ - if (result) - { // If the caller wants the pipe number, include that - if ( pipe_num ) + if ( pipe_num ){ + uint8_t status = get_status(); *pipe_num = ( status >> RX_P_NO ) & B111; - - // Clear the status bit - - // ??? Should this REALLY be cleared now? Or wait until we - // actually READ the payload? - - write_register(STATUS,_BV(RX_DR) ); - - // Handle ack payload receipt - if ( status & _BV(TX_DS) ) - { - write_register(STATUS,_BV(TX_DS)); - } + } + return 1; } - return result; + return 0; + + + } /****************************************************************************/ -bool RF24::read( void* buf, uint8_t len ) -{ +void RF24::read( void* buf, uint8_t len ){ + // Fetch the payload read_payload( buf, len ); - // was this the last of the data available? - return read_register(FIFO_STATUS) & _BV(RX_EMPTY); + //Clear the two possible interrupt flags with one command + write_register(STATUS,_BV(RX_DR) | _BV(TX_DS) ); + } /****************************************************************************/ @@ -736,9 +717,10 @@ void RF24::writeAckPayload(uint8_t pipe, const void* buf, uint8_t len) bool RF24::isAckPayloadAvailable(void) { - bool result = ack_payload_available; - ack_payload_available = false; - return result; + if (!( read_register(FIFO_STATUS) & _BV(RX_EMPTY) )){ + return 1; + } + return 0; } /****************************************************************************/ @@ -905,7 +887,7 @@ rf24_datarate_e RF24::getDataRate( void ) { rf24_datarate_e result ; uint8_t dr = read_register(RF_SETUP) & (_BV(RF_DR_LOW) | _BV(RF_DR_HIGH)); - + // switch uses RAM (evil!) // Order matters in our case below if ( dr == _BV(RF_DR_LOW) ) @@ -931,11 +913,11 @@ rf24_datarate_e RF24::getDataRate( void ) void RF24::setCRCLength(rf24_crclength_e length) { uint8_t config = read_register(CONFIG) & ~( _BV(CRCO) | _BV(EN_CRC)) ; - + // switch uses RAM (evil!) if ( length == RF24_CRC_DISABLED ) { - // Do nothing, we turned it off above. + // Do nothing, we turned it off above. } else if ( length == RF24_CRC_8 ) { diff --git a/RF24.h b/RF24.h index 18914f0..83cb562 100644 --- a/RF24.h +++ b/RF24.h @@ -51,9 +51,10 @@ private: bool p_variant; /* False for RF24L01 and true for RF24L01P */ uint8_t payload_size; /**< Fixed size of payloads */ bool ack_payload_available; /**< Whether there is an ack payload waiting */ - bool dynamic_payloads_enabled; /**< Whether dynamic payloads are enabled. */ + bool dynamic_payloads_enabled; /**< Whether dynamic payloads are enabled. */ uint8_t ack_payload_length; /**< Dynamic size of pending ack payload. */ uint64_t pipe0_reading_address; /**< Last address set on pipe 0 for reading. */ + bool avail; protected: /** @@ -219,6 +220,11 @@ protected: /**@}*/ public: + +//TMRh20 + void txStandBy(); + bool writeClear( const void* buf, uint8_t len ); + /** * @name Primary public interface * @@ -301,7 +307,7 @@ public: * @param len Maximum number of bytes to read into the buffer * @return True if the payload was delivered successfully false if not */ - bool read( void* buf, uint8_t len ); + void read( void* buf, uint8_t len ); /** * Open a pipe for writing @@ -351,7 +357,7 @@ public: /**@}*/ /** - * @name Optional Configurators + * @name Optional Configurators * * Methods you can use to get or set the configuration of the chip. * None are required. Calling begin() sets up a reasonable set of @@ -406,7 +412,7 @@ public: * @return Payload length of last-received dynamic payload */ uint8_t getDynamicPayloadSize(void); - + /** * Enable custom payloads on the acknowledge packets * @@ -486,7 +492,7 @@ public: * @return true if the change was successful */ bool setDataRate(rf24_datarate_e speed); - + /** * Fetches the transmission data rate * @@ -518,9 +524,9 @@ public: /**@}*/ /** - * @name Advanced Operation + * @name Advanced Operation * - * Methods you can use to drive the chip in more advanced ways + * Methods you can use to drive the chip in more advanced ways */ /**@{*/ @@ -642,9 +648,9 @@ public: * debugging. Setting either pin to 0xff is the way to * indicate that this is not a real radio. * - * @return true if this is a legitimate radio + * @return true if this is a legitimate radio */ - bool isValid() { return ce_pin != 0xff && csn_pin != 0xff; } + bool isValid() { return ce_pin != 0xff && csn_pin != 0xff; } /**@}*/ }; @@ -653,12 +659,12 @@ public: * @example GettingStarted.pde * * This is an example which corresponds to my "Getting Started" blog post: - * Getting Started with nRF24L01+ on Arduino. + * Getting Started with nRF24L01+ on Arduino. * - * It is an example of how to use the RF24 class. Write this sketch to two - * different nodes. Put one of the nodes into 'transmit' mode by connecting - * with the serial monitor and sending a 'T'. The ping node sends the current - * time to the pong node, which responds by sending the value back. The ping + * It is an example of how to use the RF24 class. Write this sketch to two + * different nodes. Put one of the nodes into 'transmit' mode by connecting + * with the serial monitor and sending a 'T'. The ping node sends the current + * time to the pong node, which responds by sending the value back. The ping * node can then see how long the whole cycle took. */ @@ -690,14 +696,14 @@ public: */ /** - * @example pingpair_maple.pde + * @example pingpair_maple.pde * * This is an example of how to use the RF24 class on the Maple. For a more * detailed explanation, see my blog post: * nRF24L01+ Running on Maple * * It will communicate well to an Arduino-based unit as well, so it's not for only Maple-to-Maple communication. - * + * * Write this sketch to two different nodes, * connect the role_pin to ground on one. The ping node sends the current time to the pong node, * which responds by sending the value back. The ping node can then see how long the whole cycle @@ -759,25 +765,25 @@ public: * @mainpage Driver for nRF24L01(+) 2.4GHz Wireless Transceiver * * @section Goals Design Goals - * + * * This library is designed to be... * @li Maximally compliant with the intended operation of the chip * @li Easy for beginners to use * @li Consumed with a public interface that's similiar to other Arduino standard libraries * * @section News News - * - * NOW COMPATIBLE WITH ARDUINO 1.0 - The 'master' branch and all examples work with both Arduino 1.0 and earlier versions. + * + * NOW COMPATIBLE WITH ARDUINO 1.0 - The 'master' branch and all examples work with both Arduino 1.0 and earlier versions. * Please open an issue if you find any problems using it with any version of Arduino. * - * NOW COMPATIBLE WITH MAPLE - RF24 has been tested with the - * Maple Native, + * NOW COMPATIBLE WITH MAPLE - RF24 has been tested with the + * Maple Native, * and should work with any Maple board. See the pingpair_maple example. * Note that only the pingpair_maple example has been tested on Maple, although * the others can certainly be adapted. * * @section Useful Useful References - * + * * Please refer to: * * @li Documentation Main Page @@ -799,11 +805,11 @@ public: * * RF24 Getting Started - Finished Product * - * Getting Started with nRF24L01+ on Arduino + * Getting Started with nRF24L01+ on Arduino * * Nordic FOB and nRF24L01+ * - * Using the Sparkfun Nordic FOB + * Using the Sparkfun Nordic FOB * * RF Duinode V3 (2V4) *