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