diff --git a/CPU_temp/README.md b/CPU_temp/README.md index 8a7b1f4..804b9fa 100644 --- a/CPU_temp/README.md +++ b/CPU_temp/README.md @@ -1,107 +1,344 @@ # CPU Temp — индикатор температуры процессора на ESP32-C3 -Плата ESP32-C3 со встроенным OLED 0.42″ (72×40) стоит на столе и показывает -температуру процессора компьютера: крупная цифра, загрузка CPU рядом и график -за последнюю минуту. +Небольшая плата 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 + (через драйвер PawnIO) раз в секунду "T=67.0;L=34" U8g2 ``` -Плата ничего не измеряет сама: она принимает готовые строки и рисует их. Всё -чтение железа происходит на ПК. +**Плата ничего не измеряет сама.** Она принимает готовые строки по USB и рисует +их. Всё чтение датчиков происходит на компьютере — иначе никак: температура CPU +доступна только через драйвер уровня ядра. -## Состав +Подключение — по 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 + +Скачать с и установить. Проект собран на +версии 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 Настройки платы + +Подключить плату кабелем. В меню **Инструменты** выставить: + +| Пункт меню | Значение | |---|---| -| `config.h` | **все настройки**: пины, пороги тревоги, шкала графика, тайминги | -| `CPU_temp.ino` | основной скетч: свежесть данных, гистерезис тревоги, светодиод | -| `link.h` / `link.cpp` | неблокирующее чтение строки из Serial и её разбор | -| `ui.h` / `ui.cpp` | отрисовка через U8g2 и кольцевой буфер истории | -| `tools/i2c_scan/` | одноразовый скетч: поиск пинов встроенного дисплея | -| `agent/` | агент для ПК, см. [agent/README.md](agent/README.md) | -| `docs/` | спецификация и план реализации | +| Плата | **ESP32C3 Dev Module** | +| **USB CDC On Boot** | **Enabled** ← обязательно | +| Flash Size | **4MB (32Mb)** | +| Partition Scheme | Default 4MB with spiffs | +| Upload Speed | 921600 | +| Порт | тот, что появился после подключения платы | -## Железо +Два пункта критичны: -| Параметр | Значение | -|---|---| -| Плата | ESP32-C3 (QFN32), ревизия v0.4, встроенный OLED 0.42″ | -| Флеш | 4 МБ | -| Дисплей | SSD1306 72×40, I2C, адрес `0x3C`, **SDA = GPIO5, SCL = GPIO6** | -| Подключение к ПК | нативный USB, `VID_303A&PID_1001` | +- **USB CDC On Boot = Enabled.** По умолчанию стоит `Disabled`, и тогда `Serial` + уходит в аппаратный UART вместо USB. Прошивка соберётся и зальётся, но данные + до платы доходить не будут, а на экране навсегда останется `NO DATA`. +- **Flash Size должен совпадать с физическим объёмом флеша.** У этой платы 4 МБ. + Посмотреть фактический объём можно через **Инструменты → Get Board Info** или + в выводе утилиты прошивки. При несовпадении разделы уезжают за границу + микросхемы. -Пины дисплея определены сканером `tools/i2c_scan`, а не по шелкографии: подписи -`GPIO8/GPIO9` на плате относятся к дефолтным Wire-пинам чипа, а не к разводке -экрана. +Если номер 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 . ``` -`CDCOnBoot=cdc` обязателен: по умолчанию в ядре стоит `Disabled`, и тогда -`Serial` уходит в аппаратный UART вместо USB, а данные до платы не доходят. +Вместо `COM4` подставить свой порт. -**Перед заливкой остановить агента** — он держит тот же COM-порт: +--- + +## Часть 2. Агент на компьютере + +Агент — скрипт `agent/cpu_temp_agent.ps1`. Он читает датчики и раз в секунду +шлёт плате строку. + +### 2.1 LibreHardwareMonitor + +Скачать со страницы релизов + архив +**`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 + +Скачать со страницы файл +`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 — его тоже надо закрыть. -Одна строка в секунду, `\n` в конце: +--- + +## Настройка + +Всё, что имеет смысл менять, собрано в `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` | уровень, при котором светодиод горит | +| `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 рисовались бы во +всю высоту экрана и выглядели тревожно, хотя ничего не происходит. + +--- + +## Что показывает экран + +| Состояние | Экран | Светодиод GPIO8 | +|---|---|---| +| Норма | температура, загрузка, график | погашен | +| Перегрев | то же, мигает негативом | мигает синхронно с экраном | +| Нет данных дольше 5 с | `NO DATA` | горит ровно | + +В режиме `NO DATA` история графика не пополняется — иначе обрыв связи выглядел бы +как резкое падение температуры. + +--- + +## Протокол обмена + +Агент шлёт плате по одной строке в секунду, в конце — перевод строки: ``` T=67.0;L=34 ``` -`T` — температура CPU Package в °C, `L` — общая загрузка CPU в процентах. -Неизвестные ключи прошивка игнорирует, поэтому протокол можно расширять -(например, `G=` для GPU), не трогая старую прошивку. - -Если валидная строка не приходила дольше 5 секунд, на экране появляется -`NO DATA`, и история перестаёт пополняться — чтобы пауза связи не выглядела -как падение температуры. - -## Экран - -Шкала графика фиксированная, 30–90 °C. Автомасштаб сознательно не используется: -при нём шум ±1 °C в простое рисовался бы горами и создавал ложную тревогу. - -Ориентация задаётся константой `DISPLAY_ROTATION` в `config.h`: `U8G2_R0` — -как разведено на плате, `U8G2_R2` — поворот на 180°. - -## Тревога по перегреву - -Порогов два, `ALARM_ON_C` и `ALARM_OFF_C` (по умолчанию 85 и 82 °C). Тревога -включается на верхнем и отпускает только на нижнем — с одним порогом она -дёргалась бы каждую секунду, пока температура топчется на границе. - -В тревоге экран мигает негативом, а светодиод на GPIO8 мигает с ним в такт -(общая фаза, период `ALARM_BLINK_MS`). - -Светодиод по состояниям: - -| Состояние | Светодиод | +| Ключ | Смысл | |---|---| -| Норма | погашен | -| Тревога | мигает синхронно с экраном | -| `NO DATA` | горит ровно | +| `T` | температура CPU Package, °C | +| `L` | общая загрузка CPU, % | -Светодиод зажигается **низким** уровнем (`LED_ACTIVE_LEVEL = LOW`, определено -опытом). Попутно это удобно: в обычном режиме пин удерживается высоким, а GPIO8 -— пин начальной загрузки, которому высокий уровень и нужен. +Неизвестные ключи прошивка молча игнорирует, поэтому протокол расширяем: можно +добавить, например, `G=` для видеокарты, не трогая уже прошитые платы. -Проверять тревогу можно, не трогая пороги: остановить агента и подать значения -вручную. +**Разделитель дробной части — только точка.** Агент форматирует числа через +`InvariantCulture`: в русской локали иначе вышло бы `T=67,0`, и разбор на плате +прочитал бы `67`, молча потеряв дробную часть. -## Требования к ПК +Плату можно проверить без агента, отправив строки вручную. Порт при этом должен +быть свободен, то есть агента надо остановить: -Windows, LibreHardwareMonitor и драйвер PawnIO — порядок установки и разбор -подводных камней в [agent/README.md](agent/README.md). +``` +$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% её оперативной памяти.