timerarduinocollisionservosoftware-serial

Having timer collision issue with using Servo and SoftwareSerial


I'm having timer collision problems with using Servo.h and SoftwareSerial.h on Arduino Nano board. And now I need 2 pairs of Serial pins by using NFC Module and Arduino's Serial Monitor on my laptop.

If the information I got is not wrong, there are three timers (timer0, timer1, timer2) available in Nano board. As I heard timer1 is 16bit timer, and both Servo.h and SoftwareSerial.h use that timer1 at the same time on Nano board that's why they can't avoid timer collision issue.

Yet I need to use both header files without timer collision. In this case, what should I do? Do I have to modify Servo.h file for not using timer1?

Because all I do with my Servo Motor is controlling angular position.

Therefore, using 16bit-timer is of no use in this project I'm proceeding unless I use PWM control.

So, at this point, I want to use timer0 or timer2 (both are 8 bit-timer) instead of using timer1. If not, the timer1 from both header files of Servo and Software will be collide. Following is the source code I use.

const unsigned char wake[24]={
  0x55, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x03, 0xfd, 0xd4, 0x14, 0x01, 0x17, 0x00};//wake up NFC module
const unsigned char firmware[9]={
  0x00, 0x00, 0xFF, 0x02, 0xFE, 0xD4, 0x02, 0x2A, 0x00};//
const unsigned char tag[11]={
  0x00, 0x00, 0xFF, 0x04, 0xFC, 0xD4, 0x4A, 0x01, 0x00, 0xE1, 0x00};//detecting tag command
const unsigned char std_ACK[25] = {
  0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x0C, 
0xF4, 0xD5, 0x4B, 0x01, 0x01, 0x00, 0x04, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x4b, 0x00};
unsigned char old_id[5];

unsigned char receive_ACK[25];//Command receiving buffer
//int inByte = 0;               //incoming serial byte buffer

#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#define print1Byte(args) mySerial.write(args)
#define print1lnByte(args)  mySerial.write(args),mySerial.println()
#else
#include "WProgram.h"
#define print1Byte(args) mySerial.print(args,BYTE)
#define print1lnByte(args)  mySerial.println(args,BYTE)
#endif

#include <Servo.h>
#include <NeoSWSerial.h>

NeoSWSerial mySerial(5,6);

volatile uint32_t newlines = 0UL;

Servo sv;

int pos1=0; //initial value = 93 degree
int pos2=180;
int sw1 = 4;

static void handleRxChar( uint8_t c )
    {
      if (c == '\n')
        newlines++;
    }

void setup(){
  mySerial.attachInterrupt( handleRxChar );
  pinMode(sw1, INPUT_PULLUP);
  sv.attach(9);
  Serial.begin(9600);  // open serial with PC
  mySerial.begin(9600);  //open serial1 with device
  //Serial2.begin(115200);
  wake_card();
  delay(100);
  read_ACK(15);
  delay(100);
  display(15);
}

void loop(){
  send_tag(); 
  read_ACK(25);
  delay(100);
  if (!cmp_id ()) {   //nfc tag
    if (test_ACK ()) {
      display (25);
      sv.write(pos1);
      delay(2500);
      sv.write(pos2);
    }
  }
  else if (cmp_id()){   // switch
    if(digitalRead(sw1) == LOW){
      sv.write(pos1);   // waits 15ms for the servo to reach the position
      }
    else if(digitalRead(sw1) == HIGH){
      sv.write(pos2);
    }
  }
  copy_id ();
}

void copy_id (void) {//save old id
  int ai, oi;
  for (oi=0, ai=19; oi<5; oi++,ai++) {
    old_id[oi] = receive_ACK[ai];
  }
}

char cmp_id (void){//return true if find id is old
  int ai, oi;
  for (oi=0,ai=19; oi<5; oi++,ai++) {
    if (old_id[oi] != receive_ACK[ai])
      return 0;
  }
  return 1;
}

int test_ACK (void) {// return true if receive_ACK accord with std_ACK
  int i;
  for (i=0; i<19; i++) {
    if (receive_ACK[i] != std_ACK[i])
      return 0;
  }
  return 1;
}

void send_id (void) {//send id to PC
  int i;
  Serial.print ("ID: ");
  for (i=19; i<= 23; i++) {
    Serial.print (receive_ACK[i], HEX);
    Serial.print (" ");
  }
  Serial.println ();
}

void UART1_Send_Byte(unsigned char command_data){//send byte to device
  print1Byte(command_data);
#if defined(ARDUINO) && ARDUINO >= 100
  mySerial.flush();// complete the transmission of outgoing serial data 
#endif
} 

void UART_Send_Byte(unsigned char command_data){//send byte to PC
  Serial.print(command_data,HEX);
  Serial.print(" ");
} 

void read_ACK(unsigned char temp){//read ACK into reveive_ACK[]
  unsigned char i;
  for(i=0;i<temp;i++) {
    receive_ACK[i]= mySerial.read();
  }
}

void wake_card(void){//send wake[] to device
  unsigned char i;
  for(i=0;i<24;i++) //send command
    UART1_Send_Byte(wake[i]);
}

void firmware_version(void){//send fireware[] to device
  unsigned char i;
  for(i=0;i<9;i++) //send command
    UART1_Send_Byte(firmware[i]);
}

void send_tag(void){//send tag[] to device
  unsigned char i;
  for(i=0;i<11;i++) //send command
    UART1_Send_Byte(tag[i]);
}

void display(unsigned char tem){//send receive_ACK[] to PC
  unsigned char i;
  for(i=0;i<tem;i++) //send command
    UART_Send_Byte(receive_ACK[i]);
  Serial.println();
}

Summary

I am having timer collision issue with using Servo.h and SoftwareSerial.h.

they both share timer1 at the same time. To avoid this collision issue and make these two work fine, what should I do? Should I do something with the source code like by adding up few lines of code or modify those header files?


Solution

  • Normally, I would have suggested AltSoftSerial as the alternative to SoftwareSerial (read more here), but it also conflicts with the Servo library's TIMER1 use. It can only be used on two specific pins.

    I think my NeoSWSerial would do the trick. It re-uses the micros() clock (TIMER0) and Pin Change interrupts to implement a software serial port. This limits it to bauds rates 9600, 19200 and 38400, but it is much more efficient than SoftwareSerial. It can be used on any two pins.


    Update

    I would not suggest using a software serial port at 115200, as it can be unreliable above 38400. You might be able to send a baud rate configuration command to the NFC module to set it to a lower rate.

    BTW, if you are sending information (not just receiving), all software serial port libraries disable interrupts during transmission, except AltSoftSerial... which you can't use. Just be aware of that, because it may affect your Servo when you transmit on NeoSWSerial.

    Also, be sure you are using one of the PWM pins for the servo. If the Servo library is creating the PWM signal with software (just like a software serial port), the CPU won't have time for much else.

    It might be better to put the NFC module on the hardware serial port, Serial. For debug prints, use NeoSWSerial connected to a TTL Serial-to-USB converter. Then open the Serial Monitor on that converter's COM port. Remove the debug later, because transmitting disables interrupts.

    There are other boards that have additional UARTS. For example, an Arduino Leo (ATMega32U4 MCU) has an extra serial port, Serial1, that you could use for the NFC. Serial would still be available for debug prints.