Go Down

Topic: fast encoder reading and fast serial communication - possible ? (Read 577 times) previous topic - next topic

unromeo

Ok guys, first of all, this is my first post, so please be patient with me :)

I am also a newbie in programming...

What I want to do :
- read 2 optical encoders (built from a mouse)
- send the values as often as possible through serial port to the pc (50 times per second is fine).
The speed of the encoders is not extremely high, aprox. 500 - 800 pulses per sec. at maximum speed.
Currently, my program is just for one encoder. 
The serial.print is called once every 50 ms using mills method, sends the value , and reset's the counter of the encoder. (so, sending just 20 times per second now)
The problem I encounter :
- when the speed of the encoder is high, I get more pulses per rotation, as when the speed is lower. I thing this is due to the fact that on every serial.print, my arduino is missing some pulses. So in low speed mode, more serial.print's are called in one revolution of the encoder, and that's why I get less pulses counted.
  The big question is :
  Can I send a constant stream of data while counting encoder pulses without loosing any pulse ???
  This is my code so far (it doesn't use interrupts, but I tried with the interrupts and the problem is still there).

Code: [Select]


#define ENC_A 54
#define ENC_B 55
#define ENC_PORT PINF
int counter = 0;
int tmpdata = 0;
long previousMillis = 0;
long interval = 50;   


void setup() {

  pinMode(ENC_A, INPUT);
  pinMode(ENC_B, INPUT);
  Serial.begin(9600);
}

void loop() {
   tmpdata = read_encoder();
unsigned long currentMillis = millis();
  if (tmpdata) {
   counter = counter + tmpdata;
   
   
   if(currentMillis - previousMillis > interval) {
     previousMillis = currentMillis;   
    Serial.println(counter);
    counter = 0;
  }
  }

}

int8_t read_encoder()
{
  int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
  static uint8_t old_AB = 0;
  old_AB <<= 2;                   
  old_AB |= ( ENC_PORT & 0x03 );
  return ( enc_states[( old_AB & 0x0f )]);
}


As you can see, serial.print is called only when there is movement in the encoder.

If anyone can come with a suggestion, I will be grateful.

Thank you in advance.

PaulS

Code: [Select]
  Serial.begin(9600);
The argument here defines the number of bits per second that can be sent. This value can be increased, to 19200, 38400, 57600, or 115200.

Since 115200 is 12 times as fast, you will be able to send 12 times as much data in one second.

115200 is the maximum supported value.

Code: [Select]
    Serial.println(counter);
This code converts the value in counter to a string. That takes time. If the value is greater than 99, 3 bytes, plus the carriage return and line feed will be sent.

You could break the int into two bytes, using highByte() and lowByte, then send the two bytes. Use a single character delimiter, instead of the 2 character delimiter that you are using now.

Sending a value like 1000 the way you are now takes 6 bytes. Sending it as two bytes (using Serial.write()) with a 1 character delimiter, will cut the amount of data in half.

If the value in counter does not exceed 255, it could be a byte, instead of an int. If that were the case, there would be no need to send a delimiter, so every byte is a full payload, and you can send as many as 11,500 of them per second.

Nick Gammon

I would change:

Code: [Select]
int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};


to:

Code: [Select]
static int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};


Judging by a disassembly of compiler output, it is doing a loop copying those 16 numbers every time you do read_encoder, which will waste time.

Also, does tmpdata really need to be an int, when you are putting an int8_t into it?

Anyway, interrupts would be a good bet. If they didn't help, maybe it was the way you used them. Properly-used, interrupts should react very quickly to external events.
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

unromeo

Ok, here's what I've done :

Currently, the value won't exceed +/-255 (resetting the counter every 20 ms or so) so I am sending it in 2 bytes, first for sign, and second for value,and using 2 serial.write, don't know if it's ok (it will be increased at 4 bytes for the 2 encoders). I need the byte for the sign because the encoders move in both directions.
serial speed is 115200.
declared int8_t enc_states[] as static, and tempdata as int8_t.
The issue has been improved, but not completely gone. I still get some pulses lost in high vs low speed. I would prefer zero loses.
I don't know what else to change. As I said before, I've used interrupts, but in a completely different program, that counted just half of the pulses (don't know why, don't have a lot of experience with programming). I would like to implement interrupts in the actual program to see if the situation will improve even better. Anyone can help me with that please ?

Thanks in advance.
(here's my current code)

Code: [Select]
#define ENC_A 54
#define ENC_B 55
#define ENC_PORT PINF
int counter = 0;
int8_t tmpdata = 0;
long previousMillis = 0;
long interval = 80;   

void setup() {
  pinMode(ENC_A, INPUT);
  pinMode(ENC_B, INPUT);
  Serial.begin(115200);
}

void loop() {
   tmpdata = read_encoder();
unsigned long currentMillis = millis();
  if (tmpdata) {
   counter = counter + tmpdata;
    if(currentMillis - previousMillis > interval) {
     previousMillis = currentMillis;
     if (counter < 0 ) {
        Serial.write(45);
        Serial.write(256 - counter);
     }
     else if (counter > 0) {
      Serial.write(43);
      Serial.write(counter);
     }
  counter = 0;
  }
  }
}

int8_t read_encoder()
{
  static int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
  static uint8_t old_AB = 0;
  old_AB <<= 2;                   
  old_AB |= ( ENC_PORT & 0x03 );
  return ( enc_states[( old_AB & 0x0f )]);
}



dafid

The hardware serial device has two bytes of buffer inside it for writing..

That means you can write 2 characters with no blocking (if the serial device is idle).

At 115200 baud, each character is taking about 100 microseconds to send, so sending 4 will block for 200 micro-seconds before returning.

Or you can send 2 characters each 200 microseconds without blocking.


Go Up