diff --git a/CPU_temp/CPU_temp.ino b/CPU_temp/CPU_temp.ino index c04cff6..eb6c06a 100644 --- a/CPU_temp/CPU_temp.ino +++ b/CPU_temp/CPU_temp.ino @@ -13,6 +13,30 @@ 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); @@ -32,14 +56,21 @@ 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) { - updateAlarm(stats.tempC); + // Тревога по усреднённому значению: иначе экран показывал бы 84, а мигание + // уже шло бы. Побочно это защищает от срабатывания на одиночный выброс. + updateAlarm(tempShown); } else { alarmActive = false; // без данных о перегреве судить не по чему } @@ -57,11 +88,11 @@ void loop() { // Историю дописываем только при живой связи: пауза не должна рисоваться провалом. if (isFresh && millis() - lastSample >= SAMPLE_MS) { lastSample = millis(); - uiPushSample(stats.tempC); + uiPushSample(stats.tempC); // график по сырым: 5 градусов на пиксель, шум там не виден } if (millis() - lastDraw >= DRAW_MS) { lastDraw = millis(); - uiRender(isFresh, stats.tempC, stats.loadPct, alarmActive && blinkOn); + uiRender(isFresh, tempShown, stats.loadPct, alarmActive && blinkOn); } } diff --git a/CPU_temp/README.md b/CPU_temp/README.md index 9f4498c..3ae7c3b 100644 --- a/CPU_temp/README.md +++ b/CPU_temp/README.md @@ -241,6 +241,7 @@ Start-ScheduledTask -TaskName 'CPU Temp Agent' | `ALARM_BLINK_MS` | 500 | полупериод мигания | | `PIN_LED` | 8 | светодиод | | `LED_ACTIVE_LEVEL` | `LOW` | уровень, при котором светодиод горит | +| `TEMP_AVG_SAMPLES` | 5 | окно сглаживания температуры, замеров | | `SCALE_MIN`, `SCALE_MAX` | 30.0, 90.0 | шкала графика, °C | | `SERIAL_BAUD` | 115200 | скорость обмена; менять вместе с агентом | | `STALE_MS` | 5000 | молчание, после которого показывается `NO DATA` | @@ -253,6 +254,26 @@ Start-ScheduledTask -TaskName 'CPU Temp Agent' пока температура топчется на границе. Поэтому включение и отпускание разведены: загорается на 85, гаснет только при спуске до 82. +### Почему цифра температуры усредняется + +Мгновенное значение плавает на ±1 °C вокруг стабильного, и цифра мельтешит +каждую секунду, хотя ничего не происходит — это отвлекает. На экран выводится +среднее за `TEMP_AVG_SAMPLES` последних замеров, то есть за 5 секунд: агент +шлёт раз в секунду. + +Плата за это — запаздывание реакции на реальный нагрев примерно на 2–3 секунды. +Для температуры процессора незаметно: она физически инертна. + +Что осталось несглаженным и почему: + +- **загрузка CPU** — её скачки настоящие, ради них на неё и смотрят; +- **график** — 12 пикселей на диапазон 60 °C дают 5 °C на пиксель, шум там и так + не виден, зато история остаётся сырой. + +Тревога считается по усреднённому значению, а не по сырому: иначе экран +показывал бы 84, когда мигание уже началось. Побочно это защищает от +срабатывания на одиночный выброс — перегрев должен продержаться пару секунд. + ### Почему шкала графика фиксированная Автомасштаб отвергнут сознательно: в простое колебания ±1 °C рисовались бы во @@ -335,7 +356,7 @@ $sp.Close() | `tools/i2c_scan/` | одноразовый скетч поиска пинов дисплея | | `agent/cpu_temp_agent.ps1` | агент для ПК | | `agent/README.md` | подробности по агенту | -| `docs/` | спецификация и план реализации | +| `docs/` | спецификация, план реализации и [бэклог](docs/BACKLOG.md) | Разделение простое: `link` ничего не знает об экране, `ui` не знает, откуда взялись данные, а логика состояний живёт в скетче. diff --git a/CPU_temp/config.h b/CPU_temp/config.h index 236329d..7b1b4d3 100644 --- a/CPU_temp/config.h +++ b/CPU_temp/config.h @@ -37,6 +37,15 @@ static const uint8_t PIN_LED = 8; // высокий уровень и нужен. static const uint8_t LED_ACTIVE_LEVEL = LOW; +// --- Сглаживание показаний ------------------------------------------------- + +// Скользящее среднее температуры: цифра не должна мельтешить на +-1 градус, +// пока ничего не происходит. Единица - один принятый замер, а агент шлёт раз +// в секунду, поэтому 5 означает "среднее за последние 5 секунд". +// Загрузка сознательно не сглаживается: её скачки настоящие, ради них на неё +// и смотрят. +static const uint8_t TEMP_AVG_SAMPLES = 5; + // --- График ---------------------------------------------------------------- // Шкала фиксированная: при автомасштабе шум +-1 градус в простое diff --git a/CPU_temp/docs/BACKLOG.md b/CPU_temp/docs/BACKLOG.md new file mode 100644 index 0000000..4f2cff7 --- /dev/null +++ b/CPU_temp/docs/BACKLOG.md @@ -0,0 +1,90 @@ +# Бэклог + +Идеи и незакрытые хвосты по проекту. Не план работ: сюда складывается то, что +решено не делать сейчас, чтобы не потерялось. + +--- + +## 1. Другие показатели и листалка по кнопке BOOT + +**Что хочется.** Показывать не только процессор: видеокарту, оперативную память, +диски. Переключать экраны кнопкой **BOOT** на плате — нажал, увидел следующий +набор данных. + +**Почему это дёшево со стороны протокола.** Формат `T=67.0;L=34` изначально +сделан расширяемым: прошивка молча игнорирует незнакомые ключи. Агент может +начать слать `G=61;GL=88;M=73` (видеокарта, её загрузка, память), и уже прошитые +платы от этого не сломаются — просто не покажут новое. + +**Что придётся решить при реализации:** + +- **Кнопка BOOT сидит на GPIO9.** Это пин начальной загрузки: при сбросе он + должен быть в высоком уровне, иначе плата уйдёт в режим прошивки. Читать его + как `INPUT_PULLUP` в работе безопасно, но проверить фактически. +- **Дребезг контактов.** Нужна программная защита, иначе одно нажатие пролистает + три экрана. +- **Свой макет под каждый экран.** На 72×40 не получится «то же самое, только + другие цифры»: у памяти уместнее проценты и гигабайты, у диска — температура + без графика. Каждый экран проектировать отдельно. +- **Отдельная история на каждый показатель.** Сейчас кольцевой буфер один и + хранит температуру CPU. На каждый график нужен свой буфер: 72 байта на штуку, + памяти достаточно. +- **Что показывать, если данных нет.** Например, в системе нет дискретной + видеокарты — экран GPU надо либо пропускать при листании, либо честно писать, + что источника нет. +- **Запоминать ли выбранный экран** между перезагрузками. Если да, понадобится + энергонезависимое хранилище, которого у прошивки сейчас нет вовсе. +- **Тревога при листании.** Сейчас перегрев виден всегда, потому что экран один. + С листалкой можно стоять на экране памяти и не увидеть, что горит процессор. + Нужно решить: принудительно перекидывать на экран CPU, показывать тревогу + поверх любого экрана или мигать только светодиодом. + +Последний пункт — самый важный: он затрагивает смысл устройства, а не удобство. + +--- + +## 2. Обрезание строки при 100° и 100% + +При трёхзначной температуре и трёхзначной загрузке верхняя строка занимает 79 +пикселей из 72: `100` — 49 px, зазор 2, `100%` — 28 px. Лишние 7 пикселей +уходят за правый край, и загрузка обрезается. + +Дефект существовал с самого начала, проявляется только под предельной нагрузкой. +Для i5-11400H это не гипотеза: предельная температура около 100 °C. + +Решение на выбор: при трёхзначной температуре переходить на более мелкий шрифт +загрузки либо убирать знак процента. Оценка — около получаса вместе с проверкой. + +Решено оставить как есть: в такой момент экран всё равно мигает тревогой. + +--- + +## 3. Автозапуск не проверен перезагрузкой + +Задание Планировщика `CPU Temp Agent` зарегистрировано с триггером «при входе в +систему», запускается вручную и работает. Живой перезагрузкой триггер не +испытан. + +Проверится само при следующей перезагрузке: экран должен ожить без участия +пользователя в течение примерно полуминуты. + +--- + +## 4. Скрипт заливки + +Цикл «остановить агента → залить прошивку → запустить агента» делается тремя +командами вручную. Пока правок мало, это терпимо; если работа по экрану +продолжится, стоит свести в `flash.ps1`. + +Причина, по которой шаг вообще нужен: плата прошивается через тот же COM-порт, +по которому идут данные, и занятый порт валит заливку с `PermissionError`. + +--- + +## 5. Вес обзорного изображения + +`docs/promo.png` весит 2,1 МБ при исходниках примерно на 30 КБ. Сжатие до +300–500 КБ не ухудшило бы читаемость, но старый файл всё равно останется в +истории git, так что выигрыш только для будущих клонов. + +Решено оставить как есть.