为什么需要波特率自动检测机制

  1. 我们都知道,串口通讯是需要约定波特率才能够进行准确的通讯。
  2. 此时假设,你们公司开发了一个板子,有一个串口与外接设备进行通讯。因为你们公司开发的产品要提供给多个客户,而有些客户可能之前就有一个类似功能的产品,之所以现在用你们公司的产品,可能是基于安全,价格,稳定性等因素考虑。
  3. 现在,你规定自己板子上的串口波特率为115200,而你客户之前用的那个产品波特率是9600。你板子都要卖给客户,总不可能要求客户去该他们的波特率吧。好,你们公司为了保住这个订单,现在修改波特率为9600,然后安排人将程序重新烧录到产品中。
  4. 这个订单是保住了,现在又来了一个客户,他们也想要用你们的产品,不过他们的波特率是19200。没办法,客户是上帝,你们又得重新修改波特率,然后烧录。
  5. 人工成本也是钱呀,所以说,如果不提出一个好的解决办法,这家公司迟早会凉凉。
  6. 为了解决这个问题,我们可用用0x55/0xAA这两个特殊值进行处理。

波特率自动检测机制的原理

  1. 我们知道0x55/0xAA从二进制角度来看,就是变化的01。我们知道,0是低电平,1是高电平。而0x55/0xAA这两个特殊的值每次变化时候,他们的01都是1bit。因此,我们只需要计算出一个高电平或者一个低电平的持续时间,即可算出波特率。
  2. 例如,现在我们截取0x55/0xAA波形中的一小段如下。现在如果我想知道这里的波特率,只需要测量出图中所指示的低电平部分持续时间即可。
  3. 测量低电平持续时间也很简单,打开MCU的双边沿检测。对端设备串口发送0x55/0xAA,此时MCU检测到下降沿那么就打开定时器,当检测到上升沿那么就关闭定时器。最终将定时器中的值读取出来,即可算出波特率。

UART 如何实现波特率自动检测原理介绍_自适应

代码

  1. 如下为网上找到的一段波特率自适应的代码,我进行简单的分析。
  2. 这里就是让 GPIO10 作为 RX 输入,不过将该引脚设置为 UART 之前,先将该引脚设置为浮空输入,测试出对端设备的波特率,然后再将该引脚设置为 UART。
  3. 打开定时器,之后 CPU 轮询 GPIO10,检测 32 次该引脚的电平变化,找到最大的两次电平变化差值。然后再将该值进行 0.75% 的修正。最终返回算出来的波特率。
  4. 这个时候肯定会有人问,如果对端设备的波特率是 115200,但是我算出来的是115249 ,阁下如何应对呢?木有关系,首先 UART 是存在一个波特率误差容限的,而且你在设置一个非标准波特率时,芯片也会匹配到一个接近的标准波特率。因为波特率本身就是利用时钟产生的。
u32 USART1_Baud(void)
{
  u16 t1=0,t2,t=0; // 定时器寄存器为16位
  u32 b1,b2;
  u32 i;

  GPIO_Init(GPIOA, 10, GPIO_IN_FLOAT); // GPIOA.10浮空输入
  TIM_Open(Tim3); // 开TIM3的时钟
  TIM_Enable(TIM3); // 开启TIM3
  b1 = GPIO_Pin_Get(GPIOA,10); // 读GPIOA.10的电平
  for(i=0;i<32;) // 连续检测GPIO.10引脚32次电平变化
   {
    b2 = GPIO_Pin_Get(GPIOA,10); // 读GPIOA.10的新值
    if(b2 != b1) // 如果有电平变化
      {
        t2 = TIM3->COUNT; // 读定时器中的值
        b1 = b2; // 更新为新的引脚值

        if((t1 == 0)&&(t==0)) // 第一个电平变化
        {
          t1 = t2; // 记录第一个时刻点
        }
        else // 不是第一个电平变化
        {
          if(t == 0) // 第一段电平
          {
            t = t2-t1; // 记录第一段电平所用时间
          }
          else // 不是第一段电平
          {
            if((t2-t1)< t) // 保留电平段的最小值
              {
                t = t2-t1;
              }
          }
          t1 = t2; // 更新为新的时刻点
        }
        i++; // 电平变化数+1
      }
   }
  TIM_Close(Tim3); // 关闭TIM3的时钟
  return ((u32)t*403/400);
  // 修正波特率值(加上电平变化的斜率,大概为0.75%,经验值)
}

参考

  1. UART串口波特率自适应