rusEFI
The most advanced open source ECU
Loading...
Searching...
No Matches
software_knock.cpp
Go to the documentation of this file.
1#include "pch.h"
2
3#if EFI_SOFTWARE_KNOCK
4
5#include "biquad.h"
6#include "thread_controller.h"
7#include "knock_logic.h"
8#include "software_knock.h"
9#include "knock_config.h"
10#include "ch.hpp"
11#include "error_handling.h"
12
13#ifdef KNOCK_SPECTROGRAM
14#include "fft/fft.hpp"
15
16#define COMPRESSED_SPECTRUM_PROTOCOL_SIZE 16 // 16 * 4 = 64 byte for transport to TS
17#define START_SPECTRORGAM_FREQUENCY 4000 // magic minimum Hz for draw spectrogram, use near value +next 64 freqs from fft
18
19static size_t spectrogramStartIndex = 0;
22
23// TODO: use big_buffer
24//static volatile bool enableKnockSpectrogram = false;
25//static BigBufferHandle buffer;
26//static SpectrogramData* spectrogramData = nullptr;
27#endif //KNOCK_SPECTROGRAM
28
29
31static int8_t currentCylinderNumber = 0;
32static int8_t channelNumber = 0;
33static efitick_t lastKnockSampleTime = 0;
35
36static volatile bool knockIsSampling = false;
37static volatile bool knockNeedsProcess = false;
38static volatile size_t sampleCount = 0;
39
40chibios_rt::BinarySemaphore knockSem(/* taken =*/ true);
41
43 knockNeedsProcess = true;
44
45 // Notify the processing thread that it's time to process this sample
46 chSysLockFromISR();
47 knockSem.signalI();
48 chSysUnlockFromISR();
49}
50
51void onStartKnockSampling(uint8_t cylinderNumber, float samplingSeconds, uint8_t channelIdx) {
53 return;
54 }
55
56 // Cancel if ADC isn't ready
57 if (!((KNOCK_ADC.state == ADC_READY) ||
58 (KNOCK_ADC.state == ADC_ERROR))) {
59 return;
60 }
61
62 // If there's pending processing, skip this event
64 return;
65 }
66
67 // Convert sampling time to number of samples
68 constexpr int sampleRate = KNOCK_SAMPLE_RATE;
69 sampleCount = 0xFFFFFFFE & static_cast<size_t>(clampF(100, samplingSeconds * sampleRate, efi::size(sampleBuffer)));
70
71 // Select the appropriate conversion group - it will differ depending on which sensor this cylinder should listen on
72 auto conversionGroup = getKnockConversionGroup(channelIdx);
73
74 //current chanel number for spectrum TS plugin
76
77 // Stash the current cylinder's number so we can store the result appropriately
78 currentCylinderNumber = cylinderNumber;
79
80 adcStartConversionI(&KNOCK_ADC, conversionGroup, sampleBuffer, sampleCount);
82}
83
84class KnockThread : public ThreadController<UTILITY_THREAD_STACK_SIZE> {
85public:
86 KnockThread() : ThreadController("knock", PRIO_KNOCK_PROCESS) {}
87 void ThreadTask() override;
88};
89
90static KnockThread kt;
91
94
95 float frequencyHz;
96
99 } else {
100 frequencyHz = 1000 * bore2frequency(engineConfiguration->cylinderBore);
101 frequencyHz = engineConfiguration->knockDetectionUseDoubleFrequency ? 2 * frequencyHz : frequencyHz;
102 }
103
104 knockFilter.configureBandpass(KNOCK_SAMPLE_RATE, frequencyHz, 3);
105
106 #ifdef KNOCK_SPECTROGRAM
108
109 // TODO: use big buffer
110 //buffer = getBigBuffer(BigBufferUser::KnockSpectrogram);
111 // if (!buffer) {
112 // engineConfiguration->enableKnockSpectrogram = false;
113 // return;
114 // }
115 //spectrogramData = buffer.get<SpectrogramData>();
116
118
119 int freqStartConst = START_SPECTRORGAM_FREQUENCY;
120 int minFreqDiff = freqStartConst;
121 int freqStart = 0;
122 float freqStep = 0;
123
124 for (size_t i = 0; i < FFT_SIZE/2; i++)
125 {
126 float freq = float(i * KNOCK_SAMPLE_RATE) / FFT_SIZE;
127 int min = abs(freq - freqStartConst);
128
129 // next after freq start index
130 if(i == spectrogramStartIndex + 1) {
131 freqStep = abs(freq - freqStart);
132 }
133
134 if(min < minFreqDiff) {
135 minFreqDiff = min;
137 freqStart = freq;
138 }
139 }
140
141 engine->module<KnockController>()->m_knockFrequencyStart = (uint16_t)freqStart;
143 }
144 #else // KNOCK_SPECTROGRAM
145 criticalAssertVoid(!engineConfiguration->enableKnockSpectrogram, "KNOCK_SPECTROGRAM not enabled");
146 #endif // KNOCK_SPECTROGRAM
147
148 // fun fact: we do not offer any ADC channel flexibility like we have for many other kinds of inputs
149 efiSetPadMode("knock ch1", KNOCK_PIN_CH1, PAL_MODE_INPUT_ANALOG);
150#if KNOCK_HAS_CH2
151 efiSetPadMode("knock ch2", KNOCK_PIN_CH2, PAL_MODE_INPUT_ANALOG);
152#endif
153 kt.start();
154 }
155}
156
157#ifdef KNOCK_SPECTROGRAM
158static uint8_t toDb(const float& voltage) {
159 float db = 200 * log10(voltage*voltage) + 40; // best scaling for view
160 db = clampF(0, db, 255);
161 return uint8_t(db);
162}
163#endif
164
166 if (!knockNeedsProcess) {
167 return;
168 }
169
170 float sumSq = 0;
171
172 // todo: reduce magic constants. engineConfiguration->adcVcc?
173 constexpr float ratio = 3.3f / 4095.0f;
174
175 size_t localCount = sampleCount;
176
177 // Prepare the steady state at vcc/2 so that there isn't a step
178 // when samples begin
179 // todo: reduce magic constants. engineConfiguration->adcVcc?
181
182 // Compute the sum of squares
183 for (size_t i = 0; i < localCount; i++) {
184 float volts = ratio * sampleBuffer[i];
185
186 float filtered = knockFilter.filter(volts);
187 if (i == localCount - 1 && engineConfiguration->debugMode == DBG_KNOCK) {
190 }
191
192 sumSq += filtered * filtered;
193 }
194
195 // take a local copy
196 auto lastKnockTime = lastKnockSampleTime;
197
198 // We're done with inspecting the buffer, another sample can be taken
199 knockNeedsProcess = false;
200
201#ifdef KNOCK_SPECTROGRAM
204
207 } else {
209 }
210
211 auto* spectrum = &engine->module<KnockController>()->m_knockSpectrum[0];
212 for(uint8_t i = 0; i < COMPRESSED_SPECTRUM_PROTOCOL_SIZE; ++i) {
213
214 uint8_t startIndex = spectrogramStartIndex + (i * 4);
215
216 uint8_t a = toDb(fft::amplitude(spectrogramData->fftBuffer[startIndex]));
217 uint8_t b = toDb(fft::amplitude(spectrogramData->fftBuffer[startIndex + 1]));
218 uint8_t c = toDb(fft::amplitude(spectrogramData->fftBuffer[startIndex + 2]));
219 uint8_t d = toDb(fft::amplitude(spectrogramData->fftBuffer[startIndex + 3]));
220
221 uint32_t compressed = uint32_t(a << 24 | b << 16 | c << 8 | d);
222
223 {
224 chibios_rt::CriticalSectionLocker csl;
225 spectrum[i] = compressed;
226 }
227 }
228
229 uint16_t compressedChannelCyl = uint16_t(channelNumber << 8 | currentCylinderNumber);
230
231 {
232 chibios_rt::CriticalSectionLocker csl;
233 engine->module<KnockController>()->m_knockSpectrumChannelCyl = compressedChannelCyl;
234 }
235 }
236
237#endif
238
239 // mean of squares (not yet root)
240 float meanSquares = sumSq / localCount;
241
242 // RMS
243 float db = 10 * log10(meanSquares);
244
245 // clamp to reasonable range
246 db = clampF(-100, db, 100);
247
248 engine->module<KnockController>()->onKnockSenseCompleted(currentCylinderNumber, db, lastKnockTime);
249}
250
251void KnockThread::ThreadTask() {
252 while (1) {
253 knockSem.wait();
254
257 }
258}
259
260
261#endif // EFI_SOFTWARE_KNOCK
const ADCConversionGroup * getKnockConversionGroup(uint8_t channelIdx)
void efiSetPadMode(const char *msg, brain_pin_e brainPin, iomode_t mode)
float filter(float input)
Definition biquad.cpp:74
void configureBandpass(float samplingFrequency, float centerFrequency, float Q)
Definition biquad.cpp:32
void cookSteadyState(float steadyStateInput)
Definition biquad.cpp:84
TunerStudioOutputChannels outputChannels
Definition engine.h:109
constexpr auto & module()
Definition engine.h:200
A base class for a controller that requires its own thread.
virtual void ThreadTask()=0
efitick_t getTimeNowNt()
Definition efitime.cpp:19
static EngineAccessor engine
Definition engine.h:413
static constexpr engine_configuration_s * engineConfiguration
uint16_t adcsample_t
ADC sample data type.
float uint8_t channelIdx
static union @47 NO_CACHE
void blackmanharris(float *w, unsigned n, bool sflag)
Definition fft.hpp:188
float amplitude(const complex_type &fft)
Definition fft.hpp:133
bool fft_adc_sample_filtered(Biquad &knockFilter, float *w, float ratio, float sensitivity, const adcsample_t *data_in, complex_type *data_out, const size_t size)
Definition fft.hpp:98
bool fft_adc_sample(float *w, float ratio, float sensitivity, const adcsample_t *data_in, complex_type *data_out, const size_t size)
Definition fft.hpp:88
@ KnockAnalyzer
@ SoftwareKnockProcess
m_knockFrequencyStart("Knock: Start Freq", SensorCategory.SENSOR_INPUTS, FieldType.INT16, 1090, 1.0, 0.0, 0.0, "Hz")
m_knockSpectrumChannelCyl("Knock: ChannelAndCylNumber", SensorCategory.SENSOR_INPUTS, FieldType.INT16, 1088, 1.0, 0.0, 0.0, "compressed N + N")
m_knockFrequencyStep("Knock: Step Freq", SensorCategory.SENSOR_INPUTS, FieldType.INT, 1092, 1.0, 0.0, 0.0, "Hz")
static SpectrogramData * spectrogramData
static volatile bool knockIsSampling
static int8_t channelNumber
static void processLastKnockEvent()
static size_t spectrogramStartIndex
static Biquad knockFilter
void onStartKnockSampling(uint8_t cylinderNumber, float samplingSeconds, uint8_t channelIdx)
void initSoftwareKnock()
void onKnockSamplingComplete()
static volatile size_t sampleCount
static uint8_t toDb(const float &voltage)
chibios_rt::BinarySemaphore knockSem(true)
static NO_CACHE adcsample_t sampleBuffer[1800]
static KnockThread kt
static volatile bool knockNeedsProcess
static SpectrogramData spectrogramData0
static efitick_t lastKnockSampleTime
static int8_t currentCylinderNumber
fft::complex_type fftBuffer[FFT_SIZE]
float window[FFT_SIZE]