I'm trying to use 8-bit ADC to control the brightness of a LED via PWM. The goal is to have a smooth fade in to the brightness set by the trim pot so I eventually want to introduce linearization correction as outlined in this thread, but for now I just want to see it working without that.

If I use the 1:16 Pre-scaler with PR2 = 24 values from Mister E's wonderful Multicalc, everything works fine:

Name:  PR2_24.png
Views: 649
Size:  8.7 KB

Code:
' For production version of this code, comment out the line below re:
' LCD debugging and also update config fuses to have code protect on
#DEFINE USE_LCD_FOR_DEBUG     ; comment out for non-debug use

' ***************************************************************
' Pin Connections
' ***************************************************************

' VDD    -> pin 1            -> +5V
' RA5/Rc -> pin 2            -> EUSART receive
' RA2    -> pin 5            -> 1kohm -> 2N2222A transistor -> LEDs
' RA1    -> pin 6            -> Trim pot input
' RA0/Tx -> pin 7            -> EUSART transmit (LCD)
' VSS    -> pin 8            -> GND

DEFINE OSC 16               ; Set oscillator 16Mhz

' ***************************************************************
' EUSART Settings for Tx/Rc (e.g. LCD)
' ***************************************************************

' > use Mister E PIC Multi-Calc application to get register/DEFINE settings
' > as the values are dependent on the OSC and desired baud rate

DEFINE HSER_RCSTA 90h      ' Enable serial port & continuous receive
DEFINE HSER_TXSTA 24h      ' Enable transmit, BRGH = 1
DEFINE HSER_CLROERR 1      ' Clear overflow automatically
DEFINE HSER_SPBRG 160      ' 9600 Baud @ 16MHz, -0.08%

' ***************************************************************
' Device Fuses
' ***************************************************************
' PIC chip data sheets can be found here: C:\Program Files\Microchip\MPASM Suite

#CONFIG
   __config _CONFIG1, _FOSC_INTOSC & _WDTE_ON & _PWRTE_ON & _MCLRE_OFF & _CP_OFF & _CPD_OFF
   __config _CONFIG2, _PLLEN_OFF & _STVREN_ON & _BORV_LO & _LVP_OFF
#ENDCONFIG

' ***************************************************************
' Initialization
' ***************************************************************

OSCCON    = %01111000        ' 16MHz internal osc
pause 100                    

APFCON.2  = 0                ; Tx on RA0 for LCD display
APFCON.7  = 1                ; Rc on RA5
APFCON.0  = 0                ; CCP1 on RA2

' Some LCD serial modules need inverted data, some do not
' Enable the line below if needed, but for SparkFun SerLCD it should be
' commented out

'BAUDCON.4 = 1               ; Transmit inverted data to the Tx pin  

' From Mister E's Multi-Calc for EUSART:
' *****************************************************************
SPBRGH    = 1
BAUDCON.3 = 1                ' Enable 16 bit baudrate generator
' *****************************************************************

TRISA     = %00100010	     ' Make all pins output except for RA1 (trim pot input)
                             ' and RA5 (EUSART Rc)
ANSELA    = %00000010        ; Analog on PORTA.1 (AN1) only

FVRCON    = 0                ' Fixed Voltage Reference is disabled
ADCON0    = %00000101        ' ADC (analog-to-digital) is enabled on AN1 (RA1) only
PAUSEUS 20                   ; wait for the analog switch 'glitch' to die down
ADCON1    = %00110000        ; Left-justified results in 8-bits; Frc as timer

#IFDEF USE_LCD_FOR_DEBUG
    LCD_INST   CON 254       ' instruction
    LCD_CLR    CON 1         ' Clear screen
    LCD_L1     CON 128       ' LCD line 1
    LCD_L2     CON 192       ' LCD line 2
    LCD_BR_CMD CON 124       ' command character for adjusting backlight brightness
    LCD_BR_LVL CON 140       ' 140==40%
#ENDIF

ADCInVal       VAR BYTE      ; stores ADCIN result read from trim pot
compVal        VAR BYTE      ; stores last-changed ADC value

CounterA       var BYTE		 ' Just a BYTE Temporary working variable
DataW          var WORD		 ' Just a WORD Temporary working variable
RawData        var WORD [16] ' Array holding ADC Result

LEDBrVal       VAR BYTE

i              VAR BYTE

