NeoSWSerial - SoftwareSerial replacement with attachInterrupt

I have just published NeoSWSerial, a C++ library that is in the same category as NeoHWSerial and NeoICSerial. I have modified jboyton's excellent gSoftSerial library to support interrupt-driven character processing, and he has generously offered to let me support debug publish it. :wink:

As stated in the other threads, I have seen occasional questions about handling received characters with interrupts. Many GPS users have been stymied by serial libraries only providing polled reading/events, which frequently leads to input buffer overflow.

I extended his library to support multiple pins (not concurrently), multiple boards, and I also added an attachInterrupt method, identical to the pre-existing [

attachInterrupt

](https://www.arduino.cc/en/Reference/AttachInterrupt) for digital Pin Change interrupts. Whenever a character is received, it calls the registered procedure from the ISR:

static void handleRxChar( uint8_t c )
{
  gps.decode( c );
}

void setup()
{
  serial_port.attachInterrupt( handleRxChar );
  serial_port.begin( 9600 );
}

I have also updated NeoGPS to use this class, in all examples if the header is included, but especially in the NMEA_isr example to handle GPS characters as they are received. Be sure to see the github page for limitations and advantages.

Thanks are also due to Robin2 for original work in this thread, and to robtillart for helpful posts on a similar topic here.

Cheers,
/dev

After removing some previous limitations, NeoSWSerial is now a complete drop-in replacement for SoftwareSerial, at bauds 9600, 19200 and 38400. NeoSWSerial does not disable interrupts while receiving characters.

An additional advantage is that it does not require any extra timers -- it runs completely off of pin change interrupts and the millis() timer, TIMER0.

Cheers,
/dev

38400 character/second * 10 bits/character = 384000 bits/second
1/384000 = 2.6uS per bit, pretty fast rate for PCINTs. Data reception is showing to be reliable at that speed?

38400 character/second * 10 bits/character = 384000 bits/second

Other way around!
38400 Baud is that many bits/second, so 3840 10-bit characters per second.

NeoSWSerial now supports simultaneous RX and TX!

Cheers,
/dev

Hi /Dev it's weird how we keep meeting on this forum,

Regarding your statement that NeoSWSerial works on a 1284p; is this confirmed?

I have a 1284p based Moteino Mega that just will not communicate with a hm-10 bluetooth module over standard software serial. If I connect it to HW serial (Serial1) all is well, but I get no comms link using the standard software serial library, and after hours of searching online I found that the 1284p does not play well with it.

So I saw your SWSerial library and rejoiced! I changed my code to this......

#include <NeoSWSerial.h>



int bluetoothTx = 14;  // TX-O pin of bluetooth
int bluetoothRx = 13;  // RX-I pin of bluetooth

NeoSWSerial bluetooth(bluetoothTx, bluetoothRx);

void setup()
{
 

  Serial.begin(9600);  // Begin the serial monitor at 9600bps


  delay(100);  // Short delay, wait for the Mate to send back CMD
  bluetooth.begin(9600);  // Start bluetooth serial at 9600
  Serial.print("init");

}

void loop()
{
  if(bluetooth.available())  // If the bluetooth sent any characters
  {
    // Send any characters the bluetooth prints to the serial monitor
    Serial.print((char)bluetooth.read());  
  }
  if(Serial.available())  // If stuff was typed in the serial monitor
  {
    // Send any characters the Serial monitor prints to the bluetooth
    bluetooth.print((char)Serial.read());
  }
  // and loop forever and ever!
}

But still get nothing.

If you can confirm that your library does work with 1284p's it will help me discover what is going wrong. Or I can help you get your library to work if not :slight_smile:

P.S. both HW serial ports are in use, so I need software serial

I am working with Xbees and ethernet, everything is going well but the serial comunication btw arduinos isnt working well, the arduino who hold the ethernet module is not reading and buffering the incoming from the other arduino..

If you can confirm that your library does work with 1284p's it will help me discover what is going wrong. Or I can help you get your library to work if not :slight_smile:

I do not have a 1284P to try, but NeoSWSerial does have a section for it. The key is making sure the RX pin can be used for a Pin Change Interrupt. This usually means looking at

~~ ~~    <Arduino-install-dir>/hardware/Arduino/avr/variants/xxx/pins_arduino.h~~ ~~

** **     Documents/Arduino/hardware/MightyCore/avr/variants/standard/pins_arduino.h** **

Also, I was momentarily confused by your Rx/Tx naming... I would suggest this Arduino-centric naming as more common:

const int bluetoothRx = 14;  // TX-O pin of bluetooth
const int bluetoothTx = 13;  // RX-I pin of bluetooth

NeoSWSerial bluetooth(bluetoothRx, bluetoothTx);

According to this, pin 14 must be correctly mapped to the corresponding PCICR and PCMSK registers/bits in pins_arduino.h.

Here is an excerpt from a 1284P variant I found at maniacbug's github:

#define digitalPinToPCICRbit(p) (((p) <= 7) ? 1 : (((p) <= 15) ? 3 : (((p) <= 23) ? 2 : 0)))
#define digitalPinToPCMSK(p)    (((p) <= 7) ? (&PCMSK2) : (((p) <= 13) ? (&PCMSK0) : (((p) <= 21) ? (&PCMSK1) : ((uint8_t *)0))))

The PCICRbit looks good, but the PCMSK doesn't look right to me... Here's an excerpt from the "standard" variant:

#define digitalPinToPCICRbit(p) (((p) <= 7) ? 2 : (((p) <= 13) ? 0 : 1))
#define digitalPinToPCMSK(p)    (((p) <= 7) ? (&PCMSK2) : (((p) <= 13) ? (&PCMSK0) : (((p) <= 21) ? (&PCMSK1) : ((uint8_t *)0))))

Note how PCICR 2/0/1 is returned for 0-7 / 8-13 / 14+, and the corresponding PCMSK 2/0/1/null is returned for 0-7 / 8-13 / 14-21 / 21+.

I would have expected this in the 1284P pins_arduino.h, to match PCICRbit:

#define digitalPinToPCICRbit(p) (((p) <= 7) ? 1 : (((p) <= 15) ? 3 : (((p) <= 23) ? 2 : 0)))
#define digitalPinToPCMSK(p)    (((p) <= 7) ? (&PCMSK1) : (((p) <= 15) ? (&PCMSK3) : (((p) <= 23) ? (&PCMSK2) : (((p) <= 31) ? (&PCMSK0) : ((uint8_t *)0)))))

That would be PCICR 1/3/2/0 for pins 0-7 / 8-15 / 16-23 / 24-31 for the corresponding PCMSK 1/3/2/0/null for pins 0-7 / 8-15 / 16-23 / 24-31 / 32+

Where did you get the "boards" files? Maybe a cut & paste error? It looks like it has the original "standard" definition, not one that would work for a 1284P. LOL, here's a similar complaint (and fix?).

Cheers,
/dev


P.S. @wkpedro, did you have a question about NeoSWSerial? If not, you may need to start your own thread with specific questions.

Confirmed, Jack Christensen has fixed it on his github repo here. (Your related thread here.)

No, i will give it a try for my project with arduino +ethernet + xbee

I can confirm that your library does now work on my 1284p. The issue was with my Moteino core files. I temporarily fixed it by selecting the "maniacbug 1284p" board in arduino IDE and re-uploading. Since then LowPowerLab has fixed his core files after I pointed out the problem.

Hi dev, did NeoSWSerial support Attiny85? I tried to test it but it fail to compile with error as below.

"C:\Program Files (x86)\Arduino\hardware\tools\avr/bin/avr-g++" -c -g -Os -w -std=gnu++11 -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -MMD -mmcu=attiny85 -DF_CPU=16000000L -DARDUINO=10611 -DARDUINO_AVR_ATTINYX5 -DARDUINO_ARCH_AVR "-IC:\Program Files (x86)\Arduino\hardware\ATTinyCore\avr\cores\tiny" "-IC:\Program Files (x86)\Arduino\hardware\ATTinyCore\avr\variants\tinyX5" "-IC:\Program Files (x86)\Arduino\libraries\NeoSWSerial" "-IC:\Program Files (x86)\Arduino\libraries\IRremote" "C:\Program Files (x86)\Arduino\libraries\NeoSWSerial\NeoSWSerial.cpp" -o "C:\Users\limck01\AppData\Local\Temp\build74bb293a6625b790e30578cdce40a20b.tmp\libraries\NeoSWSerial\NeoSWSerial.cpp.o"
C:\Program Files (x86)\Arduino\libraries\NeoSWSerial\NeoSWSerial.cpp: In member function 'void NeoSWSerial::listen()':

C:\Program Files (x86)\Arduino\libraries\NeoSWSerial\NeoSWSerial.cpp:124:5: error: 'TCCR2A' was not declared in this scope

TCCR2A = 0x00;

^

C:\Program Files (x86)\Arduino\libraries\NeoSWSerial\NeoSWSerial.cpp:125:5: error: 'TCCR2B' was not declared in this scope

TCCR2B = 0x03; // divide by 32

^

C:\Program Files (x86)\Arduino\libraries\NeoSWSerial\NeoSWSerial.cpp: In member function 'virtual size_t NeoSWSerial::write(uint8_t)':

C:\Program Files (x86)\Arduino\libraries\NeoSWSerial\NeoSWSerial.cpp:505:13: error: 'PCIFR' was not declared in this scope

if (PCIFR & PCIbit) {

^

does NeoSWSerial support Attiny85?

Not right now, nor does it support the ATmega32U4. I've been looking at it, but I don't have an ATtiny85 to test it with.

Would you be willing to test it? Do you have a second Arduino? It would be best if you had a logic analyzer or oscilloscope to verify the timings. And a fair warning: "Remote debugging" like this could be frustrating...

Cheers,
/dev

Thanks for the info.

i am willing to test it but i have very limited knowledge in mcu and programming. i am mechanical background. i have 2 Arduino Uno with me but don't have logic analyzer or oscilloscope to verify timing.

Some recent contributions to the library:

  • ATmega644P is now supported
  • NeoSWSerial can coexist with other PinChange interrupt handlers

I also have a contribution for inverse bit logic, but I need some help testing it. Any volunteers with two Arduinos, a logic analyzer and/or oscilloscope to confirm timings?

Cheers,
/dev

Is there any way to make this library coexist with the standard SoftwareSerial in the same sketch? I have two (plus USB) serial devices & I can't get the sketch to compile when I replace one of the SWSerial's with NeoSWSerial. Unfortunately I can't replace them both because one of the devices uses a library that apparently uses SWSerial.

Here is the compile error I get:

Arduino: 1.8.3 (Windows Store 1.8.6.0) (Windows 10), Board: "Arduino/Genuino Uno"

libraries\SoftwareSerial\SoftwareSerial.cpp.o (symbol from plugin): In function `SoftwareSerial::read()':

(.text+0x0): multiple definition of `__vector_3'

libraries\NeoSWSerial-master\NeoSWSerial.cpp.o (symbol from plugin):(.text+0x0): first defined here

libraries\SoftwareSerial\SoftwareSerial.cpp.o (symbol from plugin): In function `SoftwareSerial::read()':

(.text+0x0): multiple definition of `__vector_5'

libraries\NeoSWSerial-master\NeoSWSerial.cpp.o (symbol from plugin):(.text+0x0): first defined here

libraries\SoftwareSerial\SoftwareSerial.cpp.o (symbol from plugin): In function `SoftwareSerial::read()':

(.text+0x0): multiple definition of `__vector_4'

libraries\NeoSWSerial-master\NeoSWSerial.cpp.o (symbol from plugin):(.text+0x0): first defined here

collect2.exe: error: ld returned 1 exit status

exit status 1
Error compiling for board Arduino/Genuino Uno.

Is there any way to make this library coexist with the standard SoftwareSerial in the same sketch?

No. In that other library, simply search and replace all occurrences of "SoftwareSerial" with "NeoSWSerial". This catches the include statments and the variables of that type. I've done this many times with SIM libraries.

Remember, SoftwareSerial is so inefficient that it almost certainly interferes with other parts of your sketch. And it cannot transmit and receive at the same time. It wastes the CPU time every time it sends or receives one character, making the Arduino twiddle its thumbs for 1ms at 9600 baud. It could have executed 10,000 instructions during that time.

Thank you. That makes sense. While I was waiting for a reply I went ahead and re-wrote all of the GPRS code so I didn't need to use the library. The code snippet below illustrates the problem. I have two serial devices (plus the screen) One is an mP3 player, the other is a GPRS module. Everything compiles fine, but if I .begin the GPRS after the Mp3, the Mp3 doesn't work. If I switch the order and start the GPRS before the mP3, then the GPRS doesn't work. This happens with both neoSWSerial and SoftwareSerial.

Unfortunately I'm not sure the code will be of any help, since there are no errors the only sign it doesn't work is that the file doesn't play or the GPRS doesn't respond (I didn't include the GPRS code in this example.)

Thanks very much for your help!

#include <NeoSWSerial.h>
//#include <SoftwareSerial.h>



#define mP3_Tx           10    // mP3 Tx line
#define mP3_Rx           6    // mP3 Rx line

#define GPRS_Tx           8    //  GPRS Tx line
#define GPRS_Rx           7    //  GPRS Rx line

#define GPRS_Baud       19200    //GPRS baud rate
#define mP3_Baud        19200    //GPRS baud rate


//SoftwareSerial mP3Player(mP3_Rx, mP3_Tx); // RX, TX for mp3 player
NeoSWSerial mP3Player(mP3_Rx, mP3_Tx); // RX, TX for mp3 player
NeoSWSerial myGPRS(GPRS_Rx, GPRS_Tx);  // Initialize GPRS library
//SoftwareSerial myGPRS(GPRS_Rx, GPRS_Tx);  // Initialize GPRS library


void setup() {

  myGPRS.begin(GPRS_Baud);  //when this line is commented out, mp3 file plays.  If it is included, file does not play.
  mP3Player.begin(mP3_Baud);// Reversing the order of the begin statements makes the mp3 play, but then the gprs doesn't work.

  mP3Player.println("\\R_onemo.mp3");
}
void loop() {
  // put your main code here, to run repeatedly:

}

Which Arduino?

Software serial libraries can only listen to one pin at a time, and it's always the last port for which you called begin or listen(). NeoSWSerial can only transmit to the port it is listening to (I'm thinking about changing this).

I assume you are using Serial for the "screen", and you only transmit to the screen and the MP3 player. If that's the case, I would suggest a little rewiring to make your system more reliable:

1) Connect the GPRS to Serial, pins 0 and 1, because it is the only device that you receive from.

2) Connect the screen to pins 8 & 9 so you can use AltSoftSerial. It is even more efficient than NeoSWSerial, because it does not disable interrupts to transmit or receive.

