| |
NTPheat
My attention was recently draw to the ntpheat program, which is a simple way
to turn a Raspberry Pi's crystal into a more stable frequency. No, not
TCXO standard, but considerably better than a naked RPi, particularly in a
centrally heated environment, and especially when that RPi is near to a central
heating radiator! It worked very well for me, but it may not help in your
particular configuration, but there's no harm in trying it and seeing what it
does!
The program itself
NTPheat is written in Python, and requires no installation or extra libraries
- at least it didn't on my RPi. It monitors the CPU temperature (as an
approximation to the actual crystal temperature) and uses CPU cycles to heat up
the CPU if its temperature is too low. There's no CPU cooling, of
course! The original
ntpheat was part of the NTPsec project and had lines to check for that NTP
environment, whereas the program itself is completely general, I had to
remove the NTPsec-specific lines to allow the program to work for reference
NTP. Lines 24-29 and 51-53 were removed, leaving the code below.
#!/usr/bin/env python
#
# generate some heat!
#
# Wrap your RasPi in bubble wrap. Then run ntpheat in the background.
# It will try to stabilize the CPU temperature at 65C by default.
# Sometimes one copy of ntpheat can use 100% of one CPU and
# still not heat up your RasPi as much as you want. The temptation
# is to add more insulation to your RasPi, but then it will overshoot
# your target temperature if your load factor goes high.
#
# The solution is to run more than one copy of ntpheat. This is
# easy to do with the -c option.
#
# To run 3 copies of ntpheat: ntpheat -c 3
import argparse
import hashlib
import os
import sys
import time
# Work with argvars
parser = argparse.ArgumentParser(description="make heat")
parser.add_argument('-c', '--copies',
default=[1],
dest='copies',
help="Number of copies to run. Default is 1",
nargs=1,
type=int)
parser.add_argument('-t', '--temp',
default=[65.0],
dest='target_temp',
help="Temperature to hold. Default is 65.0",
nargs=1,
type=float)
parser.add_argument('-w', '--wait',
default=[0.001],
dest='wait',
help="Set delay time in seconds, default is 0.1",
nargs=1,
type=float)
args = parser.parse_args()
args.copies[0] -= 1
while args.copies[0]:
args.copies[0] -= 1
pid = os.fork()
if pid:
# I am the fork
break
zone0 = '/sys/class/thermal/thermal_zone0/temp'
cnt = 0
m = hashlib.md5()
temp = 0
max_cnt = args.wait[0] * 200000
# on a RasPi 3 the temp steps seem to be about 0.537 to 0.539C
temp_gate = args.target_temp[0]
while True:
# on a RasPi 3, 200,000 of the m.update() can be one second
delta = temp_gate - temp
if 0 < delta:
# heat it up
m.update("Nobody inspects the spammish repetition")
else:
cnt = max_cnt
# cools off slower than it heats up.
# undocumented Python 'feature', no sleep less than 1 milli Sec
sleep = args.wait[0] * 10.0 * -delta
if 0.001 > sleep:
sleep = 0.001
time.sleep(sleep)
cnt += 1
# read the temperature every max_cnt
if max_cnt < cnt:
cnt = 0
zone_data = open(zone0, 'r')
for line in zone_data:
temp = float(line) / 1000
zone_data.close()
|
How well does it perform?
Before...
Showing the timekeeping swing with CPU temperature changes before the
program was installed.
During...
The day after the program was installed. You can see
that the CPU temperature is being held to a much more stable value of ~65
°C.
After...
The program continues to work stabilising the CPU
temperature. The swings during the day are much reduced, although the
sudden change when the heating is switched on is still present, but at a lower
level (7 µs) compared to the 20+ µs swing before. The CPU load averaged
34% for the performance shown below.
Acknowledgments
My thanks for the authors of this program, and the folk on the [time-nuts]
list for highlighting this program and advising how it could be modified.
Folkert van Heusden has a program which also use the GPU to heat the processor,
leaving the CPU cores free for real processing. Folkert's program is here
(it requires https://github.com/mn416/QPULib) |