#IFDEF USE_LCD_FOR_DEBUG
    pause 1000
    HSEROUT [LCD_INST, LCD_CLR, "LCD Init"]
    pause 5
    HSEROUT [LCD_INST, LCD_L2, "  SerLCD_V2_5"]
    pause 2000
#ENDIF

' Should only need to do this one time to adjust backlight brightness
'#IFDEF USE_LCD_FOR_DEBUG
'    HSEROUT [LCD_BR_CMD, LCD_BR_LVL]
'    PAUSE 5
'#ENDIF

' ***************************************************************
' Set up PWM on CCP1
' ***************************************************************

CCP1CON = %00001100          ; Use CCP1 in PWM mode

' Set duty cycle registers initially to 0
CCP1CON.4 = 0
CCP1CON.5 = 0
CCPR1L    = 0

' Use Mister E's PICMultiCalc_1.3.1.exe application (Windows only)   
' to determine prescaler and PR2 values for given OSC frequency (e.g. 16Mhz)
' and duty cycle (use 100% to see highest actual value)

' ************************
' CCP1 uses TMR2
' ************************
                       
T2CON   = %00000110         ; Timer2 on with 1:16 prescaler
PR2     = 24                ; For 16Mhz OSC the desired output freq of 10,000Hz
                            ; is achieved with this PR2 value (8-bit resolution
                            ; with 1:16 prescaler)
                            ; MAX DUTY = 100
                            
MaxDuty       VAR BYTE      ; According to Darrel:
                            ;   MaxDuty = (PR2 + 1) * 4

MaxDuty = (PR2 + 1) * 4     ; 100
MinDuty       CON 25        ; Minimum brightness for this application
MaxADCVal     CON 255       ; 255 for 8-bit; 1023 for 10-bit

FadeInPause   CON 100       ; Pause during LED fade in

GOSUB Do_ADC
compVal = ADCInVal          ; set initial compare value
GOSUB Map_ADCInVal_to_PWM_Duty

#IFDEF USE_LCD_FOR_DEBUG
    HSEROUT [LCD_INST, LCD_CLR]
    pause 5
    HSEROUT ["LEDBrVal=",DEC LEDBrVal,"  "]
    pause 500
#ENDIF

' Fade in LED to set brightness
FOR i = 0 to LEDBrVal
    CCP1CON.4 = i.0
    CCP1CON.5 = i.1
    CCPR1L    = i >> 2 
            
    pause FadeInPause

    #IFDEF USE_LCD_FOR_DEBUG
        HSEROUT [LCD_INST, LCD_CLR]
        pause 5
        HSEROUT ["i=",DEC i,"  "]
    #ENDIF
NEXT i


Main:
    GOSUB Do_ADC
    PAUSE 100

    If (compVal > (ADCInVal + 1)) or (compVal < (ADCInVal - 1)) THEn
        compVal = ADCInVal
        GOSUB Map_ADCInVal_to_PWM_Duty
        gosub ChngLEDBrightness

        #IFDEF USE_LCD_FOR_DEBUG
            HSEROUT [LCD_INST, LCD_CLR]
            pause 5
            HSEROUT ["new ADCInVal", LCD_INST, LCD_L2, DEC ADCInVal, "  "]
        #ENDIF
    ENDIF
GOTO Main

Do_ADC:
'	Stuff 16 Element WORD Array full of ADC values
'	----------------------------------------------
	For CounterA = 0 to 15
        ADCON0    = %00000101 ' Select Channel, Turn-On A/D
						      '	7=0 Unused
						      '	6=0 Unused
						      '	5=0 )
						      '	4=0 )
						      '	3=0 ) selects AN1
						      '	2=0 )
						      '	1=0 Go-done Bit
						      '	0=1 switch-On ADC Module
		Pauseus 50			     ' Wait for channel to setup
        ADCON0.1 = 1			 ' Start conversion
		While ADCON0.1=1:Wend	 ' Wait for conversion
'		DataW.HighByte=ADRESH	 
'		DataW.LowByte=ADRESL
        DataW = ADRESH           ' Read variable from ADC and save
		RawData(CounterA) = DataW

	Next CounterA

'	Sort ADC Input
'	--------------
	CounterA = 0
 
 GetADCSortLoop:

	If RawData(CounterA + 1) < RawData(CounterA) then
		DataW = RawData(CounterA)
		RawData(CounterA) = RawData(CounterA+1)
		RawData(CounterA+1) = DataW
		If CounterA > 0 then CounterA = CounterA - 2
	endif
	CounterA=CounterA+1
	If CounterA < 15 then goto GetADCSortLoop

