Measuring Temperature With The Raspberry Pico

Measuring temperature with the Pico is one of the easiest “quick wins” for getting familiar with the platform. Along with turning the onboard LED on and off, you can achieve something that feels like interaction with the real world with nothing more than a few lines of Micropython due to the Pico’s onboard temperature sensor.

A quick search for some example code brought up:

import machine
import utime
 
sensor_temp = machine.ADC(4)
conversion_factor = 3.3 / (65535)
 
while True:
    reading = sensor_temp.read_u16() * conversion_factor 
    temperature = 27 - (reading - 0.706)/0.001721
    print(temperature)
    utime.sleep(2)

It works by measuring differences in internal voltage that vary by temperature. A better explanation, and the reason for all the maths, can be found on the same page as the code.

So far so good, until I looked at the output, which was a couple of degrees warmer than the Hive radiator thermostat that I was sitting right next to. It wasn’t beyond the realms of possibility that Hive was wrong, or was measuring a very localised temperature (although I had no reason to think so).

Some more searching raised doubts about the accuracy of the Pico, and the variability between different boards. I tried a couple of others and got similar results, all around the same range, and all warmer than Hive was telling me. I was more inclined to believe the Hive thermostat anyway, especially as it didn’t feel all that warm at the time.

A bit more searching and it appears that voltage variability can be a big factor in these calculations. Basically the conversion_factor above takes the Pico voltage at 3.3v, which it more-or-less is, but if this varies by even the smallest amount then the temperature can be out by a couple of degrees.

It seemed likely that I’d found my culprit, especially since I had the Pico plugged into a USB hub connected by USB-C to the PC, which was turning AC into DC voltage anyway. There were a number of places where a few millivolts could get lost.

A Temperature Measurement Comparison

I decided to experiment with some other measurement systems, including:

  • A very basic analog temperature sensor, the TMP36
  • A TMP225 analog temperature sensor
  • A Monk Makes plant monitor, which has its own analog to digital convertor onboard (and does things other than just temperature)

They look like this respectively:

A wooden thermometer

I can’t tell if there’s much difference in principle between the first two TMP sensors, beyong one being on a board, but they were both about the same price.

And for comparison I have a wooden thermometer, which I hope should be accurate enough. Plus I still have the trusty Hive radiator thermostat.

To cut a long story short, I ended up with all three components connected to the Pico at once, outputting a temperature on a polled basis. They were very simple to connect up and find code examples for. The wooden thermometer would just have to sit on its own.

And the results? These are averaged over 10 readings to smooth out voltage fluctuations after leaving running for a few minutes:

Hive radiator valve21.1°C
Wooden thermometerJust over 21°C
Pico onboard sensor21.9°C
TMP3623.0°C
TMP23523.4°C
Monk Makes plant monitor22.6°C

Maybe I’m too willing to accept an answer that happens to be believable, but to my eyes the fact that the thermometer agrees with the professionally constructed Hive radiator valve points to the four Pico measurements all being out by some degree. Reading a few more posts on forums certainly seems to indicate that none of these temperature sensors are going to be really accurate with a variable input voltage, especially since the Pico and the two TMP models explicitly need the 3.3v figure in the temperature calculation.

Changing The Temperature Calculation Formula

For all three except the Monk Makes plant monitor the voltage figure is in the temperature calculation equation. If the voltage is less than 3.3v then output temperature will show as higher than actual. The equation looks like this:

adc_value = adc2.read_u16()
volt = adc_value * (3.3 / 65535)
temperature = 100 * volt - 50

Changing the 3.3 figure to something a little lower has a substantial effect on temperature. adc_value is generally quite low and therefore the voltages are quite low – somewhere between .5v (which would be 0°C), and .8v at 30°C. In the equation above a voltage change of just .01V equates to a temperature variance of 1°C.

For the purposes of “calibration” I’m going to assume that the only thing I need to change is the value of the input voltage. And hence if I amend the above code for the TMP36 sensor to:

adc_value = adc2.read_u16()
volt = adc_value * (3.21 / 65535)
temperature = 100 * volt - 50

then I start to get about the same temperature as Hive and the thermometer are telling me, with a tenth of a degree anyway. Similarly, 3.20v seems to work for the TMP225.

For the Monk Makes sensor I just scale down the temperature it tells me, by 0.925.

Next Steps

The results all seem to be plausible now, but it’s not very scientific. Plus, I’ve only tested it at a very narrow temperature range. If I want to put sensors in a greenhouse I’ll need to check that the linear scale assumptions I’ve made above still hold.

Secondly, I’m assuming that it’s the voltage that’s the culprit. I have a multimeter ordered so I can test what’s actually being passed to the sensors and that’ll hopefully tell me if my estimate of 3.2v is correct or if the inaccuracy is somewhere else in the system.

Thirdly, this has been powered by a USB hub, which is much noisier than a smooth DC power source (like a battery). I did a quick experiment with a USB battery power pack and got similar readings, but there’s no real way to know if that’s outputting exactly 5v or not.

The good news? The cheap and cheerful TMP36 seems to be just as reliable (or unreliable) as any of the other options.

Playing Simple Sounds With The Pico And Adafruit STEMMA Speaker

I’m starting to get more confident with putting components together with the Pico, so without too much research I bought a simple speaker/amplifier breakout board. Still, given how every time I try something simple on the Pico it turns out to involve hours of Googling I assumed that getting a speaker to work would be more fiddly than it should be.

But, au contraire, it turned out to be remarkably easy. Just three cables and not even a dozen lines of code (stolen by Googling, of course…) produced a simple note:

A Raspberry Pico connected to a Adafruit STEMMA Speaker by three crodocile clips

With this code:

from machine import Pin, PWM
from utime import sleep

SPEAKER_PIN = 21

# create a Pulse Width Modulation Object on this pin
speaker = PWM(Pin(SPEAKER_PIN))
# set the duty cycle to be 50%
speaker.duty_u16(1000)
speaker.freq(1000) # 50% on and off
sleep(1) # wait a second
speaker.duty_u16(0)
# turn off the PWM circuits off with a zero duty cycle
speaker.duty_u16(0)

Despite really haven’t done nothing except copy and paste I was proud of the beep that emitted from the small speaker.

The next challenge: to play an audio file, of any format. Back to Google I went, and after some dead ends (due to Micropython changing over the previous five years) I settled on a library called PicoAudioPWM by Daniel Perron.

A bit of MP3 to WAV mangling later, and this code produced a very ropey sound:

from machine import Pin
import wave
from wavePlayer import wavePlayer

try:
    player = wavePlayer()
    player.play('clip.wav')
    player.stop()
except Exception as ex:
    Pin(2, Pin.OUT, Pin.PULL_DOWN)

Pin(2, Pin.OUT, Pin.PULL_DOWN)

I changed the output pin from 21 in the simple beep experiment to 2 here, as that’s the default the library uses.

For a reason that I expect is specific to the speaker I had to add a Pin PULL_DOWN after playback was complete otherwise the audio was followed by constant hissing from the speaker, even after the program had stopped running.

The sound quality was pretty awful, but hey-ho: at least I got noise out of the thing.

Now to go and read about what “Pulse Width Modulation” might be…