Машинка на Arduino, управляемая Android-устройством по Bluetooth, — полный цикл (часть 1) / Хабр

Машинка на Arduino, управляемая Android-устройством по Bluetooth, — полный цикл (часть 1) / Хабр Самолеты
Содержание
  1. Введение
  2. Основа конструкции
  3. ↑ что нам потребуется для сборки машинки?
  4. ↑ болото мыслей и техзадание
  5. ↑ будем строить машинку с ду!
  6. ↑ достойный образец игрушки
  7. ↑ как я выбирал шасси
  8. ↑ как я разбираю пакет
  9. ↑ немножко дёгтя на «лыжи»
  10. ↑ общая схема подключения
  11. ↑ передача пакета данных
  12. ↑ приём пакета данных
  13. ↑ приложение под android для машинки
  14. ↑ программа микроконтроллера
  15. ↑ резюме
  16. ↑ схема в протеусе
  17. ↑ файлы
  18. Блютуз игрушка купить дешево – низкие цены, бесплатная доставка в интернет-магазине joom
  19. Выбираем компоненты
  20. Где посмотреть
  21. Задача
  22. Немного об уровне, авторе и предостережения
  23. Определение угла поворота
  24. Подключение bluetooth
  25. Подключение угла и код
  26. Понадобится
  27. Радиокот :: радиоуправление моделью через bluetooth le
  28. Распараллеливание ходовых колес
  29. Система отправки команд
  30. Соединяем bluetooth с управляющей платой
  31. Соединяем управляющую плату с моторами
  32. Удалённое управление
  33. Установка arduino
  34. Заключение первой части

Введение

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

Основа конструкции

За основу была взята машина Lego Outdoor Challenger (в реальности выглядит менее пафосно). Все, что от нее осталось: корпус (все элементы украшения сняты) и три двигателя.

image

У машинки была своя плата, но одна из задач подразумевала универсальность: это сделал я, это смогут повторить другие. Мозги вынул, поставил Arduino Uno.

↑ что нам потребуется для сборки машинки?

Поворотные колеса отсутствуют, значит, управление поворотом будет как у гусеничного ТС. То есть, для прямого движения, вперед/назад, правая и левая сторона приводов вращаются с одинаковой скоростью. А для осуществления поворота, скорость вращения на одной из сторон должна быть меньше или больше.

2. Bluetooth–модуль «HC-06» .

Для дистанционного управления машинкой используем канал Bluetooth. Модуль «HC-06» — это мост Bluetooth, последовательный интерфейс, позволяющий передавать данные в обе стороны. На входе — TTL-сигналы последовательного интерфейса «RxD» и «TxD» для подключения к микроконтроллеру (целевой плате).

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

3. Драйвер двигателей TB6612FNG двухканальный.

Драйвер двухканальный, на левую и правую пару колес. Драйвер имеет логические входы для изменения полярности выхода (направления вращения) и вход ШИМ, можно будет сделать управление скоростью вращения.

4. Плата управления MOD-IO (Olimex)

Выбрана эта плата т.к. валялась в ящике стола и полностью подходит для нашей цели. Есть дискретные входы/выходы, выведены сигналы МК «RxD» и «TxD», куда будет подключен «HC-06“.

Забегая вперёд скажу, что продукт Олимекс MOD-IO — это жёсткий перебор. Прекрасно можно будет применить и обычную

, о чём в продолжении истории!

Итого: шасси плата управления Bluetooth-модуль программа управления под Android.

↑ болото мыслей и техзадание

Когда держишь в руках перспективную вещь, например, в плане возможностей по обвесу модели всевозможными датчиками, серво и пр., начинаешь тонуть в болоте мыслей и трясине перспектив. Но, скажем себе — СТОП! И составим себе мини-ТЗ на прототип с кратким описанием всех узлов.
У нас должна получиться RC-модель наземного траспортного средства с управлением по Bluetooth, с возможностью реверса и плавного регулирования скорости вращения колес.

↑ будем строить машинку с ду!

Практический и технический интерес вызывают игрушки с радиоуправлением. Однако, ребёнку в возрасте 4-6 лет не будут дарить игрушки со «взрослым» пропорциональным управлением. Скорее всего, игрушка будет сломана, а деньги выкинуты на ветер.

В итоге, обычно дарят что-то недорогое. Из всего этого – «недорогого» — машинки или шибко быстрые или тормозные; танки хилые; и прочие очевидные и скрытые недостатки. И уж конечно никакого пропорционального управления.

В один прекрасный день у одной из машинок перестало вращаться правое колесо. Разобрал, проверил моторчик – исправный.На плате управления три микросхемы – Китай голимый, вменяемой документации найти не смог. Один чип – приёмник радиосигнала с логическими выходами и два мостовых драйвера двигателей. Один из драйверов вышел из строя. Сваять сходу мостовой драйвер двигателя из дискретных компонентов у меня не получилось.

В местном магазине радиодеталей ничего подходящего не было. Вот я и подался в дальние страны за чудо-микросхемами. Собрал пожитки, набил карманы сухарями, налил чашечку кофе, запустил браузер и пошел… на Алиэкспресс.Нашел подходящий по параметрам драйвер двигателя, заказал сразу два.

Не откладывая в долгий ящик идею о своей машинке, я снова подался на Алиэкспресс для выбора основы — шасси будущей машинки. Шасси бывают разные, для наземного транспорта: гусеничные, колесные, с двумя, тремя, четырьмя колесами и т.п.

↑ достойный образец игрушки

У меня двое детей, дочь и сын. Обоим на дни рождения дарят игрушки. То, что дарят дочери, как правило, не вызывает моих негативных реакций. А сыну, как и полагается, дарят всевозможные машинки, танки и прочую технику. Из всей этой прорвы китайщины у меня не вызывает нареканий только игрушечная бензопила, которую я сам и подарил.

Почему так? Наверное потому что эта пила продавалась в магазине инструментов «STIHL». Как я полагаю, «STIHL» сделал игрушечный аналог своей продукции небольшим рекламным тиражом. В результате появилась на свет вполне вменяемая игрушка, очень похожая на своего старшего брата.

