15W408AS PCA捕获测频率,只能600Hz-180KHz,指导
总思路是:PCA中断后记录捕获数据6次,包括中断PCA计数组(6位)、PCA溢出数组(6位),然后关断PCA捕获,将数据处理计算出频率。现在可以在600Hz-180Khz,低频问题,应该是溢出没有处理好,但高频是怎么回事,头大,外部晶振已经是35Mhz,我用串口调试助手打印,发现PCA停止不了,中断次数不是0-5,而是很大。//此版本可以基本测量频率,35MHz时钟 1T 最高可测到190Khz,再高频率就不对了,以下是源代码,理论来讲35Mhz,怎么也得捕获M级的频率了。
measurement_active = 0;// 暂停捕获 我用CR=0,CMOD &= ~(1 << 0);都试过PCA关断不了,index 数据很大。
串口信息如下Capture index: 1536 Capture index: 256 Capture index: 512 Capture index: 768 Capture index: 1024 Capture index: 1280 Capture index: 1536 Capture index: 256 Capture index: 512 Capture index: 768 Capture index: 1024 Capture index: 1280 Capture index: 1536 Capture index: 256 Capture index: 512 Capture index: 768 Capture index: 1024 Capture index: 1280 Capture index: 1536 Capture index: 256
#include <STC15.h>
#include <intrins.h>
#include <stdio.h>
// 引脚定义
sbit RS = P3^2;
sbit RW = P3^3;
sbit EN = P3^4;
sbit LED_IND = P3^7;
#define DATA_PORT P1
// 全局变量 - 优化内存使用
volatile unsigned long pca_overflow_count = 0;
volatile bit new_capture = 0;
volatile unsigned char capture_index = 0; // 当前捕获索引
volatile bit capture_complete = 0; // 6次捕获完成标志
volatile bit measurement_active = 1; // 测量激活标志
// 6次捕获数据存储 - 移到xdata段节省DATA空间
volatile unsigned int xdata capture_values; // 6次捕获值
volatile unsigned long xdata overflow_counts; // 6次溢出计数
// 捕获处理变量
volatile unsigned long last_capture_value = 0;
volatile unsigned long overflow_at_last_capture = 0;
volatile bit first_capture = 1;
unsigned char display_buffer;// 优化:刚好8字符,节省1字节
unsigned char i;
// 串口初始化 - 使用定时器2作为波特率发生器
void uart_init() {
// 设置串口引脚
P3M0 &= ~(1 << 0);// P3.0 输入
P3M1 &= ~(1 << 0);
P3M0 |=(1 << 1);// P3.1 推挽输出
P3M1 &= ~(1 << 1);
SCON = 0x50; //8位数据,可变波特率
AUXR |= 0x01; //串口1选择定时器2为波特率发生器
AUXR |= 0x04; //定时器时钟1T模式
T2L = 0xB4; //设置定时初始值
T2H = 0xFF; //设置定时初始值
AUXR |= 0x10; //定时器2开始计时
// ET1 = 0; //禁止定时器中断
//TR1 = 1; //定时器1开始计时
// ES = 1; // 使能串口中断
// EA = 1; // 使能总中断
}
// printf重定向到串口
char putchar(char c) {
SBUF = c;
while (!TI);
TI = 0;
return c;
}
// 打印PCA捕获值
void print_pca_values() {
unsigned char i;
printf("PCA Values:\r\n");
for (i = 0; i < 6; i++) {
printf("Capture[%d]: %u Overflow: %lu\r\n",
i, capture_values, overflow_counts);
}
printf("----------------\r\n");
}
// 延时函数
void delay_us(unsigned int us) {
while (us--) {
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
}
}
void delay_ms(unsigned int ms) {
unsigned int i, j;
for (i = 0; i < ms; i++)
for (j = 0; j < 120; j++)// 优化:从240减少到120,节省代码空间
delay_us(1);
}
// LCD函数
void lcd_busy() {
delay_us(50);
}
void lcd_write_cmd(unsigned char cmd) {
lcd_busy();
RS = 0;
RW = 0;
DATA_PORT = cmd;
EN = 1;
_nop_(); _nop_();
EN = 0;
delay_us(50);
}
void lcd_write_data(unsigned char dat) {
lcd_busy();
RS = 1;
RW = 0;
DATA_PORT = dat;
EN = 1;
_nop_(); _nop_();
EN = 0;
delay_us(50);
}
void lcd_init() {
delay_ms(15);
lcd_write_cmd(0x30);
delay_ms(5);
lcd_write_cmd(0x30);
delay_us(100);
lcd_write_cmd(0x30);
delay_us(100);
lcd_write_cmd(0x38);
lcd_write_cmd(0x0C);
lcd_write_cmd(0x01);
lcd_write_cmd(0x06);
delay_ms(2);
}
// 显示8字符字符串
void lcd_show_line(unsigned char line, unsigned char *str) {
if (line == 0) {
lcd_write_cmd(0x80);
} else {
lcd_write_cmd(0xC0);
}
for(i = 0; i < 8 && str != '\0'; i++) {
lcd_write_data(str);
}
for(; i < 8; i++) {
lcd_write_data(' ');
}
}
// IO初始化
void io_init() {
P1M0 = 0x00;
P1M1 = 0x00;
P1 = 0xFF;
P3M0 = 0x00;
P3M1 = 0x00;
P3 = 0xFF;
P3M1 |= 0x20;
P3M0 |= 0x80;
LED_IND = 0;
}
// PCA初始化
void pca_init() {
CR = 0;
P_SW1 &= ~(1 << 5);
P_SW1 |=(1 << 4);
CMOD = 0x09;
CCAPM0 = 0x21;
CL = 0;
CH = 0;
CCAP0L = 0;
CCAP0H = 0;
CCON = 0x00;
// 初始化捕获相关变量
pca_overflow_count = 0;
capture_index = 0;
capture_complete = 0;
measurement_active = 1;
CR = 1;
}
// PCA中断服务函数 - 只记录数据,不做计算
void pca_isr() interrupt 7 {
if (CF) {
CF = 0;
pca_overflow_count++;
}
if (CCF0 && measurement_active) {
CCF0 = 0;
// 直接存储捕获值和溢出值
capture_values = (CCAP0H << 8) | CCAP0L;
overflow_counts = pca_overflow_count;
capture_index++;
// 检查是否完成6次捕获
if (capture_index >= 6 && !capture_complete) {
capture_complete = 1;
measurement_active = 0;// 暂停捕获
new_capture = 1; // 通知主循环处理
}
}
}
// 直接显示频率值
void display_frequency() {
if (capture_complete) {
// 计算频率:使用相邻捕获值计算周期
unsigned long period;
float freq;
unsigned char i;
// 计算相邻捕获值之间的周期(取平均值)
period = 0;
for ( i = 1; i < 6; i++) {
if (capture_values >= capture_values) {
period += (capture_values - capture_values);
} else {
period += ((65536UL - capture_values) + capture_values);
}
}
period = period / 5;// 取平均值
// 计算频率
if (period > 0) {
freq = 35000000.0 / period;// 35MHz时钟
// 第一行显示频率值
if (freq < 1000) {
sprintf(display_buffer, "%7.1f ", freq);
} else if (freq < 1000000) {
sprintf(display_buffer, "%7.3f ", freq/1000);
} else {
sprintf(display_buffer, "%7.3f ", freq/1000000);
}
lcd_show_line(0, display_buffer);
// 第二行显示单位
if (freq < 1000) {
lcd_show_line(1, "Hz ");
} else if (freq < 1000000) {
lcd_show_line(1, "kHz ");
} else {
lcd_show_line(1, "MHz ");
}
} else {
lcd_show_line(0, "Error ");
lcd_show_line(1, "Calc ");
}
// 重置捕获状态,准备下一轮测量
capture_complete = 0;
capture_index = 0;
first_capture = 1;
measurement_active = 1;
}
}
// 主函数
void main() {
io_init();
lcd_init();
uart_init();
pca_init();
EA = 1;
lcd_show_line(0, "Freq Meter");
delay_ms(1000);
while(1) {
if (new_capture) {
// 有新的捕获数据,显示一次
new_capture = 0;
display_frequency();
print_pca_values();
} else {
// 等待捕获完成,显示等待状态
if (!capture_complete) {
lcd_show_line(0, "Waiting");
lcd_show_line(1, "Signal");
}
}
delay_ms(100);// 100ms检查一次
}
}
请大神帮忙分析 PCA为什么不按预期只中断6次停下来,中断次数数据这么大的原因。 这款单片机能不能测频1Hz-600Khz{:4_184:}
针对您在使用STC15W408AS的PCA模块进行频率捕获时遇到的问题,以下为专业分析与解决方案:
一、问题概述
您在使用PCA模块进行频率测量时,发现:
低频段(600Hz~180kHz):可正常工作,但存在溢出处理不准确的情况;
高频段(超过180kHz):出现中断次数异常(数值过大),且无法停止PCA捕获;
外部晶振为35MHz,理论上应支持更高频率的测量。
当前代码中通过记录6次PCA中断和溢出事件来计算频率,但实际运行中出现异常现象,需进一步排查原因。
二、可能原因分析
1. PCA模式配置错误
PCA(可编程计数器阵列)在不同模式下对输入信号的响应方式不同。若未正确设置PCA模式(如CCAPM0/CCAPM1寄存器),可能导致捕获行为异常。
建议检查:
CCAPM0 和 CCAPM1 的配置是否正确;
是否启用了捕获模式(CCAPMx 中的 CAPN 和 CAPM 位);
是否设置了正确的捕获边沿(上升沿或下降沿)。
2. 中断优先级或使能设置不当
如果PCA中断未被正确关闭或优先级设置冲突,可能导致中断频繁触发,导致数据异常。
建议检查:
EA(全局中断使能)是否开启;
PCAP 和 PCAIE 是否启用;
中断服务函数中是否正确清除了中断标志位(PCAIF)。
3. 溢出计数逻辑不完善
在记录6次捕获数据后,若未正确处理PCA计数器溢出,可能导致计算结果偏差。
建议改进:
在每次捕获前读取并保存当前的PCA计数值;
对溢出次数进行累加,并在计算频率时考虑溢出影响;
确保在捕获结束后及时关闭PCA模块(CMOD &= ~0x01)。
4. 高频下捕获速度不足
虽然系统时钟为35MHz,但在1T模式下,PCA的捕获精度和速度可能受限于硬件资源分配。
建议优化:
考虑降低系统时钟频率以提高稳定性;
或采用更高效的中断处理机制,避免因中断延迟导致的数据丢失。
5. 外部信号干扰或输入波形不稳定
高频信号可能受到外部噪声干扰,导致捕获误判。
建议检查:
输入信号是否稳定;
使用示波器观察输入波形,确认无毛刺或畸变;
可尝试增加滤波电路或软件去抖处理。
三、建议的改进方案
1. 完善PCA初始化流程
确保PCA模块初始化正确,包括:
c
// 初始化PCA
CMOD = 0x00; // 设置为1T模式
CCAPM0 = 0x0A; // 启用捕获模式(上升沿捕获)
CCAPM1 = 0x00; // 关闭CCAP1
PCAIF = 0; // 清除中断标志
PCAIE = 1; // 使能PCA中断
EA = 1; // 全局中断使能
2. 增强中断处理逻辑
在中断服务函数中,确保正确记录捕获值和溢出次数:
c
void PCAISR(void) interrupt 23 {
static uint8t count = 0;
static uint16t capturevalues;
static uint16t overflowcounts;
if (count < 6) {
capturevalues = CCAP0L;// 读取捕获值
overflowcounts = PCAOV;// 读取溢出次数
count++;
} else {
// 数据采集完成,关闭PCA
CMOD &= ~0x01;
measurementactive = 0;
}
PCAIF = 0;// 清除中断标志
}
3. 优化频率计算算法
根据捕获值和溢出次数计算频率:
c
float calculatefrequency(uint16t capture, uint16t overflow, uint8t count) {
uint32t totaltime = 0;
for (uint8t i = 0; i < count; i++) {
totaltime += (uint32t)(overflow65536 + capture);
}
return (float)(35000000 / (totaltime / count));
}
4. 添加调试输出
通过串口打印关键变量,帮助定位问题:
c
printf("Capture: %d, Overflow: %d\n", capturevalues, overflowcounts);
四、总结
您的设计思路基本合理,但在高频场景下,可能存在以下问题:
PCA模式或中断配置不准确;
溢出计数逻辑未充分考虑;
中断处理效率不足;
外部信号稳定性不佳。
建议从上述几个方面逐一排查,并结合实际测试数据进行优化。如需进一步协助,可提供完整代码及测试波形图以便深入分析。
如需进一步技术文档或参考资料,请告知,我将为您整理相关资料。
[本文内容由国芯人工智能辅助生成,仅供参考] 附件的例子供参考,通过调整PCA时钟源可以修改捕获频率范围
捕获高频脉冲可用系统时钟作为PCA时钟源
15系列16位PCA捕获测量脉宽
//本示例在Keil开发环境下请选择Intel的8058芯片型号进行编译
//若无特别说明,工作频率一般为11.0592MHz
#include "reg51.h"
#include "intrins.h"
#define FOSC 11059200L
typedef unsigned char BYTE;
typedef unsigned int WORD;
typedef unsigned long DWORD;
sfr P0M1 = 0x93;
sfr P0M0 = 0x94;
sfr P1M1 = 0x91;
sfr P1M0 = 0x92;
sfr P2M1 = 0x95;
sfr P2M0 = 0x96;
sfr P3M1 = 0xb1;
sfr P3M0 = 0xb2;
sfr P4M1 = 0xb3;
sfr P4M0 = 0xb4;
sfr P5M1 = 0xC9;
sfr P5M0 = 0xCA;
sfr P6M1 = 0xCB;
sfr P6M0 = 0xCC;
sfr P7M1 = 0xE1;
sfr P7M0 = 0xE2;
sfr P_SW1 = 0xA2; //外设功能切换寄存器1
#define CCP_S0 0x10 //P_SW1.4
#define CCP_S1 0x20 //P_SW1.5
sfr CCON = 0xD8; //PCA控制寄存器
sbit CCF0 = CCON^0; //PCA模块0中断标志
sbit CCF1 = CCON^1; //PCA模块1中断标志
sbit CR = CCON^6; //PCA定时器运行控制位
sbit CF = CCON^7; //PCA定时器溢出标志
sfr CMOD = 0xD9; //PCA模式寄存器
sfr CL = 0xE9; //PCA定时器低字节
sfr CH = 0xF9; //PCA定时器高字节
sfr CCAPM0 = 0xDA; //PCA模块0模式寄存器
sfr CCAP0L = 0xEA; //PCA模块0捕获寄存器 LOW
sfr CCAP0H = 0xFA; //PCA模块0捕获寄存器 HIGH
sfr CCAPM1 = 0xDB; //PCA模块1模式寄存器
sfr CCAP1L = 0xEB; //PCA模块1捕获寄存器 LOW
sfr CCAP1H = 0xFB; //PCA模块1捕获寄存器 HIGH
sfr CCAPM2 = 0xDC; //PCA模块2模式寄存器
sfr CCAP2L = 0xEC; //PCA模块2捕获寄存器 LOW
sfr CCAP2H = 0xFC; //PCA模块2捕获寄存器 HIGH
sfr PCA_PWM0 = 0xf2; //PCA模块0的PWM寄存器
sfr PCA_PWM1 = 0xf3; //PCA模块1的PWM寄存器
sfr PCA_PWM2 = 0xf4; //PCA模块2的PWM寄存器
BYTE cnt; //存储PCA计时溢出次数
DWORD count0; //记录上一次的捕获值
DWORD count1; //记录本次的捕获值
DWORD length; //存储信号的时间长度(count1 - count0)
void main()
{
P0M0 = 0x00;
P0M1 = 0x00;
P1M0 = 0x00;
P1M1 = 0x00;
P2M0 = 0x00;
P2M1 = 0x00;
P3M0 = 0x00;
P3M1 = 0x00;
P4M0 = 0x00;
P4M1 = 0x00;
P5M0 = 0x00;
P5M1 = 0x00;
P6M0 = 0x00;
P6M1 = 0x00;
P7M0 = 0x00;
P7M1 = 0x00;
ACC = P_SW1;
ACC &= ~(CCP_S0 | CCP_S1); //CCP_S0=0 CCP_S1=0
P_SW1 = ACC; //(P1.2/ECI, P1.1/CCP0, P1.0/CCP1, P3.7/CCP2)
//ACC = P_SW1;
//ACC &= ~(CCP_S0 | CCP_S1); //CCP_S0=1 CCP_S1=0
//ACC |= CCP_S0; //(P3.4/ECI_2, P3.5/CCP0_2, P3.6/CCP1_2, P3.7/CCP2_2)
//P_SW1 = ACC;
//
//ACC = P_SW1;
//ACC &= ~(CCP_S0 | CCP_S1); //CCP_S0=0 CCP_S1=1
//ACC |= CCP_S1; //(P2.4/ECI_3, P2.5/CCP0_3, P2.6/CCP1_3, P2.7/CCP2_3)
//P_SW1 = ACC;
CCON = 0; //初始化PCA控制寄存器
//PCA定时器停止
//清除CF标志
//清除模块中断标志
CL = 0; //复位PCA寄存器
CH = 0;
CCAP0L = 0;
CCAP0H = 0;
CMOD = 0x09; //设置PCA时钟源为系统时钟,且使能PCA计时溢出中断
CCAPM0 = 0x21; //PCA模块0为16位捕获模式(上升沿捕获,可测从高电平开始的整个周期),且产生捕获中断
//CCAPM0 = 0x11; //PCA模块0为16位捕获模式(下降沿捕获,可测从低电平开始的整个周期),且产生捕获中断
//CCAPM0 = 0x31; //PCA模块0为16位捕获模式(上升沿/下降沿捕获,可测高电平或者低电平宽度),且产生捕获中断
CR = 1; //PCA定时器开始工作
EA = 1;
cnt = 0;
count0 = 0;
count1 = 0;
while (1);
}
void PCA_isr() interrupt 7
{
if (CCF0)
{
CCF0 = 0;
if (CF && ((CCAP0H & 0x80) == 0))
{
CF = 0;
cnt++;
}
count0 = count1; //备份上一次的捕获值
((BYTE *)&count1) = CCAP0L;//保存本次的捕获值
((BYTE *)&count1) = CCAP0H;
((BYTE *)&count1) = cnt;
((BYTE *)&count1) = 0;
length = count1 - count0; //计算两次捕获的差值,即得到时间长度
((BYTE *)&length) = 0;
}
if (CF)
{
CF = 0;
cnt++; //PCA计时溢出次数+1
}
}
Printy陈 发表于 2025-8-26 11:23
请大神帮忙分析 PCA为什么不按预期只中断6次停下来,中断次数数据这么大的原因。 这款单片机能不能测频1Hz- ...
PCA不大可能测频600KHz。
PCA捕获进入中断、退出中断就要各1us,还要处理数据,所以做不到了。
应该是10KHz以内可以使用PCA捕获周期计算频率,超过10KHz就应该用定时器对外计数来测频。 之前我用是P3.4 脚接外部脉冲的,就是看网上贴子说PCA可以测频率更准,就改成了P3.5 CCP0_2 引脚 PCA。哎。外部定时器T0确定可以测1-600K吗,我要换P3.4重新弄{:weiqu:} 乘风飞扬 发表于 2025-8-26 12:04
附件的例子供参考,通过调整PCA时钟源可以修改捕获频率范围
捕获高频脉冲可用系统时钟作为PCA时钟源
用的是35M1T 测频率,但只能调200Khz以内,麻烦帮我看看可以测到600K吗,不可以,改用定时器外部计数是否可以
Printy陈 发表于 2025-8-26 13:41
用的是35M1T 测频率,但只能调200Khz以内,麻烦帮我看看可以测到600K吗,不可以,改用定时器外部计数 ...
按照例子里面的计算公式:捕获脉宽时间=捕捉的时钟数/PCA时钟源
捕获脉冲频率=PCA时钟源/捕捉的时钟数
用35M时钟源捕获600K脉冲,捕捉的时钟数=58左右
高频脉冲捕获最好使用带PLL高速时钟的PWM,可参考下面帖子:
如何测量72MHz以下的外部信号,STC32的144MHz的高速PWM
https://www.stcaimcu.com/thread-1360-1-1.html
(出处: 国芯人工智能技术交流网站)
乘风飞扬 发表于 2025-8-26 14:20
按照例子里面的计算公式:捕获脉宽时间=捕捉的时钟数/PCA时钟源
捕获脉冲频率=PCA时钟源/捕捉的时钟数
用 ...
58按理说STC15W408AS应该可以做到吧
页:
[1]
2