
简介面向STC单片机开发者特别是使用STC15W204S进行PWM输出的工程师这份代码包提供了基于PCA模块实现PWM生成与端口切换的完整工程方案可解决PWM占空比配置、时钟源选择及多通道输出等常见问题适用于电机调速、LED亮度控制、音频信号生成等嵌入式场景。包内共20个文件、约51KB包含2个C源码文件与2个头文件以及Keil工程配置文件、编译生成的obj/lst/hex/m51等中间文件还有启动汇编文件等便于直接查看代码、查阅编译映射信息或烧录验证。目前已有171人学习下载。通过分析C源码与头文件的代码结构读者可以理解PCA工作模式、比较值设定以及运行时切换PCA通道的关键策略并能基于现有工程快速移植到其他STC15系列芯片同时配合启动文件与编译映射信息可进一步梳理单片机外设初始化和Keil工程组织方式节省底层开发时间便于项目快速落地。1. STC15W204S 的 PCA PWM一颗 8 脚 51 为什么还要做专用 PWMSTC_PWM.zip这类压缩包名字在下载站里一眼就能认出是 STC 单片机例程芯片型号 15w204s功能是 PWM分享者 ID 是 apple5ze。包里代码的核心不是用定时器中断去翻 IO而是 STC15W204S 这颗 SOP8 封装 51 芯片自带的 PCA可编程计数器阵列模块。先纠正一个联想PCA 在统计学里是主成分分析在 STC 手册里是 Programmable Counter Array一个 16 位硬件计数器加两路捕获/比较/PWM 单元。用它输出 PWM 的好处是硬件自己刷波形、全程不占 CPU分辨率可以做到 8 位、9 位、10 位对 LED 调光、小型电机调速、舵机控制、433 遥控波形复制这几类活儿都够用。这篇文章按“先看懂寄存器、再跑通工程、最后调参验证”的顺序把 15W204S 上 PCA PWM 的完整做法讲清楚。2. 先把寄存器摆平CMOD、CCON、CCAPMn 与 PCA_PWMn 的频率/占空比换算2.1 PCA 计数器的四样家当PCA 可以理解成“一个 16 位计数器 CH:CL 加上每路独立比较器”。计数器从 0 数到 65535 溢出回 0每一路模块在计数值等于自己的比较值时动作PWM 模式只是把“匹配置高、溢出置低”这个行为固定下来。STC15W204S 封装小PCA 只有 0、1 两路引脚分别复用在 P3.0 和 P3.1但在 SOP8 里这已经是两个完整的硬件波形发生器了。控制它的寄存器一共四组寄存器地址复位值干什么用CMOD0xD90x00PCA 计数器时钟源、空闲模式行为、溢出中断开关CCON0xD80x00CR 运行控制位、CF/CCF 标志位CH / CL0xF9 / 0xE90x0016 位计数值程序不要乱写CCAPM0 / CCAPM10xDA / 0xDB0x00各路工作模式CCAP0L / CCAP0H0xEA / 0xFA0x00模块 0 比较值PWM 模式下就是占空比CCAP1L / CCAP1H0xEB / 0xFB0x00模块 1 比较值PCA_PWM0 / PCA_PWM10xF2 / 0xF30x008/9/10 位位宽选择与高位比较值地址按 STC15 系列通用 SFR 映射写程序里直接用寄存器名没必要硬编码地址。CMOD 里三个关键位CIDLbit7为 1 时空闲模式停 PCA 计数ECFbit3是溢出中断使能PWM 场景一般不开CPS[2:0] 选 PCA 时钟源000 是系统时钟 1 分频111 是定时器 0 溢出脉冲中间的 001/010/011/100/101/110 依次是 2、4、6、8、12、16 分频。CCON 的 CRbit6是总开关CR1 计数器才跑CF 是溢出标志软件清CCF0/CCF1 是各路中断标志PWM 输出用不上。2.2 8 位 PWM 的占空比公式和 CCAPnH 更新时机8 位 PWM 模式下比较器只看 CL 的低 8 位计数器每数 256 个 PCA 时钟完成一个周期所以频率公式只有一条f_PWM PCA 时钟 / 256占空比由匹配和溢出的动作决定当 CL 的低 8 位等于 CCAPnL 时输出置高低 8 位溢出时输出置低。这样 CCAPnH 越大、匹配发生得越晚、高电平维持越久占空比越大公式是占空比 CCAPnH / 2560x80 是 50%0x00 是接近 100% 常亮0xFF 是约 0.4% 接近全灭。这里有个反直觉的坑想“写 0 灭灯”的人会得到一盏常亮的灯真要灭灯得写 0xFF这是 PCA PWM 和很多定时器 PWM 极性相反导致的。更新时机由硬件决定每个周期溢出时硬件把 CCAPnH 的内容搬进 CCAPnL所以主程序只写 CCAPnH下一周期自然生效8 位模式不需要先写低字节再写高字节。2.3 9 位/10 位模式与 PCA_PWMn 位宽选择想提高分辨率把 PCA_PWMn 从默认的 0x008 位改成 9 位或 10 位模式。位宽变宽后周期变长9 位是 512 个 PCA 时钟10 位是 1024 个频率公式分母变成 512/1024占空比公式分子分母同步放大。STC15 例程里普遍这么写8 位是 0x009 位写 0x8010 位写 0xC0置的是 PCA_PWMn 的高位模式位不同手册版本对这两个位的命名有 EPCOH/EPC0H 两种写法但数值组合是通用的。9/10 位模式还有一个必须养成的习惯占空比更新要先写 CCAPnL、再写 CCAPnH。比较值跨两个字节硬件在任何一拍的匹配都可能发生颠倒顺序会让比较器在一个周期里匹配到高低字节混搭的撕裂值LED 上就是偶发的闪动。8 位模式只有一个字节不存在这个问题。以 CPS000、8 位模式为例把 STC-ISP 里可选的 IRC 档位对应的 PWM 频率算出来方便先算后配IRC 档位 (MHz)PCA 时钟 (MHz)8 位 PWM 频率 (kHz)5.52965.529621.66623.411.059211.059243.2121246.875242493.75系统时钟来自内部 IRC下载时在 STC-ISP 里选定后固化代码里改不了。觉得 46.875kHz 太高先把 CPS 改成 2 分频频率降到 23.4kHz不用动代码逻辑。3. Keil C51 最小工程15W204S 双路 PCA PWM 从初始化到呼吸灯3.1 引脚映射与最小硬件STC15W204S 的 SOP8 封装上P3.0 复用 CCP0P3.1 复用 CCP1有些资料直接标成 PWM1/PWM2。上电复位后 IO 默认是准双向弱上拉PWM 波形能出来但驱动能力一般带一颗 5V 灯珠串 220Ω 电阻没问题要驱动三极管或 MOS 管的基极建议先把对应 IO 配成推挽输出否则高电平会被负载拉掉一截占空比和亮度的对应关系会漂。配推挽的寄存器是 P3M0/P3M1不同型号定义一致按头文件里的位操作即可。3.2 初始化代码#include STC15.H // STC-ISP 添加到 Keil 的头文件含 STC15W204S SFR 定义 void PCA_PWM_Init(void) { CMOD 0x80; // CIDL1 空闲停止PCACPS000 PCA时钟系统时钟/1ECF0 不开溢出中断 CCON 0x00; // 清除 CF/CCF 标志CR0 让计数器先停在确定状态 CCAPM0 0x42; // 模块0比较器PWM 模式STC 官方例程标准值 CCAPM1 0x42; // 模块1比较器PWM 模式 PCA_PWM0 0x00; // 模块0 选择 8 位 PWM PCA_PWM1 0x00; // 模块1 选择 8 位 PWM CCAP0H CCAP0L 0x80; // 通道0 占空比 50% CCAP1H CCAP1L 0x80; // 通道1 占空比 50% CR 1; // 启动 PCA 计数器PWM 立即输出 }CCAPM00x42 置位的是“使能比较器 ECOMn”和“使能 PWM 模式 PWMn”两个功能位PWM 输出不需要开 ECCFn 中断。注意 PCA_PWM0 必须和 CCAPM0 配套写只写 CCAPM0 不写 PCA_PWM0 时复位值碰巧是 8 位也能跑但一旦误配成 9 位输出频率就会悄悄变成计算值的一半。3.3 主循环里改占空比8 位模式下改占空比只写 CCAPnH 一个字节void PWM0_Set(unsigned char duty) { CCAP0H duty; // 溢出时硬件自动装入 CCAP0L下一周期生效 } void main(void) { unsigned char duty 0x01; unsigned char dir 1; unsigned int n; PCA_PWM_Init(); while(1) { if(dir) { if(duty 0xFE) duty; else dir 0; } // 值越大占空比越大逐渐变亮 else { if(duty 0x01) duty--; else dir 1; } // 值越小占空比越小逐渐变暗 PWM0_Set(duty); for(n 0; n 30000; n); // 12MHz 下约 30ms 一档呼吸节奏够用 } }方向逻辑按“值大亮、值小暗”写和占空比公式保持一致。如果你习惯 0x00 表示灭、0xFF 表示最亮那就把写进 CCAP0H 的值改成 255 − duty等价于把输出极性反了过来但切记极性反转后 0x00 才是灭。延时循环在 11.0592MHz 或 24MHz 下时长会变呼吸灯无所谓做电机匀加速就必须换定时器计时CPU 空转不是好习惯。3.4 烧录动作STC-ISP 选型、IRC 频率与冷启动Keil C51 工程建好后先用 STC-ISP 的“添加 STC 型号到 Keil”功能把 15W204S 加进器件库这样头文件和仿真配置一次到位。下载时 STC-ISP 里芯片型号选 STC15W204SIRC 频率按需选 11.0592MHz 或 12MHz这个选择直接决定 PCA 时钟和 PWM 频率。STC 的下载流程是冷启动点“下载”按钮后给板子断电再上电。烧录 bootloader 占用 P3.0/P3.1 串口但它只在下载阶段存在运行期间不会干扰 PWM 输出。如果开发机是 macOSKeil C51 没有原生版本常见做法是 Windows 虚拟机或者用 SDCC 加命令行烧录工具但网上最多的例程都是 Keil C51 的器件库建议先按 Keil 跑通再折腾工具链。4. 调参实战PCA 时钟源切换、舵机 50Hz 与死区/防飞车边界4.1 占空比撕裂与 8/9/10 位的写入顺序8 位模式只写 CCAP0H 没问题一旦切到 9 位或 10 位比较值跨两个字节必须按 L→H 顺序写。先写 CCAP0L 再写 CCAP0H中间即使发生匹配也只是低 8 位暂时是新值、高位还是旧值最多一个周期内占空比略偏不会持续抖动。反过来先写高字节硬件在溢出瞬间搬入的是高低字节混搭值LED 会出现肉眼可见的闪动。网上例程里 setpwm 函数先写 L 后写 H就是这个原因不是强迫症。4.2 频率不够低时改走 T0 溢出舵机 50Hz 的完整换算8 位 PWM 在 12MHz 下是 46.875kHz给舵机用显然不行——舵机要 50Hz 周期、1.5ms 中位脉宽频率差了 900 多倍。出路是 CMOD 的 CPS111把 PCA 时钟源切到定时器 0 溢出。STC15 复位后 T0 默认 12T 模式12MHz 分频后是 1MHz用 8 位自动重装做到约 12.8kHz 溢出率void T0_As_PCA_Clock(void) { TMOD 0xF0; // 只清 T0 的 M1M0 位不动 T1 的配置 TMOD | 0x02; // T0 模式28 位自动重装溢出频率最稳定 TH0 TL0 178; // 1MHz / (256-17878) 12820Hz TR0 1; // 启动 T0 CMOD | 0x07; // CPS111 PCA 时钟切到 T0 溢出保留 CIDL 原值 }256−1787812MHz/12/78≈12820Hz再除以 256 得 50.08Hz舵机周期完全能接受。占空比按脉宽折算CCAPnH / 256 × 20ms 就是高电平毫秒数。1.5ms 中位算出来 CCAP0H≈0x130.5ms 和 2.5ms 两个极限大约是 0x06 和 0x20写程序时把角度线性映射到这个区间即可舵机脉宽占空比CCAP0H0.5ms左极限2.5%0x061.5ms中位7.5%0x132.5ms右极限12.5%0x204.3 PCA PWM 没有死区H 桥与电机驱动的常见处理15W204S 的 PCA 输出是单端信号没有互补输出更不会自动插死区。直接拿两路 PWM 去推 H 桥上下管切换瞬间必然直通烧管这是用这颗芯片做有刷电机正反转最常见的炸机点。常见做法是外接自带死区的半桥驱动芯片比如 IR2104 这类常用件让驱动芯片去处理互锁PCA 只负责出占空比要求不高的小功率场合有人用 RC 延时给 PWM 边沿做微秒级延迟但温漂大批量产品不建议。至于电机飞车和 PWM 故障保护PCA 没有故障刹车输入引脚软件里检测到过流或堵转只能尽快把两个 CCAPnH 写成 0xFF 再把 CR 清 0但 8051 跑飞时硬件 PWM 会按最后的值一直输出外置看门狗加硬件使能脚才是兜底方案。4.4 常见故障对照表现象常见原因处理P3.0 一直没波形CR 没置 1或 CCAPM0 写成了只捕获模式确认 CCAPM00x42 且 CR1占空比和代码反着误用 256−CCAPnH 公式极性理解反写入值换成 255−duty反过来映射频率是计算值的一半误配成 9 位模式分母变成 512检查 PCA_PWM0应为 0x00高电平带不动负载准双向弱上拉驱动不足按手册把 P3.0/P3.1 配成推挽呼吸有明显台阶8 位分辨率只有 0.4%换 9/10 位模式并按 L→H 顺序写上电瞬间 LED 全亮一下复位后 CCAPnH0对应 100% 占空比初始化阶段先写 0xFF最后再置 CR1最后一条是不少调光板“一上电闪白”的元凶复位后所有寄存器都是 0CCAPnH0 意味着常亮只要在 CR1 之前把初值写进 CCAPnH问题就消失了。5. 上板验证三板斧频率核对、渐变观察与负载边界5.1 第一斧频率核对写一个只输出 50% 固定占空比的最小固件示波器探头接 P3.0先看频率再看占空比。12MHz IRC 下应当看到 46.875kHz 的方波、占空比 50% 左右频率偏差超过 3%基本就是 STC-ISP 里 IRC 档位选错了。没有示波器就用带频率档的万用表读数会抖但数量级能判断配置有没有错。注意探头要用 ×10 档×1 档的输入电容会把高频方波边沿弄圆影响占空比读数。5.2 第二斧渐变占空比换方向把呼吸灯延时调小到 2ms 一档观察亮度是“先暗后亮再暗”还是“先亮后暗再亮”。前者说明方向逻辑和 CCAPnH/256 公式一致后者说明你把占空比写反了直接把映射改成 255−duty。顺手验证 9 位模式切到 PCA_PWM00x80 后按 L→H 顺序写亮度末段应当比 8 位时平顺如果出现周期性抖动就是某个更新路径只写了高字节。5.3 第三斧负载边界PCA 的 PWM 直接驱动 LED 没有悬念但负载侧要分清边界小型直流电机的 PWM 频率最好压在 1k~10kHz46.875kHz 虽然听不到啸叫MOS 管开关损耗会明显上升续流二极管必须加WS2811 这类 800kHz 单总线灯珠要用关中断位操作或定时器中断去模拟PCA PWM 出不了它要的脉宽序列。原板子上如果是 555 调光电路PCA 可以整套替换——频率由 IRC 而不是 RC 常数决定温漂小一个数量级但 555 自带模拟比较器PCA 这边欠压、过流时没有 555 那种模拟回路的快速响应保护得自己在软件里做。两路同用还有一个相位细节初始化时先给两路写相同的初值再统一把 CR1两路波形几乎同相如果先跑起来再分别改占空比两路相位差取决于各自写 CCAPnH 的时刻对需要严格同步的负载就不够用了。本文还有配套的精品资源点击获取