О чём это я? Ах, да, об архитектуре! Это я к тому, что при желании отличную игрушку сделать можно. И есть на что равняться.

↑ как я выбирал шасси

Во-первых, я выбрал наземный вид транспорта, значит и шасси у меня будет наземное. Гусеничные шасси, как правило, дороже и не слишком быстроходные. Двух-трёх-колесные мне кажутся слабо проходимыми, такое шасси сможет ездить только по ровной поверхности.

Я остановился на 

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

• 4 колеса

• 4 привода в сборе (электродвигатель редуктор)

• 4 диска с прорезями для датчиков скорости, по одному на каждое колесо

• крепеж

Да, это снова Китай. Да, дешёвый. Да, нешибко качественный. НО! Нам бы для начала попробовать. Ведь «взрослое» шасси и стоит по-взрослому, мы до него ещё не доросли.

↑ как я разбираю пакет

Теперь самое интересное, как я разбираю пакет. В пакете могут передаваться данные размерностью больше байта, знаковые данные, данные с плавающей точкой.

Объясню на примере пакета для управления шасси. Кроме стартового и стопового байтов, в своём пакете мне необходимо передавать одну команду, и два значения ШИМ, для левой и правой стороны приводов. Для команды мне достаточно одного байта, а для каждого значения ШИМ, я передаю
int16
 — 16 бит, знаковый тип. То есть, я не передаю флаг (байт/признак) направления. Для смены направления, я передаю положительное или отрицательное значение ШИМ.

Приёмный пакет у меня организован в виде структуры.

struct RxPacket
{
	uint8_t	StartByte;
	uint8_t      Command;
	int16_t      Left_PWM;
	int16_t      Right_PWM;
	uint8_t	StopByte;
} RxPacket;

Вызывая функцию RB_read_buffer ((uint8_t*)&RxPacket), в качестве аргумента передаем указатель на структуру приёмного пакета. То есть, при принятом пакете, будет все разложено по своим полочкам в структуре RxPacket. Дальше остается прочитать эти данные из структуры например так:

↑ немножко дёгтя на «лыжи»

Есть косяк у моего телефона. Ага, телефон «лыжа» — LG G2 mini. На нём соединение по Bluetooth устанавливается неадекватно. Соединение устанавливается нормально только, если Bluetooth был включен непосредственно перед запуском приложения.

Я сделал так: при запуске приложении проверяю включен ли Bluetooth, если выключен — делаю запрос на включении. А при «сворачивании», закрытии приложения — принудительно выключаю Bluetooth.

И еще один момент, при смене ориентации экрана, Bluetooth выключается и снова выдается запрос на включения, приходится выключать автоматическое переключение разворота экрана.

↑ общая схема подключения

Не схема в чистом виде, а именно схема подключения, так как все платы у нас уже готовые, и остаётся их соединить между собой.

↑ передача пакета данных

Хоть в моей программе передача пока не используется, тем не менее, возможность передачи реализована. Передавать данные проще. Точно также, как и для приемного пакета, создадим структуру:

struct TxPacket
{
	uint8_t StartByte;
	uint8_t Rc5System;
	uint8_t Rc5Command;
	uint8_t StopByte;
} TxPacket;

Где, есть стартовый и стоповый байт и информационная часть. Мы уже инициализировали приемник USART.Для инициирования передачи пакета — вызываем функцию

void send_packet()
{
	// Запись стартового байта в регистр UDR
	UDR = START_BYTE;
}

В данном примере, в этой функции, я записываю только стартовый байт в регистр UDR. Вроде бы немного, но в этой же функции можно реализовать подготовку пакета или еще что-то полезное. И это, на мой взгляд более логично. Логично в плане самодокументирования кода. То есть, если я в коде, просто запишу значение в регистр UDR это может быть воспринято, как передача всего одного байта, а вызывая самоговорящую функцию
send_packet ()
 — я говорю о том, что я отправляю пакет данных.

Далее, когда передатчик USART отправит весь байт из регистра UDR — будет вызван обработчик прерывания по передаче.

ISR(USART_TXC_vect)
{
	unsigned char *Pointer = (unsigned char *)&(TxPacket);

	static unsigned char TxIndex = 1;

	if ( TxIndex < sizeof(TxPacket) )
	{
		UDR = *(Pointer   TxIndex);
		TxIndex  ;
	}
	else TxIndex = 1;
}

В обработчике я объявляю переменную указателя и присваиваю её адрес структуры TxPacket. Далее объявляется статическая переменная — индекс передаваемого байта, которой при объявлении присвоено значение 1. Начинаем с одного потому, что первый байт из структуры мы уже отправили.

Условие

if ( TxIndex < sizeof(TxPacket) )

проверяет, что индекс меньше чем размер пакета. Если условие верно, то записываем байт в регистр UDR:

UDR = *(Pointer   TxIndex);

инкрементируем TxIndex. Когда USART передаст очередной байт, то мы снова попадём в обработчик, но будет передан уже следующий байт из структуры и так будут переданы все байты структуры. Когда TxIndex будет больше чем размер структуры — условие будет не верно и мы попадем в 

else TxIndex = 1;

Где будет проинициализирован TxIndex, но в регистр UDR уже ничего не записывается, соответственно обработчик больше не будет вызван до следующего инициирования передачи пакета. Таким образом, процесс передачи полностью автоматический, и даже, если мы изменим структуру пакета, то обработчик переписывать не придётся.

В рамках описания программы МК осталось рассказать про реализацию управления драйверами. Драйвер управляется тремя сигналами: A1 (B1), A2 (B2) и PWMA (PWMB). A1 и A2 предназначены для включения/выключения драйвера и для изменения полярности выхода. На вход PWMA подается ШИМ сигнал с МК — можно управлять скоростью вращения. Для ШИМ сигнала я задействовал два аппаратных ШИМа таймера 1.

#define _WGM13 0
#define _WGM12 1
#define _WGM11 0
#define _WGM10 1

// Timer 1 init
TCCR1A = ( 1 << COM1A1 ) | ( 0 << COM1A0 ) | ( 1 << COM1B1 ) |
 ( 0 << COM1B0 ) | ( _WGM11 << WGM11 ) | ( _WGM10 << WGM10 );
