You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于PIC18F47-Q18开发板的10位ADC信号转8位PWM输出适配问题

基于PIC18F47-Q18开发板的10位ADC信号转8位PWM输出适配问题

看起来你遇到的核心问题是10位ADC值到8位PWM的适配逻辑错误,再加上代码里几个关键的配置/逻辑bug,导致LED亮度循环四次。我来一步步帮你梳理和解决:

问题根源分析

  1. ADC到PWM的缩放错误:
    10位ADC的输出范围是01023,而8位PWM占空比寄存器(`PWM4DCH`)的范围是0255。你直接把ADRESL(10位值的低8位)赋值给PWM4DCH,相当于把10位值拆成了4个256的区间,每个区间的PWM值重复,所以LED会四次循环亮灭——这就是你看到的现象。

  2. ADC转换等待逻辑完全写反:
    你代码里的while (ADCON0bits.GO ==0);是错误的!GO/DONE位在转换开始时被置1,转换完成后自动清0。正确的等待逻辑应该是等待GO位为1的状态结束,也就是while (ADCON0bits.GO == 1);。你现在的代码会在转换还没完成时就读取ADC值,得到的都是旧数据或错误值。

  3. Timer2被反复开关,导致PWM输出中断:
    PWM模块依赖Timer2作为时基,必须让Timer2持续运行才能输出稳定的PWM波形。你在主循环里反复启停Timer2,会让PWM输出断断续续,进一步加剧LED的异常闪烁。

  4. 可能的ADC对齐位设置错误:
    你提到ADC是右对齐,但代码里ADCON0 = 0x84,查PIC18F47-Q18手册可知,ADCON0的bit1是ADFM(ADC结果对齐位),1是右对齐,0是左对齐。0x84是0b10000100,ADFM位是0——这意味着你实际设置的是左对齐,和你描述的ADRESL/ADRESH的存储逻辑完全相反,这会导致ADC值读取完全错误!

解决方案步骤

1. 修正ADC配置与读取逻辑

  • 确保ADC右对齐:把ADCON0设置为0x86(0b10000110),这样ADFM=1,ADRESL存储低8位,ADRESH存储高2位,符合你的预期。
  • 修正转换等待逻辑:改成等待GO位为1的状态结束。

2. 正确缩放10位ADC值到8位PWM

把10位ADC值(01023)缩放为8位PWM值(0255)最高效的方法是右移2位(等价于除以4,因为1023/4≈255.75,刚好对应8位的最大值255)。代码实现可以是:

// 合并10位ADC值
uint16_t adc_val = ((uint16_t)ADRESH << 8) | ADRESL;
// 缩放为8位PWM值
PWM4DCH = adc_val >> 2;

或者直接用位运算(更高效,适合单片机):

PWM4DCH = (ADRESL >> 2) | (ADRESH << 6);

这样当电位器转到最小时(ADC值0),PWM占空比0,LED最暗;电位器转到最大时(ADC值1023),PWM占空比255,LED最亮,完全符合你的需求。

3. 让Timer2持续运行

在初始化代码里打开Timer2(T2CONbits.T2ON = 1),之后不要再在主循环里反复开关它。PWM需要稳定的时基才能输出连续波形。

修正后的完整代码

// CONFIG1L
#pragma config FEXTOSC = OFF
#pragma config RSTOSC = HFINTOSC_64MHZ

// CONFIG1H
#pragma config CLKOUTEN = OFF
#pragma config CSWEN = OFF
#pragma config FCMEN = OFF

// CONFIG3L
#pragma config WDTCPS = WDTCPS_31
#pragma config WDTE = OFF

#define clock OSCCON1           // define the timer control bits
#define freq OSCFRQ

#define enable_timer T2CONbits.T2ON         // define the timer control bits
#define interrupt_enable PIE4bits.TMR2IE 
#define PB1 PORTBbits.RB4
#define PB2 PORTCbits.RC5
#define light PORTAbits.RA7

void main(void) {   
    clock = 0x60;               // use High Mhz clock
    freq = 0x03;                // Use 8Mhz clock
    
    T2CLKCON = 0x01;            // Make Timer2 Fosc/4
    
    ANSELA = 0x01;              // set up port A,B,C D/A
    TRISA = 0x01;               // set up port A,B,C I/O
    ANSELB = 0x00;   
    TRISB = 0xFF;   
    ANSELC = 0x00;   
    TRISC = 0xFF;   
  
    T2PR = 0xC7;                // period of the PWM
    PWM4DCH = 0x00;             // 初始占空比0
    PWM4DCL = 0x00;             // 8位PWM的话,PWM4DCL可以忽略(因为只用到高8位)
    RA4PPS = 0x08;              // set RA4 to  be PWM output            
    T2CON = 0x20;               // set pre scale value of 1:4
    PWM4CONbits.PWM4EN = 1;     // enable the PWM
    PWM4CONbits.POL = 1;        // reverse Polarity of PWM
    enable_timer = 1;           // 初始化时就打开Timer2,持续运行

    while (1)
    {
        ADCON0 = 0x86;                  // ADC使能 + 右对齐(ADFM=1) + CHS=0
        ADPCH = 0x00;                   // Selected channel is RA0       
        ADCON0bits.GO = 1;              // start ADC conversion
        while (ADCON0bits.GO == 1);     // 等待转换完成(GO位从1变0)
       
        // 缩放10位ADC值到8位PWM
        uint16_t adc_val = ((uint16_t)ADRESH << 8) | ADRESL;
        PWM4DCH = adc_val >> 2;
       
        ADCON0bits.ADON = 0;            // Turn off ADC enable bit
    }
    return;
}

额外说明

  • 关于你尝试的合并ADC值再赋值的代码:PWM4DCH = (ADRESH<<8 | ADRESL); 是错误的,因为PWM4DCH是8位寄存器,16位值会被自动截断低8位,和直接赋值ADRESL效果完全一样,所以解决不了问题。
  • 后续改用中断的思路是对的,等你熟悉中断后,可以把ADC转换放在ADC完成中断里,把Timer2溢出中断作为PWM的时基,这样代码会更高效。

备注:内容来源于stack exchange,提问作者Ivan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 10:28:05