Продвинутый умный счетчик для баскетбола с Bluetooth, датчиками и обновлением в реальном времени
Компоненты и материалы
- Arduino Nano R3 — 1 шт.
- Bluetooth-модуль HC-05 — 1 шт.
- ИК-датчик приближенности E18-D80NK — 1 шт.
- Датчик вибрации SW-420 (Grove) — 1 шт.
- Датчик температуры и влажности DHT22 — 1 шт.
- RGB-светодиод 8 мм — 1 шт.
- Резисторы: 10 кОм (3 шт.), 100 Ом (3 шт.), 47 Ом (1 шт.), 1 кОм (1 шт.), 2,21 кОм (1 шт.)
- Крона 9В — 1 шт.
Необходимые инструменты и сервисы
- Arduino IDE
- MIT App Inventor (Android IDE)
О проекте
Я регулярно занимаюсь баскетболом и веду статистику тренировок (броски, попадания, промахи). Ручной подсчет утомителен. Умные мячи дороги, неточны и быстро изнашиваются.
В предыдущем проекте я использовал устройство Avnet SmartEdge для отслеживания тренировок: акселерометр определял броски, датчик приближенности — попадания. Теперь цель — создать готовое решение.
Версии
Версия 1.1.0: собрана из имеющихся компонентов, ПО написано для Arduino и Android. Работала хорошо.
Версия 2.0.0: заменены компоненты на более дешевые, улучшено ПО.
- Arduino Mega 2560 → Arduino Nano R3
- Bluetooth Shield → HC-05
- Li-ion аккумулятор → Крона 9В
- Добавлен датчик DHT22
ИК-датчик E18-D80NK выбран за устойчивость к солнечному свету. Датчик вибрации SW-420 работает надежно.
Схемы для обеих версий в разделе «Схемы».
Шаг 1: Arduino
Аппаратная часть
Компоненты версии 2.0.0 перечислены выше.
Программная часть
Разработано в Arduino IDE. Алгоритм:
- После инициализации (переменные, светодиод, Bluetooth) постоянно опрашиваются датчики.
- Если датчик приближенности срабатывает — это попадание.
- Если срабатывает датчик вибрации — был бросок, ждем 2 секунды для решения.
- В это время, если сработает датчик приближенности — сразу фиксируем попадание.
- По истечении 2 секунд, если приближенность не зафиксирована — это промах.
- Arduino передает по Bluetooth в Android информацию о броске (попадание/промах).
- Цикл повторяется.
Изменения в версии 2.0.0: поддержка HC-05, добавлен DHT22, улучшена логика детекции и сигнализации через светодиод.
Коды для обеих версий в разделе «Код».
Шаг 2: Android-приложение
Разработано в MIT App Inventor. Алгоритм:
- После инициализации ждет нажатия «Старт».
- При старте постоянно мониторит Bluetooth-соединение.
- При получении данных обновляет табло и воспроизводит звук уведомления.
- Повторяется до нажатия «Пауза».
- Кнопки: выбор Bluetooth-устройства, сброс счетчиков, корректировка табло.
Изменения в версии 2.0.0: горизонтальная ориентация, добавлены освещенность, влажность, температура, тепловой индекс, улучшены кнопки корректировки, функция «экран всегда включен», звуки нажатий, уведомления каждые 50 бросков.


Шаг 3: Монтаж на баскетбольную корзину
Использовалась обычная корзина для тренировок.

Сделано отверстие под датчик приближенности под кольцом.

Небольшое отверстие для датчика вибрации. Из-за кривизны пластика сделанה опорка из МДФ.


Датчики закреплены болтами и гайками.

Подключены остальные компоненты.



Дымовое тестирование.

Финальная установка на корзину.

