Go Down

Topic: Raw source code for full duplex, multi instance soft serial ports (Read 1 time) previous topic - next topic

Flipuino

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.

2) RAM per port:
- 10 Byte + Rx buffer + Tx buffer
- 1 byte for local variables (on stack)

3) 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. :-)

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




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:
Code: [Select]

#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:
Code: [Select]

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):
Code: [Select]

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:
Code: [Select]

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:
Code: [Select]

// 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:
Code: [Select]

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:
Code: [Select]

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:
Code: [Select]

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:
Code: [Select]

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;
}

Robin2

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
Two or three hours spent thinking and reading documentation solves most programming problems.

Go Up