基于蓝桥杯 STC15F2K60S2 单片机 PCA(可编程计数器阵列)学习指南

PCA(可编程计数器阵列)学习指南

适合嵌入式小白的完整学习指南
基于蓝桥杯 STC15F2K60S2 单片机
版本:V1.0 | 日期:2026-03-14


:open_book: 目录

  1. PCA 是什么
  2. 硬件结构
  3. 关键寄存器
  4. 四种工作模式
  5. 实战:超声波测距
  6. 代码详解
  7. 常见问题

1. PCA 是什么?

1.1 PCA 概述

PCA(Programmable Counter Array,可编程计数器阵列) 是 STC15 系列单片机内置的高级定时外设:

  • :white_check_mark: 一个 16 位自由运行计数器(CH:CL),精度极高
  • :white_check_mark: 三个独立 CCP 模块(CCP0/CCP1/CCP2),每个可独立配置
  • :white_check_mark: 每个 CCP 模块支持四种模式:捕获、软件定时器、高速脉冲输出、PWM
  • :white_check_mark: 时钟源可选:Fosc/12、Fosc/2、Timer0 溢出、外部 ECI 引脚

1.2 蓝桥杯中的 PCA

在蓝桥杯大模板中,PCA 主要用于超声波测距

功能 使用的 PCA 部分
超声波计时 CH:CL 16位计数器 + CR 控制 + CF 溢出标志
CCP 模块 完全不需要配置(三个模块保持默认值即可)

一句话理解:PCA 就是一个 16 位的"秒表",可以精确计时到 1μs(12MHz 晶振下),用来测量超声波从发射到接收的时间差,再换算成距离。


2. 硬件结构

2.1 时钟源选择

通过 CMOD 寄存器的 CPS1:CPS0 位选择:

CPS1 CPS0 时钟源 频率(12MHz晶振)
0 0 Fosc/12 ★ 1 MHz(每 tick = 1μs)
0 1 Fosc/2 6 MHz
1 0 Timer0 溢出 由 Timer0 决定
1 1 ECI(P1.2) 外部信号

超声波测距使用 Fosc/12,1μs 精度正好对应 1cm/58μs 的声速特性。

2.2 引脚分配

模块 引脚 备注
CCP0 P1.1 超声波 TX 发射
CCP1 P1.0 超声波 RX 接收
CCP2 P3.7 / P1.2 预留
ECI P1.2 外部时钟输入(少用)

3. 关键寄存器

3.1 CMOD(PCA 工作模式寄存器)

地址:D9H,可位寻址

名称 功能
7 CIDL 1=空闲模式停止计数,0=继续计数
6 WDTE 看门狗定时器使能(通常为 0)
5-4 保留
3 CPS1 时钟源选择高位
2 CPS0 时钟源选择低位
1 保留
0 ECF 1=允许 CF 溢出中断

超声波用法CMOD = 0x00(Fosc/12,不开溢出中断)

3.2 CCON(PCA 控制寄存器)

地址:D8H,可位寻址

名称 功能
7 CF 计数器溢出标志,软件清零
6 CR 1=启动计数,0=停止计数
5-3 保留
2 CCF2 CCP2 中断标志,软件清零
1 CCF1 CCP1 中断标志,软件清零
0 CCF0 CCP0 中断标志,软件清零

3.3 CCAPMn(CCP 模块控制寄存器)

每个 CCP 模块有一个对应的 CCAPMn 寄存器,控制该模块的工作模式:

// 位定义(从高到低):— ECOM CAPP CAPN MAT TOG PWM ECCPn
CCAPM0 = 0x00;  // CCP0 不使用(超声波场景下保持默认)
CCAPM1 = 0x00;  // CCP1 不使用
CCAPM2 = 0x00;  // CCP2 不使用

4. 四种工作模式

4.1 模式对比

模式 关键位 典型用途
捕获(Capture) CAPP/CAPN=1 测脉冲宽度、频率
软件定时器 ECOM=MAT=1 多路软件定时
高速脉冲输出 ECOM=MAT=TOG=1 产生方波
PWM 输出 ECOM=PWM=1 电机调速、LED 调光

4.2 PWM 输出详解

// 设置 CCP0 为 8位 PWM,占空比 50%
CMOD = 0x00;     // Fosc/12
CCAPM0 = 0x42;   // ECOM=1, PWM=1
CCAP0L = 128;    // 占空比 = (256-128)/256 = 50%
CCAP0H = 128;    // 与 L 相同
CR = 1;          // 启动计数

:warning: 蓝桥杯模板中 PWM 调光是用 Timer1 中断手动实现的(软件 PWM),并非使用 PCA 硬件 PWM,这样更容易控制、不受 PCA 限制。


5. 实战:超声波测距

5.1 测距原理

  1. 单片机向 TX(P1.0) 发送 8 个 40kHz 方波脉冲
  2. 超声波模块发出声波
  3. 声波碰到障碍物后反射
  4. 模块收到回波,RX(P1.1) 输出高电平
  5. 用 PCA 计时:RX 高电平持续时间 = 声波往返时间 T(μs)
  6. 距离 = T × 声速 / 2 = T × 340/2/10000 cm = T × 0.017 cm

5.2 为什么用 PCA 而不用 Timer?

比较项 Timer1 PCA CH:CL
位数 16 位 16 位
是否被占用 ✗ 已做系统心跳(uwTick) ✓ 空闲可用
精度 1μs(Fosc/12) 1μs(Fosc/12)
灵活性 有中断打断风险 可查询 CF 超时

