Learn
SoftwareSerial vs hardware serial — one is a peripheral, the other is your CPU pretending
Troniction

An Arduino Uno has exactly one hardware serial port, and it is already spoken for. It is wired to the USB connection you use to upload sketches and read the Serial Monitor. So the moment you add a Bluetooth module, a GPS or a second board, you need a serial port you do not have.
SoftwareSerial is the standard answer, it is in every tutorial, and it works. It is also a fundamentally different thing from the hardware port, in ways that explain most of the odd behaviour people run into and never quite diagnose.
What the hardware port actually is
The USART on an ATmega328P is a dedicated peripheral — real silicon sitting beside the CPU, with its own shift registers, its own baud rate generator and its own state machine.
When you call Serial.write(), the byte is handed to that peripheral and your code moves on immediately. The hardware clocks the bits out at the configured rate, one at a time, with no further involvement from the processor. Receiving works the same way in reverse: the peripheral watches the pin, detects the start bit, assembles the byte, and only then raises an interrupt to say one is ready.
Two consequences matter. Your sketch is not doing the work, so timing is exact regardless of what else is running. And because there is a buffer, a byte can arrive while your code is busy elsewhere and still be waiting for you afterwards.
That is what a peripheral buys you: the transfer happens in parallel with your program rather than instead of it.
What SoftwareSerial does instead
SoftwareSerial has no hardware. It creates the same waveform using the only tool available — the CPU itself.
To send a byte it writes a pin low, busy-waits for one bit-time, writes the next bit, waits again, and so on. To receive, it attaches a pin-change interrupt to catch the falling edge of a start bit, then sits in a timing loop sampling the pin at each bit position.
This works, and the waveform it produces is genuinely valid serial. But three properties fall straight out of the implementation:
Sending blocks completely. At 9600 baud a byte takes just over a millisecond, and for that whole time the CPU is inside a delay loop doing nothing else. Ten bytes is eleven milliseconds of your sketch not running.
Interrupts are disabled while transmitting. They have to be — an interrupt firing mid-byte would stretch a bit-time and corrupt the frame. So while SoftwareSerial transmits, millis() does not advance and other interrupt-driven libraries do not run.
Only one instance can receive at a time. Receiving means monitoring a pin continuously, and the library can only track one. If you create two SoftwareSerial ports, whichever you last called listen() on is the live one, and bytes arriving on the other are discarded with no error at all. This is the single most surprising behaviour in the library, and it is why "my GPS and my Bluetooth module cannot both work" is such a common question.
Where the timing margin goes
There is a deeper reason SoftwareSerial gets unreliable as you speed up, and it connects directly to how serial tolerates error at all.
A UART receiver has about half a bit-time of slack — it samples near the middle of each bit, so it can be somewhat early or late and still read the right value. The arithmetic is worked through in how much baud error is too much, and it comes out around 5% total for an 8N1 frame.
Hardware spends almost none of that budget: the peripheral's timing comes from the same crystal that clocks everything else, so its only error is the divider rounding. SoftwareSerial starts from the same crystal but must then also survive interrupt latency, the time taken to enter its own interrupt handler, and whatever else your sketch is doing at that instant. Every microsecond of that comes out of the same budget.
At 9600 baud one bit lasts about 104 microseconds, and a few microseconds of jitter is irrelevant. At 115200 a bit lasts 8.7 microseconds, and a single competing interrupt can consume a meaningful fraction of one. That is the whole story of why the same code is solid at 9600 and flaky at 115200.
Hardware Serial | SoftwareSerial | |
|---|---|---|
| Who does the work | dedicated peripheral | the CPU, in a loop |
| Blocks your sketch | no | yes, for the whole byte |
| Interrupts during TX | unaffected | disabled |
| Simultaneous receive | every port | one port only |
| Realistic ceiling | 115200 and well beyond | 9600 dependable, 38400 practical |
| Ports on an Uno | 1, used by USB | as many as you have pins |
When SoftwareSerial is the right call
Most of the time, honestly. If you are sending drive commands to a robot or reading a sensor every few hundred milliseconds at 9600 baud, none of the above will ever bother you. The blocking is invisible, one port is all you need, and the margin at 9600 is enormous.
It becomes the wrong tool when any of these are true: you need two receiving ports, you are streaming continuously, you depend on precise timing elsewhere in the sketch, or you want to run above about 38400.
What to use instead
Move to a board with more hardware UARTs. This is the real fix rather than a workaround. An Arduino Mega has four, an ESP32 has three, and most 32-bit boards have several. A Bluetooth module on Serial1 gets a genuine peripheral while the USB connection keeps Serial — no blocking, no listening conflict, no ceiling.
Use AltSoftSerial if you are stuck on an Uno and need one reliable extra port. It uses the hardware timer capture unit rather than pure busy-waiting, so it does not block and tolerates higher rates. The cost is that it works on fixed pins only — 8 and 9 on an Uno.
Reconsider using pins 0 and 1. They are the real hardware UART, and a module on them works perfectly at full speed. The catch is that they are shared with USB, so uploads fail while something is connected there. That trade-off, and the ways round it, is why your Arduino will not upload with Bluetooth connected.
The short version
Hardware serial is a peripheral that runs alongside your code. SoftwareSerial is your code pretending to be a peripheral, and everything strange about it follows from that one fact: it blocks, it silences interrupts, it can only listen in one place, and its accuracy degrades exactly as the timing budget tightens.
At 9600 baud, for one module, none of that matters and you should just use it. Past that, the answer is not a better software UART — it is a board with another real one.
Whichever you use, both ends still have to agree on the rate. If yours are producing garbage rather than misbehaving under load, the problem is upstream of all of this, and garbage characters in the Serial Monitor is the place to start.
Common questions
- What is the difference between Serial and SoftwareSerial?
- Serial is a hardware peripheral inside the microcontroller that shifts bits in and out on its own while your code runs. SoftwareSerial has no hardware behind it — the CPU toggles a pin and counts time in a loop, so sending occupies the processor completely and receiving depends on an interrupt arriving promptly.
- Why can only one SoftwareSerial port receive at a time?
- Receiving means watching a pin for a start bit and then timing the following bits precisely. The library can only track one pin at a time, so instances take turns and whichever you last called listen() on is the active one. Data arriving on the others is discarded silently.
- What is the maximum reliable SoftwareSerial baud rate?
- The library supports up to 115200 on a 16 MHz board, but 38400 is a realistic practical ceiling and 9600 is where it is genuinely dependable. The faster it runs, the smaller the timing margin and the more damage any other interrupt in your sketch can do.
- How do I avoid SoftwareSerial entirely?
- Use a board with more than one hardware UART. An Arduino Mega has four and an ESP32 has three, so a Bluetooth module can have a real peripheral to itself while the USB connection keeps the first one. That removes the whole class of problem rather than working around it.
Still not connecting?
Arduino Bluetooth — Make It Connect is 62 pages of every way the link fails, why, and the fix — HC-05, HC-06 and HM-10 BLE, including the clone family almost nothing covers. $9.