/* Nucleo C031C6 - ADC median-filter + validated thermal alarm demo
Paste into Wokwi Arduino-style project (Nucleo C031C6).
Wiring: Potentiometer -> left=3V3, right=GND, wiper=PA0/A0
*/
const int ADC_PIN = A0; // PA0 analog input in Wokwi
const int LED_PIN = LED_BUILTIN; // on-board LED (PA5 typically)
//
// Configurable parameters
//
const int SAMPLE_RATE_MS = 100; // sample every 100 ms (10 Hz)
const int WINDOW_SIZE = 5; // median filter window (must be odd; here 5)
const int VALID_COUNT = 3; // consecutive filtered samples >= threshold to alarm
const float TEMP_THRESHOLD = 70.0; // degC threshold to consider over-temp
const float HYSTERESIS = 5.0; // degC hysteresis for clearing alarm
const float TEMP_MAX = 150.0; // mapping top temperature (ADC_MAX -> TEMP_MAX)
//
// Runtime variables
//
float windowArr[WINDOW_SIZE];
int widx = 0;
int validCounter = 0;
bool alarmState = false;
int ADC_MAX = 4095; // will be detected in setup if actual ADC is 1023
// ---------- Helper functions ----------
// Map ADC raw to temperature (0 .. TEMP_MAX)
float adcToTemp(int raw) {
if (ADC_MAX <= 0) return 0.0f;
return ((float)raw / (float)ADC_MAX) * TEMP_MAX;
}
// Median of WINDOW_SIZE elements (WINDOW_SIZE is 5 here)
float median5(const float *v) {
// copy
float t[WINDOW_SIZE];
for (int i = 0; i < WINDOW_SIZE; ++i) t[i] = v[i];
// insertion sort (small fixed array)
for (int i = 1; i < WINDOW_SIZE; ++i) {
float key = t[i];
int j = i - 1;
while (j >= 0 && t[j] > key) {
t[j + 1] = t[j];
j--;
}
t[j + 1] = key;
}
return t[WINDOW_SIZE / 2]; // middle element
}
// Slight safe clamp helper (not strictly necessary)
int clampInt(int v, int lo, int hi) {
if (v < lo) return lo;
if (v > hi) return hi;
return v;
}
// ---------- Setup & Loop ----------
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
// small delay for serial to start
delay(200);
// Detect ADC resolution: ask for a single read; if <=1023 assume 10-bit
int sample = analogRead(ADC_PIN);
if (sample <= 1023) ADC_MAX = 1023;
else ADC_MAX = 4095;
Serial.print("Detected ADC_MAX = ");
Serial.println(ADC_MAX);
Serial.print("Mapping ADC -> 0..");
Serial.print(TEMP_MAX, 1);
Serial.println(" °C");
// Initialize median window with current reading to avoid startup artifacts
for (int i = 0; i < WINDOW_SIZE; ++i) {
int r = analogRead(ADC_PIN);
windowArr[i] = adcToTemp(r);
delay(10);
}
Serial.println("Median-filtered alarm demo started");
Serial.println("Format: RAW_ADC RAW_TEMP°C FILTER°C VALID_CNT ALARM");
}
void loop() {
// Read raw ADC
int raw_adc = analogRead(ADC_PIN);
raw_adc = clampInt(raw_adc, 0, ADC_MAX);
// Convert raw to temperature
float rawTemp = adcToTemp(raw_adc);
// Update sliding window
windowArr[widx] = rawTemp;
widx = (widx + 1) % WINDOW_SIZE;
// Compute median filtered temperature
float filtTemp = median5(windowArr);
// Validation logic: require VALID_COUNT consecutive filtered samples >= threshold
if (filtTemp >= TEMP_THRESHOLD) {
validCounter++;
if (validCounter >= VALID_COUNT && !alarmState) {
alarmState = true;
digitalWrite(LED_PIN, HIGH);
Serial.println("🔥 VALIDATED THERMAL ALARM TRIGGERED 🔥");
}
} else {
// Clear only when below (threshold - hysteresis)
if (filtTemp < (TEMP_THRESHOLD - HYSTERESIS)) {
// Reset counter and clear alarm
validCounter = 0;
if (alarmState) {
alarmState = false;
digitalWrite(LED_PIN, LOW);
Serial.println("✔ Alarm CLEARED");
}
} else {
// inside hysteresis band: gently decay validCounter to avoid sticky counts
if (validCounter > 0) validCounter = max(validCounter - 1, 0);
}
}
// Print diagnostics
Serial.print(raw_adc);
Serial.print("\t");
Serial.print(rawTemp, 2);
Serial.print(" C\t");
Serial.print(filtTemp, 2);
Serial.print(" C\t");
Serial.print(validCounter);
Serial.print("\t");
Serial.println(alarmState ? "ALARM" : "OK");
delay(SAMPLE_RATE_MS);
}