Go Down

Topic: Better way to read 60+ bytes from slow serial 8192 baud (Read 4524 times) previous topic - next topic

jcwoltz

Hello,

I am trying to find a better way to read about 68 bytes from 8192 baud serial. Currently I am doing this:
Code: [Select]

 for (int i=0; i < dataStreamSize; i++) {
   while (!Serial.available()) {}
   dataStream[i] = Serial.read();
 }


I would like to move to an interrupt based serial receive, or better alternative as Arduino already puts the Hardware serial into a ring buffer. As is, it takes about 83 milliseconds to receive 68 bytes. My loop is taking around 119 milliseconds. I would like to be doing calculations and other tasks, until all the data has come in.

Thank you in advance.

Complete (current) test code:
Code: [Select]

#include <NewSoftSerial.h>
NewSoftSerial mySerial(2, 3);
const int rxControl=5;
bool inSync=false;
bool inMode1=false;
int mdelay=3350;

const int dataStreamSize=69;
unsigned char dataStream[dataStreamSize];

#define Bit_Set(port,bit_num) (port = port | (0x01 << bit_num))
#define Bit_Clear(port,bit_num) (port = port & ~(0x01 << bit_num))


void setup() {
 pinMode(rxControl,OUTPUT);
 digitalWrite(rxControl,LOW);
 mySerial.begin(57600);
 Serial.begin(8192);
}

void loop() {
 sendMode1();
 displayDataStream();
 
}

int sendMode1(void) {
 
 //Check if we are already synchronized. If not, wait for ecu to send 0xF0 probe
 //The delay takes a little bit of working out.
 //(@16MHz 3389 works for AVR C, 3353 works for Arduino Serial)
 //
 if (inSync == false) {
   Bit_Clear(PORTD,rxControl);
   while (Serial.read() != 0xF0) {}
   Bit_Set(PORTD,rxControl);
   delayMicroseconds(mdelay);
 }
 
 //Send commands to ECU for mode 1
 Bit_Set(PORTD,rxControl);
 Serial.print(0xF0,BYTE);
 Serial.print(0x57,BYTE);
 Serial.print(0x01,BYTE);
 Serial.print(0x00,BYTE);
 Serial.print(0xB8,BYTE);
 Bit_Clear(PORTD,rxControl);
 
 //Start Reading Data. To save time, only read first 5 bytes.
 //If we are not in sync, we do not read the probe messages.
 for (int i=0; i < 5; i++) {
   while(!Serial.available()) {}
   dataStream[i] = Serial.read();
 }
 
 //The ECU will send a 0xF0 and 0x95 and 0x01 to indicate it is in mode 1
 //Different hardware moves this by a byte or two.
 if (!((dataStream[1] == 0xF0 && dataStream[2] == 0x95) || (dataStream[2] == 0xF0 && dataStream[3] == 0x95))) {
   Serial.flush();
   inSync = false;
   mdelay = mdelay + 1;
   return 3;
 }
 
 //Read the rest of the data stream if the first 5 bytes are ok.
 for (int i=5; i < dataStreamSize; i++) {
   while (!Serial.available()) {}
   dataStream[i] = Serial.read();
 }
 
 //check for 0xF0 and 0x95, this can probably be removed due to earlier check
 if (dataStream[2] == 0xF0 && dataStream[3] == 0x95) {
   inSync = true;
   return 1;
 } else {
   if (inSync == false) {
     mdelay = mdelay +1;
   }
   inSync = false;
   return 0;
 }
   
}

void displayDataStream(void) {
 for (int j=2; j < dataStreamSize; j++) {
     mySerial.print(dataStream[j],HEX);
 }
 mySerial.println();
 mySerial.print(mdelay);
 mySerial.print("\t");
 if (inSync) {
   mySerial.print("inSync\t");
   byte a = calcCheckSum(2);
   if (a != 0) {
     mySerial.print(a,HEX);
     mySerial.print("\t");
   }
 }
 mySerial.println(millis());
}

byte calcCheckSum(int dss) {
//Pass the dataStreamStart(dss) as argument.
byte checkSum=0;
for (int cc=dss; cc < dataStreamSize; cc++) {
checkSum += dataStream[cc];
}
return checkSum;
}

AWOL

Serial reception already is interrupt-driven, so why reinvent it?
Your while is blocking, which wastes time when you could possibly be doing other stuff.

jcwoltz

I understand my while is blocking. Which is why I am asking for a better way.

How do I structure my program to read when I have all the data?

