2020-08-28 18:13:50 -07:00
|
|
|
|
|
|
|
#include "global.h"
|
|
|
|
#include "engine.h"
|
|
|
|
#include "biquad.h"
|
|
|
|
#include "perf_trace.h"
|
|
|
|
#include "thread_controller.h"
|
|
|
|
#include "software_knock.h"
|
|
|
|
|
|
|
|
#if EFI_SOFTWARE_KNOCK
|
|
|
|
|
|
|
|
EXTERN_ENGINE;
|
|
|
|
|
|
|
|
#include "knock_config.h"
|
|
|
|
|
|
|
|
adcsample_t sampleBuffer[2000];
|
2020-08-31 18:05:33 -07:00
|
|
|
int8_t currentCylinderIndex = 0;
|
2020-08-28 18:13:50 -07:00
|
|
|
Biquad knockFilter;
|
|
|
|
|
|
|
|
static volatile bool knockIsSampling = false;
|
|
|
|
static volatile bool knockNeedsProcess = false;
|
|
|
|
static volatile size_t sampleCount = 0;
|
|
|
|
|
|
|
|
binary_semaphore_t knockSem;
|
|
|
|
|
2021-01-19 12:20:35 -08:00
|
|
|
static void completionCallback(ADCDriver* adcp) {
|
2020-08-28 18:13:50 -07:00
|
|
|
palClearPad(GPIOD, 2);
|
|
|
|
|
|
|
|
if (adcp->state == ADC_COMPLETE) {
|
|
|
|
knockNeedsProcess = true;
|
|
|
|
|
|
|
|
// Notify the processing thread that it's time to process this sample
|
|
|
|
chSysLockFromISR();
|
|
|
|
chBSemSignalI(&knockSem);
|
|
|
|
chSysUnlockFromISR();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-23 06:43:16 -08:00
|
|
|
static void errorCallback(ADCDriver*, adcerror_t) {
|
2020-08-28 18:13:50 -07:00
|
|
|
}
|
|
|
|
|
2020-10-03 22:54:29 -07:00
|
|
|
static const uint32_t smpr1 =
|
|
|
|
ADC_SMPR1_SMP_AN10(KNOCK_SAMPLE_TIME) |
|
|
|
|
ADC_SMPR1_SMP_AN11(KNOCK_SAMPLE_TIME) |
|
|
|
|
ADC_SMPR1_SMP_AN12(KNOCK_SAMPLE_TIME) |
|
|
|
|
ADC_SMPR1_SMP_AN13(KNOCK_SAMPLE_TIME) |
|
|
|
|
ADC_SMPR1_SMP_AN14(KNOCK_SAMPLE_TIME) |
|
|
|
|
ADC_SMPR1_SMP_AN15(KNOCK_SAMPLE_TIME);
|
|
|
|
|
|
|
|
static const uint32_t smpr2 =
|
|
|
|
ADC_SMPR2_SMP_AN0(KNOCK_SAMPLE_TIME) |
|
|
|
|
ADC_SMPR2_SMP_AN1(KNOCK_SAMPLE_TIME) |
|
|
|
|
ADC_SMPR2_SMP_AN2(KNOCK_SAMPLE_TIME) |
|
|
|
|
ADC_SMPR2_SMP_AN3(KNOCK_SAMPLE_TIME) |
|
|
|
|
ADC_SMPR2_SMP_AN4(KNOCK_SAMPLE_TIME) |
|
|
|
|
ADC_SMPR2_SMP_AN5(KNOCK_SAMPLE_TIME) |
|
|
|
|
ADC_SMPR2_SMP_AN6(KNOCK_SAMPLE_TIME) |
|
|
|
|
ADC_SMPR2_SMP_AN7(KNOCK_SAMPLE_TIME) |
|
|
|
|
ADC_SMPR2_SMP_AN8(KNOCK_SAMPLE_TIME) |
|
|
|
|
ADC_SMPR2_SMP_AN9(KNOCK_SAMPLE_TIME);
|
|
|
|
|
2020-09-02 04:19:02 -07:00
|
|
|
static const ADCConversionGroup adcConvGroupCh1 = { FALSE, 1, &completionCallback, &errorCallback,
|
2020-08-28 18:13:50 -07:00
|
|
|
0,
|
|
|
|
ADC_CR2_SWSTART,
|
2020-10-03 22:54:29 -07:00
|
|
|
// sample times for channels 10...18
|
|
|
|
smpr1,
|
|
|
|
// sample times for channels 0...9
|
|
|
|
smpr2,
|
2020-08-28 18:13:50 -07:00
|
|
|
|
|
|
|
0, // htr
|
|
|
|
0, // ltr
|
|
|
|
|
|
|
|
0, // sqr1
|
|
|
|
0, // sqr2
|
|
|
|
ADC_SQR3_SQ1_N(KNOCK_ADC_CH1)
|
|
|
|
};
|
|
|
|
|
2020-09-02 04:19:02 -07:00
|
|
|
// Not all boards have a second channel - configure it if it exists
|
|
|
|
#if KNOCK_HAS_CH2
|
|
|
|
static const ADCConversionGroup adcConvGroupCh2 = { FALSE, 1, &completionCallback, &errorCallback,
|
|
|
|
0,
|
|
|
|
ADC_CR2_SWSTART,
|
2020-10-03 22:54:29 -07:00
|
|
|
// sample times for channels 10...18
|
|
|
|
smpr1,
|
|
|
|
// sample times for channels 0...9
|
|
|
|
smpr2,
|
2020-09-02 04:19:02 -07:00
|
|
|
|
|
|
|
0, // htr
|
|
|
|
0, // ltr
|
|
|
|
|
|
|
|
0, // sqr1
|
|
|
|
0, // sqr2
|
|
|
|
ADC_SQR3_SQ1_N(KNOCK_ADC_CH2)
|
|
|
|
};
|
|
|
|
|
|
|
|
static bool cylinderUsesChannel2(uint8_t cylinderIndex) {
|
|
|
|
// C/C++ can't index in to bit fields, we have to provide lookup ourselves
|
|
|
|
switch (cylinderIndex) {
|
|
|
|
case 0: return CONFIG(knockBankCyl1);
|
|
|
|
case 1: return CONFIG(knockBankCyl2);
|
|
|
|
case 2: return CONFIG(knockBankCyl3);
|
|
|
|
case 3: return CONFIG(knockBankCyl4);
|
|
|
|
case 4: return CONFIG(knockBankCyl5);
|
|
|
|
case 5: return CONFIG(knockBankCyl6);
|
|
|
|
case 6: return CONFIG(knockBankCyl7);
|
|
|
|
case 7: return CONFIG(knockBankCyl8);
|
|
|
|
case 8: return CONFIG(knockBankCyl9);
|
|
|
|
case 9: return CONFIG(knockBankCyl10);
|
|
|
|
case 10: return CONFIG(knockBankCyl11);
|
|
|
|
case 11: return CONFIG(knockBankCyl12);
|
|
|
|
default: return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-23 06:43:16 -08:00
|
|
|
#endif // KNOCK_HAS_CH2
|
|
|
|
|
2020-09-02 04:19:02 -07:00
|
|
|
const ADCConversionGroup* getConversionGroup(uint8_t cylinderIndex) {
|
|
|
|
#if KNOCK_HAS_CH2
|
|
|
|
if (cylinderUsesChannel2(cylinderIndex)) {
|
|
|
|
return &adcConvGroupCh2;
|
|
|
|
}
|
2021-01-11 05:49:20 -08:00
|
|
|
#else
|
|
|
|
(void)cylinderIndex;
|
2020-09-02 04:19:02 -07:00
|
|
|
#endif // KNOCK_HAS_CH2
|
|
|
|
|
|
|
|
return &adcConvGroupCh1;
|
|
|
|
}
|
|
|
|
|
2020-08-28 18:13:50 -07:00
|
|
|
void startKnockSampling(uint8_t cylinderIndex) {
|
|
|
|
if (!CONFIG(enableSoftwareKnock)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-08-31 18:05:33 -07:00
|
|
|
if (!engine->rpmCalculator.isRunning()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-08-28 18:13:50 -07:00
|
|
|
// Cancel if ADC isn't ready
|
|
|
|
if (!((KNOCK_ADC.state == ADC_READY) ||
|
|
|
|
(KNOCK_ADC.state == ADC_COMPLETE) ||
|
|
|
|
(KNOCK_ADC.state == ADC_ERROR))) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If there's pending processing, skip this event
|
|
|
|
if (knockNeedsProcess) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sample for 45 degrees
|
|
|
|
float samplingSeconds = ENGINE(rpmCalculator).oneDegreeUs * 45 * 1e-6;
|
|
|
|
constexpr int sampleRate = KNOCK_SAMPLE_RATE;
|
|
|
|
sampleCount = 0xFFFFFFFE & static_cast<size_t>(clampF(100, samplingSeconds * sampleRate, efi::size(sampleBuffer)));
|
|
|
|
|
2020-09-02 04:19:02 -07:00
|
|
|
// Select the appropriate conversion group - it will differ depending on which sensor this cylinder should listen on
|
|
|
|
auto conversionGroup = getConversionGroup(cylinderIndex);
|
|
|
|
|
|
|
|
// Stash the current cylinder's index so we can store the result appropriately
|
2020-08-31 18:05:33 -07:00
|
|
|
currentCylinderIndex = cylinderIndex;
|
2020-09-02 04:19:02 -07:00
|
|
|
|
|
|
|
adcStartConversionI(&KNOCK_ADC, conversionGroup, sampleBuffer, sampleCount);
|
2020-08-28 18:13:50 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
class KnockThread : public ThreadController<256> {
|
|
|
|
public:
|
|
|
|
KnockThread() : ThreadController("knock", NORMALPRIO - 10) {}
|
|
|
|
void ThreadTask() override;
|
|
|
|
};
|
|
|
|
|
2020-09-02 04:19:02 -07:00
|
|
|
static KnockThread kt;
|
2020-08-28 18:13:50 -07:00
|
|
|
|
|
|
|
void initSoftwareKnock() {
|
|
|
|
chBSemObjectInit(&knockSem, TRUE);
|
|
|
|
|
|
|
|
if (CONFIG(enableSoftwareKnock)) {
|
|
|
|
knockFilter.configureBandpass(KNOCK_SAMPLE_RATE, 1000 * CONFIG(knockBandCustom), 3);
|
|
|
|
adcStart(&KNOCK_ADC, nullptr);
|
|
|
|
|
|
|
|
efiSetPadMode("knock ch1", KNOCK_PIN_CH1, PAL_MODE_INPUT_ANALOG);
|
2020-09-22 00:48:17 -07:00
|
|
|
#if KNOCK_HAS_CH2
|
2020-08-28 18:13:50 -07:00
|
|
|
efiSetPadMode("knock ch2", KNOCK_PIN_CH2, PAL_MODE_INPUT_ANALOG);
|
2020-09-22 00:48:17 -07:00
|
|
|
#endif
|
2020-08-28 18:13:50 -07:00
|
|
|
kt.Start();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void processLastKnockEvent() {
|
|
|
|
if (!knockNeedsProcess) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
float sumSq = 0;
|
|
|
|
|
2021-01-14 19:34:06 -08:00
|
|
|
// todo: reduce magic constants. engineConfiguration->adcVcc?
|
2020-08-28 18:13:50 -07:00
|
|
|
constexpr float ratio = 3.3f / 4095.0f;
|
|
|
|
|
|
|
|
size_t localCount = sampleCount;
|
|
|
|
|
|
|
|
// Prepare the steady state at vcc/2 so that there isn't a step
|
|
|
|
// when samples begin
|
2021-01-14 19:34:06 -08:00
|
|
|
// todo: reduce magic constants. engineConfiguration->adcVcc?
|
2020-08-28 18:13:50 -07:00
|
|
|
knockFilter.cookSteadyState(3.3f / 2);
|
|
|
|
|
|
|
|
// Compute the sum of squares
|
2021-01-14 19:34:06 -08:00
|
|
|
for (size_t i = 0; i < localCount; i++) {
|
2020-08-28 18:13:50 -07:00
|
|
|
float volts = ratio * sampleBuffer[i];
|
|
|
|
|
|
|
|
float filtered = knockFilter.filter(volts);
|
|
|
|
|
|
|
|
sumSq += filtered * filtered;
|
|
|
|
}
|
|
|
|
|
|
|
|
// mean of squares (not yet root)
|
|
|
|
float meanSquares = sumSq / localCount;
|
|
|
|
|
|
|
|
// RMS
|
|
|
|
float db = 10 * log10(meanSquares);
|
|
|
|
|
2020-08-31 18:05:33 -07:00
|
|
|
tsOutputChannels.knockLevels[currentCylinderIndex] = roundf(clampF(-100, db, 100));
|
2020-08-28 18:13:50 -07:00
|
|
|
tsOutputChannels.knockLevel = db;
|
|
|
|
|
|
|
|
knockNeedsProcess = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void KnockThread::ThreadTask() {
|
2021-01-14 19:34:06 -08:00
|
|
|
while (1) {
|
2020-08-28 18:13:50 -07:00
|
|
|
chBSemWait(&knockSem);
|
|
|
|
|
|
|
|
ScopePerf perf(PE::SoftwareKnockProcess);
|
|
|
|
processLastKnockEvent();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif // EFI_SOFTWARE_KNOCK
|