UART Flow Control

Tip / Sign in to post questions, reply, level up, and achieve exciting badges. Know more

cross mob
lock attach
Attachments are accessible only for community members.
CaDu_3933941
Level 4
Level 4
50 replies posted 25 replies posted 10 replies posted

Hi all,

Like some might remember, I have been working in an RC car for which I need a transmitter and Receiver.

From the Transmitter size, I am sending packets of data to be unpacked by the receiver.

My problem is that it seems that data is being overrun. The transmitter, which sends the car's control signals, is running less processes than the receiver (which is running anything from a digital filter to a PID controller). My theory is that this causes the transmitter to send data at a much faster rate than the receiver can handle. To solve this, I tried to implement an error checking scheme with cheksum and other things. Now my problem seems to persist, for which I then I tried to implement some sort of flow control, where the transmitter does not send data unless the receiver sends a data_ready bit. So far, I have been unsuccessful so I was hoping anyone could take a look a it?

I am attaching both the receiver and the transmitter:

0 Likes
1 Solution
MotooTanaka
Level 9
Level 9
Distributor - Marubun (Japan)
First comment on blog Beta tester First comment on KBA

Hi,

In general, floating point calculations take much longer than the integer calculations.

And also the time required for calculation may vary depending on the value(s).

But it is difficult for me to decide if the time required is too long or not.

So after you applied everything discussed so far and still see the problem,

considering a fixed point math may be useful for reducing the time.

(Please take advantage google and wiki for this topic.)

moto

View solution in original post

0 Likes
18 Replies
MotooTanaka
Level 9
Level 9
Distributor - Marubun (Japan)
First comment on blog Beta tester First comment on KBA

Hi,

I just skimmed your sources.

Your transmitter seems to sending a packet each 20ms, which is 50Hz or 50 times per second.

I wonder how quickly the car must react to the control.

An RC airplane may require very fast response, but for the RC car it may not require that quick response.

So, if I were you I would try one of these

(1) Change the CyDelay(20) in the RC_Transmitter's loop to CyDelay(50), this will make the response to 20Hz.

(2) In the RC_Receiver, use a GPIO and set it every time packet_received is set and clear it at the beginning of the for (;;)

    so that you can measure the time required for the packet received event.

    After you get a ball figure measure of required time, set the CyDelay() in the Transmitter to be longer than that.

moto

0 Likes

Motoo,

I added that delay in the transmitter since it seems that's the only way it can operate without an overrun. I should've been more clear.

Ideally, I would want to get rid of that delay and manage the flow control the following way:

- The transmitter would send data and wait for the receiver to unpack it and send back an acknowledge bit. Once the Ack bit is received by the transmitter, the next packet would be sent, so on and o forth.

I am assuming this scheme would be enough to avoid said overrun (If you have any ideas, I would appreciate it)

0 Likes
lock attach
Attachments are accessible only for community members.
MotooTanaka
Level 9
Level 9
Distributor - Marubun (Japan)
First comment on blog Beta tester First comment on KBA

Hi,

If that is the case, I would try to modify your main.c of RC_Transmitter like below.

The changing points are,

(1) I clear the RxBuffer before sending packet

(2) Let Transmitter wait for the ack, with timeout (I defined RX_TIMEOUT_MS 500, but you can change it as you like)

(3) If some response was received, read data_ready or set data_ready as it means timeout occurred.

Note: May be for the ack value using something beside 1 is good, for example 0xA5 or something which can not happen easily.

But for the time being, I just used the test for only 0 or not.

=================

        if(data_ready||!UART_GetRxBufferSize()){

            Data_Out[tx_index++]=(Increase_Decrease<<6)|Cruise_Control<<4|Y_2;

            Data_Out[tx_index++]=Y_1;

            Data_Out[tx_index++]=Servo_2;

            Data_Out[tx_index++]=Servo_1;

            for(i = 0; i<=(tx_index-1); i++){

                sum+=Data_Out;

            }

            Data_Out[tx_index++] = sum & 0xFF;

            Data_Out[tx_index++]=CR;

            UART_ClearRxBuffer() ; /* === */

            UART_PutArray(Data_Out,tx_index); // Data Out

            data_ready=0;

        }

        // wait for a response from the receiver

        timeout_count = 0 ;

        while(UART_GetRxBufferSize() == 0) {

            timeout_count++ ;

            if (timeout_count >= RX_TIMEOUT_MS) {

                break ;

            }

        }

        if (UART_GetRxBufferSize()) { // if we have receiced a response

            data_ready = UART_ReadRxData();

        } else { /* timeout occures, assume we can send new one */

            data_ready = 1 ;

        }

=================

Since I have no way to test this, I just made sure that the project is "compile-able".

moto

0 Likes

Motoo,

Thanks, how would this be processed on the receiver side?

0 Likes
MotooTanaka
Level 9
Level 9
Distributor - Marubun (Japan)
First comment on blog Beta tester First comment on KBA

Hi,

> Thanks, how would this be processed on the receiver side?

In the main.c of RC_Receiver line 302 - 305

        if(data_ready){

            UART_WriteTxData(1);

            data_ready=0;

        }