'	Quanticise, discarding top and bottom FOUR elements
'	----------------------------------------------------
	DataW = 0
	For CounterA = 4 to 11
		DataW = DataW+RawData(CounterA)
	Next CounterA
	
    ADCInVal = DataW>>3			' Divide Result by EIGHT


'    PAUSEUS 50              ' Wait for A/D channel acquisition time
'    ADCON0.1 = 1            ' Start conversion
    
'    WHILE ADCON0.1 = 1      ' Wait for it to complete
'    WEND

'    ADCInVal = ADRESH
return

'*********** Map Vref to duty range ************************************
Map_ADCInVal_to_PWM_Duty:
'   Arduino Map function to emulate:
'   ===============================
'   map(value, fromLow, fromHigh, toLow, toHigh)

'   long map(long x, long in_min, long in_max, long out_min, long out_max)
'   {
'     return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
'   }

'   TODO: If switching from onboard trim pot to ext trim pot and there isn't one
'         one connected to the 3-pin connector, may need to make in_min greater 
'         than 0

    LEDBrVal = (compVal - 0) * (MaxDuty - MinDuty)/(MaxADCVal - 0) + MinDuty
'    LEDBrVal = (compVal ** 39322) + MinDuty  ' Same as 100 + VrefInValue * 0.600006 but likely faster than 100+VrefInValue*6/10
    
'    #IFDEF USE_LCD_FOR_DEBUG
'        HSEROUT [LCD_INST, LCD_CLR]
'        pause 5
'        HSEROUT ["compVal=",DEC compVal,"  ",LCD_INST, LCD_L2]
'        pause 500
'        HSEROUT ["MotorDuty=",DEC MotorDuty,"  "]
'    #ENDIF

RETURN

'*********** Set duty registers ****************************************
ChngLEDBrightness:
    CCP1CON.4 = LEDBrVal.0
    CCP1CON.5 = LEDBrVal.1
    CCPR1L    = LEDBrVal >> 2
    
RETURN
With the above, if I have the trim pot wiper arm all the way to the right it fades in the LED to 100 duty cycle and I can vary the brightness via the duty cycle by changing the trim pot (Min: 25, Max: 100) via the Map_ADCInVal_to_PWM_Duty function.

But if I try to implement more discrete PWM steps as discussed in the thread linked above, I get weird results.

Name:  PR2_127.png
Views: 583
Size:  8.2 KB

Code:
' For production version of this code, comment out the line below re:
' LCD debugging and also update config fuses to have code protect on
#DEFINE USE_LCD_FOR_DEBUG     ; comment out for non-debug use

' ***************************************************************
' Pin Connections
' ***************************************************************

' VDD    -> pin 1            -> +5V
' RA5/Rc -> pin 2            -> EUSART receive
' RA2    -> pin 5            -> 1kohm -> 2N2222A transistor -> LEDs
' RA1    -> pin 6            -> Trim pot input
' RA0/Tx -> pin 7            -> EUSART transmit (LCD)
' VSS    -> pin 8            -> GND

DEFINE OSC 16               ; Set oscillator 16Mhz

' ***************************************************************
' EUSART Settings for Tx/Rc (e.g. LCD)
' ***************************************************************

' > use Mister E PIC Multi-Calc application to get register/DEFINE settings
' > as the values are dependent on the OSC and desired baud rate

DEFINE HSER_RCSTA 90h      ' Enable serial port & continuous receive
DEFINE HSER_TXSTA 24h      ' Enable transmit, BRGH = 1
DEFINE HSER_CLROERR 1      ' Clear overflow automatically
DEFINE HSER_SPBRG 160      ' 9600 Baud @ 16MHz, -0.08%

' ***************************************************************
' Device Fuses
' ***************************************************************
' PIC chip data sheets can be found here: C:\Program Files\Microchip\MPASM Suite

#CONFIG
   __config _CONFIG1, _FOSC_INTOSC & _WDTE_ON & _PWRTE_ON & _MCLRE_OFF & _CP_OFF & _CPD_OFF
   __config _CONFIG2, _PLLEN_OFF & _STVREN_ON & _BORV_LO & _LVP_OFF
#ENDCONFIG

' ***************************************************************
' Initialization
' ***************************************************************

OSCCON    = %01111000        ' 16MHz internal osc
pause 100                    

