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

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

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

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

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

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

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

Прикрепленные файлы:


Категория: Аппаратура радиоуправления моделями
Метки:

Написать коментарий

*
= 5 + 4

Добавить изображение

Последние статьи