Raw source code for full duplex, multi instance soft serial ports

Hi all,

I recently needed 2 soft serial ports on my 328p (for a thermal printer and a FN-M16P). In order to ensure compatibility with other libraries and functions which I use on the same Arduino, I did not try or use existing soft serial libraries. Instead, I implemented the soft serial ports on my own.

Some details:

  • variable number of start bits / stop bits (#defines)
  • I did my best to conserve ressources (RAM, runtime, timer ressources)
  • callback on Rx timeout (messge complete --> message processing)

Ressources:

  1. Timer (assuming same baudrate for all ports):
  • one free running timer (normal mode 0)
  • 8 bit or LSB of 16 bit
  • prescaler set to: prescaler > f_sys / (100 * baudrate)
  • one compare ISR
    If the ports need different baudrates, then separate compare ISRs and maybe separate timers are required.
  1. RAM per port:
  • 10 Byte + Rx buffer + Tx buffer
  • 1 byte for local variables (on stack)
  1. Others
  • PCINT ISR(s) for all Rx lnes

Tested:

  • I use 2 ports (2 x Rx, 2 x Tx), both at 9600 baud 8N1. Works perfect for my purpose. :slight_smile:

Not yet tested / implemented:

  • Parity (I don't need it, but easy to add)
  • Error handling, eg. frame error (I don't need it, but easy to add)
  • I did not check the max baudrate, because it depends on number of instances etc
  • The code is designed for full duplex on all instances, but I did not test it.
  • compatiility with other libraries (It works fine with the set of libraries that I use)
  • Robustness against noisy signals (I don't need it, hard to implement)

My questions associated with this post are:

  • Does a library already exist for a multi instance, full duplex soft serial interface?
  • Is anybody interested to check out my implementation?
  • If my code is helpful for others: Is anybody interested to wrap it into a library?

I an happy to share my code with everybody who is interested to check it out (see below). In case it would be published as a library, I would appreciate being mentioned :-).

Flipuino

This the code for one instance (for a second instance, double the code with a replaced by b). The macros SET_PIN_HIGH etc are my own wrappers for bit manipulations on the registers. I can provide these macros if required, but they can be replaced by any other code to set / unset a pin.

Defines:

#define XTAL_CLOCK_FREQ_KHZ 16000
#define TIMER2_PRESCALER 64
#define TIMER2_OCR_BY_ISR_FREQ_HZ(f) (((1000ul * (XTAL_CLOCK_FREQ_KHZ)) / (f)) / (TIMER2_PRESCALER) + 0)

#define SWUART_BAUD_RATE 9600
#define SWUART_TIMER2_DELTA_1BIT0  TIMER2_OCR_BY_ISR_FREQ_HZ(SWUART_BAUD_RATE)
#define SWUART_TIMER2_DELTA_0BIT5  (SWUART_TIMER2_DELTA_1BIT0 >> 1)
#define SWUART_TIMER2_DELTA_1BIT5  (SWUART_TIMER2_DELTA_1BIT0 + SWUART_TIMER2_DELTA_0BIT5)
#define SWUART_TIMER2_RX_TIMEOUT 96 // 96 bit at 9600 baud = 10ms timeout
#define SWUART_TX_START_BITS 1
#define SWUART_TX_STOP_BITS 2
#define SWUART_RX_START_BITS 1
#define SWUART_RX_STOP_BITS 0

// Size of Buffers for SW UART data
#define SWUART_TX_BUF_SIZE 168
#define SWUART_RX_BUF_SIZE 22

Variables:

unsigned char swuart_a_tx_buf[SWUART_TX_BUF_SIZE];
unsigned char swuart_a_rx_buf[SWUART_RX_BUF_SIZE];
unsigned char swuart_a_tx_buf_index = 0;
unsigned char swuart_a_tx_msg_size = 0;
unsigned char swuart_a_rx_buf_index = 0;
unsigned char swuart_a_rx_timeout = 0;
unsigned int swuart_a_tx_reg = 0;
unsigned int swuart_a_rx_reg = 0;
unsigned char swuart_a_rx_state = 0;
unsigned char swuart_a_rx_tcnt2 = 0;

Initialisation (to be called in setup):

void swuart_a_init()
{
  // init pins
  SET_PIN_HIGH(PIN_ID_16_SWUART_A_TX);
  SET_PIN_OUT(PIN_ID_16_SWUART_A_TX);
  SET_PIN_IN(PIN_ID_17_SWUART_A_RX);

  // init swuart
  memset(swuart_a_tx_buf, 0, sizeof(swuart_a_tx_buf));
  swuart_a_tx_buf_index = 0;
  swuart_a_tx_msg_size = 0;
  memset(swuart_a_rx_buf, 0, sizeof(swuart_a_rx_buf));
  swuart_a_rx_buf_index = 0;
  swuart_a_rx_timeout = 0;
  swuart_a_tx_reg = 0;
  swuart_a_rx_reg = 0;
  swuart_a_rx_state = GET_PIN_LEVEL(PIN_ID_17_FN_M16P_RX);
  swuart_a_rx_tcnt2 = 0;

  // init timer 2 (normal mode, prescaler = 64
  noInterrupts();
  TCCR2A = 0x00; // No Pin changes, Mode 0 = Normal
  TCCR2B = 0x04; // No Force Compare, Mode 0 = normal, Prescaler = 1/64
  OCR2B = TCNT2 + TIMER2_OCR_BY_ISR_FREQ_HZ(SWUART_BAUD_RATE);
  TIMSK2 |= (1 << OCIE2B);
  interrupts();

  // init pin interrupts
  PCMSK0 |= 0x08; // enable PCINT03 for Rx
  PCICR |= 0x01; // enable PCIE0 group (PCINT00 - PCINT07)
}

Timer ISR:

ISR(TIMER2_COMPB_vect) 
{
  swuart_a_rx_timer_isr();
  swuart_a_tx_timer_isr();

  OCR2B += TIMER2_OCR_BY_ISR_FREQ_HZ(SWUART_BAUD_RATE);
}

Pin change ISR:

// ISR is called on rising and falling edge of all port B signals
ISR(PCINT0_vect) 
{
  swuart_a_rx_pin_isr();
}

Timer ISR function for Rx:

void swuart_a_rx_timer_isr()
{
  // swuart a: update receiver
  if (swuart_a_rx_state & 0xf0)
  {
    // update Rx register with old current state
    while ((swuart_a_rx_state < (SWUART_RX_START_BITS + 8 + SWUART_RX_STOP_BITS) << 4) && ((unsigned char) (TCNT2 - swuart_a_rx_tcnt2) > SWUART_TIMER2_DELTA_1BIT5))
    {
      swuart_a_rx_tcnt2 += SWUART_TIMER2_DELTA_1BIT0; // time of start of most recent bit 
      swuart_a_rx_reg >>= 1;
      if (swuart_a_rx_state & 0x01)
        HIGH_BYTE(swuart_a_rx_reg) |= 1 << (SWUART_RX_START_BITS + SWUART_RX_STOP_BITS - 1);
      swuart_a_rx_state += 0x10; // increment bit counter
    }

    // end of frame?
    if (swuart_a_rx_state >= (SWUART_RX_START_BITS + 8 + SWUART_RX_STOP_BITS) << 4)
    {
      if (swuart_a_rx_buf_index >= SWUART_RX_BUF_SIZE)
        swuart_a_rx_buf_index = SWUART_RX_BUF_SIZE-1;
      swuart_a_rx_reg >>= SWUART_RX_START_BITS;
      swuart_a_rx_buf[swuart_a_rx_buf_index++] = LOW_BYTE(swuart_a_rx_reg);
      swuart_a_rx_state &= 0x01;
    }

    // reset rx timeout counter
    swuart_a_rx_timeout = SWUART_TIMER2_RX_TIMEOUT;
  }
  else
  {
    // no frame in progress: check timeout
    if (swuart_a_rx_timeout > 1)
      swuart_a_rx_timeout -= 1;
    else
      if (swuart_a_rx_timeout == 1)
      {
        // process data
        // This is the callback function when an Rx timeout occurs (message complete)
        swuart_a_rx_message_callback();

        // reset receiver
        swuart_a_rx_state = GET_PIN_LEVEL(PIN_ID_17_SWUART_A_RX);
        swuart_a_rx_timeout = 0;
        swuart_a_rx_buf_index = 0;
      }
  }
}

Timer ISR function for Tx:

void swuart_a_tx_timer_isr()
{
  // swuart a: something to send?
  do
  {
    if (swuart_a_tx_buf_index >= SWUART_TX_BUF_SIZE)
    {
      // something went wrong
      swuart_a_tx_msg_size = 0;
      swuart_a_tx_buf_index = 0;
      swuart_a_tx_reg = 0;
      SET_PIN_HIGH(PIN_ID_16_SWUART_A_TX);
      break;
    }

    // tx message size is defined, but message is not yet sent: send characters    
    if (swuart_a_tx_msg_size)
    {
      // new byte to be sent?
      if (!swuart_a_tx_reg)
      {
        if (swuart_a_tx_buf_index >= swuart_a_tx_msg_size)
        {
          // message was sent
          swuart_a_tx_msg_size = 0;
          swuart_a_tx_buf_index = 0;
          swuart_a_tx_reg = 0;
          SET_PIN_HIGH(PIN_ID_16_SWUART_A_TX);
          break;
        }
    
        LOW_BYTE(swuart_a_tx_reg) = swuart_a_tx_buf[swuart_a_tx_buf_index++]; // 8 data bits
        swuart_a_tx_reg <<= SWUART_TX_START_BITS; // start bits
        HIGH_BYTE(swuart_a_tx_reg) |= ((1 << SWUART_TX_STOP_BITS) - 1) << SWUART_TX_START_BITS; // no parity, add stop bits
      }

      // send bit
      if (LOW_BYTE(swuart_a_tx_reg) & 0x01)
        SET_PIN_HIGH(PIN_ID_16_SWUART_A_TX);
      else
        SET_PIN_LOW(PIN_ID_16_SWUART_A_TX);

      // shift tx register
      swuart_a_tx_reg >>= 1;
      break;
    }

    SET_PIN_HIGH(PIN_ID_16_SWUART_A_TX);
  }
  while (0);
}

Pin change function for Rx:

void swuart_a_rx_pin_isr()
{
  unsigned char rx_signal;

  // swuart a: change of Rx signal?
  rx_signal = GET_PIN_LEVEL(PIN_ID_17_SWUART_A_RX);

  // transmission in progress and signal state change?
  if ((swuart_a_rx_state & 0xf0) && (rx_signal != (swuart_a_rx_state & 0x01)))
  {
    // update Rx register with old signal state
    while ((swuart_a_rx_state < (SWUART_RX_START_BITS + 8 + SWUART_RX_STOP_BITS) << 4) && ((unsigned char) (TCNT2 - swuart_a_rx_tcnt2) > SWUART_TIMER2_DELTA_1BIT5))
    {
      swuart_a_rx_tcnt2 += SWUART_TIMER2_DELTA_1BIT0; // time of start of most recent bit 
      swuart_a_rx_reg >>= 1;
      if (swuart_a_rx_state & 0x01)
        HIGH_BYTE(swuart_a_rx_reg) |= 1 << (SWUART_RX_START_BITS + SWUART_RX_STOP_BITS - 1);
      swuart_a_rx_state += 0x10; // increment bit counter
    }

    // update Rx register with new signal state and synchronize
    if (swuart_a_rx_state < (SWUART_RX_START_BITS + 8 + SWUART_RX_STOP_BITS) << 4)
    {
      swuart_a_rx_tcnt2 = TCNT2; // time of start of most recent bit 
      swuart_a_rx_reg >>= 1;
      if (rx_signal & 0x01)
        HIGH_BYTE(swuart_a_rx_reg) |= 1 << (SWUART_RX_START_BITS + SWUART_RX_STOP_BITS - 1);
      swuart_a_rx_state += 0x10; // increment bit counter
    }

    // end of frame?
    if (swuart_a_rx_state >= (SWUART_RX_START_BITS + 8 + SWUART_RX_STOP_BITS) << 4)
    {
      if (swuart_a_rx_buf_index >= SWUART_RX_BUF_SIZE)
        swuart_a_rx_buf_index = SWUART_RX_BUF_SIZE-1;
      swuart_a_rx_reg >>= SWUART_RX_START_BITS;
      swuart_a_rx_buf[swuart_a_rx_buf_index++] = LOW_BYTE(swuart_a_rx_reg);
      swuart_a_rx_state = 0;
    }

    // update signal state
    swuart_a_rx_state &= 0xf0;
    swuart_a_rx_state |= rx_signal;

    // reset rx timeout counter
    swuart_a_rx_timeout = SWUART_TIMER2_RX_TIMEOUT;

    // that's all for now
    return;
  }

  // start of new data frame?
  if ((swuart_a_rx_state == 0x01) && (!rx_signal))
  {
    // no transmission in progress and signal is low --> start of new data frame
    swuart_a_rx_tcnt2 = TCNT2; // time of start of most recent bit (start bit)
    swuart_a_rx_reg = 0;
    swuart_a_rx_state = 0x10; // set bit counter to 1 and signal level to 0 (start bit)

    // reset rx timeout counter
    swuart_a_rx_timeout = SWUART_TIMER2_RX_TIMEOUT;

    // that's all for now
    return;
  }
      
  // update signal state
  swuart_a_rx_state &= 0xf0;
  swuart_a_rx_state |= rx_signal;
}

Code to send something:

void send_something_on_swuart_a()
{
  unsigned char ibyte = 0;

  // transmission in progress?
  if (swuart_a_tx_msg_size)
    return;
  // In case you want to avoid a transmission while you are waiting for a reply, uncomment the following 2 lines
  // if (swuart_a_rx_state & 0xf0)
  //   return;
    
  // define message bytes
  swuart_a_tx_buf[ibyte++] = ...; // 1st byte
  // ...
  swuart_a_tx_buf[ibyte++] = ...; // Last byte

  // set number of bytes to be sent to start transmission
  swuart_a_tx_msg_size = ibyte;
}

Flipuino:
I an happy to share my code with everybody

Please post your program as a single entity. It is all to easy to make mistakes when trying to join pieces together.

This Thread should probably be in the Exhibition / Gallery section. If you click Report to Moderator you could ask to have it moved.

…R