Essentially, how to I structure this in a way to say:
-Send mode 1 command at correct time?
-As soon as the buffer is full, store data, send mode 1, then process.

AWOL


cmiyc

Serial.Available() returns how many bytes are in the 128 byte receive buffer.
Capacitor Expert By Day, Enginerd by night.  ||  Personal Blog: www.baldengineer.com  || Electronics Tutorials for Beginners:  www.addohms.com

jcwoltz

I guess I do not know how to explain what I am trying to do.

Since doing this will not work (from main loop and removing all the for/while/serial.reads):
if (Serial.available() >= dataStreamSize -1) {
  calcData();
}

also this never gets sync:
if (Serial.available() > 5) {
  for (int i=0;i < 5; i++) {
    dataStream = Serial.read();
  }
  code to check sync here.....
}

Those were my previous attempts...

From a simple program point of view, it will work. But the challenge is the timing. When that dataStream fills up, I need to be writing the next 0xF0,0x57,0x01,0x00,0xB8. I need a way to detect weather or not the two devices are in sync. That is the point of this statement:
if (dataStream[2] == 0xF0 && dataStream[3] == 0x95) and the incrementing of the mdelay variable.

AWOL, you say make better use of serial.available, but how?
I am not asking anyone to write the program for me. I am trying to ask how to structure it better.
I am not a programmer, but am trying to ask for friendly advice.

Does anyone have advice, methodology, or high level pseudo code to help.

I need a way to initially send the serial commands. I need a way to make sure I am in sync. If I am not in sync, I need to add a little bit more delay.
Then once in sync should I check serial when it has all the bytes? Should I check the serial  only a couple of bytes in to see if I'm in sync?
If I am in sync, and I get all of the data, should I alternate between two buffers? One to fill up, the previous to run calculations on?

and why does Serial.write(0x00); not work? That is why I use Serial.print(0x00,BYTE);

This is not some assignment that was just assigned. I have spent days reading/researching. The code in my original post works. Has all of my requirement (with the calculations,display,logging removed). However, I can not come up with better way to use serial.available that works.

GoForSmoke

You want to synch what to what? And why? I look but.....
What/where are your regular and custom serial lines connected?
Trying to make actual sense of this is like hearing one end of a 3-way phone conversation.

You don't want to read the serial buffer until the full entry is in? I dunno why but Serial.peek() would allow you to look for end of line without removing bytes from the queue. Since you're looking anyway it'd be quicker to grab available bytes right then wouldn't it?
1) http://gammon.com.au/blink  <-- tasking Arduino 1-2-3
2) http://gammon.com.au/serial <-- techniques howto
3) http://gammon.com.au/interrupts
Your sketch can sense ongoing process events in time.
Your sketch can make events to control it over time.

wildbill

Could you provide a bit more detail about what the device you're trying to communicate with is, and what the communications protocol is - it's a little hard to try and deduce how to help with what we have so far.

jcwoltz

@GoForSmoke:

I'm connecting to 8192 baud ALDL. (Vehicle model/year not important for this part) In short, bi-directional half-duplex single wire serial. The AVR's UART (in this case PD0 and PD1) are connected through 3 transistors and four resistor to do this. PD5 (or digital 5) is connected to one transistor that essentially cuts off the AVR's TX or RX pin. PD5 low enables receive, PD5 high enables transmit from AVR.
NewSoftSerial is only for debugging and goes to a terminal (minicom/putty/hyper terminal).

I have to check two things with the serial buffer, if we are in sync and the checksum at the end to verify a valid message.
The code at the end has this debug output:
Code: [Select]

beginMode1 3361
dataStream copied to dataStreamp
F095124EB02004343130000006500800808A8A5F5FEDEFFB1D42DA78000882B5300000000000208081106F1D2020540240FF
3361    inSync  9578
checkSync failed!
beginMode1 3362
dataStream copied to dataStreamp
F095124EB02004343130000006500800808A8A5F5FEDEFFB1D43D979000882B5300000000000208081106F1D2020540240FE
3362    inSync  15183
checkSync failed!
beginMode1 3363


The code in my first post has this output:
Code: [Select]

F055BB0000000000000000000000000000000000000000000000000000000000000000
3353    15696
F095124EB0200434313000000000800808A8A5F5FEFEFFB1D43D970002802B53000000000002080811001F424E054010056
3353    inSync  15997
F095124EB0200434313000000000800808A8A5F5FEFEFFB1D43D900005902B53000000000002080811001F424E0540112ED
3353    inSync  16116
F095124EB0200434313000000000800808A8A5F5FEFEFFB1D43D90000B1C2B53000000000002080811001F424E05401105D
3353    inSync  16234
F095124EB0200434313000000000800808A8A5F5FEFEFFB1D43D90000B1C2B53000000000002080811001F424E05401125B
3353    inSync  16352


