diff --git a/.gitignore b/.gitignore index 96374c4..f57828d 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,14 @@ $RECYCLE.BIN/ Network Trash Folder Temporary Items .apdisk + +# Atom / PlatformIO +.pio +.pioenvs +.piolibdeps +.clang_complete +.gcc-flags.json +lib/readme.txt +platformio.ini +.history +.vscode diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..caf501d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,18 @@ +language: python +python: + - "2.7" + +sudo: false +cache: + pip: true + directories: + - "~/.platformio" + +env: + - PLATFORMIO_CI_SRC=examples/passThrough + +install: + - pip install -U platformio + +script: + - platformio ci --lib="." --board=uno --board=feather328p --board=megaatmega2560 --board=sparkfun_megapro8MHz --board=leonardo --board=feather32u4 --board=mightycore1284 --board=mayfly diff --git a/README.md b/README.md index cafccff..d2d2759 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ The class methods are nearly identical to the built-in `SoftwareSerial`, except There are five, nay, **six** advantages over `SoftwareSerial`: -**1)** It uses *much* less CPU time. +**1)** It uses *much* less CPU time. **2)** Simultaneous transmit and receive is fully supported. @@ -28,15 +28,15 @@ There are five, nay, **six** advantages over `SoftwareSerial`: ``` #include NeoSWSerial ss( 4, 3 ); - + volatile uint32_t newlines = 0UL; - + static void handleRxChar( uint8_t c ) { if (c == '\n') newlines++; } - + void setup() { ss.attachInterrupt( handleRxChar ); @@ -68,4 +68,6 @@ void setup() } ``` -This class supports the following MCUs: ATtinyx61, ATtinyx4, ATtinyx5, ATmega328P (Pro, UNO, Nano), ATmega32U4 (Micro, Leonardo), ATmega2560 (Mega), ATmega2560RFR2, ATmega1284P and ATmega1286 +This class supports the following MCUs at 16MHz: ATmega328P (Pro, UNO, Nano), ATmega2560 (Mega), ATmega256RFR2 (Xplained Pro, Altair, Pinoccio), ATmega1284P (MightyCore), ATmega32U4 (Micro, Leonardo), ATtinyx61, ATtinyx4, ATtinyx5 (Trinket), and AT90USB1286 (Teensy++) + +These MCUs are also supported at 8MHz: ATmega328P (Pro, Fio, Feather 328), ATmega2560 (Mega), ATmega256RFR2, ATmega1284P (Mayfly, Mbili), ATmega32U4 (Feather 32U4, Flora), and ATtinyx5 (Trinket, Gemma). To run on 8MHz boards, NeoSWSerial must set another timer prescaler - timer4 on the 32U4, timer1 on the AtTiny, and timer2 on the others. For the vast majority of cases, this will not be a problem. But, if not used carefully, this will cause the tone() function (and possibly others) to behave strangely. It could also cause the [EnviroDIY SDI-12](https://github.com/EnviroDIY/Arduino-SDI-12) library (which was partly modeled on NeoSWSerial) to malfunction. To avoid these problems make sure that you ignore() or end() all instances of NeoSWSerial before using the other functions/libraries. You must then begin() or listen() again to restart NeoSWSerial. diff --git a/examples/passThrough/passThrough.ino b/examples/passThrough/passThrough.ino new file mode 100644 index 0000000..0dc0676 --- /dev/null +++ b/examples/passThrough/passThrough.ino @@ -0,0 +1,30 @@ +#include +#include + +uint16_t baudrate = 9600; + +NeoSWSerial nss( 10, 11 ); + +void setup(){ + Serial.begin(115200); + delay(50); + + while (!Serial){}; + + Serial.print( F("NeoSWSerial pass through @ ") ); + Serial.println( baudrate ); + Serial.flush(); + + nss.begin(baudrate); + delay(50); + +} + +void loop() { + while (nss.available()) { + Serial.write(nss.read()); + } + while (Serial.available()) { + nss.write(Serial.read()); + } +} diff --git a/library.properties b/library.properties index b908d25..31b1d8a 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=NeoSWSerial -version=3.0.5 +version=3.0.6 author=SlashDevin maintainer=SlashDevin sentence=An efficient replacement for SoftwareSerial at baud rates 9600, 19200 and 38400. diff --git a/src/NeoSWSerial.cpp b/src/NeoSWSerial.cpp index ce8edfe..fc8a251 100644 --- a/src/NeoSWSerial.cpp +++ b/src/NeoSWSerial.cpp @@ -42,19 +42,36 @@ static const uint8_t BITS_PER_TICK_31250_Q10 = 128; static const uint8_t BITS_PER_TICK_38400_Q10 = 157; // 1s/(38400 bits) * (1 tick)/(4 us) * 2^10 "multiplier" +// Choose the timer to use #if F_CPU == 16000000L - #define TCNTX TCNT0 - #define PCI_FLAG_REGISTER PCIFR + #define TCNTX TCNT0 // 8-bit timer w/ PWM, default prescaler has divisor of 64, thus 250kHz + #define PCI_FLAG_REGISTER PCIFR // Pin change interrupt flag register + +// Have to use alternate timer for an 8 MHz system because timer 0 doesn't have the correct prescaler #elif F_CPU == 8000000L #if defined(__AVR_ATtiny25__) | \ defined(__AVR_ATtiny45__) | \ - defined(__AVR_ATtiny85__) - #define TCNTX TCNT1 - #define PCI_FLAG_REGISTER GIFR + defined(__AVR_ATtiny85__) + #define TCNTX TCNT1 // 8-bit timer/counter w/ independent prescaler + #define PCI_FLAG_REGISTER GIFR // Pin change interrupt flag register + static uint8_t preNSWS_TCCR1; + + #elif defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__) + #define TCNTX TCNT4 // 10-bit high speed timer, usable as 8-bit timer by ignoring high bits + #define PCI_FLAG_REGISTER PCIFR // Pin change interrupt flag register + static uint8_t preNSWS_TCCR4A; + static uint8_t preNSWS_TCCR4B; + static uint8_t preNSWS_TCCR4C; + static uint8_t preNSWS_TCCR4D; + static uint8_t preNSWS_TCCR4E; + #else - #define TCNTX TCNT2 - #define PCI_FLAG_REGISTER PCIFR + #define TCNTX TCNT2 // 8-bit timer w/ PWM, asynchronous operation, and independent prescaler + #define PCI_FLAG_REGISTER PCIFR // Pin change interrupt flag register + static uint8_t preNSWS_TCCR2A; + static uint8_t preNSWS_TCCR2B; #endif + #endif static NeoSWSerial *listener = (NeoSWSerial *) NULL; @@ -76,6 +93,7 @@ static uint8_t rxTail; // buffer pointer output static uint8_t rxBitMask, txBitMask; // port bit masks static volatile uint8_t *txPort; // port register +static bool inv; //invert the signal on the line //#define DEBUG_NEOSWSERIAL #ifdef DEBUG_NEOSWSERIAL @@ -135,27 +153,56 @@ void NeoSWSerial::listen() if (listener) listener->ignore(); + inv = inverse; pinMode(rxPin, INPUT); rxBitMask = digitalPinToBitMask( rxPin ); rxPort = portInputRegister( digitalPinToPort( rxPin ) ); - txBitMask = digitalPinToBitMask( txPin ); - txPort = portOutputRegister( digitalPinToPort( txPin ) ); - if (txPort) - *txPort |= txBitMask; // high = idle - pinMode(txPin, OUTPUT); + if (txPin >= 0) { + txBitMask = digitalPinToBitMask( txPin ); + txPort = portOutputRegister( digitalPinToPort( txPin ) ); + + if (txPort) { + if (inv) + *txPort &= ~txBitMask; // set TX line low + else + *txPort |= txBitMask; // set TX line high + } + + pinMode(txPin, OUTPUT); + } else { + txPort = 0; + } - if (F_CPU == 8000000L) { + // Set the timer prescaling as necessary - want to be running at 250kHz + #if F_CPU == 8000000L // Have to use timer 2 for an 8 MHz system. #if defined(__AVR_ATtiny25__) | \ defined(__AVR_ATtiny45__) | \ - defined(__AVR_ATtiny85__) - TCCR1 = 0x06; // divide by 32 + defined(__AVR_ATtiny85__) + preNSWS_TCCR1 = TCCR1; + TCCR1 = 0x06; // 0b00000110 - Clock Select bits 12 & 11 on - prescaling source = CK/32 + // timer now running at 8MHz/32 = 250kHz + #elif defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__) + preNSWS_TCCR4A = TCCR4A; + preNSWS_TCCR4B = TCCR4B; + preNSWS_TCCR4C = TCCR4C; + preNSWS_TCCR4D = TCCR4D; + preNSWS_TCCR4D = TCCR4E; + TCCR4A = 0x00; // "normal" operation - Normal port operation, OC4A & OC4B disconnected + TCCR4B = 0x06; // 0b00000110 - Clock Select bits 42 & 41 on - prescaler set to CK/32 + // timer now running at 8MHz/32 = 250kHz + TCCR4C = 0x00; // "normal" operation - Normal port operation, OC4D0 disconnected + TCCR4D = 0x00; // No fault protection + TCCR4E = 0x00; // No register locks or overrides #else - TCCR2A = 0x00; - TCCR2B = 0x03; // divide by 32 + preNSWS_TCCR2A = TCCR2A; + preNSWS_TCCR2B = TCCR2B; + TCCR2A = 0x00; // "normal" operation - Normal port operation, OC2A & OC2B disconnected, normal waveform generation + TCCR2B = 0x03; // 0b00000011 - Clock Select bits 21 & 20 on - prescaler set to clkT2S/32 + // timer now running at 8MHz/32 = 250kHz #endif - } + #endif volatile uint8_t *pcmsk = digitalPinToPCMSK(rxPin); if (pcmsk) { @@ -164,27 +211,29 @@ void NeoSWSerial::listen() flush(); // Set up timings based on baud rate - + switch (_baudRate) { - case 9600: + case 9600: // default is 9600 txBitWidth = TICKS_PER_BIT_9600 ; bitsPerTick_Q10 = BITS_PER_TICK_38400_Q10 >> 2; rxWindowWidth = 10; break; case 31250: - if (F_CPU > 12000000L) { + if (F_CPU >= 12000000L) { txBitWidth = TICKS_PER_BIT_31250; bitsPerTick_Q10 = BITS_PER_TICK_31250_Q10; rxWindowWidth = 5; break; } // else use 19200 + __attribute__ ((fallthrough)); case 38400: - if (F_CPU > 12000000L) { + if (F_CPU >= 12000000L) { txBitWidth = TICKS_PER_BIT_9600 >> 2; bitsPerTick_Q10 = BITS_PER_TICK_38400_Q10 ; rxWindowWidth = 4; break; } // else use 19200 + __attribute__ ((fallthrough)); case 19200: txBitWidth = TICKS_PER_BIT_9600 >> 1; bitsPerTick_Q10 = BITS_PER_TICK_38400_Q10 >> 1; @@ -225,6 +274,24 @@ void NeoSWSerial::ignore() SREG = prevSREG; } + #if F_CPU == 8000000L + // Un-set the timer pre-scalers + #if defined(__AVR_ATtiny25__) | \ + defined(__AVR_ATtiny45__) | \ + defined(__AVR_ATtiny85__) + TCCR1 = preNSWS_TCCR1; + #elif defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__) + TCCR4A = preNSWS_TCCR4A; + TCCR4B = preNSWS_TCCR4B; + TCCR4C = preNSWS_TCCR4C; + TCCR4D = preNSWS_TCCR4D; + TCCR4E = preNSWS_TCCR4E; + #else + TCCR2A = preNSWS_TCCR2A; + TCCR2B = preNSWS_TCCR2B; + #endif + #endif + } // ignore //---------------------------------------------------------------------------- @@ -234,8 +301,10 @@ void NeoSWSerial::setBaudRate(uint16_t baudRate) if (( ( baudRate == 9600) || ( baudRate == 19200) || - ((baudRate == 31250) && (F_CPU == 16000000L)) || - ((baudRate == 38400) && (F_CPU == 16000000L)) + ( baudRate == 31250 ) || + ( baudRate == 38400 ) + // ((baudRate == 31250) && (F_CPU == 16000000L)) || + // ((baudRate == 38400) && (F_CPU == 16000000L)) ) && (_baudRate != baudRate)) { @@ -252,7 +321,7 @@ void NeoSWSerial::setBaudRate(uint16_t baudRate) int NeoSWSerial::available() { uint8_t avail = ((rxHead - rxTail + RX_BUFFER_SIZE) % RX_BUFFER_SIZE); - + if (avail == 0) { cli(); if (checkRxTime()) { @@ -268,11 +337,19 @@ int NeoSWSerial::available() //---------------------------------------------------------------------------- +int NeoSWSerial::peek() +{ + if (rxHead == rxTail) return -1; // Empty buffer? If yes, -1 + return rxBuffer[rxTail]; // Otherwise, read from "tail" +} // peek + +//---------------------------------------------------------------------------- + int NeoSWSerial::read() { - if (rxHead == rxTail) return -1; - uint8_t c = rxBuffer[rxTail]; - rxTail = (rxTail + 1) % RX_BUFFER_SIZE; + if (rxHead == rxTail) return -1; // Empty buffer? If yes, -1 + uint8_t c = rxBuffer[rxTail]; // Otherwise, grab char at tail + rxTail = (rxTail + 1) % RX_BUFFER_SIZE; // increment tail return c; @@ -305,47 +382,76 @@ void NeoSWSerial::startChar() void NeoSWSerial::rxISR( uint8_t rxPort ) { - uint8_t t0 = TCNTX; // time of data transition (plus ISR latency) - uint8_t d = rxPort & rxBitMask; // read RX data level + uint8_t t0 = TCNTX; // time of data transition (plus ISR latency) + if (inv) { + rxPort = ~rxPort; + } + uint8_t d = rxPort & rxBitMask; // read RX data level + // Check if we're ready for a start bit, and if this could possibly be it + // Otherwise, just ignore the interrupt and exit if (rxState == WAITING_FOR_START_BIT) { - - // If it looks like a start bit then initialize; - // otherwise ignore the rising edge and exit. - - if (d != 0) return; // it's high so not a start bit, exit + // If it is HIGH it's not a start bit, exit + if (d != 0) return; + // If it is LOW, this should be a start bit + // Thus set the rxStat to 0, create an empty character, and a new mask with a 1 in the lowest place startChar(); - } else { // data bit or stop bit (probably) received + } + + // if the character is incomplete, and this is not a start bit, + // then this change is from a data or stop bit + else { DBG_NSS_ARRAY(bitTransitionTimes, bitTransitions, (t0-prev_t0)); - // Determine how many bit periods have elapsed since the last transition. - + // check how many bit times have passed since the last change + // the rxWindowWidth is just a fudge factor uint16_t rxBits = bitTimes( t0-prev_t0 ); - uint8_t bitsLeft = 9 - rxState; // ignores stop bit + // calculate how many *data* bits should be left + // We know the start bit is past and are ignoring the stop bit + uint8_t bitsLeft = 9 - rxState; + // note that a new character *may* have started if more bits have been + // received than should be left. + // This will also happen if last bit(s) of the character are all 0's. bool nextCharStarted = (rxBits > bitsLeft); - if (nextCharStarted) + if (nextCharStarted) { DBG_NSS_ARRAY(rxStartCompletionBits,rxStartCompletions,(10*rxBits + bitsLeft)); + } - uint8_t bitsThisFrame = nextCharStarted ? bitsLeft : rxBits; + // check how many data bits have been sent in this frame + // If the total number of bits in this frame is more than the number of data + // bits remaining in the character, then the number of data bits is equal + // to the number of bits remaining for the character and partiy. If the total + // number of bits in this frame is less than the number of data bits left + // for the character and parity, then the number of data bits received + // in this frame is equal to the total number of bits received in this frame. + // translation: + // if nextCharStarted then bitsThisFrame = bitsLeft + // else bitsThisFrame = rxBits + uint8_t bitsThisFrame = nextCharStarted ? bitsLeft : rxBits; + // Tick up the rxState by that many bits rxState += bitsThisFrame; - // Set all those bits - + // Set all the bits received between the last change and this change + // If the current state is LOW (and it just became so), then all bits between + // the last change and now must have been HIGH. if (d == 0) { // back fill previous bits with 1's while (bitsThisFrame-- > 0) { - rxValue |= rxMask; - rxMask = rxMask << 1; + rxValue |= rxMask; // Add a 1 to the LSB/right-most place + rxMask = rxMask << 1;; // Shift the 1 in the mask up by one position } - rxMask = rxMask << 1; - } else { // d==1 + rxMask = rxMask << 1; // Shift the 1 in the mask up by one more position + } + // If the current state is HIGH (and it just became so), then this bit is HIGH + // but all bits between the last change and now must have been LOW + else { // d==1 // previous bits were 0's so only this bit is a 1. - rxMask = rxMask << (bitsThisFrame-1); - rxValue |= rxMask; + rxMask = rxMask << (bitsThisFrame-1); // Shift the 1 in the mask up by the number of bits past + rxValue |= rxMask; // Add that shifted one to the character being created } // If 8th bit or stop bit then the character is complete. @@ -353,14 +459,15 @@ void NeoSWSerial::rxISR( uint8_t rxPort ) if (rxState > 7) { rxChar( rxValue ); + // if this is HIGH, or we haven't exceeded the number of bits in a + // character (but have gotten all the data bits) then this should be a + // stop bit and we can start looking for a new start bit. if ((d == 1) || !nextCharStarted) { - rxState = WAITING_FOR_START_BIT; - // DISABLE STOP BIT TIMER - + rxState = WAITING_FOR_START_BIT; // DISABLE STOP BIT TIMER } else { - // The last char ended with 1's, so this 0 is actually - // the start bit of the next character. - + // If we just switched to LOW, or we've exceeded the total number of + // bits in a character, then the character must have ended with 1's/HIGH, + // and this new 0/LOW is actually the start bit of the next character. startChar(); } } @@ -377,6 +484,7 @@ bool NeoSWSerial::checkRxTime() if (rxState != WAITING_FOR_START_BIT) { uint8_t d = *rxPort & rxBitMask; + if (inv) d = ~d; if (d) { // Ended on a 1, see if it has been too long @@ -442,6 +550,8 @@ void NeoSWSerial::rxChar( uint8_t c ) NeoSWSerial::rxISR(pin); \ } } + // Only supported at 16MHz + // ?? Are there any boards that use one of these at 16Mhz? #if defined(__AVR_ATtiny261__) | \ defined(__AVR_ATtiny461__) | \ defined(__AVR_ATtiny861__) @@ -455,57 +565,64 @@ void NeoSWSerial::rxChar( uint8_t c ) } } + // Supported at 8 and 16 MHZ #elif defined(__AVR_ATtiny25__) | \ defined(__AVR_ATtiny45__) | \ - defined(__AVR_ATtiny85__) + defined(__AVR_ATtiny85__) - PCINT_ISR(0, PINB); + PCINT_ISR(0, PINB); // D0-D5 - #elif defined(__AVR_ATtiny24__) | \ - defined(__AVR_ATtiny44__) | \ - defined(__AVR_ATtiny84__) + // Only supported at 16MHz + // ?? Are there any boards that use one of these at 16Mhz? + #elif defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny24A__) || \ + defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny44A__) ||\ + defined(__AVR_ATtiny84__) || defined(__AVR_ATtiny84A__) - PCINT_ISR(0, PINA); - PCINT_ISR(1, PINB); + PCINT_ISR(0, PINA); // D0-D7 + PCINT_ISR(1, PINB); // D10, D9, D8, D11 - #elif defined(__AVR_ATmega328P__) + // Supported at 8MHz and 16MHz; conflicts with "tone" at 8MHz + #elif defined(__AVR_ATmega168__) || defined(__AVR_ATmega168A__) || \ + defined(__AVR_ATmega168P__) || defined(__AVR_ATmega168PA__) || \ + defined(__AVR_ATmega328__) || defined(__AVR_ATmega328P__) - PCINT_ISR(0, PINB); - PCINT_ISR(1, PINC); - PCINT_ISR(2, PIND); + PCINT_ISR(0, PINB); // D8-D13 + PCINT_ISR(1, PINC); // D14-D19 + PCINT_ISR(2, PIND); // D0-D7 - #elif defined(__AVR_ATmega32U4__) + // Supported at 8MHz and 16MHz + #elif defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__) - PCINT_ISR(0, PINB); + PCINT_ISR(0, PINB); // D17 (SS), D15 (SCK), D16 (MOSI), D14 (MISO), D8, D9, D10. D11, + // Only supported at 16MHz #elif defined(__AVR_AT90USB1286__) PCINT_ISR(0, PINB); - #elif defined(__AVR_ATmega2560__) + // Supported at 8MHz and 16MHz; conflicts with "tone" at 8MHz + #elif defined(__AVR_ATmega2560__) || defined(__AVR_ATmega2561__) || \ + defined(__AVR_ATmega1281__) || defined(__AVR_ATmega1280__ ) || \ + defined(__AVR_ATmega640__) - PCINT_ISR(0, PINB); - PCINT_ISR(1, PINJ); - PCINT_ISR(2, PINK); + PCINT_ISR(0, PINB); // D53-D50, D10-D13 + PCINT_ISR(1, PINJ); // D15-D14, D70-D74 (fake) + PCINT_ISR(2, PINK); // D62-D69 - #elif defined(__AVR_ATmega1281__) + // Supported at 8MHz and 16MHz; conflicts with "tone" at 8MHz + #elif defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega644P__) || \ + defined(__AVR_ATmega1284__) || defined(__AVR_ATmega644__) - PCINT_ISR(0, PINB); - // PCINT8 on PE0 not supported. Other 7 are on PJ0..6 - PCINT_ISR(1, PINJ); - PCINT_ISR(2, PINK); + PCINT_ISR(0, PINA); // D24-D31 (most) or D21-D14 (bobuino) + PCINT_ISR(1, PINB); // D0-D7 or D8-D15 (Mayfly/mbilli) or D4-D13 (bobuino) + PCINT_ISR(2, PINC); // D16-D23 (most) or D22-D29 (bobuino) + PCINT_ISR(3, PIND); // D8-D15 or D0-D7 (Mayfly/mbilli) or D0-D3, D30, D8-D9, D31 (bobuino) - #elif defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega644P__) + // Supported at 8MHz and 16MHz; conflicts with "tone" at 8MHz + #elif defined(__AVR_ATmega256RFR2__) - PCINT_ISR(0, PINA); - PCINT_ISR(1, PINB); - PCINT_ISR(2, PINC); - PCINT_ISR(3, PIND); - - #elif defined(__AVR_ATmega2560RFR2__) - - PCINT_ISR(0, PINB); - PCINT_ISR(1, PINE); + PCINT_ISR(0, PINB); // PIN?? + PCINT_ISR(1, PINE); // PIN?? #else #error MCU not supported by NeoSWSerial! @@ -527,7 +644,7 @@ size_t NeoSWSerial::write(uint8_t txChar) uint8_t width; // ticks for one bit uint8_t txBit = 0; // first bit is start bit - uint8_t b = 0; // start bit is low + bool b = false; // start bit is logic 0 uint8_t PCIbit = bit(digitalPinToPCICRbit(rxPin)); uint8_t prevSREG = SREG; @@ -535,13 +652,13 @@ size_t NeoSWSerial::write(uint8_t txChar) uint8_t t0 = TCNTX; // start time - // TODO: This would benefit from an early break after + // TODO: This would benefit from an early break after // the last 0 data bit. Then we could wait for the - // remaining 1 data bits and stop bit with interrupts + // remaining 1 data bits and stop bit with interrupts // re-enabled. while (txBit++ < 9) { // repeat for start bit + 8 data bits - if (b) // if bit is set + if (b != inv) // if desired state is high *txPort |= txBitMask; // set TX line high else *txPort &= ~txBitMask; // else set TX line low @@ -551,7 +668,7 @@ size_t NeoSWSerial::write(uint8_t txChar) (width == TICKS_PER_BIT_9600/4) && (txBit & 0x01)) { // The width is 6.5 ticks, so add a tick every other bit - width++; + width++; } // Hold the line for the bit duration @@ -572,11 +689,17 @@ size_t NeoSWSerial::write(uint8_t txChar) // Q: would a signed >> pull in a 1? } - *txPort |= txBitMask; // stop bit is high + // stop bit is logic 1 + if (inv) + *txPort &= ~txBitMask; // else set TX line low + else + *txPort |= txBitMask; // set TX line high + SREG = prevSREG; // interrupts on for stop bit while ((uint8_t)(TCNTX - t0) < width) { - if (checkRxTime()) + if (checkRxTime()) { DBG_NSS_COUNT(stopBitCompletions); + } } return 1; // 1 character sent diff --git a/src/NeoSWSerial.h b/src/NeoSWSerial.h index 1e54499..78e9e14 100644 --- a/src/NeoSWSerial.h +++ b/src/NeoSWSerial.h @@ -24,8 +24,8 @@ // This software serial library is intended as a more-efficient replacement // for SoftwareSerial at baud rates 9600, 19200 and 38400. // -// Any of the pins supported by SoftwareSerial may be used. Pins (0-19) -// on the Uno may be used. Other boards can use any of the pins +// Any of the pins supported by SoftwareSerial may be used. Pins (0-19) +// on the Uno may be used. Other boards can use any of the pins // allowed by digitalPinToPCMSK in pins_arduino.h // // This code uses a pin change interrupt on the selected RX pin. @@ -42,9 +42,9 @@ // Supported baud rates are 9600 (default), 19200 and 38400. // The baud rate is selectable at run time. // -// The size of the RX buffer may be changed by editing the -// accompanying .cpp file. For optimal performance of the interrupt -// service routines, the buffer size should be chosen to be a +// The size of the RX buffer may be changed by editing the +// accompanying .cpp file. For optimal performance of the interrupt +// service routines, the buffer size should be chosen to be a // power of 2 (i.e., 2, 4, 8, 16, 32, 64,...). // // v1.0 Nov 2014 jboyton - Created @@ -65,22 +65,23 @@ class NeoSWSerial : public Stream NeoSWSerial & operator =( const NeoSWSerial & ); // Not allowed public: - NeoSWSerial(uint8_t receivePin, uint8_t transmitPin) + NeoSWSerial(int8_t receivePin, int8_t transmitPin, bool inverse_logic = false) { rxPin = receivePin; txPin = transmitPin; + inverse = inverse_logic; _isr = (isr_t) NULL; } void begin(uint16_t baudRate=9600); // initialize, set baudrate, listen void listen(); // enable RX interrupts void ignore(); // disable RX interrupts - void setBaudRate(uint16_t baudRate); // 9600 [default], 19200, 38400 + void setBaudRate(uint16_t baudRate); // 9600 [default], 19200, 31250, 38400 virtual int available(); virtual int read(); virtual size_t write(uint8_t txChar); using Stream::write; // make the base class overloads visible - virtual int peek() { return 0; }; + virtual int peek(); virtual void flush() {}; void end() { ignore(); } @@ -89,7 +90,8 @@ class NeoSWSerial : public Stream void detachInterrupt() { attachInterrupt( (isr_t) NULL ); }; private: - uint8_t rxPin, txPin; + int8_t rxPin, txPin; + bool inverse; volatile uint8_t *rxPort; uint16_t _baudRate; @@ -104,7 +106,10 @@ class NeoSWSerial : public Stream public: // visible only so the ISRs can call it... static void rxISR( uint8_t port_input_register ); + static bool invert; // invert levels on line - //#define NEOSWSERIAL_EXTERNAL_PCINT // uncomment to use your own PCINT ISRs + #ifndef NEOSWSERIAL_EXTERNAL_PCINT + #define NEOSWSERIAL_EXTERNAL_PCINT // uncomment to use your own PCINT ISRs + #endif }; #endif diff --git a/test/test.ino b/test/test.ino deleted file mode 100644 index ec228aa..0000000 --- a/test/test.ino +++ /dev/null @@ -1,414 +0,0 @@ -#include - -NeoSWSerial nss( 52, 53 ); - - uint16_t baudrate = 9600; - uint8_t rc[520]; - uint16_t received = 0; - uint16_t sent = 0; - -//--------------------------------------------------------------------- -//#define INTER_CHAR_TIME 50 -#define INTER_CHAR_TIME 0 - -//#define BITDUMP - -#ifdef BITDUMP - extern uint8_t bitTransitionTimes[]; - extern uint8_t bitTransitions; - struct rbd_t { uint8_t loop_bits; uint8_t mul_bits; uint16_t prod; }; - extern rbd_t diffRXbits[]; - extern uint8_t diffRXbitsCount; - - extern uint16_t availCompletions; - extern uint16_t rxStartCompletions; - extern uint8_t rxStartCompletionBits[]; - extern uint16_t checkRxCompletions; - extern uint16_t polledPCI; - extern uint16_t polledPCICompletions; - extern uint16_t stopBitCompletions; - extern uint16_t highBitWaits; -#endif - -//--------------------------------------------------------------------- - -static void dumpBits() -{ -#ifdef BITDUMP - for (uint8_t i=0; i 0) { - Serial.print( F(" (") ); - for (uint8_t i=0;;) { - Serial.print( diffRXbits[i].prod ); - Serial.print( ' ' ); - Serial.print( diffRXbits[i].mul_bits ); - Serial.print( F(" != ") ); - Serial.print( diffRXbits[i].loop_bits ); - i++; - if (i >= diffRXbitsCount) - break; - Serial.print( F(", ") ); - } - Serial.print( ')' ); - diffRXbitsCount = 0; - } -#endif -} // dumpDiffs - -//--------------------------------------------------------------------- - -static uint16_t errors = 0; - -void testOne( Stream & ins, Stream & outs, uint8_t c ) -{ - uint8_t pair[2]; - pair[0] = c; - pair[1] = c+1; - uint8_t len = 1; - outs.write( pair, len ); - if (INTER_CHAR_TIME < 10) - outs.flush(); - - static bool dotPrinted = false; - - uint8_t received = 0; - bool gotIt = false; - uint32_t start = millis(); - - do { - if (ins.available()) { - uint8_t received_c = ins.read(); - - dotPrinted = false; - - gotIt = (received_c == c); - - if (!gotIt || (INTER_CHAR_TIME > 10)) { - Serial.print( F("rx ") ); - if (received_c < 0x10) Serial.print( '0' ); - Serial.print( received_c, HEX ); - - if (!gotIt) { - Serial.print( F(" != tx ") ); - if (c < 0x10) Serial.print( '0' ); - Serial.print( 0xFF & (c), HEX ); - } - - if (received == 0) { - dumpBits(); - dumpDiffs(); - Serial.print( ' ' ); - } - } - - received++; - c++; - } - } while (millis() - start < INTER_CHAR_TIME); - - if (INTER_CHAR_TIME > 10) { - if (!received) { - Serial.print( '.' ); - dumpBits(); - dumpDiffs(); - Serial.println(); - dotPrinted = true; - } else - Serial.println(); - - Serial.flush(); - } - -} // testOne - -//--------------------------------------------------------------------- - -static void testTwo( uint8_t & c, uint8_t & received_c ) -{ - if (nss.available()) { - uint8_t rcActual = nss.read(); - if (rcActual != received_c) { - errors++; - Serial.print( F("rx ") ); - if (received_c < 0x10) Serial.print( '0' ); - Serial.print( 0xFF & rcActual, HEX ); - Serial.print( F(" != tx ") ); - if (received_c < 0x10) Serial.print( '0' ); - Serial.print( 0xFF & (received_c), HEX ); - dumpBits(); - dumpDiffs(); - Serial.println(); - } - received_c++; - } -} // testTwo - -//--------------------------------------------------------------------- - -static void printStats() -{ - if (received != sent) { - Serial.print( received ); - Serial.print( F(" received, ") ); - Serial.print( sent ); - Serial.println( F(" sent") ); - } - - Serial.print( errors ); - Serial.println( F(" errors") ); -#ifdef BITDUMP - Serial.print( rxStartCompletions ); - Serial.print( F(" RX start completions:") ); - for (uint16_t i=0; i < rxStartCompletions; i++) { - Serial.print( ' ' ); - Serial.print( rxStartCompletionBits[i] ); - } - Serial.println(); - - Serial.print( availCompletions ); - Serial.println( F(" available() completions") ); - Serial.print( checkRxCompletions ); - Serial.println( F(" checkRxTime completions") ); - Serial.print( polledPCI ); - Serial.println( F(" polled PCI detected") ); - Serial.print( polledPCICompletions ); - Serial.println( F(" polled PCI completions") ); - Serial.print( stopBitCompletions ); - Serial.println( F(" stop bit completions") ); - Serial.print( highBitWaits ); - Serial.println( F(" high bit waits") ); - - rxStartCompletions = 0; - availCompletions = 0; - checkRxCompletions = 0; - polledPCI = 0; - polledPCICompletions = 0; - stopBitCompletions = 0; - highBitWaits = 0; -#endif - - Serial.println( F("-----------------") ); - Serial.flush(); - - received = 0; - sent = 0; - errors = 0; - -} // printStats - -//--------------------------------------------------------------------- - -static void printRC() -{ - for (uint16_t i=0; i < received; i++) { - uint8_t c = rc[i]; - if (c < 0x10) Serial.print( '0' ); - Serial.print( 0xFF & (c), HEX ); - Serial.print( ' ' ); - if ((i & 0x1F) == 0x1F) - Serial.println(); - } - Serial.println(); -} // printRC - -//--------------------------------------------------------------------- - -void setup() { - //Initialize serial and wait for port to open: - Serial.begin(9600); - while (!Serial) - ; - - Serial3.begin( baudrate ); - delay( 10 ); - Serial3.print( 'U' ); - Serial3.flush(); - delay( 10 ); - - nss.begin( baudrate ); - -} // setup - -//--------------------------------------------------------------------- - -void loop() -{ - Serial.print( F("NeoSWSerial test @ ") ); - Serial.println( baudrate ); - Serial.flush(); - - Serial.println( F("Individual RX test") ); - Serial.flush(); - - uint8_t c=0; - do { - testOne( nss, Serial3, c ); - c++; - } while (c != 0); - testOne( nss, Serial3, c ); - uint32_t start = millis(); - while (millis() - start < 100) { - if (nss.available()) { - nss.read(); - start = millis(); - } - } - - printStats(); - - //===================================== - - Serial.println( F("RX test") ); - Serial.flush(); - - for (uint8_t times=0; times<1; times++) { - do { - Serial3.write( c++ ); - sent++; - if (nss.available()) - rc[ received++ ] = nss.read(); - } while (c != 0); - } - Serial3.write( c++ ); - sent++; - - start = millis(); - while (millis() - start < 100) { - if (nss.available()) { - rc[ received++ ] = nss.read(); - start = millis(); - } - } - - if (received < sent) { - Serial.print( sent-received ); - Serial.println( F(" chars dropped.") ); - } - printRC(); - - printStats(); - - //===================================== - - Serial.println( F("TX test") ); - Serial.flush(); - - for (uint16_t i=0; i<=256; i++) { - nss.write( (uint8_t) (i & 0xFF) ); - if (Serial3.available()) - rc[ received++ ] = Serial3.read(); - } - - start = millis(); - - uint32_t ms; - do { - ms = millis(); - if (Serial3.available()) { - start = ms; - rc[ received++ ] = Serial3.read(); - } - } while (ms - start < 100); - - printRC(); - - printStats(); - - //===================================== - - Serial.println( F("RX and TX test") ); - Serial.flush(); - - for (uint16_t i=0; i<=256; i++) { - Serial3.write( (uint8_t) i & 0xFF ); - if (nss.available()) { - c = nss.read(); - nss.write( c ); - } - if (Serial3.available()) - rc[ received++ ] = Serial3.read(); - } - - start = millis(); - - do { - ms = millis(); - if (nss.available()) { - start = ms; - c = nss.read(); - nss.write( c ); - } - if (Serial3.available()) { - start = ms; - rc[ received++ ] = Serial3.read(); - } - } while (ms - start < 100); - - printRC(); - - printStats(); - - //===================================== - - Serial.println( F("TX and RX test") ); - Serial.flush(); - - for (uint16_t i=0; i<=256; i++) { - nss.write( (uint8_t) i & 0xFF ); - if (Serial3.available()) { - c = Serial3.read(); - Serial3.write( c ); - } - while (nss.available()) - rc[ received++ ] = nss.read(); - } - - start = millis(); - - do { - ms = millis(); - if (Serial3.available()) { - start = ms; - c = Serial3.read(); - Serial3.write( c ); - } - while (nss.available()) { - start = ms; - rc[ received++ ] = nss.read(); - } - } while (ms - start < 100); - - printRC(); - - printStats(); - - //===================================== - - while (!Serial.available()) - ; - - do { - while (Serial.available()) { - Serial.read(); - start = millis(); - } - } while (millis() - start < 20); - - if (baudrate == 38400) - baudrate = 9600; - else - baudrate <<= 1; - nss.setBaudRate( baudrate ); - Serial3.begin( baudrate ); -} diff --git a/tests/bounce/bounce.ino b/tests/bounce/bounce.ino new file mode 100644 index 0000000..1e7b4e9 --- /dev/null +++ b/tests/bounce/bounce.ino @@ -0,0 +1,62 @@ +#include + +uint16_t baudrate = 9600; +HardwareSerial &checkStream1 = Serial1; +HardwareSerial &checkStream2 = Serial2; + +// prints a single character prettily +void printCharHex(uint8_t c) +{ + if (c < 0x10) Serial.print( '0' ); + Serial.print( 0xFF & (c), HEX ); + Serial.print( ' ' ); + if ((c & 0x0F) == 0x0F) + Serial.println(); + if ((c & 0xFF) == 0xFF) + Serial.println(); +} // printChar + +void setup(){ + Serial.begin(115200); + delay(50); + checkStream1.begin(baudrate); + delay(50); + checkStream2.begin(baudrate); + delay(50); + + Serial.print(F("Serial bouncer at ")); + Serial.println(baudrate); + +} + +void loop() { + if (checkStream1.available()) + { + uint8_t c = checkStream1.read(); + printCharHex(c); + checkStream1.write(c); + } + if (checkStream2.available()) + { + uint8_t c = checkStream2.read(); + printCharHex(c); + checkStream2.write(c); + } + + // if given input on serial, repeat everything at the next baud rate + if ( Serial.available() ) { + Serial.println(Serial.readString()); + + if (baudrate == 38400) + baudrate = 31250; + else if (baudrate == 31250) + baudrate = 9600; + else + baudrate <<= 1; + checkStream1.begin( baudrate ); + checkStream2.begin( baudrate ); + + Serial.print(F("Serial bouncer at ")); + Serial.println(baudrate); + } +} diff --git a/tests/test/test.ino b/tests/test/test.ino new file mode 100644 index 0000000..ce6c6d6 --- /dev/null +++ b/tests/test/test.ino @@ -0,0 +1,557 @@ +#include + +NeoSWSerial nss( 10, 11 ); + +HardwareSerial &checkSerial = Serial1; +// NeoSWSerial &checkSerial = nss; + +uint16_t baudrate = 9600; +uint8_t rc[770]; // Received character array +uint8_t sc[770]; // Sent character array - only for error checking +uint16_t received = 0; // # characters received +uint16_t dropped = 0; // # characters received +uint16_t sent = 0; // # characters sent +static uint16_t errors = 0; // # errors + +//--------------------------------------------------------------------- +#define INTER_CHAR_TIME 50 +// #define INTER_CHAR_TIME 0 + +//#define BITDUMP + +#ifdef BITDUMP + #define DEBUG_NEOSWSERIAL + extern uint8_t bitTransitionTimes[]; + extern uint8_t bitTransitions; + struct rbd_t { uint8_t loop_bits; uint8_t mul_bits; uint16_t prod; }; + extern rbd_t diffRXbits[]; + extern uint8_t diffRXbitsCount; + + extern uint16_t availCompletions; + extern uint16_t rxStartCompletions; + extern uint8_t rxStartCompletionBits[]; + extern uint16_t checkRxCompletions; + extern uint16_t polledPCI; + extern uint16_t polledPCICompletions; + extern uint16_t stopBitCompletions; + extern uint16_t highBitWaits; +#endif + +//--------------------------------------------------------------------- + +static void dumpBits() +{ +#ifdef BITDUMP + for (uint8_t i=0; i 0) { + Serial.print( F(" (") ); + for (uint8_t i=0;;) { + Serial.print( diffRXbits[i].prod ); + Serial.print( ' ' ); + Serial.print( diffRXbits[i].mul_bits ); + Serial.print( F(" != ") ); + Serial.print( diffRXbits[i].loop_bits ); + i++; + if (i >= diffRXbitsCount) + break; + Serial.print( F(", ") ); + } + Serial.print( ')' ); + diffRXbitsCount = 0; + } +#endif +} // dumpDiffs + + +void emptyBuffer ( Stream & ins ) { + uint32_t start = millis(); + while (millis() - start < 100) { + if (ins.available()) { + ins.read(); + start = millis(); + } + } +} + +//--------------------------------------------------------------------- + +// prints a single character prettily +void printCharHex(uint8_t c) +{ + if (c < 0x10) Serial.print( '0' ); + Serial.print( 0xFF & (c), HEX ); +} // printChar + +// prints the whole received character array +void printRC() +{ + Serial.println(F("Received: ")); + for (uint16_t i=0; i < received; i++) { + uint8_t c = rc[i]; + printCharHex(c); + Serial.print( ' ' ); + if ((i & 0x1F) == 0x1F) + Serial.println(); + } + Serial.println(); +} // printRC + +// prints the whole sent character array +void printSC() +{ + Serial.println(F("Sent: ")); + for (uint16_t i=0; i < sent; i++) { + uint8_t c = sc[i]; + printCharHex(c); + Serial.print( ' ' ); + if ((i & 0x1F) == 0x1F) + Serial.println(); + } + Serial.println(); +} // printSC + +// Checks for errors in the receive array vs the sc array +void checkRC() +{ + // printSC(); + // printRC(); + for (uint16_t i=0; i < received; i++) { + uint8_t cActual = rc[i]; + uint8_t cExpected = sc[i]; + if (cActual != cExpected) { + errors++; + // Serial.print( F("rx ") ); + // printCharHex(cActual); + // Serial.print( F(" != tx ") ); + // printCharHex(cExpected); + // Serial.println(); + } + } +} // checkRC + +static void printStats() +{ + if (received != sent) { + Serial.print( received ); + Serial.print( F(" received, ") ); + Serial.print( sent ); + Serial.println( F(" sent") ); + } + if (received < sent) { + Serial.print( sent-received ); + Serial.println( F(" chars dropped.") ); + } + + + Serial.print( errors ); + Serial.println( F(" errors") ); +#ifdef BITDUMP + Serial.print( rxStartCompletions ); + Serial.print( F(" RX start completions:") ); + for (uint16_t i=0; i < rxStartCompletions; i++) { + Serial.print( ' ' ); + Serial.print( rxStartCompletionBits[i] ); + } + Serial.println(); + + Serial.print( availCompletions ); + Serial.println( F(" available() completions") ); + Serial.print( checkRxCompletions ); + Serial.println( F(" checkRxTime completions") ); + Serial.print( polledPCI ); + Serial.println( F(" polled PCI detected") ); + Serial.print( polledPCICompletions ); + Serial.println( F(" polled PCI completions") ); + Serial.print( stopBitCompletions ); + Serial.println( F(" stop bit completions") ); + Serial.print( highBitWaits ); + Serial.println( F(" high bit waits") ); + + // Zero everything after we've printed it + rxStartCompletions = 0; + availCompletions = 0; + checkRxCompletions = 0; + polledPCI = 0; + polledPCICompletions = 0; + stopBitCompletions = 0; + highBitWaits = 0; +#endif + + Serial.println( F("-----------------") ); + Serial.flush(); + + // Zero everything after we've printed it + received = 0; + sent = 0; + errors = 0; + +} // printStats + + +//--------------------------------------------------------------------- + +// In the first round of testing, the sketch waits for the echo of each character. + +// With 2 Arduinos, this tests whether the send process works independently +// from the receive process (the 2nd Arduino echo doesn't begin until the 1st +// Arduino is done sending). +// With 1 Arduino + loopback wires, this tests whether the receive process works +// during the transmit process (each sent bit is simulataneously received). + +// And by incrementing the sent character, all byte values can be tested. + +void testOne( Stream & ins, Stream & outs, uint8_t c ) +{ + + // Write one character out and wait for it to finish + uint8_t pair[2]; + pair[0] = c; + pair[1] = c+1; + uint8_t len = 1; + outs.write( pair, len ); + sent++; + if (INTER_CHAR_TIME < 10) + outs.flush(); + + bool gotIt = false; + uint32_t start = millis(); + + do { + if (ins.available()) { + uint8_t received_c = ins.read(); + + gotIt = (received_c == c); + + if (!gotIt /*|| (INTER_CHAR_TIME > 10*/) { + Serial.print( F("rx ") ); + printCharHex(received_c); + + if (!gotIt) { + Serial.print( F(" != tx ") ); + printCharHex(c); + errors++; + } + + if (received == 0) { + dumpBits(); + dumpDiffs(); + Serial.print( ' ' ); + } + + Serial.println(); + } + + received++; + c++; + } + } while (millis() - start < INTER_CHAR_TIME + 10); // Give a little time for bounce with two units + + /*if (INTER_CHAR_TIME > 10) { + if (!received) { + Serial.print( '.' ); + dumpBits(); + dumpDiffs(); + Serial.println(); + } else + Serial.println(); + + Serial.flush(); + }*/ + +} // testOne + + +// In the second round of testing, the sketch sends the same characters as fast +// as it can (no inter-character wait time). +// NeoSWSerial writes each byte immediately; there is no TX buffer. + +// With 2 Arduinos, the echo from the second Arduino begins coming in after the +// 1st Arduino finishes sending the first byte. Then the 1st Arduino starts +// sending the second byte. + +// With 1 Arduino + loopback wires, the first character arrives at the same time +// it is being sent. + +// This tests the simultaneous sending and receiving parts of NeoSWSerial, +// synchronously (1 Arduino + loopback wires) and asynchronously (2 Arduinos). + +void testTwo( Stream & ins, Stream & outs, uint8_t numRepeats ) +{ + for (uint8_t times=0; times