TCCR1B = ( 0 << CS12 ) | ( 0 << CS11 ) | ( 1 << CS10 ) |
			 ( _WGM13 << WGM13 ) | ( _WGM12 << WGM12 );

	TCNT1 =0x0000;
	OCR1A = 0;
	OCR1B = 0;

Во-первых, это пошло от программы под Андроид. Дело в том что в Java нет без знаковых типов и я уже наступал на эти грабли. И для передачи числа от 0 до 255 мне пришлось бы как то извернуться. Я решил пойти более простым путем — отсылаю знаковое 16-бит число. При этом, 16 бит знакового типа это от -32786 до 32768, нам хватит.

Во-вторых, так на мой взгляд более прозрачно — скорость вращения и направления описывается всего одной переменной.

И в-третьих, как не крути, для наших целей меньше чем в три байта не уложиться. Пожертвуем еще одним байтом, зато всё становится понятно, положительное значение ШИМ — прямое вращение, отрицательное значение — обратное вращение.

Для управления приводами я написал функцию drive (int leftPWM, int rightPWM);.

void drive(int leftPWM, int rightPWM)
{
	// Движение ВПЕРЁД левое колесо
	if ( leftPWM > 0 ){
		ClearBit( A2_PORT, A2_PIN );
		SetBit( A1_PORT, A1_PIN );
	}
	// Движение НАЗАД левое колесо
	if ( leftPWM < 0 ){
		ClearBit( A1_PORT, A1_PIN );
		SetBit( A2_PORT, A2_PIN );
	}
	// Движение ВПЕРЁД правое колесо
	if ( rightPWM > 0 ){
		ClearBit( B2_PORT, B2_PIN );
		SetBit( B1_PORT, B1_PIN );
	}
	// Движение НАЗАД правое колесо
	if ( rightPWM < 0 ){
		ClearBit( B1_PORT, B1_PIN );
		SetBit( B2_PORT, B2_PIN );
	}
	// Остановка
	if ( leftPWM == 0 ){
		ClearBit( A1_PORT, A1_PIN );
		ClearBit( A2_PORT, A2_PIN );
	}
	// Остановка
	if ( rightPWM == 0 ){
		ClearBit( B1_PORT, B1_PIN );
		ClearBit( B2_PORT, B2_PIN );
	}

	set_PWM( (uint8_t)(abs(leftPWM)), (uint8_t)(abs(rightPWM)) );
}

В соответствии со значением ШИМ, осуществляется управление сигналами A1 (B1), A2 (B2) и устанавливается значение ШИМ вызовом функции
set_PWM (leftPWM, rightPWM)

Смотрите про коптеры:  Как самостоятельно включить WiFi на ноутбуке: подробное руководство

Уфф, выдохся… Подытожим: приняли пакет, разобрали, передали значение ШИМ в функцию drive.

↑ приём пакета данных

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

ISR(USART_RXC_vect)
{
	uint8_t Data = UDR;
	RB_push_char( Data );	// Складываем принятый байт в кольцевой буфер
}

Да, я пока пренебрег всякими проверками фрейма.

Теперь нам остается время от времени проверять наш буфер. Для этого я инициировал таймер, в котором я устанавливаю флаг разрешающий проверку кольцевого буфера

ISR(TIMER0_OVF_vect)
{
	TCNT0 = 0x64;
	ReadRingBuffer = 1;
}

В основном цикле добавляем условие для проверки флага.

if ( ReadRingBuffer )
{
if ( RB_read_buffer((uint8_t*)&RxPacket) == 1 ) Чтение буфера
			{
                                          // Разбираем пакет, делаем что-нибудь
			}
			ReadRingBuffer = 0;
		}

Функция RB_read_buffer проверяет кольцевой буфер, если размер пакета, стартовый и стоповые байты находятся на своих местах — пакет считается валидным, функция возвращает “1». В качестве аргумента, функция принимает указатель, куда складывать принятый пакет.

↑ приложение под android для машинки

Нет, так подробно, как программу для МК я разбирать не буду. В разработке ПО под Android я ещё новичок и не готов рассказывать достаточно компетентно и глубоко.

Основная функция программы – передача данных модулю HC-06 по каналу Bluetooth. Программа имеет не замысловатый интерфейс.

Сверху выпадающий список спаренных Bluetooth устройств для выбора модуля. Сначала этого списка не было, но уже под конец работы над статьёй решил сделать по-человечески, ведь не все смогут разобраться в исходниках.

Дальше кнопка «Выкл» — включает/отключает связь с «HC-06». Ниже слева направо: передаваемое значение ШИМ левого канала, тип датчика, значение правого канала. Ниже два слайдера регулировки чувствительности скорости и поворота.

В программе реализовано два типа управления машинкой. Для переключение типа датчика нужно коснуться надписи названия датчика: «Наклон» или «Шаркать».

1. Управление машинкой с помощью наклона телефона. Нулевое положение телефона — горизонтальное. При наклоне телефона вперед значение ШИМ увеличивается пропорционально наклону в диапазоне от 0 до 255. При наклоне телефона назад значение ШИМ уменьшается пропорционально наклону в диапазоне от 0 до -255

Что бы повернуть влево или вправо — нужно наклонить телефон вперед или назад и, одновременно, влево или вправо соответственно. Да, как в настоящей машине, пока не поддашь газку поворот не осуществляется.

2. Управление касанием. Моё фирменное название для такого управления — «шаркать».

Можно воспринимать как тачпад. При касании в сером квадрате, значение ШИМ увеличивается/уменьшается в зависимости от места касания, чем дальше от центра вниз или вверх тем больше/меньше значение.

Никаких «красивостей» и наворотов нет. Вот вроде бы и всё.

↑ программа микроконтроллера

Программа МК умеет принимать команды по последовательному интерфейсу с Bluetooth-модуля.

И, в соответствии с командами, управлять левой и правой парой приводов. Реверс и управление скоростью работают при помощи ШИМ.

Код в достаточной мере прокомментирован. Хочу отдельно остановиться на моей реализации обмена данными.Приём данных у меня реализован через кольцевой буфер. Вещь не новая и реализаций много.

