Машинка на контроллере с .NET Micro Framework, управляемая акселерометром Android-устройства

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

Инструментарии разработки: Java/Eclipse для Android и .NET Micro Framework/Visual C# Express для микроконтроллера.


Вступление


Я не сторонник различных навороченных смартфонов, айпэдов и пр. крутых технологических гаджетов. С 2001 г. имею всего лишь второй телефон: Samsung B2700 (первый был Siemens C55, работающих до сих пор у бабушки). Но по роду деятельности понадобилось устройство для доступа по SSH к серверам, да и к Web-интерфейсу, когда я был в поездках. В связи с чем был куплен один из самых дешевых китайских Android планшетов: Ainol Aurora. Работал он вполне нормально, и web, и ssh, консоль, и даже ftp-клиент, в общем свои функции выполнял на все 100%. Но нужен он был мне только в поездках, остальное время он тупо валялся выключенным дома, т.к. играть я не играю, читать с экрана не могу, для веб-серфинга есть стационарные ПК. Ну и решил я копнуть глубже, приспособить его еще для чего-нибудь. Первая идея была сделать умный дом, но часть системы уже функционировала, а ремонт закончил недавно, новые девайсы, кабеля, подводку питания и прочее городить не хотелось. Поэтому идею отбросил, однако ради спортивного интереса купил платку Seeeduino ADK Main Board и немного поигрался с передачей информации по USB соединению (Open Accessory и MicroBridge). В процессе работы выяснил, что хоть на планшете и стоит Android 4.0.3, то это не означает, что он поддерживает Open Accessory. Поэтому игрался с режимом MicroBridge, обмен данными происходил успешно, однако все равно остались кое какие непонятные мне моменты…

В итоге, я пришел к 2-ой идее: связать устройство по радиоканалу. Wi-Fi модули дороговаты, да и хотелось начать с чего-нибудь попроще, поэтому купил на ebay парочку Serial Bluetooth модулей по цене 6-7$ за штуку. В качестве реальной задачи решил сделать управляемую машинку с Android'а. На тот момент, в зарубежном интернете было уже немало подобных проектов, но в большей их части устройство управлялось с стандартной программы Bluetooth Terminal, путем ввода команд с клавиатуры. Это конечно все хорошо, но очень неудобно. Попадались проекты и с собственным Android-ПО, но как правило управление происходило при помощи экранных клавиш. Хотелось придумать что-то оригинальное, а т.к. почти все планшеты оснащены встроенным акселерометром, то решено было его и задействовать. Опыта кодинга для Android'а не было, также как и практически не имел дела с современными языками ООП. Но пришлось немного разобраться с Java и особенностями написания приложений по Android. И в данной статье я хочу описать изготовление простой РУ-bluetooth машинки, с управлением от Android-девайса.

Для реализации задуманного понадобится:
1. Android устройство
2. Контроллер (ШИМ, UART)
3. Bluetooth модуль
4. Драйвер двигателей
5. Платформа для машинки

Я думаю, что основная масса посетителей хабра — программисты и люди так или иначе связанные с ИТ. Поэтому им малоинтересны будут варианты, где в качестве контроллера будут использоваться PIC, STM32, Arduino и т.п. Именно поэтому я решил задействовать валявшийся без дела контроллер FEZ Panda II с .NET Micro Framework. Разработка ПО для данного контролера ведется в среде Visual C# Express, т.е. для большинства программистов это будет привычный инструментарий, хотя .NET там урезанный и многих функций нет.

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

Контроллер — любой под .NET Micro Framework. Т.е. подойдут Netduino, платы GHI Electronics и др. Я использовал FEZ Panda II:

В качестве Bluetooth модуля использован китайский UART модуль HC-06. Подойдут любые Serial Bluetooth. Лучше брать с штыревыми выводами, чтобы не пришлось паять, т.к. расстояние между выводами очень маленькое. Стоимость такого модуля на AliExpress/eBay составляет в среднем 5-10$.

В качестве драйвера я заюзал платку с микросхемой L298N, которая представляет собой сдвоенный мостовой драйвер двигателей и предназначена для управления DC и шаговыми двигателями. Цена вопроса 4-5$.

Для платформы машинки можно использовать все что угодно с 2 или 4 DC моторами. Я купил на eBay готовое 4WD шасси для DIY-проектов с 4-мя DC-моторчиками.

