蓝桥杯STC15F2K60S2单片机CCP/PCA/PWM模块实战:从寄存器解析到多场景应用
1. 别再只盯着定时器了你的STC15F2K60S2还有更强大的“瑞士军刀”很多刚开始玩蓝桥杯CT107D开发板的同学可能和我当初一样觉得把定时器0、1、2用明白了比赛和项目就够用了。定时器中断做个流水灯串口发个数据感觉已经掌握了单片机的“核心科技”。我当初也是这么想的直到有一次看到一个高手用STC15F2K60S2驱动了一个舵机波形又稳又准代码还特别简洁一问才知道人家根本没怎么用传统定时器用的是芯片自带的CCP/PCA/PWM模块。那一瞬间我感觉自己错过了宝藏。这个模块简直就是嵌在单片机里的一把“瑞士军刀”。它把捕获Capture、比较Compare、脉宽调制PWM三大功能集于一身而且硬件自动完成不占用CPU大量时间。你想测量一个脉冲的宽度不用再像以前那样在外部中断里手忙脚乱地记录定时器值了CCP模块可以帮你硬件捕获。你想产生一个精确的、可调占空比的PWM波来控制电机亮度或舵机角度也不用再繁琐地配置定时器重装值和翻转IO口了PWM模式直接硬件输出。甚至它还能当作一个额外的、非常灵活的16位定时器使用。所以如果你还在为资源不够用、代码效率低发愁或者想在蓝桥杯等竞赛中写出更优雅、更高效的代码那么深入理解并掌握CCP/PCA/PWM模块绝对是让你从“够用”到“精通”的关键一步。这篇文章我就结合我这几年在项目里摸爬滚打的经验带你从最根本的寄存器配置逻辑开始把这把“瑞士军刀”的每一种用法都掰开揉碎了讲清楚并附上在CT107D板上实测可用的代码。咱们不搞纯理论就讲怎么用怎么用好。2. 庖丁解牛认识CCP/PCA模块的核心寄存器想要灵活驾驭这个模块死记硬背例程代码是没用的必须得理解背后那几个关键寄存器是怎么配合工作的。这就好比开车你得知道方向盘、油门、刹车分别是干嘛的而不是只会背“第一步拧钥匙第二步挂D挡”。STC15F2K60S2的CCP/PCA模块相关的寄存器不多但每个都至关重要。2.1 模块的“大脑”CCON和CMOD寄存器我们可以把PCA模块想象成一个多功能厨房电器CCONPCA控制寄存器就是它的总开关和状态指示灯而CMODPCA模式寄存器就是它的功能选择旋钮和速度调节钮。先看CCON地址D8H。这个寄存器里我们需要关注几个位CFPCA计数器溢出标志当PCA的16位计数器CL, CH从FFFF加到0000时这个位会被硬件置1。如果你开启了中断就会触发PCA溢出中断。用不用它取决于你的需求。CRPCA计数器运行控制这是最重要的位之一相当于整个PCA模块的电源开关。你只有把CR置1PCA的计数器才开始“滴答滴答”地走。在初始化模块的最后一步一定要记得CR 1;。CCF2, CCF1, CCF0各模块中断标志这三个位分别对应PCA模块2、1、0的中断标志。当某个模块的捕获或比较事件发生时对应的CCFx位会被硬件置1。在中断服务函数里必须手动用软件把这个标志位清零否则你会一直卡在中断里出不来。比如用了模块0中断里就要写CCF0 0;。然后是CMOD地址D9H它决定了PCA计数器怎么工作CIDL空闲模式下是否停止PCA计数器一般不用管设为0。CPS2, CPS1, CPS0时钟源选择位这是“速度调节钮”。它决定了PCA计数器的时钟从哪里来以及分频多少。比如CPS20, CPS10, CPS00表示时钟源是系统时钟的12分频和传统定时器0/1的12T模式一样。如果你想得到更高的PWM频率或更精确的捕获可以选择系统时钟不分频CPS20, CPS10, CPS01。具体组合查手册最靠谱我常用的就是12分频和不分频这两种。ECFPCA溢出中断使能如果你需要PCA计数器溢出即从FFFF到0000时产生中断就把这个位置1。大部分时候我们用各个模块自己的比较或捕获中断这个位可以设为0禁止。2.2 模块的“双手”CCAPMx与PCA_PWMx寄存器PCA模块有三个独立的工作单元叫PCA模块0、模块1、模块2。每个模块都有自己的“双手”来具体干活。CCAPMxx0,1,2就是指挥这双手“干什么活”的指令集。以CCAPM0地址DAH为例我们通过设置它的各个位来定义模块0的工作模式ECOM0比较器使能位。如果你想用这个模块做定时器或者PWM这个位必须置1允许比较功能。CAPP0 / CAPN0捕获控制位。CAPP01表示在引脚如CCP0的上升沿捕获PCA计数器的值CAPN01表示下降沿捕获两个都置1就是上升沿和下降沿都捕获。这就是实现脉冲宽度测量的硬件基础。MAT0匹配控制位。当PCA计数器的值和我们预设的CCAP0L/H值相等时如果MAT01就会自动将CCF0中断标志置1。做定时器功能时必须置1。TOG0翻转控制位。这个位非常有意思当MAT0条件满足即匹配时如果TOG01模块对应的CCP0引脚会自动发生电平翻转。这是产生高速脉冲方波输出的关键。PWM0脉宽调制使能位。想用这个模块输出PWM波就必须把这个位置1。同时ECOM0也必须为1。ECCF0模块0的中断使能位。你想让模块0的匹配或捕获事件触发中断就把它置1。而PCA_PWMxx0,1,2寄存器是专门为PWM模式服务的“精细调节器”。在STC15里PWM可以是6位、7位、8位、10位等等分辨率这个寄存器里的EPCnH和EPCnL位就是用来设置PWM占空比的高位部分当分辨率超过8位时。对于常用的8位PWM我们通常只操作CCAPxL来设置占空比这个寄存器保持默认值0即可。但如果你需要更高精度的PWM比如控制舵机需要0.5°的精度就需要用到它来和CCAPxL配合。2.3 模块的“心脏”与“目标”CL/CH与CCAPnL/HCL和CH组成了一个16位的PCA基础计数器它是三个模块共用的“心脏”一直在循环累加从0到65535然后溢出回到0。所有模块的捕获和比较动作都是参照这个计数器的值来进行的。CCAPnL和CCAPnHn0,1,2是每个模块的“目标值”寄存器。在不同的模式下它们的作用不同比较模式定时器、PWMPCA计数器CL,CH不断累加当它的值增长到与CCAPnH, CCAPnL这对预设值相等时就触发“匹配”事件可能产生中断或翻转引脚。捕获模式当指定的引脚沿上升/下降到来时硬件会瞬间把此刻PCA计数器CL,CH的值“抓拍”下来存入CCAPnH, CCAPnL中。我们只需要在中断里读取这个值就能知道事件发生的精确时刻。理解了这个“心脏”CL/CH和“目标”CCAPnL/H的关系整个模块的工作逻辑就清晰了。3. 实战第一步将PCA模块配置为外部中断很多同学知道P1^3, P3^2这些外部中断引脚但有时候引脚资源紧张或者想用不同的边沿触发方式PCA模块的中断功能就能派上用场。它可以把CCP0、CCP1、CCP2等引脚配置成外部中断输入而且支持上升沿、下降沿或双边沿触发非常灵活。这里我们以模块0CCP0引脚默认在P1.1为例实现一个下降沿触发的外部中断每按一次按键引脚接地就让开发板上的LED状态翻转一次。核心配置逻辑如下引脚映射首先通过AUXR1也叫P_SW1寄存器确认CCP0引脚映射在哪里。CT107D板子默认是在P1.1所以我们通常不需要改动但代码里最好保留配置语句以示规范。停止计数器初始化时先将CCON寄存器清0主要是把CR位清零让PCA计数器先停下来。设置工作模式在CMOD寄存器里选择时钟源。对于外部中断功能计数器其实只是在后台运行用于记录捕获的时刻时钟源选择默认的12分频即可CMOD0x00并且禁止溢出中断ECF0。配置模块为捕获模式这是关键设置CCAPM0寄存器。我们想要下降沿触发所以将CAPN0位置1同时为了能进中断必须把ECCF0模块0中断使能也置1。因此CCAPM0 0x11;即0001 0001B。启动与开中断将CR位置1启动PCA计数器。然后打开总中断EA。当中断发生时硬件会把当前的PCA计数器值捕获到CCAP0L/H中虽然这个值在简单外部中断里我们可能不读并置位CCF0标志。我们在中断服务程序里需要手动清除CCF0然后执行我们的任务比如翻转LED。// PCA中断服务函数 void PCA_ISR() interrupt 7 using 1 { CCF0 0; // 必须手动清除中断标志 LED ~LED; // 你的任务例如LED取反 }注意PCA中断的入口号是7即interrupt 7。using 1是指定使用第1组寄存器组这在一些对实时性要求高、中断嵌套复杂的场景有用可以加快中断响应。简单应用可以省略。这个功能看似简单但它揭示了PCA捕获模式的基础。相比于传统外部中断它的优势在于可以精确记录下中断发生的时刻捕获到的计数器值为后续测量脉冲时间间隔打下了基础。你可以尝试修改CCAPM0为0x21上升沿触发或0x31双边沿触发体验一下不同的触发方式。4. 化身为精准的定时器PCA的16位定时器模式当你觉得芯片自带的定时器0、1、2不够用或者你想用一个更“独立”的定时器时PCA模块的定时器模式就是你的救星。它实现的是一个16位自动重装的定时器精度高使用起来比配置传统定时器还要直观。它的工作原理是这样的PCA计数器CL,CH自由向上计数。我们给模块0设定一个目标值比如CCAP0L0x78, CCAP0H0xEC对应十进制60536。当PCA计数器的值增长到和目标值相等时发生“匹配”事件这会触发两个动作第一硬件置位CCF0中断标志如果开启了中断第二硬件会自动将我们预先计算好的下一个目标值比如value T100Hz加载到CCAP0L/H中。这样就实现了自动重装无需在中断里手动重装初值非常省心。如何配置基本配置同上先进行引脚映射虽然定时器模式可能不用具体引脚但也建议配置、停止计数器CR0。设置时钟源在CMOD中设置PCA计数器时钟。例如CMOD 0x00;表示系统时钟12分频。如果系统时钟是12MHz那PCA计数频率就是1MHz计一个数耗时1微秒。计算并设置初始比较值这是核心步骤。假设我们要实现一个10ms的定时中断。PCA计数频率是1MHz那么10ms就需要计数1000000 * 0.01 10000次。因为PCA计数器是16位最大值65535我们可以让计数器从0开始计到10000时中断。那么初始比较值就设为10000。但注意我们采用“自动重装”思路需要定义一个变量value来存储当前和下一个比较值。#define FOSC 12000000L //系统频率12MHz #define T100Hz (FOSC/12/100) //计算100Hz10ms对应的计数值即10000 unsigned int value; value T100Hz; //初始比较值 CCAP0L value; //写入低8位 CCAP0H value 8; //写入高8位 value T100Hz; //计算下一个比较值预存起来配置模块为16位定时器模式设置CCAPM0寄存器。需要使能比较器ECOM01使能匹配功能MAT01使能模块中断ECCF01。所以CCAPM0 0x49;即0100 1001B。启动与开中断置位CR开启总中断EA。在中断服务函数里我们只需要做三件事清中断标志、更新比较值寄存器、执行定时任务比如累加一个软件计数器实现1秒闪烁。void PCA_ISR() interrupt 7 { CCF0 0; CCAP0L value; CCAP0H value 8; // 将预存的下一个比较值写入完成“自动重装” value T100Hz; // 计算再下一个比较值为下次中断准备 // 你的定时任务例如 if(cnt 100){ // 10ms * 100 1s cnt 0; LED ~LED; } }这种方式的定时器非常精准因为重装值是硬件在匹配时刻自动完成的没有软件重装带来的时间误差。你可以通过修改T100Hz的定义轻松得到不同周期的定时中断非常灵活。5. 释放硬件威力用PCA输出可调PWM与高速脉冲这是CCP/PCA模块最吸引人的功能之一——硬件PWM输出。传统上用定时器模拟PWM需要占用CPU时间不断翻转IO口精度和频率也难做高。而硬件PWM完全由PCA模块自动完成CPU只需要设定好参数就可以去干别的事了效率极高。5.1 高速脉冲方波输出在讲PWM之前我们先看一个更简单的“高速脉冲”模式。它本质上利用了定时器模式的“匹配翻转”功能。当我们配置CCAPM0寄存器时不仅设置了ECOM01, MAT01, ECCF01还加上了TOG01。这意味着每次PCA计数器与CCAP0L/H的值匹配时除了触发中断还会自动翻转CCP0引脚的电平。CCAPM0 0x4d; // 即0100 1101B包含了ECOM0, MAT0, TOG0, ECCF0这样CCP0引脚就会输出一个频率稳定的方波。方波的周期由我们设置的匹配间隔决定。例如我们设置匹配间隔为T100Hz对应10ms那么引脚就会每10ms翻转一次输出一个周期为20ms50Hz的方波。通过调整匹配间隔可以轻松产生从几Hz到几百KHz的方波信号非常适合产生时钟或驱动某些需要脉冲信号的器件。5.2 真正的硬件PWM输出PWM模式则更进一步它可以产生占空比可调的脉冲波形。STC15的PCA模块支持多种PWM分辨率6/7/8/10位等我们最常用的是8位PWM因为计算和调节最简单。8位PWM的工作原理在PWM模式下PCA模块内部使用了一个8位的计数器由CL的低8位或高8位构成取决于设置与CCAPxL寄存器进行比较。当内部计数器值小于CCAPxL时引脚输出一种电平比如高电平当大于或等于CCAPxL时输出另一种电平比如低电平。当内部计数器溢出时硬件会自动将CCAPxH的值重载到CCAPxL中从而实现无干扰地更新占空比。配置步骤使能PWM模式和比较器设置CCAPM0寄存器PWM01,ECOM01。通常我们不希望PWM匹配产生中断所以ECCF0可以设为0。因此CCAPM0 0x42;即0100 0010B。设置PWM的周期和占空比PWM频率周期由PCA计数器的时钟源和PWM的位数共同决定。对于8位PWMPWM频率 PCA时钟输入频率 / 256。如果PCA时钟是系统时钟12分频1MHz那么PWM频率就是 1MHz / 256 ≈ 3.9KHz。占空比由CCAP0L的值决定。CCAP0L的值可以在0~255之间设置。例如CCAP0L 128;则占空比就是 128/256 50%。注意在PWM模式下我们更新占空比时是更新CCAP0H寄存器下次PWM周期开始时CCAP0H的值会自动装载到CCAP0L。这样可以避免在PWM周期中间更新占空比导致输出毛刺。// 初始化时设置初始占空比 CCAP0H 128; // 50%占空比 CCAP0L 128; // 首次也需要写入L // 运行时动态改变占空比 CCAP0H 64; // 改为25%占空比会在下一个PWM周期生效启动PCA计数器CR 1;。配置好后CCP0引脚就会自动输出PWM波了。你可以通过修改CCAP0H的值来实时、平滑地调整亮度、速度或角度这在控制LED调光、电机调速、舵机转动时非常有用。我做过一个小项目用这个功能做了一个呼吸灯代码极其简洁CPU占用率几乎为0效果却非常平滑。6. 进阶应用捕获模式精确测量脉冲宽度捕获功能是CCP模块的另一个绝活它能以硬件的方式精准地“抓住”外部引脚上跳变沿发生的时刻。比如你想测量一个超声波模块回响信号的高电平宽度或者测量一个遥控器PWM信号的脉冲长度用捕获功能就再合适不过了。其工作流程如下初始化PCA计数器让它自由运行。配置CCAPMx寄存器为捕获模式。例如设置CAPP01上升沿捕获和ECCF01使能中断。当CCP0引脚上出现一个上升沿时硬件会立即做两件事第一将此刻PCA计数器CL,CH的完整16位值“咔嚓”一下保存到CCAP0L和CCAP0H中第二置位CCF0中断标志。在中断服务程序中我们读取CCAP0L/H的值这个值就是上升沿发生的时刻T1。如果需要测量脉宽我们通常会在第一次上升沿捕获时记录下T1并将捕获边沿改为下降沿通过修改CCAPM0寄存器。当下降沿到来时再次触发中断捕获到时刻T2。那么高电平的脉宽就是(T2 - T1) * 计数周期。测量完成后再将边沿改回上升沿准备下一次测量。这种方法的精度取决于PCA计数器的频率如果采用系统时钟不分频12MHz那么理论测量精度可以达到1/12微秒远超软件计时。在蓝桥杯的某些需要测量频率或占空比的赛题中这个功能是“大杀器”。一个简单的测量思路代码框架unsigned int capture_start 0; bit capture_flag 0; // 标志是否已捕获到起始沿 void PCA_ISR() interrupt 7 { CCF0 0; if(!capture_flag) { // 第一次捕获到上升沿记录起始值并改为下降沿捕获 capture_start (CCAP0H 8) | CCAP0L; CCAPM0 0x21; // 改为下降沿捕获 capture_flag 1; } else { // 第二次捕获到下降沿计算脉宽 unsigned int capture_end (CCAP0H 8) | CCAP0L; unsigned int pulse_width; // 处理计数器溢出情况 if(capture_end capture_start) { pulse_width capture_end - capture_start; } else { pulse_width 65536 - capture_start capture_end; } // 将pulse_width转换为时间微秒例如pulse_width / (FOSC/12/1000000) // ... 你的处理代码 ... // 准备下一次测量改回上升沿捕获清除标志 CCAPM0 0x11; capture_flag 0; } }7. 融会贯通在蓝桥杯CT107D平台上的综合应用思路学完了单点功能最终还是要落到实际应用上。在蓝桥杯CT107D这样的综合实训平台上PCA模块可以帮你优雅地解决很多问题。场景一四路舵机控制。比赛中小车、机械臂可能需要多个舵机。传统一个定时器控制多个舵机软件逻辑复杂且精度难保证。你可以使用PCA模块0、1、2的三个PWM输出通道分别产生三路独立的、占空比可调的PWM波周期20ms来控制三个舵机。如果还需要更多可以用定时器模式模拟第四路精度稍低或者用普通IO配合一个PCA定时器中断来软件生成。场景二超声波测距与电机调速协同。你可以用PCA模块0的捕获功能来精确测量超声波回响脉宽同时用PCA模块1的PWM模式来驱动电机比如L298N的使能端。两个任务都由硬件并行处理互不干扰CPU只需要在中断里读取结果和更新占空比即可大大减轻了主循环的负担让程序结构更清晰。场景三生成精准的时钟信号。如果需要驱动某些需要特定频率时钟的芯片比如某些传感器、显示屏可以用PCA的高速脉冲输出模式产生一个非常稳定的方波时钟比用延时函数模拟的可靠得多。配置上的几个坑我帮你踩过了引脚复用STC15的CCP/PCA引脚是复用的默认可能在P1口也可能在P3、P2口。一定要在程序开头用P_SW1(AUXR1) 寄存器明确配置到你想要使用的引脚上否则程序可能不工作。CT107D板上的LED、数码管等外设已经占用了很多IO规划引脚时要避开。中断号别记错PCA的中断号是7不是5也不是1。写interrupt 7。中断标志要清零无论是CCFx还是CF标志只要进了中断一定要记得用软件清零这是硬件规定的。PWM占空比更新更新PWM占空比时是写CCAPxH寄存器而不是CCAPxL。写CCAPxH后新的占空比会在当前PWM周期结束后下一个周期开始时自动生效这样输出不会有毛刺。最后我建议你拿到代码后不要仅仅下载进去看现象。最好用示波器或者逻辑分析仪如果没有可以用Keil的软件仿真配合虚拟逻辑分析仪看一下CCP引脚上实际输出的波形。亲眼看到那干净利落的方波、平滑变化的PWM你会对硬件模块的强大有更深刻的理解。从“知道”到“会用”再到“用好”中间就差动手实践这一步。希望这篇文章能帮你把这把“瑞士军刀”磨锋利了在接下来的项目和竞赛里让它成为你得心应手的利器。