Функции кольцевого буфера у меня вынесены в отдельную библиотеку состоящую из: заголовочного файла ring_buffer.h и файла реализаций функций ring_buffer.сДля использования библиотеки её нужно подключить в main.c

#include "ring_buffer.h"

Далее, в заголовочном файле необходимо настроить библиотеку. Для настройки необходимо задать всего четыре директивы:

#define RX_PACKET_SIZE		7	// Размер RxD пакета
#define BUFFER_SIZE			16	
// Размер приёмного буфера. Должен быть в два раза больше RX_PACKET_SIZE

#define START_BYTE			's'     // Стартовый байт
#define STOP_BYTE			'e'     // Стоповый байт

Собственно настраивать больше нечего.

Использование кода В main.c настраиваем USART микроконтроллера.Вызываем функцию настройки USART

USART_Init(MYUBRR);
#define BAUD 9600
#define MYUBRR F_CPU/16/BAUD-1

void USART_Init( unsigned int ubrr )
{
	/* Set baud rate */
	UBRRH = (unsigned char)( ubrr >> 8 );
	UBRRL = (unsigned char)ubrr;
	/* Enable receiver and transmitter */
	UCSRB = (1 << TXCIE) | (1 << RXCIE)| (1 << TXEN) | (1 << RXEN);
	/* Set frame format: 8data, 2stop bit */
	UCSRC = (1 << URSEL) | (0 << USBS) | (3 << UCSZ0);
}

↑ резюме

Я считаю, что достиг цели! При не очень больших усилиях я создал RC-модель с вменяемым пропорциональным управлением. Машинкой можно с увлечением играть даже взрослому, делая развороты на месте, выписывая сложные пируэты, притормаживая и ускоряясь при необходимости.

И её легко починить при поломке.

Есть еще поле для деятельности, есть куда расти. Можно «наворачивать» шасси, можно модернизировать и улучшать ПО для телефона.И об этом будет продолжение!

↑ схема в протеусе

Расписывать, что и куда я подключил не буду. Скорее всего у вас будет другая плата управления. Исходники я прикладываю, так что прошивку сможете править. Ну, а если кто-то не в силах скомпилировать прошивку под свою плату, обращайтесь — помогу по мере свободного времени.

↑ файлы

Документация на драйвер двигателя, PDF
 300.88 Kb ⇣ 24

Схема платы MOD-IO, PDF 🎁 mod-io-schematic.pdf 46.37 Kb ⇣ 23

Исходные коды программы микроконтроллера🎁 carmcu.7z 98.25 Kb ⇣ 23

Исходные коды программы для телефона🎁 carcontrolandroidsrc.7z 4.96 Mb ⇣ 25

Установочный файл для телефона, apk🎁 car-control_apk.7z 896.35 Kb ⇣ 22

Спасибо за внимание!

Блютуз игрушка купить дешево – низкие цены, бесплатная доставка в интернет-магазине joom

Bluetooth-динамик 5.0 × 6 TWS, беспроводной, ноутбук, телефон, ПК, водонепроницаемый, наружная стерео музыка, совместим с TF, aux, USB, FM

§

Выбираем компоненты

В прошлом мы с приятелями

собственную прошивку для роботов Lego Mindstorms NXT, поддерживающую удалённое управление роботом по bluetooth с Android-коммуникатора. Для экспериментов тогда была приобретена отладочная плата Olimex SAM7 с ARM7 на борту, которую в этот раз можно использовать в качестве управляющего контроллера.

В качестве шасси используем корпус игрушечной машинки.

Нужен bluetooth-модуль. Результат хотелось получить быстро, поэтому модуль был выбран из единственного доступного в магазине на тот момент BTM-112, хотя он оказался сравнительно дорогим.

Кроме того, оказалось, что управление с платы не может идти напрямую на моторы, нужен усиливающий контроллер (об этом чуть позже).

В сумме наши компоненты:

Теперь все необходимое у нас есть.

Где посмотреть

Весь код есть в открытом доступе.

Прошивка для платы Olimex —


Код Android приложения

, собранная версия на

Задача

Задача легчайшая – заставить ездить машинку, управляемую Arduino, а пульт заменить андроидом. Но в большинстве моментов пришлось изобретать колесо, потому что в интернетах подходящего решения найдено не было.

Немного об уровне, авторе и предостережения


Я, автор, пацан 16-17 лет с подмосковной деревни, специализируюсь на написании android-приложений (а там сложнее что-то сжечь), поэтому ответственность за оптимальный подход к решению задач с себя снимаю.

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

Определение угла поворота

За поворот машинки отвечает на удивление не сервопривод, а обычный двигатель. Встает проблема: хорошо бы было его не сжечь, ведь угол поворота ограничен, а крутиться двигатель может сколько угодно.

Вариант с методом тыка отпадает, так как при разном уровне батареи количество тока, подаваемое на двигатель, будет изменяться, что приведет к постоянно меняющемуся углу. Крутить до упора тоже нельзя, рано или поздно рассыплются шестеренки.

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

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

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

Подключение bluetooth

Я использовал модель HC-05, что сыграло роковую шутку. Подключаются все блютузы одинаково: один провод на 3.3В (иногда начинал работать только от 5В), второй на минус, еще два на порт 0 и 1 (чтение и отправка соответственно). Провод, подписанный RXD на bluetooth, втыкается в TXD ардуино, а TXD в RXD (если перепутаете, то данных не увидите).

Есть оговорка: порты 0 и 1 по умолчанию используются Serial, через который заливает скетч. То есть, пока воткнут блютуз, скетч не зальется. Есть два выхода: вынимать блютуз на время заливки или переназначить входы и выходы блютуза. Второй вариант осуществляется двумя строчками

#include <SoftwareSerial.h> \подключение библиотеки
SoftwareSerial BTSerial(8, 9); \установка 8 и 9 пина заместо 0 и 1

Подводный камень, съевший у меня трое суток работы – скорость общения. По привычке установил 9600 и пошел пробовать. То данные не приходили, то была каша символов. И в конце концов ответ – модель HC-05 общается на 38400! Очень сильно обратите внимание на то, что в Setup() я выполню BTSerial.begin(39400), хотя Serial.begin(9600).