Ну и дополнительно понадобились аккумуляторы, темроусадка, хомуты, соединительные провода и пр.

Сборка шасси


4WD платформа от DFRobot поставляется в разобранном виде:

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

В отсек с моторчиками я поместил плату L298N, подключил их к ней, и вывел наружу 6 проводов: 4 для управления, GND и питание.

Подробную сборку 4WD платформы я описывать не буду, все равно у каждого она будет своя и даже если и будет шасси 4WD от DFRobot, то полюбому будут какие-то свои коррективы в зависимости от находящегося дома оборудования и материалов.

Bluetooth модуль я использовал китайский HC-06. Рекомендую брать сразу с штыревыми выводами, чтобы не замарачиваться с пайкой.
Необходимые пины модуля HC-06:
UART_TX (pin 1), UART_RX (pin 2)
3,3V (pin 12) — питание 3.3В.
GND (pin 13) — общий.
PIO1 (pin 24) — индикатор рабочего режима. Если соединение не установлено — то светодиод мигает, если установлено, то постоянно горит.

На плату L298N необходимо от контроллера привести 5 проводов: GND (Общий), IN1, IN2, IN3, IN4 — входы управления двигателями (ШИМ и направление вращения для левого двигателя и тоже самое для правого).

Питание DC двигателей осуществляется от Li-Po аккумуляторов 3.7В 1100 мА. Контроллер питается от отдельного аккумулятора 3.7В (хотя требуется 5В, но прекрасно работает и от 3.7В). Питание Bluetooth модуля берется с платы FEZ.

В проводах наблюдается большой беспорядок из-за того, что до этого проект собирался на STM32 и был разобран специально для реализации с контроллером FEZ Panda II. В дальнейшем, машинка будет опять на STM32 и Arduino с кое-какими усовершенствованиями. Но это тема другой статьи, возможно…

Схема подключения выглядит следующим образом:

Плату FEZ к платформе прикрепил при помощи 2-х стороннего скотча:

Далее, все было собрано и подключено. Получилось так:


Программное обеспечение


Итак, в приложении для Android я реализовал 3 способа управления:
1. Акселерометром — путем наклона устройства
2. Кнопками — на экране смартфона прорисовывается 4 кнопки: вперед, назад, поворот влево, поворот вправо.
3. Touch-управление. Данный способ я подсмотрел в игре Death Rally, захотелось попробовать реализовать. Честно сказать не очень удобно получилось, но оставил как есть.
4. 15.02.2013 в ПО добавил новый вид управления «виртуальный руль»

Все вычисления происходят в Android-приложении. На контроллер поступают готовые данные для ШИМ, направления вращения и команды для работы с Flash-памятью. Т.о. получилось универсальное приложение с гибкими настройками, которое может подойти под разные типы контроллеров, в разными значениями ШИМ и т.п. Собственно, что уже частично и сделано: данный проект уже реализован на STM32, Arduino и FEZ Panda II. В ближайшее время есть в планах реализовать под другие платформы: MSP430, PIC и др.

Пример команды, передаваемой через Bluetooth:
L-60\rR-100\r
Символ L означает команды для левого двигателя, знак минус — вращение назад, 60 — значение ШИМ. R — для правого двигателя, 100 — значение ШИМ (максимальное значение для FEZ Panda II). Для Arduino к примеру, максимальное значение ШИМ равно 255, его можно задавать в настройках Android приложения. По данной команде машинка будет двигаться назад, т.к. левые колеса буду вращаться немного медленнее правых, но ее будет слегка поворачивать влево. Как видим, такой способ управления больше подходит для гусеничной платформы.

Fw1035\t — команда записи в Flash-память микроконтроллера значения таймаута (через какое время машинка остановиться при потери связи или неполучении команды). Первая цифра 1 означает включение таймаута (соответственно 0 — выключен). Для получения значения в секундах необходимо 035 разделить на 10, т.е. в данном случае таймаут составит 3.5 секунды. Соответственно диапазон значений от 0.1 до 99.9 секунд.

К машинке можно подцепить множетсво дополнительных каналов: фары, сигнал, пиропатрон и многое другое. Команда для доп. канала: H1\r — где 1-включить. Соответственно если подать 0, то выключить. В представленном ПО реализован только 1 дополнительный канал, однако по аналогии с ним можно сделать сколько их угодно.

Кстати, символы команд можно также задавать в настройках Android приложения. Сами настройки задаются с главного активити, вот их скриншот:

