Minor "noise" with NewSoftSerial

Hi,

I'm using an Arduino Duemilanove and NewSoftSerial to interface with a MAX232 chip to talk RS-232.

When I talk to the OBD serial cable directly, no problem. However, when I try to talk to the OBD serial cable using Arduino (through MAX232), I get what I want plus some "noise", e.g. I get some strange characters before the output expected.

For example, using OBD serial cable directly:

ELM327 v1.0a.

>atrv
11.7V

However, when I send "atrv" from Arduino, I get:

QÃYÃ)Ã11.7V

Never used NewSoftSerial, and I suspect that my MAX232 circuit is OK, since it's simple and I following the datasheets found around.

Any hints? Can I use NewSoftSerial interfacing with MAX232 as a normal, real UART serial? Thank you very much.

(I'm sorry, I am reposting here cause I posted on Hardware > Interfacing, but it's not the case.)

If it's not a hardware issue (and I'm not convinced it isn't), then it must be a software issue. You haven't shown any code, so how can we help you?

Wrong baud rate in the receiver?

Unhandled inversion?

Using the dreaded goto statement?

Sorry, the code:

#include <NewSoftSerial.h>

#define NUL '\0'
#define CR '\r'

#define BUFSIZE 16

/* hint from http://www.arduino.cc/playground/OpenBSD/CLI */
extern "C" void __cxa_pure_virtual(void) {
  while(1);
}

char elmrx[BUFSIZE];
char *p;

NewSoftSerial elm327(3, 2);

void setup() {
  Serial.begin(38400);
  elm327.begin(38400);
}

void loop() {
  elm327.println("ATRV");
  delay(100); // delay to wait for data fill the buffer

  while ((*p = elm327.read()) != '\n') {
    p++;
  }

  *p = NUL;
  p = elmrx;
  Serial.println(elmrx);

  elm327.flush();
}

Is it something related to pullup resistors?

Thank you.

I've seen this before when the two clocks are a bit too far apart. Mordern hardware UARTs automatically correct for clock differences; something that is difficult to impossible to do with software.

Try slightly higher and slightly lower baud rates. I don't know if NewSoftSerial supports "odd" baud rates but there isn't much to lose by trying.

Mordern hardware UARTs automatically correct for clock differences; something that is difficult to impossible to do with software.

How so?
UARTs normally sample at some multiple of the line rate (usually 16 times), and when they find the edge of the start bit, just count 8 cycles to check that the start bit is still there, then sample every 16 cycles.
No reason a software implementation shouldn’t do the same.

How so?

I could easily be wrong.

But it doesn't matter. I do know for a fact that a difference in clocks can cause that behaviour. A difference between the hardware UART timing and the NewSoftSerial timing could easily be why one case works and the other case doesn't.

Another possibility: NewSoftSerial has trouble framing the first few bytes when the baud rate is high and there are no breaks between bytes.

So, this is what I tried:

  1. Verified my hardware with MAX232, checked for short circuits, etc. No apparent problem. Otherwise, I would not be able to receive what I expected, e.g. 11.7V.

  2. I was told to me that can be synchronization problems. Doesn't seems to. The device I'm trying to connect works with 38400. I'm connecting with 38400. I can't receive anything otherwise. Tried with 31250, 9600, etc.

  3. Pullup resistor: put an software pullup and a hardware pullup, setting RX port (pin 3) to +5V with a 10kOhm resistor.

Are there any other trick in using NewSoftSerial?

Thank you very much.

Can you try a lower baud rate?

Instead of NewSoftSerial can you use the hardware UART on the Arduino?

Can you try a lower baud rate?

Yes, but it doesn’t work. The OBD serial cable I’m using is hardcoded at 38400. It has an AT option to change to 9600 and to 10400 (not supported by NewSoftSerial, looking at the code), but doesn’t seems to affect anything. Just to clarify: even if I set the speed in OBD cable with “AT IB 96” command to 9600 bps, I can only connect and see something with 38400.