Подключение угла и код


Для каждого уровня был выбран свой цвет: нижний – зеленый, первый снизу – красный, второй – черный, третий – белый. На начальном этапе использовались breadboard и светодиоды для визуальной отладки.

Схема подключения показана на рисунке. Плюс тянем к зеленому, остальные протягиваем к минусу. Через резистор, установленный для устранения помех и отсутствия КЗ, подключаем провода к выходам A0-A2. Выбраны они просто из экономии остальных портов.

Код дан с комментариями. Подключаем пины и опрашиваем их через digitarRead(). Если напряжение есть, вернется значение true. Далее смотрим, если результат означает, что колеса в крайних положениях, запрещаем дальнейший поворот в эту сторону.

Небольшая хитрость: поскольку выходы на 5В и 3.3В понадобятся в будущем, можно поставить плюс на один из digital-пинов. Перед каждой проверкой угла выдавать ток через digitalWrite(whitePin), потом проверять угол и убирать ток.

int speedTurn = 180; //скорость поворота, от 0 до 255

//пины для определения поворота
int pinRed = A0;
int pinWhite = A1;
int pinBlack = A2;

int pinAngleStop = 12; //выводит ток на светодиод, если достигнут максимальный угол, нужен 
//только для отладки 

void setup() {
 //пины поворота на считывание
  pinMode(pinRed, INPUT);
  pinMode(pinBlack, INPUT);
  pinMode(pinWhite, INPUT);
//светодиод 
  pinMode(pinAngleStop, OUTPUT);
//пины драйвера двигателя, направление и скорость
  pinMode(angleDirection, OUTPUT);
  pinMode(angleSpeed, OUTPUT);

  Serial.begin(9600);
}
//функция вызывается из loop(), когда приходит команда с андроида
void turn(int angle) {
  digitalWrite(pinAngleStop, HIGH); //выдаем ток на провод, подключенный к плюсу
  delay(5); //немного ждем, чтобы ток "успел" дойти
  
  if(angle > 149) {
        if( digitalRead(pinWhite) == HIGH && digitalRead(pinBlack) == LOW && digitalRead(pinBlack) == LOW) { 
          //если достигнуто крайне правое положение, выйти из функции не подавая ток, чтобы не 
          //сжечь мотор
          return;
        }
        //если угол не максимальный, поворачиваем
        digitalWrite(angleDirection, HIGH);
        analogWrite(angleSpeed, speedTurn);
  } else if (angle < 31) { 
        if(digitalRead(pinRed) == HIGH && digitalRead(pinBlack) == HIGH && digitalRead(pinWhite) == HIGH) {
          //если достигнуто крайне левого положение, выйти из функции не подавая ток, чтобы не 
          //сжечь мотор
          return;
        }
        //если угол не максимальный, поворачиваем
        digitalWrite(angleDirection, LOW);
        analogWrite(angleSpeed, speedTurn);
  }
  digitalWrite(pinAngleStop, LOW); //убираем ток с определителя угла
  delay(5);
}

Понадобится

  1. Arduino
  2. Motor Shield (в моем случае две)
  3. Bluetooth
  4. Android
  5. Провода обычные
Смотрите про коптеры:  РадиоКот :: 10 командное радиоуправление на MRF49XA

Радиокот :: радиоуправление моделью через bluetooth le

РадиоКот >Схемы >Цифровые устройства >Игрушки >

Радиоуправление моделью через Bluetooth LE

Система дистанционного управления, представленная в статье, предназначена для работы на небольших расстояниях – где-то до 30м в открытом пространстве. Основное её достоинство – малое токопотребление, что во многих случаях может быть решающим фактором. В данном случае управление производится моделью машинки, в которой имеется один мотор для движения вперёд-назад и один мотор для поворота передних колёс. Модель для опытов была приобретена в местном молле уже собранной и изначально оснащённой аналоговой системой радиоуправления на частоте 27 мгц. Оба мотора модели нормированы на рабочее напряжение 3В и в оригинальном исполнении питались от одного литиевого аккумулятора напряжением 3.6В. Само управление производилось джойстиками в передатчике без регулировки скорости движения. Нашей целью было переделать модель под управление её траекторией и скоростью путём наклона платы передатчика, положение которой в пространстве отслеживалось-бы с помощью акселерометра. В сети известны примеры проектов использования смартфона для подобного управления, такой проект имеется и у нас. Однако на этот раз нам хотелось сделать что-то ощутимо менее энерго-потребяющее на передающей стороне. Это был первый обучающий BLE проект моего студента Нэйсена.

Ограничение на радиус действия управления связано прежде всего с использованием протокола BLE (Bluetooth Low Energy), который разработан преимущественно для небольших расстояний. Нами были использованы радиомодули семейства EZ-BLE фирмы Cypress с максимальной выходной мощностью 3dBm (2mW). Эти модули очень просты и удобны в программировании при использовании системы PSoC Creator. Про подобные модули у меня есть пара статей, см. [1] и ссылки в ней. Применённые в нашем проекте модули весьма просто паяются любым паяльником на плату благодаря большому шагу выводов (1.5мм) и наличию контактных площадок на рёбрах их плат. Данная модель модулей выпускается в двух модификациях – с установленным на плате экраном и без него. Отличие этих модификаций главным образом в наличиии сертификации и в цене. Мы использовали более дешёвые и несертифицированные модули без экранов, которые вполне пригодны для домашних поделок. Микроконтроллер модулей оснащён всей стандартной периферией, типичной для микроконтроллеров сегодня, так что вся система может быть собрана на основе одного лишь модуля. Микроконтроллер выполнен по архитектуре ARM Cortex-M0 с максимальной частотой тактирования 48 мгц от внутреннего генератора. На плате модуля установлен кварц на частоту 24 мгц для тактирования радио-тракта, а также часовой кварц для прецезионного формирования временных интервалов при работе стека протокола. В целях снижения токопотребления мы тактируем нижние уровни стека BLE на частоте 3 мгц от кварцевого генератора, а всё остальное приложение на частоте 6 мгц.

Приёмник

