STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
noise_analyzer.cpp
Go to the documentation of this file.
1#include "noise_analyzer.h"
2
3#include <math.h>
4
6 : _count(0),
7 _index(0),
8 _statsDirty(true),
9 _cachedMean(0.0),
10 _cachedStdDev(0.0),
11 _cachedMin(0),
12 _cachedMax(0) {
13 for (size_t i = 0; i < WINDOW_SIZE; ++i) {
14 _buf[i] = 0;
15 }
16}
17
19 _count = 0;
20 _index = 0;
21 _statsDirty = true;
22 _cachedMean = 0.0;
23 _cachedStdDev = 0.0;
24 _cachedMin = 0;
25 _cachedMax = 0;
26}
27
28void NoiseAnalyzer::addSample(int32_t code) {
29 _buf[_index] = code;
30 _index = (_index + 1) % WINDOW_SIZE;
31 if (_count < WINDOW_SIZE)
32 _count++;
33 _statsDirty = true; // Mark stats as needing recalculation
34}
35
37 if (!_statsDirty || _count == 0)
38 return;
39
40 // Calculate mean, min, max in one pass
41 double sum = 0.0;
42 int32_t minVal = _buf[0];
43 int32_t maxVal = _buf[0];
44
45 for (size_t i = 0; i < _count; ++i) {
46 int32_t val = _buf[i];
47 sum += (double)val;
48 if (val < minVal)
49 minVal = val;
50 if (val > maxVal)
51 maxVal = val;
52 }
53
54 _cachedMean = sum / (double)_count;
55 _cachedMin = minVal;
56 _cachedMax = maxVal;
57
58 // Calculate standard deviation in second pass
59 if (_count < 2) {
60 _cachedStdDev = 0.0;
61 } else {
62 double sumSqDev = 0.0;
63 for (size_t i = 0; i < _count; ++i) {
64 double dev = (double)_buf[i] - _cachedMean;
65 sumSqDev += dev * dev;
66 }
67 // Use unbiased estimator (N-1)
68 _cachedStdDev = sqrt(sumSqDev / (double)(_count - 1));
69 }
70
71 _statsDirty = false;
72}
73
74double NoiseAnalyzer::mean() const {
75 if (_count == 0)
76 return NAN;
78 return _cachedMean;
79}
80
82 if (_count < 2)
83 return NAN;
85 return (float)_cachedStdDev;
86}
87
89 if (_count < 2)
90 return NAN;
92 return (float)(_cachedMax - _cachedMin);
93}
94
95float NoiseAnalyzer::noiseFreeBits(uint8_t adcBits) const {
96 if (_count < 50)
97 return NAN; // Need at least 50 samples for good statistics
98
99 float rms = rmsNoiseLsb();
100 if (!isfinite(rms) || rms <= 0.0f) {
101 return (float)adcBits; // effectively no measurable noise
102 }
103
104 // Noise-free bits calculation:
105 // Peak-to-peak noise ≈ 6.6 × RMS for Gaussian distribution (99.99%
106 // confidence) NFB = log2(2^adcBits / noise_pp) = adcBits - log2(noise_pp)
107 // NFB = adcBits - log2(6.6 × RMS)
108 double noisePP = 6.6 * (double)rms;
109 double nfb = (double)adcBits - log2(noisePP);
110
111 // Clamp to reasonable range
112 if (nfb < 0.0)
113 nfb = 0.0;
114 if (nfb > (double)adcBits)
115 nfb = (double)adcBits;
116
117 return (float)nfb;
118}
119
120float NoiseAnalyzer::enob(uint8_t adcBits) const {
121 if (_count < 50)
122 return NAN; // Need at least 50 samples for good statistics
123
124 float rms = rmsNoiseLsb();
125 if (!isfinite(rms) || rms <= 0.0f) {
126 return (float)adcBits;
127 }
128
129 // ENOB (Effective Number of Bits) per ADC datasheet:
130 // ENOB = log2(FSR / Vn,rms)
131 //
132 // Where:
133 // FSR = Full Scale Range = 2·VREF / gain (in volts)
134 // Vn,rms = RMS noise (in volts)
135 //
136 // When working with codes instead of voltages:
137 // FSR_codes = 2^adcBits
138 // Noise_codes = RMS_LSB
139 //
140 // Therefore:
141 // ENOB = log2(2^adcBits / RMS_LSB)
142 // ENOB = log2(2^adcBits) - log2(RMS_LSB)
143 // ENOB = adcBits - log2(RMS_LSB)
144
145 double enobValue = (double)adcBits - log2((double)rms);
146
147 // Clamp to reasonable range
148 if (enobValue < 0.0)
149 enobValue = 0.0;
150 if (enobValue > (double)adcBits)
151 enobValue = (double)adcBits;
152
153 return (float)enobValue;
154}
155
156bool NoiseAnalyzer::isStable(float cvThreshold) const {
157 if (_count < 10)
158 return false; // Need reasonable sample size
159
160 _updateStats();
161
162 // Check if mean is close to zero (avoid division by zero)
163 // For ADC codes, we consider "near zero" as absolute value < 100
164 if (fabs(_cachedMean) < 100.0) {
165 // If mean is near zero, check if std dev is small (< 50 LSB)
166 return _cachedStdDev < 50.0;
167 }
168
169 // Coefficient of Variation = StdDev / |Mean|
170 double cv = _cachedStdDev / fabs(_cachedMean);
171 return cv < (double)cvThreshold;
172}
double mean() const
float rmsNoiseLsb() const
static const size_t WINDOW_SIZE
void _updateStats() const
float peakToPeakNoiseLsb() const
void addSample(int32_t code)
float noiseFreeBits(uint8_t adcBits) const
int32_t _buf[WINDOW_SIZE]
float enob(uint8_t adcBits) const
bool isStable(float cvThreshold=0.01f) const