Блок‑схема устройства
Логика на уровне блоков:
- Источник 12 В в машине → DC‑DC 12→5 В (автомотив, с защитами).
- 5 В → питание RP2040 (Pico/Pico‑совместимая плата).
- 5 В → питание LiDAR (TFmini‑S/TF03, смотрим даташит). [mouser](https://www.mouser.com/catalog/specsheets/Benewake_10152020_TF03-100.pdf)
- LiDAR ↔ RP2040: UART (TX датчика → RX RP2040, RX датчика → TX RP2040, общая GND). [worldronemarket](https://www.worldronemarket.com/benewake-tf03-rangefinder-100-m-datasheet/)
- RP2040 → буззер (через транзистор/модуль), светодиоды, опционально маленький дисплей (I2C OLED).
Текстовая блок‑диаграмма:
[12V авто сеть]
|
[DC-DC 12->5V c фильтрацией]
|
+----+-----------------------------+
| |
[LiDAR (TFmini-S/TF03)] [RP2040 + CircuitPython]
| UART TX/RX |
+------------------------------+
|
[buzzer + LED/индикаторы]
UART на RP2040 в CircuitPython поднимается через busio.UART, пины укажешь под свою плату (например, GP4/GP5). [youtube](https://www.youtube.com/watch?v=tvqMbFoBlVk)
Протокол LiDAR (TFmini‑подобный)
У TFmini/TFmini‑S базовый протокол: пакеты по 9 байт, заголовок 0x59 0x59, потом distance low/high, strength, температура, checksum. [blog.csdn](https://blog.csdn.net/shanghaiwren/article/details/152655977)
У TF03 похожая идея, но другой формат и длина пакета (смотри актуальный мануал TF03 UART/CAN). [en.benewake](https://en.benewake.com/uploadfiles/2024/04/20240426134845102.pdf)
Ниже — пример под “классический” TFmini‑совместимый поток на 115200 бод, с чтением фреймов 9 байт. [forums.raspberrypi](https://forums.raspberrypi.com/viewtopic.php?t=327477)
Пример CircuitPython‑кода для RP2040 (UART + пороги)
Этот код:
- Инициализирует UART.
- Читает поток пакетов TFmini‑формата.
- Выделяет
distance_cm. - В зависимости от дистанции управляет буззером и тремя светодиодами.
import time
import board
import busio
import digitalio
# ---------- Пины под свою плату ----------
# Пример для Raspberry Pi Pico:
UART_TX_PIN = board.GP4
UART_RX_PIN = board.GP5
BUZZER_PIN = board.GP10
LED_FAR_PIN = board.GP11 # "далеко" (зелёный)
LED_MID_PIN = board.GP12 # "средне" (жёлтый)
LED_NEAR_PIN = board.GP13 # "близко" (красный)
# ---------- Пороговые дистанции в метрах ----------
FAR_THRESHOLD_M = 30 # >30 м — почти тишина
MID_THRESHOLD_M = 15 # 15–30 м — редкие сигналы
NEAR_THRESHOLD_M = 5 # 5–15 м — частые сигналы, <5 м — постоянный
# ---------- Инициализация UART ----------
# Для TFmini/TFmini-S/многих Benewake: 115200 бод по умолчанию
uart = busio.UART(
UART_TX_PIN,
UART_RX_PIN,
baudrate=115200,
timeout=0.05,
)
# ---------- Инициализация вывода ----------
buzzer = digitalio.DigitalInOut(BUZZER_PIN)
buzzer.direction = digitalio.Direction.OUTPUT
buzzer.value = False
led_far = digitalio.DigitalInOut(LED_FAR_PIN)
led_far.direction = digitalio.Direction.OUTPUT
led_mid = digitalio.DigitalInOut(LED_MID_PIN)
led_mid.direction = digitalio.Direction.OUTPUT
led_near = digitalio.DigitalInOut(LED_NEAR_PIN)
led_near.direction = digitalio.Direction.OUTPUT
def set_leds_mode(mode: str) -> None:
# mode: "far", "mid", "near", "alarm", "off"
if mode == "far":
led_far.value = True
led_mid.value = False
led_near.value = False
elif mode == "mid":
led_far.value = False
led_mid.value = True
led_near.value = False
elif mode == "near":
led_far.value = False
led_mid.value = False
led_near.value = True
elif mode == "alarm":
# можно мигать всеми сразу, но тут просто все включены
led_far.value = True
led_mid.value = True
led_near.value = True
else:
led_far.value = False
led_mid.value = False
led_near.value = False
def beep(duration: float) -> None:
buzzer.value = True
time.sleep(duration)
buzzer.value = False
# ---------- Парсер пакетов TFmini‑формата ----------
def read_distance_cm():
"""
Читает поток UART и возвращает дистанцию в сантиметрах (int) или None.
Ожидает пакеты длиной 9 байт формата:
0: 0x59, 1: 0x59, 2: Dist_L, 3: Dist_H, 4: Strength_L, 5: Strength_H,
6: Temp_L, 7: Temp_H, 8: Checksum.
"""
# Пытаемся найти заголовок 0x59 0x59 в потоке
data = uart.read(1)
if not data:
return None
if data[0] != 0x59:
return None
data2 = uart.read(1)
if not data2:
return None
if data2[0] != 0x59:
return None
# Читаем ещё 7 байт до конца фрейма
rest = uart.read(7)
if not rest or len(rest) != 7:
return None
frame = bytes([0x59, 0x59]) + rest
# Контрольная сумма: сумма первых 8 байт & 0xFF == 9-й байт
checksum_calc = sum(frame[0:8]) & 0xFF
checksum_frame = frame [youtube](https://www.youtube.com/watch?v=tvqMbFoBlVk)
if checksum_calc != checksum_frame:
return None
dist_cm = frame [en.benewake](https://en.benewake.com/uploadfiles/2024/04/20240426134845102.pdf) + (frame [blog.csdn](https://blog.csdn.net/shanghaiwren/article/details/152655977) << 8)
# strength = frame [mybotshop](https://www.mybotshop.de/Datasheet/Benewake_TF03_Manual.pdf) + (frame [mouser](https://www.mouser.com/catalog/specsheets/Benewake_10152020_TF03-100.pdf) << 8)
# temperature = (frame [mybotshop](https://www.mybotshop.de/Datasheet/Benewake_TFMini-S_Datasheet.pdf) + (frame [worldronemarket](https://www.worldronemarket.com/benewake-tf03-rangefinder-100-m-datasheet/) << 8)) / 8 - 256
return dist_cm
# ---------- Главный цикл ----------
last_beep_time = 0.0
while True:
dist_cm = read_distance_cm()
now = time.monotonic()
if dist_cm is None or dist_cm == 0:
# нет корректных данных
set_leds_mode("off")
buzzer.value = False
continue
dist_m = dist_cm / 100.0
# Простейший фильтр: с ограничением частоты чтения
# (датчик и так выдаёт ~100–1000 Гц, нам этого выше крыши)
# Логику порогов можно усложнить, добавив усреднение/гистерезис.
if dist_m > FAR_THRESHOLD_M:
# далеко — еле-еле пищим
set_leds_mode("far")
# допустим, один короткий писк раз в 3 секунды
if now - last_beep_time > 3.0:
beep(0.03)
last_beep_time = now
elif MID_THRESHOLD_M < dist_m <= FAR_THRESHOLD_M:
# середина — редкие сигналы
set_leds_mode("mid")
# короткий писк раз в 1 секунду
if now - last_beep_time > 1.0:
beep(0.05)
last_beep_time = now
elif NEAR_THRESHOLD_M < dist_m <= MID_THRESHOLD_M:
# близко — частые короткие сигналы
set_leds_mode("near")
# пик раз в 0.3 сек
if now - last_beep_time > 0.3:
beep(0.08)
last_beep_time = now
else:
# очень близко: < NEAR_THRESHOLD_M
set_leds_mode("alarm")
# почти непрерывный тон (можно прерывать для эффекта)
buzzer.value = True
# чтобы не забить основной цикл, чуть спим
time.sleep(0.05)
continue
# немного разгружаем цикл
time.sleep(0.01)
Основу read_distance_cm() я опирал на типичный разбор TFmini‑пакета: заголовок 0x59 0x59, 9‑байтовый фрейм, checksum как сумма первых 8 байт. [docs.circuitpython](https://docs.circuitpython.org/projects/tfmini/en/latest/)
Что подправить под TF03
У TF03 другая длина и формат фрейма (обычно 10 байт с заголовком 0x59 0x59 или 0x57 0x0A, зависящий от режима). Это нужно перепроверить по свежему TF03 UART/CAN User Manual и даташиту. Там подобная логика: [mybotshop](https://www.mybotshop.de/Datasheet/Benewake_TF03_Manual.pdf)
- ищем подпись,
- читаем фиксированное количество байт,
- проверяем checksum,
- парсим distance/strength.
Тебе останется заменить read_distance_cm() на парсер конкретного формата из мануала TF03 и поправить baudrate, если он другой. [en.benewake](https://en.benewake.com/uploadfiles/2024/04/20240426134845102.pdf)
Железо/питание и авто‑нюансы
- Запитывать всё через DC‑DC с входом 9–36 В и защитой от бросков, плюс TVS/RC‑фильтр на 12 В после зажигания. [directindustry](https://www.directindustry.com/prod/benewake/product-201153-2420998.html)
- LiDAR лучше ставить за стеклом/пластиком с известной прозрачностью в ИК по даташиту; многие Benewake это допускают, но с поправкой по дальности. [mouser](https://www.mouser.com/catalog/specsheets/Benewake_10152020_TF03-100.pdf)
- RP2040 держать подальше от “грязной массы”; общая GND всё равно нужна, но разводка звёздочкой и фильтры сильно помогают.
[file-name 000378_2026-06-22_10-24-46.txt]