APFCON.2  = 0                ; Tx on RA0 for LCD display
APFCON.7  = 1                ; Rc on RA5
APFCON.0  = 0                ; CCP1 on RA2

' Some LCD serial modules need inverted data, some do not
' Enable the line below if needed, but for SparkFun SerLCD it should be
' commented out

'BAUDCON.4 = 1               ; Transmit inverted data to the Tx pin  

' From Mister E's Multi-Calc for EUSART:
' *****************************************************************
SPBRGH    = 1
BAUDCON.3 = 1                ' Enable 16 bit baudrate generator
' *****************************************************************

TRISA     = %00100010	     ' Make all pins output except for RA1 (trim pot input)
                             ' and RA5 (EUSART Rc)
ANSELA    = %00000010        ; Analog on PORTA.1 (AN1) only

FVRCON    = 0                ' Fixed Voltage Reference is disabled
ADCON0    = %00000101        ' ADC (analog-to-digital) is enabled on AN1 (RA1) only
PAUSEUS 20                   ; wait for the analog switch 'glitch' to die down
ADCON1    = %00110000        ; Left-justified results in 8-bits; Frc as timer

#IFDEF USE_LCD_FOR_DEBUG
    LCD_INST   CON 254       ' instruction
    LCD_CLR    CON 1         ' Clear screen
    LCD_L1     CON 128       ' LCD line 1
    LCD_L2     CON 192       ' LCD line 2
    LCD_BR_CMD CON 124       ' command character for adjusting backlight brightness
    LCD_BR_LVL CON 140       ' 140==40%
#ENDIF

ADCInVal       VAR WORD      ; stores ADCIN result read from trim pot
compVal        VAR WORD      ; stores last-changed ADC value

CounterA       var BYTE		 ' Just a BYTE Temporary working variable
DataW          var BYTE		 ' Just a WORD Temporary working variable
RawData        var BYTE [16] ' Array holding ADC Result

LEDBrVal       VAR WORD

i              VAR WORD

'#IFDEF USE_LCD_FOR_DEBUG
'    pause 1000
'    HSEROUT [LCD_INST, LCD_CLR, "LCD Init"]
'    pause 5
'    HSEROUT [LCD_INST, LCD_L2, "  SerLCD_V2_5"]
'    pause 2000
'#ENDIF

' Should only need to do this one time to adjust backlight brightness
'#IFDEF USE_LCD_FOR_DEBUG
'    HSEROUT [LCD_BR_CMD, LCD_BR_LVL]
'    PAUSE 5
'#ENDIF

' ***************************************************************
' Set up PWM on CCP1
' ***************************************************************

CCP1CON = %00001100          ; Use CCP1 in PWM mode

' Set duty cycle registers initially to 0
CCP1CON.4 = 0
CCP1CON.5 = 0
CCPR1L    = 0

' Use Mister E's PICMultiCalc_1.3.1.exe application (Windows only)   
' to determine prescaler and PR2 values for given OSC frequency (e.g. 16Mhz)
' and duty cycle (use 100% to see highest actual value)

' ************************
' CCP1 uses TMR2
' ************************
                       
T2CON   = %00000110         ; Timer2 on with 1:16 prescaler
PR2     = 127               ; For 16Mhz OSC the desired output freq of 1954Hz
                            ; is achieved with this PR2 value (9-bit resolution
                            ; with 1:16 prescaler)
                            ; MAX DUTY = 100
                            
MaxDuty       VAR WORD      ; According to Darrel:
                            ;   MaxDuty = (PR2 + 1) * 4

MaxDuty = (PR2 + 1) * 4     ; 100
MinDuty       CON 100       ; Minimum brightness for this application
MaxADCVal     CON 255       ; 255 for 8-bit; 1023 for 10-bit

FadeInPause   CON 50        ; Pause during LED fade in

GOSUB Do_ADC
compVal = ADCInVal          ; set initial compare value
GOSUB Map_ADCInVal_to_PWM_Duty

#IFDEF USE_LCD_FOR_DEBUG
    HSEROUT [LCD_INST, LCD_CLR]
    pause 5
    HSEROUT ["LEDBrVal=",DEC LEDBrVal,"  "]
    pause 750
#ENDIF

