【问题标题】:Display previously received UART values显示之前收到的 UART 值
【发布时间】:2011-08-05 16:09:51
【问题描述】:

这对于熟悉 C 的人来说应该很容易回答。我想在 LCD 上显示一个变量(微控制器上的 UART (RS-232) 接收寄存器)的先前值。这是我当前的实现,它工作正常。但我想知道是否有办法在我的中断程序中花费更少的时间。目前,外设配置为在接收到 UART 馈送中的一个新字符后立即跳转到中断例程。有人建议吗?

//Initialization
char U1RX_data = '\0';
char p0_U1RX_data = '\0';
char p1_U1RX_data = '\0';
char p2_U1RX_data = '\0';
char p3_U1RX_data = '\0';
char p4_U1RX_data = '\0';
char p5_U1RX_data = '\0';
char p6_U1RX_data = '\0';
char p7_U1RX_data = '\0';

char U1buf[] = {p7_U1RX_data, p6_U1RX_data, p5_U1RX_data,
                p4_U1RX_data, p3_U1RX_data, p2_U1RX_data,
                p1_U1RX_data, p0_U1RX_data, U1RX_data, '\0'};
disp_string(-61, 17, 1, U1buf); //X, Y, mode, string

void _U1RXInterrupt(void){
    p7_U1RX_data = p6_U1RX_data;
    p6_U1RX_data = p5_U1RX_data;
    p5_U1RX_data = p4_U1RX_data;
    p4_U1RX_data = p3_U1RX_data;
    p3_U1RX_data = p2_U1RX_data;
    p2_U1RX_data = p1_U1RX_data;
    p1_U1RX_data = p0_U1RX_data;
    p0_U1RX_data = U1RX_data;

    U1RX_data = U1RXREG;
    IFS0bits.U1RXIF = 0;    
}

【问题讨论】:

  • 感谢您的所有回答。我会看看循环缓冲区。出于好奇,这是在时钟频率为 7.3 MHz 的 dsPIC30F4013 上进行的。在这种情况下可能不需要优化,但如果我遇到问题我会知道该怎么做。

标签: c microcontroller microchip uart


【解决方案1】:

您可以在中断中使用memmove,如下所示:

void _U1RXInterrupt(void)
{
    memmove(&U1Buf[0], &U1Buf[1], 7);
    U1Buf[7] = U1RX_data;
    ...
}

这取代了您当前手动执行的任务,并且更惯用。

我希望我能正确理解你;要点是使用memmove 将缓冲区向下移动一个字节。顺便说一句,当目标缓冲区和源缓冲区重叠时,使用memmove 而不是memcpy 很重要,如本例所示。

