8
votes

I am transmitting data from my PIC24H microcontroller over 460Kbaud UART to a bluetooth radio module. Under most conditions, this flow works just fine and the bluetooth module uses CTS and RTS lines to manage flow control when its internal data buffers are full. However, there is a bug of some kind in the bluetooth module that resets it when data is continuously sent to it without any breaks, which happens if my data gets backed up in another bottleneck.

It would be nice if the module worked properly, but that's out of my control. So it seems that my only option is to do some data throttling on my end to make sure I don't exceed the data throughput limits (which I know roughly by experimentation).

My question is how to implement data rate throttling?

My current UART implementation is a RAM circular FIFO buffer 1024 bytes long that the main loop writes data to. A peripheral interrupt is triggered by the PIC when the last byte has been sent out by the UART hardware and my ISR reads the next byte from the buffer and sends it to the UART hardware.

Here's an idea of the source code:

uart_isr.c

//*************** Interrupt Service routine for UART2 Transmission  
void __attribute__ ((interrupt,no_auto_psv)) _U2TXInterrupt(void)
{       
//the UART2 Tx Buffer is empty (!UART_TX_BUF_FULL()), fill it
//Only if data exists in data buffer (!isTxBufEmpty())
while(!isTxBufEmpty()&& !UART_TX_BUF_FULL())    {
    if(BT_CONNECTED)
    {   //Transmit next byte of data
        U2TXREG = 0xFF & (unsigned int)txbuf[txReadPtr];
        txReadPtr = (txReadPtr + 1) % TX_BUFFER_SIZE;
    }else{
        break;
    }
}
IFS1bits.U2TXIF = 0;
}

uart_methods.c

//return false if buffer overrun
BOOL writeStrUART(WORD length, BYTE* writePtr)
{
    BOOL overrun = TRUE;
    while(length)
    {
        txbuf[txWritePtr] = *(writePtr);
        //increment writePtr
        txWritePtr = (txWritePtr + 1) % TX_BUFFER_SIZE;
        if(txWritePtr == txReadPtr)
        {
            //write pointer has caught up to read, increment read ptr
            txReadPtr = (txReadPtr + 1) % TX_BUFFER_SIZE;
            //Set overrun flag to FALSE
            overrun = FALSE;
        }

        writePtr++;
        length--;
    }

    //Make sure that Data is being transmitted
    ensureTxCycleStarted();

    return overrun;
}


void ensureTxCycleStarted()
{
    WORD oldPtr = 0;
    if(IS_UART_TX_IDLE() && !isTxBufEmpty())
    {
        //write one byte to start UART transmit cycle
        oldPtr = txReadPtr;
        txReadPtr = (txReadPtr + 1) % TX_BUFFER_SIZE;//Preincrement pointer
        //Note: if pointer is incremented after U2TXREG write,
        //      the interrupt will trigger before the increment
        //      and the first piece of data will be retransmitted.
        U2TXREG = 0xFF & (unsigned int)txbuf[oldPtr];
    }
}

Edit
There are two ways that throttling could be implemented as I see it:

  1. Enforce a time delay in between UART byte to be written that puts an upper limit on data throughput.

  2. Keep a running tally of bytes transmitted over a certain time frame and if the maximum number of bytes is exceeded for that timespan create a slightly longer delay before continuing transmission.

Either option would theoretically work, its the implementation I'm wondering about.

4
Describe what sort of "breaks" the module requires to work...Ben Jackson
@Ben Jackson it would be nice if I knew. It's a bug not a feature so it's not documented. Basically it resets after about 3 seconds of constant throughput, even though it's flow controlled. I can get away with about 25kbps throughput on average.CodeFusionMobile

4 Answers

3
votes

Maybe a quota approach is what you want. Using a periodic interrupt of relevant timescale, add a quota of "bytes to be transmitted" to a global variable to a point that you don't go over some level adjusted for the related deluge. Then just check if there is quota before you come to send a byte. On new transmission there will be an initial deluge but later the quota will limit the transmission rate.

~~some periodic interrupt
if(bytes_to_send < MAX_LEVEL){
  bytes_to_send = bytes_to_send + BYTES_PER_PERIOD;
  }
~~in uart_send_byte
if(bytes_to_send){
  bytes_to_send = bytes_to_send - 1;
  //then send the byte
2
votes

If you have a free timer, or if you can use an existing one, you could do some kind of "debounce" of the bytes sent.

Imagine you have this global var, byte_interval and you have a timer overflowing (and triggering the ISR) every microsecond. Then it could look something like this:

timer_usec_isr() {
    // other stuff
    if (byte_interval)
        byte_interval--;
}

And then in the "putchar" function, you could have something like:

uart_send_byte(unsigned char b) {
    if (!byte_interval) { // this could be a while too, 
                          // depends on how you want to structure the code
        //code to send the byte
        byte_interval = AMOUNT_OF_USECS;
    }

}

I'm sorry to not look much into your code so I could be more specific. This is just an idea, I don't know if it fits for you.

1
votes

First, there's two types of serial flow control in common use.

You say CTS is on, but you might want to see if XON/XOFF can be enabled in some way.

Another approach if you can configure it is simply to use a lower baud rate. This obviously depends on what you can configure on the other end of the link, but it's usually the easiest way of fixing problems when devices aren't able to cope with higher speed transfers.

1
votes

Timer approach which adds delay to Tx at specific time:

  • Configure a free running timer at an appropriate periodic rate.
  • In the timer ISR, toggle a bit in a global state variable (delayBit)
  • In the UART ISR, if delayBit is high and delayPostedBit is low, then exit the TX ISR without clearing the TX interrupt flag and set a bit in a global state variable (delayPostedBit). If delayBit is low, then clear delayPostedBit. The result is to cause a delay equal to one ISR schedule latency, since the ISR will be entered again. This is not a busy-wait delay so won't affect the timing of the rest of the system.
  • Adjust the period of the timer to add latency at appropriate intervals.