Files
Arduino/CPU_temp/README.md
T
oskarvitaliiandClaude Opus 5 4344fa8873 feat(agent): слать загрузку оперативной памяти ключом M
Прошивка её пока не показывает - на 72x40 нет места под третий
показатель. Это намеренно: неизвестный ключ прошивка игнорирует, поэтому
агента можно обновлять независимо от плат.

Память берётся у железа с именем Total Memory - это физическая память.
У соседнего Virtual Memory датчик называется точно так же, но считает
вместе с файлом подкачки и показывает заниженную цифру: 87% против
фактических 97%.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 19:04:11 +07:00

21 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;M=97
Ключ Смысл
T температура CPU Package, °C
L общая загрузка CPU, %
M загрузка оперативной памяти, %

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

Берётся он у железа с именем 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% её оперативной памяти.