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(«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


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

Добавить комментарий

Ваш адрес email не будет опубликован.