Weird ADC / Math Results with 9-Bit PWM


Closed Thread
Results 1 to 22 of 22

Hybrid View

  1. #1

    Default Weird ADC / Math Results with 9-Bit PWM

    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: 734
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: 662
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?

  2. #2
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,610


    Did you find this post helpful? Yes | No

    Default Re: Weird ADC / Math Results with 9-Bit PWM

    Hi,
    Try switching the left/right justification and see if the makes it any better.

    /Henrik.

  3. #3
    Join Date
    Aug 2011
    Posts
    453


    Did you find this post helpful? Yes | No

    Default Re: Weird ADC / Math Results with 9-Bit PWM

    I think the justification is ok... if you want to use the ADC in 8-bit mode then you want it to be left-justified (ADFM=0)
    and just read the ADRESH register, disgarding the lower bits in ADRESL.

    I think the problem is in the section that averages the readings
    Code:
    '	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
    Here DataW must be a WORD since you're adding 8 byte values together which could give you 8 x 255 = 2040.

  4. #4


    Did you find this post helpful? Yes | No

    Default Re: Weird ADC / Math Results with 9-Bit PWM

    Quote Originally Posted by tumbleweed View Post
    I think the justification is ok... if you want to use the ADC in 8-bit mode then you want it to be left-justified (ADFM=0)
    and just read the ADRESH register, disgarding the lower bits in ADRESL.

    I think the problem is in the section that averages the readings
    Code:
    '	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
    Here DataW must be a WORD since you're adding 8 byte values together which could give you 8 x 255 = 2040.
    Thanks tumbleweed. I think I had that as a WORD earlier, but I will try it again today.

  5. #5


    Did you find this post helpful? Yes | No

    Default Re: Weird ADC / Math Results with 9-Bit PWM

    Reset DataW to WORD variable (as it should have been all along) as well as the two ADC variables (since it's left-justified or 8-bit) but am still only getting a max value of 254 from the Map_ADCInVal_to_PWM_Duty function. The ADC is now correctly getting ranges of 0-255, but the LEDBrVal is capped at 254 (should be 512). Also, when I move the trim pot wiper the LED doesn't change brightness as smoothly as with PR2=24; it kinda flickers a bit.

    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_SP_CMD CON 124       ' special command character (for adjusting backlight brightness or toggling splash screen)
        LCD_BR_LVL CON 140       ' 140==40%
        LCD_SPLASH CON 9         ' toggles splash screen display
    #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 BYTE [16] ' Array holding ADC Result
    
    LEDBrVal       VAR WORD
    
    i              VAR WORD
    
    ' Should only need to do this one time to adjust backlight brightness
    '#IFDEF USE_LCD_FOR_DEBUG
    '    HSEROUT [LCD_SP_CMD, LCD_BR_LVL]
    '    PAUSE 5
    '#ENDIF
    
    ' Should only need to do this one time to disable splash screen
    '#IFDEF USE_LCD_FOR_DEBUG
    '    HSEROUT [LCD_SP_CMD, LCD_SPLASH]
    '    PAUSE 5
    '#ENDIF
    
    #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
    
    ' ***************************************************************
    ' 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

  6. #6
    Join Date
    Aug 2011
    Posts
    453


    Did you find this post helpful? Yes | No

    Default Re: Weird ADC / Math Results with 9-Bit PWM

    The problem is with this statement:
    Code:
    LEDBrVal = (compVal - 0) * (MaxDuty - MinDuty)/(MaxADCVal - 0) + MinDuty
    If compVal = 255 then that evaluates to:
    LEDBrVal = 255 * 412/255 + 100

    255 * 412 = 105060, which is larger than a word (which has a max of 65535)
    105060 is $19a64, so the upper portion gets tossed out and you're left with $9a64 = 39524

    LEDBrVal = 39524/255 + 100
    LEDBrVal = 154 + 100
    LEDBrVal = 254

Similar Threads

  1. Weird PWM Behaviour on 16F1825
    By RossWaddell in forum mel PIC BASIC Pro
    Replies: 14
    Last Post: - 26th October 2012, 21:59
  2. Averaging 16 bit values without using 32 bit math
    By sirvo in forum mel PIC BASIC Pro
    Replies: 2
    Last Post: - 5th October 2007, 22:18
  3. Strangw results when using PWM command
    By malc-c in forum mel PIC BASIC Pro
    Replies: 20
    Last Post: - 10th July 2006, 12:14
  4. Strange Results in Math
    By CocaColaKid in forum mel PIC BASIC Pro
    Replies: 6
    Last Post: - 31st August 2005, 07:09
  5. PBP 16-bit ADC result math
    By sonic in forum mel PIC BASIC Pro
    Replies: 0
    Last Post: - 13th March 2005, 14:21

Members who have read this thread : 0

You do not have permission to view the list of names.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts