Commit 92a19af8 authored by TMRh20's avatar TMRh20

enableDynamicAck(), Single NOACK, default RT,

- Added new function: enableDynamicAck()  to enable single-write NOACK
payloads (merge closer with gcopeland fork)
- Added single NOACK writes for write(), writeFast(), startWrite() and
startFastWrite()
- powerUp() now checks to see if radio is powered on. No delay if
powered up.
- radio must still be powered up manually if powered down manually
- use setRetries function in begin()
- set CE low on powerDown
- updated readme
parent 07eb9f87
...@@ -2,20 +2,23 @@ ...@@ -2,20 +2,23 @@
Design Goals: This library is designed to be... Design Goals: This library is designed to be...
* More compliant with the manufacturer specified operation of the chip * More compliant with the manufacturer specified operation of the chip, while allowing advanced users
* to work outside the reccommended operation.
* Utilize the capabilities of the radio to their full potential via Arduino
* More reliable and feature rich * More reliable and feature rich
* Easy for beginners to use * Easy for beginners to use, with well documented examples and features
* Consumed with a public interface that's similiar to other Arduino standard libraries * Consumed with a public interface that's similiar to other Arduino standard libraries
* Built against the standard SPI library. * Built against the standard SPI library.
March 2014: Optimization begun April 2014: Official Release: Still some work to do, but most benefits have been realized
April 2014: Optimization nearing completion
* The library has been tweaked to allow full use of the FIFO buffers for maximum transfer speeds * The library has been tweaked to allow full use of the FIFO buffers for maximum transfer speeds
* Changes to read() functionality have increased reliability and response * Changes to read() and available () functionality have increased reliability and response
* Extended timeout periods have been added to aid in noisy or otherwise unreliable environments * Extended timeout periods have been added to aid in noisy or otherwise unreliable environments
* Delays have been removed where possible to ensure maximum efficiency * Delays have been removed where possible to ensure maximum efficiency
* Arduino Due fully supported with extended SPI functions * Full Due support with extended SPI functions
* More! See the links below and class documentation for more info. * ATTiny 24/44/84 25/45/85 now supported.
* More! See the links below and class documentation for more info.
Please refer to: Please refer to:
...@@ -27,4 +30,11 @@ Please refer to: ...@@ -27,4 +30,11 @@ Please refer to:
This chip uses the SPI bus, plus two chip control pins. Remember that pin 10 must still remain an output, or This chip uses the SPI bus, plus two chip control pins. Remember that pin 10 must still remain an output, or
the SPI hardware will go into 'slave' mode. the SPI hardware will go into 'slave' mode.
Supported Boards:
* Uno, Nano, etc (328 based boards)
* Mega Types (2560, 1280, etc)
* ARM (Arduino Due) via extended SPI methods
* ATTiny 24/44/84 25/45/85
* See the [documentation](http://tmrh20.github.io/) for more info
\ No newline at end of file
...@@ -135,7 +135,7 @@ uint8_t RF24::write_register(uint8_t reg, uint8_t value) ...@@ -135,7 +135,7 @@ uint8_t RF24::write_register(uint8_t reg, uint8_t value)
/****************************************************************************/ /****************************************************************************/
uint8_t RF24::write_payload(const void* buf, uint8_t data_len) uint8_t RF24::write_payload(const void* buf, uint8_t data_len, const uint8_t writeType)
{ {
uint8_t status; uint8_t status;
const uint8_t* current = reinterpret_cast<const uint8_t*>(buf); const uint8_t* current = reinterpret_cast<const uint8_t*>(buf);
...@@ -147,7 +147,7 @@ uint8_t RF24::write_payload(const void* buf, uint8_t data_len) ...@@ -147,7 +147,7 @@ uint8_t RF24::write_payload(const void* buf, uint8_t data_len)
#if defined (__arm__) #if defined (__arm__)
status = SPI.transfer(csn_pin, W_TX_PAYLOAD , SPI_CONTINUE); status = SPI.transfer(csn_pin, writeType , SPI_CONTINUE);
if(blank_len){ if(blank_len){
while ( data_len--){ while ( data_len--){
...@@ -160,14 +160,14 @@ uint8_t RF24::write_payload(const void* buf, uint8_t data_len) ...@@ -160,14 +160,14 @@ uint8_t RF24::write_payload(const void* buf, uint8_t data_len)
}else{ }else{
while( --data_len ){ while( --data_len ){
SPI.transfer(csn_pin,*current++, SPI_CONTINUE); SPI.transfer(csn_pin,*current++, SPI_CONTINUE);
} }
SPI.transfer(csn_pin,*current); SPI.transfer(csn_pin,*current);
} }
#else #else
csn(LOW); csn(LOW);
status = SPI.transfer( W_TX_PAYLOAD ); status = SPI.transfer( writeType );
while ( data_len-- ) { while ( data_len-- ) {
SPI.transfer(*current++); SPI.transfer(*current++);
} }
...@@ -205,7 +205,7 @@ uint8_t RF24::read_payload(void* buf, uint8_t data_len) ...@@ -205,7 +205,7 @@ uint8_t RF24::read_payload(void* buf, uint8_t data_len)
} }
while ( --blank_len ){ while ( --blank_len ){
SPI.transfer(csn_pin,0xFF, SPI_CONTINUE); SPI.transfer(csn_pin,0xFF, SPI_CONTINUE);
} }
SPI.transfer(csn_pin,0xFF); SPI.transfer(csn_pin,0xFF);
}else{ }else{
while ( --data_len ){ while ( --data_len ){
...@@ -346,7 +346,7 @@ void RF24::print_address_register(const char* name, uint8_t reg, uint8_t qty) ...@@ -346,7 +346,7 @@ void RF24::print_address_register(const char* name, uint8_t reg, uint8_t qty)
/****************************************************************************/ /****************************************************************************/
RF24::RF24(uint8_t _cepin, uint8_t _cspin): 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(false), p_variant(false),
payload_size(32), ack_payload_available(false), dynamic_payloads_enabled(false), payload_size(32), ack_payload_available(false), dynamic_payloads_enabled(false),
pipe0_reading_address(0) pipe0_reading_address(0)
{ {
...@@ -466,8 +466,6 @@ void RF24::begin(void) ...@@ -466,8 +466,6 @@ void RF24::begin(void)
csn(HIGH); csn(HIGH);
#endif #endif
// Must allow the radio time to settle else configuration bits will not necessarily stick. // Must allow the radio time to settle else configuration bits will not necessarily stick.
// This is actually only required following power up but some settling time also appears to // This is actually only required following power up but some settling time also appears to
// be required after resets too. For full coverage, we'll always assume the worst. // be required after resets too. For full coverage, we'll always assume the worst.
...@@ -479,7 +477,7 @@ void RF24::begin(void) ...@@ -479,7 +477,7 @@ void RF24::begin(void)
// Set 1500uS (minimum for 32B payload in ESB@250KBPS) timeouts, to make testing a little easier // Set 1500uS (minimum for 32B payload in ESB@250KBPS) timeouts, to make testing a little easier
// WARNING: If this is ever lowered, either 250KBS mode with AA is broken or maximum packet // WARNING: If this is ever lowered, either 250KBS mode with AA is broken or maximum packet
// sizes must never be used. See documentation for a more complete explanation. // sizes must never be used. See documentation for a more complete explanation.
write_register(SETUP_RETR,(B0100 << ARD) | (B1111 << ARC)); setRetries(5,15);
// Restore our default PA level // Restore our default PA level
setPALevel( RF24_PA_MAX ) ; setPALevel( RF24_PA_MAX ) ;
...@@ -516,9 +514,10 @@ void RF24::begin(void) ...@@ -516,9 +514,10 @@ void RF24::begin(void)
flush_rx(); flush_rx();
flush_tx(); flush_tx();
powerUp(); powerUp(); //Power up by default when begin() is called
delay(5);
//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 ) // 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 )
// PTX should use only 22uA of power
write_register(CONFIG, ( read_register(CONFIG) ) & ~_BV(PRIM_RX) ); write_register(CONFIG, ( read_register(CONFIG) ) & ~_BV(PRIM_RX) );
...@@ -528,7 +527,8 @@ void RF24::begin(void) ...@@ -528,7 +527,8 @@ void RF24::begin(void)
void RF24::startListening(void) void RF24::startListening(void)
{ {
write_register(CONFIG, read_register(CONFIG) | _BV(PWR_UP) | _BV(PRIM_RX)); powerUp();
write_register(CONFIG, read_register(CONFIG) | _BV(PRIM_RX));
write_register(STATUS, _BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT) ); write_register(STATUS, _BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT) );
// Restore the pipe0 adddress, if exists // Restore the pipe0 adddress, if exists
...@@ -559,6 +559,7 @@ void RF24::stopListening(void) ...@@ -559,6 +559,7 @@ void RF24::stopListening(void)
void RF24::powerDown(void) void RF24::powerDown(void)
{ {
ce(LOW); // Guarantee CE is low on powerDown
write_register(CONFIG,read_register(CONFIG) & ~_BV(PWR_UP)); write_register(CONFIG,read_register(CONFIG) & ~_BV(PWR_UP));
} }
...@@ -567,17 +568,23 @@ void RF24::powerDown(void) ...@@ -567,17 +568,23 @@ void RF24::powerDown(void)
//Power up now. Radio will not power down unless instructed by MCU for config changes etc. //Power up now. Radio will not power down unless instructed by MCU for config changes etc.
void RF24::powerUp(void) void RF24::powerUp(void)
{ {
write_register(CONFIG,read_register(CONFIG) | _BV(PWR_UP)); uint8_t cfg = read_register(CONFIG);
delay(5);
// if not powered up then power up and wait for the radio to initialize
if (!(cfg & _BV(PWR_UP))){
write_register(CONFIG,read_register(CONFIG) | _BV(PWR_UP));
delayMicroseconds(1500);
}
} }
/******************************************************************/ /******************************************************************/
//Similar to the previous write, clears the interrupt flags //Similar to the previous write, clears the interrupt flags
bool RF24::write( const void* buf, uint8_t len ) bool RF24::write( const void* buf, uint8_t len, const bool multicast )
{ {
//Start Writing //Start Writing
startWrite(buf,len); startWrite(buf,len,multicast);
//Wait until complete or failed //Wait until complete or failed
//ACK payloads that are handled improperly will cause this to hang //ACK payloads that are handled improperly will cause this to hang
...@@ -595,6 +602,9 @@ bool RF24::write( const void* buf, uint8_t len ) ...@@ -595,6 +602,9 @@ bool RF24::write( const void* buf, uint8_t len )
return 1; return 1;
} }
bool RF24::write( const void* buf, uint8_t len ){
write(buf,len,0);
}
/****************************************************************************/ /****************************************************************************/
//For general use, the interrupt flags are not important to clear //For general use, the interrupt flags are not important to clear
...@@ -617,7 +627,7 @@ bool RF24::writeBlocking( const void* buf, uint8_t len, uint32_t timeout ) ...@@ -617,7 +627,7 @@ bool RF24::writeBlocking( const void* buf, uint8_t len, uint32_t timeout )
} }
//Start Writing //Start Writing
startFastWrite(buf,len); //Write the payload if a buffer is clear startFastWrite(buf,len,0); //Write the payload if a buffer is clear
return 1; //Return 1 to indicate successful transmission return 1; //Return 1 to indicate successful transmission
} }
...@@ -638,7 +648,7 @@ void RF24::reUseTX(){ ...@@ -638,7 +648,7 @@ void RF24::reUseTX(){
/****************************************************************************/ /****************************************************************************/
bool RF24::writeFast( const void* buf, uint8_t len ) bool RF24::writeFast( const void* buf, uint8_t len, const bool multicast )
{ {
//Block until the FIFO is NOT full. //Block until the FIFO is NOT full.
//Keep track of the MAX retries and set auto-retry if seeing failures //Keep track of the MAX retries and set auto-retry if seeing failures
...@@ -656,12 +666,14 @@ bool RF24::writeFast( const void* buf, uint8_t len ) ...@@ -656,12 +666,14 @@ bool RF24::writeFast( const void* buf, uint8_t len )
} }
//Start Writing //Start Writing
startFastWrite(buf,len); startFastWrite(buf,len,multicast);
return 1; return 1;
} }
bool RF24::writeFast( const void* buf, uint8_t len ){
writeFast(buf,len,0);
}
/****************************************************************************/ /****************************************************************************/
...@@ -670,9 +682,10 @@ bool RF24::writeFast( const void* buf, uint8_t len ) ...@@ -670,9 +682,10 @@ bool RF24::writeFast( const void* buf, uint8_t len )
//Otherwise we enter Standby-II mode, which is still faster than standby mode //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 //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::startFastWrite( const void* buf, uint8_t len ){ //TMRh20 void RF24::startFastWrite( const void* buf, uint8_t len, const bool multicast){ //TMRh20
write_payload( buf,len); //write_payload( buf,len);
write_payload( buf, len,multicast?static_cast<uint8_t>(W_TX_PAYLOAD_NO_ACK):static_cast<uint8_t>(W_TX_PAYLOAD) ) ;
ce(HIGH); ce(HIGH);
} }
...@@ -680,11 +693,12 @@ void RF24::startFastWrite( const void* buf, uint8_t len ){ //TMRh20 ...@@ -680,11 +693,12 @@ void RF24::startFastWrite( const void* buf, uint8_t len ){ //TMRh20
//Added the original startWrite back in so users can still use interrupts, ack payloads, etc //Added the original startWrite back in so users can still use interrupts, ack payloads, etc
//Allows the library to pass all tests //Allows the library to pass all tests
void RF24::startWrite( const void* buf, uint8_t len ){ void RF24::startWrite( const void* buf, uint8_t len, const bool multicast ){
// Send the payload // Send the payload
write_payload( buf, len ); //write_payload( buf, len );
write_payload( buf, len,multicast?static_cast<uint8_t>(W_TX_PAYLOAD_NO_ACK):static_cast<uint8_t>(W_TX_PAYLOAD) ) ;
ce(HIGH); ce(HIGH);
ce(LOW); ce(LOW);
...@@ -933,6 +947,27 @@ void RF24::enableAckPayload(void) ...@@ -933,6 +947,27 @@ void RF24::enableAckPayload(void)
/****************************************************************************/ /****************************************************************************/
void RF24::enableDynamicAck(void){
//
// enable dynamic ack features
//
write_register(FEATURE,read_register(FEATURE) | _BV(EN_DYN_ACK) );
// If it didn't work, the features are not enabled
if ( ! read_register(FEATURE) & _BV(EN_DYN_ACK) ){
// So enable them and try again
toggle_features();
write_register(FEATURE,read_register(FEATURE) | _BV(EN_DYN_ACK) );
}
IF_SERIAL_DEBUG(printf("FEATURE=%i\r\n",read_register(FEATURE)));
}
/****************************************************************************/
void RF24::writeAckPayload(uint8_t pipe, const void* buf, uint8_t len) void RF24::writeAckPayload(uint8_t pipe, const void* buf, uint8_t len)
{ {
const uint8_t* current = reinterpret_cast<const uint8_t*>(buf); const uint8_t* current = reinterpret_cast<const uint8_t*>(buf);
......
...@@ -256,6 +256,22 @@ public: ...@@ -256,6 +256,22 @@ public:
*/ */
void powerUp(void) ; void powerUp(void) ;
/**
* Write for single NOACK writes. Disables acknowledgements/autoretries for a single write.
*
* @note enableDynamicAck() must be called to enable this feature
*
* Can be used with enableAckPayload() to request a response
* @see enableDynamicAck()
* @see setAutoAck()
* @see write()
*
* @param buf Pointer to the data to be sent
* @param len Number of bytes to be sent
* @param multicast Request ACK (0), NOACK (1)
*/
bool write( const void* buf, uint8_t len, const bool multicast );
/** /**
* @note Optimization: New Command * * @note Optimization: New Command *
* This will not block until the 3 FIFO buffers are filled with data. * This will not block until the 3 FIFO buffers are filled with data.
...@@ -289,6 +305,19 @@ public: ...@@ -289,6 +305,19 @@ public:
*/ */
bool writeFast( const void* buf, uint8_t len ); bool writeFast( const void* buf, uint8_t len );
/**
* WriteFast for single NOACK writes. Disables acknowledgements/autoretries for a single write.
*
* @note enableDynamicAck() must be called to enable this feature
* @see enableDynamicAck()
* @see setAutoAck()
*
* @param buf Pointer to the data to be sent
* @param len Number of bytes to be sent
* @param multicast Request ACK (0) or NOACK (1)
*/
bool writeFast( const void* buf, uint8_t len, const bool multicast );
/** /**
* @note Optimization: New Command * @note Optimization: New Command
* This function extends the auto-retry mechanism to any specified duration. * This function extends the auto-retry mechanism to any specified duration.
...@@ -383,6 +412,20 @@ public: ...@@ -383,6 +412,20 @@ public:
*/ */
void writeAckPayload(uint8_t pipe, const void* buf, uint8_t len); void writeAckPayload(uint8_t pipe, const void* buf, uint8_t len);
/**
* Enable dynamic ACKs (single write multicasting) for chosen messages
*
* @note To enable full multicasting or per-pipe multicast, use setAutoAck()
*
* @warning This MUST be called prior to attempting single write NOACK calls
* @code
* radio.enableDynamicAck();
* radio.write(&data,32,1); // Sends a payload with no acknowledgement requested
* radio.write(&data,32,0); // Sends a payload using auto-retry/autoACK
* @endcode
*/
void enableDynamicAck();
/** /**
* Determine if an ack payload was received in the most recent call to * Determine if an ack payload was received in the most recent call to
* write(). * write().
...@@ -424,11 +467,16 @@ public: ...@@ -424,11 +467,16 @@ public:
* @see startWrite() * @see startWrite()
* @see writeBlocking() * @see writeBlocking()
* *
* For single noAck writes see:
* @see enableDynamicAck()
* @see setAutoAck()
*
* @param buf Pointer to the data to be sent * @param buf Pointer to the data to be sent
* @param len Number of bytes to be sent * @param len Number of bytes to be sent
* @param multicast Request ACK (0) or NOACK (1)
* @return True if the payload was delivered successfully false if not * @return True if the payload was delivered successfully false if not
*/ */
void startFastWrite( const void* buf, uint8_t len ); void startFastWrite( const void* buf, uint8_t len, const bool multicast );
/** /**
* Non-blocking write to the open writing pipe * Non-blocking write to the open writing pipe
...@@ -445,11 +493,16 @@ public: ...@@ -445,11 +493,16 @@ public:
* @see startFastWrite() * @see startFastWrite()
* @see whatHappened() * @see whatHappened()
* *
* For single noAck writes see:
* @see enableDynamicAck()
* @see setAutoAck()
*
* @param buf Pointer to the data to be sent * @param buf Pointer to the data to be sent
* @param len Number of bytes to be sent * @param len Number of bytes to be sent
* @param multicast Request ACK (0) or NOACK (1)
* *
*/ */
void startWrite( const void* buf, uint8_t len ); void startWrite( const void* buf, uint8_t len, const bool multicast );
/** /**
* Optimization: New Command * Optimization: New Command
...@@ -776,7 +829,7 @@ private: ...@@ -776,7 +829,7 @@ private:
* @param len Number of bytes to be sent * @param len Number of bytes to be sent
* @return Current value of status register * @return Current value of status register
*/ */
uint8_t write_payload(const void* buf, uint8_t len); uint8_t write_payload(const void* buf, uint8_t len, const uint8_t writeType);
/** /**
* Read the receive payload * Read the receive payload
...@@ -1003,18 +1056,18 @@ private: ...@@ -1003,18 +1056,18 @@ private:
* @section Goals Design Goals * @section Goals Design Goals
* *
* This library fork is designed to be... * This library fork is designed to be...
* @li More compliant with the manufacturer specified operation of the chip * @li More compliant with the manufacturer specified operation of the chip, while allowing advanced users
* to work outside the reccommended operation.
* @li Utilize the capabilities of the radio to their full potential via Arduino * @li Utilize the capabilities of the radio to their full potential via Arduino
* @li More reliable, responsive and feature rich * @li More reliable, responsive and feature rich
* @li Easy for beginners to use * @li Easy for beginners to use, with well documented examples and features
* @li Consumed with a public interface that's similiar to other Arduino standard libraries * @li Consumed with a public interface that's similiar to other Arduino standard libraries
* *
* @section News News * @section News News
* *
* <b>March 2014: Optimization begun<br> * <b>April 2014: Official Release: Still some work to do, but most benefits have been realized </b><br>
* April 2014: Optimization nearing completion </b><br>
* - The library has been tweaked to allow full use of the FIFO buffers for maximum transfer speeds * - The library has been tweaked to allow full use of the FIFO buffers for maximum transfer speeds
* - Changes to read() functionality have increased reliability and response * - Changes to read() and available () functionality have increased reliability and response
* - Extended timeout periods have been added to aid in noisy or otherwise unreliable environments * - Extended timeout periods have been added to aid in noisy or otherwise unreliable environments
* - Delays have been removed where possible to ensure maximum efficiency * - Delays have been removed where possible to ensure maximum efficiency
* - Full Due support with extended SPI functions * - Full Due support with extended SPI functions
......
/* /*
Copyright (c) 2007 Stefan Engelke <mbox@stefanengelke.de> Copyright (c) 2007 Stefan Engelke <mbox@stefanengelke.de>
Portions Copyright (C) 2011 Greg Copeland
Permission is hereby granted, free of charge, to any person Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software. included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
*/ */
...@@ -117,6 +118,7 @@ ...@@ -117,6 +118,7 @@
/* P model memory Map */ /* P model memory Map */
#define RPD 0x09 #define RPD 0x09
#define W_TX_PAYLOAD_NO_ACK 0xB0
/* P model bit Mnemonics */ /* P model bit Mnemonics */
#define RF_DR_LOW 5 #define RF_DR_LOW 5
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment