PIC18F46K22中CCP1与CCP2同时生成PWM时频率异常问题问询
解决双CCP模块SPWM频率异常及相位差实现问题
嘿,我看你这问题是双CCP模块生成同步SPWM时遇到了频率异常的坑,这在PIC单片机里挺常见的,我来帮你捋捋原因和解决办法:
问题根源分析
单独用CCP1正常,同时开CCP2就频率跑偏,核心原因大概率是两个CCP模块依赖的TMR2时基配置有冲突,或者占空比更新不同步——毕竟PWM模式下两个CCP都是靠TMR2提供计数基准的,只要其中一个模块的配置干扰了TMR2的运行,或者两路占空比更新时机不一致,就会导致整个时基紊乱。
分步解决办法
1. 确认TMR2的核心配置完全正确
首先得确保TMR2的参数和模式完全匹配10kHz的开关频率:
- 假设你用的是PIC系列单片机(从CCP、PR2这些寄存器判断),以8MHz晶振为例,指令周期
Tcy=0.5us,PWM周期公式是PWM周期 = (PR2+1) × 预分频 × Tcy。要得到10kHz(周期100us),计算下来(199+1)×1×0.5us=100us,刚好匹配,所以:- 必须把T2CON寄存器的预分频位
T2CKPS<1:0>设为00(预分频1) - 必须开启TMR2(
TMR2ON=1) - 代码里要明确写死T2CON的配置,比如:
T2CON = 0x04; // 对应PIC16F系列:TMR2ON=1,预分频1,后分频1
- 必须把T2CON寄存器的预分频位
2. 强制两路CCP的占空比同步更新
单独更新某一路的CCPRx寄存器时,可能会在TMR2计数过程中触发异常更新,干扰时基。最好的办法是在TMR2溢出中断里同步更新两路的占空比:
- 先开启TMR2中断:
INTCONbits.PEIE = 1; // 开启外设中断 PIE1bits.TMR2IE = 1; // 开启TMR2中断 INTCONbits.GIE = 1; // 开启全局中断 - 然后在中断服务函数里同时更新CCP1和CCP2的占空比,这样每一个PWM周期只会更新一次,完全同步,不会干扰TMR2的计数。
3. 正确实现90°相位差的正弦/余弦SPWM
要得到相位差90°的两路波形,本质是把正弦表和余弦表错开1/4基波周期:
- 你的基波是50Hz(周期20ms),开关频率10kHz(每个周期100us),所以一个基波周期对应200个PWM采样点(20ms/100us=200)。
- 所以正弦表的第n个点,对应余弦表的第
(n+50)%200个点(200/4=50),这样就能精准实现90°相位差。
4. 检查CCP模块的模式和引脚配置
- 确保两个CCP模块都被设置为PWM模式:
CCP1CON = 0x0C;、CCP2CON = 0x0C;(CCP模式位CCP<3:0>设为1100) - 确认对应引脚的TRIS位设为输出(比如CCP1一般是RC2,CCP2是RC1,所以
TRISCbits.TRISC2=0; TRISCbits.TRISC1=0;),你代码里的TRISD=0; TRISC=0虽然覆盖了,但最好明确指定对应引脚,避免后续改代码出问题。
修正后的完整代码示例
#include <pic16f877a.h> // 根据你的单片机型号调整头文件 #define SINE_TABLE_LEN 200 // 预先生成的10位分辨率正弦表(范围0~1023,对应0~100%占空比) unsigned int sine_table[SINE_TABLE_LEN] = { 512, 537, 562, 587, 612, 636, 660, 683, // 省略其余采样点,需自行补全 ... }; unsigned char pwm_cnt = 0; // 中断服务函数 void interrupt isr_handler(void) { if(PIR1bits.TMR2IF) { // 更新CCP1的正弦波占空比 CCPR1L = sine_table[pwm_cnt] >> 2; // 高8位 CCP1CONbits.DC1B = sine_table[pwm_cnt] & 0x03; // 低2位 // 更新CCP2的余弦波占空比(相位差90°) unsigned char cos_idx = (pwm_cnt + 50) % SINE_TABLE_LEN; CCPR2L = sine_table[cos_idx] >> 2; CCP2CONbits.DC2B = sine_table[cos_idx] & 0x03; // 更新采样索引 pwm_cnt++; if(pwm_cnt >= SINE_TABLE_LEN) pwm_cnt = 0; PIR1bits.TMR2IF = 0; // 清除中断标志 } } void main(void) { // 初始化IO和关闭ADC ANSELD = 0x00; ANSELC = 0x00; PORTD = 0x00; TRISD = 0x00; PORTC = 0x00; TRISCbits.TRISC2 = 0; // CCP1引脚设为输出 TRISCbits.TRISC1 = 0; // CCP2引脚设为输出 // 初始化TMR2(10kHz PWM频率) TMR2 = 0x00; PR2 = 199; T2CON = 0x04; // TMR2开启,预分频1,后分频1 // 初始化CCP1和CCP2为PWM模式 CCP1CON = 0x0C; CCP2CON = 0x0C; // 初始化中断 PIR1bits.TMR2IF = 0; PIE1bits.TMR2IE = 1; INTCONbits.PEIE = 1; INTCONbits.GIE = 1; // 主循环空转,所有逻辑在中断里处理 while(1); }
额外注意事项
- 正弦表的分辨率要和PWM的分辨率匹配:比如10位PWM的话,数值范围是0~1023,这样占空比才能线性变化。
- 要是还是有问题,检查T2CON里的后分频位
T2OUTPS<3:0>,设为0000(后分频1),这样每个PWM周期触发一次中断,刚好对应一次占空比更新。
内容的提问来源于stack exchange,提问作者Ken Adams
相关产品推荐
相关产品推荐