3) Connect the MP3 player to any NeoSWSerial pin. NeoSWSerial disables interrupts while each character is being transmitted, but that won't affect Serial (the GPRS), because Serial has a two-character buffer.

This configuration

* puts the critical TX/RX device (GPRS) on the super-efficient hardware serial port (i.e., Serial);

* puts the most TX-intensive device (screen) on the next best software serial port (i.e., AltSoftSerial); and

* puts the least-used TX device (MP3) player on the least-efficient serial port (i.e., NeoSWSerial).

If by "screen" you meant the Serial Monitor window, you would need a Serial-to-USB converter (aka FTDI) to connect the Arduino pins 8 & 9 to your PC.

Cheers,
/dev

Thanks very much. I'm using a UNO. By "screen" I mean the serial monitor in the IDE.

As I recall, AltSoftSerial requires pins 8&9 on the UNO, correct? Unfortunately the GPRS is hardwired to pin 9 for power control (it is a shield). It also uses 7&8 for rx/tx, but I can configure it to use the UNO UART instead.

So I could try putting the GPRS on the hardware serial port (0,1) but since I can't use ALtSoftSerial I'd still have to use NeoSWSerial for the mP3 and screen. That's probably worth a try.

I did try putting a mP3.listen() before each mp3 call and a gprs.listen() before each gprs call. It worked like a charm in my test code (which was failing before I did that) but it isn't working yet in my actual code.

Thanks again for your help!