// Индикатор температуры CPU: плата принимает строки "T=67.0;L=34" по USB-COM // от агента на ПК и рисует их на встроенном OLED 72x40. Сама ничего не меряет. // // Все настройки — в config.h. #include "config.h" #include "link.h" #include "ui.h" static CpuStats stats = {0.0f, 0}; static uint32_t lastData = 0; static uint32_t lastSample = 0; static uint32_t lastDraw = 0; static bool everGot = false; static bool alarmActive = false; static bool wasFresh = false; static float tempShown = 0.0f; // усреднённое значение, именно оно на экране // Скользящее среднее температуры. Сумма пересчитывается заново, а не копится: // на пяти числах это бесплатно, зато нет накопления ошибки округления за сутки // непрерывной работы. static float tempWindow[TEMP_AVG_SAMPLES]; static uint8_t tempCount = 0; static uint8_t tempIndex = 0; static void tempAvgReset() { tempCount = 0; tempIndex = 0; } static float tempAvgPush(float v) { tempWindow[tempIndex] = v; tempIndex = (uint8_t)((tempIndex + 1) % TEMP_AVG_SAMPLES); if (tempCount < TEMP_AVG_SAMPLES) tempCount++; float sum = 0.0f; for (uint8_t i = 0; i < tempCount; i++) sum += tempWindow[i]; return sum / tempCount; } void setup() { pinMode(PIN_LED, OUTPUT); digitalWrite(PIN_LED, !LED_ACTIVE_LEVEL); linkBegin(); uiBegin(); } // Гистерезис: включаемся на верхнем пороге, отпускаем только на нижнем. static void updateAlarm(float tempC) { if (!alarmActive && tempC >= ALARM_ON_C) alarmActive = true; else if (alarmActive && tempC <= ALARM_OFF_C) alarmActive = false; } void loop() { CpuStats fresh; if (linkPoll(fresh)) { stats = fresh; tempShown = tempAvgPush(fresh.tempC); lastData = millis(); everGot = true; } const bool isFresh = everGot && (millis() - lastData < STALE_MS); // После паузы связи среднее нельзя считать по значениям до обрыва. if (wasFresh && !isFresh) tempAvgReset(); wasFresh = isFresh; if (isFresh) { // Тревога по усреднённому значению: иначе экран показывал бы 84, а мигание // уже шло бы. Побочно это защищает от срабатывания на одиночный выброс. updateAlarm(tempShown); } else { alarmActive = false; // без данных о перегреве судить не по чему } // Общая фаза для экрана и светодиода, чтобы они моргали синхронно. const bool blinkOn = (millis() / ALARM_BLINK_MS) % 2 == 0; // Тревога — мигает, нет данных — горит ровно, норма — погашен. bool ledOn; if (!isFresh) ledOn = true; else if (alarmActive) ledOn = blinkOn; else ledOn = false; digitalWrite(PIN_LED, ledOn ? LED_ACTIVE_LEVEL : !LED_ACTIVE_LEVEL); // Историю дописываем только при живой связи: пауза не должна рисоваться провалом. if (isFresh && millis() - lastSample >= SAMPLE_MS) { lastSample = millis(); uiPushSample(stats.tempC); // график по сырым: 5 градусов на пиксель, шум там не виден } if (millis() - lastDraw >= DRAW_MS) { lastDraw = millis(); uiRender(isFresh, tempShown, stats.loadPct, alarmActive && blinkOn); } }