' Fade in LED to set brightness
FOR i = 0 to LEDBrVal
    CCP1CON.4 = i.0
    CCP1CON.5 = i.1
    CCPR1L    = i >> 2 
            
    pause FadeInPause

    #IFDEF USE_LCD_FOR_DEBUG
        HSEROUT [LCD_INST, LCD_CLR]
        pause 5
        HSEROUT ["i=",DEC i,"  "]
    #ENDIF
NEXT i


Main:
    GOSUB Do_ADC
    PAUSE 100

    If (compVal > (ADCInVal + 1)) or (compVal < (ADCInVal - 1)) THEn
        compVal = ADCInVal
        GOSUB Map_ADCInVal_to_PWM_Duty
        gosub ChngLEDBrightness

        #IFDEF USE_LCD_FOR_DEBUG
            HSEROUT [LCD_INST, LCD_CLR]
            pause 5
            HSEROUT ["new ADCInVal", LCD_INST, LCD_L2, DEC ADCInVal, "  "]
        #ENDIF
    ENDIF
GOTO Main

Do_ADC:
'	Stuff 16 Element WORD Array full of ADC values
'	----------------------------------------------
	For CounterA = 0 to 15
        ADCON0    = %00000101 ' Select Channel, Turn-On A/D
						      '	7=0 Unused
						      '	6=0 Unused
						      '	5=0 )
						      '	4=0 )
						      '	3=0 ) selects AN1
						      '	2=0 )
						      '	1=0 Go-done Bit
						      '	0=1 switch-On ADC Module
		Pauseus 50			     ' Wait for channel to setup
        ADCON0.1 = 1			 ' Start conversion
		While ADCON0.1=1:Wend	 ' Wait for conversion
'		DataW.HighByte=ADRESH	 
'		DataW.LowByte=ADRESL
        DataW = ADRESH           ' Read variable from ADC and save
		RawData(CounterA) = DataW

	Next CounterA

'	Sort ADC Input
'	--------------
	CounterA = 0
 
 GetADCSortLoop:

	If RawData(CounterA + 1) < RawData(CounterA) then
		DataW = RawData(CounterA)
		RawData(CounterA) = RawData(CounterA+1)
		RawData(CounterA+1) = DataW
		If CounterA > 0 then CounterA = CounterA - 2
	endif
	CounterA=CounterA+1
	If CounterA < 15 then goto GetADCSortLoop

'	Quanticise, discarding top and bottom FOUR elements
'	----------------------------------------------------
	DataW = 0
	For CounterA = 4 to 11
		DataW = DataW+RawData(CounterA)
	Next CounterA
	
    ADCInVal = DataW>>3			' Divide Result by EIGHT


'    PAUSEUS 50              ' Wait for A/D channel acquisition time
'    ADCON0.1 = 1            ' Start conversion
    
'    WHILE ADCON0.1 = 1      ' Wait for it to complete
'    WEND

'    ADCInVal = ADRESH
return

'*********** Map Vref to duty range ************************************
Map_ADCInVal_to_PWM_Duty:
'   Arduino Map function to emulate:
'   ===============================
'   map(value, fromLow, fromHigh, toLow, toHigh)

'   long map(long x, long in_min, long in_max, long out_min, long out_max)
'   {
'     return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
'   }

'   TODO: If switching from onboard trim pot to ext trim pot and there isn't one
'         one connected to the 3-pin connector, may need to make in_min greater 
'         than 0

    LEDBrVal = (compVal - 0) * (MaxDuty - MinDuty)/(MaxADCVal - 0) + MinDuty
'    LEDBrVal = (compVal ** 39322) + MinDuty  ' Same as 100 + VrefInValue * 0.600006 but likely faster than 100+VrefInValue*6/10
    
'    #IFDEF USE_LCD_FOR_DEBUG
'        HSEROUT [LCD_INST, LCD_CLR]
'        pause 5
'        HSEROUT ["compVal=",DEC compVal,"  ",LCD_INST, LCD_L2]
'        pause 500
'        HSEROUT ["MotorDuty=",DEC MotorDuty,"  "]
'    #ENDIF

RETURN

'*********** Set duty registers ****************************************
ChngLEDBrightness:
    CCP1CON.4 = LEDBrVal.0
    CCP1CON.5 = LEDBrVal.1
    CCPR1L    = LEDBrVal >> 2
    
RETURN
With this code, I only see ADC values ranging from 0-31. The Map_ADCInVal_to_PWM_Duty function seems to only give me a max of 150, whereas it should be 512.

Can anyone help steer me onto the right path?