起因

 

一直想通过定时器的捕获通道捕获 PWM,这种需求是非常基本的。各种开发板例程也都有,但是精度不怎么样,能捕获的频率也不是很高。对于高频和高精度情况下明显不适用。

 

有经验的工程师会选择参考手册中介绍的 PWM 输入捕获功能,但是该功能有一个很大的限制:

 

 

一个定时器有四个通道,但是只有两个通道才可以使用  PWM 输入捕获,这极大的限制了可用通道,而在有些需求中,定时器可用通道当然是越多越好。

 

所以鱼鹰一直在苦思冥想一个高精度的通用的 PWM 输入捕获程序。

 

领悟

一天晚上,鱼鹰一边洗衣,一边思考,终于灵光一闪,既然编码器功能可以借助两组通道完成编码器,为什么 PWM 输入捕获不能使用类似的机制呢?

 

手册中的 PWM 输入因为使用了 从模式 功能,才有上述限制,只要我不使用从模式,仅仅使用两个通道的捕获功能,就可以解除该限制了吧?

 

所以借助《如何高效的扩展定时/计数器?》里面介绍的知识,鱼鹰顿悟了……

 

效果

左边用逻辑分析仪设置的输出参数,右边通过捕获程序捕获的结果。

 

STM32F103,主频 72 Mhz,定时器时钟频率 72 MHz。 

 

 

 

 

我们从上图可以知道,即使输入 PWM 频率高达 1 Mhz,我们的程序还是将其准确测量出来了。

 

当然在占空比计算方面,因为定时器频率自身的原因,计算的并不精确,和实际的误差比较大,不过在这么高的频率下,只是 10% 的误差,我认为还是可以接受的。

 

而频率达到 3 MHz 时,频率比较接近(4 % 误差),但是占空比已经出现了很大误差,已经不可用了(可能计算有点问题)。

 

100 K,占空比 5 % 的情况下,频率测量误差 0%,占空比误差 1%!!!

 

优缺点

由于时间与篇幅原因(花了很多时间做测试),本篇笔记不会介绍捕获原理(鱼鹰要写的话,一定会尽可能的介绍清楚,所以字数会比较多,目前时间不足),主要是想通过本篇介绍 F103 72Mhz 的情况下所能达到的极致效果,让各位在接任务时有一个概念,看是否能简单通过 103 完成功能。

 

现在简单介绍一下鱼鹰这个方案的优缺点:

 

优点:

1、不占用 CPU 资源,使用 DMA 与定时器捕获功能完成。    而如果使用中断的方式,100KHz 下,10 us 就需要进入一次中断,基本不用干其他事情了。一旦中断被别的代码短暂禁用,错过了捕获,那计算的结果可能就有问题了。

2、可以分时使用定时器的四个通道,不会被所谓的从模式所限制,但能达到和手册介绍 PWM 输入捕获功能一样的精度。

3、可以捕获高频信号(MHz),并且精度高。

4、精度和定时器时钟频率有很大关系(F1  1/72 us),如果 F4 的芯片,定时器频率更高,那么可以达到更高的精度(可以算优点,也可以算缺点)。

5、因为采用 DMA 传输 + 硬件(定时器)方式,计算的结果可以信任,不需要太多的处理(比如滤波)。

6、单个通道可以达到很高的捕获率,甚至可以通过特定的算法进行实时捕获,而对 CPU 本身的影响却很小(即不占用 CPU 资源实时捕获计算)。

7、解除STM32定时器只能捕获上升沿或下降沿,而不能双边沿捕获的限制。

 

缺点:

1、只能分时测量每个通道的 PWM,不能同时测量(如果频率很高,几十个微秒就能切换 一次测量,问题不大)。

2、占用 DMA 传输通道,导致对应的通道的外设不能使用(比如串口)

3、代码很简单,但是很难理解(下面的代码计算了所有捕获,实际上只要计算几个就行,因为数据可以信任)。

uint32_t cal_duty_cycle(capture_buff_def *buff, uint32_t size){    assert_param(size > CCR_VALUE_BUFF);        for(int i = 0; i < size - 1; i++)    {        cycle_temp[(i << 1)]        = buff[i + 1].ccr1 - buff[i].ccr1;         cycle_temp[(i << 1) + 1]    = buff[i + 1].ccr2 - buff[i].ccr2;    }      for(int i = 0; i < size - 1; i++)    {        duty_cycle_temp[(i << 1)]     = /* cycle_temp[(i << 1)]  - */(buff[i].ccr1 - buff[i].ccr2);         duty_cycle_temp[(i << 1) + 1] = /* cycle_temp[(i << 1) + 1]  - */(buff[i].ccr1 - buff[i].ccr2);    }        cycle       = (1000 * 1000 * 72) / cycle_temp[20];     duty_cycle  = duty_cycle_temp[20] * 100 / cycle_temp[20];     return 0;}

 

咱们下期再见了!不出意外更新的应该就是本篇的续集了。

 

 

来源:公众号【鱼鹰谈单片机】

作者:鱼鹰Osprey(emOsprey)