You cannot run ReadWriteRegister . At 115200, each character takes about 87us to send or receive. During this time, Arduino can execute about 100 lines of code!
Take a 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 characters have been transmitted. It will take 500US!
Then you see if a 3-character response is received. But the team did not finish being transferred, and you probably did not wait 258us (3 times 86us). This may 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 the following:
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 a “lock” because the Arduino will do nothing else while you wait for an answer.
However, if a character is skipped, your program may “hang” there, waiting for the 4th character if the second character is not sent / received correctly (this happens). Therefore, you should set a timeout of 500us at a time when the 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 if you really got the answer:
UPDATED with the full 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 pace things a little bit more for testing: delay here until // it 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 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:
- An error occurred in the calculation of the results, which (probably) was recorded in the answer above. I believe that something other than
double outside of adding made you lose the top 8 bits. The calculation, as indicated above, should give the correct answer. - After a little googling search, I see that this is for the lock serial communication controller . That would be helpful to know. It describes the checksum that I used in the
ReadWriteRegister function above. A function can tell you if it was calculated or the checksum was incorrect. This also implies that longer timeouts may be required. It is not clear whether your device will wait up to 480 ms to get the latest value, or if it continuously caches them and immediately responds with the last value received from ESC. However, records will not be displayed in read values up to 480 ms because of the time it takes for the ESC to receive a command and then to send new values. See the ESC Castle Link protocol . - The
ReadWriteRegister function returns a 16-bit integer that will be more efficient. Comparing floating point numbers is never good. BTW, double is just one float on an 8-bit Arduinos. - The
ReadWriteRegister function does not need a writemode argument, since the register number determines whether you are writing or reading a device. - The
throttle value is recorded only in the settings.
UPDATE 2
In your L ogic A snapshot snapshots, a “scan” for ESC will appear. He tries every device identifier, and some of them respond with non-zero voltage. In addition, it runs on 9600, NOT 115200. Is this from a different setting?
Regardless, he confirms that the controller specification says: write 5 bytes, read 3. Checksum values as expected. However, it runs 10 times slower than your program, so it does not provide much new timeout information. This may mean a slight delay before the device responds, perhaps ~ 1 bit of time or about 100US.
Have you read the controller specification? You should compare the program with the specification to make sure that you understand how the controller works.
I changed the program above:
- synchronize with the controller in
setup (write 5 zero bytes and wait 250 ms), - use scaling numbers from spec (instead of their inverse?),
- use significant constants instead of “magic” numbers (for example, 2042),
- use integer or boolean types for multiple registers instead of
double (see safeState , linkLiveStatus and eStopStatus ) - increase the timeout to 2 ms (continue to increase it if you continue to receive frequent timeouts) and
- print
reg number when an error occurs.
If you want to succeed in this area, you must learn to read the specification and translate its requirements into the appropriate code. The program you started with is not conformal at worst, or at best misleading. I am particularly surprised at the comments that say "INTEGERS" and "FLOATS", but these sections contain the opposite.
Perhaps this is a lesson in fixing the code of another? You really have a lot of problems that you will encounter. If I had nickel every time I said:
- "What is this number for?"
- "This comment is incorrect!"
- "The spectrum says you should ..."
- "Why is it so hard to read? I will just add a few intervals."
... I would be a very rich person. :)
(end of update)
PS
This also corresponds to the symptoms you described: since you did not wait for the transfer to complete, 0 bytes are read for the first time ( read returns byte -1 or 0xFF).
After you called this procedure several times (and queued several commands in the output buffer), 500us passed, and the first command was finally sent. The device responds by sending 3 characters. Later, Arduino finally got the first character. It is being read by one of your read statements, but who knows which one? It will be random, based on the past tense.
Additional commands are sent, and individual characters are received and read by one of these operators until 64 bytes of the OR queues are queued OR Serial.println chars . Then the write OR OR Serial.print command is blocked until there is no room for the newest command to exit. (This refers to the title of your question.)
When a sufficient number of bytes of commands or characters of the debug message are finally transmitted, Serial1.write or Serial.print . In the meantime, the received characters are sent to the input buffer. (Where they are stored until you call read .)
At this point, the three read statements on the line will actually receive the characters sent by the device. But due to accidental consumption of characters earlier, this may be the last character of one answer, followed by the first two characters of the next answer. You are out of sync with 3-byte responses.
To remain “synchronized” with the device, you need to wait for the send to complete using flush and wait for the return response with while .