SoftwareSerial VS Interrupt

Hi Folks,

I’m working on a project, where I have (among other thing) a RC remote control coming in, an Accellerometer and a SaberTooth motor driver module.

The Arduino used is a Leonardo. In a nutshell, the goal is to send a command with the remote control, and based on the value recorded by the accelerometer, give a command to both motor.

My problem is that in order to “extract” the RC command, I use an interrupt: I capture the time value went to ‘1’ then when it became a ‘0’. I the calculate the time in-between …

For the SaberTooth, I use “SimplifiedSerial” mode.

Now if the RC portion is enabled only, it works fine, but as soon as I enable the serial communication with the SaberTooth, the value received by the RC is somehow erratic. My guess is that the serial comm to the SaberTooth, is interfering my the interrupt for the RC.

I have tried to enable/disable the serial comm to the SaberTooth, in order to limit the interference, with no luck.

Right now, I use pin for the Serial Comm to the SaberTooth, but I could re-design my PCB to use Pin 1 (original Tx) but I’m not even sure if it would improve anything.

I originally was planning on using the RC mode on the SaberTooth, but it wasn’t impressive ! My PCB being made, that’s why I’m trying to use Pin 11 for serial comm.

/*
 * Revision notes
 * Includes ADXL345 logic
 * includes Servo out (1 channel)
 * Includes temperature sensor
 * EEPROM Mapping
 * 0 = Offset
 * 1 = Polarity
*/

#include <EEPROM.h>
#include <Wire.h>
#include <ADXL345.h>
#include <LiquidCrystal_I2C.h>
#include <NewPing.h>
#include <SimpleTimer.h>
#include <SoftwareSerial.h>
#include <SabertoothSimplified.h>

SoftwareSerial SWSerial(NOT_A_PIN, 5); // RX on no pin (unused), TX on pin 11 (to S1).
SabertoothSimplified ST(SWSerial); // Use SWSerial as the serial port.

int TRIGGER_PIN = 13;
int ECHO_PIN = 12;
int MAX_DISTANCE = 200;
int Angle = 0;
int ValLH = 0; 
int ValRH = 0;
String Line1 ="";
String Line2 ="";
String Line3 ="";
String Line4 ="";
int Temperature = 0;
int Distance = 0;
int TimeInCh1 = 0;
int TimeOutCh1 = 0;
int ValueCh1 = 0;
int Buffer = 0;
int MinSpeed = 1500;
int MaxSpeed = 1500;
int AbsoluteAngle = 0;
int MotorLeftCorrection = 0;
int MotorRightCorrection = 0;
int Correction = 0;
int Offset = 3;
byte ReadValue = 0;
byte Polarity = 0;

ADXL345 adxl; //variable adxl is an instance of the ADXL345 library
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address
NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE);
SimpleTimer timer;

void setup()
{
  //Serial.begin(9600);
  //Serial.println("Initializing Serial comm....");
  Manipulate2Read();
  pinMode(7, INPUT);  //Remote Control Input
  pinMode(8, INPUT);  //Decrease Offset
  pinMode(9, INPUT);  //Write Offset to EEPROM
  pinMode(10, INPUT); //Increase Offset
  pinMode(11, INPUT); //Read Offset from EEPROM
  attachInterrupt(digitalPinToInterrupt(7), CalculateRemoteCommand, CHANGE);

  //SWSerial.begin(9600);
  adxl.powerOn();
  lcd.begin(20,4);   // initialize the lcd for 16 chars 2 lines, turn on backlight
  lcd.backlight(); // turn backlight on  
  timer.setInterval(333, Display);
  timer.setInterval(250, ReadDistance);
}