Instead of NewSoftSerial can you use the hardware UART on the Arduino?

Yes. I made some changes today and added a jumper, so I can use an Arduino Mega. I added an define so my code can be easily compiled for both Duemilanove and Mega2560.

I borrowed an Mega2560 and it works fine, so I discarded the chance that my hardware, a MAX232 interface, is the problem. Can NewSoftSerial work ok with one baudrate and not with another? Actually I really didn’t dig hard at NewSoftSerial code; until now I wasn’t suspecting on this.

It’s ok that works with Mega2560, but I would really want to use Duemilanove + NewSoftSerial.

I posted the full code below. The code is mostly C, not “Wiring”, and not so simple, but maybe it’s useful for someone. Not polished, nor revised.

Thank you for helping.

#include <string.h>

/* if defined, need a change in BSDmakefile to:
MCU=atmega2560
AVRDUDE_PROGRAMMER = stk500v2
UPLOAD_RATE = 115200 */

// #define MEGA2560

#ifndef MEGA2560
#define ASERIAL elm327
#include <NewSoftSerial.h>
#else
#define ASERIAL Serial1
#endif

#define NUL '\0'
#define CR '\r' /* Carriage Return */
#define PROMPT '>'

#define NCMD 10 /* number of commands */
#define BUFSIZE 16

/* hint from http://www.arduino.cc/playground/OpenBSD/CLI */
extern "C" void __cxa_pure_virtual(void) {
  while(1);
}

/*
char *elmcommand(char *cmdtx);
*/

/* update NCMD if you add or remove functions */
int getload(void);    /* PID 0104, Calculated engine load value */
int gettemp(void);    /* PID 0105, Engine coolant temperature */
int getrpm(void);     /* PID 010C, Engine RPM */
int getspd(void);     /* PID 010D, Vehicle speed */
int gettimeon(void);  /* PID 011F, Run time since engine start */
int getdstmil(void);  /* PID 0121, Distance traveled with MIL on */
int gettimemil(void); /* PID 014D, Time run with MIL on */
int getethperc(void); /* PID 0152, Ethanol fuel % */
int getvoltage(void); /* ATRV, from ELM327 */
int help(void);       /* internal function */

const struct cmd {
  const char *name;
  const char *sname; // short name
  int (*func)();
} cmds[] = {
  { "getload",    "gl",  getload    },
  { "gettemp",    "gt",  gettemp    },
  { "getrpm",     "gr",  getrpm     },
  { "getspd",     "gs",  getspd     },
  { "gettimeon",  "gto", gettimeon  },
  { "getdstmil",  "gdm", getdstmil  },
  { "gettimemil", "gtm", gettimemil },
  { "getethperc", "gep", getethperc },
  { "getvoltage", "gv",  getvoltage },
  { "help",       "h",   help }
};

char cmdtx[BUFSIZE];
char cmdrx[BUFSIZE];
char cmdbuf[BUFSIZE];

int pflag = 0; /* prompt flag */
int i, found, first;

char elmrx[BUFSIZE];
char *e, *p;

/* an entry in BSDmakefile was needed
                      +------> receive pin
                      |  +---> transmit pin
                      |  |                */
#ifndef MEGA2560
NewSoftSerial ASERIAL(3, 2);
#endif

void setup() {
  Serial.begin(38400);
  ASERIAL.begin(38400);

  Serial.flush();
  ASERIAL.flush();
    
  /* software-enabled pullup resistor (doesn't work, but it's here)
  pinMode(3, INPUT);
  digitalWrite(3, LOW); */

  p = cmdbuf;
  e = elmrx;

  found = 1;
  first = 1;

  Serial.println("TCCino v0.1");
  Serial.print("Sending command to reset ELM327...");
  ASERIAL.println("ATZ");
  delay(1000);
  ASERIAL.println("ATZ");
  delay(1000);
  Serial.println(" ATZ sent.");    
  ASERIAL.flush();
}

