如何限制中断驱动的UART传输PIC24H?

8
我正在使用PIC24H微控制器通过460Kbaud UART传输数据到蓝牙无线电模块。在大多数情况下,这个流程都很正常,当蓝牙模块的内部数据缓冲区满时,蓝牙模块使用CTS和RTS线路来管理流量控制。然而,蓝牙模块存在某种错误,当连续发送数据而没有任何休息时,它会被重置,这种情况会发生在我的数据在另一个瓶颈中被阻塞时。
如果模块能正常工作那就太好了,但这超出了我的控制范围。因此,我的唯一选择似乎是在我的端上进行一些数据限速,以确保我不超过数据吞吐量限制(我通过实验大致知道)。
我的问题是如何实现数据速率限制?
我的当前UART实现是一个RAM循环FIFO缓冲区,长度为1024字节,主循环将数据写入其中。当UART硬件发送出最后一个字节时,PIC触发外设中断,我的ISR从缓冲区读取下一个字节并将其发送到UART硬件。
以下是源代码的想法: 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];
    }
}

编辑
在我看来,限流可以通过以下两种方式实现:

  1. 强制在UART字节之间施加时间延迟,从而对数据吞吐量设置上限。

  2. 记录在一定时间范围内传输的字节数,如果超过了该时间段内的最大字节数,则在继续传输之前创建稍长的延迟。

理论上,任何一种选项都可以起到限流的作用,但我想知道的是具体的实现方法。


描述模块需要哪种“中断”才能正常工作... - Ben Jackson
@Ben Jackson,如果我知道就好了。这是一个错误而不是特性,因此没有记录。基本上,在持续吞吐量约3秒后,它会重置,即使它是流控制的。我可以平均每秒获得大约25kbps的吞吐量。 - CodeFusionMobile
4个回答

3
也许您需要的是配额方法。 使用相关时间尺度的定期中断,向全局变量添加“要传输的字节数”配额,直到您不会超过与相关洪水有关的某个水平为止。 然后,在发送字节之前,只需检查是否有配额。在新的传输中,将会有一个初始洪水,但后来配额将限制传输速率。
~~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

我喜欢这种方法。仍然发送数据块,但允许管理流而不延迟每个字节。 - CodeFusionMobile

2
如果你有空闲的定时器,或者可以使用现有的定时器,你可以对发送的字节进行某种形式的“去抖动”(debounce)。
假设你有一个全局变量 byte_interval,并且你有一个每微秒溢出(并触发ISR)一次的定时器。那么它可能看起来像这样:
timer_usec_isr() {
    // other stuff
    if (byte_interval)
        byte_interval--;
}

在“putchar”函数中,你可以有如下内容:
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;
    }

}

很抱歉我没有深入研究你的代码以便更具体地回答。 这只是一个想法,我不确定它是否适合你。

我不想在每个字节之间设置延迟,因为那将需要频繁重新启动tx周期。有没有办法可以适应这种情况,检测在过去的y微秒内传输了x个(太多)字节,并触发计时器延迟?这将更好地适应我的代码结构。 - CodeFusionMobile
你能否让UART在没有实际字符的情况下设置txdone?如果可以,那么它应该发送停止位,如果它没有传输实际数据。然后,您只需忽略每1024个(比如)字符中的2或3个txints,并在这些2或3个字符时间过去后再次开始。如果您能够解决它,那就是我首先尝试的方法。 - Pete Wilson
@Pete Wilson 我无法在不启动传输周期的情况下设置txdone,否则会推出空白数据。我必须使用定时器延迟或其他方法来触发中断。 - CodeFusionMobile
@CodeFusionMobile -- 好的,那么当您使用传输请求进入驱动程序时(在主流级别上,对吧?),我会计算已传输的字符数,并且如果超过1024个字符,则开始短暂的超时,例如1或2毫秒,这将以您所看到的比特率提供一堆字符时间。当超时触发时:(1)如果在中断级别上,请像从txdone进入一样启动UART;(2)如果在主流级别上,请在清除已发送字符计数器后重新发出传输。这对您来说合理吗?让我知道。我喜欢这个UART / USART的东西:它就像我的家 :-) - Pete Wilson

1

首先,常见的串行流控制有两种类型。

你说CTS已经开启了,但你可能想看看是否可以以某种方式启用XON/XOFF。

如果你可以进行配置,另一种方法就是简单地使用较低的波特率。这显然取决于你在链接的另一端可以配置什么,但通常这是解决设备无法处理更高速传输时问题最简单的方法。


硬件流控制已启用,并由蓝牙模块驱动。我尝试了降低波特率设置,但这引起了太多其他问题(尽管它完美地解决了这个特定问题)。对于提供的数据给出一个好答案再加一分。 - CodeFusionMobile
顺便提一下,其他问题源于我有两个运行时配置的数据速度,并且要切换波特率,我必须配置BT模块然后重置它,总共需要2秒的时间,还要重新连接。因此,从应用程序特定的角度来看,降低波特率是行不通的。 - CodeFusionMobile

1

在特定时间为Tx添加延迟的计时器方法:

  • 以适当的周期率配置一个自由运行计时器。
  • 在计时器ISR中,切换全局状态变量(delayBit)中的位。
  • 在UART ISR中,如果delayBit高且delayPostedBit低,则退出TX ISR而不清除TX中断标志,并在全局状态变量(delayPostedBit)中设置一位。如果delayBit低,则清除delayPostedBit。结果是导致延迟等于一个ISR调度延迟,因为将再次进入ISR。这不是忙等待延迟,因此不会影响系统其余部分的时间。
  • 调整计时器的周期以在适当的时间间隔内添加延迟。

退出 TX ISR 而不清除中断标志会导致 ISR 锁定 CPU 并不断调用,从而阻塞主循环,这对我的应用程序是不可接受的。 - CodeFusionMobile
它应该在第二次通过ISR时退出ISR。这是意图(添加等于1个ISR延迟的延迟),这将提供允许的最细粒度定时。我假设所需的延迟是以微秒为单位而不是毫秒。 - Jonathan Cline IEEE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接