repka-team
repka-team
481 просмотров0 комментариев

Проект “Ультразвуковой парковочный ассистент” на базе одноплатного компьютера Repka PI 4

Содержание #

  1. Введение
  2. Электрическая принципиальная схема
  3. Монтажная схема
  4. Сборка проекта
  5. Запуск проекта
  6. Программная реализация
  7. Практическая значимость проекта
  8. Расширение проекта
  9. Видеообзор проекта

Введение #

Представляю вам образовательный проект — “Ультразвуковой парковочный ассистент”, разработанный в рамках учебно-методического комплекса на базе одноплатного компьютера Repka PI 4.

Этот проект использует ультразвуковой датчик HC-SR04 для измерения расстояния до объекта, что позволяет удобно отслеживать приближение автомобилей или других объектов, например, при парковке. Система визуализирует информацию о расстоянии на 7-сегментном дисплее, а также управляет RGB-светодиодами и пассивным зуммером, которые изменяют свое поведение в зависимости от того, насколько близко находится объект.

Проект «Ультразвуковой парковочный ассистент» является отличным примером применения ультразвуковой технологии в повседневной жизни, позволяя с помощью простых визуальных и звуковых сигналов помогать пользователю в процессе парковки.

Проект будет собираться с использованием “Учебно-методический комплекс REPKA”. Схему сборки можно найти в разделе "Примеры готовых проектов" учебного пособия УМК “REPKA”.

Также все необходимые материалы и схемы подключения доступны в репозитории на платформе Gitflic.

Используемые в проекте компоненты

  1. Зуммер пассивный модуль (Passive buzzer KY-006) — для подачи звуковых сигналов, изменяющихся по тону в зависимости от расстояния, см. рисунок 1.

2. 7-сегментный индикатор 0.36" — для отображения числовых значений расстояния, см рисунок 2.

3. Адресная светодиодная лента (WS2812) — используется для визуальной индикации состояния с помощью цветных светодиодов, см рисунок 3.

4. Ультразвуковой датчик расстояния (HC-SR04) — для измерения расстояния до объекта, см. рисунок 4.

Вы можете приобрести все необходимые компоненты отдельно от "Учебно-методический комплекс REPKA". Ссылки на модули приведены в таблице ниже.

Компонент Ссылка на приобретение
Монтажная/макетная плата Ссылка
Шлейф Ссылка
Переходник с шлейфа на макетную плату Ссылка
Соединительные провода

Провода соединительные м-п

Провода соединительные п-п

Провода соединительные п-п

7-сегментный индикатор 0.36" Ссылка
Зуммер пассивный модуль (Passive buzzer KY-006) Ссылка
Адресная светодиодная лента (WS2812) Ссылка
Ультразвуковой датчик расстояния (HC-SR04) Ссылка

Перед началом сборки следует выполнить подключение монтажной платы.

Во время сборки проекта будем регулярно обращаться к электрической принципиальной схеме и монтажной схеме, представленными в учебном пособии (см. рисунки 5 и 6). Эти схемы будут служить основным ориентиром на всех этапах подключения компонентов, обеспечивая точность и правильность сборки устройства.

Для разработки кода будет использоваться текстовый редактор Geany, который входит в состав стандартного ПО Репка ОС.

Электрическая принципиальная схема #

Монтажная схема #

Сборка проекта

1. Подключение пассивного зуммера.

Обратимся к рисункам 5 и 6. Из них видно, что устройство подключается через GPIO12 и питается от 3.3V.

1.1. Выполним подключение к макетной плате согласно таблице 1.

Макетная плата

Пассивный зуммер

3.3V

VCC

GPIO12

IO

GND

GND

Таблица 1. Подключение пассивного зуммера к макетной плате.

1.2. Результат подключения будет выглядеть следующим образом, см. рисунок 7:

2. Для проверки правильности подключения используем python скрипт из репозитория repka-pi_iot-examples.

2.1. Клонируем репозиторий:

git clone git@gitflic.ru:repka_pi/repka-pi_iot-examples.git

2.2. Переходим в репозиторий:

cd repka-pi_iot-examples/

2.3. Выполним установку зависимостей.

2.3.1. Если хотите установить зависимости только для зуммера, выполните:

make setup-KY-006

2.3.2. Если хотите установить зависимости для всех датчиков и проектов, выполните:

make setup-all

2.4. Запускаем скрипт для проверки работоспособности прибора:

make KY-006

2.4.1. Если нет никакой реакции, то проверьте номер GPIO указанный в скрипте по пути devices/executive/KY-006_example/py

BEEP_PIN = 11  # Укажите номер пина, к которому подключен BEEP

2.4.2. Обратимся к пособию УМК “REPKA”, в котором представлена распиновка Repka PI 4 (рисунок 8).  Из нее следует, что уникальный идентификатор порта GPIO12 равен 360 и находится на физическом пине 32.

2.4.3. Внесем правки в код и выполним скрипт

 BEEP_PIN = 32  # Укажите номер пина, к которому подключен BEEP

2.5. После запуска зуммер должен воспроизвести мелодию.

3. Подключение 7-сегментного индикатор 0.36".

Как видно из схемы устройство подключается через порты GPIO и резисторы 100 Ом.

