» » Машинка с пропорциональным управлением по Bluetooth из-под Android

 
 
 
3

Машинка с пропорциональным управлением по Bluetooth из-под Android

Разместил Discover 23 мая 2016. Просмотров: 4 295

Не будем покупать плохие игрушки у Китайцев, а купим у них дешевый конструктор-шасси, несколько модулей и приложим руки!
Вот что у меня получилось в итоге: проходимое шасси, управляемое — ТА-ДА!!! — с моего смартфона на Андроиде.

«Я и прямо, я и боком,
     С поворотом, и с прискоком,
         И с разбега, и на месте,
               И двумя ногами вместе…»

Сегодня мы соберём забавную машинку с дистанционным управлением по Bluetooth. Исходники программы управления под Android в комплекте.

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

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

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

Вот такая игрушечная пила


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

Будем строить машинку с ДУ!

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

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

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

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

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

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

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

Болото мыслей и Техзадание

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

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

Все компоненты списком:
1. Шасси 4 WD с четырьмя колесами.
2. Bluetooth–модуль «HC-06» .
3. Драйвер двигателей TB6612FNG двухканальный.
4. Плата управления MOD-IO (Olimex) или другая МК-плата, например обычная платка Ардуино Нано с правкой исходников.

1. Шасси 4 WD с четырьмя колесами.

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


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.

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

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


Схема в Протеусе


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

Программа микроконтроллера

Программа МК умеет принимать команды по последовательному интерфейсу с 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);
}


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

В функции обработчика прерывания приема байта по 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». В качестве аргумента, функция принимает указатель, куда складывать принятый пакет.
Для пущей надежности, пакет можно снабдить контрольной суммой, в одном из своих коммерческих проектов я так и сделал. То есть к проверке размера, старт/стопового байтов прибавляется еще проверка контрольной суммы. Но пока обойдемся без неё.

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

Теперь самое интересное, как я разбираю пакет. В пакете могут передаваться данные размерностью больше байта, знаковые данные, данные с плавающей точкой.
Объясню на примере пакета для управления шасси. Кроме стартового и стопового байтов, в своём пакете мне необходимо передавать одну команду, и два значения ШИМ, для левой и правой стороны приводов. Для команды мне достаточно одного байта, а для каждого значения ШИМ, я передаю 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. Дальше остается прочитать эти данные из структуры например так:
       lCmd = RxPacket.Command;
        lLPWM = RxPacket.Left_PWM;
        lRPWM = RxPacket.Right_PWM;


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

Хоть в моей программе передача пока не используется, тем не менее, возможность передачи реализована. Передавать данные проще. Точно также, как и для приемного пакета, создадим структуру:
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;


Таймер 16-битный, но ШИМ инициализирован на 8 бит. И как вы наверное уже заметили, в приёмном пакете у меня есть два значения для задания ШИМ, для левого и правого привода соответственно. Переменные знаковые 16-битные.
Объясню почему я так сделал.

Во-первых, это пошло от программы под Андроид. Дело в том что в 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).

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

Приложение под Android для машинки

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

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


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


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

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

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

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

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

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

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

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

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

Резюме

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

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

Файлы

Документация на драйвер двигателя, PDF
tb6612fng.pdf | Файл 300,88 Kb загружен 8 раз.

Схема платы MOD-IO, PDF
mod-io-schematic.pdf | Файл 46,37 Kb загружен 6 раз.

Исходные коды программы микроконтроллера
carmcu.7z | Файл 98,25 Kb загружен 5 раз.

Исходные коды программы для телефона
carcontrolandroidsrc.7z | Файл 4,96 Mb загружен 7 раз.

Установочный файл для телефона, apk
car-control_apk.7z | Файл 896,35 Kb загружен 6 раз.

Спасибо за внимание!
Иван Гаврилов (Discover)
Вологодская обл. Череповецкий р-н, п.Тоншалово
Профиль Discover
Дата рождения: 13.09.1977.
Работаю в компании "Нординкрафт" , электрик по совместительству в своем ТСЖ.
С паяльником с 12 лет.
Интересы: паяльник, работа, фото, лес.
C 2007г занимаюсь микроконтроллерами AVR.

