Monitor PICO-APL3 CPU Cores’ temperature using LEDs on a custom external circuit
In the previous tutorial we followed the steps required to retrieve the temperature information provided by the on-board sensors, using the lm_sensors package. We extracted this information from the output of the sensors command and displayed the results on the terminal screen.
Taking the work done so far as a starting point, we will now show how the temperature information can be used to control external devices. A real-life situation in which this can be useful is the control of a cooling fan for the processor/board. The speed of the fan can be increased or decreased depending on the measured temperatures, or, if they are low enough, the fan can be stopped.
But for the purpose of demonstrating the interaction with external devices we will take a simpler approach and use the digital signals that we already discussed in previous posts to drive a set of LEDs. This will also give us the chance to see how the GPIO can be controlled from a Python script.
GPIO Control in Python
When we worked with the GPIO signals from the command line or in shell scripts we had to access different files in the /sys/class/gpio/ directory hierarchy. In Python we have a library that encapsulates and hides these accesses and provides us with a very convenient programming interface. This library is python-periphery and we need to install it before making any additions to our Python code. But first we need to install pip, a Python package manager that is used to install python-periphery. So, these are the installation steps:
> sudo apt install python3-pip > sudo pip3 install python-periphery
Now we are ready to use the periphery module in our Python code.
Python Script: temp_control.py
We will complete our new Python program by adding to our previous script the code that manages the GPIO.
When we execute the sensor command on the PICO-APL3 board we get four temperature measurements. For the sake of simplicity we are using the maximum of these temperatures as our unique control input. For that reason we have modified the update_output() function to compute and display the maximum temperature.
The other modifications are additions needed to manage the GPIO and are highlighted in the following listing.
import subprocess import re import sys import time from periphery import GPIO
N_OUTPUTS = 4 gpio_outs =  temp_thrs = [39.0, 46.0, 50.0, 53.0]
def get_sensor_data(): sensors_out = subprocess.run(['sensors'], stdout=subprocess.PIPE) return sensors_out.stdout.decode('utf-8')
def parse_temps(sensor_data): temps = re.findall('\d+(?:\.\d+)?°C ', sensor_data, re.MULTILINE) return [x[:-3] for x in temps]
def update_output(temps): sys.stdout.write("Board: %s - Package: %s - Core 0: %s - Core 2: %s | MAX: %s\r" % (temps, temps, temps, temps, max(temps))) sys.stdout.flush()
def shutdown(): cleanup_gpio() print() print("Goodbye!") print()
def init_gpio(n_outs): global gpio_outs gpio_outs = [GPIO(n, "low") for n in range(n_outs)]
def cleanup_gpio(): for gpio in gpio_outs: gpio.write(False) gpio.direction = "in" gpio.close()
def drive_leds(values): temp = float(max(values)) for k in range(len(gpio_outs)): gpio_outs[k].write(temp > temp_thrs[k])
# Main program init_gpio(N_OUTPUTS) print() print("System temperatures (°C) reported by the 'sensors' command (Ctrl-C to stop):") try: while True: data = get_sensor_data() temp_values = parse_temps(data) update_output(temp_values) drive_leds(temp_values) time.sleep(1) except KeyboardInterrupt: shutdown()
At the beginning of the script the GPIO class is imported from the periphery module and some variables are defined:
- N_OUTPUTS: The number of digital outputs used by the script.
- gpio_outs: a list of GPIO objects, which represent the four digital outputs.
- temp_thrs: a list of temperature thresholds.
Additionally, we define the new functions:
- init_gpio(): sets up the specified number of GPIOs as digital outputs and sets its value to LOW.
- cleanup_gpio(): changes all allocated outputs to inputs (for safety) and releases them.
- drive_leds(): computes the maximum temperature and compares it with the threshold values defined in temp_thrs: if the temperature is greater than one threshold, then the corresponding digital output is set HIGH, otherwise it is set LOW. For example, for the values shown in the listing above, for a temperature of 52°C the first three outputs will be activated, but not the fourth (the fourth threshold is 53°C).
The drive_leds() function is called on every iteration of the main loop, so the digital outputs will be updated periodically following the variations of the maximum temperature.
We have used a digital inverter IC (74HC04) to provide buffering for the GPIO signals. The main reasons is that this type of device is very easy to get, has a wide power supply range that allows to use the 3.3V available in the CN60 header, their outputs can drive the currents required by an LED and, finally, this way the wiring is greatly simplified.
We connect the GPIO signals (digital outputs) to the inverters’ inputs, and drive the LEDs with the outputs. The 74HC04 includes 6 inverters as shown in the following diagram:
We are only using four or them (1-4), so we connect the inputs of the remaining inverters 5 and 6 to Vcc (could be also GND, they just need to be tied to a constant logic level) to avoid unwanted switching and oscillations.
Every LED is controlled by one of the GPIOs. So as the temperature increases the LEDs will turn on in order, controlled by the drive_leds() function:
Testing the Script
In this case the script requires root permissions because of the access to the GPIO signals:
apl3@APL3:~$ sudo python3 temp_control.py
System temperatures (°C) reported by the 'sensors' command (Ctrl-C to stop): Board: 35.0 - Package: 39.0 - Core 0: 41.0 - Core 2: 39.0 | MAX: 41.0
The control of the LEDs can be checked by temporarily increasing the processor load and thereby the temperature:
> stress -c 16 -t 30s -q & python3 temp_display.py
The stress command will cause a temperature increase enough to turn on the four LEDs. After 30 seconds the load will return to a normal level and the ‘higher’ LEDs will turn off; only L0 (and maybe, sporadically, L1) will remain on.