遇到问题的时候,自己研究一段时间后仍不能解决,就要及早提问,少钻牛角尖
————小白
相比软件模拟PWM
而言,软件捕获PWM
则十分不稳定,因为需要大量的计算过程,这些过程消耗的时间可能会拖延处理捕获点的到达,导致出现捕获点丢失的情况,除非能够保证数据运算在两个捕获点间隔内完成,否则结果常常是错误的。还是那句话,能用硬件就不用软件,所以慎用软件捕获PWM
01 - 软件捕获PWM思路
软件捕获PWM
的方法有多种,但是无论哪一种,基本思想都是提取高电平和低电平的时间进行计算,小白使用最简单的一种,一个定时器和一个可以边沿触发的外部中断,整体思路图如下:
下面是具体步骤:
- 初始化外部中断
En
为边沿触发,初始化相应端口Pn
- 选定一个定时器
Tn
作计数器 - 第一次外部中断,记录端口当前电平
flag
,开启定时器Tn
,定时器内进行计数,包含溢出 - 第二次外部中断,记录计数值
S1
,Tn
继续跑 - 第三次外部中断,再次记录计数值
S2
,一次捕获完成,置标志位Kn
,重置Tn
和相关变量 - 主进程监听
Kn
,标志位完成后对flag
、S1
和S2
进行处理 - 回到步骤3
当然,这些理想状态,即我们假设每一个捕获点都能被CPU处理,但实际并不是,如果PWM
源的周期频率未知,那么这样做的结果几乎是错误的,如果能够知道PWM
源的一些信息比如是ms级别还是us级别、周期范围等等,就比较好处理
02 - 核心源码
作为例子,选用SINO的SH79M1612B(8051)
进行具体配置
定时器1
#define Fsys_Mhz 12.0
#define timer1_start() TCON |= 0x40
#define timer1_stop() TCON &= ~0x40
#define set_timer1_ms(x) TL1 = ((uint16)((MAX_16BIT-(x)*1000*Fsys_Mhz)))&0x00ff; \
TH1 = (((uint16)((MAX_16BIT-(x)*1000*Fsys_Mhz)))&0xff00)>>8;}
#define set_timer1_value(x) TL1 = (x); TH1 = (x);}
volatile uint32 timer1_overflow = 0;
void timer1_init(void)
{
TMOD |= 0x10;
TCON1 |= 0x08;
set_timer1_ms(1);
ET1 = 1; //定时器1中断允许
EA = 1;
}
void timer1_ISR(void) interrupt 3
{
set_timer1_ms(1);
timer1_overflow++;
}
捕获PWM
#define set_port_in(x,y) P##x##PCR |= (1UL<<(y));\
P##x##CR &= ~(1UL<<(y));
#define enable_ex() IEN1 |= (1<<2)
#define disable_ex() IEN1 &= ~(1<<2)
uint8 ex_counter = 0;
uint8 ex_high_flag = 0;
volatile uint16 ex_flag = 0;
volatile uint32 ex_sum1 = 0;
volatile uint32 ex_sum2 = 0;
void catch_pwm_init(void)
{
timer1_init();
set_port_in(1,2); //P1.2
EXF0 = ((3<<2)); //high and low
enable_ex();
EA = 1;
}
void ex_ISR(void) interrupt 9
{
//wait for handle
if(!ex_flag)
{
ex_counter++;
//first, start timer
if(1 == ex_counter)
{
timer1_start();
ex_high_flag = P1_2;
}
//second, record sum1
if(2 == ex_counter)
{
t = TH1;
t = (t<<8) + TL1;
ex_sum1 = t + (timer1_overflow&0xff)*0xffff;
}
//third, record sum2
if(3 == ex_counter)
{
timer1_stop();
disable_ex();
t = TH1;
t = (t<<8) + TL1;
ex_sum2 = t + (timer1_overflow&0xff)*0xffff;
//set
ex_flag = 1;
//ready next
ex_counter = 0;
timer1_overflow = 0;
set_timer1_value(0);
enable_ex();
}
}
}
主进程
uint32 period,rate;
catch_pwm_init();
while(1)
{
if(ex_flag)
{
//period(ms)
period = ex_sum2/Fsys_Mhz/1000;
//high/low rate
if(ex_high_flag)
{
rate = ex_sum1*100/ex_sum2;
}
else
{
rate = (ex_sum2-ex_sum1)*100/ex_sum2;
}
//output
printf("%u-%u\r\n",period,rate);
//ready next
set_timer1_value(0);
ex_flag = 0;
enable_ex();
}
}
03 - 软件捕获PWM测试结果
我们取【嵌入式底层知识修炼】软件捕获PWM获取周期和占空比的测试用例:
- 软件模拟
Period
= 10ms,High
= 4ms
- 软件模拟
Period
= 20ms,High
= 14ms
- 软件模拟
Period
= 100ms,High
= 56ms
- 软件模拟
Period
= 500ms,High
= 123ms
- 软件模拟
Period
= 1000ms,High
= 456ms
- 软件模拟
Period
= 2000ms,High
= 1000ms
可以看到,软件捕获PWM
得到的结果与PWM
源存在比较大的差别,极其不稳定
04 - 源码链接
整体Keil工程
百度网盘 提取码:wxyy
05 - 总结
- 软件捕获
PWM
极其不稳定,慎用 - 若要改进,可以获取多次,然后求平均,或者使用算法改进