3.1. Согласно рисункам 6 и 7 подключим резисторы к индикатору:


3.2. Подключаем 7-сегментный индикатор к GPIO: 18, 23, 24, 25, 4, 17, 27, 22 через резисторы для защиты цепей. Важно не перепутать выводы, так как цифра на индикаторе строятся путем подачи низкого или высокого сигнала:

4. Аналогично пункту 2 выполним проверку подключения датчика.

4.1. Если ранее не устанавливали все зависимости командой setup-all, то установим зависимости для FYS-3611, выполнив:

make setup-FYS-3611

4.2. Запустим python скрипт, который находится по пути devices/input-output/FYS-3611_example:

make FYS-3611

4.3. Если все подключено верно, то все сегменты должны загореться, см. рисунок 11.

5. Подключение адресной светодиодной ленты (WS2812).

Как видно из рисунков 5 и 6 устройство подключается к MOSI через резистор 100 Ом и питается от 5V.

5.1. Выполним подключение согласно таблице 2.

Макетная плата

WS2812

5V

5V

GND

GND

MOSI

MOSI

Таблица 2. Подключение WS2812 к макетной плате.

5.2. Результат подключения устройства можно наблюдать на рисунке 12.

6. Аналогично пункту 2 выполним проверку подключения датчика.

6.1. Если ранее не устанавливали все зависимости командой setup-all, то установим зависимости для FYS-3611, выполнив:

make setup-ws2812

6.2. Запустим python скрипт, который находится по пути devices/input-output/WS2812_example:

make ws2812

6.3. При правильном подключении лента будет поочередно освещаться тремя основными цветами: сначала зеленым, затем красным и синим.

7. Подключение ультразвуковой датчик расстояния (HC-SR04).

Наконец, подключим ключевой компонент нашего проекта — ультразвуковой датчик. Как показано на рисунках 5 и 6, датчик работает от питания 5V и подключается к GPIO пинам 5 и 6 для передачи сигнала.

7.1. Выполним подключение согласно таблице 3.

Макетная плата

HC-SR04

5V

5V

GND

GND

GPIO 5

ECHO

GPIO 6

TRIG

Таблица 3. Подключение HC-SR04 к макетной плате.

7.2. Итоговый вид проекта выглядит следующим образом:

8. Аналогично пункту 2 выполним проверку подключения датчика.

8.1. Если ранее не устанавливали все зависимости командой setup-all, то установим зависимости для RGB модуля, выполнив:

make setup-HC-SR04 

8.2. В скрипте, который находится по пути devices/sensors/HC-SR04_example, необходимо изменить уникальные идентификаторы пинов GPIO, чтобы они соответствовали вашему подключению.

# Инициализация пинов

TRIGGER_PIN = 31  # GPIO пин для Trigger
ECHO_PIN = 29     # GPIO пин для Echo

8.2. Запустим python скрипт:

make HC-SR04 

8.3. При правильном подключении в консоли будет отображаться расстояние до цели:

Запуск проекта

Теперь, когда все компоненты подключены, можно запустить проект "Парковочиный". Для этого в репозитории repka-pi_iot-examples выполняем команду:

make parking-assistant

После запуска в консоли может наблюдать расстояние до цели:
На 7-сегментном дисплее будет отображаться первый разряд числа, которое измеряется с помощью ультразвукового датчика, и отображаться на дисплее, показывая значение до сотни. Зуммер будет изменять свой тон в зависимости от расстояния: чем ближе объект, тем выше тон. RGB-лента также будет изменять цвет в зависимости от расстояния: зеленый — для дальнего расстояния, красный — для ближнего, синий — для средней дистанции.

Вы можете собрать более бюджетную версию данного проекта.

Для более бюджетной реализации проекта можно обойтись без активного охлаждения, используя Repka Pi 4 в стандартной комплектации, а также макетную плату без внешнего источника питания — при этом остальные компоненты остаются неизменными. Макетную плату можно приобрести здесь.

Для подключения нам потребуется “Распиновка портов на 40 pin разъёме на Repka Pi 4“, см. рисунок 8.

Поскольку расширительная плата GPIO полностью повторяет конфигурацию распиновки, можно применить те же таблицы и схемы, которые использовались ранее для устройств.

9. Выполним зуммера к макетной плате согласно таблице 1:

Проверка подключения зуммера осуществляется аналогично пункту 2.

10. Выполним подключение 7-сегментного индикатор 0.36" аналогично пункту 3:

11. Подключение адресной светодиодной ленты (WS2812). Выполним подключение согласно таблице 2:

Аналогично пункту 6 выполним проверку подключения ленты.

12. Подключение ультразвуковой датчик расстояния (HC-SR04). Выполним подключение согласно таблице 3:

Для снижения нагрузки на линию 5 В одноплатного компьютера Repka Pi 4, рекомендуется использовать дополнительное питание 5 В:

А. Подключение с использованием лабораторного блока питания:

Б. Подключение с использованием аккумуляторного блока питания:

Программная реализация #

Программное обеспечение для "Ультразвукового парковочного ассистента" решает несколько задач одновременно: измеряет расстояние до препятствия, отображает его на цифровом индикаторе, визуализирует опасность с помощью RGB-ленты и издает звуковой сигнал. Рассмотрим, как эта логика реализована на Python.