Обратите внимание, что MAC-адрес Bluetooth модуля задается в настройках приложения. Узнать его можно при помощи какой-нибудь программы для работы с Bluetooth, к примеру при помощи Bluetooth Terminal или Bluetooth Chat. Конечно удобнее было бы сделать поиск устройств и сопряжение в самом ПО, но честно признаюсь у меня не хватило ума и опыта, провозился с этим вопросом дня 2, читал форумы и Stack Exchange, но так и не получилось. Может быть в дальнейшем вернусь к этому вопросу.

Исходный код приложения для Android я приводить не буду, т.к. он достаточно обьемен. Кому интересно, внизу статьи полный проект для Eclipse. Отмечу лишь, что в приложении используется главное activity с кнопками меню и настройками, а также 4 дополнительных activity — 3 для управления и один для работы с памятью микроконтроллера.

Работу с Bluetooth я вынес в отдельный класс cBluetooth. Для приема данных от BT создается отдельный поток thread, который в случае приема данных помещает их в очередь сообщений Handler. Далее, в основном потоке происходит обработка этих сообщений и в активити выводятся надписи и реализуются другие действия.

Исходник для .NET Micro Framework:
using System;
using System.IO.Ports;
using System.Threading;
using System.Text;

using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;

using GHIElectronics.NETMF.Hardware;
using GHIElectronics.NETMF.FEZ;

namespace CxemCAR
{
    public class Program
    {
        public const char cmdL = 'L';       // команда UART для левого двигателя
        public const char cmdR = 'R';       // команда UART для правого двигателя
        public const char cmdH = 'H';       // команда UART для доп. канала 1 (к примеру сигнал Horn)
        public const char cmdF = 'F';       // команда UART для работы с EEPROM памятью МК для хранения настроек
        public const char cmdr = 'r';       // команда UART для работы с EEPROM памятью МК для хранения настроек (чтение)
        public const char cmdw = 'w';       // команда UART для работы с EEPROM памятью МК для хранения настроек (запись)

        //public const int t_TOut = 2500;     // кол-во миллисекунд, через которое машинка останавливается при потери связи
        static int sw_autoOFF;
        static int autoOFF = 2500;
        static byte[] storage = new byte[InternalFlashStorage.Size];                                // переменная для хранения значений FLASH памяти МК

