Проект реализует «аналоговый компаратор» на 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 задать:

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())

[file-name 000353_2026-03-07_23-06-10.txt]