In an attempt to save an I/O pin originally used for the DHT11 to measure ambient temperature in one of my projects, I attempted to do the same using the dsPIC Charge Time Measurement Unit (CTMU) instead. After studying the dsPIC33EP512MC502 datasheet as well as Microchip’s DS61167B/TB3016 documents on how the CTMU can be used to measure the PIC’s die temperature, I tried to search for sample codes but could not find any useful code snippets. Attempts to post question on the Microchip forum would only result in replies from unfriendly folks telling me to read the manual and referring me back to the datasheet. Well, it took me a day to come up with an implementation that works, which I will share here for those who are interested.
The theory behind the measurement of temperature is described in Microchip’s TB3016 as well as this link. Granted, this is the PIC’s core temperature and not the ambient temperature which is measured by the DHT11, but unless the PIC is running hot, both measurements should approximate each other. Basically, the process involves connecting a diode to one of the PIC’s A/D pins, applying a constant current source using the CTMU, and finally calculating the temperature from the voltage across the diode. For the dsPIC, the diode is built-in so all you need to do is to write codes to activate the CTMU as well as the ADC in order to perform the measurements. The following code shows how to initialize the ADC (modify it to suit your needs):
void initADC()
{
AD1CON1bits.ADON = 0;
AD1CSSH = 0;
AD1CSSL = 0;
AD1CON1bits.AD12B = 1;
AD1CON1bits.SSRCG = 0; AD1CON1bits.SSRC = 0b111;
AD1CON1bits.ADSIDL = 1;
AD1CON1bits.ASAM = 0; AD1CON1bits.SAMP = 0;
AD1CON2bits.VCFG = 0; AD1CON2bits.CSCNA = 0;
AD1CON2bits.ALTS = 0; AD1CON2bits.BUFM = 0;
AD1CON3bits.SAMC = 0b11111; AD1CON3bits.ADCS = 255; AD1CON3bits.ADRC = 1; AD1CON1bits.ADON = 1; }
Although the datasheet
recommends using 10-bit ADC mode as not all PICs have the diode connected in 12-bit, on my dsPIC33EP512MC502
, the CTMU works well (and is more accurate) in 12-bit ADC mode. I would still recommend you to use 10-bit ADC first and only switch to 12-bit once you are sure everything is working.
After initializing the ADC, the next step is to do the same for the CTMU and the integrated diode:
void initCTMU()
{
CTMUCON1bits.CTMUEN = 0;
CTMUCON1bits.CTMUSIDL = 1; CTMUCON1bits.TGEN = 0; CTMUCON1bits.EDGEN = 0; CTMUCON1bits.EDGSEQEN = 0; CTMUCON1bits.CTTRIG = 0;
CTMUCON2bits.EDG1STAT = 1; CTMUCON2bits.EDG2STAT = 1;
CTMUICONbits.IRNG = 0b11; }
Next, we need to retrieve the CTMU temperature readings. The following function returns the temperature between -128°C to 127°C, accurate to 1°C:
char getSingleCTMUTemperature()
{
AD1CHS0bits.CH0SA = 0b11110; AD1CHS0bits.CH0NA = 0; CTMUCON1bits.CTMUEN = 1; AD1CON1bits.FORM = 0;
CTMUCON1bits.IDISSEN = 1; delay_us(50);
CTMUCON1bits.IDISSEN = 0;
AD1CON1bits.SAMP = 1;
unsigned char c = 0;
while (AD1CON1bits.DONE == 0)
{
if (c > 200)
{
debugPrint("CTADErr");
return 0;
}
c++;
}
unsigned int adcVal = ADC1BUF0;
double VF = adcVal * (3.3 / 4095); double ctmuTemp = 25.0 + ( (VF - 0.721) / (-1.56) ) * 1000.0;
debugPrint("CTM:%dC(%04X) OK:%d", (int)ctmuTemp, adcVal, isCTMUOK);
CTMUCON1bits.CTMUEN = 0;
return (char)(ceilf(ctmuTemp));
}
The while
loop at the beginning is needed to ensure the code waits for the ADC conversion to complete. The formula is taken from here. Use 1023 (10-bit ADC) or 4095 (12-bit ADC) for ADC_STEPS
. Section “Electrical Characteristics” of the datasheet
provides the diode rate of change and forward voltage. The values we need are 0.721V and -1.56mV/°C at 25°C:
With the above code, you should be able to get a sensible temperature reading such as 31°C from the CTMU. But all is not said and done yet. Ignoring the fact that the dsPIC might run hot resulting in measured temperature being higher than ambient temperature, which is not expected to happen in my product, a few tests revealed that the readings were much more unstable than the DHT11. One reading could be 31°C, the next could be 28°C and the next could be 32°C. Occasionally, there might be a value as low as 25°C or as high as 35°C! This was most likely due to noises on the VCC line which is also used as VREF for the ADC. The noises cannot be suppressed alone with decoupling capacitors (I tried very hard). Because of the 1000 multiplication factor in the formula, a single ADC count could result in a temperature shift of 2 or 3 degrees! Although I could use an external VREF for more stable ADC measurements, this defeated the purposes as I might as well use the DHT11 instead. After some considerations, I decided to fix the problem by taking multiple readings, sorting them in ascending order and averaging out only the middle readings. This effectively removes outliers and returns the average value of measurements within a certain confidence interval. The following sample codes show how to do this by taking 50 measurements and returning the average of the middle 30 values:
char getAvgCTMUTemperature()
{
#define MAX_CTMU_COUNT 50
#define MAX_CTMU_COUNT_5TH (MAX_CTMU_COUNT / 5)
char allVals[MAX_CTMU_COUNT];
char temp;
unsigned char c1, c2;
double avgVal;
for (c1 = 0; c1 < MAX_CTMU_COUNT; c1++)
{
allVals[c1] = getSingleCTMUTemperature();
}
for (c1 = 0; c1 < MAX_CTMU_COUNT - 1; c1++)
for (c2 = c1 + 1; c2 < MAX_CTMU_COUNT; c2++)
{
if (allVals[c1] > allVals[c2])
{
temp = allVals[c1];
allVals[c1] = allVals[c2];
allVals[c2] = temp;
}
}
avgVal = 0;
for (c1 = MAX_CTMU_COUNT_5TH; c1 < 4 * MAX_CTMU_COUNT_5TH; c1++)
{
avgVal += allVals[c1];
}
avgVal = ceilf(avgVal / (3 * MAX_CTMU_COUNT_5TH));
temp = (char)avgVal;
debugPrint("CTMU: %dC", temp);
return temp;
}
With this implementation, the returned temperature appeared to be much more stable. During my tests within 15 minutes, the returned values were consistently 28°C or 29°C, with no excessively high or low values. Still, the reading-to-reading shift, even if single degree e.g. from 28°C to 29°C, might not be good enough for some users. I worked around this by not updating the display if the temperature only changes by one degree, unless a significant amount of time has lapsed (e.g. more than 5 minutes). For my product, this implementation is good enough.
Another issue that I observed is that, if the PIC has been kept in storage for a long time, then the first CTMU reading immediately after startup might read rather high or low. For example, if the ambient temperature is 29°C, the reading might be 21°C or 35°C. This issue does not happen very often but once it happens, the measurement will stay consistent for 2-3 minutes before becoming normal again, presumably after the PIC has warmed up. I am not sure what the issue is, but I suspect that characteristics of the diode might change slightly after not being used for a long time causing the hard-coded parameters (rate of change and forward voltage) to be no longer valid. It could also be due to my LT1763 voltage regulator momentarily not being able to provide exactly 3.3V on the VCC line which is used as VREF. In any case, the inaccuracies do not seem to appear if the PIC is constantly in use and is therefore not a major issue for me. Regardless, I would recommend using DHT11 or some other dedicated temperature sensor chip if you have enough I/O pins in your design. The CTMU should only be used to measure temperature at a last resort.