和我一起学51单片机(2)——PWM测量(两路,频率,占空比,相位差可测)
一、方法选择
(对概念的解释来自维库)
1.测频法
计数法是基本的频率测量方法,也是数字频率计常用的测量方式。这种方法通过计数输入信号在一定时间内的周期数来计算频率。
原理:
将待测信号的周期信号输入频率计。频率计内部有一个高精度时钟,通过计时器或计数器记录一定时间内输入信号的周期数。
计算公式为: f=TN
其中,f 为频率,N 为在时间 T 内计数的周期数。
步骤:
1.设定一个时间基准(例如 1秒)。
2.统计这段时间内输入信号的周期数N。
3.频率 f=N(假设基准时间为 1 秒)。
优点:测量精度高,能够实现较为准确的频率测量,适合于稳定频率的信号测量。
缺点:需要有足够长的采样时间才能达到较高的精度,短时间内的高频信号可能无法准确捕捉。
2.测周法
2. 测周法
周期测量法通过测量信号的周期来间接计算频率。信号的频率是周期的倒数,周期越长,频率越低。
原理:测量输入信号的一个周期时间T,然后利用公式f=1/T 计算信号频率
步骤:测量输入信号的周期 T
优点:对于低频信号来说,周期测量法可以提供很高的精度,对于信号的周期比较稳定的情况,测量结果较为准确
缺点:当信号频率较高时,周期非常短,需要更高的采样精度来进行测量(定时器频率开太高了频繁进中断其他功能就不好了),在实际应用中,可能需要较长时间来准确测量周期
3. 等精度测量法(简略)
上述两种方法都会产生±1 个被测时钟周期的误差,在实际应用中有一定的局限性,对于测频法很有可能你的一秒开始和结束并不是完美的一个周期的结束或开始;而且根据两种方式的测量原理,很容易发现频率测量法适合于测量高频时钟信号,而周期测量法适合于低频时钟信号的测量,但二者都不能兼顾高低频率同样精度的测量要求。
等精度测量法,不过是不让它的一秒闸门(虚拟闸门)在一秒时关闭,而是当检测到最后一个波的周期结束时,关闭闸门,就得到了一个实际闸门的时间,这样去算,就会更加容易实现。
等精度测量法与前两种方式不同,其最大的特点是,测量的实际闸门时间不是一个固定值,它与被测PWM相关,是被测PWM波的整数倍。在实际闸门下,同时对标准时钟和被测时钟信号的时钟周期进行计数,再通过公式计算得到被测PWM的时钟频率。 由于实际闸门时间是被测时钟周期的整数倍,就消除了被测信号产生的±1 时钟周期的误差。原则上闸门时间越长,则精度越高。
但是等精度测量法的优势在51里面好像体现不出来(悲)
那咱们就选择 测周法
二、代码实现
1.定时器初始化,选择自动重装
void Timer0_Init(void) //100微秒@11.0592MHz
{
// AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x02; //设置定时器模式
TL0 = 0xA4; //设置定时初始值
TH0 = 0xA4; //设置定时重载值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1; //使能定时器0中断
}
2.中断函数
counta++;
if(P2_6==1&&hisA==0)
{
Acontainer=counta;
counta=0;
}
if(P2_6==0&&hisA==1)
dcontainer=counta;
hisA=P2_6;
现在进行逐行解释
counta++;
一个变量不断++,去计量时间
if(P2_6==1&&hisA==0)
{
Acontainer=counta;
counta=0;
}
软件检测上升沿,每测量一次,把计时的变量清零,并把当前时间赋给一个变量,用于计算
if(P2_6==0&&hisA==1)
dcontainer=counta;
检测下降沿,计量高电平时间,这个是为了计算占空比
第二路波同理
countb++;
if(P2_7==1&&hisB==0)
{
Pcontainer=counta;
Bcontainer=countb;
countb=0;
}
if(P2_7==0&&hisB==1)
ddcontainer=countb;
hisB=P2_7;
但你可能注意到了Pcontainer=counta;
是的,这是用来测量相位差的,因为每次测量计时变量都会清零,所以你只要将两个波拉到同一时间线,就可以测量两个上升沿的时间间隔。一定要统一时间线,否则无法测量
其实就是你在测第一路波的时候(愉快),检测到第二路波的上升沿,顺手把第二路波上升沿的时间记录下来了而已。
3.计算
F = 10000 / Acontainer;
D = dcontainer * (1.0 * 100 / Acontainer);
FF = 10000 / Bcontainer;
DD = ddcontainer * ( 1.0 * 100 / Bcontainer);
p = 360 - Pcontainer *(1.0 * 360 / Acontainer);
对于这里的1.0的使用,大家都知道他是隐式转换,但是笔者并不是怕除不开就使用他的,而是受到了51单片机的性能限制。
注意!!!
注意!!!
注意!!!
如果你发现某些时候你的计算式中的各个数据都对,并且整个式子你自己手算也对,但就是单片机算不对的话,很有可能是因为你超出了他的运算上限。51单片机是8位,最大就到65535,运算过程一旦超过65535,那就会计算异常,所以若涉及乘除,我们一般先用一个较大的数去除以某个数,接着再乘下一个数。
主函数
void main()
{ P2_6=1;
P2_7=1;
Timer0_Init();
while(1)
{
F = 10000 / Acontainer;
D = dcontainer * (1.0 * 100 / Acontainer);
FF = 10000 / Bcontainer;
DD = ddcontainer * ( 1.0 * 100 / Bcontainer);
p = 360 - Pcontainer *(1.0 * 360 / Acontainer);
}
}
Acontainer和Bcontainer是测量出来的周期,有T=Acontainer*100/1000000(us换为s)=1/f,所以F = 10000 / Acontainer;
至于为什么把P2_6和P2_7拉高?
这也是一个知识点
PO口是三态口:高阻态、输入、输出,如果想要用时,需要接10K的上拉电阻。
P1、P2、P3口是准双向的8位IO口,内带上拉电阻。之所以称它为“准双向”,是因为该口在作为输入使用前,要先向该口写1操作,然后单片机内部才能正确读出外部信号,也就是使其有个“准”备的过程,所以才称为准双向口。
tips:但其实笔者更喜欢用外部中断的方法去检测下降沿,这样会更准确一些,占空比只需要通过定时器中断计量高电平时间就可以了。
这里浅说一下如何测量相位差(摆出两个外部中断的函数)
void ce()interrupt 0
{
time3++;//记次数
if(time3==1)
{
phasepart1=time1;
u=time33;
}
if(time2==1)
{
level=timer;
timer=0;
time2=0;//结束了
time5=time3;
time3=0;
time4=time1;//记录时间
time1=0;//时间清零
}
}
void ce2()interrupt 2
{
time33++;//记下跳沿次数
if(time33==(1+u))//相位差计算,i来自统一时间线
{
phasepart2=time1;//记录时间,为了计算相位差
}
if(time22==1)//说明闸门该关闭了,但是可能最后一次波还未结束,因此暂缓关闭,并且该次为最后一次
{
levelmirror=timermirror;//记录高电平的时间
timermirror=0;//清零时间
time22=0;//结束了
time55=time33;//记录下跳沿次数,为了算频率
time33=0;//清零次数
time44=time11;//记录最后一次周期结束时候的时间
time11=0;//时间清零
}
}
关键在于
if(time3==1)
{
phasepart1=time1;
u=time33;
}
和
if(time33==(1+u))//相位差计算,i来自统一时间线
{
phasepart2=time1;//记录时间,为了计算相位差
}
检测time3==1是在记录A波第一次下降沿的时候的时间节点,但我们不知道B波现在第几次下降沿了,于是记录当前B波的当前下降沿次数为u,所以,我们记录下一次B波的下降沿,也就是u+1次的时间,这样就有两路波下降沿的时间差了。
其他就很容易实现了,笔者就不写了。
并且,你可以和上一篇文章联立,实现一个定时器出两路波,测两路波。
(笔者要跨年去了!)
结语
笔者的51就先写到这里吧,以后说说stm32的事情,如果有错误欢迎指正