从蓝牙到UART的数据丢失

时间:2014-08-05 18:47:26

标签: android bluetooth embedded microcontroller pic

我正在使用PIC18F87J11RN42 Bluetooth module,波特率为38400。我制作了一个连接蓝牙模块的Android应用程序,并将一些数据从我的手机发送到Micrcontroller。一切都很好但我注意到有时候我会丢失一些数据。 PIC18预计大约100个字符,如果应用程序发送这100个字符没有问题,但有时它发送大约98个字符。然后PIC18继续等待2个以上的字符,但下次应用程序发送长度为100个字符的内容。

这导致应用程序与微控制器不同步,因为第一个字节代表一个命令。我发送的每个字符之间有大约20毫秒的延迟但是没有解决问题。这并不是一直发生的,但一旦发生,它就会弄乱一切。我尝试增加时间延迟似乎很有帮助,但我不希望它太慢。

导致部分数据丢失的原因是什么?如何解决?

UART代码和中断

#define CLOCK_FREQ 8000000 
#define BAUD_RATE 38400
#define SPBRG_VAL   ( ((CLOCK_FREQ/BAUD_RATE)/16) - 1)

void ConsoleInit(void)
{
 TRISCbits.TRISC7 = 1;
 TRISCbits.TRISC6 = 0;
 TXSTA = 0x24;
 RCSTA = 0x90; // 0b10010000;
 SPBRG = SPBRG_VAL;
}

BYTE ConsoleGet(void)
{
   if(RCSTAbits.OERR)
        {
        RCSTAbits.CREN = 0;   // Disable UART receiver
        RCSTAbits.CREN = 1;   // Enable UART receiver
        }

        return RCREG;
}


void timerInit (void)
{
// Time Period Achieved : 0.001s
T0CONbits.T08BIT = 0;
T0CONbits.T0CS = 0;
T0CONbits.PSA = 1;
TMR0H = 0xF8;
TMR0L = 0x30;
T0CONbits.TMR0ON = 1;
}

中断例程

if (TMR_IF) {
TMR0H = 0xF8;
TMR0L = 0x30;
  if (PIR1bits.RCIF )
           {
                ProcessMenu(); // In this function I call ConsoleGet() and have switch statment 
           }

        if (INTCONbits.TMR0IE) {
            /* there was a timer overflow */
            PIR1bits.RCIF = 0;

        }

    }

谢谢!

2 个答案:

答案 0 :(得分:2)

当接收程序丢失通过UART接收的字节时,通常意味着接收程序没有足够快地为UART的接收中断服务。 UART只能保存有限数量的接收字节,可能只有一个。因此,在UART接收下一个字节之前,程序必须从UART读取接收到的字节。如果在下一个字节到达之前没有从UART读取字符,那么UART将丢弃一个字节,程序将丢失它。

如果您正在使用ISR来处理UART的接收中断,那么我可以想到两个可能的原因,即您的程序可能无法足够快地为中断提供服务。一个原因是程序可能花费太多时间来处理更高优先级的中断。然后,较低优先级的UART接收中断可能会被推迟太长时间,UART将丢弃字节。您是否有更高优先级的ISR执行了几毫秒?

第二个原因是你的程序可能在接收中断本身花费了太多时间。你的ISR调用似乎可疑的ProcessMenu()。如果ProcessMenu()执行大量处理并且花费的时间超过串行接口的字符间延迟,则程序将无法足够快地为接收中断提供服务,并且UART将丢弃字节。 ProcessMenu()的最长执行时间是多少?

这些原因是两个例子,说明为什么有一般的经验法则可以保持ISR的简短。

如果ProcessMenu()确实花了太长时间,那么修复就是,不要在ISR中调用ProcessMenu()。 ISR应该只读取UART中的字节并将其复制到RAM中的循环接收缓冲区。通过这种方式,ISR可以非常快速地完成,并在UART接收到另一个字节之前再次准备就绪。然后,在ISR之外,主编程循环应轮询接收缓冲区并在有可用字节时调用ProcessMenu()。

答案 1 :(得分:1)

PIC18F87J11有2个不同的UART模块,UART1和UART2。您必须在写入寄存器时指定您正在使用的那个。您既不使用BAUDCON来指定波特率值是8还是16位,

TXSTA1 = 0b00100100; // TXEN, BRGH
RCSTA1 = 0b10010000; // SPEN, CREN (ASYNC assume)
BAUDCON1 = 0b00000000; //8 bit baudrate at 38400.
SPBRGL = .12;         //ecimal value for 38400 BS, 0.16% error

这是UART1初始化。输入/输出由uart模块直接驱动,如数据表中所述 EUSARTx控制将自动执行 将引脚从输入重新配置为输出 需要的。

然后,在你的中断例程中,假设你已经设置了必要的位,你就搞砸了一些东西:

PIE.RC1IE = 1; //Enable receive interrupt IPEN = 0; //All masked interrupt enabled GIE = 1; // PEIE = 1; //Enable peripheral interrupts (RC1IE)

然后,在中断例程的括号内

   INTCON.GIE = 0;   //Disable all interrupts
   //Series of IF to gather interrupt source
   if (PIR1.RC1IF)    //Receive serial flag up?
    {
      if (ucTrackBuffer >= BufferSize){ucTrackBuffer= 0;}
      //if ucTrackBuffer >= BufferSize, reset 0.
      ucTabRx232[ucIndiceTampon] = RCREG1;//Receive uart to buffer
      ucTrackBuffer++;                    //increm buffer tracking
    }
   INTCON.GIE = 1;               //Re-start interrupts
   PIR1.RCIF = 0;                //Lower flag to permit a second interrupt

所有这些代码都可以帮助您。注意你的ISR中的括号,再也不要在ISR中调用函数,因为你可以跳过宝贵的指令周期(跳过一个字符!)。

中断服务

if (PIR1.RC1IF)
 {
   //Code for serial reception
 }
if (TMR1_IF)
 { 
   //Code for timer1 overflow
 }

希望有所帮助,如果您还有其他需要,请告诉我。