//********************************************
void loop(){

timer.run(); 
int x,y,z;  
adxl.readAccel(&x, &y, &z); //read the accelerometer values and store them in variables  x,y,z

Line1 = "Cmd= ";
Line1 = Line1 + ValueCh1;
Line1 = Line1 + " Offset:";
Line1 = Line1 + Offset;

Angle = map(x,-480,128,-90,90);
Angle = Angle + Offset;
Line2 = "Angle= ";
Line2 = Line2 + Angle;
Line2 = Line2 + " Temp: ";
Line2 = Line2 +Temperature;

ValLH = map(MotorLeftCorrection, -100, 100, -127, 127);     
ValRH = map(MotorRightCorrection, -100, 100, -127, 127); 
if(ValLH < -127)
  {
    ValLH = -127;
  }
if(ValLH > 127)
  {
    ValLH = 127;
  }
if(ValRH < -127)
  {
    ValRH = -127;
  }
if(ValRH > 127)
  {
    ValRH = 127;
  }
SWSerial.begin(9600);
ST.motor(1, ValLH);
ST.motor(2, ValRH);
SWSerial.end();
Temperature = map(analogRead(0),0,204,0,100);

Line3 = "LH :";
Line3 = Line3 + ValLH;
Line3 = Line3 + " RH :";
Line3 = Line3 + ValRH;

Line4 = "Dist to wall: ";
Line4 = Line4 + Distance;
Line4 = Line4 + " cm";
if (digitalRead(8)==1)
  {
  Offset = Offset+1;
  ManipulateOffset2Save();
  while(digitalRead(8)==1)
    {
    }
  }
if (digitalRead(10)==1)
  {
  Offset = Offset-1;
  ManipulateOffset2Save();
  while(digitalRead(10)==1)
    {
    }
  }
CalculateMotor();

if(digitalRead(9)==1)
  {
    ManipulateOffset2Save();
    while(digitalRead(9)==1)
    {
      //wait
    }
  }
if(digitalRead(11)==1)
  {
    Offset = EEPROM.read(0);
    while(digitalRead(11)==1)
    {
      //wait
    }
  }


}

//********************************************
void ReadDistance()
{
unsigned int uS = sonar.ping();
pinMode(ECHO_PIN,OUTPUT);
digitalWrite(ECHO_PIN,LOW);
pinMode(ECHO_PIN,INPUT);
Distance = uS / US_ROUNDTRIP_CM;
}

//********************************************
void Display()
{
  lcd.clear();
  lcd.setCursor(0,0); //Start on line 0
  lcd.print(Line1);
  lcd.setCursor(0,1); //Start on line 1
  lcd.print(Line2);
  lcd.setCursor(0,2); //Start on line 2
  lcd.print(Line3);
  lcd.setCursor(0,3); //Start on line 3
  lcd.print(Line4);
}

//********************************************
void CalculateRemoteCommand()
{
if(digitalRead(7)==1)
  {
  TimeInCh1 = micros();  
  } 
if(digitalRead(7)==0)
  {
  TimeOutCh1 = micros();
  Buffer = TimeOutCh1 - TimeInCh1;  
  }

/*if(Buffer>900)
  {
  if(Buffer<1450)
    {
    ValueCh1 = map(Buffer,1025,1450, 100, 0);
    }  
  }

if(Buffer>1550)
  {
  if(Buffer<1975)
    {
    ValueCh1 = map(Buffer,1450,1975, 0, -100);
    }  
  }
if(Buffer>1449)
  {
  if(Buffer<1551)
    {
    ValueCh1 = 0;
    }  
  }
//ValueCh1 = 25;*/
ValueCh1 = Buffer;
}

//********************************************
void CalculateMotor()
{
  if (Angle <0)
  {
    AbsoluteAngle = Angle*-1; 
  }
  else
  {
    AbsoluteAngle = Angle;
  }
Correction = ((AbsoluteAngle * AbsoluteAngle));
if(ValueCh1==0)
{
  Correction = Correction *1.5; //Set the gain of correction
  
}
 if (Correction >=81)
  {
  Correction = 81;
  }
 if (Angle>0)
 {
  MotorLeftCorrection = ValueCh1-(Correction/2);
  MotorRightCorrection = ValueCh1+(Correction/2);
 }
 if (Angle<0)
 {
  MotorLeftCorrection = ValueCh1+(Correction/2);
  MotorRightCorrection = ValueCh1-(Correction/2);
 }
}
//********************************************
void ManipulateOffset2Save()
{
  if(Offset>0)
  {
    EEPROM.write(0,Offset);
    EEPROM.write(1,1);    
  }
  if(Offset <0)
  {
    EEPROM.write(0,!Offset);
    EEPROM.write(1,0);     
  }
}

void Manipulate2Read()
{
//Serial.println("Entering Read routine....");
    ReadValue = EEPROM.read(0);
    Polarity = EEPROM.read(1);
    if(Polarity ==1)
    {
      Offset = ReadValue;
    }
    if(Polarity ==0)
    {
      Offset = !ReadValue;
    }
//Serial.print("Offset Value is:");
//Serial.println(Offset);
}