Алгоритм работы #

В основе программы лежит простой, но эффективный алгоритм, работающий в непрерывном цикле для постоянного мониторинга обстановки позади автомобиля.


Блок схема для проекта Ультразвуковой парковочный ассистент

Описание алгоритма:

  1. Старт и инициализация: При запуске программа инициализирует все аппаратные компоненты: настраивает GPIO-пины для ультразвукового датчика, 7-сегментного индикатора и зуммера, а также открывает SPI-интерфейс для управления RGB-лентой.
  2. Начало цикла: Программа входит в бесконечный цикл.
  3. Измерение расстояния: На каждой итерации с помощью ультразвукового датчика HC-SR04 измеряется расстояние до ближайшего препятствия.
  4. Анализ и индикация:
    • 7-сегментный индикатор: Если расстояние меньше или равно 100 см, оно отображается на цифровом дисплее. Если препятствие дальше, дисплей гаснет.
    • RGB-лента: Цвет всей ленты меняется в зависимости от расстояния, создавая интуитивно понятную визуальную подсказку:
      • Зеленый: Расстояние безопасно (более 50 см).
      • Желтый: Препятствие близко (от 20 до 50 см).
      • Красный: Критическое сближение (менее 20 см).
    • Зуммер: Если расстояние меньше 100 см, активируется звуковой сигнал. Частота сигнала напрямую зависит от расстояния — чем ближе препятствие, тем выше и тревожнее звук.
  5. Пауза: Программа делает короткую паузу (1 секунду) перед следующим измерением.
  6. Повторение: Цикл возвращается к шагу 3.
  7. Завершение: При нажатии Ctrl+C программа корректно завершает работу, отключая все компоненты и освобождая системные ресурсы.

Реализация на Python #

1. Функциональный подход #

Этот подход реализует программу как набор отдельных функций, каждая из которых отвечает за свою часть общей задачи.

Импорты, константы и инициализация

В начале скрипта импортируются библиотеки и определяются все необходимые константы и глобальные объекты для работы с оборудованием.

import time
import RepkaPi.GPIO as GPIO
import spidev

# Определение номеров пинов
TRIGGER_PIN = 31
ECHO_PIN = 29
segment_pins = {
    'A': 18, 'B': 22, 'C': 13, 'D': 11,
    'E': 7,  'F': 12, 'G': 16, 'H': 15
}
BEEP_PIN = 32

# Настройка SPI
NUM_LEDS = 50
spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz = 3200000

Объяснение:

  • Библиотеки: Используется RepkaPi.GPIO для управления всеми GPIO-пинами и spidev для отправки данных на RGB-ленту по протоколу SPI.
  • Константы: Номера пинов для всех компонентов (датчик, сегменты дисплея, зуммер) и параметры SPI вынесены в начало кода для легкой конфигурации.
  • Инициализация SPI: Создается и настраивается объект spidev, который будет использоваться для управления светодиодной лентой.

Функции-драйверы

Каждая аппаратная часть системы управляется своим набором функций.

def measure_distance():
    # ... (код для отправки импульса и измерения времени эха) ...

def display_number(number):
    # ... (код для отображения числа на 7-сегментном индикаторе) ...

def send_colors(colors):
    # ... (код для преобразования цветов и отправки по SPI) ...

def alarm_signal(distance):
    # ... (код для генерации звука на зуммере) ...

Объяснение:

  • measure_distance: Реализует алгоритм работы с датчиком HC-SR04: посылает короткий ультразвуковой импульс и измеряет время, за которое он вернется, отразившись от препятствия. На основе этого времени вычисляется расстояние.
  • display_number: Управляет 7-сегментным индикатором. Она использует словарь-карту segment_map для определения, какие из 8 сегментов нужно зажечь для отображения конкретной цифры.
  • send_colors: Отвечает за сложный процесс управления RGB-лентой. Она преобразует массив цветов в специфическую последовательность байтов, которую понимают светодиоды WS2812B, и отправляет эти данные через SPI.
  • alarm_signal: Генерирует звуковой сигнал с помощью программного ШИМ на пине зуммера. Частота сигнала вычисляется на основе расстояния, создавая эффект нарастающей тревоги.

Основной цикл программы

Главный блок try...finally инициализирует GPIO и запускает бесконечный цикл, который объединяет работу всех функций.