Схема приёмника чрезвычайно проста. Помимо радио-модуля IC1 она включает разъём для его программирования, драйвер моторов на микросхеме IC2, светодиод для индикации состояния программы приложения (см. ниже) и стабилизатор напряжения IC3. Для питания схемы решено было использовать 4 пальчиковые батареи типоразмера АА, так что напряжение на них не опускается ниже 4В при разрядке в процессе работы системы.

Машинка на Arduino, управляемая Android-устройством по Bluetooth, — полный цикл (часть 1) / Хабр

Применённый драйвер IC2 идеально подходит для управления небольшими моторами с потреблением до 1А. Он выполнен по КМОП технологии с малым падением напряжения на транзисторах выходного каскада, что предотвращает нагрев корпуса микросхемы даже при работе с максимальмым током. Нами использован вариант драйвера в корпусе с металлизированной подложкой, которая после припаивания к плате позволяет использовать фольгу на последней в качестве теплоотвода. Однако, эта мера предосторожности для трёх-вольтовых моторов модели оказалась совершенно излишней и подложку можно было-бы вообще не припаивать к плате, или даже использовать вариант корпуса без подложки. Четыре управляющих сигнала драйвера разбиты на 2 пары. В каждой паре один из сигналов используется для подачи из модуля ШИМ на соответствующий мотор, а второй для управления направлением его вращения. ШИМ для мотора поворота колёс предполагалось использовать для снижения эффективного напряжения на нём.

Внутри PSoC задействованы 3 модуля. Модуль PWM формирует ШИМ для управления моторами. В результате экспериментов была выбрана частота ШИМ в 2 мгц / 127 = 15.7 кгц при 7-битном разрешении. Разрешение выбрано из соображений посылки лишь одного байта для управления скоростью, положительное значение которого соответствует движению модели вперёд, а отрицательное – назад. При этом используются лишь 3 фиксированные скорости: малая, средняя, и большая. Как оказалось, в модели использована дешёвая коробка передач с пластиковыми шестерёнками без нормальных подшипников и с огромными потерями, а также весьма неэффективные моторы. Из-за увеличенного четырьмя батареями веса модели при очень малой скорости вращения колёс в воздухе она не могла тронуться с места после установки на пол. Поэтому и было принято решение насчёт трёх дискретных скоростей, так что даже на минимальной скорости моторы развивают достаточный момент для начального старта модели. Вот как выглядит соединение модулей PSoC в графическом конфигураторе ресурсов. Выводы A1 и B2 для изменения направления вращения моторов управляются непосредственно из программы.

Машинка на Arduino, управляемая Android-устройством по Bluetooth, — полный цикл (часть 1) / Хабр

Нижний на схеме модуль ШИМ предназначен для управления частотой мигания светодиода. Светодиод использовался преимущественно в режиме отладки для индикации различных фаз установки соединения с передатчиком путём изменения частоты его мигания. При этом постоянное его свечение соответствует успешному завершению процесса соединения. Однако, в конечном варианте соединение устанавливается в доли секунды после включения передатчика, так что светодиод практически сразу переходит к постоянному свечению.

Сердцем приёмника является модуль BLE в PSoC. Он сконфигурирован на GAP роль периферийного устройства с реализацией проприетарного GATT профиля (custom profile), и выступает в роли сервера. В профиле помимо стандартных сервисов Generic Access и Generic Attribute, регламентируемых BLE SIG, имеется и проприетарный сервис Car Control, включающий 3 характеристики Turn, Speed, и Control для управления поворотами и скоростью модели. Следует отметить, что наш проект отличается от опубликованного в [2], где GAP роли приёмника и передатчика противоположны нашим. Это сделано намеренно, чтобы Nathan глубже проникся миром BLE. Соответственно, заново разработана логика работы программ приёмника и передатчика и логика их взаимодействия со стеком BLE.

Приём команд передатчика, именно записи новых значений в характеристики профиля, реализован на основе обслуживания прерываний стека BLE в обрабочике GeneralEventHandler. При этом обрабатываются 4 события. Событие CYBLE_EVT_STACK_ON генерируется по завершении инициализации стека и используется для начала посылки advetisement с идентификатором приёмника, используемым для соединения его с передатчиком. В качестве идентификатора используется уникальный 48-битный код, зашитый в модуль на фабрике. Его можно изменить при необходимости, но мы это не делали. Следующие 2 события CYBLE_EVT_GAP_DEVICE_CONNECTED и CYBLE_EVT_GAP_DEVICE_DISCONNECTED генерируются стеком после установки соединения с передатчиком и при разъединении с ним. В первом случае мы просто конфигурируем светодиод на постоянное свечение, а во втором останавливаем модель и возобновляем посылку advetisement, предоставляя тем самым передатчику возможность возобновления соединения. Светодиод при отсутствии соединения с передатчиком начинает мигать с периодом 1 сек.

Наконец, главное для логики работы приложения событие CYBLE_EVT_GATTS_WRITE_REQ генерируется стеком при приёме команды записи значения характеристики в базу данных сервера. При этом мы проверяем какую именно характеристику следует изменить, посылаем подтверждение приёма команды в передатчик, и вызываем соответствующую функцию Set_Turn() и/или Set_Speed() для выполнения команды путём манипуляции сигналов на входах IC2.

В данной реализации протокола связи соединение с передатчиком происходит каждые 100 миллисекунд. В промежутках между соединениями система погружается в сон с потреблением 4.7 мА при неработающих моторах. Из этого потребления около 1.3 мА приходится на светодиод. Из-за необходимости постоянной генерации ШИМ для моторов не представляется возможным погрузить PSoC в глубокий сон во время движения модели, т.к. при этом останавливаются все генераторы и прекращается работа ШИМ. Потребление можно снизить примерно в 3 раза если отключать генерацию ШИМ при остановке модели и погружать систему в глубой сон.

Приёмник собран на односторонней печатной плате размером 37×26 мм, приклеенной на отрезок пластиковой упаковки, который в свою очередь приклеен к шасси модели. Батарейный отсек размещён в центральной части модели. Несмотря на фиксацию батарей крышкой корпуса отсека, при столкновении модели с препятствием или даже движении по шероховатой поверхности иногда возникает потеря контакта в пружинном держателе батарей, что приводит к кратковременной потере соединения с передатчиком и миганию светодиода на плате. Недаром в оригинальном исполнении в модели использовался аккумулятор с проволочными выводами, припаянными к плате.