结论:Timer1 已被系统调度占用,PCA 是最自然的替代选择。


6. 代码详解

6.1 超声波驱动(ultrasound.c)

#include "ultrasound.h"

sbit US_TX = P1^0;   // 发射引脚
sbit US_RX = P1^1;   // 接收引脚

// 发送一个 40kHz 周期(12.5μs 高 + 12.5μs 低)
void Ut_Send_Wave()
{
    unsigned char i;
    for (i = 0; i < 8; i++)       // 发 8 个脉冲
    {
        US_TX = 1;
        Delay12us();               // 高电平 12μs(40kHz 半周期)
        US_TX = 0;
        Delay12us();               // 低电平 12μs
    }
}

6.2 完整测距函数

float Ut_Wave_Data()
{
    unsigned int time;

    // ① 配置 PCA
    CMOD = 0x00;       // Fosc/12 = 1MHz,不开溢出中断
    CH = 0; CL = 0;   // 清零计数器
    CCON = 0x00;       // CF=0, CR=0

    // ② 发送超声波脉冲
    Ut_Send_Wave();

    // ③ 等待回波到来(RX 变高)
    while (US_RX == 0);

    // ④ 启动 PCA 计时
    CR = 1;

    // ⑤ 等待回波结束,同时检查是否超时
    while (US_RX == 1 && CF == 0);

    // ⑥ 停止计时
    CR = 0;

    // ⑦ 判断是否超时
    if (CF == 1)
    {
        CF = 0;
        return 0;           // 超时,超出量程
    }

    // ⑧ 读取计时值
    time = CH * 256 + CL;  // 单位:μs

    // ⑨ 换算距离
    return time * 0.017f;  // 单位:cm
}

6.3 逐步分析

步骤 代码 说明
CMOD = 0x00 时钟 = Fosc/12 = 1MHz
CH=CL=0; CCON=0x00 清零计数器和标志
Ut_Send_Wave() 发 8×40kHz 脉冲
while(US_RX==0) 等回波高电平
CR = 1 启动 PCA 计数
while(US_RX==1 && CF==0) 等回波结束或超时
CR = 0 停止计数
if(CF==1) return 0 CF=1 表示计数溢出,超出量程
time = CH*256+CL 读计数值(单位 μs)
return time * 0.017f 距离 = 时间(μs) × 0.017(cm/μs)

6.4 在主循环中使用

// main.c 任务调度
float distance;

void Task_Distance()
{
    distance = Ut_Wave_Data();
    // 如果 distance == 0,说明超出量程(> 约65cm)
}

// 在 task_t 数组中注册(120ms 执行一次)
{ Task_Distance, 120, 0 }

7. 常见问题

:cross_mark: 问题1:距离始终返回 0

原因

  1. 超声波模块没有供电
  2. TX/RX 引脚接反(P1.0 接 TRIG,P1.1 接 ECHO)
  3. CF 标志没有清零,进入就超时

解决

// 每次测距前确保 CF 清零
CF = 0;
CH = 0; CL = 0;
CCON = 0x00;

:cross_mark: 问题2:距离跳动很厉害

原因:PCA 计数和主程序读取之间有中断干扰

解决

EA = 0;                       // 关中断
time = CH * 256 + CL;
EA = 1;                       // 开中断

:cross_mark: 问题3:精度不够(每次差 1~2cm)

原因:发脉冲函数的延时精度影响起始时刻

解决

  • 延时函数用精确计算的 NOP 延时
  • 测距频率不要太高(模板中 120ms 一次,合理)
  • 适当做多次平均滤波

:cross_mark: 问题4:PCA 和 Timer0 冲突?

PCA 与 Timer0 不冲突。冲突的是:

  • P3.4(T0 外部计数引脚) 与键盘矩阵第4行(S9~S12)冲突
  • PCA 的 ECI 引脚是 P1.2,与 CCP2 备用引脚共用

结论:超声波(PCA)+ 频率计(Timer0 计数模式)可以同时使用,无冲突。


:cross_mark: 问题5:计数值溢出怎么处理?

CH:CL 是 16 位,最大 65535。
65535 μs × 0.017 ≈ 111 cm 是测量上限。

// 实际项目中典型上限约 65cm(等待 RX 时间更长)
// 若 CF=1,说明超出量程
if (CF == 1)
{
    CF = 0;   // 必须手动清零!
    return 0;
}

:books: 学习检查清单

完成以下任务,确保真正掌握 PCA:

  • 能说出 PCA 中 CH:CL、CR、CF 分别代表什么
  • 知道如何用 CMOD 选择时钟源
  • 理解超声波测距的完整时序
  • 能解释为什么用 Fosc/12 而不是 Fosc/2
  • 知道 CF 标志需要软件清零
  • 能写出完整的 Ut_Wave_Data() 函数
  • 理解距离公式 time × 0.017 的来源
  • 知道 PCA 模块与 Timer0/Timer1 各自的使用场景

:light_bulb: 公式速记

时钟频率  = Fosc ÷ 12 = 12MHz ÷ 12 = 1 MHz
计时精度  = 1 / 1MHz = 1 μs / tick
最大计时  = 65535 μs ≈ 65.5 ms
声速      = 340 m/s = 0.034 cm/μs
距离(cm)  = T(μs) × 声速/2 = T × 0.034/2 = T × 0.017

文档版本:V1.0
最后更新:2026-03-14
适用于:蓝桥杯单片机竞赛 STC15F2K60S2