<===

ProNotes

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())   
← Previous Next →
Back to list