Monitoring the temperature of a Raspberry Pi has become quite a popular project to create, probably from most of us spending years dealing with hot power-hungry Windows machines that required constant attention.
Whilst the Pi is both less expensive and less prone to temperature issues, myself and many others just can’t resist finding new ways to keep an eye on the temperature levels. It’s certainly peace of mind for existing projects such as VPN, webservers etc where the Pi is on 24/7.
I previously wrote a bit of code to show the Pi’s temperature on a 16×2 LCD screen, but this time my project is lot more visible, uses minimal GPIO pins, and is also able to be removed very easily.
In this post I’ll show you how to use 7-segment displays in conjunction with the MCP23017 to create a very cool temperature monitor. In Part 2 I’ll complete the project using an EZasPi board to house all the parts in a tidy removable package.
What you need
- A large pack of male to male jumpers (if using a breakout board) OR male to female jumpers (if wiring direct to the Pi)
- A large breadboard (or 2 pushed together)
- 24 x 560 Ohm resistors (get 100 on eBay for £1)
- 3 x Common Cathode 7-segment displays (rated around 20mA per segment)
- 2 x MCP23017 IC chips
As with most projects, there are a few potential risks and things you should know:
Pi Power Limitations
The Pi’s 3.3v output is limited to 50 milliamps. Considering each of the LED segments can take up around 20mA, I decided to use the 5v connection instead. (another option is to use resistors that would limit the current below this 50mA limit)
The MCP23017 has limits as well. Here are the specs:
- Maximum current out of VSS pin – 150 mA
- Maximum current into VDD pin – 125 mA
So if you want to remain within the designed operating limits, you have to plan around all 24 of the LEDs not exceeding 125mA in totality – that’s around 5mA per LED segment, with a bit of room for error as not all LED segments will be on at once.
Assuming I’m right, 560 Ohm resistors is what you need for that – as per the solution here:
You could create the same effect by multiplexing, but I’m going for the simple method in this tutorial.
Build the prototype
This is one of those prototypes that needs a video – there are far too many wires involved! So here it is, a nice long video with step-by-step instructions:
Some points in this video get very messy with wires – but once you get going, and get familiar with the chip pin layout, it gets very easy. I’ve been playing with the MCP23017 for a while now and it’s a great IC to have ready in your mental toolbox.
Check the ICs are recognised
So, you’ve got it all wired up right?
Before we run a test script for the LEDs, let’s take a second to check that the MCP23017 chips are being recognised at least. If you copied my wiring, the following command should show addresses 0x20 and 0x21:
(I’m using a Rev1 board – use 1 instead of 0 if you have a Rev2 board)
sudo i2cdetect -y 0
[email protected] ~ $ sudo i2cdetect -y 0 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: 20 21 -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- --
All good? let’s continue…
Test the Prototype
All that wiring doesn’t mean squat if it doesn’t work. Time to get some code on the go and see if this thing works.
I created a Python script to test if I had the segments wired correctly, to help me later on when working out how to create numbers on the segment displays. It goes through each individual segment, for each IC and display, showing you where each IC output is being fed to. It’s good to learn!
Here’s a link to the full code, but I’ll go through each part of it to show you how it all works:
After the initial blurb, the first part of the code looks like this, which is just importing the required modules for this script:
#Imports import smbus import sys import getopt import time import os
Then we set up smbus – part of the ‘brains’ that controls the I2C chips:
#Set up SMBus bus = smbus.SMBus(0) #for revision 2 board...use 1
Next we set the MCP23017 addresses in the script. We already know what they are from the previous step (0x20 and 0x21). We have ‘address1’ as 0x20 and ‘address2’ as 0x21 – aka chip 1 on the left, and chip 2 on the right:
#Set the I2C address address1 = 0x20 (Address pins all to GND) address2 = 0x21 (First 2 address pins to GND, last address pin to 5v)
Now we need to set the 16 I/O ports on each chip to outputs (because these chips can be used as either inputs or outputs – or even a mixture of both). Notice we are already using the addresses we set in the last step:
# Set both banks to outputs bus.write_byte_data(address1,0x00,0x00) bus.write_byte_data(address1,0x01,0x00) bus.write_byte_data(address2,0x00,0x00) bus.write_byte_data(address2,0x01,0x00)
Next we set some names to reference each bank on the MCP chips. This allows us to direct the code to use a particular segment display:
#Bank mapping bank1 = 1 #Port A on 0x20 bank2 = 2 #Port B on 0x20 bank3 = 3 #Port A on 0x21
Then, this section makes our lives a bit easier later on by making the ‘set_led’ command (i’m sure my terminology is wrong here). It says “When the code finds ‘set_led’, take the first number in the following brackets as the I/O pin number, and take the second number as the chip and bank this I/O is to come from”
For example, set_led(1,bank1) would light the first LED on the first MCP23017 chip (because bank1 is related to ‘address1’ here:
#More Bank Mapping def set_led(data,bank): if bank == 1: bus.write_byte_data(address1,0x12,data) #Bank 1 = MCP port A if bank == 2: bus.write_byte_data(address1,0x13,data) #Bank 2 = MCP port B if bank == 3: bus.write_byte_data(address2,0x12,data) #Bank 3 = MCP port A (second chip)
Now we start to get into the real controlling elements. Before we jump into the main program, this bit of code turns off all LEDs – just in case one is still on from previous usage:
#Clear all Segments set_led(0,bank1) set_led(0,bank2) set_led(0,bank3) time.sleep(2)
The next part is the main program.
We start by setting the initial count to 1 – this is the last time it will be ‘1’, as later on we double this number as part of the loop.
def main(): count = 1
We then start the while loop (see code below) – we use ‘set_led’, but the first number in the brackets is the count number. Why? Well this starts with 1 as per above, and is doubled each time this loops. This eventually gets to 256 and the program closes.
If we look at how the MCP23017’s outputs are addressed, you’ll see that this means it will cycle through each I/O and then stop when there are no more left:
Output 0 = 1
Output 1 = 2
Output 2 = 4
Output 3 = 8
Output 4 = 16
Output 5 = 32
Output 6 = 64
Output 7 = 128
See how the number doubles each time? So whilst this script will test one LED at a time, we can actually combine these numbers to light many at once – for example 1+2+4+8 will light the first 4 LEDs, and I’d enter set_led(15,bank1) (1+2+4+8 = 15).
So looking at the code below, you will see that each segment on each display is lit one at a time (as you can see the bank number changes each time), then the LED segments are cleared again, then the count number doubled, before it loops around again. Once the count doubles to the point it hits 256, the ‘if statement’ kicks in and closes the script.
Hopefully that makes sense, and you can start to see how we will make numbers with these segments by doing this.
while 1: set_led(count,bank1) time.sleep(2) set_led(count,bank2) time.sleep(2) set_led(count,bank3) time.sleep(2) #Clear all Segments set_led(0,bank1) set_led(0,bank2) set_led(0,bank3) time.sleep(2) #Add count to count, to get the next port number count = count + count #Check for last segment if count == 256: return
If you have wired your segments the same way as I have, the reference number of the segments should be as follows:
Remember what I showed you earlier?
Output 0 = 1
Output 1 = 2
Output 2 = 4
Output 3 = 8
Output 4 = 16
Output 5 = 32
Output 6 = 64
Output 7 = 128
Now that we know which number lights each segment, we can start to add these together to make a number shape, like the example for number “7” below, which is lit using number 52 by adding together the segments we want to light i.e. set_led(52,bank1) :
Let’s move on to the temperature monitor script and cover this in more detail later.
Temperature Monitor Script
Now time for the fun stuff – let’s get this prototype monitoring the temperature. We know the ICs, segments and wiring is all working and correctly set up – so let’s dive right in.
Here’s a link to the full code:
I won’t cover the first sections that are the same as the test file above – we already went through that – I’ll just kick off with the main program in this script.
Ok so the first part of the programme creates a variable (terminology may be wrong again here) that we will use later on:
def main(): #Define supporting variable timelastchecked = 0
Next we jump into a while loop. The first part here (timelastchecked) captures the temperature at regular intervals and converts this into 3 separate numbers – one for each segment display.
We initially run the command “/opt/vc/bin/vcgencmd measure_temp” to return a temperature string such as “temp=43.3’C ” (note the space at the end). To break this down, we take this string and cut characters off the front and back, 3 times, to get the first, second and third digits of the temperature:
while 1: if time.time() >= timelastchecked: timelastchecked = time.time()+3 #Check every 3 seconds mytemp = "" firstchar = "" secondchar = "" thirdchar = "" f=os.popen("/opt/vc/bin/vcgencmd measure_temp") #Temperature command for terminal for i in f.readlines(): mytemp += i firstchar = mytemp[5:-6] #removes 5 characters from the front, 6 from the back secondchar = mytemp[6:-5] #second character string thirdchar = mytemp[8:-3] #third character string
Now that we have these 3 separate characters (firstchar, secondchar and thirdchar) of the temperature, we can use this to drive the individual LED segments in order to make numbers light up. I use IF and ELIF statements to run through number 0 to 9 – when the first character matches a number, the combination of LEDs are lit on the display to create that number.
We then nest two more while loops for the remaining two segments to do the same thing (banks 2 and bank 3). Note the second while loop has different numbering – this is because we light the decimal on this segment:
while 1: #first segment if firstchar == '0': #If first digit is 1 set_led(119,bank1) elif firstchar == '1': #If first digit is 1 set_led(20,bank1) elif firstchar == '2': #if not 1, try 2 set_led(79,bank1) elif firstchar == '3': #if not 2, try 3 set_led(182,bank1) elif firstchar == '4': #and so on... set_led(212,bank1) elif firstchar == '5': set_led(230,bank1) elif firstchar == '6': set_led(231,bank1) elif firstchar == '7': #and so on... set_led(52,bank1) elif firstchar == '8': set_led(247,bank1) elif firstchar == '9': set_led(244,bank1) while 1: #second segment if secondchar == '0': set_led(127,bank2) elif secondchar == '1': set_led(28,bank2) elif secondchar == '2': set_led(187,bank2) elif secondchar == '3': set_led(190,bank2) elif secondchar == '4': set_led(220,bank2) elif secondchar == '5': set_led(238,bank2) elif secondchar == '6': set_led(239,bank2) elif secondchar == '7': set_led(60,bank2) elif secondchar == '8': set_led(255,bank2) elif secondchar == '9': set_led(252,bank2) while 1: #third segment if thirdchar == '0': set_led(119,bank3) elif thirdchar == '1': set_led(20,bank3) elif thirdchar == '2': set_led(179,bank3) elif thirdchar == '3': set_led(182,bank3) elif thirdchar == '4': set_led(212,bank3) elif thirdchar == '5': set_led(230,bank3) elif thirdchar == '6': set_led(231,bank3) elif thirdchar == '7': set_led(52,bank3) elif thirdchar == '8': set_led(247,bank3) elif thirdchar == '9': set_led(244,bank3) time.sleep(2) main()
That’s pretty much it for the prototype. I’m sure there are better, more mathematical ways of doing this – but this is the only way I know how to do this just now, and hey, it works right?
I’d be very interested to hear any suggestions on alternative coding methods.
Once my resistors arrive, i’ll be writing a second part to this blog where I’ll cover the assembly of the parts onto something a bit more permanent – an EZasPi board from Mikronauts.
If you have any questions, just leave a comment below.