【讨论】:

    【解决方案2】:

    你总是可以制作一个固定长度的循环缓冲区

    char buffer[8] = { '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',);
    char position = 0;
    
    
    /* useable if you have a version of disp_string that takes a "number of chars"*/
    char buffer_nprint()
    {
        /* print from position to the end of the buffer*/
        disp_string(-61, 17, 1, &buffer[position], 8 - position);
        if (position > 0)
        {
            /* now print from start of buffer to position */
            disp_string(-61, 17, 1, buffer, position);
        }
    }
    
    /* if you _don't_ have a version of disp_string that takes a "number of chars"
       and are able to do stack allocations*/
    char buffer_print()
    {
        char temp[9];
        temp[8] = '/0';
        memcpy(temp, &buffer[position], 8 - position);
        memcpy(temp, buffer, position);
        temp[8] = '/0';
        disp_string(-61, 17, 1, temp);
    }
    
    char buffer_add(char new_data)
    {
        char old_data = buffer[position];
        buffer[position] = new_data;
        position = ((position + 1) & 8);
    }
    
    void _U1RXInterrupt(void)
    {
        buffer_add(U1RXREG);
        IFS0bits.U1RXIF = 0;
    }
    

    【讨论】:

      【解决方案3】:

      正如 Emerick 所说,memmove() 如果您可以访问它,它将很好地工作。如果没有,就从谷歌那里获取一个简单的实现,它不应该占用太多的指令内存。

      微控制器上的时钟频率是多少?要考虑的另一件事是,如果您的时钟速度明显高于您的波特率,那么很多这些事情都不会成为问题。例如,如果您的时钟速度为 16 MHz,那么您真的不必担心创建世界上最短的 ISR,只要您在其中不做任何疯狂的计算密集型操作即可。此外,如果您的系统时钟速度明显快于波特率,轮询也是一种选择。

      编辑:我刚刚想到的另一个选择,使用中断。

      您可以将缓冲区存储为字符数组,然后将全局索引保留到下一个空槽,如下所示:

      #define UART_BUF_SIZE 16
      char uart_buf[UART_BUF_SIZE] = {0, 0, 0, 0, 0, 0, 0, 0
                                      0, 0, 0, 0, 0, 0, 0, 0};
      char uart_buf_index = 0;
      

      然后在您的 ISR 中,您需要做的就是将新字节转储到索引指向的存储桶中并增加索引。当起始位置绕缓冲区旋转时,这将自动覆盖缓冲区中最旧的字符。

      void my_isr()
      {
          uart_buf[uart_buf_index] = get_uart_byte();
          uart_buf_index = (uart_buf_index + 1) % UART_BUF_SIZE;
      }
      

      基本上此时您已经有了一个带有旋转起始位置的缓冲区,但它使您不必在每个 ISR 移动 16 个字节的内存。诀窍在于把它读回来,因为你必须考虑到环绕。

      char i;
      for (i = uart_buf_index; i < UART_BUF_SIZE; i++)
      {
          lcd_write_byte(uart_buf[i]);
      }
      for (i = 0; i < uart_buf_index; i++)
      {
          lcd_write_byte(uart_buf[i]);
      }
      

      【讨论】:

      • 更好的是,使用 DMA。中断仍然有每个字符的中断。
      【解决方案4】:

      我将创建一个先前值的数组,并将其视为循环缓冲区。然后中断例程简单地在下一个槽中记录新值,覆盖最后一个值,并增加索引。

      #define DIM(x)  (sizeof(x)/sizeof(*(x)))
      static int  index = 0;
      static char uart[8];
      
      void _U1RXInterrupt(void){
          if (++index >= DIM(uart))
              index = 0;
          uart[index] = U1RXREG;
          IFS0bits.U1RXIF = 0;        
      }
      
      int uart_value(unsigned n)
      {
          int i = index + DIM(uart) - (n % DIM(uart));
          if (i > DIM(uart))
              i -= DIM(uart);
          return(uart[i]);
      }
      

      我假设是同步的、非线程的操作;如果您必须处理多线程,那么可以保护索引变量免受并发访问。它还为缓冲区已满之前的最后一个读数返回零。等等。如果你对自己的编码有信心,你也可以去掉模运算。

      【讨论】:

      • 在中断例程中,可能使用模而不是分支(例如idx = (idx + 1) % DIM(uart);)更快 - 特别是如果您的缓冲区大小是 2 的幂(因为那么编译器应该将其优化为按位与 - 如果idx 是无符号的,这将有所帮助)。我通常不会提到这样的微优化,但在嵌入式架构上最小化中断处理程序中的执行时间似乎是一个真正重要的情况。
      • (您也曾交替使用过idxindex)。
      【解决方案5】:

      由于它是一个 dspic,您可能想看看处理 uart DMA 的 exaples ce214 和 ce114。

      到这里搜索“uart”:

      http://www.microchip.com/TechDoc.aspx?type=CodeExamples&ctl00_MainContent_DocListGridChangePage=6

      DMA 方法是面向块的,每个块一个中断是最轻量的。但是,如果您的信息流不连续,则块方向也可能是一个缺点,因为字节可能会保留在缓冲区中。

      也许你可以通过设置一个计时器来解决这个问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多