void loop() {
  if (!pflag) {
    Serial.print("> ");
    pflag = 1;
  }

  if (Serial.available() > 0) {
    *p = Serial.read();

    if (*p == '\r') {
      *p = NUL;
      Serial.print("\n\r");
   
      p = cmdbuf;
      pflag = 0;

      found = 1;
      for(i = 0; i <= NCMD; i++) {
        if(!strcmp(cmds[i].name, cmdbuf) ||
           !strcmp(cmds[i].sname, cmdbuf)) {
          found = (*cmds[i].func)();
          break;
        }
      }
      
      Serial.flush();

      if (found == 1)
        Serial.println("command not found, try \"h\" for help");

    } else {
      Serial.print(*p);
      p++;
      *p = NUL;
    }
  }
}

/* The ELM functions below was inspired in OBDuino ones, but uses
 * NewSoftSerial library available at:
 *
 * http://arduiniana.org/libraries/newsoftserial/
 *
 * It's clearer and simpler too.
 */

/*
char *elmcommand(char *cmdbuf) {
  char t;
  int i = 0;

  ASERIAL.println(cmdbuf);
  while ((t = ASERIAL.read()) != PROMPT) {
    if (t >= ' ')
      cmdrx[i++] = t;
  }
  
  cmdrx[i] = '\0';
  return cmdrx;
}
*/
int getload() {

}

int getvoltage() {
  ASERIAL.println("ATRV");
  delay(100); // delay to wait for data fill the buffer

  while ((*e = ASERIAL.read()) != PROMPT) {
    // if ((*e >= '0' && *e <= '9') || *e == '.' || *e == 'V' || *e == ' ')
    e++;
  }

  *e = NUL;
  e = elmrx;
  if (first == 1) {
    first = 0;
  }
  Serial.println(elmrx);
  
  ASERIAL.flush();
  Serial.flush();

  return 0;
}

int gettemp() {

}

int getrpm() {
}

int getspd() {
}

int gettimeon() {
}

int getdstmil() {
}

int gettimemil() {
}

int getethperc() {
}

int help() {
  Serial.println("TCCino v0.1 HELP section");
  Serial.println("========================");
  Serial.println();
  Serial.println("HELP FORMAT:");
  Serial.println("full command syntax, SHORT command syntax: description");
  Serial.println("-------------------, --------------------: -----------");
  Serial.println("getload, gl: shows calculated engine load value");
  Serial.println("gettemp, gt: shows engine coolant temperature");
  Serial.println("getrpm, gr: shows engine RPM");
  Serial.println("getspd, gs: shows vehicle speed");
  Serial.println("gettimeon, gto: shows run time since engine start");
  Serial.println("getdstmil, gdm: shows distance traveled with MIL on");
  Serial.println("gettimemil, gtm: shows time run with MIL on");
  Serial.println("getethperc, gep: shows ethanol fuel %");
  Serial.println("getvoltage, gv: shows voltage");
  Serial.println("help, h: shows this help");
  Serial.println("                     *MIL: malfunction indication lamp");

  return 0;
}

The OBD serial cable I'm using is hardcoded at 38400. It has an AT option to change to 9600 and to 10400 (not supported by NewSoftSerial, looking at the code), but doesn't seems to affect anything

Why can't you start the Duemilanove + NewSoftSerial at 38400, issue the AT command to the OBD cable, then change NewSoftSerial to 9600?

Is the noise only present when the system first starts? Or, does the noise continue on and off past the startup?

Why can't you start the Duemilanove + NewSoftSerial at 38400, issue the AT command to the OBD cable, then change NewSoftSerial to 9600?

I can. I can connect directly to the OBD serial cable and set. But it doesn't make any difference. Even if I set the cable to work at 9600 bps, it only work if I connect to it at 38400 bps, no matter if it's directly connected or if it's with Arduino.

Is the noise only present when the system first starts? Or, does the noise continue on and off past the startup?

