ai: circuitpython — устойчивый цикл: ошибки печатаются в консоль (не в REPL), эхо принятой команды ->, свип до f1 <= f_s/6
This commit is contained in:
@@ -11,14 +11,17 @@
|
|||||||
# Архитектура — без потоков (threading есть не на всех портах):
|
# Архитектура — без потоков (threading есть не на всех портах):
|
||||||
# один цикл одновременно обновляет выборку синуса и опрашивает USB-консоль
|
# один цикл одновременно обновляет выборку синуса и опрашивает USB-консоль
|
||||||
# (supervisor.runtime.serial_bytes_available). f_s измеряется на старте
|
# (supervisor.runtime.serial_bytes_available). f_s измеряется на старте
|
||||||
# (обычно ~10–40 кГц на RP2040) и печатается; под неё и подбираются
|
# (обычно 3–40 кГц на RP2040) и печатается; свип держите до f1 <= f_s/6.
|
||||||
# номиналы RC-фильтра (см. README);
|
|
||||||
# - прямоугольник от 20 Гц — аппаратный ШИМ pwmio (точная частота и
|
# - прямоугольник от 20 Гц — аппаратный ШИМ pwmio (точная частота и
|
||||||
# скважность, как в Arduino); ниже 20 Гц — «программный» прямоугольник
|
# скважность, как в Arduino); ниже 20 Гц — «программный» прямоугольник
|
||||||
# в основном цикле;
|
# в основном цикле;
|
||||||
# - команды те же: F / W / D / M / A / SWEEP / STOP (через USB-консоль,
|
# - команды те же: F / W / D / M / A / SWEEP / STOP (через USB-консоль,
|
||||||
# терминал: 115200).
|
# терминал: 115200).
|
||||||
#
|
#
|
||||||
|
# УСТОЙЧИВОСТЬ: ошибки не роняют скетч в REPL — они печатаются в консоль
|
||||||
|
# (строки "! ..."), а генератор продолжает работать. Каждая принятая строка
|
||||||
|
# логируется как "-> ..." — видно, что реально пришло с терминала.
|
||||||
|
#
|
||||||
# Протокол команд (ввод строкой в терминале, Enter = конец команды):
|
# Протокол команд (ввод строкой в терминале, Enter = конец команды):
|
||||||
# F <Гц> — установить частоту (1..200000)
|
# F <Гц> — установить частоту (1..200000)
|
||||||
# W Q | W S — режим: Q — прямоугольник, S — синус
|
# W Q | W S — режим: Q — прямоугольник, S — синус
|
||||||
@@ -52,13 +55,14 @@ import supervisor
|
|||||||
|
|
||||||
PIN_OUT = board.GP15 # выход сигнала (синус — через RC-фильтр)
|
PIN_OUT = board.GP15 # выход сигнала (синус — через RC-фильтр)
|
||||||
PIN_TRIG = board.GP14 # синхроимпульс (для осциллографа)
|
PIN_TRIG = board.GP14 # синхроимпульс (для осциллографа)
|
||||||
PIN_ADC = board.GP26 # ADC0 = A0 — вход измерения (через делитель!)
|
PIN_ADC = board.GP26 # ADC0 = A0 — вход измерения (через делителем)
|
||||||
|
|
||||||
CARRIER_HZ = 125000 # несущая ШИМ — много выше f_s, биений нет
|
CARRIER_HZ = 125000 # несущая ШИМ — много выше f_s, биений нет
|
||||||
PWM_RANGE = 65535 # duty_cycle pwmio — 16 бит
|
PWM_RANGE = 65535 # duty_cycle pwmio — 16 бит
|
||||||
AMP_MAX = 127 # максимальная амплитуда синуса
|
AMP_MAX = 127 # максимальная амплитуда синуса
|
||||||
SW_SQ_MIN_HZ = 20 # ниже — «программный» прямоугольник в цикле
|
SW_SQ_MIN_HZ = 20 # ниже — «программный» прямоугольник в цикле
|
||||||
FS_INIT = 20000 # оценка f_s до калибровки
|
FS_INIT = 20000 # оценка f_s до калибровки
|
||||||
|
STEP_2P32 = 1 << 32 # аккумулятор фазы 32 бит
|
||||||
|
|
||||||
# ─────────────────── состояние генератора ─────────────────────
|
# ─────────────────── состояние генератора ─────────────────────
|
||||||
state = {
|
state = {
|
||||||
@@ -119,13 +123,14 @@ def dds_tick(phase):
|
|||||||
«программный» прямоугольник. В режиме Q с f >= SW_SQ_MIN_HZ ШИМ работает
|
«программный» прямоугольник. В режиме Q с f >= SW_SQ_MIN_HZ ШИМ работает
|
||||||
аппаратно, функция только возвращает фазу.
|
аппаратно, функция только возвращает фазу.
|
||||||
"""
|
"""
|
||||||
step = int(state["freq"] * (1 << 32) / fs_eff)
|
f = state["freq"]
|
||||||
|
step = f * STEP_2P32 // fs_eff
|
||||||
phase = (phase + step) & 0xFFFFFFFF
|
phase = (phase + step) & 0xFFFFFFFF
|
||||||
if state["mode"] == "S":
|
if state["mode"] == "S":
|
||||||
idx = phase >> 24
|
idx = phase >> 24
|
||||||
s = LUT[idx] * state["amp"] // 127
|
s = LUT[idx] * state["amp"] // 127
|
||||||
pwm_duty(128 + s)
|
pwm_duty(128 + s)
|
||||||
elif state["freq"] < SW_SQ_MIN_HZ:
|
elif f < SW_SQ_MIN_HZ:
|
||||||
pwm_duty(255 if (phase >> 24) < state["duty"] else 0)
|
pwm_duty(255 if (phase >> 24) < state["duty"] else 0)
|
||||||
return phase
|
return phase
|
||||||
|
|
||||||
@@ -202,7 +207,7 @@ def trigger_pulse():
|
|||||||
|
|
||||||
|
|
||||||
def handle(line, phase):
|
def handle(line, phase):
|
||||||
"""Выполнить команду. Возвращает (ok, phase)."""
|
"""Выполнить команду. Возвращает (ok, phase). Любые ошибки — внутрь."""
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if not line:
|
if not line:
|
||||||
return False, phase
|
return False, phase
|
||||||
@@ -246,12 +251,15 @@ def main():
|
|||||||
global fs_eff
|
global fs_eff
|
||||||
print("# DDS generator ready (CircuitPython). help: F/W/D/A/SWEEP/STOP")
|
print("# DDS generator ready (CircuitPython). help: F/W/D/A/SWEEP/STOP")
|
||||||
phase = 0
|
phase = 0
|
||||||
fs_eff, phase = calibrate_fs(phase) # в режиме 'Q', f=1000 Гц,
|
try:
|
||||||
# аппаратный ШИМ: тики холостые
|
fs_eff, phase = calibrate_fs(phase)
|
||||||
|
except Exception as e:
|
||||||
|
print("! calibrate error:", e)
|
||||||
print("# sample rate:", fs_eff, "Hz")
|
print("# sample rate:", fs_eff, "Hz")
|
||||||
|
|
||||||
buf = ""
|
buf = ""
|
||||||
while True:
|
while True:
|
||||||
|
try:
|
||||||
phase = dds_tick(phase)
|
phase = dds_tick(phase)
|
||||||
|
|
||||||
# свип: точка готова, когда прошло dwell
|
# свип: точка готова, когда прошло dwell
|
||||||
@@ -273,15 +281,29 @@ def main():
|
|||||||
|
|
||||||
# командная строка (неблокирующий опрос USB-консоли)
|
# командная строка (неблокирующий опрос USB-консоли)
|
||||||
while supervisor.runtime.serial_bytes_available:
|
while supervisor.runtime.serial_bytes_available:
|
||||||
|
try:
|
||||||
c = sys.stdin.read(1)
|
c = sys.stdin.read(1)
|
||||||
|
except Exception as e:
|
||||||
|
print("! serial read error:", e)
|
||||||
|
break
|
||||||
if not c:
|
if not c:
|
||||||
break
|
break
|
||||||
if c == "\n" or c == "\r":
|
if c == "\n" or c == "\r":
|
||||||
|
if buf.strip():
|
||||||
|
print("->", buf.strip()) # эхо принятой строки
|
||||||
|
try:
|
||||||
_, phase = handle(buf, phase)
|
_, phase = handle(buf, phase)
|
||||||
|
except Exception as e:
|
||||||
|
print("! command error:", e)
|
||||||
buf = ""
|
buf = ""
|
||||||
elif c != "\x00":
|
elif c != "\x00":
|
||||||
buf += c
|
buf += c
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# НЕ умираем в REPL: печатаем и продолжаем
|
||||||
|
print("! loop error:", e)
|
||||||
|
time.sleep(0.05)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
Reference in New Issue
Block a user