Files
Arduino/CPU_temp/README.md
T
oskarvitaliiandClaude Opus 5 fcc96f5090 feat(ui): сглаживать температуру скользящим средним за 5 замеров
Мгновенное значение плавает на +-1 градус, и цифра мельтешила каждую
секунду при полном покое. На экран теперь идёт среднее за
TEMP_AVG_SAMPLES последних замеров; агент шлёт раз в секунду, так что
это ровно 5 секунд.

Сумма пересчитывается заново на каждом шаге, а не копится: на пяти
числах это бесплатно, зато нет накопления ошибки округления за сутки
непрерывной работы. Окно сбрасывается при обрыве связи, иначе после
паузы среднее считалось бы по значениям до обрыва.

Тревога переведена на усреднённое значение: иначе экран показывал бы 84,
когда мигание уже идёт. Загрузка и график намеренно остались сырыми.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 00:41:20 +07:00

20 KiB
Raw Blame History

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% её оперативной памяти.