Передатчик

Помимо радио-модуля, такого-же как в приёмнике, передатчик содержит 3-осный аналоговый акселерометр IC2 и светодиод для индикации наличия соединения с моделью. Конденсаторы C2-C4 наряду с резисторами внутри IC2 образуют ФНЧ для фильтрации шумов на выходе аналоговой части акселерометра. Измерение его сигналов на выходах X, Y, Z производится 12-битным АЦП в составе PSoC. Измерение производится каждые 100 миллисекунд в соответствии с периодом соединения с моделью. Временной интервал измерения параметров обеспечивается Watchdog-ом в PSoC, тактируемым от часового кристалла на плате модуля.

Машинка на Arduino, управляемая Android-устройством по Bluetooth, — полный цикл (часть 1) / Хабр АЦП сконфигурирован на недефференциальное включение. Начало процесса измерения сигналов акселерометра производится программно. АЦП тактируется от своего генератора на частоте 6 мгц, так что измерение данных со всех трёх осей акселерометра производится за 36 микросекунд, включая усреднение четырёх измерений по каждой оси. Поворот модели осуществляется наклоном платы передатчика вправо и влево на угол превосходящий примерно 30°. Этим предотвращается подача ложных команд при небольших флюктуациях положения платы в руках оператора. Скорость и направление движения модели регулируются наклоном платы передатчика в направлении от себя или к себе. При этом скорость модели пропорциональна степени наклона. Поскольку используются 3 дискретных значения скорости, соответствующих пороговым значениям наклона платы, изменение скорости не происходит при наклоне платы между этими пороговыми значениями.

Смотрите про коптеры:  Управление по Bluetooth любым Android устройством |

Светодиод индицирует фазы соединения с приёмником. Частота его вспышек увеличивается от начала сканирования BLE устройств, до обнаружения BLE устройства с идентификатором приёмника, соединения с приёмником, и процессом окончания чтения предоставляемым приёмником сервисов и их характеристик. По завершении всего процесса светодиод светит постоянно без мигания. При этом его генератор ШИМ отключается и светодиод управляется непосредсвенно с выхода соответствующего пина порта PSoC.

В отличии от приёмника удалось помещать систему в глубокий сон в промежутках между измерением ускорения и обработки событий стека. При этом токопотребление передатчика оказалось около 1.6 мА, из которых около 1.3 мА приходится на светодиод. Для дальнейшего снижения токопотребления следовало-бы также погружать акселерометр в сон подачей соответствующего сигнала на его вход ENA.

Машинка на Arduino, управляемая Android-устройством по Bluetooth, — полный цикл (часть 1) / Хабр

Передатчик смонтирован на односторонней печатной плате размером 74×42 мм. Фольга вблизи антенны модуля удалена в соответствии с рекомендациями производителя. Батарейный отсек размещён на тыльной стороне платы и привинчен к ней двумя винтами M2.

Машинка на Arduino, управляемая Android-устройством по Bluetooth, — полный цикл (часть 1) / Хабр

Соединение передатчика (ведущее устройство – master) с приёмником (ведомое устройство – slave) производится на основе обработки событий стека BLE в файле BLEclient.c. Как и в приёмнике, событие CYBLE_EVT_STACK_ON генерируется по окончании инициализации стека. Как только это произойдёт, вызывается API функция CyBle_GapcStartScan для начала сканирования доступных передатчику BLE устройств. При обнаружении такового генерируется событие CYBLE_EVT_GAPC_SCAN_PROGRESS_RESULT и стек передаёт обработчику идентификатор (MAC address) устройства. Одновременно генерируется событие CYBLE_EVT_GAPC_SCAN_START_STOP. В обработчике первого события полученный идентификатор сравнивается с таковым для модели приёмника. При его несовпадении сканирование устройств продолжается путём подачи соответствующей команды в обработчике второго события. В противном случае там-же подаётся команда CyBle_GapcConnectDevice произвести соединение с приёмником. При успешном соединении сначала генерируется событие CYBLE_EVT_GATT_CONNECT_IND, при котором обработчик получает дескриптор устройства (device handle) приёмника, который сохраняется в переменной connHandle и впоследствии используется для подачи команд приёмнику. Далее стеком генерируется событие CYBLE_EVT_GAP_DEVICE_CONNECTED, символизирующее об окончании процесса соединения. В этот момент обычно начинают процесс сбора информации о сервисах, предoставляемых соединённым устройством, их характеристиках и дескрипторах. Однако, в целях упрощения кода мы этого не делаем, т.к. наверняка знаем какие сервисы следует ожидать от нашего приёмника. Значения дескрипторов характеристик (characteristic handle) Turn, Speed и Control профиля приёмника (в нашем случае 0х0012, 0х0016, 0x001A) можно найти в файле BLE_custom.h проекта приёмника. Этот файл автоматически генерируется конфигуратором при создании проекта.

На фоне обработки событий стека BLE логика работы передатчика чрезвычайно проста. По получении прерывания от сторожевого таймера производится пробуждение АЦП и измерение сигналов на выходе акселерометра. Значения вектора ускорения сравнивается с пороговыми для поворотов и скоростей в функции Check_Control(), и производится заполнение полей структур управления характеристиками. Далее подаётся команда CyBle_GattcWriteCharacteristicValue передачи соответствующей характеристики приёмнику. Параметрами этой функции являются дескриптор соединения с приёмником (полученный при соединении) и структура дескриптора соответствующей характеристики. Последние инициализируются лишь один раз при подаче питания в фунции Init_RAM(), т.к. в этих структурах требуется указать лишь адрес значения надлежащей характеристики. При изменении значения кода скорости и поворота адрес этих значений при статическом расположении их в RAM не меняется.

При повторении конструкции следует в программе передатчика (файл BLEclient.c) изменить в массиве serverAddress MAC адрес моего модуля приёмника на Ваш. Для этого следует включить приёмник модели, который в режиме advertisement будет передавать свой MAC адрес и который можно будет прочитать любым сканером BLE устройств. Например, с помощью проложения CySmart для смартфонов, свободно доступного в сети. Файлы плат для Eagle и исходники проекта прилагаются.

