Printy陈 发表于 2025-8-26 11:19:17

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检查一次
    }
}

Printy陈 发表于 2025-8-26 11:23:09

请大神帮忙分析 PCA为什么不按预期只中断6次停下来,中断次数数据这么大的原因。 这款单片机能不能测频1Hz-600Khz{:4_184:}

国芯人工智能 发表于 2025-8-26 11:23:00

针对您在使用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模式或中断配置不准确;
溢出计数逻辑未充分考虑;
中断处理效率不足;
外部信号稳定性不佳。

建议从上述几个方面逐一排查,并结合实际测试数据进行优化。如需进一步协助,可提供完整代码及测试波形图以便深入分析。

如需进一步技术文档或参考资料,请告知,我将为您整理相关资料。

[本文内容由国芯人工智能辅助生成,仅供参考]

乘风飞扬 发表于 2025-8-26 12:04:21

附件的例子供参考,通过调整PCA时钟源可以修改捕获频率范围
捕获高频脉冲可用系统时钟作为PCA时钟源

DebugLab 发表于 2025-8-26 12:10:32

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
    }
}



梁工 发表于 2025-8-26 12:56:49

Printy陈 发表于 2025-8-26 11:23
请大神帮忙分析 PCA为什么不按预期只中断6次停下来,中断次数数据这么大的原因。 这款单片机能不能测频1Hz- ...

PCA不大可能测频600KHz。
PCA捕获进入中断、退出中断就要各1us,还要处理数据,所以做不到了。
应该是10KHz以内可以使用PCA捕获周期计算频率,超过10KHz就应该用定时器对外计数来测频。

Printy陈 发表于 2025-8-26 13:36:52

之前我用是P3.4 脚接外部脉冲的,就是看网上贴子说PCA可以测频率更准,就改成了P3.5 CCP0_2 引脚 PCA。哎。外部定时器T0确定可以测1-600K吗,我要换P3.4重新弄{:weiqu:}

Printy陈 发表于 2025-8-26 13:41:23

乘风飞扬 发表于 2025-8-26 12:04
附件的例子供参考,通过调整PCA时钟源可以修改捕获频率范围
捕获高频脉冲可用系统时钟作为PCA时钟源



用的是35M1T 测频率,但只能调200Khz以内,麻烦帮我看看可以测到600K吗,不可以,改用定时器外部计数是否可以

乘风飞扬 发表于 2025-8-26 14:20:19

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
(出处: 国芯人工智能技术交流网站)

Printy陈 发表于 2025-8-26 15:06:33

乘风飞扬 发表于 2025-8-26 14:20
按照例子里面的计算公式:捕获脉宽时间=捕捉的时钟数/PCA时钟源
捕获脉冲频率=PCA时钟源/捕捉的时钟数
用 ...

58按理说STC15W408AS应该可以做到吧
页: [1] 2
查看完整版本: 15W408AS PCA捕获测频率,只能600Hz-180KHz,指导