So, although my original code is far from optimal, it gets the data quickly.

Here is new code. Any advice how to optimize:
Code: [Select]

#include <NewSoftSerial.h>
NewSoftSerial mySerial(2, 3);
const int rxControl=5;
bool inSync=false;
bool inMode1=false;
int dsCounter=0;
int mdelay=3360;

const int dataStreamSize=69;
unsigned char dataStream[dataStreamSize];
unsigned char dataStreamp[dataStreamSize];

#define Bit_Set(port,bit_num) (port = port | (0x01 << bit_num))
#define Bit_Clear(port,bit_num) (port = port & ~(0x01 << bit_num))


void setup() {
  pinMode(rxControl,OUTPUT);
  digitalWrite(rxControl,LOW);
  mySerial.begin(57600);
  Serial.begin(8192);
  beginMode1();
}

void loop() {
  //sendMode1();
  if (Serial.available() > 5 && dsCounter == 0) {
    for (int i=0; i < 5; i++) {
      dataStream[i] = Serial.read();
      dsCounter++;
    }
    if (!checkSync()) {
      beginMode1();
    }
  }
  if (inSync) {
    if (Serial.available() >= dataStreamSize - dsCounter) {
      for (int i=dsCounter; i < dataStreamSize; i++) {
        dataStream[i] = Serial.read();
        dsCounter++;
      }
      byte a = calcCheckSum(2);
      if (a == 0) {
        for (int i=0; i < dataStreamSize; i++) {
          dataStreamp[i] = dataStream[i];
          dataStream[i] = 0;
        }
        mySerial.println("dataStream copied to dataStreamp");
        dsCounter = 0;
        displayDataStream();
      } else {
        inSync = false;
        mySerial.print("failed checksum: ");
        mySerial.println(a,HEX);
        displayDataStream2();
        beginMode1();       
      } //checksum
    } //Serial.available
    //displayDataStream();
  } //(inSync)
  //displayDataStream();
 
}

int checkSync(void) {
  if ((dataStream[1] == 0xF0 && dataStream[2] == 0x95) || (dataStream[2] == 0xF0 && dataStream[3] == 0x95)) {
    inSync = true;
    return 1;
  } else {
    mySerial.println("checkSync failed!");
    Serial.flush();
    inSync = false;
    mdelay = mdelay + 1;
    return 0;
  }
}

void beginMode1(void) {
  Serial.flush();
  dsCounter=0;
  mySerial.print("beginMode1 ");
  //for (int i=0; i < dataStreamSize; i++) {
  //  dataStream[i]=0;
  //}
  mySerial.println(mdelay);
 
  if (inSync == false) {
    Bit_Clear(PORTD,rxControl);
    while (Serial.read() != 0xF0) {}
    Bit_Set(PORTD,rxControl);
    delayMicroseconds(mdelay);
  }
 
  //Send commands to ECU for mode 1
  Bit_Set(PORTD,rxControl);
  Serial.print(0xF0,BYTE);
  Serial.print(0x57,BYTE);
  Serial.print(0x01,BYTE);
  Serial.print(0x00,BYTE);
  Serial.print(0xB8,BYTE);
  Bit_Clear(PORTD,rxControl);



void displayDataStream(void) {
  for (int j=2; j < dataStreamSize; j++) {
      mySerial.print(dataStreamp[j],HEX);
  }
  mySerial.println();
  mySerial.print(mdelay);
  mySerial.print("\t");
  if (inSync) {
    mySerial.print("inSync\t");
  }
  mySerial.println(millis());
}

void displayDataStream2(void) {
  for (int j=2; j < dataStreamSize; j++) {
    mySerial.print(j);
    mySerial.print(":");
      mySerial.print(dataStream[j],HEX);
      mySerial.print(" ");
  }
  mySerial.println();
  mySerial.print(mdelay);
  mySerial.print("\t");
  if (inSync) {
    mySerial.print("inSync\t");
  }
  mySerial.println(millis());
}

byte calcCheckSum(int dss) {
//Pass the dataStreamStart(dss) as argument.
byte checkSum=0;
for (int c=dss; c < dataStreamSize; c++) {
checkSum += dataStream[c];
}
return checkSum;
}


Go Up