        static OutputPort MotorL_d = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di4, false);           // направление вращения двигателя 1
        static OutputPort MotorR_d = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di7, false);           // направление вращения двигателя 2
        static OutputPort Channel1 = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di8, false);           // доп. канал 1
        static PWM MotorL = new PWM((PWM.Pin)FEZ_Pin.PWM.Di5);                                      // ШИМ вывод для управления двигателем 1 (левый)
        static PWM MotorR = new PWM((PWM.Pin)FEZ_Pin.PWM.Di6);                                      // ШИМ вывод для управления двигателем 2 (правый)
        static SerialPort UART1 = new SerialPort("COM1", 9600);                                     // новый объект UART1 (порт COM1)
        static Timer timerTO;                                                                       // таймер
       
        public static void Main()
        {
            byte[] L_Data = new byte[4];        // строковый массив для данных мотора L
            byte L_index = 0;                   // индекс массива
            byte[] R_Data = new byte[4];        // строковый массив для данных мотора R
            byte R_index = 0;                   // индекс массива
            byte[] H_Data = new byte[1];        // строковый массив для доп. канала
            byte H_index = 0;                   // индекс массива
            byte[] F_Data = new byte[8];        // строковый массив данных для работы с EEPROM
            byte F_index = 0; 
            char command = ' ';                 // команда: передача координат R, L, H, F или конец строки

            int i_tmp_L = 0;
            int i_tmp_R = 0;
            int i_tmp_H = 0;

            byte[] incomingByte = new byte[1];  // байт с UART
          
            UART1.Open();
            UART1.Flush();

            timerTO = new Timer(new TimerCallback(TimeOut), null, autoOFF, autoOFF);  // инициализация таймера потери связи
            timer_init();                                                             // инициализируем программный таймер

            while (true)
            {
                int read_count = UART1.Read(incomingByte, 0, 1);        // считываем данные
                if (read_count > 0)                                     // пришли данные?
                {
                    if (incomingByte[0] == cmdL)                        // если пришли данные для мотора L
                    {
                        command = cmdL;                                 // текущая команда
                        Array.Clear(L_Data, 0, L_Data.Length);          // очистка массива
                        L_index = 0;                                    // сброс индекса массива
                    }
                    else if (incomingByte[0] == cmdR)                   // если пришли данные для мотора R
                    {
                        command = cmdR;                                 // текущая команда
                        Array.Clear(R_Data, 0, R_Data.Length);          // очистка массива
                        R_index = 0;                                    // сброс индекса массива
                    }
                    else if (incomingByte[0] == cmdH)                   // если пришли данные для доп. канала 1
                    {
                        command = cmdH;                                 // текущая команда
                        Array.Clear(H_Data, 0, H_Data.Length);          // очистка массива
                        H_index = 0;                                    // сброс индекса массива
                    }
                    else if (incomingByte[0] == cmdF)                   // если пришли данные для доп. канала 1
                    {
                        command = cmdF;                                 // текущая команда
                        Array.Clear(F_Data, 0, F_Data.Length);          // очистка массива
                        F_index = 0;                                    // сброс индекса массива
                    }
                    else if (incomingByte[0] == '\r') command = 'e';    // конец строки
                    else if (incomingByte[0] == '\t') command = 't';    // конец строки для команд работы с памятью


                    if (command == cmdL && incomingByte[0] != cmdL)
                    {
                        if (ValidData(incomingByte[0]))
                        {
                            L_Data[L_index] = incomingByte[0];              // сохраняем каждый принятый байт в массив
                            if (L_index < (L_Data.Length - 1)) L_index++;   // увеличиваем текущий индекс массива
                        }
                    }
                    else if (command == cmdR && incomingByte[0] != cmdR)
                    {
                        if (ValidData(incomingByte[0]))
                        {
                            R_Data[R_index] = incomingByte[0];
                            if (R_index < (R_Data.Length - 1)) R_index++;
                        }
                    }
                    else if (command == cmdH && incomingByte[0] != cmdH)
                    {
                        if (ValidData(incomingByte[0]))
                        {
                            H_Data[H_index] = incomingByte[0];
                            if (H_index < (H_Data.Length - 1)) H_index++;
                        }
                    }
                    else if (command == cmdF && incomingByte[0] != cmdF)
                    {
                        F_Data[F_index] = incomingByte[0];
                        if (F_index < (F_Data.Length - 1)) F_index++;
                     }
                    else if (command == 'e')                                // если приняли конец строки
                    {
                        timerTO.Dispose();                                  // останавливаем таймер потери связи
                        string tmp_L = new string(System.Text.UTF8Encoding.UTF8.GetChars(L_Data));      // формируем строку из массива
                        string tmp_R = new string(System.Text.UTF8Encoding.UTF8.GetChars(R_Data));
                        string tmp_H = new string(System.Text.UTF8Encoding.UTF8.GetChars(H_Data));

                        try
                        {
                            if (tmp_L != null) i_tmp_L = int.Parse(tmp_L);                              // и пытаемся преобразовать в int
                            if (tmp_R != null) i_tmp_R = int.Parse(tmp_R);
                            if (tmp_H != null) i_tmp_H = int.Parse(tmp_H);
                        }
                        catch { 
                            Debug.Print("Error: convert String to Integer"); 
                        }


                        if (i_tmp_L > 100) i_tmp_L = 100;
                        else if (i_tmp_L < -100) i_tmp_L = -100;
                        if (i_tmp_R > 100) i_tmp_R = 100;
                        else if (i_tmp_R < -100) i_tmp_R = -100;

                        Control4WD(i_tmp_L, i_tmp_R, i_tmp_H);
                        timerTO.Change(autoOFF, autoOFF);                                               // таймер считает сначала
                    }
                    else if (command == 't')                                                            // если приняли конец строки для работы с памятью
                    {
                        Flash_Op(F_Data[0], F_Data[1], F_Data[2], F_Data[3], F_Data[4]);
                    }
                }
            }
        }

        static void Flash_Op(byte FCMD, byte z1, byte z2, byte z3, byte z4)
        {
            if (FCMD == cmdr && sw_autoOFF != 255)                              // если команда чтения EEPROM данных 
            {
                byte[] buffer = Encoding.UTF8.GetBytes("FData:");               // подготавливаем байтовый массив для вывода в UART
                UART1.Write(buffer, 0, buffer.Length);                          // запись данных в UART
                byte[] buffer2 = new byte[4] { storage[0], storage[1], storage[2], storage[3] };
                UART1.Write(buffer2, 0, buffer2.Length);
                byte[] buffer3 = Encoding.UTF8.GetBytes("\r\n");
                UART1.Write(buffer3, 0, buffer3.Length);
            }
            else if (FCMD == cmdw)                                              // если команда записи EEPROM данных
            {
                byte[] varToSave = new byte[InternalFlashStorage.Size];
                varToSave[0] = z1;
                varToSave[1] = z2;
                varToSave[2] = z3;
                varToSave[3] = z4;
                InternalFlashStorage.Write(varToSave);                          // запись данных в FLASH память МК
                timer_init();		                                            // переинициализируем таймер
                byte[] buffer2 = Encoding.UTF8.GetBytes("FWOK\r\n");            // подготавливаем байтовый массив для вывода в UART
                UART1.Write(buffer2, 0, buffer2.Length);	                    // посылаем сообщение, что данные успешно записаны
            }
        }

        static void timer_init()
        {
            InternalFlashStorage.Read(storage);                                 // чтение данных с FLASH памяти
            sw_autoOFF = storage[0];
            if(sw_autoOFF == '1'){                                              // если таймер останова включен
                byte[] var_Data= new byte[3];
                var_Data[0] = storage[1];
                var_Data[1] = storage[2];
                var_Data[2] = storage[3];
                string tmp_autoOFF = new string(System.Text.UTF8Encoding.UTF8.GetChars(var_Data));
                autoOFF = int.Parse(tmp_autoOFF)*100;
                timerTO.Change(autoOFF, autoOFF);                               // изменяем параметры таймера
            }
            else if(sw_autoOFF == '0'){
                timerTO.Dispose();                                              // выключаем таймер
            } 

            Debug.Print("Timer Init" + autoOFF.ToString());
        }

        static void TimeOut(object o)
        {
            //Debug.Print(DateTime.Now.ToString());
            Control4WD(0, 0, 0);                                                // при таймауте останавливаем машинку
        }

        public static void Control4WD(int mLeft, int mRight, int Horn)
        {
            bool directionL, directionR;                                        // направление вращение для L298N
            int valueL, valueR;                                                 // значение ШИМ M1, M2 (0-100)

            if (mLeft > 0)
            {
                valueL = mLeft;
                directionL = false;
            }
            else if (mLeft < 0)
            {
                valueL = 100 - System.Math.Abs(mLeft);
                directionL = true;
            }
            else
            {
                directionL = false;
                valueL = 0;
            }

            if (mRight > 0)
            {
                valueR = mRight;
                directionR = false;
            }
            else if (mRight < 0)
            {
                valueR = 100 - System.Math.Abs(mRight);
                directionR = true;
            }
            else
            {
                directionR = false;
                valueR = 0;
            }

            if (Horn == 1)
            {
                Channel1.Write(true);
            }
            else Channel1.Write(false);

            //Debug.Print("L:" + valueL.ToString() + ", R:" + valueR.ToString());
            
            MotorL.Set(30000, (byte)(valueL));
            MotorR.Set(30000, (byte)(valueR));

            MotorL_d.Write(directionL);
            MotorR_d.Write(directionR);
        }

        public static bool ValidData(byte chIncom)                  // проверка поступившего символа на принадлежность к "0..9" или "-"
        {
            if ((chIncom >= 0x30 && chIncom <= 0x39) || chIncom == 0x2D) return true;
            else return false;
        }
    }
}

