折腾了几天,因为头文件的问题,加上各种调试,终于调通了使用PCA捕捉外部脉冲宽度的处理。以下是程序:
// 捕获外部脉冲宽度
// 工作主频 20MHz, PCA的计数周期=1/20微妙
// 测试时,需要短接P20 - P13,使定时器2输出的方波信号输出给PCA,作为PCA外部信号的输入信号
///*
// PCA 模块工作于捕获模式时,对模块的外部 CCP0/CCP1/CCP2 管脚的输入跳变进行采样。
// 当采样到有效跳变时,PCA 控制器立即将 PCA 计数器 CH 和 CL 中的计数值装载到模块的捕获寄存器中 CCAPnL 和 CCAPnH,
// 同时将 CCON 寄存器中相应的 CCFn 置 1。若 CCAPMn 中的 ECCFn 位被设置为 1,将产生中断。
// 由于所有 PCA 模块的中断入口地址是共享的,所以在中断服务程序中需要判断是哪一个模块产生了中断,并注意中断标志位需要软件清零。
// */
#include "../comm/Ai8051U.h"
#include "intrins.h"
#include "stdio.h"
void Timer0_init(void);
#define FOSC 20000000UL //定义主时钟
#define BRT (65536 - (FOSC / 115200) / 4) //加 2 操作是为了让 Keil 编译器,自动实现四舍五入运算
#define Timer0_Reload (FOSC / 2000) //Timer 0 中断频率, 2000次/秒,周期0.5微妙,IO翻转后形成1000Hz脉冲波
#define CF 0x80
#define CR 0x40
#define CCF0 0x01
unsigned char cnt; // 存储 PCA 计时溢出次数
unsigned long count0; // 记录上一次的捕获值
unsigned long count1; // 记录本次的捕获值
unsigned long len1, len0; // 存储信号的时间长度
unsigned long rec[100]; // 存储100组记录
unsigned int index=0;
unsigned long result = 0;
// 测试用输出引脚
// P2.0链接P1.3,P2.0输出的脉冲信号,由可编程计数器阵列PCA0由P1.3引脚捕获,作为对比,由P2.1输出
sbit TEST_OUT = P2^0;
sbit PCA0_OUT = P2^1;
bit busy1;
char wptr1;
char rptr1;
char buffer1[64];
char flag = 0;
// timer0初始化函数.
void Timer0_init(void) {
TR0 = 0; //停止计数
ET0 = 1; //允许中断
TMOD &= ~0x03; // 16位自动重装模式
TMOD &= ~T0_CT; // 定时器模式
INTCLKO &= ~T0CLKO; //不输出时钟
AUXR |= T0x12; //1T mode
TH0 = (unsigned char)((65536UL - Timer0_Reload) / 256);
TL0 = (unsigned char)((65536UL - Timer0_Reload) % 256);
TR0 = 1; //开始运行
}
//========================================================================
// 函数: void timer0_int (void) interrupt TIMER0_VECTOR
// 描述: timer0中断函数.
//========================================================================
void timer0_int (void) interrupt 1 {
// P20电平翻转
P20 = ~P20;
}
// 测试使用Timer2作为串口的波特率发生器
void Timer2Init(void) {
T2L = BRT;
T2H = BRT >> 8;
//S1BRT = 1; // S1BRT:串口1的波特率发生器选择位: 0-选择定时器1作为波特率发生器;1-选择定时器2作为波特率发生器(默认)
//T2x12 = 1;
//T2R = 1;
AUXR |= S1BRT | T2x12 | T2R;
}
// 串口P3.6(RXD),P3.7(TXD)
////---------------------------------------------------------------
//void Uart1Isr() interrupt 4 {
// if (TI) {
// TI = 0;
// busy1 = 0;
// }
// if (RI) {
// RI = 0;
// buffer1[wptr1++] = SBUF;
// wptr1 &= 0x0f;
// }
//}
void Uart1Init() {
SCON = 0x50; //
wptr1 = 0x00;
rptr1 = 0x00;
busy1 = 0;
P_SW1 |= 0x40; // 选择P3.6,P3.7,因为擎天柱开发板上不提供P3.0,P3.1引脚
}
void Uart1Send(char dat) {
//while (busy1);
// 送出要发送的数据
SBUF = dat;
// 等待串口发送完成
while (TI==0);
TI=0; // 清除标志
//busy1 = 1;
// // 送出要发送的数据
// SBUF = dat;
}
void Uart1SendStr(char *p) {
while (*p) {
Uart1Send(*p++);
}
}
// 可编程计数器模块中断
void PCA_Isr() interrupt 7 {
char buf[64]={'\0'};
// 判断是否发生了溢出(无论是否检测到脉冲沿时,发生了溢出中断)
if (CCON & CF) {
CCON &= ~CF; // 清除中断标志
cnt++; // PCA 计时溢出次数 +1
}
// 是否发生捕获中断,指检测到脉冲沿了
if (CCON & CCF0) {
// 输出测试引脚电平翻转
PCA0_OUT = ~PCA0_OUT;
// 软件清除中断标志
CCON &= ~CCF0;
// 保存当前计数
len0=len1;
len1 = ((cnt*256 + CCAP0H) * 256 + CCAP0L);
if (flag == 0) {
if (index<100) {
//Uart1Send('>');
rec[index++] = len1-len0;
} else {
//Uart1Send('/');
flag = 1;
}
}
//sprintf(buf, "(%d,%d,%d) %ld - %ld = %ld \r\n", cnt, CCAP0H, CCAP0L, len1, len0, len1-len0);
//Uart1SendStr(buf);
}
}
void main(void) {
int i = 0;
char buf[64]={'\0'};
//EAXFR = 1; // 允许访问扩展的特殊寄存器, XFR
P_SW2 |= EAXFR;
//(32 位模式请使用这句,注释下一句 )
// P_SW2 |= 0x80; // 允许访问扩展的特殊寄存器, XFR
//(8 位模式请使用这句,注释上一句 )
WTST = 0; // 设置取程序代码等待时间,
// 赋值为 0 表示不等待,程序以最快速度运行
CKCON = 0; // 设置访问片内的 xdata 速度,
// 赋值为 0 表示用最快速度访问,不增加额外的等待时间
P0M0 = 0x00; P0M1 = 0x00; P1M0 = 0x00; P1M1 = 0x00;
P2M0 = 0x00; P2M1 = 0x00; P3M0 = 0x00; P3M1 = 0x00;
P4M0 = 0x00; P4M1 = 0x00; P5M0 = 0x00; P5M1 = 0x00;
// 初始化定时器0,定时周期1000Hz
Timer0_init();
// 初始化定时器2,给串口1做波特率发生器用
Timer2Init();
// 初始化串口1
Uart1Init();
// 用户变量初始化
cnt = 0;
count0 = 0;
count1 = 0;
len1 = 0; len0=0;
Uart1SendStr("Start main ......\r\n");
CCON = 0x00;
// CMOD设置:
// B7 = 0 :空闲模式下 PCA 继续计数
// B6 - B5(CCP_S) = 00 :ECI-P1.2 、 CCP0 - P1.3、 CCP1 - P1.4、CCP2 - P1.1
// B4 - B1(CPS) = 0100 :PCA 时钟为系统时钟
// B0= 1 :使能 PCA 计数器溢出中断
CMOD = 0x09; // PCA 时钟为系统时钟 , 使能 PCA 计数器溢出中断
// 计数器清零
CL = 0x00;
CH = 0x00;
// B6(ECOM0) = 0 :禁止PCA0的比较功能
// B5(CCAPP0) = 0 :禁止PCA0上升沿捕捉
// B4(CCAPN0) = 1 :允许PCA0下降沿捕捉
// B3(MAT0) = 0 :禁止PCA0匹配
// B2(TOG0) = 0 :禁止PCA0高速脉冲输出
// B1(PWM0) = 0 :禁止PCA0输出PWM
// B0(EXXF0) = 1 :允许PCA0匹配/捕获中断
CCAPM0 = 0x11; // 16 位下降沿捕获
//CCAPM0 = 0x21; // 16 位上升沿捕获)
//CCAPM0 = 0x31; // 16 位边沿捕获捕获模式(上升沿、下降沿均捕获)
CCAP0L = 0x00;
CCAP0H = 0x00;
CCON |= CR; // 启动 PCA 计时器
// // 允许串口中断
// ES = 1;
EA = 1;
while (1) {
if (flag == 1) {
//Uart1Send('=');
for (i=0; i<100; i++) {
sprintf(buf, "\r\n[%d] = %ld", i, rec[i]);
Uart1SendStr(buf);
}
index = 0;
flag = 0;
}
}
}
最初还想着使用串口中断方式输出调试信息,结果发现中断之间的影响导致测试信号异常。于是不用串口中断,使用UartSend函数输出串口信息,然后发现在中断处理函数PCA_Isr()中使用UartSend函数会使定时器0输出的方波信号失真,正常占空比为50%的方波变成下面的样子(黄色的信号是定时器0产生的测量用信号,给PCA输入用的;粉色的是PCA中断产生的信号,也就是捕捉下降沿时产生的测试信号。粉色信号是正常的):

同时获得的测量数据(就是相邻换色信号下降沿之间的PCA计数值):

数据偏差还是挺大的。这应该是终端中使用了while导致的。于是改为在PCA的中断中收集数据,在主程序的while中输出,才获得正常信号。

获取到的PCA计数值:

PCA计数值总体来说偏差不大。但是还有一个比较奇怪的地方,就是46、47组数据,严重偏离。开始以为是随机出现的。后来通过不断复位程序测量发现,这是个固定规律的结果,连数据值都完全一样。这下就不知道是什么原因了。不过这不影响对PCA测量外部脉冲宽度的影响。更何况PCA的输入信号是单片机自身的TIMER0产生的。以后再试试完全用外部信号实验。
我要赚赏金
