Developing in C for the ATmega328P: A Second Serial Port

3 minute read

Where I discuss in detail, adding a second serial port to the Uno, soft_serial, and how to use it.

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.

Constants Required

Defaults shown.

soft_serial.h

  • #define SOFT_RX_PIN PIND2 // Define the RX pin
  • #define SOFT_TX_PIN PIND3 // Define the TX pin

env.make

  • SOFT_BAUD = 28800 - can range from 9600 to 28800

Functions:

  • #include “soft_serial.h” - Required
  • 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

// Store the string in program memory
const char success[] PROGMEM = "Success";
// write the string
void soft_pgmtext_write(const char* success) ;

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.

Comments powered by Talkyard.