Using the ADCC peripheral on PIC18FxxQ43 devices to read their Temperature

Started by top204, Mar 29, 2024, 07:08 PM

Previous topic - Next topic

top204

When I did a little demo on the main forum concerning the ADCC peripheral on the newer PIC18F46Q43 device, I came across a built in temperature sensor mechanism in its datasheet that I had never noticed before, but it has been with some devices for quite a few years now, so I read up on it using the microchip app notes. However, the app notes are actually, mostly, incorrect and as hard as I tried to use them, they just did not work correctly!

So I set about reading the datasheet on how the on-board temperature sensor actually worked, and wrote some code to use it. The one important thing the app notes did not mention was the gain and offset values held in the device's DIA (Device Information Area), which are absolutely necessary for the temperature sensor to operate. It was the device's datasheet that mentioned them, and where they were located in flash memory (see section 9 in the PIC18FxxQ43 datasheet).

The temperature section of the device's datasheet (section 39) gave a rough workout of how the temperature sensor works and some pseudo code to make it read a temperature in Celcius, which the app notes seem to have mis-interpreted and forgot to add to their texts and code listings. So I created a demonstration program based upon the pseudo code, and eleborated on it for the Positron8 compiler. With the Positron8 compiler, the code is easy to understand and follow, and even the assembler code produced from the BASIC code listing is a delight to view. :-)

The code listing is below to read the FVR's (Fixed Voltage Reference) temperature sensor using the ADCC peripheral, and it transmits the temperature in both Celcius and Fahrenheit to a serial terminal. It also reads the ADCC as normal on pin AN0, to show that it can still operate as an ADC, as well as read the temperature:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' A demonstration of using the FVR and ADCC peripherals on PIC18FxxQ43 devices to read their temperature
' Written for the Positron8 BASIC compiler by Les Johnson
'
    Device = 18F26Q43                                   ' Tell the compiler what device to compile for
    Declare Xtal = 64                                   ' Tell the compiler what frequency the device is operating at (in MHz)
'
' Setup USART1
'
    Declare Hserial_Baud = 9600
    Declare HRSOut1_Pin  = PORTC.6
'
' ADCC channel values to measure other peripherals
'
$define cADCC_Chan_VSS         $3B                      ' Connects the ADCC's input to Gnd
$define cADCC_Chan_Temperature $3C                      ' Connects the ADCC's input to the temperature reading from the FVR peripheral
$define cADCC_Chan_DAC1        $3D                      ' Connects the ADCC's input to the output voltage of DAC1
$define cADCC_Chan_FVR_Buffer1 $3E                      ' Connects the ADCC's input to the FVR buffer 1
$define cADCC_Chan_FVR_Buffer2 $3F                      ' Connects the ADCC's input to the FVR buffer 2
'
' Create any global variables here
'
    Dim wTSHR1_Value As Word                            ' Holds the temperature Gain value for High Range, read from DIA memory
    Dim wTSHR3_Value As Word                            ' Holds the temperature Offset value for High Range, read from DIA memory
    Dim wTempCel     As SWord                           ' Holds the device's temperature in Celcius
    Dim wTempFah     As SWord                           ' Holds the device's temperature in Fahrenheit
    Dim wADC_AN0     As Word                            ' Holds the raw ADC value

'-------------------------------------------------------------------------------------
' The main program starts here
' Read the ADCC peripheral and transmit its temperature value to a serial terminal
' Also read the ADC on pin AN0, to show that the ADC can also operate as normal
'
Main:
    Setup()                                             ' Setup the program and any peripherals

    Do                                                  ' Create a loop
        wTempCel = ADCC_GetTempCelc()                   ' Read the device's temperature in Celcius
        wTempFah = ADCC_GetTempFahr()                   ' Read the device's temperature in Fahrenheit
        HRSOutLn "Temperature is ", SDec wTempCel, "°C : ", ' \
                                    SDec wTempFah, "°F"     ' / Transmit the temperature values to a serial terminal
        wADC_AN0 = ADCC_Read12(0)                       ' Read the ADC from pin AN0
        HRSOutLn "AN0 = ", Dec wADC_AN0                 ' Transmit the ADC value to a serial terminal
        DelayMS 2000                                    ' A delay to see things changing
    Loop                                                ' Do it forever

