CxemCAR на .NET Micro Framework — Bluetooth управление машинкой с Android

Первая, вводная часть статьи проекта CxemCAR с описанием ПО и исходниками для Android находится здесь. В данной статье я хотел бы описать вариант проекта для контроллеров с .NET Micro Framework. По сравнению с проектом на STM32, изменений мало, за исключением замены контроллера и программной части для него.   В качестве контроллера можно использовать Netduino, платы GHI Electronics и др. Я использовал FEZ Panda II:   Плата FEZ Panda II Питание DC двигателей осуществляется от Li-Po аккумуляторов 3.7В 1100 мА. Контроллер питается от отдельного аккумулятора 3.7В (хотя требуется 5В, но прекрасно работает и от 3.7В). Питание Bluetooth модуля берется с платы FEZ.

Фото элементов питания:

Фото элементов питания

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

Схема подключения

Плату FEZ Panda II к 4WD шасси прикрепил при помощи 2-х стороннего скотча:

uprav52-4.jpg

Далее, все было собрано и подключено. Итог на фото:

uprav52-5.jpg

Исходник для .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("rn");                 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("FWOKrn");            // подготавливаем байтовый массив для вывода в 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() происходит вычисление направления, а также небольшие расчеты и затем управляющие сигналы выводятся на соответствующие пины контроллера.

Проект на GitHub