How can I control Timing of Serial.write ??

I am a beginner with Arduino. I have a project requiring RS485 half duplex communications. There is no hardware control with a single twisted wire pair for RS485 except with software control of a third wire to control the RX/TX enable pins 2 (RE) and 3(DE) on the 485 chip. These are inverted from each other so pulling both low or high together, enables and disables them reciprocally. I wrote the following basic subroutine to enable TX, send several bytes, then go back to RX state.

byte sendPacket[5] = {2, 136, 49, 6, 4};

void SendToSerial()
{
  delay (50);  // needed for my device TX must not be sent for at least 50msecs after RX

  digitalWrite(MAX483_RX_TX_Control, HIGH);    // sets the RE HIGH(output disabled) and DE LOW(input enable)on MAX483
    for (int i=0; i < 5; i++)
    {
      Serial.write(sendPacket[i]);
    }
  digitalWrite(MAX483_RX_TX_Control, LOW);    // sets the DE HIGH(disabled) and RE LOW(enable)on MAX483  
}

I found that the the above routine is executed much faster and completes before the serial output is completed.

A picture is attached from my scope of the output from the Arduino with the MAX483_RX_TX_Control pin being set high then low. In order for the RX/TX control pin to stay high until Serial.write was complete I added a 7 msec delay before the last digitalWrite command since the entire TX packet at 9600 baud is about 5 msec in duration. A picture of that is attached as well.

It took me some time to figure out why my code was not working correctly. I thought that the serial hardware was interrupt driven and that the next instruction, digitalWrite of the RX/TX Control pin, would not begin until serial TX was completed.

QUESTION: What does one do when the length of the TX packet being sent is unknown and could be very long? How do I provide for this in my code without slowing everything else down unnecessarily?

Hi k1jos

What does one do when the length of the TX packet being sent is unknown and could be very long? How do I provide for this in my code without slowing everything else down unnecessarily?

If you are OK to block until the last byte has been sent, add Serial.flush() after the last print statement and before you toggle the control line. The flush() method will return once the transmit buffer is empty. Assumes you are on IDE version 1.0.x.

With variable length data, this will wait the appropriate length of time. But it obviously is blocking, so you can't do anything else in that time, which may be an problem for you.

I don't know if there is a non-blocking way of checking the state of the transmit buffer.

Regards

Ray

Found this thread which seems to have a way of testing for transmit buffer empty.

The example program blocks until the buffer empties, but it could be modified to let you do other things between checks.

If you want to be able to send the bytes without blocking the rest of your code you need to reorganize things into a system controlled by states.

Create a function that sends the data one byte at a time for every call from loop()