'-------------------------------------------------------------------------------------
' Read the device's temperature in Celcius using the ADCC and FVR peripherals
' Input     : None
' Output    : Returns a signed integer value that represents the temperature in Celcius
' Notes     : According to the PIC18FxxQ43 datasheet, the temperature can range from -40°C to +125°C
'           : Has a resolution of 1 degree
'           : For use with a PIC18FxxQ43 device
'
Proc ADCC_GetTempCelc(), SWord
    Dim dTempIn_C     As SDword                         ' Holds the temperature calculation
    Dim wADC_Value    As dTempIn_C.SWord0               ' Holds the ADC's raw value
    Dim bADCON2_Store As Byte Heap = ADCON2             ' Store a copy of the value held in ADCON2
    Dim bADREF_Store  As Byte Heap = ADREF              ' Store a copy of the value held in ADREF
    Dim bADCON0_Store As Byte Heap = ADCON0             ' Store a copy of the value held in ADCON0
'
' Initialise the ADCC peripheral for temperature sensing mode
'
    ADCON2 = %00010000                                  ' Setup the ADCC for basic mode to match a standard ADC peripheral
    ADREF = %00000011                                   ' -Vref is VSS. +Vref is FVR
    ADCON0bits_ADCS = 1                                 ' ADCS is Frc
    wADC_Value = ADCC_Read12(cADCC_Chan_Temperature)    ' Read the ADCC channel for temperature
'
' Put the ADCC peripheral back to its original settings
'
    ADCON2 = bADCON2_Store                              ' Restore the value of ADCON2
    ADREF = bADREF_Store                                ' Restore the value of ADREF
    ADCON0 = bADCON0_Store                              ' Restore the value of ADCON0

    dTempIn_C = (wADC_Value * wTSHR1_Value) / 256       ' Multiply the ADC result by the DIA Gain and divide by 256
    Result = (dTempIn_C + wTSHR3_Value) / 10            ' Add the DIA Offset and divide it by 10
EndProc

'-------------------------------------------------------------------------------------
' Read the device's temperature in Fahrenheit using the ADCC and FVR peripherals
' Input     : None
' Output    : Returns an integer value that represents the temperature in Fahrenheit
' Notes     : Has a resolution of 1 degree
'
Proc ADCC_GetTempFahr(), SWord
    Result = ADCC_GetTempCelc()                         ' Read the temperature in Celcius
    Result = (Result * 18) + 320                        ' Convert Celcius to Fahrenheit using integer maths
    Result = Result / 10                                ' Divide the result by 10
EndProc

'-------------------------------------------------------------------------------------
' Setup the program and any peripherals
' Input     : None
' Output    : None
' Notes     : For use with a PIC18FxxQ43 device
'
Proc Setup()
    FVR_Init()                                          ' Initialise the FVR peripheral so the ADCC can read it for the temperature
    ADCC_InitBasic(64)                                  ' Initialise the ADCC peripheral in basic mode, and use a fOsc/64 clock for it
    wTSHR1_Value = Read_DIA_16($2C002A)                 ' Read the temperature Gain for High Range from DIA memory
    wTSHR3_Value = Read_DIA_16($2C002E)                 ' Read the temperature Offset for High Range from DIA memory
    ANSELA.0 = 1                                        ' Make pin AN0 analogue
EndProc