Радиус действия управления, даже при использовании протокола BLE, можно существенно повысить до нескольких сотен метров путём использования модулей с большей выходной мощностью. Таковые имеются у Cypress, я прежде всего имею в виду новые модули CYBLE-224110-00 с огромной для BLE мощностью 9.5 dBm (около 9 mW). Конечно, при такой мощности это будет уже не совсем Low Energy. Фирма любезно предоставила нам эти модули для экспериментов, однако про них подробнее как-нибудь в другой раз. В заключении видео управления моделью.

Литература

1. Реалиазция стандартного GATT-BLE профиля на RSoC фирмы Cypress
2. Project #047: PSoC 4 BLE – Accelerometer Controlled Car

Файлы:
Архив ZIP

Все вопросы в
Форум.



Эти статьи вам тоже могут пригодиться:

Распараллеливание ходовых колес

Изначально два ходовых двигателя соединены вместе. Их рассоединил по двум причинам: поворот эффективней, если колеса крутятся в разные стороны, и два мощных двигателя одна плата не вытянет.

Проблема: у motor shield два выхода, каждый из которых выдает до 2 ампер. Каждый двигатель ест по 0,7А. Вроде меньше, но не при максимальных нагрузках. Допустим, машинка застряла в песке или уперлась, ток возрастает выше ампера. Не критично, но потенциально опасно.

А вот критичным оказалось то, что плата греется. Через минуты полторы после заезда, motor shield нагревалась и начинала работать безобразно: токи подаются не те, колеса не крутятся и прочее.

Решение обоих проблем: один двигатель подключил к одной motor shield, второй – к другой. Как ни странно, помогло. Температура упала, перегрев отсутствует. Можно было поставить радиатор, но крепить тяжело.

Система отправки команд

Статья становится слишком длинной, поэтому рассмотрение кода Arduino и Android вынесу в отдельную вторую часть, а сейчас опишу принцип.

На андроид устройстве есть джойстик (круг, о реализации которого также во второй части). Андроид считывает показания с него и конвертирует их в подходящие для ардуино числа: скорость из пикселей превращает в значение от -255 до 255 (отрицательные – задний ход), а также определяет угол.

После установки сокета данные отправляются в следующем формате: @скорость#*угол#. @ — говорит о том, что следующие цифры содержат скорость, # — извещает об окончании значения скорости, * — начало значения угла, # — закончить запись угла. Цикл бесконечен, команды отправляются каждые 100 миллисекунд (цифра подобрана оптимальная). Если ничего не нажато на андроиде, то ничего и не отправляется.

Алгоритм приема данных подробно описан в коде скетча. Он не раз переписывался и, как по мне, работает идеально.

Соединяем bluetooth с управляющей платой

Для начала настроим доставку команд управляющему блоку. Схема соединения bluetooth-модуля и контроллера простая: по сути дела это UART с некоторыми управляющими ножками, то есть надо лишь присоединить модуль к интегрированному UART-контроллеру.

Программная составляющая не сильно сложнее. При соединении модуль пишет строку вида “CONNECT RE:MO:TE:BT:MA:CC xxx rn”, а при отсоединении — “DISCONNECT xxx rn”. Задача драйвера состоит в наблюдении за потоком символов и, в случае если сейчас активно соединение, передаче потока управляющему приложению.

Соединяем управляющую плату с моторами

Изначально я думал, что смогу использовать управление со старой платы (зашитой в саму машинку), но оказалось, что использовать её в чистом виде нельзя, поскольку управление моторами было крепко сцеплено с радиочастью. Управлять моторами напрямую с ног головного контроллера тоже не получится из-за малой выходной мощности микросхемы.

К сожалению, опыта в этой области у меня нет никакого (все-таки я программист, а не схемотехник), поэтому пришлось прибегнуть к помощи знакомых. По совету я взял микросхему усилителя L298N (избыточную для моих нужд, зато работающую из коробки) и собрал

Напомню, препарируемая машинка детская, и управление у нее немного хромает; когда я начал разбираться с моторами, я понял одну из причин. Для механизма поворота используется не сервопривод, а обычный электродвигатель, который может находиться в одном из трех состояний (выкл., вперед и назад). Таким образом, о плавном повороте мечтать не приходится.

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

Так как Android-приложение передает плавное управление, состояние моторов изменяется по преодолению заданного порога.

Код получился очень простой, я не буду приводить его здесь, но в конце поста есть ссылки на исходники всего проекта.

Удалённое управление


Как было обещано, удаленное управление осуществляется с помощью Android-коммуникатора.

Наше прошлое приложение для Android (которое управляло Lego Mindstorms NXT, тоже посредством bluetooth) с помощью акселерометра отслеживало наклон коммуникатора и посылало соответствующие команды управления на NXT. Так что наиболее простым решением стало добавление протокола управления машинкой в уже существующее приложение.

Итак, на данном этапе мы можем “порулить” нашей платкой, правда моторы пока не подключены, так что индикация движений производится встроенными светодиодами (видео, к сожалению, нет, в этот момент снял только фотку), но всё равно прогресс есть, он виден, и это греет душу.

Установка arduino

Создатели почему-то не предусмотрели места для Arduino, потому крепил на шурупы, просверлив пластик. Под плату подложил фанеру, чтобы ничего не закоротило. Под шурупы лучше подсунуть что-то пластиковое (кусочек бутылки), ибо плата от железный болтов не защищена.

Поверх платы сразу поставил две motor shiled, так надо. Чтобы управлять второй, придется прокинуть один провод с любого digital порта на H1 (направление) и второй с пина с поддержкой шима (помечены знаком «~», обычно 10, 11) на E1 (скорость).

Заключение первой части

В этой статье я попытался раскрыть все, что касается физической части машинки. Вероятнее всего, что-то упустил, так что обязательно спрашивайте.

Но самое интересное, как по мне, осталось на второе – программа Arduino и приложение на Android, там творится настоящая магия, по крайней мере, для молодого меня.

Оцените статью
Радиокоптер.ру
Добавить комментарий

Adblock
detector