Quantcast
Channel: Raspberry Pi Forums
Viewing all articles
Browse latest Browse all 4906

Python • Re: very noisy SoC temperature measurement (RPi5)

$
0
0
More like:
sysfs_vs_vcgencmd.png
The temperature from vcgencmd is in red. A cpu stress was initiated at midtime of the experiment.
Script to reproduce:

Code:

import subprocessfrom time import sleepfrom math import pifrom statistics import mean, pstdevdef get_temps():    '''    two measurements of the cpu's temperature    returned as a tuple:        temp_sysfs: float, derived from an integer value read from /sys        temp_vcgen: float, derived from the output of the command vcgencmd    There is NO guarantee that the two measurements are from the same output    of the SoC's ADC, in fact it's hardly ever the case.    This routine is around 2ms long on a RPi5 in idle conditions, this only    gives a boundary of the time lag between the two measurements.    The only reasonable assumption is that the lag is sufficiently small    with respect to the dynamics of the underlying thermal behaviour.    '''    with open('/sys/class/thermal/thermal_zone0/temp') as f:        cputemp = f.readline()    temp_sysfs = int(cputemp.strip('\n')) / 1000    ret = subprocess.run(["vcgencmd", "measure_temp"], capture_output=True)    retstring = ret.stdout.decode()    temp_vcgen = float(retstring.lstrip("temp=").rstrip("'C\n"))    return temp_sysfs, temp_vcgenclass filter_critic():    '''    IIR filter, lowpass, order 2, critically damped    designed with bilinear transform s -> 2*fs*(z-1)/(z+1)    there is no prewarping    '''    def __init__(self, fs, tr):        '''        fs: sampling frequency, in Hz        tr: 5% response time, in seconds        '''        assert tr > 0        # A response time can be defined in more than one way, but is always inversely proportional to the -6dB corner frequency        # Here it's the time it takes for a step response to reach 95% of the input step        fc = 0.7550095 / tr                     # this magic constant is valid for the previous definition only        assert fc < fs / 2                      # Shannon        A = 2 * pi * fc / fs                    # dimensionless        rat1 = (A - 2) / (A + 2)        rat2 = A / (A + 2)        self.coef_y_1 = 2 * rat1        self.coef_y_2 = rat1 * rat1        self.coef_com = rat2 * rat2        self.x_1 = 0.0                           # x[n-1]        self.x_2 = 0.0                           # x[n-2]        self.y_1 = 0.0                           # y[n-1]        self.y_2 = 0.0                           # y[n-2]    def __call__(self, x):        '''        x: float, input at instant n        returns: float, output at instant n        '''        y =  self.coef_com * (x + 2 * self.x_1 + self.x_2) - self.coef_y_1 * self.y_1 - self.coef_y_2 * self.y_2        # update the filter's memory, preparing for next call        self.x_2 = self.x_1        self.x_1 = x        self.y_2 = self.y_1        self.y_1 = y        return ydef main(sampling_freq=20, resp_time=3, dur=60):    '''    sampling_freq: sample rate of measurements, in Hz    resp_time: response time (5%) of the filters, in seconds    dur: duration of the experiment, in seconds    The duration of the function is actually more than this, because    some extra time is provisioned for the startup transients    '''    assert 0 < sampling_freq < 25               # trying a higher rate would't be reasonable, because vcgencmd is so slow    N = int(sampling_freq * dur)    I = int(1.5 * sampling_freq * resp_time)    # allow 1.5 * response time for filters to settle    ofile = '/tmp/temp_comparison.csv'    print('acquiring temperatures...')    vector = []    dt = 1 / sampling_freq    for _ in range(N + I):        temps = get_temps()        vector.append( temps )        sleep(dt)    # offline filtering    filter_sysfs = filter_critic(sampling_freq, resp_time)    filter_vcgen = filter_critic(sampling_freq, resp_time)    fvector = []    for n in range(N + I):        x_sysfs, x_vcgen = vector[n]        y_sysfs = filter_sysfs(x_sysfs)        y_vcgen = filter_vcgen(x_vcgen)        fvector.append( (y_sysfs, y_vcgen, y_vcgen - y_sysfs) )    fvector = fvector[I:]   # discard init data to ensure statistical significance    vector = vector[I:]    with open(ofile,'w') as f:        f.write('sample\ttemp_sysfs\ttemp_vcgen\tsysfs_filtered\tvcgen_filtered\tdelta\n')        for n in range(N):            xn_sysfs, xn_vcgen = vector[n]            yn_sysfs, yn_vcgen, delta = fvector[n]            f.write(f'{n}\t{xn_sysfs}\t{xn_vcgen}\t{yn_sysfs}\t{yn_vcgen}\t{delta}\n')    print(f'...measurements and filter outputs are in: {ofile}\n')    deltas = [fdata[2] for fdata in fvector]    print('statistics for the offset between the two curves:')    print(f'mean: {mean(deltas):.3f}°C  standard deviation: {pstdev(deltas):.3f}°C')if __name__ == '__main__':    main()      # temperatures are acquired for 60 + 4.5 seconds                # sample rate is 20Hz                # filtering is done with a 3s response time
Output of the script, for previous chart:

Code:

acquiring temperatures......measurements and filter outputs are in: /tmp/temp_comparison.csvstatistics for the offset between the two curves:mean: 0.339°C  standard deviation: 0.048°C

Statistics: Posted by pragma — Sat Jan 20, 2024 6:10 am



Viewing all articles
Browse latest Browse all 4906

Trending Articles