'-------------------------------------------------------------------------------------
' Initialise the FVR peripheral for temperature sensing
' Input     : None
' Output    : None
' Notes     : FVR is enabled
'           : TSEN is enabled
'           : TSRNG is High Range
'           : FVR Buffer 1 is 2x (2.048 volts)
'           : FVR Buffer 2 is off
'           : For use with a PIC18FxxQ43 device
'
Proc FVR_Init()
    FVRCON = %10110010
EndProc

'-------------------------------------------------------------------------------------
' Initialise the ADCC peripheral in basic mode using ADCLK
' Input     : pClock holds the clock to use fro the ADCC peripheral (2 to 128, in steps of 2)
'           : If pClock holds 0, the FRC clock will be used
' Output    : None
' Notes     : Setup the ADCC for standard ADC mode
'           : Initialised for 12-bit operation
'           : Oscillator set for clock, where:
'               ADCLK Value is (Division / 2) - 1
'               ADC Clock Frequency = FOSC/(2 * (ADCLK Value + 1))
'           : -Vref as Gnd, +Vref as VDD
'           : Setups are for a PIC18FxxQ43 device
'
Proc ADCC_InitBasic(pClock As Byte)
    ADLTHL = $00
    ADLTHH = $00
    ADUTHL = $00
    ADUTHH = $00
    ADSTPTL = $00
    ADSTPTH = $00
    ADACCU = $00
    ADRPT = $00
    ADPCH = $00
    ADACQL = $00
    ADACQH = $00
    ADCAP = $00
    ADPREL = $00
    ADPREH = $00
    ADCON1 = $00
    ADCON2 = %00010000                      ' Setup the ADCC for basic mode to match a standard ADC peripheral
    ADCON3 = $00
    ADSTAT = $00
    ADREF = %00000000                       ' -Vref is VSS. +Vref is VDD
    ADACT = $00
    If pClock = 0 Then                      ' Is pClock 0?
        ADCLK = %00000000                   ' Yes. So clear ADCLK because it is not used
        ADCON0 = %10010100                  ' ADCS is Frc. Right justified for 12-bit operation. ADC is enabled
    Else                                    ' Otherwise... Use a clock ratio for the oscillator
        ADCLK = (pClock / 2) - 1            ' Calculate the clock frequency the ADCC peripheral will use
        ADCON0 = %10000100                  ' ADCS is ADCLK. Right justified for 12-bit operation. ADC is enabled
    EndIf
EndProc

'-------------------------------------------------------------------------------------
' Read the ADC in 12-bit mode
' Input     : pChannel holds the ADC channel to read
' Output    : Returns the 12-bit ADC reading
' Notes     : None
'
Proc ADCC_Read12(pChannel As Byte), Word
    Dim wADRES As ADRESL.Word               ' Create a 16-bit SFR from ADRESL and ADRESH

    ADPCH = cADCC_Chan_VSS                  ' Discharges the ADCC peripheral's sampling capacitor before taking a sample
    ADPCH = pChannel                        ' Select the ADC's Channel
    ADCON0bits_ADGO = 1                     ' Start the conversion
    DelayCS 2                               ' A tiny delay and clear the watchdog timer if it is enabled
    Repeat: Until ADCON0bits_ADGO = 0       ' Wait for the conversion to finish
    Result = wADRES                         ' Return the ADC reading
EndProc

'-------------------------------------------------------------------------------------
' Read a 16-bit value from flash memory
' Input     : pAddress holds the 24-bit address
' Output    : Returns the 16-bit value read from flash memory
' Notes     : For use with a PIC18FxxQ43 device
'
Proc Read_DIA_16(pAddress As Long), SWord
    Dim lTbPtr As TBLPTRL.Long                          ' Create a 24-bit SFR from SFRs TBLPTRL\H\U

    lTbPtr = pAddress                                   ' Load SFRs TBLPTRL\H\U with the address to read
    Tblrd*+                                             ' Read a byte from flash memory into TABLAT and increment the address
    Result.Byte0 = TABLAT                               ' Transfer the contents of TABLAT into the low byte of the return variable
    Tblrd*                                              ' Read a byte from flash memory into TABLAT
    Result.Byte1 = TABLAT                               ' Transfer the contents of TABLAT into the high byte of the return variable
    TBLPTRU = 0                                         ' Clear TBLPTRU before exiting
