UPDATED Where I discuss in detail, adding a second serial port to the Uno, soft_serial, and how to use it. New soft serial port has been rewritten in AVR assembly providing much higher speeds.
Introduction#
While I was developing code for a robotic arm, I realized it would be best, if I used one serial port for communicating with the arm and another serial port to accept commands for the arm. Thus I created a second serial port, called soft_serial.
The port didn’t need to be fast, as it would be used as a communication link between someone on a keyboard and the Uno. It needed to do the following:
- read a line of text and parse into tokens
- read/write a single byte
- write a text string from program memory
- provide a debugging capability
The Driver#
The first thing I needed was a driver. This is the code which provides the most fundamental actions of a serial port, reading and writing a byte. Once you have the capability to communicate using a single byte, everything else builds off of it. Interestingly, I used AI to generate the driver. It did quite well, working on the first try!
It doesn’t use interrupts, it uses polling to determine if a character has been received, which is inefficient. However, for this use case and similar ones, where the intent is to use it for user input, polling is fine as the Uno will be waiting on the user for input anyway.
As of June, 2026 based on some work I had done with the ATtiny13A, I updated the driver to be AVR assembly code-based. This provides much higher speeds and greater flexibility as to the range of speeds which can be used. The new range is 28800 to 230400, set by the parameter SOFT_BAUD in the env.make.
Constants Required#
Defaults shown.
registers.S#
#define SOFT_TX_PIN PD3 ; transmit pin, output
#define SOFT_RX_PIN PD2 ; receive pin, input pullup
#define IO_PORT _SFR_IO_ADDR(PORTD) ; PORT for RX and TX pins
#define IO_DDR _SFR_IO_ADDR(DDRD) ; DDR for RX and TX pins
#define IO_PIN _SFR_IO_ADDR(PIND) ; PIN for RX and TX pinsenv.make#
The default is 57600, it can range from 28800 to 230400.
SOFT_BAUD = 57600UL
Functions:#
- #include “soft_serial.h” - Required Contains the higher level routines
- #include “serial_asm.h” - Required Contains the assembly driver for soft_char_read and soft_char_write
- void init_soft_serial() - Required to use soft_serial, is used to initialize various components required for soft_serial
- void soft_char_write(char data) - writes a single character to SOFT_TX_PIN
- int8_t soft_char_read() - reads a single character from SOFT_RX_PIN
Higher Level Functionality#
Typically, you want to read or write more complex strings than a single character. In that case, here are functions which will prove worthwhile:
String functions#
- int8_t soft_string_write(char * buffer, int8_t len) - write a string from a buffer of length len
- int8_t soft_readLine(char * buffer, int8_t size) - read until carriage return or size number of characters
Integer functions#
void soft_int16_write(int16_t number) - write a 16-bit number
- void soft_int16_writef(int16_t number, int8_t size)* - write a 16-bit number, pad with spaces upto size void soft_int8_write(int8_t number) - write an 8-bit number
Hard coded Text Functions#
To save RAM, its best to use program memory to store strings, instead of using RAM. To do so, use this process
const char success[] PROGMEM = "Success"; // Store the string in program memory
...code...
soft_pgmtext_write(success) ; // write the string
Two constant characters which ard used a lot are the newline (NL) and blank space (BL).
- void soft_char_NL(void) - print a newline
- void soft_char_space(void) - print a space
Debugging#
It’s helpful to have a series of debugging functions to aid in determining where a program is failing. I have added a function which takes a number 1-3 and prints “debug:n “. This provides a simple capabilty to add a short statement anywhere in the code and see if the code reaches the statement. I use it like this:
...code...
debug(1);
...failing code...
debug(2);More than likely I’ll see “debug1: “ printed, however, “debug2: “ might not print, indicating I have a problem with the code section, in between the two statements.