When data_ready != 0, receiver sends back 1 to the transmitter.

But this response has not been really utilized.

So I added some lines to confirm the response from the receiver.

But I regret that I should have made it something like below

     UART_WriteTxData(0xA5) ;

so that a simple noise can not be mistaken as a response.

But to start with this may be OK.

If you change this. You also would like to change main.c of Transmitter line 103-107

from

        if (UART_GetRxBufferSize()) { // if we have receiced a response

            data_ready = UART_ReadRxData();

        } else { /* timeout occures, assume we can send new one */

            data_ready = 1 ;

        }

to

        if (UART_GetRxBufferSize()) { // if we have receiced a response

            data_ready = UART_ReadRxData();

             if (data_ready != 0xA5) { /* 0xA5 is the expected response */

                        data_ready = 0 ;

               }

        } else { /* timeout occurred, assume we can send new one */

            data_ready = 1 ;

        }

moto

0 Likes

Got it, son on the receiver side, where on the code should I state that data_ready = 1 in order to enter that if statement? Because I was originally thinking I could set it to 1 at the end of the interrupt right before clearing it.

0 Likes
MotooTanaka
Level 9
Level 9
Distributor - Marubun (Japan)
First comment on blog Beta tester First comment on KBA

Hi,

In terms of "communication" may be after the line of

     packet_received = 0 ;

in the block of "if (packet_received) {"

But to allow safe calculation, may be after the line of

        // Servo Control

        PWM_Servo_WriteCompare(Servo);

And put

        if(data_ready){

            UART_WriteTxData(1); // 1 or 0xA5 depending on your check procedure in the transmitter

            data_ready=0;

        }

moto

0 Likes

Got it,

Also, any chance that processing the buffer on the UART ISR Handler might be holding the CPU up for too long?

0 Likes
MotooTanaka
Level 9
Level 9
Distributor - Marubun (Japan)
First comment on blog Beta tester First comment on KBA

Hi,

As far as I see, the RX_Handler seems to be ok.

But may be you need to have RX_ISR_ClearPending()  outside of  "if (UART_GetRxBufferSize() .. " block

===================

CY_ISR(RX_Handler){

    if(UART_GetRxBufferSize() &&

        packet_received == 0){

        Bytes_In=UART_GetByte();  

        if(Bytes_In == CR){ /*End of message received*/

             /*If end of message received, unpack buffered data onto corresponding variables*/

            packet_sum = (Bytes_In[0]+ Bytes_In[1]+Bytes_In[2]+Bytes_In[3])&0xFF; /*Calculates cheksum an compares*/

            if(packet_sum == Bytes_In[4]){

                packet_received =1;

            }

            else packet_received = 0;

        }

        i++;

        if(i == MSG_LENGTH) {

            i = 0;

        }

        data_ready=1;

//        RX_ISR_ClearPending(); /* from here */

    }

     RX_ISR_ClearPending(); /* To here */

}

===================

And also if the devices connected allow, you may want to make I2C faster.

400KHz ?

moto

0 Likes

Woops, thanks for that. I didn't realize it.

Would speeding up the I2C help?

0 Likes
MotooTanaka
Level 9
Level 9
Distributor - Marubun (Japan)
First comment on blog Beta tester First comment on KBA

Hi,

> Would speeding up the I2C help?

At least it will shorten the main loop time.

But only if the devices connected can go with that speed.

moto

0 Likes

Got it, I shall try that.

Another thing I noticed, it seems like the communication is fine if I power up the devices the following order:

1. Receiver ON

2.Transmitter ON

However, if I turn off the receiver off and then back on, it stops working and it seems as if the data packets get stored in the wrong buffer addresses.

I contrast, if I turn the transmitter off and then back on, while keeping the receiver on, communication will pick back up just as normal.

Another thing, I have a cruise control mode and when communication between both tx and rx is fine, whenever I set the cruise control to come on, then the communication falls apart once more and the receiver stops responding. The Cruise control functionality is this one:

if(Cruise_Control_Status==TRUE){

           

            I2C_DataWrite[0]=0x05;

            I2C_DataWrite[1]=0xFF;

            I2C_MasterWriteBuf(LED_ADDR, I2C_DataWrite, 2, I2C_MODE_COMPLETE_XFER);

            while(I2C_MasterStatus()!=I2C_MSTAT_WR_CMPLT);

            start_measure();

            while(!dataReady){}

            speed=65*iFreq/20;

            speed_out=Speed_Filter(0.1);

            if(Increase_Decrease == 0x01||

                Increase_Decrease == 0x02){

                Timer_Speed_Set_Start();

                Reset_Speed_Write(1);

                Reset_Speed_Write(0);

               

            }

            if(!c){

                Speed_Set = (int)speed_out;

                c=1;

            }

            PWM_Motor_Start();

            PID(Speed_Set,2.0,0.8,0);

        }

You can find the PID and speedout functions on the project files I provided for the receiver.

Would any of this look like something that would hold the program up?

0 Likes
MotooTanaka
Level 9
Level 9
Distributor - Marubun (Japan)
First comment on blog Beta tester First comment on KBA

