Files
oskarvitaliiandClaude Opus 5 f25f24b6c5 docs: привести README в соответствие с реализацией
Сверка документации с кодом выявила четыре расхождения, все появились
после вчерашней правки протокола:

- схема в шапке и пример вывода агента показывали строку без ключа M,
  хотя агент его шлёт: человек сверил бы свой вывод с образцом и решил,
  что у него что-то не так;
- в таблице состава не были упомянуты кнопка и выбор экрана;
- формулировка "короткое нажатие" подразумевала различие с длинным,
  которого в коде нет.

Все 16 констант из таблицы настроек сверены с config.h, поведенческие
утверждения - с кодом, расход прошивки подтверждён сборкой: 26% и 4%.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 12:08:49 +07:00

23 KiB
Raw Permalink Blame History

CPU Temp — индикатор температуры процессора на ESP32-C3

Небольшая плата ESP32-C3 со встроенным OLED-экраном 0.42″ стоит на столе и показывает состояние компьютера: крупная цифра, мелкий показатель рядом и график за последнюю минуту. Экранов два — процессор и оперативная память, переключаются кнопкой BOOT. При перегреве процессора экран мигает негативом.

LibreHardwareMonitorLib.dll ──> PowerShell-агент ──COM (USB CDC)──> ESP32-C3 ──I2C──> OLED 72×40
   (через драйвер PawnIO)         раз в секунду    "T=67.0;L=34;M=97"       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;M=97
T=48.0;L=14;M=97

Одновременно на экране платы появится температура. Остановить — 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 светодиод
PIN_BUTTON 9 кнопка BOOT, переключает экраны
BUTTON_DEBOUNCE_MS 50 защита от дребезга контактов
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 рисовались бы во всю высоту экрана и выглядели тревожно, хотя ничего не происходит.


Что показывает экран

Экранов два, переключаются нажатием кнопки BOOT (длительность не важна):

Экран Крупно Мелко График
Процессор температура, °C загрузка, % температура, шкала 30–90 °C
Память загрузка, % знак процента загрузка, шкала 0–100 %

Значок в правом верхнем углу говорит, какой экран открыт: микросхема — процессор, планка памяти — ОЗУ.

Состояние Экран Светодиод GPIO8
Норма выбранный экран погашен
Перегрев принудительно экран процессора, мигает негативом мигает синхронно с экраном
Нет данных дольше 5 с NO DATA горит ровно

Тревога в приоритете. При перегреве плата сама возвращается на экран процессора, и кнопка перестаёт работать до тех пор, пока температура не упадёт. Смысл тревоги в том, что её нельзя прощёлкать.

Выбранный экран не запоминается при перезагрузке платы: энергонезависимого хранилища в прошивке нет вовсе, после сброса всегда показывается процессор.

Тревога считается только по температуре процессора. По памяти её нет намеренно: загрузка ОЗУ под 100 % — обычное рабочее состояние, мигать на него бессмысленно.

В режиме NO DATA история графиков не пополняется — иначе обрыв связи выглядел бы как резкое падение значений.


Протокол обмена

Агент шлёт плате по одной строке в секунду, в конце — перевод строки:

T=67.0;L=34;M=97
Ключ Смысл
T температура CPU Package, °C
L общая загрузка CPU, %
M загрузка оперативной памяти, %

Ключ M показывается на отдельном экране памяти — третьему показателю на экране 72×40 места нет. Если агент старый и ключа M не шлёт, экран памяти из листания выпадает сам, и кнопка ничего не делает.

Берётся он у железа с именем Total Memory — это физическая память. У соседнего Virtual Memory датчик называется точно так же, но считает вместе с файлом подкачки и показывает заниженную цифру.

Неизвестные ключи прошивка молча игнорирует, поэтому протокол расширяем: можно добавить, например, 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% её оперативной памяти.