Мгновенное значение плавает на +-1 градус, и цифра мельтешила каждую секунду при полном покое. На экран теперь идёт среднее за TEMP_AVG_SAMPLES последних замеров; агент шлёт раз в секунду, так что это ровно 5 секунд. Сумма пересчитывается заново на каждом шаге, а не копится: на пяти числах это бесплатно, зато нет накопления ошибки округления за сутки непрерывной работы. Окно сбрасывается при обрыве связи, иначе после паузы среднее считалось бы по значениям до обрыва. Тревога переведена на усреднённое значение: иначе экран показывал бы 84, когда мигание уже идёт. Загрузка и график намеренно остались сырыми. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
20 KiB
CPU Temp — индикатор температуры процессора на ESP32-C3
Небольшая плата ESP32-C3 со встроенным OLED-экраном 0.42″ стоит на столе и показывает температуру процессора компьютера: крупная цифра, загрузка CPU рядом и график за последнюю минуту. При перегреве экран мигает негативом.
LibreHardwareMonitorLib.dll ──> PowerShell-агент ──COM (USB CDC)──> ESP32-C3 ──I2C──> OLED 72×40
(через драйвер PawnIO) раз в секунду "T=67.0;L=34" U8g2
Плата ничего не измеряет сама. Она принимает готовые строки по USB и рисует их. Всё чтение датчиков происходит на компьютере — иначе никак: температура CPU доступна только через драйвер уровня ядра.
Две поправки к изображению: экран на этой плате 0.42″ (72×40), а не 0.96″; график строится по температуре, а не по загрузке — загрузка выводится цифрой.
Подключение — по USB-кабелю, которым плата и так питается. Wi-Fi не используется: настраивать нечего, задержки нет.
Что понадобится
| Что | Примечание | |
|---|---|---|
| Железо | Плата ESP32-C3 со встроенным OLED 0.42″ (72×40) | «фиолетовая» плата с разъёмом Type-C |
| Кабель USB Type-C | он же питание, он же канал данных | |
| ПО на ПК | Windows | агент написан под Windows PowerShell 5.1 |
| Arduino IDE 2.x | сборка и заливка прошивки | |
| LibreHardwareMonitor | чтение датчиков | |
| PawnIO | драйвер, без него температуры не будет |
Всё программное обеспечение бесплатное.
Часть 1. Прошивка платы
1.1 Arduino IDE
Скачать с https://www.arduino.cc/en/software и установить. Проект собран на версии 2.3.10.
1.2 Ядро ESP32
Инструменты → Плата → Менеджер плат, найти esp32 (Espressif Systems), установить. Проект собран на версии 3.3.8.
Добавлять URL в настройках IDE не нужно: пакет есть в штатном индексе Arduino.
1.3 Библиотека U8g2
Инструменты → Управлять библиотеками, найти U8g2 (автор oliver), установить. Проект собран на версии 2.36.19.
Библиотека поддерживает экран 72×40 готовым драйвером. Это важнее, чем кажется: у такого экрана ненулевое смещение по обеим осям, и вручную его подбирать пришлось бы вслепую.
1.4 Настройки платы
Подключить плату кабелем. В меню Инструменты выставить:
| Пункт меню | Значение |
|---|---|
| Плата | ESP32C3 Dev Module |
| USB CDC On Boot | Enabled ← обязательно |
| Flash Size | 4MB (32Mb) |
| Partition Scheme | Default 4MB with spiffs |
| Upload Speed | 921600 |
| Порт | тот, что появился после подключения платы |
Два пункта критичны:
- USB CDC On Boot = Enabled. По умолчанию стоит
Disabled, и тогдаSerialуходит в аппаратный UART вместо USB. Прошивка соберётся и зальётся, но данные до платы доходить не будут, а на экране навсегда останетсяNO DATA. - Flash Size должен совпадать с физическим объёмом флеша. У этой платы 4 МБ. Посмотреть фактический объём можно через Инструменты → Get Board Info или в выводе утилиты прошивки. При несовпадении разделы уезжают за границу микросхемы.
Если номер COM-порта неочевиден: Диспетчер устройств → Порты (COM и LPT),
плата видна как устройство с идентификатором VID_303A&PID_1001 (Espressif).
1.5 Определить пины дисплея
На этой плате экран подключён к SDA = GPIO5, SCL = GPIO6, адрес 0x3C —
уже прописано в config.h, шаг можно пропустить.
Если плата похожая, но другая, не доверяйте надписям на текстолите: подписи
GPIO8/GPIO9 рядом с пометкой I2C относятся к стандартным пинам чипа, а не к
разводке встроенного экрана. Залейте tools/i2c_scan/i2c_scan.ino и откройте
монитор порта на скорости 115200. Сканер перебирает пары пинов и печатает
найденное:
--- I2C scan ---
SDA=5 SCL=6: device 0x3C
SDA=6 SCL=5: nothing
SDA=8 SCL=9: nothing
SDA=9 SCL=8: nothing
Найденную пару вписать в config.h (PIN_SDA, PIN_SCL).
1.6 Залить прошивку
Открыть CPU_temp.ino в Arduino IDE и нажать Загрузить.
Сразу после заливки на экране появится NO DATA — так и должно быть: агента на
компьютере ещё нет.
То же самое из командной строки:
arduino-cli compile -b esp32:esp32:esp32c3:CDCOnBoot=cdc,FlashSize=4M,PartitionScheme=default .
arduino-cli upload -p COM4 -b esp32:esp32:esp32c3:CDCOnBoot=cdc,FlashSize=4M,PartitionScheme=default .
Вместо COM4 подставить свой порт.
Часть 2. Агент на компьютере
Агент — скрипт agent/cpu_temp_agent.ps1. Он читает датчики и раз в секунду
шлёт плате строку.
2.1 LibreHardwareMonitor
Скачать со страницы релизов
https://github.com/LibreHardwareMonitor/LibreHardwareMonitor/releases архив
LibreHardwareMonitor.zip и распаковать в D:\Soft\LibreHardwareMonitor.
- Путь можно выбрать другой, но тогда поправьте переменную
$LhmPathв начале скрипта. - Не берите
LibreHardwareMonitor.NET.10.zip— это сборка под .NET 10, а Windows PowerShell 5.1 работает на .NET Framework и такую сборку не загрузит. - Запускать саму программу не нужно. Используются только библиотека
LibreHardwareMonitorLib.dllи лежащий рядомHidSharp.dll.
2.2 Драйвер PawnIO
Скачать со страницы https://github.com/namazso/PawnIO.Setup/releases файл
PawnIO_setup.exe и запустить без аргументов, двойным щелчком, затем пройти
мастер. Потребуются права администратора.
Установщик не принимает ключей командной строки: на /S или /quiet он
отвечает кодом 87 и молча ничего не делает.
Зачем это нужно. Начиная с версии 0.9.5 LibreHardwareMonitor читает регистры процессора через PawnIO, а не через прежний WinRing0, и в архив с программой драйвер не входит. Без него загрузка CPU читается, а все температуры пустые — даже из консоли администратора. Симптом сбивает с толку: выглядит как нехватка прав, хотя права ни при чём.
2.3 Проверить вручную
Открыть PowerShell от имени администратора (правый щелчок по значку → «Запуск от имени администратора») и выполнить:
powershell -ExecutionPolicy Bypass -File "D:\Soft\Arduino\CPU_temp\agent\cpu_temp_agent.ps1" -Echo
Ожидается поток строк раз в секунду:
connected: COM4
T=47.0;L=24
T=48.0;L=14
Одновременно на экране платы появится температура. Остановить — Ctrl+C.
Права администратора нужны всегда: без них драйвер не открывается. Агент проверяет это при старте и называет причину, а не молчит.
2.4 Автозапуск при входе в систему
В консоли администратора:
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File "D:\Soft\Arduino\CPU_temp\agent\cpu_temp_agent.ps1"'
$trigger = New-ScheduledTaskTrigger -AtLogOn -User "$env:USERDOMAIN\$env:USERNAME"
$set = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit ([TimeSpan]::Zero)
Register-ScheduledTask -TaskName 'CPU Temp Agent' -Action $action -Trigger $trigger -Settings $set -RunLevel Highest -Force
-RunLevel Highestобязателен — это и есть «выполнить с наивысшими правами».- Нулевой
ExecutionTimeLimitнужен, иначе Планировщик убьёт агента через трое суток.
Проверить, не перезагружаясь:
Start-ScheduledTask -TaskName 'CPU Temp Agent'
(Get-ScheduledTask -TaskName 'CPU Temp Agent').State
Ожидается Running.
Удалить задание:
Unregister-ScheduledTask -TaskName 'CPU Temp Agent' -Confirm:$false
Повторная заливка прошивки
Плата прошивается через тот же COM-порт, по которому идут данные. Пока агент
держит порт, заливка падает с PermissionError. Порядок такой:
Stop-ScheduledTask -TaskName 'CPU Temp Agent'
# ... залить прошивку ...
Start-ScheduledTask -TaskName 'CPU Temp Agent'
Порт может держать и монитор порта Arduino IDE — его тоже надо закрыть.
Настройка
Всё, что имеет смысл менять, собрано в config.h:
| Константа | По умолчанию | Смысл |
|---|---|---|
PIN_SDA, PIN_SCL |
5, 6 | пины экрана |
DISPLAY_ROTATION |
U8G2_R2 |
ориентация; U8G2_R0 — повернуть обратно |
ALARM_ON_C |
85.0 | температура включения тревоги |
ALARM_OFF_C |
82.0 | температура отпускания тревоги |
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 |
SAMPLE_MS |
1000 | шаг истории графика |
DRAW_MS |
200 | период перерисовки экрана |
Почему порогов тревоги два, а не один
С одним порогом тревога дёргалась бы включениями и выключениями каждую секунду, пока температура топчется на границе. Поэтому включение и отпускание разведены: загорается на 85, гаснет только при спуске до 82.
Почему цифра температуры усредняется
Мгновенное значение плавает на ±1 °C вокруг стабильного, и цифра мельтешит
каждую секунду, хотя ничего не происходит — это отвлекает. На экран выводится
среднее за TEMP_AVG_SAMPLES последних замеров, то есть за 5 секунд: агент
шлёт раз в секунду.
Плата за это — запаздывание реакции на реальный нагрев примерно на 2–3 секунды. Для температуры процессора незаметно: она физически инертна.
Что осталось несглаженным и почему:
- загрузка CPU — её скачки настоящие, ради них на неё и смотрят;
- график — 12 пикселей на диапазон 60 °C дают 5 °C на пиксель, шум там и так не виден, зато история остаётся сырой.
Тревога считается по усреднённому значению, а не по сырому: иначе экран показывал бы 84, когда мигание уже началось. Побочно это защищает от срабатывания на одиночный выброс — перегрев должен продержаться пару секунд.
Почему шкала графика фиксированная
Автомасштаб отвергнут сознательно: в простое колебания ±1 °C рисовались бы во всю высоту экрана и выглядели тревожно, хотя ничего не происходит.
Что показывает экран
| Состояние | Экран | Светодиод GPIO8 |
|---|---|---|
| Норма | температура, загрузка, график | погашен |
| Перегрев | то же, мигает негативом | мигает синхронно с экраном |
| Нет данных дольше 5 с | NO DATA |
горит ровно |
В режиме NO DATA история графика не пополняется — иначе обрыв связи выглядел бы
как резкое падение температуры.
Протокол обмена
Агент шлёт плате по одной строке в секунду, в конце — перевод строки:
T=67.0;L=34
| Ключ | Смысл |
|---|---|
T |
температура CPU Package, °C |
L |
общая загрузка CPU, % |
Неизвестные ключи прошивка молча игнорирует, поэтому протокол расширяем: можно
добавить, например, G= для видеокарты, не трогая уже прошитые платы.
Разделитель дробной части — только точка. Агент форматирует числа через
InvariantCulture: в русской локали иначе вышло бы T=67,0, и разбор на плате
прочитал бы 67, молча потеряв дробную часть.
Плату можно проверить без агента, отправив строки вручную. Порт при этом должен быть свободен, то есть агента надо остановить:
$sp = New-Object System.IO.Ports.SerialPort 'COM4',115200,'None',8,'One'
$sp.DtrEnable = $true; $sp.RtsEnable = $false
$sp.Open()
40..90 | ForEach-Object { $sp.WriteLine("T=$_.0;L=50"); Start-Sleep -Milliseconds 400 }
$sp.Close()
Так же удобно проверять тревогу, не меняя пороги в config.h: подать значение
выше 85, затем 83 (тревога должна сохраниться) и 81 (должна отпустить).
Если не работает
| Симптом | Причина | Что делать |
|---|---|---|
На экране всегда NO DATA, хотя агент шлёт строки |
USB CDC On Boot выключен, Serial ушёл в аппаратный UART |
включить в меню платы и перезалить |
| Экран не светится вовсе | неверные пины I2C | залить tools/i2c_scan, найти пару, вписать в config.h |
| Агент пишет, что температура недоступна | не установлен PawnIO | установить драйвер, см. 2.2 |
| Агент отказывается запускаться из-за прав | консоль не от администратора | открыть «от имени администратора» |
Заливка падает с PermissionError |
порт держит агент или монитор порта IDE | Stop-ScheduledTask, закрыть монитор |
| Температура без дробной части | число пришло с запятой вместо точки | агент должен форматировать через InvariantCulture |
Скрипт .ps1 падает с ошибками про скобки |
файл сохранён в UTF-8 без BOM | пересохранить в UTF-8 с BOM: PowerShell 5.1 иначе читает файл как ANSI и портит кириллицу |
| После перетыкания в другой разъём данные не идут | сменился номер порта | ничего не делать: агент ищет плату по VID_303A&PID_1001 |
Как устроено
| Путь | Что это |
|---|---|
config.h |
все настройки |
CPU_temp.ino |
свежесть данных, гистерезис тревоги, светодиод |
link.h / link.cpp |
неблокирующее чтение строки из Serial и её разбор |
ui.h / ui.cpp |
отрисовка через U8g2, кольцевой буфер истории, негатив |
tools/i2c_scan/ |
одноразовый скетч поиска пинов дисплея |
agent/cpu_temp_agent.ps1 |
агент для ПК |
agent/README.md |
подробности по агенту |
docs/ |
спецификация, план реализации и бэклог |
Разделение простое: link ничего не знает об экране, ui не знает, откуда
взялись данные, а логика состояний живёт в скетче.
Расход ресурсов
Агент занимает около 70 МБ оперативной памяти и со временем не растёт. Почти всё это — обвязка Windows PowerShell и .NET Framework; сам цикл держит в памяти несколько чисел.
Прошивка занимает 26% флеша платы и 4% её оперативной памяти.
