I made an attempt to improve the SoftwareSerial library to make it possible to use Pin Change Interrupts in conjunction. I made some progress, but it’s not working yet. I’ll share what I’ve learned, and spell out a simple (though Kludgy) workaround.
Implementing asynchronous serial without hardware isn’t an easy task, and the Software Serial library is a pretty cool hack. It takes processor resources to implement that protocol though, especially with any speed, and as always there’s often competition for resources.
The limitations of Software Serial are widely discussed in various Arduino forums, and there are alternatives such as Paul Stoffregen’s AltSoftSerial which looks very promising but comes with it’s own set of tradeoffs (it uses one of the timer’s so disables PWM on some of the pins)
The limitations of SoftSerial that comes with the Arduino IDE appear to be 2:
- Interrupts are disabled during transmit so you can’t transmit and receive at the same time, and it may cause you to miss other interrupts. (Paul’s library deals with this)
- It uses Pin Change interrupts in such a way as to eliminate their use for anything else.
It is this second limitation that my friend Justin Shaw of Wyolum labs and I were discussing, and I thought I’d take a look to see if it could be remedied.
There’s one pin change interrupt vector per port (and a mask register as well)
That means if you are using more than one pin from a port for pinchange interrupts, the interrupt handler for that port has to determine what to do.
Interrupts are covered in many fine pages on the web, so I won’t go into details here, but there are two kinds on the arduino:
1. Dedicated external interrupts – tied to pin 2 and 3 each with their own interrupt vector.
2. Pin Change Interrupts – allow’s you to use any IO pin. There are registers to tell which pin(s) you want to watch in each port, and a vector for each set of IO pins (Ports) on an Atmega 328, there are three: Port B, C and D.
There are libraries (such as PinChangeInt.h) that handle setting up these handlers, but SoftwareSerial doesn’t use them. It has it’s own interrupt handling routine, and uses the Interrupt service vectors in a very selfish way. It points all the port interrupt vectors to it’s own routine, even if it’s only using a couple of pins from a single port.
The code below from SoftwareSerial.cpp sets the interrupt handler for all the ports that are defined (by processor type)
PCINT0 is for Port B (PB0-PB7)
PCINT1 is for Port C (PC0-PC6)
PCINT2 is for Port D (PD0-PD7)
PCINT3 isn’t defined for atmega328p
So, if you are using PB5 as a softserial rx,
you could comment out the code for PCINT1-PCINT3.
If you are using the PinChangeInt.h library, you’d also want to add:
Ahead of your #include PinChangeInt.h
so it doesn’t try to hook the vector softserial is using.