No, it's present at every read. Every time I read a temperature, I read the "noise" too:

QÃYÃ)Ã11.7V

Thank you.

Is it the same "noise" each time? If so, perhaps it isn't "noise" at all. Perhaps there is some significance to the values, but not as characters.

Could you, in addition to printing the data as characters, also print each byte using the DEC optional 2nd argument?

Is it the same "noise" each time?

No, but most often appears "Q", "Y", ")" and "Ã". A little test I made:

dbolgheroni@mob-compaq:~$ sudo tip -38400 /dev/ttyU0
connected
TCCino v0.1
Sending command to reset ELM327... ATZ sent.
> gv
QÃYÃ)Ã12.3V


> gv
GÃÃÃ12.3V


> gv
QÃYÃ)Ã12.3V


> gv
ÃYÃ
    12.3V


> gv
QÃYÃ)Ã12.3V


>
dbolgheroni@mob-compaq:~$ sudo tip -38400 /dev/ttyU0

If so, perhaps it isn't "noise" at all. Perhaps there is some significance to the values, but not as characters.

You are right, but why don't I get these characters using an UART, e.g. a hardware serial of Mega2560?

You are right, but why don't I get these characters using an UART, e.g. a hardware serial of Mega2560?

I don't know that I'm right. I would still like to see that the individual bytes look like, using

Serial.print("Byte: ");
Serial.println(t, DEC);

I see that you set pinMode for the NewSoftSerial pin, AFTER telling NewSoftSerial about the pin. I wouldn't think this advisable.

I also see that you use Serial.flush(). This throws away any data in the input buffer that has not been read yet. Rarely is this a good idea.

    if (t >= ' ')
      cmdrx[i++] = t;

It might be necessary, based on the results of printing t as a DEC value, to expand the if statement to exclude characters on the other end of the ASCII table, too.

I see that you set pinMode for the NewSoftSerial pin, AFTER telling NewSoftSerial about the pin. I wouldn’t think this advisable.

This is there because it was suggested this pin would need an pullup resistor. This can be made by software (ATmega328 have internal 20k pullup resistors) and by hardware. But it doesn’t change anything since the code is commented.

I also see that you use Serial.flush(). This throws away any data in the input buffer that has not been read yet. Rarely is this a good idea.

What I have is this:

computer <--> Arduino* <--> *OBD-II serial cable <--> OBD-II complaint car
  • the Arduino to OBD-II cable is the one using NewSoftSerial

Maybe the Serial.flush() you’re mentioning are the one in loop(). I’m cleaning Serial AFTER I read a command from the computer, and after all the input needed was received, e.g. the function getvoltage() already reached the prompt from OBD-II cable; at this point I know the cable connected to the car won’t send anything until I ask again with a new command.

Thank you.

Daniel,

When you perform the identical test using the hardware serial port, is the command string, i.e. "atrv" echoed back from the serial device? If so, my theory is that the "noise" you are seeing is this command string being corrupted.

And Paul is correct; you should not touch any pins whose control you have turned over to NewSoftSerial (or any other library, I would think).

Mikal

When you perform the identical test using the hardware serial port, is the command string, i.e. "atrv" echoed back from the serial device? If so, my theory is that the "noise" you are seeing is this command string being corrupted.

Yes! That's a detail I forgot to mention. In this case, I get this:

>ATRV
ATRV
11.7V

>

But I didn't understand how can the OBD cable get fine what I send through NewSoftSerial, .e.g. the ATRV command, return the expected value and JUST corrupt the echoed command.

I would like to understand. I'm not the kind of person who just leave things half-made. And I really want to make this NewSoftSerial works right, even if it's working with Mega2560.

And Paul is correct; you should not touch any pins whose control you have turned over to NewSoftSerial (or any other library, I would think).

Yes, I agree, but was just an attempt. This is why I commented out after seeing it doesn't work. I will remove it anyway, to avoid confusion.

Thank you again.