Напаяно уже много чего, всего и не припомню.
 

Понравилось? Палец вверх!

  • всего лайков: 54

Поделись с друзьями!

Связанные материалы:


Схема на Датагоре. Новая статья Kaspersky FREE. Бесплатная годовая лицензия для России, Белоруссии и Украины... Привет, друзья! Очередной подарок от kaspersky.ru — антивирус «Kaspersky FREE». Спасибо Евгению...
Схема на Датагоре. Новая статья Блок управления вытяжным вентилятором. Наладка, результаты... Всем здоровья! Утюг починен. Хочу коротко отчитаться в проделанной работе и результатах наладки...
Схема на Датагоре. Новая статья Восстановлена схема генератора ГЗШ-63. Ламповый «гена» из 1965!... Генератор звуковой школьный, хорошо известный по радиокружкам, с мощным выходом и классическим...
Схема на Датагоре. Новая статья Microchip проглотила Atmel. Официально... Привет, дрУги! Не удивляйтесь, зайдя сегодня на официальную страницу atmel.com и увидя красенький...
Схема на Датагоре. Новая статья Пусть всегда будет солнце!... Сегодня День Победы. Мы так долго живём без войны, что начали забывать, что это такое, а без этой...
Схема на Датагоре. Новая статья Доработанный вариант малошумящего двухполярного источника питания... Здравствуйте, коллеги! Размещаю дополнение к статье «Малошумящий двухполярный блок питания...
Схема на Датагоре. Новая статья Гитарный преамп Tomato - исправленная печатка. Tomato updated pcb. Обновлено.... Маленькая заметочка в стиле «возвращаясь к напечатанному» для устранения замеченных ошибок....
Схема на Датагоре. Новая статья Кит отладочной платы ЦАПа «Silver Wolf R192» (WM8805, WM8740, TOSLINK, S/PDIF с гальванической развязкой)... Для разработки следующего Датагорского ЦАПа и написания управляющего софта была создана и заказана...
Схема на Датагоре. Новая статья Спасибо деду за Победу 2015!... Датагорцы, сограждане, друзья! У кого деды-ветераны живы — успевайте, поздравляйте, расспрашивайте...
Схема на Датагоре. Новая статья 17/01 киберсубботник на портале... Привет, дрУги! В ближайщую субботу на нашем портале и в магазине будет проводиться...
Схема на Датагоре. Новая статья Coil32 v9.0 - программа для расчета катушек индуктивности... Всем, кто занимался изготовлением (и ремонтом) приемников, передатчиков, акустических систем, ИБП,...
Схема на Датагоре. Новая статья Сэмплы гитарного комбоусилителя U-96... Как и обещал, записал пару сэмплов на скорую руку с моего комбика из статьи "Новый гитарный...
<
  • Главный редактор
23 мая 2016 20:19

Игорь Петрович Котов / Datagor

  • С нами с 25.02.2011
  • Ушёл в реал Пользователь offline
  • 1 631 комментарий
  • 261 публикация
 
  • +1
Ваня, когда я увидел разворот на месте, я просто влюбился!
Спасибо за отличный проект и новую статью!
handshake

<
  • Гражданин
24 мая 2016 11:26

Владимир / vladimirm2

  • С нами с 5.01.2010
  • Ушёл в реал Пользователь offline
  • 131 комментарий
  • 14 публикаций
 
  • 0
Хорошая, достойная работа! Делал подобное, но гораздо проще, поэтому вижу этот объем работы, впечатляет.

Спасибо за статью, есть многое, что для себя выделил и взял на вооружение! )) Супер игрушка!

<
  • Гражданин
24 мая 2016 23:14

Антон / basilevscom

  • С нами с 8.01.2010
  • Ушёл в реал Пользователь offline
  • 111 комментариев
  • 15 публикаций
 
  • 0
Просто супер! Обязательно надо будет попробовать повторить )))

Информация
Вы не можете участвовать в комментировании. Вероятные причины:
— Администратор остановил комментирование этой статьи.
— Вы не авторизовались на сайте. Войдите с паролем.
— Вы не зарегистрированы у нас. Зарегистрируйтесь.
— Вы зарегистрированы, но имеете низкий уровень доступа. Получите полный доступ.