How to use attachInterruptVector in Teensy 4.1 with Teensyduino to call instruction when Serial write buffer has transmitted all bits

nus_dev

New member
Hi everybody

I am controlling a BLACF30-C142 linear actuator (https://en.inspire-robots.com/product/blacf30-c142-series) from my Teensy 4.1 (I use Teensyduino 1.8.13 linuxaarch64). Therefore, I have connected my Teensy's RX3 and TX3 pins to a UART-RS485 converter and the actuator's A+ and B- ports to the same converter. Afterwards, I chose my Teensy's pin 18 to be the "ENABLE" pin (HIGH when the serial line is in writing mode and LOW otherwise). The baud rate is set to 115200 bits/second.

Since I want to send an array of values to the linear actuator, I wrote the following code as per the linear actuator's protocol:

C++:
void setup() {
  Serial3.begin(115200);// Baud rate set to 115200
  pinMode(18,OUTPUT);// Enable pin is OUTPUT
  digitalWrite(18,LOW);// Enable pin is LOW
}

void loop() {
    // Construct a packet to send to the linear actuator
    uint8_t TX_Array[40] = {0};
    TX_Array[0] = 0x55;
    TX_Array[1] = 0xAA;
    TX_Array[2] = 0x03;// Length of the data frame
    TX_Array[3] = 0x01;// Motor ID
    TX_Array[4] = 0x30;// Instruction type
    TX_Array[5] = 0x00;// Register address
    TX_Array[6] = 0x00;
    TX_Array[7] = 0x00;// Checksum
    const int Tx_Len = 3;// Length of the data
       
    // Compute the checksum
    unsigned int i = 0;
    int size_cnt = 0;
    uint8_t check_sum = 0;
    for (i=2;i<(Tx_Len + 4);i++)// The checksum does not consider the first two bytes. The last byte stores the checksum
    {
        check_sum = check_sum + TX_Array[i];
    }
    TX_Array[Tx_Len + 4] = check_sum & 0xFF;
    size_cnt = Tx_Len + 5;// The total size of the packet
   
    // Send the packet to the linear actuator via Serial3
    digitalWrite(18,HIGH); // Serial3 is in writing mode
    Serial3.write(TX_Array,size_cnt);
    delayMicroseconds(1150);
    digitalWriteFast(18,LOW);// Serial3 is in listening mode
   
    delayMicroseconds(1000);
}

The code above works well. However, I would like to know if there is a way to avoid calling the
C++:
delayMicroseconds(1150);
instruction, please. I would like the
C++:
digitalWriteFast(18,LOW)
instruction to be called as soon as the last bit in the Serial3 channel's buffer has been sent out. I am considering using the Teensyduino's interrupts (i.e.,
C++:
attachInterruptVector()
), but I do not if it is the right strategy.

If I have to go with interrupts, how could I activate the interrupt when Serial3 channel's buffer is empty?
 
Last edited:
Thank you for your answer. It works.

This is the updated code:

C++:
void setup() {
  Serial3.begin(115200);// Baud rate set to 115200
  pinMode(18,OUTPUT);// Enable pin is OUTPUT
  digitalWrite(18,LOW);// Enable pin is LOW
}

void loop() {
    // Construct a packet to send to the linear actuator
    uint8_t TX_Array[40] = {0};
    TX_Array[0] = 0x55;
    TX_Array[1] = 0xAA;
    TX_Array[2] = 0x03;// Length of the data frame
    TX_Array[3] = 0x01;// Motor ID
    TX_Array[4] = 0x30;// Instruction type
    TX_Array[5] = 0x00;// Register address
    TX_Array[6] = 0x00;
    TX_Array[7] = 0x00;// Checksum
    const int Tx_Len = 3;// Length of the data
      
    // Compute the checksum
    unsigned int i = 0;
    int size_cnt = 0;
    uint8_t check_sum = 0;
    for (i=2;i<(Tx_Len + 4);i++)// The checksum does not consider the first two bytes. The last byte stores the checksum
    {
        check_sum = check_sum + TX_Array[i];
    }
    TX_Array[Tx_Len + 4] = check_sum & 0xFF;
    size_cnt = Tx_Len + 5;// The total size of the packet
  
    // Send the packet to the linear actuator via Serial3
    digitalWrite(18,HIGH); // Serial3 is in writing mode
    Serial3.write(TX_Array,size_cnt);
    Serial3.flush();
    digitalWriteFast(18,LOW);// Serial3 is in listening mode
  
    delayMicroseconds(1000);
}
 
You could use "Serial3.flush()" rather than "delayMicroseconds(1150)". The flush() call will block until all the data has been transmitted. Information on the serial hardware is listed at td_uart.html
@joepasquariello @defragster @dundakitty
Although the "Serial3.flush()" method works, "flush()" is a blocking function. Therefore, for instance, if I am reading data from a sensor through CAN and writing in the serial port using "flush()", I might lose the incoming sensor data.

Do you know if there is a way of avoiding the "delayMicroseconds()" in a non-blocking manner, please? Do you think that an interrupt would be a solution? In that case, how should I proceed with it?
 
Last edited:
@Paul @Angelo
I have added the "transmitterEnable(pin)" function to the code and removed the "delay()" calls. This solution works well when I have one linear actuator. Nevertheless, when I send data to two linear actuators connected on the same serial port (with different identifiers), only one actuator receives the data, while the other does not. For instance, in my example below, the actuator with ID=1 would receive the data packets, but not the actuator with ID=2.

C++:
void setup() {
  Serial3.begin(115200);// Baud rate set to 115200
  Serial3.transmitterEnable(18);
}

void loop() {
    // Construct a packet to send to the linear actuator with ID=1
    uint8_t TX_Array[40] = {0};
    TX_Array[0] = 0x55;
    TX_Array[1] = 0xAA;
    TX_Array[2] = 0x03;// Length of the data frame
    TX_Array[3] = 0x01;// Motor ID
    TX_Array[4] = 0x30;// Instruction type
    TX_Array[5] = 0x00;// Register address
    TX_Array[6] = 0x00;
    TX_Array[7] = 0x00;// Checksum
    const int Tx_Len = 3;// Length of the data
     
    // Compute the checksum
    unsigned int i = 0;
    int size_cnt = 0;
    uint8_t check_sum = 0;
    for (i=2;i<(Tx_Len + 4);i++)// The checksum does not consider the first two bytes. The last byte stores the checksum
    {
        check_sum = check_sum + TX_Array[i];
    }
    TX_Array[Tx_Len + 4] = check_sum & 0xFF;
    size_cnt = Tx_Len + 5;// The total size of the packet
   
    // Construct a packet to send to the linear actuator with ID=2
    uint8_t TX_Array2[40] = {0};
    TX_Array2[0] = 0x55;
    TX_Array2[1] = 0xAA;
    TX_Array2[2] = 0x03;// Length of the data frame
    TX_Array2[3] = 0x02;// Motor ID
    TX_Array2[4] = 0x30;// Instruction type
    TX_Array2[5] = 0x00;// Register address
    TX_Array2[6] = 0x00;
    TX_Array2[7] = 0x00;// Checksum
    const int Tx_Len2 = 3;// Length of the data
     
    // Compute the checksum
    unsigned int j = 0;
    int size_cnt2 = 0;
    uint8_t check_sum2 = 0;
    for (j=2;i<(Tx_Len2 + 4);j++)// The checksum does not consider the first two bytes. The last byte stores the checksum
    {
        check_sum2 = check_sum2 + TX_Array2[j];
    }
    TX_Array2[Tx_Len2 + 4] = check_sum2 & 0xFF;
    size_cnt2 = Tx_Len2 + 5;// The total size of the packet
 
    // Send the packet to the linear actuators via Serial3
    Serial3.write(TX_Array,size_cnt);
    Serial3.write(TX_Array2,size_cnt2);
   
    delayMicroseconds(1000);
}
 
Last edited:
What append if you send only to the second actuator. Comment the first Serial3.write.
And what about 120ohms termination resistors, twisted pair cable,... ????
 
when I send data to two linear actuators connected on the same serial port (with different identifiers), only one actuator receives the data, while the other does not

Do these actuators transmit a response after receiving a message with their ID number? I don't know anything about the specific products you're using, but with RS485 devices a response is pretty common.

If they are sending response, of course your program would need to wait for the response (or some maximum wait, using elapsedMillis while calling Serial3.available()) so you don't begin transmitting the next message while the 1st device is sending its response.

It's also pretty common practice in RS485 to have a brief "turn around" delay after you receive the last byte of any response before you transmit another message. 1ms is a common choice, or the time for a few bytes if using a slower baud rate. The turn around delay allows some timing margin for devices to turn off their transmitters. It also allows for use of repeater products that buffer the signal to transmit farther, but those product usually have timer circuits keep their transmitter on as more bytes arrive, since they don't know or parse the data.
 
As others have mentioned, it sounds like, the actuators might respond to the commands. I tried to go to webpage for the device, but it insisted on cookies with no control on which ...

But sounds similar to some smart servos I have worked with in the past, like the Dynamixels from Robotis.
Before we added the transmitterEnable support, I used to do it with setting pin manually, output the packet, do the flush and then manually set the enable back to read mode...

But by default most of these servos send a response packet, with some form or ack/nak status. Sometimes they might also contain some hardware state, like, the servo is out of bounds, or overheating or ...
Some of these servos had the ability for you to set a register to control in what conditions it will respond, like: always, never, only when you ask to read the state of something...

If it were me, I would try an experiment, instead of:
Code:
Serial3.write(TX_Array,size_cnt);
    Serial3.write(TX_Array2,size_cnt2);
 
    delayMicroseconds(1000);

I might try something like:
Code:
    Serial3.write(TX_Array,size_cnt);
    elapsedMillis em;
    while (em < 100) {
        int ch = Serial3.read();
        if (ch != -1) Serial.printf("%u - %x\n", (uint32_t)em, ch);
    }
    Serial3.write(TX_Array2,size_cnt2);
    em = 0;
    while (em < 100) {
        int ch = Serial3.read();
        if (ch != -1) Serial.printf("%u - %x\n", (uint32_t)em, ch);
    }
    delayMicroseconds(1000);

And see if anything is returned from servos... And get an idea of the timing. May want to adjust the length of time,
I figured 100ms was more than enough time, but... You might still add in Serial3.flush(); after the write, you may
also want to clear the receive buffer before the loop...

Edited: elapsedMicros->elapsedMillis()
 
Last edited:
From the protocol and your code, it seems you send a read status request (0x30) to both devices.
The first device send its status at the same time you send the request to the second device. So there is collision on the bus and the second device receive something rubish.
 
I figured 100ms was more than enough time, but... You might still add in Serial3.flush(); after the write, you may
also want to clear the receive buffer before the loop...
Did you mean to use elapsedMillis in your code instead of elapsedMicros?

Update: it was changed.
 
Last edited:
Did you mean to use elapsedMillis in your code instead of elapsedMicros?
Yes, thanks Although maybe might use micros but have timeout at 1000000 or the like if interested in specifically how fast any data is returned...
 
Back
Top