Hi,

> However, if I turn off the receiver off and then back on,

> it stops working and it seems as if the data packets get stored in the wrong buffer addresses.

Please use debugger and when things go wrong, pause the program and see what is wrong and/or where the program is.

Meantime, as I read the main.c of RC_Transmitter, I thought may be you need to put the part to receive the response from the Receiver in the beginning of the main loop and if it receives the response, do the rest of the main loop otherwise let the main loop waiting for the receiver.

On the other hand, the Receiver returns response only when data_ready == 1, which does not happen unless Transmitter sends the data.

So how about let Receiver send ack either after received/processed the data from from Transmitter or some time has passed without receiving any data?

My idea sketch is something like

Note: I'm using 0xA5 for ack value, but you can chose whatever value you like.

NOTE: Followings are my "idea" and have not been tested, so please use as a reference.

RC_Transmitter main_loop

=======================

uint8_t response = 0 ;

int receiver_ready = 0 ;

for (;;) {

     receiver_ready = 0 ;

// wait for a response from the receiver

timeout_count = 0 ;

while(UART_GetRxBufferSize() == 0) {

timeout_count++ ;

if (timeout_count >= RX_TIMEOUT_MS) {

break ;

}

}

if (UART_GetRxBufferSize()) { // if we have received a response

response = UART_ReadRxData() ;

if (response == 0xA5) {

     receiver_ready = 1 ;

}

}

if (receiver_ready) {

     // do the rest of the main loop

}

}

=======================

RC_Receiver main_loop

=======================

#define MAX_IDLE_COUNT 500 // you decide this number

uint32_t idle_count = 0 ;

for(;;) {

if (data_ready) {

UART_WriteTxData(0xA5) ;

data_ready = 0 ;

idle_count = 0 ;

} else {

idle_count++ ;

if (idle_count >= MAX_IDLE_COUNT) {

UART_WriteTxData(0xA5) ;

idle_count = 0 ;

} else {

CyDelay(1) ; /* use CyDelayUs() if 1ms is too long */

}

}

// do the rest of the main loop

}

=======================

> Another thing,

Basically any line with

while( condition ) ;

type of waiting is dangerous.

So I would rewrite the part something like

=======================

#define MAX_TIMEOUT_MS 500 // you decide this number

uint32_t timeout_count = 0 ;

if(Cruise_Control_Status==TRUE){

I2C_DataWrite[0]=0x05;

I2C_DataWrite[1]=0xFF;

I2C_MasterWriteBuf(LED_ADDR, I2C_DataWrite, 2, I2C_MODE_COMPLETE_XFER);

timeout_count = 0 ;

while(I2C_MasterStatus()!=I2C_MSTAT_WR_CMPLT) {

timeout_count++ ;

if (timeout_count >= MAX_TIMEOUT_MS) {

break ;

} else {

CyDelay(1) ; // use CyDelayUs() if 1ms is too long

}

}

if (timeout_count < MAX_TIMEOUT_MS) {

start_measure();

timeout_count = 0 ;

while(!dataReady){

timeout_count++ ;

if (timeout_count >= MAX_TIMEOUT_MS) {

break ;

} else {

CyDelay(1) ; // use CyDelayUs() if 1ms is too long

}

}

if (timeout_count < MAX_TIMEOUT_MS) {

speed=65*iFreq/20;

speed_out=Speed_Filter(0.1);

if(Increase_Decrease == 0x01||

Increase_Decrease == 0x02){

Timer_Speed_Set_Start();

Reset_Speed_Write(1);

Reset_Speed_Write(0);

}

}

}

if(!c){

Speed_Set = (int)speed_out;

c=1;

}

PWM_Motor_Start();

PID(Speed_Set,2.0,0.8,0);

}

=======================

moto

0 Likes

Got it, I figured that could be it. Although I was wondering, could the floating point math be causing a big delay as well? I am running a floating point low pass filter as well as a PID controller which requires a lot of floating point multiplications.

0 Likes
MotooTanaka
Level 9
Level 9
Distributor - Marubun (Japan)
First comment on blog Beta tester First comment on KBA

Hi,

In general, floating point calculations take much longer than the integer calculations.

And also the time required for calculation may vary depending on the value(s).

But it is difficult for me to decide if the time required is too long or not.

So after you applied everything discussed so far and still see the problem,

considering a fixed point math may be useful for reducing the time.

(Please take advantage google and wiki for this topic.)

moto

0 Likes

Thank you so much for your support.

Also, What would be the purpose for clearing the RX buffer before sending the next packet?

0 Likes
MotooTanaka
Level 9
Level 9
Distributor - Marubun (Japan)
First comment on blog Beta tester First comment on KBA

Hi,

> Also, What would be the purpose for clearing the RX buffer before sending the next packet?

This time, the Transmitter is expecting receive a response from the Receiver,

but there could be some noise and/or garbage data already received in the RX buffer,

so I clear the Rx buffer to get rid of them to prepare for the new response which should be sent

from the Receiver after Transmitter sends the packet.

To be honest, this may not be necessary, but trying to make the program more robust,

adding this line won't harm, I hope.

moto

0 Likes