基于PIC18F452的ADC可调PWM异常问题求助
电位器控制PWM输出故障排查与修复
故障现象
- 电位器输入0–3V时,PWM输出正常变化;但输入超过3.15V时,示波器输出变为平线,CCP1引脚的LED也无变化。
- 启动时输入3.15V及以上电压,ADC无法正常工作,但PORTB和PORTD(用于查看10位ADC输出)能显示正确值,此时调节电位器无响应。
- Proteus的DEBUG窗口显示0-5V输入对应的ADC值(CCPR1L范围0-255)正常,但CCP1引脚的LED不工作。
原代码
int rd_adc() { int x; int y; TRISB = 0; TRISD = 0; //trise.b1 = 1; ADCON0 = 0x81; // Fosc/64, Channel 0, A/D is on ADCON1 = 0xCE; // Right justified, Fosc/64, AN0 =Analog ADCON0.GO = 1; //Start Converting while (ADCON0.GO == 1) //wait for completion of conversion ; PORTB = ADRESH; //Display low byte on PORT C PORTD = ADRESL; //Display high byte on PORT D x = ADRESH; x = (ADRESH << 8) | (ADRESL); y = x * 0.2492; //for 0.00488x51(for making 10 bit x value within range of 0-255 //x = x * 51; return y; } void main() { TRISA.F0 = 1; TRISC.F2 = 0; PR2 = 156; CCP1CON = 0x0C; T2CON = 0x02; TMR2 = 0; //clr timer reg T2CON.TMR2ON = 1; while(1) { CCPR1L = rd_adc(); PIR1.TMR2IF = 0; //clr timer flag while (PIR1.TMR2IF == 0) // wait for end of period ; } }
问题分析
- ADC重复初始化:
rd_adc函数每次调用都重新配置ADCON0和ADCON1,导致ADC在运行中频繁重置,高电压启动时容易触发ADC工作异常,出现调节无响应的情况。 - PWM占空比处理不完整:PIC的CCP1 PWM是10位精度,由
CCPR1L(高8位)和CCP1CON的CCP1X/CCP1Y位(低2位)共同决定占空比。原代码只设置CCPR1L,丢失低2位信息,当ADC值超过645(对应3.15V)时,计算后的CCPR1L值会导致占空比逻辑错误,出现平线输出。 - 浮点数计算精度损失:用
y = x * 0.2492进行缩放,浮点数运算会带来截断误差,导致占空比映射不准确。 - 端口配置重复设置:
rd_adc中每次设置TRISB=0和TRISD=0属于冗余操作,且可能干扰端口状态。
修复后的代码
#include <pic16f877a.h> // 根据实际使用的MCU型号调整 int rd_adc() { int adc_val; ADCON0.GO = 1; // 启动ADC转换 while (ADCON0.GO); // 等待转换完成 PORTB = ADRESH; // 输出ADC高字节到PORTB PORTD = ADRESL; // 输出ADC低字节到PORTD adc_val = (ADRESH << 8) | ADRESL; // 组合10位ADC值 return adc_val; } void main() { // 端口方向初始化 TRISA.F0 = 1; // AN0设为输入(电位器连接端) TRISB = 0; // PORTB设为输出(显示ADC高字节) TRISD = 0; // PORTD设为输出(显示ADC低字节) TRISC.F2 = 0; // CCP1引脚设为输出(PWM输出端) // ADC初始化(仅执行一次) ADCON0 = 0x81; // Fosc/64分频,选择AN0通道,使能ADC ADCON1 = 0xCE; // 结果右对齐,仅AN0为模拟输入,其余为数字IO // PWM初始化 PR2 = 156; // 设置PWM周期((156+1)*4*Tosc*4 ≈10ms,基于10MHz晶振) CCP1CON = 0x0C; // 配置CCP1为PWM模式 T2CON = 0x02; // TMR2预分频4 TMR2 = 0; // 重置TMR2计数器 T2CON.TMR2ON = 1;// 启动TMR2 int adc_value; while(1) { adc_value = rd_adc(); // 将10位ADC值映射到10位PWM占空比 CCPR1L = adc_value >> 2; // 取ADC值高8位给CCPR1L // 更新CCP1CON的低2位(CCP1X/CCP1Y) CCP1CON = (CCP1CON & 0xF0) | ((adc_value & 0x03) << 4); // 等待TMR2溢出,确保占空比更新同步到PWM周期 PIR1.TMR2IF = 0; while(!PIR1.TMR2IF); } }
修复说明
- ADC初始化移至main:避免每次转换重置ADC,解决高电压启动时的无响应问题。
- 完整处理10位PWM:同时设置
CCPR1L和CCP1CON的低2位,确保占空比从0到100%完整映射,解决高电压输入时的平线问题。 - 整数运算替代浮点数:直接通过位操作完成10位到10位的映射,消除精度损失。
- 冗余代码移除:去掉
rd_adc中重复的端口方向设置,优化代码逻辑。
Proteus仿真额外检查点
- 确认MCU型号与代码匹配(如PIC16F877A),ADCON1配置与模拟通道对应。
- CCP1引脚需串联220Ω电阻后连接LED,根据引脚电平极性选择LED连接方式(共阳/共阴)。
- 当ADC输入满量程5V时,PWM输出为100%占空比,LED应常亮而非无反应。
内容的提问来源于stack exchange,提问作者Zerox
相关产品推荐
相关产品推荐