Please note that this is “Work in Progress”, so some comments are not accurate/pertaining, and some “deadcode” is still present !

Doesn't the Leonardo have multiple hardware serial ports?

just checked ... nope !

Mega does....

SoftwareSerial is a real CPU-killer. It blocks interrupts for long periods of time.

HardwareSerial is the absolute best (pins 0 and 1).

If you could use pins 8 & 9, AltSoftSerial is a much better choice.

Next best is a library I maintain, NeoSWSerial. It’s only good for 9600, 19200 and 38400 baud, and receiving only textual data (char values < 128). If you need to receive binary data from SaberTooth, you can’t use it.

BTW, you should switch from using the String type to plain C strings (char arrays). You’ll save a fair amount of RAM, 1.6kB of program space, CPU time, and numerous headaches later. Since you are using quite a few libraries, Memory Fragmentation will almost certainly be a problem. Random restarts, hangs or output are the result.

Cheers,
/dev

Which devices are serially interfaced? Leonardo has two hardware serial ports. One is the USB - "Serial", and the other is on pin 0,1 - "Serial1".

good question… How do you know which one is addressed ?

I thought the Sabertooth librarie would take care of that !

jasmino: good question.... How do you know which one is addressed ? I thought the Sabertooth librarie would take care of that !

You have told it which one here...

SabertoothSimplified ST(SWSerial); // Use SWSerial as the serial port.

so all you have to do is change it to

SabertoothSimplified ST(Serial1); // Use Serial1 as the serial port.

and omit SoftwareSerial entirely.

Of course a hardware serial is the best choice, if available. There exists an AltSoftwareSerial library, which may behave better than SoftwareSerial - any experience with that library?

Aarg: thanks for pointing out how to access the HW serial port … still in the learning process !!

/Dev: any "String type versus Char type for dummies " ???

I’m reading some explanations, but can’t figure out the whole thing about those 2…

any "String type versus Char type for dummies " ???

I’m reading some explanations, but can’t figure out the whole thing about those 2…

Choose 1 response: Choose carefully.jpg


1) So you can decide for yourself? That is what’s so frustrating about the whole String vs. char[] discussion. String is easy to use, but the long-term behavior of the Dynamic Memory (aka “heap”) that String uses is an advanced topic. Sadly, the problems that manifest do not appear to be caused by String, and so everything else is suspected, tested, rewritten… Eventually, the sketch gets posted out of desperation, and the experts say “Don’t Use String™.” Then the novice says “It isn’t crashing in the section that uses String.” Ehhhhggggggg-zactly!

If that’s the path you’re on, please forgive me while I roll my eyes. A novice who doesn’t understand why the heap shouldn’t be used in an embedded system, with limited RAM, will not be able to connect the usage with the consequence. As you develop more code for this environment, you will eventually have enough experience and information to connect the dots. Ding, the light goes on. Until then, take the expert’s advice: Don’t Use String™ and be happy.

Or don’t. Recent studies have shown that negative rationale (e.g., why you shouldn’t use String) may actually fortify an irrational position (e.g., antivax, flat earth, global warming).


2) It’s great that you’re asking for more information! How else will you learn what to avoid? I’m quite happy to share this recent response to a similar question, this summary of the issues from all over the innertubes, and this cautionary tale.

To summarize the consequences:

  • Calling a function uses the stack for args and return addrs. The deeper that the calls are nested (or the bigger the local variables), the closer the stack gets to stepping on the heap (i.e., parts of a String) or vice versa: A String operation will trash the too-deep stack, and returning from the function takes you to LALALAND;
  • fragmentation or RAM exhaustion may cause a heap request to fail, but the caller doesn’t find out that the String operation failed;
  • A heap request (i.e., malloc, called from String) is actually a search, and the search is not deterministic (i.e., it may take a long time to find a chunk), which is bad juju.
  • String operations are very inefficient because they may have to reallocate the storage (e.g., to insert or append), which copies all the bytes of the String for each operation
    Honestly, the library for manipulating char arrays (aka C strings, declared in <string.h>) has been around for decades because it is the best choice: fast performance and minimal RAM. Instead of String methods, you’ll be setting an array element (e.g., [nobbc]buf[i] = c;[/nobbc]) or calling strcpy, strcmp, strchr, strcat, or strtok (reference here). Lots of folks use sscanf or sprintf, but you may only need atoi, itoa et al.

Srsly, :smiley:

Cheers,
/dev