Авто-калибровка PA и потока на QIDI Q2 — гайд
Инструкция, как на стоковой прошивке QIDI Q2 сделать себе автоматическую калибровку Pressure Advance и замер максимального объёмного потока штатным тензодатчиком-зондом. Итог: в панели Macros три кнопки — CALIBRATE_PA, CALIBRATE_FLOW, CALIBRATE_HELP; нажал → через ~5 минут число для слайсера в консоли и во всплывашке на экране. Ничего не сохраняется на принтере — только замер и вывод.
Теория и математика вынесены отдельно: метод свипа PA, метод лесенки потока. Здесь — только реализация.
Что нужно
- QIDI Q2 или Q2C на стоковой прошивке 01.01.02.01+ (в 01.01.02.03 сенсорный стек байт-в-байт тот же — я сверял распаковкой образа; Plus4 не подойдёт, там другой датчик).
- SSH-доступ root к принтеру и Moonraker
http://<ip>:7125из локалки. - numpy в klippy-env (на Q2 стоит 2.0.1; scipy 1.13.1 тоже есть — я проверял).
- Запрет: не шить мейнлайн Klipper (патчсеты MisterSheikh/n3oney) — Combo Box работает только на закрытых
box_*.soфорка QIDI.
Почему это работает (кратко)
В голове Q2 тензодатчик с 24-битным АЦП CS1237 (пины THR:PB3/PB4), уже сконфигурированный на 1280 Гц и gain 128 — штатно им принтер только щупает стол. Закрытый модуль air.so — переименованный мейнлайновый probe_eddy_current, его объекты доступны из любого стороннего extra: printer.lookup_object('probe_air').sensor_helper.read_origin_data() возвращает сырое значение датчика (int). Синхронный опрос из reactor-greenlet даёт ~39–40 Гц — этого хватает: сигнал экструзии в 120+ раз больше шума. Подробности вскрытия закрытого API — QIDI Q2 — закрытый API тензодатчика.
Состав решения: какие файлы куда кладутся
Весь код я держу в локальном репозитории (каталог autopa-q2/), на принтер он раскатывается скриптом. Обновление прошивки QIDI перезаписывает printer.cfg, но НЕ трогает klippy/extras/*.py — после прошивки достаточно перезапустить установку.
| Файл в репо | Куда на принтере | Что делает |
|---|---|---|
q2_loadcell.py | /home/qidi/klipper/klippy/extras/ | адаптер датчика: поллер-greenlet ~40 Гц поверх read_origin_data(), тарирование медианой 2 с, print_time-таймстампы, Collector-API мейнлайнового load_cell |
vendor/autopa-repo/autopa/ (форк G0BL1N/autopa, AGPL) | /home/qidi/klipper/klippy/extras/autopa/ | движок свипа PA: AUTOPA_SWEEP, bd-анализ на numpy; мои правки помечены # Q2: — гейты частоты вынесены в конфиг, сдвиг точки по X, команды профилей не регистрируются |
q2_flow.py (порт flow-модуля PrusaPATuner) | /home/qidi/klipper/klippy/extras/ | движок лесенки потока: QFLOW_MEASURE, фит + три детектора предела |
q2_calibrate.py | /home/qidi/klipper/klippy/extras/ | оркестратор: QPA_CALIBRATE/QPA_FLOW — прогрев, продувка, позиция над столом, замер, уборка, вердикт, всплывашка; QPA_ABORT, QPA_HELP |
install.sh / uninstall.sh | запускаются с компа | раскатка: scp модулей, вставка секций в printer.cfg (перед маркером SAVE_CONFIG), рестарт сервиса klipper, смок-тест датчика |
klipperscreen-macros.cfg.snippet | дописывается в свой include-файл конфига | три gcode_macro для панели |
klipperscreen-calib.conf.snippet | KlipperScreen.conf (в пользовательскую зону!) | скрытие мусорных макросов из панели |
Установка
- Запустить
./install.sh— он сам: проверит, что принтер не печатает (print_stats.state == standby— именно print_stats,/printer/infoврётreadyи во время печати); зальёт модули; вставит секции конфига; перезапустит сервис klipper; дождётся Moonraker и прогонит смокQPA_SENSOR_TEST. Успех выглядит так:q2_loadcell: 200 samples, 40.0 Hz, tare=..., sigma=96 counts, read_errors=0. - Секции, которые install.sh добавляет в
printer.cfg(перед строкой-маркером#*# <---------------------- SAVE_CONFIG ---------------------->):
[q2_loadcell]
[autopa]
min_segment_rate: 35
min_segment_samples: 12
sweep_tfast: 0.5
sweep_cycles: 10
sweep_wobble_axis: X
sweep_x_shift: 3
[q2_flow]
[q2_calibrate]Смысл значений: стоковые гейты autopa (сегмент < 40 Гц или < 20 сэмплов → брак) на фактических 38.7 Гц бракуют 100% данных, поэтому пороги 35/12; ступени свипа удлинены (tfast 0.5 с, 10 циклов) — вдвое больше сэмплов на сегмент; вобл по X, потому что жёлоб стоит на пределе Y; сдвиг точки на 3 мм на каждый шаг сетки K размазывает кучку пластика дорожкой.
- Макросы панели — дописать в свой include-файл (у меня
klipperscreen-extras.cfg, подключённый из printer.cfg) и перезапустить сервис klipper:
[gcode_macro CALIBRATE_PA]
description: Авто-подбор Pressure Advance (число вписать в слайсер). TEMP=температура сопла, FULL=1 широкая сетка K для незнакомого материала
gcode:
{% set temp = params.TEMP|default(220)|int %}
{% set full = params.FULL|default(0)|int %}
QPA_CALIBRATE TEMP={temp} FULL={full}
[gcode_macro CALIBRATE_FLOW]
description: Замер максимального объёмного потока (безопасное число вписать в слайсер в Max volumetric speed). TEMP=температура, FMIN/FMAX=границы лесенки мм3/с
gcode:
{% set temp = params.TEMP|default(220)|int %}
{% set fmin = params.FMIN|default(5)|int %}
{% set fmax = params.FMAX|default(30)|int %}
QPA_FLOW TEMP={temp} FMIN={fmin} FMAX={fmax}
[gcode_macro CALIBRATE_HELP]
description: Шпаргалка по калибровщику PA/потока
gcode:
QPA_HELPПараметры обязаны быть в стиле {% set x = params.X|default(N)|int %} — только такие KlipperScreen показывает редактируемыми полями в панели Macros. Однострочная подстановка {params.X|default(...)} полей не даёт — я на это наступил.
- Скрыть чужую свалку из панели Macros (у Q2 в ней 107 стоковых макросов): секция
[displayed_macros Printer]со строкамиИМЯ: False. ⚠️ Вставлять вKlipperScreen.confвыше строки-маркера#~# --- Do not edit below this line...— всё, что ниже маркера, KlipperScreen игнорирует и стирает при первой своей записи. Макросы при этом не удаляются, галки возвращаются в Настройки → Macros.
Разведка датчика перед постройкой (проверь на своей машине)
Все команды безопасны на простое, ответы читаются из консоли Fluidd или GET /server/gcode_store?count=30:
CS_WEIGHT_READ_CONFIG → // 传感器配置:0x3c --- 正确应该是0x3c
0x3C = SPEED 11 (1280 Гц) | PGA 11 (gain 128) | канал A — чип уже на максимуме
WEIGHTING_DEBUG_QUERY → // 传感器数据(-): 15858769 / -918447 / -2.138426(mV)
сырое_беззнаковое / знаковое (raw−2²⁴) / милливольты моста
WEIGHTING_DEBUG_ZERO_DATA → // 零点数据: 15858361 / -2.139376(mV) (тарирование зонда)
Мои опорные числа: шум покоя σ=323 отсчёта (моторы выключены) / ~787 (включены); порог срабатывания зонда delta_v: 0.08 мВ ≈ 34 400 отсчётов ≈ 190 г (пересчёт по counts_per_gram≈179.4 с чужого Q2 на мейнлайне); базлайн уезжает на ~50k отсчётов между холодной и горячей головой — поэтому тарирование на каждый запуск. Подписка probe_air.add_client(cb) в форке НЕ работает (0 колбэков даже во время настоящего G28-промера — проверено), единственный путь данных — опрос read_origin_data().
Контракт адаптера (если пишешь свой)
autopa потребляет от датчика ровно это — реализуешь пять вещей и его код работает без единой правки:
class Q2LoadCell: # объект секции [q2_loadcell]
name = 'q2_loadcell'
sensor # .get_samples_per_second() -> ~38.7 (замерить!),
# .get_range() -> (-0x800000, 0x7FFFFF)
def get_collector(self) -> Collector
class Collector:
is_started # property! сеттер False обязан ГАСИТЬ поллер:
# аварийный путь autopa делает только is_started=False
def start_collecting(self, min_time=None) # min_time — отсечка print_time
def collect_until(self, print_time) # -> ([[t, grams, counts, tare],...], errs)
# блокирует через reactor.pause, реактор жив
# обнаружение без патчей autopa — событие на klippy:ready:
self.printer.send_event('load_cell:tare', self)
# знак: давление гонит counts ВНИЗ; анализ сам считает force = -(counts - tare)
# таймстампы: mcu.estimated_print_time(reactor.monotonic()), НИКОГДА time.time()Грабли адаптера, которые у меня поймали тесты (12 регрессов + мутационные канарейки):
- Дубль поллера: погашенный greenlet замечает флаг только проснувшись; новый сбор в этом окне запускал второго — частота отчёта удваивалась (80 Гц вместо 40) и уезжала прямо в гейт анализа. Лечение: штамповать greenlet поколением при регистрации, устаревший выходит сам.
- Тарирование медианой, не средним: спайк +250 000 в тихом окне сдвигает среднее на 25 000, медиану — никак.
- Исключение чтения не должно убивать поллер: считать в
read_errors, строку пропустить, жить дальше. - Кэп буфера (у меня 60 000 строк ≈ 26 мин ≈ 12 МБ) — коробка с 498 МБ ОЗУ.
Последовательность цикла обёртки (порядок выстрадан)
- Отказ, если
print_stats.state= printing/paused; отказ, если бокс говорит «филамент не заряжен» (подсказкаBOX_LOAD TN=<n> T=<temp>). SAVE_GCODE_STATE→G28при нехоумленных осях (⚠️ G28 сбрасывает уставку камеры — прочитать до, вернутьM141 S<v>после).- Штатный
CLEAR_NOZZLE HOTEND=<temp>— продувает прежний материал из расплава, чистит сопло, сбрасывает лоток. Без этого хвост PETG в расплаве завысил мне K PLA до 0.0366 против чистых 0.032. - Стол вниз
G1 Z200, голова на центрX135 Y135,M109 S<temp>→ замер (свип или лесенка). - Финал БЕЗ продувки: хвост стокового макроса —
M106 S255+M104 S140+ стол вверх сразу (G1 Z2едет, пока сопло остывает) →TEMPERATURE_WAIT MAXIMUM=190→ щётка (5×X103↔90, 7×X114↔100, 5×X103↔90 на Y287.5, F5000) → скребок кружками на Z−0.2 (после остывания ≤150) → рычаг сброса лотка X164↔180 на Z−1.5 →M107,M104 S0,RESTORE_GCODE_STATE MOVE=0. - Вердикт «числом вперёд» в консоль + всплывашка на экран через action-prompts:
respond_info('action:prompt_begin Калибровка готова'),...prompt_text <текст>,...prompt_footer_button Закрыть|RESPOND TYPE=command MSG=action:prompt_end,...prompt_show.
Механика install.sh (что стоит скопировать)
- Пре-чек занятости — ТОЛЬКО
print_stats.state == "standby". - Пакет autopa передавать tar-пайпом через ssh во временный каталог и подменять
mv(rsync на принтере может отсутствовать,COPYFILE_DISABLE=1спасает от AppleDouble-мусора macOS). - Секции в printer.cfg вставлять python-скриптом ПЕРЕД маркером
#*# <---------------------- SAVE_CONFIG ---------------------->, идемпотентно (повторный запуск не дублирует), с бэкапом и атомарной записью с восстановлением владельца файла (chmod+chown до os.replace — пишешь под root, файл принадлежит qidi). - После рестарта сервиса ждать
/printer/info == ready, затем пинговатьPOST gcode/script?script=M115до первого{"result": "ok"}(Moonraker-лаг 30–60 с). - Смок:
QPA_SENSOR_TEST DURATION=5и парс частоты из ответа; порог ≥35 Гц.
Как пользоваться
CALIBRATE_PA TEMP=220— весь цикл сам: проверки (не печатает ли; заряжен ли филамент — если нет, подскажетBOX_LOAD), хоуминг при необходимости, штатныйCLEAR_NOZZLE(продувает прежний материал из расплава — иначе хвост PETG завышает K PLA, у меня было 0.0366 вместо 0.032), стол вниз на Z200, голова на центр, свип, протирка без продувки, вердикт. Узкая сетка K 0.02–0.05 (~0.6 г пластика, ~5 мин); для незнакомого материалаFULL=1— сетка 0.01–0.08 (~1.2 г). Ничего не применяется автоматически — K вписывается в поле Pressure Advance профиля филамента в слайсере.CALIBRATE_FLOW TEMP=240— лесенка потока 5→30 мм³/с (~1.15 г, ~2 мин), вердикт: реальный предел и безопасное число (предел × 0.85) — безопасное вписывается в Max volumetric speed профиля филамента.CALIBRATE_HELP— шпаргалка в консоль.- Аварийно:
QPA_ABORT(уборка: гасит поллер, возвращает PA, выключает нагрев; ⚠️ команда, набранная во время идущего свипа, исполнится только ПОСЛЕ него — Klipper на лету переисполняет лишь M112). Диагностика датчика:QPA_SENSOR_TEST. - После любого замера снять спагетти со стола — оно лежит на холодной плите и снимается одним движением.
Мои результаты для ориентира: PLA Rapido @220 — K 0.032 (три прогона 0.0328/0.0311/0.0331, разброс 6.2%, совпало с ручным методом линий), поток 18.7 безопасно / ~22 предел; Elegoo Rapid PETG @240 — K 0.059, поток 19.6 / 23. Вендорские профили потоку ставят 12–17 — замер дал честные +20–30% скорости.
Траблшутинг
| Симптом | Причина и лечение |
|---|---|
K_opt: none, все сегменты забракованы | стоковые гейты на 40 Гц; проверить min_segment_rate: 35, min_segment_samples: 12 в [autopa] |
| метрики не зависят от K (flat table) | PA-гейт не взводится: вобл 0 или ось не хоумлена. Проверка: мини-свипы K=0 и K=0.05 обязаны отличаться на порядки (у меня 0/223 против 23142/27028) |
| K уехал вверх после смены материала | хвост прежнего пластика в расплаве — цикл обязан начинаться с CLEAR_NOZZLE (в моей обёртке уже так) |
| σ полок пляшет, guard ругается на baseline shift | куча пластика достала до сопла — мерить только над опущенным столом |
| своя команда зарегистрирована, но молча не выполняется | цифра в имени: парсер Klipper режет CS1237_FOO до CS1237. Имена — только [A-Z_] |
| после install.sh команды «висят» | Moonraker после рестарта сервиса klipper ~30–60 с молча теряет G-code — пинговать M115 до первого ответа |
| MCU тулхеда в shutdown после аварийной остановки | жёсткий стоп посреди движения; лечится FIRMWARE_RESTART |
| секции в KlipperScreen.conf исчезли | вставлены ниже маркера #~# — переносить в пользовательскую зону |
Проверка, что зонд не пострадал (я делал до и после): G28, PROBE_ACCURACY — σ должна остаться на уровне базовой (у меня 0.00163 → 0.00166 мм).
Ссылки
- Методы: Авто-калибровка Pressure Advance тензодатчиком, Замер максимального объёмного потока тензодатчиком, Загрязнение силовых замеров экструзии
- Детали машины: QIDI Q2 — авто-калибровка PA и потока, QIDI Q2 — закрытый API тензодатчика, QIDI Q2 — зона обслуживания за столом
- Чужой код: autopa (AGPL-3.0), PrusaPATuner, KAPAT, реверс .so Q2, CS1237-драйвер мейнлайна (только читать, не шить)