initially the state will be RS485_ready = false so the opening digitalWrite will happen after that the relevant states will be RS485_ready = true and moreDataToSend = true There will be a counter to record how many bytes have been sent one byte will be sent and the counter incremented This will repeat until all the bytes are sent then the states will be RS485_ready = true and moreDataToSend = false then a timer will run (use millis() so it doesn't block) when the time has elapsed the closing digitalWrite will happen and RS485_ready will be changed to false

...R

Thanks for all the advice. How would a state machine work sending one byte per loop cycle if I need other functions managed in the loop like checking sensor data over an I2C bus, etc? Also are you also recommending not to use the hardware Serial but the Software serial library instead to send individual bytes in a controlled way? I have to try this as I dont understand how you keep the timing of my entire 'heartbeat' packet of 5 bytes together. I suspect if there are any significant time gaps between the bytes that the motor controller may not recognize the heartbeat.

It's unfortunate that write() blocks. There is a patch pending which adds a function availableForWrite() to HardwareSerial to check if write buffer has space. It sounds ideal for this application.

9600 is quite slow, about 1ms per character, so sending one byte per millisecond might work. Still, you won't know exactly when the last byte has been transmitted so toggling the enable is likely unreliable.

Really, this type of function is best implemented in the Hardware class, to be done properly requires access to the underlying hardware. Personally I think I would do my own version of HardwareSerial. Perhaps if a generic solution is found then it could be submitted as a patch.

k1jos: Thanks for all the advice. How would a state machine work sending one byte per loop cycle if I need other functions managed in the loop like checking sensor data over an I2C bus, etc?

The demo in this Thread - look at Chapter 8 - uses Serial to read characters one at a time as they become available while doing other things. You can use a similar technique to write the characters.

...R

Robin2:

k1jos: Thanks for all the advice. How would a state machine work sending one byte per loop cycle if I need other functions managed in the loop like checking sensor data over an I2C bus, etc?

The demo in this Thread - look at Chapter 8 - uses Serial to read characters one at a time as they become available while doing other things. You can use a similar technique to write the characters.

No, you can't because there isn't a similar "available" function for write!

Not yet, anyway, possibly in next release.

bobcousins: No, you can't because there isn't a similar "available" function for write!

Not yet, anyway, possibly in next release.

Give us a break here ...

I didn't mean "similar" in such a literal way. I was just trying to show how the function works in the context of other functions.

I had assumed readers would look for the elements that are relevant, not the ones that are irrelevant.

...R

Robin2: Give us a break here ...

I didn't mean "similar" in such a literal way. I was just trying to show how the function works in the context of other functions.

I had assumed readers would look for the elements that are relevant, not the ones that are irrelevant.

Yeah, the thing is there are no relevant elements in the sample you referred to. Sending data is not a problem, the problem is knowing when the data has been transmitted in order to disable the 485 transceiver.

bobcousins: Yeah, the thing is there are no relevant elements in the sample you referred to. Sending data is not a problem, the problem is knowing when the data has been transmitted in order to disable the 485 transceiver.

Perhaps we should wait and see what the person with the problem has to say ?

...R

Thanks Robin, Bob... its still a steep learning curve for me but I am loving it.

I have to know when Serial transmission has completed so I can switch the MAX3485 (now using the correct 3.3v version) RX and TX enable/disable by programming a digital control pin. Before to make sure ther full packet was sent before flipping the MAX3485 control pin, I arbitrarily placed a delay(10) . I tried Serial.flush() and that seems to use up an additional 800 usecs (based on my oscilloscope) but is at least 3-4 msecs faster than the arbitrary delay().

My greatest remaining concern is how well the Arduino can handle several simultaneous processes. The idea of a State machine sounds like the best method but may add a level of complexity that I really dont need.

Here is what my Moteino(s) (wireless a few hundred feet apart) must do:

1) listen by RF wireless for one of three commands (each one byte): Motor On (clockwise), Motor On (Counterclock) and Motor OFF command

2) After Motor Command is received begin communication via RS485 to a proprietary servo-motor (a) first listen for servo motor heartbeat packet (every 220 msec)

(b) send after each heartbeat packet (with a 60 msec delay) a reply heartbeat packet only replacing 4th byte with one of the three motor command bytes. If the timing of this sequence is by more than 20-30 msec then commands are ignored by the servocontroller

(c) ONLY while MOTOR is ON -- begin communication via I2C to a compass/heading sensor and receive updated compass headings at least every 200 msec (more frequent would be OK). Process headings (x,y,x ...corrections for azimuth, etc - these require some floating point math)

(d) send each processed updated compass headings back via Wireless to base station

From a functional perspective , most important is not to miss a Wireless command for Motor Off. Second priority is the timely reception at the base station of accurater compass heading while the motor is turning. (As separate problem I will need to add some fudge factors for delays/hysteresis after motor off command sent and processed)

So any time I can save (such as knowing when to RX?TX cycle my Max3485 chip could be important.

Again, thanks for any advice to this newbie

Jerry

Another way to possibly solve the issue is to add a second Arduino. Seeing they are cheap. Send the 5 bytes to Arduino #2 in the "normal" way and forget it. Then Arduino #2 can spend all its time getting them out to the rest of the hardware with its fiddly notions.

-jim lee

Multiprocessing !! I thought of that as a last resort, where I would have Remote Moteino#1 doing the command processing/parsing, and use flags to distribute the timing sensitive work to Remote Arduino#2.

I am finally getting all the pieces to come together and will first see how well (or not) it all works.

j

If you know the baud rate and the number of bytes to be sent you can predict the time it takes pretty closely. In the logic I tried to outline in Reply #3 I envisaged that the timer (using millis() ) would wait the appropriate time without blocking and then would execute the final digitalWrite()

In contrast to both Serial.flush() and delay() other things could be happening while the time interval is in progress.

On further thought there may not be any appreciable value to the other activities if all the bytes are "printed" at once (I had initially envisaged doing them one at a time) - but the value of using millis() to manage the time before the final digitalWrite() seems inescapable.

...R