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:
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.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
But if I try to implement more discrete PWM steps as discussed in the thread linked above, I get weird results.
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.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
Can anyone help steer me onto the right path?





Bookmarks