ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

PIC单片机PWM实现:CCP寄存器配置与占空比计算详解

PIC单片机PWM实现:CCP寄存器配置与占空比计算详解 简介面向PIC单片机初学者的PWM生成源码工程基于MPLAB集成开发环境编写适合正在学习PWM信号产生原理、定时器与PWM输出模块配置的开发者也可作为电机调速、LED调光、蜂鸣器控制等实际项目的底层参考。资源共包含7个文件类型覆盖C语言源文件、可直接烧录的HEX文件、MPLAB工程文件与工作区文件以及编译过程生成的COF、MAP、O辅助文件既方便阅读源码理解实现逻辑又能直接编译或烧录到芯片中观察波形输出。整个压缩包仅12KB轻量紧凑非常适合在MPLAB中快速打开并逐段调试。目前已有169人浏览学习属于简洁但完整的PWM入门案例。通过该工程读者可以厘清PWM周期、占空比等关键寄存器的配置顺序同时了解MPLAB工程中各类生成文件的作用为后续独立开发更复杂的PWM应用打下基础。1. 先说说PIC单片机上的PWM工程早年做设备维修时一块旧板子的调速信号来自硬件振荡器想换成单片机输出PWM手头没有DSP就翻出PIC16F877A来。PWM.rar就是我在MPLAB 8下留过的一套PIC单片机PWM工程解压后能看到PWM.c、PWM.hex、PWM.mcp、PWM.map这些文件直接用MPLAB打开PWM.mcw就能看到CCP模块怎么配。它解决的问题很具体用定时器2配合CCP模块输出占空比可调的PWM信号用来做电机调速、LED调光或者风机转速控制。适合刚学PIC单片机PWM、搞不清PR2和CCPR1L关系的初学者也适合老手拿它当模板改频率、改占空比少走弯路。2. PIC单片机PWM的周期计算与CCP寄存器选型PWM信号的两个边界参数是频率和占空比。PIC单片机的CCP模块在PWM模式下周期由定时器2的周期寄存器PR2决定占空比则由CCPR1L和CCP1CON的低两位共同组成一个10位数值决定。很多新手只改PR2不改T2CON导致输出频率不对更常见的是把占空比当成8位忘了CCP模块实际是10位。2.1 占空比计算公式PR2与CCPR1L怎么配合对PIC16F877A这类带标准CCP模块的型号PWM周期的标准写法是PWM周期 (PR2 1) × 4 × Tosc × TMR2预分频值其中Tosc是单片机振荡周期。写成频率形式就是PWM频率 Fosc / (4 × (PR2 1) × N)这里N是TMR2预分频值。我一般会先定PWM频率再算PR2最后根据目标占空比算10位寄存器值。占空比寄存器D可以这样推D 4 × (PR2 1) × 占空比D的高8位写进CCPR1L低2位写进CCP1CON5:4。这是PIC单片机PWM占空比计算公式里最容易写错的地方很多代码直接把CCPR1L写成占空比百分比结果输出要么一直高电平要么窄脉冲消失。计算代码和9.6kHz? Lets align.以Fosc4MHz、TMR2预分频N1、PWM频率10kHz为例PR2 4MHz / (4 × 10kHz) − 1 99。占空比50%时D 4 × 100 × 50% 200。200转换成10位二进制是0b0011001000高8位0x64写进CCPR1L低2位00写进CCP1CON5:4。我在调试时习惯把计算结果列成一张表PWM频率(kHz)PR2目标占空比10位D值CCPR1LCCP1CON5:4109910%40100109950%200500109990%360900109937.5%150372最后一行37.5%的D150低2位是2如果漏写CCP1CON5:4输出就会变成148实际占空比会差0.5%。所以拆位时不能只用CCPR1L还要同步更新CCP1CON低位的两位。写代码时我习惯把这段计算直接放在初始化里// Fosc4MHzTMR2预分频1:1freq_hz和duty_percent是传入参数 unsigned int period_reg (_XTAL_FREQ / (4UL * freq_hz)) - 1; unsigned int duty_reg (unsigned int)(4UL * (period_reg 1) * duty_percent / 100);period_reg对应PR2的值duty_reg是10位占空比寄存器值。后面写CCPR1L时只要取duty_reg 2写CCP1CON时取duty_reg 0x03左移4位。4UL是防止中间乘法被编译器按16位int截断尤其把PWM频率调到100kHz以上时这个细节很关键。2.2 不同PIC系列的PWM模块怎么选标准CCP、增强型ECCP和独立PWM模块并不是一回事。手头这颗PIC16F877A只有CCP1和CCP2能输出单路或双路PWM但没有死区、没有互补输出。如果要做H桥电机驱动需要带ECCP的PIC18F系列或者带独立PWM模块的PIC16F1789、PIC16F1619这类芯片。选型时可以参考下面这张表系列/型号PWM相关模块特点与适合场景PIC16F877ACCP1/CCP2单路PWM10位占空比适合调速、调光PIC18Fxx31ECCP支持半桥/全桥P1A~P1D互补输出有死区PIC16F1789PWM模块多路PWM互补输出可配死区有MCC插件PIC24/dsPIC系列独立PWM模块中心对齐、互补死区、故障保护适合BLDC如果只是驱动一个MOS管标准CCP就够用如果板子要做正反转尽量选带ECCP或者独立PWM模块的型号否则外部要加一堆死区电路和逻辑门调试成本比换芯片还高。2.3 MPLAB工程PWM.rar里的文件都干什么拿到PWM.rar后先不要急着烧hex先看文件结构。这份工程里的文件我整理过文件名作用PWM.mcwMPLAB工作区文件双击打开整个工程PWM.mcp工程文件记录源文件、编译选项、配置字PWM.c主要源码初始化CCP和TMR2PWM.cof调试符号文件MPLAB SIM仿真时用PWM.hex编译后生成的烧录文件PWM.map链接地址映射调试寄存器地址和内存分布PWM.o编译后的中间目标文件不参与烧录判断一份PIC工程能不能直接烧核心看PWM.mcp里的配置字。若是别人给的工程也可能配置字与当前晶振不匹配我一般会先看PWM.c开头和配置位代码确认振荡器类型。否则即使PWM.hex是好的烧到自己的板子上也可能频率偏一半甚至完全不输出。3. 在MPLAB里写一个可用的PIC单片机PWM程序从PWM.c到烧录如果你的目标只是复制一份代码那直接打开PWM.mcw编译就行。但很多情况下要改成自己的频率和占空比所以完整流程应该是根据晶振算PR2和占空比寄存器关掉CCP后按顺序配置引脚再开TMR2最后烧录验证。3.1 初始化代码先关CCP再配T2下面这份初始化代码适用于PIC16F877A晶振4MHzTMR2预分频1:1。我写的时候特意先关闭CCP1避免配置过程中RC2上出现不可预期的乱跳#include xc.h #define _XTAL_FREQ 4000000UL void PWM_Init(unsigned int freq_hz, unsigned char duty_percent) { unsigned int period_reg, duty_reg; CCP1CON 0x00; // 先关闭CCP1防止输出乱跳 TRISC ~0x04; // RC2/CCP1 设为输出 period_reg (_XTAL_FREQ / (4UL * freq_hz)) - 1; PR2 (unsigned char)period_reg; duty_reg (unsigned int)(4UL * (period_reg 1) * duty_percent / 100); CCPR1L duty_reg 2; CCP1CON 0x0C | ((duty_reg 0x03) 4); T2CON 0x04; // 开启TMR2预分频1:1 } void main(void) { PWM_Init(10000, 50); // 10kHz50%占空比 while (1) { // 主循环可以留空PWM由硬件持续输出 } }CCP1CON 0x00这一步很多人会省但我建议保留。CCP模块在配置过程中如果TMR2已经在跑而且PR2还没被赋值寄存器组合会随机输出脉冲先把CCP关闭寄存器写完后设成0x0CPWM输出才干净。TRISC ~0x04是把RC2的第二功能引脚设为输出漏掉这一句示波器上永远看不到正确波形。T2CON 0x04是开启TMR2且预分频为1:1如果换成0x06分频比变成16:1PWM频率会直接降低16倍排查时容易忽视。3.2 编译PWM.mcp确认PWM.hex和PWM.map在MPLAB 8里打开PWM.mcw双击Project窗口里的PWM.c点Project菜单的Build All正常会生成新的PWM.hex、PWM.cof和PWM.map。编译过程中如果没报错PWM.hex就能被烧录器识别。编译后我通常会打开PWM.map看寄存器段落在哪个bank。之前有次调PIC16F877ACCP1CON的基地址在bank0但不少人把它写成bank1地址结果程序不报错输出却不动作PWM.map可以帮你确认编译后的绝对地址尤其是用了不同bank的外设时。编译输出和烧录文件的对应关系可以按这个理解文件调试阶段烧录阶段PWM.mcp决定编译配置不参与烧录PWM.cofMPLAB SIM断点单步用不需要PWM.map看寄存器地址、变量定位不需要PWM.hex最终固件是烧录文件PWM.o编译中间产物不参与PWM.hex是个纯二进制镜像PWM.cof里有源码行号但MCC等插件生成的工程不一定都能用同一个cof文件如果调试时发现行号对不上直接删掉cof重新Build一次就好。3.3 烧录PWM.hexPICkit3与配置位烧录老PIC单片机我一般用PICkit3或MPLAB IPE。先打开MPLAB IDE选择Programmer里的PICkit3然后点击Program。烧录前要注意配置位尤其是振荡器选择和看门狗。用外部4MHz晶振时配置位一般设置成配置位推荐值说明FOSCHS高频晶振模式WDTEOFF关看门狗防止程序意外复位PWRTEON上电延时电源不稳时有帮助LVPOFF关闭低压编程释放RB3引脚BORENON电压跌落时强制复位避免PWM卡死烧录完成后RC2上应该能直接量到10kHz方波。如果量不到优先检查配置位里的FOSC是不是选成了LP或者RC内部振荡器4MHz晶振配成LP模式PWM频率会变得非常慢用示波器看会误以为没信号。另外PICkit3给目标板供电时如果板子上的稳压芯片功率不够也会造成PWM启动一段时间后消失这种软故障最费时间。4. PWM输出无波形先从寄存器级排查做过几个PIC工程后你会发现PWM不出波多半不是代码逻辑复杂而是某个引脚复用或配置位没写对。下面这套排查顺序是我从实际故障里总结出来的。4.1 示波器看不到PWM信号的常见原因用示波器点RC2之前先把下面这张表过一遍现象优先检查原因RC2一直低TRISC、CCP1CON引脚没设输出或者没进入PWM模式RC2一直高CCPR1L、CCP1CON5:4占空比寄存器可能是满值频率偏一半T2CON预分频值TMR2分频配置和计算值不一致频率严重偏低FOSC配置位晶振模式选错系统时钟不对板子几秒后无输出WDT配置位看门狗复位程序回到初始化波形有毛刺探头地线示波器地线夹太长形成环路只亮一下就不动LVP配置位RB3被低压编程占掉初始化又重设引脚我遇到过最隐蔽的情况是代码开头把ANSEL寄存器设为0。PIC16F877A没有ANSEL但PIC16F1789这类芯片默认靠ADC使能的多路复用引脚如果用的是RC2这个引脚而RC2有模拟输入功能要先把ANSELC的对应位置零否则引脚始终被模拟电路占用PWM信号出不来。手头PWM.c只针对老芯片拿到新型号时要额外检查这一项。4.2 占空比和计算值对不上时先看分辨率标准CCP的PWM占空比是10位也就是最大1023。但10位分辨率能不能全部用上取决于PR2。比如Fosc4MHzPWM频率10kHz时PR299占空比寄存器最大值是4×(991)400实际只有400级不是1023级把PWM频率调到100kHzPR29占空比寄存器最大值只有40级想从1%调到2%占空比会跳过去2.5%电机转速变化非常明显。检查占空比时我一般会在代码里加一个临时变量直接读出CCPR1L和CCP1CON低位合并后的值// 把当前占空比寄存器读回来便于调试 unsigned int read_duty_reg(void) { unsigned int d; d (unsigned int)CCPR1L 2; d | (CCP1CON 4) 0x03; return d; }这里CCPR1L取高8位CCP1CON》中的bit4和bit5是低2位先右移4位再取bit0和bit1。读回值和duty_reg对不上时多半是CCP1CON的低位被其它代码覆盖或者你把CCP1CON整个重新赋值成0x0C把低2位清掉了。完善的办法是更新时只改bit4和bit5不要对整个寄存器直接赋值。4.3 用MPLAB SIM和PWM.cof做寄存器级调试如果没有示波器可以先在MPLAB SIM里加载PWM.cof在PWM_Init函数里打断点观察period_reg和duty_reg这两个局部变量。模拟器不能真实反映RC2引脚上的电压但能确认寄存器写入是否符合预期。很多新手在代码里把CCP1CON写成0b00001100却忽略后面还要设置占空比低2位导致CCP1CON值被下一次循环覆盖这种情况下断点单步看两次函数调用之间的CCP1CON变化比反复查手册更快。调试时我还习惯在PWM.map里搜CCPR1L的地址范围确认编译器有没有把外设寄存器重映射到其它bank。PIC16F877A的寄存器是分bank的编译器会自动插入bank选择指令但如果你自己用指针操作寄存器很容易指错地址。这种问题编译期不会报错只有用PWM.map对照数据手册才能发现。5. 后续进阶动态更新PWM占空比、H桥死区与MCC生成PWM的初始化只是把硬件跑起来实际项目里更多是动态调整占空比比如慢慢加速、闭环调速。PIC16F877A的CCP模块在PWM模式下有双缓冲CCPR1L写入后会暂存在CCPR1H里等当前周期匹配完成才生效。所以可以直接在中断或者主循环里调用下面的函数void PWM_SetDuty(unsigned int duty_reg) { CCPR1L duty_reg 2; CCP1CON (CCP1CON 0xCF) | ((duty_reg 0x03) 4); }0xCF的作用是保留CCP1CON原先的bit6、bit7以及bit0到bit3只清空bit4和bit5然后写入低2位。这里不用担心当前周期中途更新会造成毛刺硬件会把新占空比锁存到下一个周期。如果做H桥驱动标准CCP模块没有互补输出需要换带ECCP或独立PWM模块的芯片。ECCP的死区时间由死区寄存器控制一般等于寄存器值乘以TMR2时钟周期不同型号的寄存器名不一样PIC18F系列叫ECCP1DELPIC16F1789系列直接叫PWM死区寄存器。配置前先查数据手册里的公式不要照搬其它型号的数值死区设太大会让半桥输出看起来像频率下降设太小会在换向时直通短路。新项目我一般直接用MPLAB X开MCC在图形界面里选PWM1填PWM频率和占空比MCC会生成PWM1_Initialize()和PWM1_LoadDutyValue()。MCC生成的代码仍然是在写PR2、CCPR1L和CCP1CON只是把10位拆分封装到了函数里所以想要快速验证思路就用MCC想彻底搞懂PIC单片机PWM行为还是得把PWM.c里那套手动配置吃透。拿到一份老PWM.rar时多看几眼PWM.map里的地址和MCC生成代码里的周期值反推一遍占空比公式比我在这里列一百个寄存器名都有用。本文还有配套的精品资源点击获取
返回列表