EndProc

'-------------------------------------------------------------------------------------
' Set the config fuses for internal oscillator, running at 64MHz, with the OSC pins as I/O pins
' For use with a PIC18FxxQ43 device
'
Config_Start
    FEXTOSC = Off                                   ' External Oscillator not enabled
    RSTOSC = HFINTOSC_64MHZ                         ' HFINTOSC with HFFRQ = 64 MHz and CDIV = 1:1
    CLKOUTEN = Off                                  ' CLKOUT function is disabled
    PR1WAY = Off                                    ' PRLOCKED bit can be set and cleared repeatedly
    CSWEN = On                                      ' Writing to NOSC and NDIV is allowed
    FCMEN = On                                      ' Fail-Safe Clock Monitor enabled
    MCLRE = EXTMCLR                                 ' RE3 pin's function is MCLR
    PWRTS = PWRT_OFF                                ' Power-up timer is disabled
    MVECEN = Off                                    ' Interrupt contoller does not use vector table to prioritze interrupts
    IVT1WAY = Off                                   ' IVTLOCKED bit can be cleared and set repeatedly
    LPBOREN = On                                    ' Low Power BOR enabled
    BOREN = SBORDIS                                 ' Brown-out Reset enabled. SBOREN bit is ignored
    BORV = VBOR_1P9                                 ' Brown-out Reset Voltage (VBOR) set to 1.9V
    ZCD = Off                                       ' ZCD module is disabled. ZCD can be enabled by setting the ZCDSEN bit of ZCDCON
    PPS1WAY = Off                                   ' PPSLOCKED bit can be set and cleared repeatedly (subject to the unlock sequence)
    STVREN = On                                     ' Stack full/underflow will cause Reset
    LVP = Off                                       ' Low Voltage Programming disabled
    XINST = Off                                     ' Extended Instruction Set and Indexed Addressing Mode disabled
    WDTCPS = WDTCPS_31                              ' WDT Period Divider ratio 1:65536; software control of WDTPS
    WDTE = Off                                      ' WDT Disabled; SWDTEN is ignored
    WDTCWS = WDTCWS_7                               ' WDT Window always open (100%), software control and keyed access not required
    WDTCCS = SC                                     ' WDT input clock Software Control
    BBSIZE = BBSIZE_512                             ' Boot Block size is 512 words
    BBEN = Off                                      ' Boot block disabled
    SAFEN = Off                                     ' Storage Area Flash disabled
    Debug = Off                                     ' Background Debugger disabled
    WRTB = Off                                      ' Boot Block not Write protected
    WRTC = Off                                      ' Configuration registers not Write protected
    WRTD = Off                                      ' Data EEPROM not Write protected
    WRTSAF = Off                                    ' SAF not Write Protected
    WRTAPP = Off                                    ' Application Block not write protected
    Cp = Off                                        ' PFM and Data EEPROM code protection disabled
Config_End

According to the datasheet, the temperatures it can handle range from -40 degrees Celcius to +125 degrees Celcius, but I have not gone to those ranges. However, I have used freezer spray and got it down to -20 degrees Celcius, and heated the PIC device with my soldering iron so it is hot. :-)

The code uses integer mathematics to keep the code memory usage low and the operation faster, but even Floating Point maths did not give a high resolution from the temperature sensor, so the above code has a resolution of one degree. This should be adequate for general temperature measuerments of the device's surroundings, and the device's temperature itself.

Below is a screenshot of the above program operating on a PIC18F26Q43 device sitting in my Amicus18 board, and transmitting the temperature and ADC data to the serial terminal:

Device_Temperature.jpg