<===
2026-03-07 23:06:10
# Async Comparator на XIAO RP2040 (CircuitPython)
Проект реализует «аналоговый компаратор» на XIAO RP2040 с использованием `asyncio`:
напряжение с входа A2 измеряется, усредняется и по единому порогу в мВ управляет выходным пином, встроенным RGB и индикатором TM1637.[web:11][web:102]
## Возможности
- Многократный замер АЦП (1000 выборок) на A2 с усреднением и пересчётом в милливольты.[web:11][web:77]
- Единый настраиваемый порог `MV_THRESHOLD` (в мВ) с гистерезисом `MV_HYST`.
- Управление:
- пином GP3 (цифровой выход по порогу);
- встроенным NeoPixel (`board.NEOPIXEL`) — цвет показывает положение относительно порога;
- четырёхразрядным TM1637 — отображает измеренное напряжение в мВ.[web:13][web:30][web:48]
- Debug‑вывод в REPL: сырое 16‑битное значение АЦП и напряжение в мВ с точностью до 0.001 мВ.
- Контроль памяти и автосброс микроконтроллера при нехватке heap’а.
## Железо
- Seeed XIAO RP2040 с CircuitPython.[web:27][web:30]
- Входной сигнал: A2 (`board.A2`).
- Выходной пин: GP3 (`board.GP3`).
- Встроенный RGB: `board.NEOPIXEL`.
- TM1637 4‑digit 7‑segment:
- CLK → GP15 (`board.GP15`),
- DIO → GP14 (`board.GP14`).[web:13][web:48]
## Настройка
В начале `code.py` задать:
```python
MV_THRESHOLD = 1200.0 # порог в мВ (пример: 1200 = 1.200 В)
MV_HYST = 20.0 # гистерезис в мВ
MEM_MIN_FREE = 10000 # минимально допустимый свободный heap, байт
MEM_CHECK_PERIOD = 5.0 # период проверки памяти, сек
```
## Логика работы
Проект построен на `asyncio` и разделён на несколько задач:[web:7][web:102]
- `adc_task()`
- Читает АЦП 1000 раз, усредняет, переводит в мВ (float) и int, сохраняет в `state`.
- Печатает `RAW: <значение> mV: <точное_значение>`.
- `reaction_task()`
- Сравнивает `state.mv_f` с порогом и гистерезисом, управляет GP3.
- При смене состояния пишет лог с точными мВ.
- `tm1637_task()`
- Показывает `state.mv_i` в виде 4‑значного числа мВ (0–9999) на TM1637.[web:13][web:48]
- `neopixel_task()`
- По тому же порогу красит встроенный NeoPixel:
- выше порога — красный,
- ниже порога — синий,
- в окне гистерезиса — зелёный.
- `memory_guard_task()`
- Периодически вызывает `gc.collect()` и проверяет `gc.mem_free()`.
- Если свободной памяти меньше `MEM_MIN_FREE`, печатает сообщение и вызывает `microcontroller.reset()` для перезапуска платы.[web:92][web:94][web:110]
Все задачи запускаются в `main()` через `asyncio.create_task()` и работают кооперативно, деля время CPU с помощью `await asyncio.sleep(...)`.[web:7][web:106]
## Запуск
1. Скопировать `code.py` и библиотеку TM1637 в `CIRCUITPY` на XIAO RP2040.[web:13][web:52]
2. Подключить питание и открыть REPL (например, через `screen` / `minicom`).
3. Наблюдать:
- строки `RAW: ... mV: ...` и `MEM free: ... bytes` в REPL;
- состояние пина GP3;
- цвет встроенного NeoPixel;
- реальное напряжение в мВ на индикаторе TM1637.
```
##############
import asyncio
import board
import analogio
import digitalio
import neopixel
import gc
import microcontroller
from tm1637 import TM1637
# ---- Настройки ----
MV_THRESHOLD = 1200.0 # порог в мВ
MV_HYST = 20.0 # гистерезис в мВ
MEM_MIN_FREE = 10_000 # минимально допустимый free heap, байт (подбери под себя)
MEM_CHECK_PERIOD = 5.0 # как часто проверять память, сек
# ---- АЦП и выходной пин ----
adc = analogio.AnalogIn(board.A2)
out_pin = digitalio.DigitalInOut(board.GP3)
out_pin.direction = digitalio.Direction.OUTPUT
out_pin.value = False
# ---- Встроенный RGB NeoPixel ----
pixels = neopixel.NeoPixel(board.NEOPIXEL, 1,
brightness=0.01, auto_write=True)
# ---- TM1637 ----
CLK = board.GP15
DIO = board.GP14
display = TM1637(CLK, DIO)
display.brightness(1)
class ADCState:
def __init__(self):
self.mv_f = 0.0 # мВ как float
self.mv_i = 0 # мВ как int
self.raw_avg = 0
state = ADCState()
def raw_to_mv_f(raw: int) -> float:
# 0..65535 -> 0..3300.000 мВ[web:11][web:85]
return (raw * 3300.0) / 65535.0
# ---- Задача измерения АЦП ----
async def adc_task():
while True:
acc = 0
for _ in range(1000):
acc += adc.value
await asyncio.sleep(0)
avg_raw = acc // 1000
mv_f = raw_to_mv_f(avg_raw)
state.raw_avg = avg_raw
state.mv_f = mv_f
state.mv_i = int(mv_f + 0.5)
# точный debug
print("RAW:", avg_raw, " mV:", "{:.3f}".format(mv_f))
await asyncio.sleep(0.05)
# ---- Реакция на порог на GP3 ----
async def reaction_task():
prev_state = None
while True:
mv = state.mv_f
if prev_state is None:
above = mv >= MV_THRESHOLD
elif prev_state:
above = mv > (MV_THRESHOLD - MV_HYST)
else:
above = mv >= (MV_THRESHOLD + MV_HYST)
out_pin.value = above
if above != prev_state:
print("PIN3:", "HIGH" if above else "LOW",
" mv=", "{:.3f}".format(mv))
prev_state = above
await asyncio.sleep(0.02)
# ---- TM1637: целые мВ ----
async def tm1637_task():
while True:
mv = state.mv_i
if mv < 0:
mv = 0
if mv > 9999:
mv = 9999
display.show(f"{mv:4d}")
await asyncio.sleep(0.1)
# ---- RGB по порогу ----
async def neopixel_task():
while True:
mv = state.mv_f
hi = MV_THRESHOLD + MV_HYST / 2.0
lo = MV_THRESHOLD - MV_HYST / 2.0
if mv > hi:
color = (255, 0, 0)
elif mv < lo:
color = (0, 0, 255)
else:
color = (0, 255, 0)
pixels[0] = color
await asyncio.sleep(0.05)
# ---- Контроль памяти и ресет кристалла ----
async def memory_guard_task():
"""
Периодически дергаем gc.collect(), смотрим gc.mem_free().
Если free < MEM_MIN_FREE – лог и жёсткий reset микроконтроллера.
"""
while True:
await asyncio.sleep(MEM_CHECK_PERIOD)
gc.collect() # убрать мусор перед измерением[web:93]
free = gc.mem_free()
print("MEM free:", free, "bytes")
if free < MEM_MIN_FREE:
print("MEM LOW, resetting MCU...")
# жёсткий reset (как кнопкой RESET)[web:92][web:95]
microcontroller.reset()
async def main():
print("START main() threshold=", MV_THRESHOLD, "mV hyst=", MV_HYST, "mV")
t1 = asyncio.create_task(adc_task())
t2 = asyncio.create_task(reaction_task())
t3 = asyncio.create_task(tm1637_task())
t4 = asyncio.create_task(neopixel_task())
t5 = asyncio.create_task(memory_guard_task())
await asyncio.gather(t1, t2, t3, t4, t5)
asyncio.run(main())
Back to list