PDA

View Full Version : Weird ADC / Math Results with 9-Bit PWM



RossWaddell
- 21st February 2016, 02:37
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 (http://www.picbasic.co.uk/forum/showthread.php?t=16187&p=112225#post112225) 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:

8190



' 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.

8191



' 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?

HenrikOlsson
- 21st February 2016, 08:29
Hi,
Try switching the left/right justification and see if the makes it any better.

/Henrik.

tumbleweed
- 21st February 2016, 11:57
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
' 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.

RossWaddell
- 21st February 2016, 13:11
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
' 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.

RossWaddell
- 21st February 2016, 14:02
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.



' 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

tumbleweed
- 21st February 2016, 14:57
The problem is with this statement:
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

RossWaddell
- 21st February 2016, 16:13
That explains it, thanks very much. When I do the math by hand the 255's cancel each other out and then it's just 412+100.

If I put brackets around the dividing part will that fix it?

tumbleweed
- 21st February 2016, 20:54
If I put brackets around the dividing part will that fix it?
That'll truncate the division, leaving you with 412/255 = 1

You can use PBPL mode so that it uses LONGs, which should work but it'll be larger and slower if that matters. Otherwise your best bet might be to turn the equation into a lookup table.
To be honest, I don't use PBP much, so maybe others can suggest something else.

RossWaddell
- 21st February 2016, 23:26
Thanks again tumbleweed. I can't use PBPL without moving up to a 18F chip, and the minimum # of pins is then 18 (I only need two outputs so an 8-pin chip is ideal. Also, the PCB is pretty cramped now as it is).

But a lookup table could be tricky given the 0-255 range of the 8-bit ADC input but a PWM duty cycle range of 512. Maybe I should switch to 10-bit ADC and just divide by 2?

Dave
- 22nd February 2016, 12:06
Why not look at using the DIV32 command?

tumbleweed
- 22nd February 2016, 13:00
That's an interesting operation. Splitting the equation up into two parts for DIV32 seems to work...
LEDBrVal = ((compVal - 0) * (MaxDuty - MinDuty))
LEDBrVal = DIV32 (MaxADCVal - 0) + MinDuty
Is it safe to do something like that or would you suggest making the DIV32 statement simpler so that you know it immediately follows the multiplication (I don't know what the OP's intent for the " - 0" part of the above is for)?

Dave
- 22nd February 2016, 19:11
Yes, The way you have it is OK. Just remenber that the result of the first multiplication before the DIV32 is to a dumby variable and it won't be usefull after the DIV32 function. It should be sized as a word.

RossWaddell
- 22nd February 2016, 20:19
That's an interesting operation. Splitting the equation up into two parts for DIV32 seems to work...
LEDBrVal = ((compVal - 0) * (MaxDuty - MinDuty))
LEDBrVal = DIV32 (MaxADCVal - 0) + MinDuty
Is it safe to do something like that or would you suggest making the DIV32 statement simpler so that you know it immediately follows the multiplication (I don't know what the OP's intent for the " - 0" part of the above is for)?

I grabbed this scaling function from an Arduino library and was emulating that:



' 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;
' }


In general, the minimum brightness level is 0. But in this case, I only want to adjust the LED brightness between 50-100% duty cycle, so maybe I need to tweak that part as well.

RossWaddell
- 22nd February 2016, 20:20
Yes, The way you have it is OK. Just remenber that the result of the first multiplication before the DIV32 is to a dumby variable and it won't be usefull after the DIV32 function. It should be sized as a word.

Thanks Dave and tumbleweed! I've never had to use DIV32 before so I appreciate the explicit examples.

RossWaddell
- 22nd February 2016, 23:15
Everything works just fine now. The last thing I need to do is introduce linearization correction to get a better, more linear fade in. I think the info in this (http://www.picbasic.co.uk/forum/showthread.php?t=16187&p=112225#post112225) thread should help.

Question: if the time it takes to calculate LEDBrVal is too long with DIV32, and I want to move to either using LOOKUP or a data table, can I have 2 data tables in one program?

RossWaddell
- 23rd February 2016, 00:42
Tried this but it doesn't fade in the LED and I never get anything on the LCD screen after the counter to 100:



' 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

' Array size = 100
' Total PWM steps: 512
' Gamma correction: 1.35
' # of values per line: 25

'-----[The data table]--------------------------------------------------------
DataWord VAR WORD
DataTable CON EXT
GOTO OverData ; Make sure data doesn't try to execute
ASM
DataTable
DW 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2
DW 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 8, 8, 9, 9, 10, 11, 11
DW 12, 13, 14, 15, 16, 17, 18, 20, 21, 23, 24, 26, 28, 30, 33, 35, 38, 41, 44, 47, 51, 55, 59, 64, 69
DW 74, 80, 86, 93,101,109,118,128,138,150,162,176,190,206,224,243 ,263,286,310,337,366,398,433,471,511
endasm
OverData:
'-----[Continue with code]----------------------------------------------------

' ************************************************** *************
' 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 75 ; Minimum brightness for this application
PWMDuty VAR WORD ; Duty cycle variable for PWM

' Fade in LED to set brightness
FOR i = 0 to 100 ; must match ARRAY SIZE of data table above
ReadCODE (DataTable + i), DataWord
PWMDuty = DataWord

GOSUB ChngLEDBrightness

pause 1
NEXT i

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

Main:
PAUSE 100

GOTO Main

'*********** Set duty registers ****************************************
ChngLEDBrightness:
CCP1CON.4 = PWMDuty.0
CCP1CON.5 = PWMDuty.1
CCPR1L = PWMDuty >> 2

RETURN

richard
- 23rd February 2016, 01:36
the fade will take 100mS what do you expect to see ?


' Fade in LED to set brightness
FOR i = 0 to 100 ; must match ARRAY SIZE of data table above
ReadCODE (DataTable + i), DataWord
PWMDuty = DataWord

GOSUB ChngLEDBrightness

pause 1
NEXT i


this is the only write to the lcd
what else do you expect ?


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


the code is doing exactly what it should do , not what you hoping for maybe

RossWaddell
- 23rd February 2016, 01:49
I expect something to show on the LCD, but it stays blank.

EDIT: I moved the LCD instructions to the loop to see what I could see and the final PWDuty value is 12291, way more than 512. The LED now ramps up in brightness but then drops down once the duty exceeds 512. I also needed to add a pause after the LCD initialization in order to see anything on the screen).



FadeInPause CON 5 ; Pause during LED fade in

' Fade in LED to set brightness
FOR i = 0 to 100 ; must match ARRAY SIZE of data table above
ReadCODE (DataTable + i), DataWord
PWMDuty = DataWord

GOSUB ChngLEDBrightness

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

pause FadeInPause
NEXT i


Where the heck is it getting 12291?

richard
- 23rd February 2016, 02:10
FOR i = 0 to 100
there is only 100 entries in the table
0 -----to --- 99

RossWaddell
- 23rd February 2016, 03:33
there is only 100 entries in the table
0 -----to --- 99
Ugh. Of course. Now it works perfectly, with a final PWM duty cycle of 511.

RossWaddell
- 26th February 2016, 17:10
In case anyone is interested in the final working code (using linearization correction):



' 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

' The PWM/fade works perfectly when **not** connected to a FET
' - if using a FET, the LEDs 'pulse' and blink but don't fade in/out
' - use a 2N2222A transistor

' ************************************************** *************
' 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

' Values generated by C:\Users\Ross\Dropbox\PBP Projects\PBP3 and MCSPX Software\LEDGammaCorrectGenerator.xlsm
' Array size = 100
' Total PWM steps: 512
' Gamma correction: 0.75 (try values in the range of 0.5 - 2.0)
' # of values per line: 15

'-----[The data table]--------------------------------------------------------
DataWord VAR WORD
numSteps CON 100
DataTable CON EXT
GOTO OverData ; Make sure data doesn't try to execute
ASM
DataTable
DW 0, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4
DW 5, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 11, 12, 12
DW 13, 14, 15, 16, 17, 18, 19, 20, 22, 23, 24, 26, 27, 29, 31
DW 32, 34, 36, 38, 41, 43, 45, 48, 51, 54, 57, 60, 63, 66, 70
DW 74, 78, 82, 87, 91, 96,101,107,112,118,124,131,138,145,152
DW 160,168,177,186,196,205,216,227,238,250,263,276,28 9,304,319
DW 334,351,368,386,404,424,445,466,488,511
endasm
OverData:
'-----[Continue with code]----------------------------------------------------

' 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 = 512; must match max PWM steps from data
; table above

PWMDuty VAR WORD ; Duty cycle variable for PWM

MaxADCVal CON 255 ; 255 for 8-bit; 1023 for 10-bit

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

LEDBrIndx VAR WORD

i VAR WORD

LCDPause CON 5 ; Pause for LCD display
FadeInPause VAR BYTE ; Pause during LED fade in
#IFDEF USE_LCD_FOR_DEBUG
FadeInPause = 12
#ELSE
FadeInPause = 12 + LCDPause
#ENDIF

' ************************************************
' Get Vref from ADC to set brightness step index
' ************************************************
GOSUB Do_ADC
compVal = ADCInVal ; set initial compare value
GOSUB Map_ADCInVal_to_PWM_Steps

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

' Fade in LED to set brightness
FOR i = 0 to (LEDBrIndx - 1) ; max is 'numSteps' from data table above
ReadCODE (DataTable + i), DataWord
PWMDuty = DataWord

GOSUB ChngLEDBrightness

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

pause FadeInPause
NEXT i

Main:
GOSUB Do_ADC
PAUSE 100

If (compVal > (ADCInVal + 1)) or (compVal < (ADCInVal - 1)) THEn
compVal = ADCInVal
GOSUB Map_ADCInVal_to_PWM_Steps

ReadCODE (DataTable + (LEDBrIndx - 1)), DataWord
PWMDuty = DataWord

gosub ChngLEDBrightness

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

Do_ADC:
' Stuff 16 Element WORD Array full of ADC values
' ----------------------------------------------
For CounterA = 0 to (16 - 1)
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 < (16 - 1) 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

return

'*********** Map Vref to PWM step **************************************
Map_ADCInVal_to_PWM_Steps:
' 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;
' }

' Need to split up the operations to get full value using DIV32
LEDBrIndx = ((compVal - 0) * (numSteps - numSteps/2))
LEDBrIndx = DIV32 (MaxADCVal - 0) + numSteps/2

RETURN

'*********** Set duty registers ****************************************
ChngLEDBrightness:
CCP1CON.4 = PWMDuty.0
CCP1CON.5 = PWMDuty.1
CCPR1L = PWMDuty >> 2

RETURN


I've also attached my Microsoft Excel workbook which calculates the LED linearization:
8195

Dave
- 28th February 2016, 13:20
Ross, Attached is the Gamma table I came up with some years ago. Enjoy...