Сверка документации с кодом выявила четыре расхождения, все появились после вчерашней правки протокола: - схема в шапке и пример вывода агента показывали строку без ключа M, хотя агент его шлёт: человек сверил бы свой вывод с образцом и решил, что у него что-то не так; - в таблице состава не были упомянуты кнопка и выбор экрана; - формулировка "короткое нажатие" подразумевала различие с длинным, которого в коде нет. Все 16 констант из таблицы настроек сверены с config.h, поведенческие утверждения - с кодом, расход прошивки подтверждён сборкой: 26% и 4%. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
23 KiB
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% её оперативной памяти.