Шаг 4: Тестирование на тренировке
Время испытаний. Гранд-финал — шоутайм!
Шаг 5: Заключительные замечания
- Система очень точная: крайне мало ложных срабатываний и редкие пропуски.
- Отлично получать статистику в реальном времени и после тренировки.
Удачи!
Исходный код
Arduino код (версия 1.1.0)
//----------------------------------------------------------------------------//
// Filename : Scoreboard.ino
// Description : Smart Basketball Scoreboard
// Version : 1.1.0
// Author : Marcelo Avila de Oliveira
//----------------------------------------------------------------------------//
// DEFINITIONS
//----------------------------------------------------------------------------//
// #define DEBUG
// #define DEBUG_PROX
// #define DEBUG_VIBR
//----------------------------------------------------------------------------//
// CONSTANTS
//----------------------------------------------------------------------------//
// PINS
const int prox_pin = 2;
const int vibr_pin = 3;
const int led_r_pin = 4;
const int led_g_pin = 5;
const int led_b_pin = 6;
// TIME
const unsigned long wait_interval = 3000;
// MATH
const float percent_to_bright_factor = 100 * log10(2) / log10(255);
//----------------------------------------------------------------------------//
// VARIABLES
//----------------------------------------------------------------------------//
// TIME
unsigned long wait_time;
// STATUS
boolean prox = false;
boolean vibr = false;
boolean wait = false;
//----------------------------------------------------------------------------//
// FUNCTIONS (SETTINGS)
//----------------------------------------------------------------------------//
void setup() {
pinMode(prox_pin, INPUT);
pinMode(vibr_pin, INPUT);
pinMode(led_r_pin, OUTPUT);
pinMode(led_g_pin, OUTPUT);
pinMode(led_b_pin, OUTPUT);
set_led(5, 100);
Serial.begin(9600);
setup_bluetooth();
set_led(4, 100);
#ifdef DEBUG
Serial.println("Board is alive");
Serial.println();
#endif
}
void setup_bluetooth() {
#ifdef DEBUG
Serial.println("Setting Bluetooth");
Serial.println();
#endif
Serial1.begin(38400);
Serial1.print("\r\n+STWMOD=0\r\n");
Serial1.print("\r\n+STNA=Arduino\r\n");
Serial1.print("\r\n+STOAUT=1\r\n");
Serial1.print("\r\n+STAUTO=0\r\n");
delay(2000);
Serial1.print("\r\n+INQ=1\r\n");
delay(2000);
while (Serial1.available()) {
delay(50);
Serial1.read();
}
}
//----------------------------------------------------------------------------//
// FUNCTIONS (LIGHT)
//----------------------------------------------------------------------------//
int percent_to_bright(int percent) {
return 256 - pow(2, percent / percent_to_bright_factor);
}
void set_led(int color, int bright) {
#ifdef DEBUG
Serial.println("Setting LED");
Serial.println();
#endif
if (color < 0 || color > 6 || bright < 0 || bright > 100) return;
int led_r_bright = 255, led_g_bright = 255, led_b_bright = 255;
int bright_aux = percent_to_bright(bright);
switch (color) {
case 0: led_g_bright = bright_aux; break; // GREEN
case 1: led_r_bright = bright_aux; led_g_bright = bright_aux; break; // YELLOW
case 2: led_r_bright = bright_aux; break; // RED
case 3: led_g_bright = bright_aux; led_b_bright = bright_aux; break; // CYAN
case 4: led_b_bright = bright_aux; break; // BLUE
case 5: led_r_bright = bright_aux; led_b_bright = bright_aux; break; // MAGENTA
case 6: led_r_bright = bright_aux; led_g_bright = bright_aux; led_b_bright = bright_aux; break; // WHITE
}
analogWrite(led_r_pin, led_r_bright);
analogWrite(led_g_pin, led_g_bright);
analogWrite(led_b_pin, led_b_bright);
}
//----------------------------------------------------------------------------//
// FUNCTIONS (CHECK)
//----------------------------------------------------------------------------//
void check_prox() {
if (!prox) {
if (digitalRead(prox_pin) == LOW) {
#ifdef DEBUG_PROX
Serial.println("Proximity detected");
Serial.println();
#endif
prox = true;
if (!vibr) {
wait = true;
wait_time = millis() + wait_interval;
}
set_shot(1);
}
}
}
void check_vibr() {
if (!prox && !vibr) {
if (digitalRead(vibr_pin) == HIGH) {
#ifdef DEBUG_VIBR
Serial.println("Vibration detected");
Serial.println();
#endif
vibr = true;
wait = true;
wait_time = millis() + wait_interval;
set_led(1, 100);
}
}
}
void check_wait() {
if (wait && millis() > wait_time) {
if (!prox) set_shot(0);
reset();
}
}
//----------------------------------------------------------------------------//
// FUNCTIONS (MIS)
//----------------------------------------------------------------------------//
void set_shot(int mode) {
if (mode == 0) set_led(2, 100);
else set_led(0, 100);
Serial1.print(mode);
delay(1000);
}
void reset() {
vibr = false; prox = false; wait = false;
set_led(4, 100);
}
//----------------------------------------------------------------------------//
// MAIN
//----------------------------------------------------------------------------//
void loop() {
check_prox();
check_vibr();
check_wait();
}
Android код (версия 1.1.0)
MIT App Inventor (https://ai2.appinventor.mit.edu/) — файл доступен для загрузки.
Arduino код (версия 2.0.0)
//----------------------------------------------------------------------------//
// Filename : Scoreboard.ino
// Description : Smart Basketball Scoreboard
// Version : 2.0.0
// Author : Marcelo Avila de Oliveira
//----------------------------------------------------------------------------//
// LIBRARIES
//----------------------------------------------------------------------------//
#include "DHT.h"
#include "SoftwareSerial.h"
//----------------------------------------------------------------------------//
// DEFINITIONS
//----------------------------------------------------------------------------//
#define DHTPIN 7
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
SoftwareSerial Bluetooth(10, 11); // RX, TX
// PINS
const int prox_pin = 2;
const int vibr_pin = 3;
const int led_r_pin = 4;
const int led_g_pin = 5;
const int led_b_pin = 6;
// TIME
const unsigned long wait_interval = 2000;
const unsigned long dht_interval = 10000;
// MATH
const float percent_to_bright_factor = 100 * log10(2) / log10(255);
//----------------------------------------------------------------------------//
// VARIABLES
//----------------------------------------------------------------------------//
unsigned long wait_time, dht_time;
float humi, temp, heat;
boolean prox = false, vibr = false, wait = false;
//----------------------------------------------------------------------------//
// FUNCTIONS (SETTINGS)
//----------------------------------------------------------------------------//
void setup() {
pinMode(prox_pin, INPUT);
pinMode(vibr_pin, INPUT);
pinMode(led_r_pin, OUTPUT);
pinMode(led_g_pin, OUTPUT);
pinMode(led_b_pin, OUTPUT);
set_led(5, 100); // MAGENTA
Serial.begin(9600);
setup_bluetooth();
dht.begin();
set_led(4, 100); // BLUE
#ifdef DEBUG
Serial.println("Board is alive");
Serial.println();
#endif
}
void setup_bluetooth() {
#ifdef DEBUG_BLUE
Serial.println("Setting Bluetooth");
Serial.println();
#endif
Bluetooth.begin(9600);
while (Bluetooth.available()) {
delay(50);
Bluetooth.read();
}
}
//----------------------------------------------------------------------------//
// FUNCTIONS (LIGHT)
//----------------------------------------------------------------------------//
int percent_to_bright(int percent) {
return 256 - pow(2, percent / percent_to_bright_factor);
}
void set_led(int color, int bright) {
#ifdef DEBUG
Serial.println("Setting LED");
Serial.println();
#endif
if (color < 0 || color > 6 || bright < 0 || bright > 100) return;
int led_r_bright = 255, led_g_bright = 255, led_b_bright = 255;
int bright_aux = percent_to_bright(bright);
switch (color) {
case 0: led_g_bright = bright_aux; break;
case 1: led_r_bright = bright_aux; led_g_bright = bright_aux; break;
case 2: led_r_bright = bright_aux; break;
case 3: led_g_bright = bright_aux; led_b_bright = bright_aux; break;
case 4: led_b_bright = bright_aux; break;
case 5: led_r_bright = bright_aux; led_b_bright = bright_aux; break;
case 6: led_r_bright = bright_aux; led_g_bright = bright_aux; led_b_bright = bright_aux; break;
}
analogWrite(led_r_pin, led_r_bright);
analogWrite(led_g_pin, led_g_bright);
analogWrite(led_b_pin, led_b_bright);
}
//----------------------------------------------------------------------------//
// FUNCTIONS (CHECK)
//----------------------------------------------------------------------------//
void check_prox() {
if (!prox) {
if (digitalRead(prox_pin) == LOW) {
#ifdef DEBUG_PROX
Serial.println("Proximity detected");
Serial.println();
#endif
set_led(0, 100);
send_data(2);
prox = true;
if (!vibr) {
wait = true;
wait_time = millis() + wait_interval;
}
}
}
}
void check_vibr() {
if (!prox && !vibr) {
if (digitalRead(vibr_pin) == HIGH) {
#ifdef DEBUG_VIBR
Serial.println("Vibration detected");
Serial.println();
#endif
set_led(1, 100);
vibr = true;
wait = true;
wait_time = millis() + wait_interval;
}
}
}
void check_wait() {
if (wait && millis() > wait_time) {
if (!prox) {
set_led(2, 100);
send_data(1);
}
reset();
}
}
void check_dht() {
if (!prox && !vibr) {
if (millis() > dht_time) {
humi = dht.readHumidity();
temp = dht.readTemperature();
heat = dht.computeHeatIndex(temp, humi, false);
#ifdef DEBUG_DHT
Serial.print("Humidity: "); Serial.print(humi); Serial.println("%");
Serial.print("Temperature: "); Serial.print(temp); Serial.println("C");
Serial.print("Heat Index: "); Serial.print(heat); Serial.println("C");
Serial.println();
#endif
send_data(0);
dht_time = millis() + dht_interval;
}
}
}
//----------------------------------------------------------------------------//
// FUNCTIONS (MIS)
//----------------------------------------------------------------------------//
void send_data(int shot) {
Bluetooth.print(humi);
Bluetooth.print(temp);
Bluetooth.print(heat);
Bluetooth.print(shot);
#ifdef DEBUG_BLUE
Serial.println("Bluetooth sent");
Serial.println();
#endif
}
void reset() {
vibr = false; prox = false; wait = false;
delay(1000);
set_led(4, 100);
}
//----------------------------------------------------------------------------//
// MAIN
//----------------------------------------------------------------------------//
void loop() {
check_prox();
check_vibr();
check_wait();
check_dht();
}
Android код (версия 2.0.0)
MIT App Inventor — файл доступен для загрузки.
Схемы


Производственный процесс
- Баскетбол: от изобретения Нейсмита 1891 года к современным производственным технологиям
- Создание открытых систем умного земледелия: укрепление фермеров доступным, интероперабельным IoT
- ECM3532: первая интегрированная ультра‑низкоэнергозатратная ИИ‑сенсорная плата Eta Compute ускоряет Edge‑разработку
- Создайте домашний монитор температуры на Raspberry Pi с MCP9808, InfluxDB и Grafana
- Умное мусорное ведро: клеточное IoT-решение для автоматизированного управления отходами
- Умный бармен: создайте свою Raspberry Pi-станцию для коктейлей
- Датчик эмоций и ЭЭГ: низкобюджетное железо для детекции эмоций в реальном времени
- Революционный ультрачувствительный стойкий датчик деформации для умных текстилей
- Революционный ультратонкий сенсор расширяет функциональность умных контактных линз
- Революция в производстве датчиков: Sensata использует умную мехатронику