lørdag den 15. februar 2020

Raspberry Pi 4 and temperature control

Raspberry Pi 4 and temperature control

I just bought a Raspberry Pi 4 to do a small project on home control (more on that later). I bought it from the fine people at Labists since they had a bundle of a PSU, SD card an case to go with the RPi. After all the project I am doing is meant to be running 24/7 so I need a case around the RPi PCB.

Not having done too much research, I only figured out that the RPi 4 needs active cooling when it is encased - at least some of the time. Labists knew this and therefore shipped a nice little fan with the machine. The fan is not extremely loud, but due to its size the noise it does make is somewhat annoying. Being a two wire fan, the option is to either mount it or not. There is no middle ground - at least not without hardware modifications.

I did a bit of measurements and most of the time the RPi keeps just around 60 degrees celcius when running with the enclosed heatsinks mounted. The problem is that it goes beyond 65 degrees under load, and it takes a looooong time to cool it down without the fan running. In my usecase it will sit around and do nothing most of the time,  which makes it sort of pointless to have the fan in except in the rare case that it actually needs to do something. 

This was enough for me to decide that I needed fan control. Not anything fancy, just a way of keeping the fan off unless it is needed. I decided to use one of the GPIOs to control the fan, but since the IO pins run at 3.3v and the fan needs 5v a few components were needed to make it happen. Therefore I did a simple motor control looking like this:




Thanks to Digikey for their nice Schematic editor

The operation is simple: When GPIO12 is low, no current can run in the transistor. When GPIO12 is pulled to 3.3v, the transistor opens completely and the fan runs. The resistor is there to make sure we limit the current drawn from the GPIO to around 4 mA and the diode is there to snub the voltage surge that can happen in the motor windings when power is cut abrubtly since that otherwise would stress the transistor. The motor will generate a reverse current when shut off which is then short-circuited by the diode. The type of diode is not important for a small motor like this, so I chose a schottky diode I had lying around. The transistor is a BC547B that can handle 100mA. The fan requires 65mA so any other transistor with similar specs will do.

I didn't do a PCB -  a birds nest on a piece of double sided tape will do in this case (and yes - my soldering skills could be better):


I cut the black wire in two using the half with the plug as ground and soldered the fan-side directly to the circuit. Then I carefully split the insulation on the red wire and soldered in the diode. Note that it sits in reverse between + and - as shown on the schematic - be sure to do this correctly as you otherwise create a short and lots of smoke first time the transistor is turned on. Lastly I had a wire with a plug on lying around that I used for the control pin on the transistor base.

Before putting it together I tested the circuit by wiring the control directly to 3.3V like this:


When I saw the fan was spinning as it should, I covered the circuit with another piece of tape and wired the control to GPIO12 (any could do - but that is what I chose:


Then the box was closed up and the hardware finished.

On the software side I chose - after some experiments - to implement a solution based on shell scripts entirely. The RPi GPIOs can be manipulated from the command line by writing to the /sys meta-files.

Two steps are needed to turn the fan on from the command line:

  1. GPIO 12 must be enabled for output in the /sys filesystem. This is done by writing the following two lines on the command line

    echo "12" > /sys/class/gpio/export
    echo "out" > /sys/class/gpio/gpio12/direction

    The first line enables control of GPIO12 over the /sys interface and the second sets the direction to out. This means that writing its value will change the state of the pin, and reading it will give the last state used. Note that these two lines can only be executed as root so you need to sodu -i before you do it
  2. Next we can control the fan simply by writing 1 or 0 to the pin:

    echo "1" > /sys/class/gpio/gpio12/value

    to turn on the fan and

    echo "0" > /sys/class/gpio/gpio12/value

    to turn it off
Now - there is of course not much fun in being able to control the fan manually - then you might as well just take the box apart and unplug it whenever you feel like it.

To automate things we use a small shell script that looks like this:

#! /bin/bash
# Read status of the temperature and fan
temp=`cat /sys/class/thermal/thermal_zone0/temp`
pin=`cat /sys/class/gpio/gpio12/value`

# Temperature is read in millidegrees celcius.
# if it exceeds 65 degrees c and fan is not running ..
if [ $temp -gt 65000 -a $pin -eq 0 ]; then
    # turn it on
    echo "1" > /sys/class/gpio/gpio12/value
fi;

# if it is below 55 degrees C and fan is running
if [ $temp -lt 55000 -a $pin -eq 1 ]; then
    # turn it off
    echo "0" > /sys/class/gpio/gpio12/value
fi;

This script will read the temperature of the CPU from the /sys filesystem and the current value of the GPIO. If then the temperature is too high while the fan doesn't run it will start the fan. If the temperature drops below a lower value it will stop the fan if it is running. This hysteresis makes sure that the fan doesn't start and stop all the time, but that the system is properly cooled before it is switched off again.

Make yourself root (sodu -i) and put the script in a file called /usr/local/bin/chktemp and remember to make it executable and owned by root - to do that you enter /usr/local/bin and do:

chmod +x chktemp
chown root.root chktemp

This script needs to run at regular intervals - like every 10 seconds. Linux has the cron daemon to do this, so you need to

sodu crontab -e

and insert the following line in the end of the crontab file:

* * * * * for i in {1..6}; do /usr/local/bin/chktemp & sleep 10; done

This line runs every minute and will run the script 6 times with 10 second intervals, i.e. every 10 seconds. That will then control the fan dynamically based on the temperature

To test this, edit chktemp and set the temperature interval between 48 and 50 degrees and hear the fan start and stop (you now need to be root to do this). You can also set the limits permanently different from what I chose if you are more or less whimpy than I am, but in general it is recommended to keep the temp below 70 degrees C.

 You can use the shellscript below to get the temperature (I put this in /usr/local/bin/printtemp) and see that the fan starts as expected (note that it can take up to 10 seconds after the temperature consistently has passed the limit before it starts):

#! /bin/bash
# Read status of the temperature
temp=`cat /sys/class/thermal/thermal_zone0/temp`
let "temp = $temp / 1000"
echo "Temperature is " $temp "C"

The last thing you need is to make sure that the GPIO is enabled after a reboot. The easiest way to do that is to modify /etc/rc.local (again you need to be root) and insert the two lines enabling it:

echo "12" > /sys/class/gpio/export
echo "out" > /sys/class/gpio/gpio12/direction

This file is run at every boot as root, so this is the last piece of the automation puzzle. That's it - the RPi is now able to  control its own temperature and survive a reboot doing it.

søndag den 12. januar 2020

Using both iGPU and discrete GPU for OpenCL

Small trick that may come in handy for others:

In my machine I have both a discrete nVidia GPU and an integrated GPU on the CPU. For some time I have wanted to use both in an OpenCL application, but until now I have not been able to access the iGPU as my primary (and only) monitor is attached to the nVidia card.

It turns out that the iGPU is turned off by default, but it is possible to force on in the BIOS. In my case (ASUS motherboard) it was done by accessing the advanced settings, then Graphics and then set

Primary display to "PCIe" (was Auto)
iGPU multi monitor to "On" (was off)

This turned on the iGPU even with no monitor attached, and triggered installataion of the Intel drives that enabled OpenCL.

I suppose similar settings may exist in other BIOS as well.