Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serial.println() affects Serial1 readings

Tags:

c++

arduino

I'm having trouble converting some C++ code to Arduino. Any help would be appreciated.

EDIT I have successfully done the above. However the only problem now is that the Arduino code I have reads the voltage accurately and properly but no other register. I can write the throttle too. If I call a different amount of Serial.println() statements, the readings on the other registers change and in some cases the voltage register stops working too.This is found in my code when I do

Serial.print("Voltage: );

If I printed out all of these registers the answers change. I can't figure out why this happens.

/* DEFINITIONS */
#include <math.h>

/* FLOATS */

uint8_t command[5];
uint8_t response[3];

/* INTEGERS */
byte deviceId = 0x17;
double throttleOut = 0;
double voltage = 0;
double rippleVoltage = 0;
double current = 0;
double power = 0;
double throttle = 0;
double pwm = 0;
double rpm = 0;
double temp = 0;
double becVoltage = 0;
double safeState = 0;
double linkLiveEnabled = 0;
double eStopStatus = 0;
double rawNTC = 0;

/* SETUP */
void setup() {
  Serial1.begin(115200);
  Serial.begin(115200);

}
void loop() {
  flushPort();
  ReadWriteRegister(128, 1000, true);//_throttleOut is 0[0%] to 65535[100%]
  voltage = ReadWriteRegister(0, 0, false) / 2042.0 / 0.05;
  rippleVoltage = ReadWriteRegister(1, 0, false) / 2042 / 0.25;
  current = ReadWriteRegister(2, 0, false) / 204200 * 50;
  power = voltage * current;
  throttle = (ReadWriteRegister(3, 0, false) / 2042.0 / 1.0);
  pwm = ReadWriteRegister(4, 0, false) / 2042.0 / 3.996735;
  rpm = ReadWriteRegister(5, 0, false) / 2042.0 / 4.89796E-5;
  int poleCount = 20;//Motor pole count
  rpm = rpm / (poleCount / 2);
  temp = ReadWriteRegister(6, 0, false) / 2042.0 * 30.0;
  becVoltage = ReadWriteRegister(7, 0, false) / 2042 / 0.25;
  safeState = ReadWriteRegister(26, 0, false);
  linkLiveEnabled = ReadWriteRegister(25, 0, false);
  eStopStatus = ReadWriteRegister(27, 0, false) == 0 ? false : true;
  rawNTC = ReadWriteRegister(9, 0, false) / 2042.0 / 0.01567091;
  rawNTC = 1.0 / (log(rawNTC * 10200.0 / (255.0 - rawNTC) / 10000.0 ) / 3455.0 + 1.0 / 298.0) - 273.0;
  Serial.print("Voltage: ");
  Serial.println(voltage);
  Serial.print("Current: ");
  Serial.println(current);
}
void flushPort() {

  command[0] = command[1] = command[2] = command[3] = command[4] = 0;
Serial1.write(command, 5);
  while (Serial1.available() > 0) {
    Serial1.read();
  }
}
double ReadWriteRegister(int reg, int value, bool writeMode) {
  // Send read command

  command[0] = (byte)(0x80 | deviceId);
  command[1] = (byte)reg;
  command[2] = (byte)((value >> 8) & 0xFF);
  command[3] = (byte)(value & 0xFF);
  command[4] = (byte)(0 - command[0] - command[1] - command[2] - command[3]);
  Serial1.write(command, 5);


  // Read response
  if(Serial1.available() == 3) {
  response[0] = (byte)Serial1.read();
  response[1] = (byte)Serial1.read();
  response[2] = (byte)Serial1.read();
  }

  if ((byte)(response[0] + response[1] + response[2]) == 0)
  {
    return (double)((response[0] << 8) + (response[1]));
  }
  else
  {
    Serial.println("Error communicating with device!");
  }
}

EDIT 2 Some photos of a usb logic analyzer shots. [1] [2] [3] [4] [5] [6] [7] And all of the packets together in this one: [All of them] Maybe this will help with timeouts etc. It's all the info I have :.

like image 649
Peter Avatar asked Feb 17 '16 08:02

Peter


1 Answers

There's no way that ReadWriteRegister will work. At 115200, each character takes about 87us to be sent or received. In that time, the Arduino can execute about 100 lines of code!

Look at this snippet:

  Serial1.write(command, 5);

  // Read response
  if(Serial1.available() == 3) {

The write function only puts the command in the output buffer and starts sending the first character. It returns before all the characters have been transmitted. That will take 500us!

Then, you look to see if there a 3-character response has been received. But the command hasn't finished being transmitted, and you certainly didn't wait 258us (3 times 86us). It could even take longer if the device needs time to execute your command.

You have to do two things: wait for the command to be sent, and wait for the response to be received. Try this:

  Serial1.write(command, 5);
  Serial1.flush(); // wait for command to go out

  // Wait for response to come back
  while (Serial1.available() < 3)
    ; // waitin'....

  // Read response
  response[0] = (byte)Serial1.read();
  response[1] = (byte)Serial1.read();
  response[2] = (byte)Serial1.read();

This is called "blocking", because the Arduino won't do anything else while you're waiting for the response.

However, if a character is missed, your program could "hang" there, waiting for a 4th character if the 2nd character is not sent/received correctly (it happens). So you should put a 500us timeout in that while loop:

  // Wait for response
  uint32_t startTime = micros();
  while ((Serial1.available() < 3) && (micros() - startTime < 500UL))
    ; // waitin'...

... or longer, if you know how quickly the device will respond. Then you can determine whether you actually got a response:

UPDATED with complete program (v2):

/* DEFINITIONS */
#include <math.h>

/* INTEGERS */

byte deviceId = 0x17;
uint8_t command[5];
uint8_t response[3];

/* FLOATS */

double  throttleOut     = 0.0;
double  voltage         = 0.0;
double  rippleVoltage   = 0.0;
double  current         = 0.0;
double  power           = 0.0;
double  throttle        = 0.0;
double  pwm             = 0.0;
double  rpm             = 0.0;
double  temp            = 0.0;
double  becVoltage      = 0.0;
uint8_t safeState       = 0;
uint8_t linkLiveEnabled = 0;
bool    eStopStatus     = 0;
double  rawNTC          = 0.0;

/* SETUP */
void setup() {
  Serial1.begin(115200);
  Serial.begin(115200);
  Serial.println( F("---------------------------") );

  // According to the spec, you can synchronize with the device by writing
  // five zeroes.  Although I suspect this is mostly for the SPI and I2c
  // interfaces (not TTL-level RS-232), it won't hurt to do it here.
  Serial1.write( command, 5 );
  delay( 250 ); // ms
  while (Serial1.available())
    Serial1.read(); // throw away

  // Set the throttle just once
  ReadWriteRegister(128, 1000);//_throttleOut is 0[0%] to 65535[100%]
}

//  For 12-bit A/D conversions, the range is 0..4096.  Values at
//  the top and bottom are usually useless, so the value is limited
//  to 6..4090 and then shifted down to 0..4084.  The middle of this
//  range will be the "1.0" value: 2042.  Depending on what is being 
//  measured, you still need to scale the result.
const double ADC_FACTOR = 2042.0;

void loop() {

  uint32_t scanTime = millis(); // mark time now so we can delay later

  voltage             = ReadWriteRegister(  0, 0 ) / ADC_FACTOR * 20.0;
  rippleVoltage       = ReadWriteRegister(  1, 0 ) / ADC_FACTOR * 4.0;
  current             = ReadWriteRegister(  2, 0 ) / ADC_FACTOR * 50.0;
  power               = voltage * current;
  throttle            = ReadWriteRegister(  3, 0 ) / ADC_FACTOR * 1.0;
  pwm                 = ReadWriteRegister(  4, 0 ) / ADC_FACTOR * 0.2502;
  rpm                 = ReadWriteRegister(  5, 0 ) / ADC_FACTOR * 20416.66;
  const int poleCount = 20;//Motor pole count
  rpm                 = rpm / (poleCount / 2);
  temp                = ReadWriteRegister(  6, 0 ) / ADC_FACTOR * 30.0;
  becVoltage          = ReadWriteRegister(  7, 0 ) / ADC_FACTOR * 4.0;
  safeState           = ReadWriteRegister( 26, 0 );
  linkLiveEnabled     = ReadWriteRegister( 25, 0 );
  eStopStatus         = ReadWriteRegister( 27, 0 );
  rawNTC              = ReadWriteRegister(  9, 0 ) / ADC_FACTOR * 63.1825;

  const double R0 =  1000.0;
  const double R2 = 10200.0;
  const double B  =  3455.0;
  rawNTC          = 1.0 / (log(rawNTC * R2 / (255.0 - rawNTC) / R0 ) / B + 1.0 / 298.0) - 273.0;

  Serial.print( F("Voltage: ") );
  Serial.println(voltage);
  Serial.print( F("Current: ") );
  Serial.println(current);
  Serial.print( F("Throttle: ") );
  Serial.println(throttle);
  Serial.print( F("RPM: ") );
  Serial.println(rpm);

  // These prints do not actually send the characters, they only queue
  // them up to be sent gradually, at 115200.  The characters will be
  // pulled from the output queue by a TX interrupt, and given to the
  // UART one at a time.
  //
  // To prevent these interrupts from possibly interfering with any other
  // timing, and to pace your program, we will wait *now* for all the
  // characters to be sent to the Serial Monitor.
  Serial.flush();

  // Let's pace things a little bit more for testing: delay here until
  // it's time to scan again.
  const uint32_t SCAN_INTERVAL = 1000UL; // ms
  while (millis() - scanTime < SCAN_INTERVAL)
    ; // waitin'
}

int16_t ReadWriteRegister(int reg, int value) {
  // Flush input, as suggested by Gee Bee
  while (Serial1.available() > 0)
    Serial1.read();

  // Send command (register number determines whether it is read or write)

  command[0] = (byte)(0x80 | deviceId);
  command[1] = (byte)reg;
  command[2] = (byte)((value >> 8) & 0xFF);
  command[3] = (byte)(value & 0xFF);
  command[4] = (byte)(0 - command[0] - command[1] - command[2] - command[3]);
  Serial1.write(command, 5);

  // The command bytes are only queued for transmission, they have not
  // actually gone out.  You can either wait for command to go out
  // with a `Serial1.flush()`  *OR*  add the transmission time to the
  // timeout value below.  However, if anything else has queued bytes
  // to be sent and didn't wait for them to go out, the calculated 
  // timeout would be wrong.  It is safer to flush now and guarantee
  // that *all* bytes have been sent: anything sent earlier (I don't 
  // see anything else, but you may change that later) *plus* 
  // these 5 command bytes.

  Serial1.flush();

  // Now wait for response to come back, for a certain number of us
  //   The TIMEOUT could be as short as 3 character times @ the Serial1
  //   baudrate: 3 * (10 bits/char) / 115200bps = 261us.  This is if
  //   the device responds immediately.  Gee Bee says 20ms, which would 
  //   be 20000UL.  There's nothing in the spec, but 1ms seems generous
  //   for reading the raw NTC value, which may require an ADC conversion.
  //   Even the Arduino can do that in 100us.  Try longer if you get
  //   timeout warnings.

  const uint32_t TIMEOUT = 2000UL;

  uint32_t startTime = micros();
  while ((Serial1.available() < 3) && (micros() - startTime < TIMEOUT))
    ; // waitin'...

  int16_t result;

  if (Serial1.available() >= 3) {
    response[0] = (byte)Serial1.read();
    response[1] = (byte)Serial1.read();
    response[2] = (byte)Serial1.read();

    // Verify the checksum
    if (response[0] + response[1] + response[2] != 0) {
      Serial.print( reg );
      Serial.println( F(" Checksum error!") );
      Serial.flush(); // optional, use it for now to stay synchronous
    }

    //  Cast to 16 bits *first*, then shift and add
    result = (((int16_t) response[0]) << 8) + (int16_t) response[1];

  } else {
    //  Must have timed out, because there aren't enough characters
    Serial.print( reg );
    Serial.println( F(" Timed out!") );
    Serial.flush(); // optional, use it for now to stay synchronous

    result = 0;
  }

  return result; // You must always return something
}

Comments:

  • There was an error in your result calculation that is (probably) fixed in the answer above. Casting to double outside the addition caused you to lose the top 8 bits, I think. Calculating as above should give the correct answer.
  • After a little googling, I see this is for a Castle Serial Link controller. That would have been useful to know. It describes the checksum, which I used in the ReadWriteRegister function above. The function can tell you if it timed out or if the checksum was wrong. This would also imply that longer timeouts may be required. It's not clear if your device waits up to 480ms to get the latest value, or if it is continuously cacheing them and responds immediately with the last value recevied from the ESC. Writes, however, will not be reflected in the read values for up to 480ms, because of the time it takes the ESC to receive a command and then send the new values. See ESC Castle Link protocol.
  • The ReadWriteRegister function returns a 16-bit integer, which will be more efficient. Comparing floating-point numbers is never good. BTW, double is just single float on the 8-bit Arduinos.
  • The ReadWriteRegister function doesn't need the writemode argument, because the register number determines whether you are writing or reading the device.
  • Writing the throttle value is only performed in setup.


UPDATE 2

Your Logic Analyzer shots appear to show a "scan" for ESCs. It is trying each device ID, and some of them reply with a non-zero voltage. Also, it appears to be running at 9600, NOT 115200. Is this from a different setup?

Regardless, it confirms what the controller spec said: write 5 bytes, read 3. The checksum values are as expected. However, it is running 10 times slower than your program, so it doesn't provide much new information regarding timeouts. It may imply there is a small delay before the device responds, perhaps ~1 bit time, or about 100us.

Have you read the controller spec? You should compare the program to the spec to make sure you understand how the controller works.

I have modified the program above to:

  • sync with the controller in setup (write 5 zero bytes and wait 250ms),
  • use scaling numbers from the spec (instead of their reciprocals?),
  • use meaningful constants instead of "magic" numbers (e.g. 2042),
  • use integer or boolean types for a few registers instead of double (see safeState, linkLiveStatus, and eStopStatus),
  • increase the timeout to 2ms (keep increasing it if you continue to get frequent timeouts), and
  • output the reg number when an error occurs.

If you want to be successful in this area, you must learn to read a specification and translate its requirements into code that conforms. The program you started with is non-conformal at worst, or misleading at best. I am particularly amused by the comments that say "INTEGERS" and "FLOATS", yet those sections contain the opposite.

Perhaps this is a lesson in fixing someone else's code? It truly has many problems that you will encounter. If I had a nickel for every time I said:

  • "What's that number for?"
  • "That comment is wrong!"
  • "The spec says you're supposed to..."
  • "Why is this so hard to read? I'll just add some spacing."

...I would be a very rich man. :)

(end of updates)


P.S.

This also matches the symptoms you describe: Because you didn't wait for the transmission to complete, 0 bytes are read the first time through (read will return a -1 or 0xFF byte).

After you've called this routine several times (and queued up several command into the output buffer), 500us have elapsed and the first command has finally been sent. The device responds by starting to send 3 characters. 87us later, the Arduino has finally received the first character. It gets read by one of your read statements, but who knows which one? It will be random, based on the elapsed time.

More commands get sent, and individual characters are received and read by one of those statements, until 64 bytes of commands OR Serial.println chars are queued up. Then the write command OR Serial.print blocks until the output has room for the newest command. (This addresses the title of your question.)

When enough command bytes or debug message chars are finally transmitted, the Serial1.write or Serial.print returns. In the meantime, received characters are going into the input buffer. (That's where they are stored until you call read.)

At this point, three read statements in a row will actually get characters that the device sent. But because of the random consumption of characters earlier, it could be the last character of one response, followed by the first two characters of the next response. You're "out-of-sync" with the 3-byte responses.

To stay "in synch" with the device, you have to wait for the send to complete with flush, and wait for the response to come back with while.

like image 114
slash-dev Avatar answered Nov 11 '22 02:11

slash-dev