Представляю вам образовательный проект — “Охота на светлячка” разработанный в рамках учебно-методического комплекса на базе одноплатного компьютера Repka PI 4.
Игра состоит из 10 раундов, в каждом из которых пользователю необходимо наклонить гироскоп в случайно выбранном направлении и нажать кнопку в течение заданного времени. За правильное выполнение задания игрок получает балл, а за ошибку теряет один. Результат каждого действия отображается с помощью RGB-светодиода и зуммера.
Проект будет собираться с использованием “Учебно-методический комплекс REPKA”. Схему сборки можно найти в разделе "Примеры готовых проектов" учебного пособия УМК “REPKA”.
Также все необходимые материалы и схемы подключения доступны в репозитории на платформе Gitflic.
Компоненты проекта
1. Дисплей OLED 0.96″ I2C см. рисунок 1. Отображает игровой процесс.
2. Зуммер пассивный модуль (Passive buzzer KY-006) см. рисунок 2. Сигнализирует о результате действия игрока.
3. Четырехконтактный светодиодный RGB модуль (RGB LED) см. рисунок 3. Отображает состояние игры: успех или ошибка.
4. Модуль трехосевого гироскопа и акселерометра (GY-521; MPU-6050) см. рисунок 4. Отслеживает наклон устройства.
5. Кнопка см. рисунок 5. Требуется для нажатия по таймингу.
Вы можете приобрести все необходимые компоненты отдельно от "Учебно-методический комплекс REPKA". Ссылки на модули приведены в таблице ниже.
Компонент | Ссылка на приобретение |
---|---|
Монтажная/макетная плата | Ссылка |
Шлейф | Ссылка |
Переходник с шлейфа на макетную плату | Ссылка |
Соединительные провода | |
Дисплей OLED 0.96 | Ссылка |
Зуммер пассивный модуль (Passive buzzer KY-006) | Ссылка |
Четырехконтактный светодиодный RGB модуль (RGB LED) | Ссылка |
Модуль трехосевого гироскопа и акселерометра (GY-521; MPU-6050) | Ссылка |
Кнопка | Ссылка |
Подготовительный этап
1. Подключим дополнительное питание 5V к макетной плате:
2. После чего выведем дополнительное питание на макетную плату:
3. Подключим переходник с шлейфа на макетную плату:
4. Соединим шлейф с переходником для подключения к макетной плате и Repka Pi 4:
5. Итоговый результат должен выглядеть таким образом:
Сборка проекта
Во время сборки проекта будем регулярно обращаться к электрической принципиальной схеме и монтажной схеме, представленными в учебном пособии (см. рисунки 6 и 7). Эти схемы будут служить основным ориентиром на всех этапах подключения компонентов, обеспечивая точность и правильность сборки устройства.
Для разработки кода будет использоваться текстовый редактор Geany, который входит в состав стандартного ПО Репка ОС.
Электрическая принципиальная схема
Монтажная схема
Т-образная расширительная плата GPIO полностью повторяет конфигурацию распиновки:
1. Подключение дисплея OLED 0.96″ I2C.
Как видно из рисунков 6 и 7 устройство подключается через интерфейс I2C и питается от 5V.
1.1. Подключим дисплей OLED 0.96″ I2C к макетной плате согласно таблице 1.
Макетная плата | Дисплей OLED 0.96″ I2C |
5V | VDD |
GND | GND |
SCL1 | SCK |
SDA1 | SDA |
Таблица 1. Подключение дисплея OLED 0.96″ I2C к макетной плате.
1.2. Результат подключения будет выглядеть следующим образом, см. рисунок 8:
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-OLED-SSD1306
2.3.2. Если хотите установить зависимости для всех датчиков и проектов, выполните:
make setup-all
2.4. Запускаем скрипт для проверки работоспособности прибора:
make OLED-SSD1306
2.5. Если на этапе 2.4. возникает ошибка, то измените в python скрипте номер шины – port или адрес на шине – address, если ошибок нет, то пропустите данный пункт.
# Инициализация подключения к OLED дисплею через шину I2C
port=1 # номер I2C-шины Repka PI (обычно 1)
address=0x3C # адрес OLED дисплея на шине I2C (часто 0x3C или 0x3D)
serial = i2c(port=1, address=0x3C
2.6. Из рисунка 9 видим, что скрипт успешно выполнился, тестовый текст успешно появился на дисплее.
3. Подключение пассивного зуммера.
Обратимся к рисункам 6 и 7. Из них видно, что устройство подключается через GPIO4 и питается от 3.3V.
3.1. Выполним подключение к макетной плате согласно таблице 2.
Макетная плата | Пассивный зуммер |
3.3V | VCC |
GPIO4 | IO |
GND | GND |
Таблица 2. Подключение пассивного зуммера к макетной плате.
3.2. Результат подключения будет выглядеть следующим образом, см. рисунок 10:
4. Аналогично пункту 2 выполним проверку подключения датчика.
4.1. Установим зависимости для зуммера, выполнив:
make setup-KY-006
4.2. Выполните команду:
make KY-006
4.2.1. Если нет никакой реакции, то проверьте номер GPIO указанный в скрипте по пути devices/executive/KY-006_example/py.
BEEP_PIN = 111 # Укажите номер пина, к которому подключен BEEP
4.2.2. Обратимся к пособию “УМК Репка. Учебно-методическое пособие”, в котором представлена распиновка Repka PI 4 (рисунок 11). Из нее следует, что уникальный идентификатор порта GPIO4 равен 362.
4.2.3. Внесем правки в код и выполним скрипт
BEEP_PIN = 362 # Укажите номер пина, к которому подключен BEEP
4.3. После запуска зуммер должен воспроизвести мелодию.
5. Подключение четырехконтактного светодиодного RGB модуля (RGB LED)
Обратимся к рисункам 6 и 7. Из них видно, что устройство подключается через GPIO17, GPIO27, GPIO22.
5.1. Подключим RGB LED к макетной плате согласно таблице 3:
Макетная плата | RGB LED |
GPIO17 | R |
GPIO27 | G |
GPIO22 | B |
GND | GND |
Таблица 3. Подключение RGB LED к макетной плате.
5.2. Результат подключения будет выглядеть следующим образом, см. рисунок 12.
6. Аналогично пункту 4 выполним проверку подключения модуля.
6.1. Если ранее не устанавливали все зависимости командой setup-all, то установим зависимости для RGB модуля, выполнив:
make setup-rgb-led
6.2. Запустим python скрипт:
make rgb-led
6.3. Если все подключено верно, то в консоли появится выбор работы программы, см. рисунок 13.
6.4. Выберем только красный цвет и оценим результат, см. рисунок 14.
7. Подключение трехосевого гироскопа и акселерометра (GY-521; MPU-6050).
Обратимся к рисункам 6 и 7. Из них видно, что устройство подключается по интерфейсу I2C и питается от 5V.
7.1. Выполним подключение к макетной плате согласно таблице 4.
Макетная плата | Трехосевой гироскоп |
5V | VCC |
GND | GND |
SCL | SCL |
SDA | SDA |
Таблица 4. Подключение трехосевого гироскопа и акселерометра к макетной плате.
7.2. Результат подключения будет выглядеть следующим образом, см. рисунок 15:
8. Аналогично пункту 4 выполним проверку подключения датчика.
8.1. Если ранее не устанавливали все зависимости командой setup-all, то установим зависимости для RGB модуля, выполнив:
make setup-gy-521
8.2. Запустим python скрипт:
make gy-521
8.3. Если все подключено правильно, в консоли отобразятся данные с датчика GY-521, как показано на рисунке 16.
9. Подключение кнопки.
Обратимся к рисункам 6 и 7. На них показано, что устройство подключается к GPIO18, а также имеется подтяжка к плюсу через резистор номиналом 4.7 кОм.
9.1. Выполним подключение к макетной плате согласно таблице 5.
Макетная плата | Трехосевой гироскоп |
GND | GND |
GPIO18 | GPIO |
Таблица 5. Подключение кнопки к макетной плате.
9.2. Результат подключения будет выглядеть следующим образом, см. рисунок 17.
10. Аналогично пункту 4 выполним проверку подключения датчика.
10.1. Установим зависимости для кнопки, выполнив:
make setup-one-button
10.2. Выполним python скрипт:
make one-button
10.2.1. Если нет никакой реакции, то проверьте номер GPIO указанный в скрипте по пути devices/input-output/one_button_example/py, в данном проекте для кнопки используется GPIO с id равным 203, см. рисунок 11.
PIN1 = 203 # GPIO17
10.3. После выполнения Python-скрипта в консоли должна появиться индикация нажатия кнопки.
Запуск проекта
Теперь, когда все компоненты подключены, можно запустить проект "Охота на светлячка". Для этого в репозитории repka-pi_iot-examples выполняем команду:
make hunting-fireflies
После запуска на дисплее можем наблюдать игровой процесс, а принимать участие в нем можем благодаря кнопки и гороскопу, см. рисунок 19.
Вы можете собрать более бюджетную версию данного проекта.
Для более бюджетной реализации проекта можно обойтись без активного охлаждения, используя Repka Pi 4 в стандартной комплектации, а также макетную плату без внешнего источника питания — при этом остальные компоненты остаются неизменными.
Для подключения нам потребуется “Распиновка портов на 40 pin разъёме на Repka Pi 4“, см. рисунок 11.
Поскольку расширительная плата GPIO полностью повторяет конфигурацию распиновки, можно применить те же таблицы и схемы, которые использовались ранее для устройств.
11. Подключим дисплей OLED 0.96″ I2C к макетной плате согласно таблице 1.
Проверка подключения дисплея осуществляется аналогично пункту 2.
12. Выполним подключение к зуммера к макетной плате согласно таблице 2.
Проверка подключения зуммера осуществляется аналогично пункту 4.
13. Подключим RGB LED к макетной плате согласно таблице 3:
Проверка подключения RGB LED осуществляется аналогично пункту 6.
14. Подключение трехосевого гироскопа и акселерометра (GY-521; MPU-6050).
Проверка подключения RGB LED осуществляется аналогично пункту 8.
Для снижения нагрузки на линию 5 В одноплатного компьютера Repka Pi 4, рекомендуется использовать дополнительное питание 5 В:
А. Лабораторный блока питания:
Б. Аккумуляторный блока питания:
Разбор кода проекта.
Проект реализован на языке python. Для того чтобы понять логику работы программы рассмотрите блок схему ниже:
Импортируются библиотеки для работы с оборудованием: гироскоп, дисплей, кнопка, светодиоды, зуммер:
import time, random
from periphery import GPIO
from mpu6050 import mpu6050
from luma.core.interface.serial import i2c
from luma.oled.device import sh1106
from PIL import Image, ImageDraw, ImageFont
Инициализация OLED-дисплея:
serial = i2c(port=1, address=0x3C)
device = sh1106(serial)
font = ImageFont.load_default()
Настройка пина кнопки: сначала как выход (сбросить уровень), затем как вход:
BTN_PIN = 203
temp_gpio = GPIO(BTN_PIN, "out")
temp_gpio.write(False)
temp_gpio.close()
btn = GPIO(BTN_PIN, "in")
Управление RGB-светодиодом:
R = GPIO(111, "out")
G = GPIO(112, "out")
B = GPIO(113, "out")
def led(r=False, g=False, b=False): ...
def led_off(): ...
Управление зуммером: генерация звука нужной частоты и длительности:
BEEP_PIN = 362
beep = GPIO(BEEP_PIN, "out")
def beep_tone(freq=1000, duration=0.2): ...
Получение данных акселерометра и определение направления наклона устройства:
Пример: is_tilted_to('up') вернет True, если устройство наклонено вверх.
sensor = mpu6050(0x68)
def is_tilted_to(direction, threshold=5): ...
Вывод текста и стрелок на OLED-дисплей:
def display_text(line1="", line2=""): ...
def draw_arrow(direction): ...
Основной игровой цикл: 10 раундов, в каждом случайное направление, проверка наклона и нажатия кнопки:
За правильное действие — +1 балл, за ошибку или промедление — -1 балл.
def play_game():
score = 0
display_text("Firefly Hunt", "Get Ready!")
time.sleep(2)
for round_num in range(1, 11):
...
required_direction = random.choice(["up", "down", "left", "right"])
draw_arrow(required_direction)
...
while time.time() - firefly_time < 1.5:
if not btn.read():
btn_pressed = True
if is_tilted_to(required_direction):
press_ok = True
else:
press_ok = False
break
time.sleep(0.01)
...
if btn_pressed and press_ok:
led(g=True)
beep_tone(2000, 0.2)
score += 1
display_text("Success!", f"Score: {score}")
else:
led(r=True)
beep_tone(1000, 0.2)
score -= 1
...
time.sleep(1)
led_off()
display_text("Game Over", f"Final: {score}")
time.sleep(5)
device.clear()
Запуск игры, обработка выхода по Ctrl+C, корректное закрытие ресурсов:
try:
play_game()
except KeyboardInterrupt:
print("Выход...")
finally:
btn.close()
R.close()
G.close()
B.close()
beep.close()
Пример кода выше с использованием ООП
Определяем класс FireflyGame, который инкапсулирует всю логику игры и работу с устройствами:
class FireflyGame:
В конструкторе класса def _init_ инициализируем все устройства:
def __init__(self):
self.device = sh1106(i2c(port=1, address=0x3C))
self.font = ImageFont.load_default()
self.btn = self._init_button(203)
self.leds = { 'R': GPIO(111, "out"), 'G': GPIO(112, "out"), 'B': GPIO(113, "out") }
self.beep = GPIO(362, "out")
self.sensor = mpu6050(0x68)
Вспомогательный метод _init_button для корректной инициализации кнопки:
Сначала выставляем низкий уровень, затем переводим пин в режим входа.
def _init_button(self, pin):
temp_gpio = GPIO(pin, "out")
temp_gpio.write(False)
temp_gpio.close()
return GPIO(pin, "in")
Метод def led для управления цветом RGB-светодиода:
Можно включать любой цвет, передав True для нужного канала.
def led(self, r=False, g=False, b=False):
self.leds['R'].write(r)
self.leds['G'].write(g)
self.leds['B'].write(b)
Метод led_off для выключения всех каналов светодиода:
def led_off(self):
self.led()
Метод beep_tone для генерации звука на зуммере с заданной частотой и длительностью:
def beep_tone(self, freq=1000, duration=0.2):
period = 1 / freq
half = period / 2
end = time.time() + duration
while time.time() < end:
self.beep.write(True)
time.sleep(half)
self.beep.write(False)
time.sleep(half)
Метод is_tilted_to для проверки, наклонено ли устройство в нужном направлении:
Использует данные акселерометра.
def is_tilted_to(self, direction, threshold=5):
accel = self.sensor.get_accel_data()
x, y = accel['x'], accel['y']
if direction == "up":
return y > threshold
elif direction == "down":
return y < -threshold
elif direction == "left":
return x < -threshold
elif direction == "right":
return x > threshold
return False
Метод display_text для вывода двух строк текста на OLED-дисплей:
def display_text(self, line1="", line2=""):
from PIL import ImageDraw, Image
image = Image.new("1", self.device.size)
draw = ImageDraw.Draw(image)
draw.rectangle((0, 0, self.device.width, self.device.height), outline=0, fill=0)
draw.text((0, 0), line1, font=self.font, fill=255)
draw.text((0, 15), line2, font=self.font, fill=255)
self.device.display(image)
Метод draw_arrow для отображения стрелки нужного направления и подсказки на дисплее:
def draw_arrow(self, direction):
from PIL import ImageDraw, Image
image = Image.new("1", self.device.size)
draw = ImageDraw.Draw(image)
draw.rectangle((0, 0, self.device.width, self.device.height), outline=0, fill=0)
cx, cy = self.device.width // 2, self.device.height // 2
if direction == "up":
draw.polygon([(cx, cy - 10), (cx - 5, cy), (cx + 5, cy)], fill=255)
elif direction == "down":
draw.polygon([(cx, cy + 10), (cx - 5, cy), (cx + 5, cy)], fill=255)
elif direction == "left":
draw.polygon([(cx - 10, cy), (cx, cy - 5), (cx, cy + 5)], fill=255)
elif direction == "right":
draw.polygon([(cx + 10, cy), (cx, cy - 5), (cx, cy + 5)], fill=255)
draw.text((0, 0), "* Firefly! *", font=self.font, fill=255)
draw.text((0, 15), "Tilt & Press", font=self.font, fill=255)
self.device.display(image)
Основной игровой цикл play:
-
10 раундов
-
В каждом раунде случайное направление
-
Ожидание наклона и нажатия кнопки
-
Подсчет очков, обратная связь через светодиод, зуммер и дисплей
def play(self):
score = 0
self.display_text("Firefly Hunt", "Get Ready!")
time.sleep(2)
for round_num in range(1, 11):
self.display_text(f"Round {round_num}", "Prepare...")
time.sleep(random.uniform(3, 6))
required_direction = random.choice(["up", "down", "left", "right"])
self.draw_arrow(required_direction)
firefly_time = time.time()
btn_pressed = False
press_ok = False
while time.time() - firefly_time < 1.5:
if not self.btn.read():
btn_pressed = True
if self.is_tilted_to(required_direction):
press_ok = True
else:
press_ok = False
break
time.sleep(0.01)
if btn_pressed and press_ok:
self.led(g=True)
self.beep_tone(2000, 0.2)
score += 1
self.display_text("Success!", f"Score: {score}")
else:
self.led(r=True)
self.beep_tone(1000, 0.2)
score -= 1
if not btn_pressed:
self.display_text("Too Slow!", f"Score: {score}")
elif not self.is_tilted_to(required_direction):
self.display_text("Wrong Tilt!", f"Score: {score}")
else:
self.display_text("Missed!", f"Score: {score}")
time.sleep(1)
self.led_off()
self.display_text("Game Over", f"Final: {score}")
time.sleep(5)
self.device.clear()
Метод cleanup для корректного освобождения всех ресурсов (закрытие пинов):
def cleanup(self):
self.btn.close()
for led in self.leds.values():
led.close()
self.beep.close()
Точка входа: создаём объект игры, запускаем игровой цикл, при завершении освобождаем ресурсы.
if __name__ == "__main__":
game = FireflyGame()
try:
game.play()
except KeyboardInterrupt:
print("Выход...")
finally:
game.cleanup()
Преимущества ООП-подхода:
1. Модульность и повторное использование кода
В ООП каждая функциональная часть системы (например, работа с датчиком, реле, дисплеем) оформляется в виде отдельного класса.
Это позволяет легко переиспользовать эти классы в других проектах или расширять функциональность без переписывания кода.
Например, если потребуется добавить второй датчик или другой тип дисплея, можно просто создать новый класс или унаследовать существующий.
2. Упрощение поддержки и масштабирования
Код, разделённый на классы с чётко определённой ответственностью, проще читать, тестировать и отлаживать.
Если возникает ошибка или требуется доработка, достаточно изменить только соответствующий класс, не затрагивая остальной код.
Это особенно важно для сложных или развивающихся проектов, где часто появляются новые требования.
3. Инкапсуляция и защита данных
ООП позволяет скрыть внутренние детали реализации (например, работу с I2C или обработку ошибок) внутри класса.
Внешний код работает только с публичными методами, не заботясь о низкоуровневых деталях.
Это снижает вероятность ошибок, связанных с неправильным использованием компонентов, и делает интерфейс системы более понятным и безопасным.
Практическая значимость проекта
Проект "Охота на светлячка" имеет большую практическую значимость в образовательном процессе. Он представляет собой интерактивную игру, которая позволяет студентам и ученикам развивать навыки работы с одноплатными компьютерами, такими как Repka PI 4, а также освоить различные компоненты и модули, включая гироскопы, RGB-светодиоды и OLED-дисплеи. Помимо этого, проект помогает в обучении основам программирования на Python, а также в освоении принципов подключения и конфигурирования электронных компонентов с использованием интерфейсов I2C и GPIO.
Проект способствует развитию навыков в области электроники, программирования и взаимодействия с различными датчиками и модулями, что является необходимым в современных направлениях инженерии и IT. Он может быть использован в качестве основы для создания более сложных проектов, связанных с сенсорами, игровыми интерфейсами и робототехникой.
Расширение проекта
Проект может быть расширен и адаптирован для более сложных приложений. Например, можно добавить возможность многопользовательского режима, в котором несколько игроков могут соревноваться за наибольшее количество баллов. Для этого потребуется реализовать дополнительные входы для кнопок и гироскопов, а также использовать более сложные алгоритмы синхронизации данных.
Для интеграции с внешними системами можно добавить возможность сохранять результаты игры в облаке или на локальном сервере, что позволит отслеживать успехи игроков и создавать таблицы лидеров. Это расширение будет полезным для организации соревнований или образовательных турниров.
Видеообзор проекта
Для более детального ознакомления с проектом, вы можете посмотреть видеообзор на платформе Rutube:
Пример использования с Python
Проект полностью реализован на языке Python. Код для работы можно найти в репозитории на платформе Gitflic.