try:
    # --- Инициализация GPIO ---
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(TRIGGER_PIN, GPIO.OUT)
    GPIO.setup(ECHO_PIN, GPIO.IN)
    for pin in segment_pins.values():
        GPIO.setup(pin, GPIO.OUT)
    GPIO.setup(BEEP_PIN, GPIO.OUT)

    while True:
        distance = measure_distance()
        print(f"Расстояние: {distance:.2f} см")

        if distance <= 100:
            display_number(int(distance // 10))
        else:
            # Очищаем дисплей
            for pin_number in segment_pins.values():
                GPIO.output(pin_number, GPIO.LOW)

        if distance < 20:
            send_colors([0x00FF00] * NUM_LEDS)  # Красный
        elif distance < 50:
            send_colors([0xFFFF00] * NUM_LEDS)  # Желтый
        else:
            send_colors([0xFF0000] * NUM_LEDS)  # Зеленый

        if distance < 100:
            alarm_signal(distance)

        time.sleep(1)

except KeyboardInterrupt:
    print("Программа завершена.")
finally:
    GPIO.cleanup()
    spi.close()

Объяснение:

  • Инициализация GPIO: Перед началом цикла все GPIO-пины настраиваются в нужный режим (вход или выход).
  • Цикл while True: На каждой итерации последовательно вызываются функции для измерения расстояния и управления всеми устройствами индикации в соответствии с бизнес-логикой.
  • Блок finally: Гарантирует, что при любом завершении программы (даже по Ctrl+C) все GPIO-пины будут освобождены (GPIO.cleanup()), а SPI-соединение — закрыто. Это критически важно для корректной работы системы.
2. Объектно-ориентированный подход (ООП) #

Этот подход инкапсулирует всю логику работы парковочного ассистента в единый класс ParkingAssistant, что делает код более структурированным и переиспользуемым.

Определение класса ParkingAssistant

Класс содержит все константы, методы для инициализации и управления каждым компонентом.

class ParkingAssistant:
    # Настройка пинов и параметров
    TRIGGER_PIN = 31
    ECHO_PIN = 29
    segment_pins = { ... }
    # ... другие константы ...

    def __init__(self):
        # Инициализация GPIO и SPI
        GPIO.setmode(GPIO.BOARD)
        # ... (полная настройка всех пинов) ...
        self.spi = spidev.SpiDev()
        self.spi.open(0, 0)
        self.spi.max_speed_hz = 3200000

    def measure_distance(self):
        # ... (логика измерения расстояния) ...

    def display_number(self, number):
        # ... (логика отображения на 7-сегментном индикаторе) ...
    
    def send_colors(self, colors):
        # ... (логика отправки цветов на RGB-ленту) ...

    def alarm_signal(self, distance):
        # ... (логика генерации звука) ...

    def cleanup(self):
        GPIO.cleanup()
        self.spi.close()

Объяснение:

  • Инкапсуляция: Вся конфигурация (номера пинов, параметры) и все функции из предыдущего примера теперь являются частью класса ParkingAssistant. Это объединяет связанные данные и логику в одном месте.
  • __init__ (Конструктор): Выполняет всю необходимую инициализацию оборудования в момент создания объекта ParkingAssistant.
  • Методы: Функции measure_distance, display_number и другие теперь являются методами класса. Они работают с атрибутами объекта (например, self.TRIGGER_PIN, self.spi).
  • cleanup: Добавлен специальный метод для освобождения ресурсов, который будет вызываться при завершении работы.

Основной цикл и точка входа

Метод run() реализует главный рабочий цикл, а блок if __name__ == "__main__" запускает систему.

    def run(self):
        try:
            while True:
                distance = self.measure_distance()
                print(f"Расстояние: {distance:.2f} см")

                if distance <= 100:
                    self.display_number(int(distance // 10))
                else:
                    self.clear_display() # Используем метод для очистки

                # ... (логика для send_colors и alarm_signal с вызовом self.методов) ...
                
                time.sleep(1)
        except KeyboardInterrupt:
            print("Программа завершена.")
        finally:
            self.cleanup() # Вызываем единый метод очистки

if __name__ == "__main__":
    assistant = ParkingAssistant()
    assistant.run()

Объяснение:

  • run(): Этот метод содержит ту же самую бизнес-логику, что и в функциональном подходе, но теперь она выглядит чище, так как вызывает методы того же объекта (self.measure_distance(), self.display_number()).
  • Точка входа: Код в if __name__ == "__main__" теперь предельно прост: создать один объект ParkingAssistant и вызвать его метод run(). Это наглядно демонстрирует, как ООП позволяет скрыть всю сложность реализации за простым и понятным интерфейсом.
3. Подход с использованием библиотеки RepkaPi.GPIO.SYSFS

Этот подход демонстрирует, как использование специализированной высокоуровневой библиотеки, разработанной для конкретного оборудования (RepkaPi), может значительно упростить код и сделать его более читаемым, при этом сохраняя полный контроль над GPIO.

Импорты, константы и инициализация

В начале скрипта импортируются библиотеки и определяются все необходимые константы и глобальные объекты для работы с оборудованием.

import time
import RepkaPi.GPIO as GPIO
import spidev

# Настройка пинов для HC-SR04
TRIGGER_PIN = 31
ECHO_PIN = 29

# Настройка пинов для 7-сегментного дисплея
segment_pins = {
    'A': 18, 'B': 22, 'C': 13, 'D': 11,
    'E': 7,  'F': 12, 'G': 16, 'H': 15
}

# Настройка SPI для RGB LED
NUM_LEDS = 50
spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz = 3200000

# Настройка пина для зуммера
BEEP_PIN = 32

Объяснение:

  • Библиотеки: Ключевым является импорт RepkaPi.GPIO as GPIO. Эта библиотека предоставляет удобный и привычный интерфейс для управления пинами ввода-вывода, аналогичный популярным библиотекам для других одноплатных компьютеров, но адаптированный специально для Repka Pi. Для управления RGB-лентой используется стандартная библиотека spidev.
  • Константы: Номера пинов для всех компонентов (датчик, сегменты дисплея, зуммер) и параметры SPI вынесены в начало кода для легкой конфигурации.

Измерение расстояния (measure_distance)

Эта функция реализует стандартный алгоритм работы с ультразвуковым датчиком HC-SR04.

def measure_distance():
    # 1. Отправляем короткий импульс на пин TRIGGER
    GPIO.output(TRIGGER_PIN, GPIO.HIGH)
    time.sleep(0.00001)
    GPIO.output(TRIGGER_PIN, GPIO.LOW)

    # 2. Ждем, пока датчик получит отраженный сигнал (эхо).
    start_time = time.time()
    stop_time = time.time()
    while GPIO.input(ECHO_PIN) == GPIO.LOW:
        start_time = time.time()
    while GPIO.input(ECHO_PIN) == GPIO.HIGH:
        stop_time = time.time()

    # 3. Рассчитываем расстояние
    duration = stop_time - start_time
    distance = (duration * 34300) / 2
    return distance

Объяснение: Функция точно следует протоколу датчика: посылает 10-микросекундный импульс на пин TRIGGER, а затем замеряет длительность ответного импульса на пине ECHO с помощью GPIO.input(). Расстояние вычисляется по классической формуле, учитывающей скорость звука.

Управление 7-сегментным индикатором (display_number)

Этот блок отвечает за корректное отображение цифр на индикаторе.

# Карта состояний сегментов для каждой цифры
segment_map = [
    (1, 1, 1, 1, 1, 1, 0, 0), # 0
    # ... и так далее для 1-9
]

def display_number(number):
    if not 0 <= number <= 9:
        # Гасим все сегменты, если число некорректно
        for pin in segment_pins.values():
            GPIO.output(pin, GPIO.LOW)
        return
    
    segment_states = segment_map[number]
    segment_order = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']

    for i in range(len(segment_order)):
        seg_name = segment_order[i]
        pin = segment_pins[seg_name]
        state = segment_states[i]
        GPIO.output(pin, GPIO.HIGH if state == 1 else GPIO.LOW)

Объяснение: Функция использует segment_map — массив, который является своего рода "шрифтом" для индикатора. Для вывода цифры она берет соответствующую строку из этого массива. Чтобы правильно сопоставить состояния с пинами (так как порядок в словаре segment_pins не гарантирован), используется промежуточный список segment_order. Это гарантирует, что состояние для сегмента 'A' из segment_map будет подано именно на пин, указанный для 'A' в словаре segment_pins.

Управление RGB-лентой и зуммером

Эти функции отвечают за визуальную и звуковую индикацию.

def color_to_spi_bytes(color):
    # ... (сложная логика преобразования цвета в последовательность байтов для WS2812B) ...

def send_colors(colors):
    data = []
    for c in colors:
        data.extend(color_to_spi_bytes(c))
    spi.xfer2(data)
    time.sleep(0.001)

def alarm_signal(distance):
    frequency = max(1000, 5000 - distance * 100)
    # ... (код для генерации звука на BEEP_PIN с помощью GPIO.output и time.sleep) ...

Объяснение:

  • color_to_spi_bytes и send_colors: Управление светодиодами WS2812B требует формирования очень специфического сигнала. Функция color_to_spi_bytes преобразует стандартное 24-битное значение цвета (например, 0xFF0000 для зеленого) в последовательность байтов, которая кодирует эти цвета для протокола WS2812B. send_colors отправляет эту последовательность на ленту через интерфейс SPI.
  • alarm_signal: Эта функция генерирует звук с помощью программного ШИМ — быстрого переключения GPIO-пина между высоким и низким состоянием. Частота переключений (frequency) вычисляется на основе расстояния, создавая эффект нарастающей тревоги при приближении к препятствию.

Основной цикл программы

Главный блок try...finally инициализирует GPIO и запускает бесконечный цикл, который объединяет работу всех функций.

try:
    # --- Инициализация GPIO ---
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(TRIGGER_PIN, GPIO.OUT)
    GPIO.setup(ECHO_PIN, GPIO.IN)
    for pin in segment_pins.values():
        GPIO.setup(pin, GPIO.OUT)
    GPIO.setup(BEEP_PIN, GPIO.OUT)

    while True:
        distance = measure_distance()
        # ... (логика вывода на дисплей, ленту и зуммер) ...
        time.sleep(1)

except KeyboardInterrupt:
    print("Программа завершена.")
finally:
    GPIO.cleanup()
    spi.close()

Объяснение:

  • Инициализация: Перед началом цикла все GPIO-пины настраиваются в нужный режим (вход или выход) с помощью GPIO.setup().
  • Цикл while True: На каждой итерации последовательно вызываются функции для измерения расстояния и управления всеми устройствами индикации в соответствии с бизнес-логикой.
  • finally: Этот блок гарантирует безопасное завершение. GPIO.cleanup() — это важная функция библиотеки RepkaPi.GPIO, которая сбрасывает состояние всех использованных пинов, предотвращая их случайную активацию после завершения скрипта. Также закрывается SPI-соединение.

Реализация на C #

Язык C в связке с библиотекой WiringRP представляет собой классический подход для программирования встраиваемых систем. Он обеспечивает максимальную производительность, низкое потребление ресурсов и полный, прямой контроль над аппаратным обеспечением, что критически важно для надежной работы таких устройств, как парковочный ассистент.

1. Реализация с WiringRP #

Данный проект демонстрирует профессиональный подход к разработке на C, используя модульную структуру. Для каждого аппаратного компонента создан свой "драйвер" в виде пары файлов: заголовочного (.h) и файла реализации (.c). Это делает код чистым, переиспользуемым и легким для отладки.

Структура проекта:

  • main.c: Содержит основную логику приложения, инициализацию всех модулей и главный цикл работы.
  • ultrasonic_hcsr04/: Модуль для точного измерения расстояния с помощью датчика HC-SR04.
  • seven_segment_display/: Драйвер для управления 7-сегментным индикатором.
  • buzzer/: Модуль для генерации звуковых сигналов на зуммере.
  • ws2812b_led_strip/: Низкоуровневый драйвер для управления адресной RGB-лентой по протоколу SPI.

Основной файл логики (main.c)

Этот файл является точкой входа и "мозгом" всей системы. Он инициализирует все модули и реализует основной цикл работы ассистента.

#include <stdio.h>
#include <stdlib.h>
#include <wiringrp/wiringRP.h>
// Подключение заголовочных файлов всех модулей
#include "ultrasonic.h"
#include "seven_segment.h"
#include "ws2812b_led.h"
#include "buzzer.h"

// ... (определение пинов и констант) ...

int main(int argc, char *argv[]) {
    // ... (настройка обработчика сигнала Ctrl+C) ...

    if (setupWiringRP(WRP_MODE_PHYS) < 0) exit(EXIT_FAILURE);
    
    // Инициализация каждого аппаратного модуля
    ultrasonic_init(TRIGGER_PIN, ECHO_PIN);
    seven_segment_init(seg_pins);
    buzzer_init(BEEP_PIN);
    if (ws2812b_init(SPI_DEVICE_PATH, NUM_LEDS) != 0) {
        exit(EXIT_FAILURE);
    }
    
    while (!done) {
        float distance = ultrasonic_measure_distance();
        printf("Расстояние: %.2f см\n", distance);

        // Логика отображения на 7-сегментном индикаторе
        if (distance <= 99) {
            seven_segment_display_digit((int)(distance / 10.0));
        } else {
            seven_segment_clear();
        }

        // Логика цвета для RGB-ленты
        uint32_t current_color;
        if (distance < 20)      current_color = 0xFF0000; // Красный
        else if (distance < 50) current_color = 0xFFFF00; // Желтый
        else                    current_color = 0x00FF00; // Зеленый
        
        uint32_t colors[NUM_LEDS];
        for (int i = 0; i < NUM_LEDS; i++) {
            colors[i] = current_color;
        }
        ws2812b_send_colors(colors);

        // Логика звукового сигнала
        if (distance < 100) {
            int freq = max(200, 3000 - (int)distance * 25);
            buzzer_beep(freq, 100);
        }

        delay(300);
    }
    
    // ... (код безопасного завершения и очистки ресурсов) ...
    return 0;
}

Объяснение:

  • Модульность: main.c подключает заголовочные файлы всех драйверов и оперирует только простыми, понятными функциями (ultrasonic_measure_distance, seven_segment_display_digit, ws2812b_send_colors, buzzer_beep). Вся сложность реализации скрыта внутри соответствующих модулей.
  • Основной цикл: В цикле while программа последовательно опрашивает датчик расстояния, а затем, на основе полученного значения, принимает решение о том, какую цифру показать, каким цветом зажечь ленту и какой звук издать.
  • Безопасное завершение: Вместо макроса ONDESTROY здесь используется стандартный для C механизм обработки сигналов (sigaction). Он "ловит" сигнал SIGINT (который посылается при нажатии Ctrl+C) и устанавливает флаг done, что позволяет циклу while завершиться корректно, после чего вызываются функции очистки ресурсов (ws2812b_release, releaseWiringRP).

Модуль ультразвукового датчика (ultrasonic.c)

Этот модуль отвечает за точное измерение времени прохождения звукового импульса.

// Фрагмент из ultrasonic.c
float ultrasonic_measure_distance(void) {
    digitalWrite(trigger_pin, HIGH);
    delayMicroseconds(10);
    digitalWrite(trigger_pin, LOW);

    unsigned long start_time_us = 0;
    unsigned long end_time_us = 0;

    // ... (циклы while с таймаутами для ожидания начала и конца эхо-сигнала) ...
    while (digitalRead(echo_pin) == LOW) { /* ждем начала */ }
    start_time_us = micros();
    while (digitalRead(echo_pin) == HIGH) { /* ждем конца */ }
    end_time_us = micros();
    
    float duration_us = (float)(end_time_us - start_time_us);
    float distance_cm = duration_us * 0.0343 / 2.0;

    return distance_cm;
}

Объяснение: Ключевым преимуществом реализации на C является использование функций delayMicroseconds() и micros() из WiringRP. Они работают с микросекундной точностью, что позволяет очень точно измерить длительность короткого эхо-импульса и, как следствие, получить более стабильные и точные показания расстояния по сравнению с Python-аналогами, основанными на time.sleep().

Модуль RGB-ленты (ws2812b_led.c)

Это самый низкоуровневый и сложный драйвер, так как светодиоды WS2812B требуют очень точных таймингов сигнала.

// Фрагмент из ws2812b_led.c
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>

// '0' кодируется как 100, '1' - как 110
const uint8_t BIT_0 = 0b100;
const uint8_t BIT_1 = 0b110;
static uint8_t *spi_buffer = NULL;

static void color_to_spi(uint32_t color, uint8_t *buffer_pos) {
    // ... (преобразование 24 бит цвета в 72 бита (9 байт) SPI-данных) ...
}

int ws2812b_init(const char *spi_device_path, int num_leds) {
    spi_fd = open(spi_device_path, O_WRONLY);
    // ... (настройка режима и скорости SPI через ioctl) ...
    size_t buffer_size = led_count * 9 + 2;
    spi_buffer = (uint8_t *)calloc(buffer_size, sizeof(uint8_t));
    return 0;
}

void ws2812b_send_colors(const uint32_t *colors) {
    for (int i = 0; i < led_count; i++) {
        color_to_spi(colors[i], spi_buffer + i * 9);
    }
    write(spi_fd, spi_buffer, led_count * 9);
}

Объяснение:

  • Кодирование таймингов: Протокол WS2812B кодирует биты '0' и '1' разной длительностью высокого сигнала. Вместо того чтобы генерировать это вручную, используется элегантный трюк: один бит цвета кодируется тремя битами, передаваемыми по SPI (100 для '0' и 110 для '1'). При определенной скорости SPI-шины (3.2 МГц) эти последовательности создают на выходе сигналы с нужными микросекундными таймингами.
  • Прямой доступ к SPI: Этот драйвер не использует WiringRP для работы с SPI, а обращается напрямую к драйверу spidev в ядре Linux через системные вызовы open, ioctl и write. Это профессиональный подход, дающий полный контроль над параметрами шины (скорость, режим), что критически важно для работы с такими требовательными устройствами, как WS2812B.
  • Буферизация: Функция ws2812b_send_colors сначала формирует в памяти (spi_buffer) полную последовательность байтов для всей ленты и только потом отправляет ее одним махом с помощью write(). Это обеспечивает непрерывный поток данных, необходимый для корректного обновления цветов.

Сравнение производительности: RepkaPi.GPIO (SysFS) vs WiringRP #

В рамках наших проектов мы использовали два разных подхода для взаимодействия с GPIO-пинами:

  1. Python с библиотекой RepkaPi.GPIO: Высокоуровневый подход, работающий через стандартный интерфейс ядра Linux SysFS.
  2. C с библиотекой WiringRP: Низкоуровневый подход, работающий максимально близко к "железу" через прямой доступ к памяти.

Возникает логичный вопрос: насколько велика разница в производительности и когда какой подход следует выбирать? Для ответа на этот вопрос был проведен объективный тест — бенчмарк.

Методика тестирования #

Чтобы измерить чистую скорость работы с GPIO, была поставлена простая задача: переключать один и тот же GPIO-пин из высокого состояния (HIGH) в низкое (LOW) и обратно так быстро, как это возможно, в течение 5 секунд. Эта операция "включить-выключить" является фундаментальной для любого проекта, работающего с GPIO, и ее скорость напрямую отражает эффективность используемой библиотеки.

Были написаны два минималистичных скрипта, реализующих этот тест.

Код на C с WiringRP

// benchmark_c_counter.c
#include <wiringrp/wiringRP.h>
#include <stdio.h>
#include <time.h>

#define TEST_PIN 7
#define BENCHMARK_DURATION 5

int main(void) {
    unsigned long long counter = 0;
    time_t start_time = time(NULL);

    if (setupWiringRP(WRP_MODE_PHYS) < 0) return 1;
    
    pinMode(TEST_PIN, OUTPUT);
    
    while (1) {
        digitalWrite(TEST_PIN, HIGH);
        digitalWrite(TEST_PIN, LOW);
        counter++;
        
        if (time(NULL) - start_time >= BENCHMARK_DURATION) {
            break;
        }
    }
    
    printf("Операций в секунду: %llu ops/sec\n", counter / BENCHMARK_DURATION);
    return 0;
}

Код на Python с RepkaPi.GPIO

# benchmark_c_counter.py
import RepkaPi.GPIO as GPIO
import time

TEST_PIN = 7
BENCHMARK_DURATION = 5

GPIO.setmode(GPIO.BOARD)
GPIO.setup(TEST_PIN, GPIO.OUT)

counter = 0
start_time = time.time()

try:
    while True:
        GPIO.output(TEST_PIN, GPIO.HIGH)
        GPIO.output(TEST_PIN, GPIO.LOW)
        counter += 1
        
        if time.time() - start_time >= BENCHMARK_DURATION:
            break
finally:
    GPIO.cleanup()

print(f"Операций в секунду: {counter // BENCHMARK_DURATION} ops/sec")

Результаты #

После компиляции C-кода и запуска обоих скриптов на Repka Pi были получены следующие результаты:

Подход Операций в секунду (ops/sec)
Python + RepkaPi.GPIO (SysFS) ~6,679
C + WiringRP (прямой доступ) ~484,638

Анализ результатов #

Как видно из таблицы, разница в производительности колоссальна: подход на C с использованием WiringRP оказался примерно в 72 раза быстрее, чем его аналог на Python. Эта разница обусловлена фундаментальными различиями в том, как эти библиотеки взаимодействуют с оборудованием.

  • WiringRP (C):

    • Компилируемый язык: Код на C преобразуется в нативные машинные инструкции, которые выполняются процессором напрямую без посредников.
    • Прямой доступ к памяти (/dev/mem): WiringRP изменяет состояние GPIO-пина путем прямой записи нужных значений в физические адреса памяти, где расположены регистры управления GPIO. С точки зрения системы, это одна быстрая операция записи в память.
  • RepkaPi.GPIO (Python):

    • Интерпретируемый язык: Код на Python выполняется через интерпретатор, который добавляет свои, хоть и небольшие, накладные расходы.
    • Интерфейс SysFS: Это ключевое отличие. Библиотека RepkaPi.GPIO работает через стандартный для Linux интерфейс SysFS. Для операционной системы GPIO-пины представлены в виде файлов в директории /sys/class/gpio/. Чтобы изменить состояние пина, библиотека выполняет целую последовательность действий:
      1. Отправляет системный вызов на открытие файла (например, /sys/class/gpio/gpio7/value).
      2. Отправляет системный вызов на запись в этот файл символа "1" или "0".
      3. Отправляет системный вызов на закрытие файла.

Каждый такой системный вызов требует переключения контекста между пользовательским пространством (где работает наш скрипт) и пространством ядра, что является относительно медленной операцией. Таким образом, одно простое действие GPIO.output() на самом деле порождает целую цепочку более медленных файловых операций.

Выводы и рекомендации #

Означают ли эти результаты, что RepkaPi.GPIO — плохая библиотека? Однозначно нет. Выбор инструмента всегда зависит от задачи.

  • Когда использовать Python и RepkaPi.GPIO? Почти всегда. Во всех проектах, которые мы рассмотрели (метеостанция, система полива, парктроник, RFID-сейф), основной цикл программы имеет задержку от сотен миллисекунд до нескольких секунд (time.sleep(1)). На фоне таких задержек разница в скорости выполнения GPIO.output() в несколько микросекунд абсолютно несущественна. Преимущества Python — скорость разработки, простота отладки, читаемость кода и огромное количество готовых библиотек — многократно перевешивают проигрыш в "чистой" производительности GPIO.

  • Когда использовать C и WiringRP? WiringRP и C становятся незаменимы, когда требуется работа в реальном времени или генерация высокочастотных сигналов. Например:

    • Программная реализация протоколов связи (например, "bit-banging" I2C или SPI).
    • Управление устройствами, требующими очень точных и коротких импульсов, недостижимых с помощью time.sleep().
    • Приложения, где критически важна минимальная и предсказуемая задержка реакции на событие.

Итог: Для подавляющего большинства образовательных и хобби-проектов удобство и скорость разработки на Python с RepkaPi.GPIO являются предпочтительным выбором. К мощи и производительности C и WiringRP следует обращаться тогда, когда вы точно знаете, что ваш проект упирается в пределы скорости или точности, которые может предоставить Python.

Практическая значимость проекта

Проект "Ультразвуковой парковочный ассистент" представляет собой эффективное применение ультразвуковых технологий для решения повседневных задач, таких как парковка. Этот проект позволяет интегрировать ультразвуковой датчик для измерения расстояния, создавая систему, которая визуально и с использованием звука информирует пользователя о расстоянии до объектов, что особенно полезно при парковке в ограниченных пространствах. Система с 7-сегментным дисплеем, RGB-светодиодами и пассивным зуммером делает использование устройства интуитивно понятным и доступным. Практическая значимость этого проекта заключается в его возможности улучшить безопасность и удобство парковки, предотвращая столкновения и обеспечивая точную информацию о расстоянии до препятствий.

Расширение проекта

Проект "Ультразвуковой парковочный ассистент" можно расширить несколькими способами для улучшения функциональности и повышения удобства использования:

  1. Интеграция с мобильными устройствами: можно добавить возможность отправки уведомлений на мобильные устройства пользователя с помощью Bluetooth или Wi-Fi, чтобы отслеживать ситуацию вокруг автомобиля дистанционно.

  2. Подключение дополнительных сенсоров: для повышения точности можно интегрировать другие типы датчиков, например, инфракрасные или камеры для создания более сложных систем парковки.

Эти дополнения сделают проект более гибким и функциональным, а также обеспечат его использование в более широком диапазоне приложений.

Видеообзор проекта

Для более детального ознакомления с проектом, вы можете посмотреть видеообзор на платформе Rutube:

Пример использования с Python

Проект полностью реализован на языке Python. Код для работы с парковочным ассистентом можно найти в репозитории на платформе Gitflic.


Комментарии (0)

Для участия в обсуждении Вы должны быть авторизованным пользователем

Еще посты по теме

Новые посты



Темы

Навигация

ВойтиРегистрация