Сама программа под FEZ не очень сложная — в цикле считываем данные с UART и формируем соответствующие массивы. Как только пришел символ окончания передачи (\r или \t), то данные преобразовываются и передаются в соответствующие функции Control4WD() или Flash_Op(). В функции Control4WD() происходит вычисление направления, а также небольшие расчеты и затем данные выводятся на соответствующие пины контроллера.

Текущая версия ПО: 1.2 от 15.02.2013.

Видео работы:


Видео работы режима «виртуальный руль» (добавил 15.02.2013):


Скачать APK файл для установки на Android устройство
Скачать проект для Eclipse
Скачать проект для Visual C# 2010 Express

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

Похожие публикации

Комментарии 41
    +1
    С акселерометром круто конечно, но неудобно.
      0
      А почему неудобно? Каким способом Вы бы реализовали? Спрашиваю, потому что можно было бы усовершенствовать проект.
      P.S. Помимо акселерометра есть еще управление с экранных кнопок.
        0
        Я бы сделал повороты планшетом как рулем в машине, а скорость пальцем на экране. Прикольно было бы еще на машину поставить wi-fi камеру и транслировать на планшет.
          0
          Коль юзать уже wifi то полностью перейти на него да и радиус покрытия по больше будет
            0
            Спасибо за идею, попробую так реализовать.
            По поводу Wi-Fi: видео realtime с передачей mjpeg потока на планшет — рабочий прототип уже имеется, правда камера покупная полностью и не бюджетная. А с wi-fi управлением планы есть, но пока что никак не подберу подходящего Wi-Fi модуля для подключения к контроллеру. Китайские на ebay брать боязно из-за скудной документации, а WIZnet модули на офсайте продают мин. кол-во 2 штуки, а в России за них заламывают большую цену. Но наверное придется брать 2 шт. wiznet за неимением других вариантов
              0
              А если брать модуль wi fi модуль от ноута, как альтернатива, конечно жуткий боян выйдет, но дешевле чем winzet
                0
                Ну у меня во первых нет лишнего Wi-Fi модуля от ноутбука, во вторых остается открытым вопрос по документации. WIZnet не сильно дорогие, 25$ за модуль, просто мин. заказ 2 шт, получается 50$ за 2 модуля.
                  0
                  Может быть подойдет роутер TL-MR3020 и прошивка OR-WRT? У меня допустим в планах попробовать скрестить его и Ваше детище, если времени хватит.
                    0
                    Для управления по Wi-Fi подойдет. Я этот вариант прорабатывал. Но вот для real-time видео навряд ли. Я пробовал, у меня raspberry Pi не тянула поток даже с камеры logitech (хотел ее задействовать).
                      0
                      d-link 320-й c прошивкой от Олега + Logitech-камера + mjpeg-streamer успевает видео вполне
              0
              Почти реализовал такое управление, как вы предложили. Поворот машинки осуществляется поворотом планшета, а под большой правый палец рисуется вертикальный seekbar. Единственное — не придумал как реализовать задний ход: либо отдельную кнопку сделать, либо разделить ползунок на 2 части — двигаю вниз — машинка едет назад, двигаю вверх ползунок — машинка едет вперед. Скорость пропорциональна положению ползунка естественно.
                0
                А может обойтись без ползунка и скрестить 2 метода управления? Поворот влево/вправо и наклон вперед/назад. Возможно это будет не удобно, просто предположение.
                  0
                  Не очень понял вопроса. Имеете ввиду при помощи акселерометра? Если да, то это так и реализовано (см. видео).

                  P.S. Кстати как придет танк, реализую еще один метод управления: по бокам 2 ползунка, левый для левой гусеницы, правый для правой. Т.е. раздельное управление.
                    0
                    У Вас реализованы наклоны влево/вправо вперед/назад, а я имею ввиду поворот, как рулем.
                      0
                      Это тоже самое. А вообще возьмите в руки телефон и попробуйте поуправлять виртуальной машинкой по вашей задумке, особенно обратите внимание в каком будет положении телефон, когда полный газ (максимально наклонили планшет вперед) и поворот влево/вправо.
                        0
                        Смотря как крутить, а так я управлял даже реальной машинкой по Вашим исходникам)
                  0
                  Я бы сделал как в радиоуправляемых пультах. Т.е. под правый большой палец например регулятор скорости, а под левый переключатель взад-вперед.
          +10
          О, классно, когда всё вместе есть, да ещё и с исходниками. Вы молодец.

          Насчёт подключения пиропатрона — хотите стрелялку сделать, или прыгать будет, как блоха? :) Как я понял, у вас задумка большая, и это только часть её. Платформа внушает, на неё много чего можно поставить.

          P.S. Сейчас к вам набегут рассказывать, как вы неправильно поступили, взяв .net, и как бы они всё ловко на ассемблере/сях закодили. Не слушайте
            0
            Сейчас к вам набегут рассказывать, как вы неправильно поступили, взяв .net, и как бы они всё ловко на ассемблере/сях закодили. Не слушайте

            Ну этого я не боюсь, т.к. на такой же платформе 4WD реализовал на STM32, плюс на другом шасси реализовал на Arduino (processing), не ассемблер конечно, но все же…
            Но вообще код контроллера не очень сложный, а вот над Android приложением голову пришлось немного поломать.
              +1
              Кстати о коде… Вы злостный нарушитель С# Code Style Convention
              Но если забить на педантизм, то гуд. Еще бы описали как среду разработки под .Net MF разворачивать (про аппаратную часть в том числе).
                0
                Не спорю, т.к. под ПК не программировал наверное с годов этак 96-97 (и то это был Visual Basic). Проект создавался исключительно для аудитории хабра, чтобы показать программистам, что и из под привычного для них инструментария разработки Microsoft Visual можно работать с контроллерами.
                Про то как разворачивать среду под .NET Micro Framework Писал с год назад, но на другом сайте. Если интересно, то вбейте в поисковик «Начинаем работать с .NET Micro Framework».
                  +2
                  Мне кажется краткий обзор сюда же (как дополнение) не помешает. Или хотя бы ссылку в конце поста.
                    0
                    Согласен!
                      0
                      Добавил линки в конце
              0
              Очень таки неплохо. А управление акселерометром очень отличное решение, правда машинка, как по мне, очень резко поворачивает.
                0
                Мне кажется это из-за того, что отсутствует рулевой механизм. Как автор сказал — система похожа на управления гусеничным траком.
                  0
                  Да именно так. К сожалению, пока что не располагаю гусеничной платформой, но в планах приобрести и немного модернизировать проект
                  0
                  Все настраивается в приложении под Android. Можно сделать, чтобы не так резко поворачивала. Ну и плюс меньше наклон планшета вбок — меньше поворот.
                  +1
                  Отлично сделали!
                  Недавно реализовал нечто подобное (синезуб+Андроид+Ардуино) для декоративной подсветки.
                  И там столкнулся с проблемой: дело в том, что при использовании модуля HC-05 невозможно корректно принять несколько символов одновременно — в серийный порт приходил полный бред — проблема в синезуб-модуле.
                  Из проблемы вышел, посылая односимвольные команды.
                  Жаль, что не слышал на тот момент про Netduino — все таки маловато статей про этот контроллер, по сравнению с Ардуино.
                  Теперь буду знать :) Спасибо.
                    0
                    Круто. Единственное смущает — класс чересчур большей класс Program в коде .net. Почему не распихать в разные классы?
                      0
                      а для чего писать во флеш время потери связи? есть же RAM? Или предусматривается пропадание питания и затем его автономное восстановление?
                        +1
                        Если есть флэш в МК, то почему бы ее не использовать? А при каждом включении передавать значение по Bluetooth и писать значение в RAM ИМХО нецелесообразно. Хотя конечно кто на что горазд… Я решил так сделать, но вариантов может быть множество.
                        А вообще, это значение AutoOFF можно жестко задать в программе (там сейчас эта строка закомментирована), а все действия с FLASH памятью удалить, т.к. оно занимает не мало строчек кода (особенно в варианте машинки на STM32) и писалось чисто для демонстрации возможностей передачи данных с Android и записи по Flash. Думаю в системах «умный дом» и т.п. это очень пригодилось бы.
                        Вообще проект больше создавался как how-to в помощь начинающим роботостроителям, т.к. в принципе проект не очень то сложный и при наличии открытых исходников (я их постарался максимально прокомментировать) позволяет во всем разобраться самому. Для себя (как придет гусеничная платформа) буду делать более навороченный проект, описание со всеми исходниками естественно потом выложу. Но это уже будет не на .NET контроллере… FEZ Panda II совместно с граф. сенсорным экранчиком уйдет под климат-контроль управления батареями центрального отопления в квартире.
                          0
                          вас понял.
                        0
                        Обожаю Хабр!!!
                          0
                          Гуд! Интересно. Это именно то что хотел реализовать я, с учётом Вашего опыта надеюсь будет сделать проще! Был бы инвайт, не пожалел бы.
                            0
                            Он уже есть, а написано вроде что из песочницы. Странно
                              0
                              Я публиковал из песочницы не имея инвайта, до этого был эккаунт Р/О. После публикации НЛО дало мне инвайт. Как-то так…
                              0
                              А чтобы качать исходники надо на меге аккаунт зарегистрировать?
                                0
                                Вопрос снят, оказывается всего то надо было на галочку нажать
                                0
                                Появился новый проект на Arduino, плюс добавилась возможность просмотра Real-Time видео с Wi-Fi IP-камеры на Android-смартфоне, а также функции управления pan/tilt камеры.

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

                                Самое читаемое