控制R.C.装有Windows Phone和蓝牙的汽车(译文)
By S.F.
本文链接 https://www.kyfws.com/news/controlling-a-r-c-car-with-windows-phone-and-bluet/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 27 分钟阅读 - 13143 个词 阅读量 0控制R.C.装有Windows Phone和蓝牙的汽车(译文)
原文地址:https://www.codeproject.com/Articles/725607/Controlling-a-R-C-Car-with-Windows-Phone-and-Bluet
原文作者:Joel Ivory Johnson
译文由本站翻译
前言
逐步修改要由Windows Phone控制的RC汽车
介绍
在2013年初,我与Razorfish的Emerging Experiences小组一起工作时,有机会与Netduino,Arduino和其他几项在Maker Movement中起着重要作用的技术进行了合作.除其他外,我还改装了一部RC汽车,使其可以通过Windows Phone通过蓝牙进行控制.几个月前,有人看到了这辆汽车的视频,并问我是如何做到的.视频的评论部分不够大,无法包含我认为足够的回应,因此我正在写这篇文章. 认识到在达到这一目标时可能会有各种各样的背景和经验水平,所以我决定从第一方开始,并在此过程中进行一些更改.最初的RC到Bluetooth的转换是使用Netduino Plus 2完成的.这次,我将使用Arduino,因为它更普及并且通常更便宜.对于此版本的汽车,我不考虑为简单起见而可能添加的其他改进.我也将重点放在狭窄的地方,并始终坚持使汽车起步并运行.现在的目标是在了解发生了什么的情况下涵盖足以使汽车运行的最低限度的内容.在以后的著作中,我计划对控件应用程序的UI进行改进,并为汽车添加更多功能.
先决条件
要理解本文,您需要了解基本的数字电子学和编程.您无需了解或记住CMOS和TTL门之间的区别即可了解此处编写的内容.我会尽量保持基础.您可能需要熟悉一种基于C的语言(C,C ++,C#),以了解本文中编写的内容.我在本文的第一个修订版中使用的是Windows Phone 8,但可能会在以后的修订版中添加Android和iOS.
什么是Arduino
我将从以前给我的Arduino的描述中借用.](http://emergingexperiences.com/2013/08/getting-started-with-arduino-and-3d-printing/)Arduino是基于32位Amtel ARM处理器的单板计算机.它的开发环境是免费的(基于C ++),它得到了从专业人士到最熟悉者的大量硬件和软件开发人员的支持.在软件方面,开发软件中包含许多现有的解决方案组件,或者可以下载这些组件.从硬件方面,您会发现各种各样的硬件添加件和附件,可以通过将它们插入Arduino来添加到解决方案中. 虽然有几种形式可以识别为Arduino,但各种公司和个人一直在发布新的变体.有关构造它们的信息很容易获得,所需的零件也很容易获得.预先构造的大多数可用的Arduino都将采用通用形式,并具有表示其配置的名称.为了避免在Arduino中出现大量的变体,在撰写本文时,我将仅考虑可通过The Arduino site获得的Arduino板.
一个Arduino与另一个Arduino有何不同?
就像PC具有不同的配置(不同的存储量,端口等)一样,Arduino也是如此.差异往往在于处理器速度,不同类型端口的数量或其他外围设备.以下是[Arduino]上的一些硬件组件(http://arduino.cc/en/Products.Compare).请注意,请注意所有Arduino都将提供所有这些功能.您可以在Arduino站点上找到一些可用的Arduino配置的表. .
输入/输出电压
您连接到的许多外围设备都将期望(或产生)3.3伏或5伏的输入和输出信号.有些设备可以工作,有些则不能.因此,您需要确保所使用的Arduino与具有可接受信号电压的外围设备配合使用.某些Arduino会有一个开关,可让您更改它在3.3伏还是5.5伏范围内工作.如果要连接两个在不同电压下工作的设备而又不存在损坏风险,则需要附加电路.
处理器时钟频率
目前可用的Arduino板的速度范围从8 MHz到84 MHz.最典型的是16 MHz.
USB端口
大多数Arduino开发板都有USB端口,用于编程和从Arduino获取信息.连接到计算机后,它将显示为另一个串行端口.因此,您可以使用任何使用串行端口与Arduino交互的终端软件.一些Arduino可以模拟其他设备,例如键盘或鼠标.一些Arduino还具有USB主机端口,可使其与其他USB外设交互.
数字端口
快速浏览一下我现在在办公桌上拥有的Arduino,它们的端口数量范围从略多于5到不足50个不等.数字端口既可以用作输出,也可以用作输入.在输出模式下运行时,可以通过编程将它们设置为高电压或低电压.当作为输入操作时,程序可以查看连接到Arduino的东西是输出高电压还是低电压.这种极其基本的能力是其他功能的重要组成部分.可以通过数字端口上的基本操作来打开灯或电动机,甚至与其他设备通信.一些数字端口也支持中断.因此,您可以将该端口与例程进行关联,如果端口状态发生更改,该例程将自动被调用.
模拟口
如果您需要将引脚的输出设置为高电平和低电平之间的某个值,那么您将要使用模拟端口.可用于控制电动机的速度,而不必打开或关闭电动机的速度,或用于控制灯光的亮度.当用作输入时,模拟端口可用于检测电位计的位置或传感器上的光强度.
PWM端口
PWM端口能够输出方波.通常,这些端口上输出的信号将具有相同的频率.但是波的脉冲宽度可以改变.接下来的两个波具有相同的频率,但是波的宽度不同.
波形在其高状态下花费的时间百分比也称为波形的占空比.当PWM信号的占空比为0%时,它始终处于关闭状态.当占空比为100%时,它始终处于打开状态. PWM信号可用于控制电动机的速度或灯的亮度.
通讯口
尽管可以通过谨慎地打开和关闭端口的时间与其他硬件进行通信,但您通常不希望自己(或需要这样做). Arduino支持您可能要与之交互的许多其他电路和传感器使用的某些标准通信协议.所有这些通信端口都是串行工作的.他们将一次发送或接收一条消息.
- 串行端口
- I2C
- SPI
Arduino可使用哪些外设
您可以将Arudinos连接到多种产品,即使这些产品在设计时并未考虑到Arduino.您执行此操作的能力将取决于您对Arduino的了解程度.但是,考虑到Arduino生产的哪些产品或可以轻松投入使用的产品呢?通过对齐一些引脚并将它们堆叠在Arduino的顶部,可以使用许多产品.这样的外围设备称为"防护罩".
- 无线上网
- 以太网屏蔽
- 1个
- 2
- GPRS无线电
- 1个
- 记忆盾
- 一种
- 蓝牙
- 1个 还有其他一些设备,虽然没有实现Arduino的接口,却不费吹灰之力,因为它们实现了简单或通用的接口
入门
让我们开始迈向" hello world".对于Arduino,这将意味着使用它打开灯.您需要以下内容
- Arduino IDE
软件安装
该软件可用于Linux,OS X和Windows.我正在使用Windowa版本,可以通过以下两种方式之一进行安装.我建议的方法是下载下载的"安装程序"版本.与许多其他Windows安装一样,安装程序唯一需要您做的就是单击"下一步"按钮几次.下载的Zip版本使您可以将IDE解压缩到您选择的任意位置.安装完成后,使用USB电缆将Arduino板连接到计算机. Arduino通常随附一个预加载的程序,该程序使板上的指示灯闪烁.插入USB后,您应该会看到Arduino strt上的指示灯闪烁并且应该安装驱动程序. 可能没有提示您输入驱动程序.我已经将Arduino Mega连接到我的计算机,但未被识别(不同于其他Arduino,它使用不同的硬件组件进行连接).它最初在设备管理器中显示为无法识别的设备.可以在Arduino应用程序文件夹中找到驱动程序.
创建一个新程序
启动Arduino IDE.默认情况下,Arduino IDE将打开空白,并准备开始编写代码.如果要启动另一个空白项目,请从"文件"菜单中选择"新建",将打开IDE的新实例.如果您正在使用IDE的1.5.5版(在撰写本文时为beta),则窗口将打开,其中定义了两个空函数.如果不是,则窗口将完全空白.如果为空,请尝试以下代码.
void setup() {
// put your setup code here, to run once:
}
void loop() {
// put your main code here, to run repeatedly:
}
我们将编译此代码并将其上传到开发板上.我们需要告诉IDE我们有什么板子,以及它连接到哪个COM端口.在"工具"菜单下,有一个"板子"子菜单,其中包含IDE支持的Arduino"板子"模型.选择与您的板名称匹配的板名称.您需要对端口执行相同的操作.如果您不知道Arduino使用的是哪个端口,请拔下它,然后再次访问菜单,以查看哪个端口名称从列表中消失了.已被删除的是您的Arduino开发板正在使用的那个. 在工具栏中,有一个按钮可用于检查语法错误代码(复选标记),以及一个可用于将编译后的程序上载到Arduino的按钮(一个箭头).
单击箭头.如果在IDE底部一切正常,您将收到一条消息,提示"成功上传",并且指示灯应停止闪烁.让我们更新程序,以便我们自己使灯闪烁.为此,我们需要将一个引脚配置为输出引脚.很多时候,您可以在不同的Arduino板上使用相同的引脚.最好定义在#define节中使用的引脚或将其定义为变量,而不是使用实际的引脚号.如果永远不需要更改,则可以在一处进行更改.引脚13通常连接到LED.向其写入" HIGH"值将打开LED.因此,我们将用于控制灯光的品脱定义为13.
#define LIGHT_PIN 13
然后,我们向其写入HIGH或LOW值.初始化步骤在设置功能中完成.引脚13可以是输入或输出引脚.我们需要使用" pinMode"功能将其设置为输出引脚.设备首次启动(或重新启动)时,它将首先运行" setup()“函数中的代码.然后它将在"循环"功能中运行代码,直到关闭设备.在"循环"中,我们要打开灯,等待一秒钟(1000毫秒),关闭灯,然后再等待一秒钟.
#define LIGHT_PIN 13
void setup() {
//set PIN 13 as a pin that we can write to
pinMode(LIGHT_PIN, OUTPUT);
}
void loop() {
digitalWrite(LIGHT_PIN, HIGH);
delay(1000);
digitalWrite(LIGHT_PIN, LOW);
delay(1000);
}
再次部署应用程序.几秒钟后,指示灯应开始闪烁.恭喜!您已经完成了第一个Arduino程序.在继续之前,让我们进行一些小的更改.我们不只是打开和关闭它,而是调整亮度以使其淡入和淡出.为此,我们需要开始添加外部电路.在下一部分中,您将需要一个LED(发光二极管),一个电阻值在300到1KΩ之间的面包板.电阻器用于降低流过LED的电流,以确保我们不会在电流过大的情况下烧坏任何东西.我们将通过使用” analogWrite()“函数来更改提供给照明灯的电量来调整LED的亮度.许多Arduino上的引脚13不支持analogWrite()
函数.
您需要查找您正在使用的Arduino上哪些引脚可用于PWM输出.转到Arduino产品页面,然后单击与您所拥有的Arduino相匹配的模型.在打开的页面上搜索"输入和输出"页面,然后在该部分中查找有关PWM的行.您可以使用的引脚号将在此处列出.以下是一些arduino板的信息
关于使用面包板
如果您之前使用过面包板,则可以跳过本部分.面包板使您可以更快地制作电路原型,并减少在没有面包板的情况下构建相同电路所需的导线数量.面包板上孔的下方是电连接组件的导体.导线都相互平行,通常垂直于面包板的长度.电力线是例外,它沿着电路板的长度延伸.董事会中心下方有一个萧条.导体不会越过该凹陷.连接在凹陷的同一侧插入到同一行(行编号)的所有组件.需要插入跨接线以连接不在分隔线的同一行和同一侧的组件.
将LED连线至Arduino
仅当电流以正确的方向通过LED时,LED才起作用. LED上有两条不同长度的电线.两条线中较长的线必须连接到正极,而较短的线必须连接到负极.使用跳线将标有``GND'‘的引脚之一连接到面包板上的一排电线.将” LED"插入板子,使较短的线与" GND"线在同一行.将电阻器连接到板上,使其中一根线与" LED"的长线在同一行中,另一端在面包板的空行中.使用第二根跨接线将电阻器的另一端连接到用于PWM的引脚.
模拟代码
一旦电路连接好,我们就可以编写代码来控制LED的亮度.当我们编写之前的程序时,必须在控制引脚之前使用" pinMode"将引脚设置为输出模式.在引脚上使用" analogWrite"时,无需执行此操作.我们将在8位模式(比10位模式更广泛的支持)中使用PWM端口.包含要写入引脚的值的变量将从0开始,计数到255,然后再回落到零.每个新值将被发送到PWM引脚.结果是LED将变亮,然后逐渐变暗.
//Change the following to the pin number that
//matches the one that you are using.
#define LIGHT_PIN 13
int brightness = 0;
int delta = 1;
void setup() {
}
void loop() {
analogWrite(LIGHT_PIN, brightness);
brightness+= delta;
if(brightness == 255)
delta = -1;
else if(brightness == 0)
delta = 1;
delay(10);
}
通过串口输入输出
串行端口可用于与连接至Arduino的外围设备进行通信或与计算机进行通信. Arduino板上将有1至4个硬件串行端口.它们将被命名为" Serial"," Serial" 1," Serial" 2和" Serial" 3.这些端口中的每一个(如果存在于Arduino板上)将分配给特定的引脚组.您还可以实例化软件串行端口.可以将软件串行端口分配给任意两个引脚.硬件和软件串行端口在功能上看起来相似.但是软件串行端口只能在任何给定的时间点发送或接收,而不能同时进行.硬件串行串行端口可以同时执行两个操作.
端口"串行"已连接到计算机.当您的程序上传到Arduino时,计算机和Arduino之间的通信通过该端口进行.您还可以通过打开适当的端口,在与连接的Arduino交互的计算机上制作自己的应用程序.计算机和Arduino将需要就波特率(数据传输速度)达成一致.
可以使用Arduino IDE的一个名为SerialM
onitor的接口(可以通过按CTRL
+Shift
+M
或从菜单路径Tools
-> SerialM
onitor打开).查看从串行端口返回或写入的值.为了进行简单的演示,我们将把arduino计数从100增加到100,并将其输出到串行端口.
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
}
void loop() {
for(int i=0;i<=100;++i)
{
Serial.println(i);
delay(100);
}
}
上载程序后,打开串行端口监视器.您会看到数字流回来.在这里我要指出的是,与其他Arduino相比,Arduino Yun,Micro和Arduingo Leonardo在行为方式上存在差异.其他许多Arduino使用与主处理器分离的芯片来处理计算机与Arduino处理器之间的通信.当您在这些板上打开串行端口监视器时,设备将重置.因此,您将能够从头到尾捕获设备输出的所有信息.在Leonardo,Micro和Yun上,串行通信不是由单独的芯片处理的.这些设备中的Amtega处理器具有内置的USB接口,可将自己显示为串行端口,也可以将自己显示为鼠标,鼠标和串行端口.如果在Yun,Mico或Leonardo上重置了处理器,则在重置其他设备时,串行连接可以保持打开状态,但虚拟串行端口在重新启动时将消失,并且与该计算机的连接也将丢失.因此,当您从计算机打开串行连接时,这些设备不会重置,并且所写入的前几个数字不会显示在串行监视器中.您可以让处理器等到串行端口打开后,才开始对程序稍加修改即可开始写入数字.语句" if(Serial)“环绕着我们刚才编写的代码,以防止其在打开串行端口之前执行.
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
}
void loop() {
if(Serial)
{
for(int i=0;i<=100;++i)
{
Serial.println(i);
delay(200);
}
}
}
到目前为止,我们完成的所有程序都是非交互式的.我们将要做我们的第一个交互式程序.在这一部分中,Arduino将侦听来自串行端口的文本,并以此来打开或关闭灯.该程序还将发送有关其运行情况的少量反馈.在更新的程序中,变量” delta"将默认为零.因此,程序启动后,灯光的亮度将不会开始改变.相反,当此程序在" Serial"对象上接收到字符" 1"时,它将" delta"更改为一个,以便亮度将增大直至达到最大值(然后将" delta"重置为设置为0以防止亮度级别改变).当程序接收到值'0’时,它将把delta设置为-1,直到亮度变量达到最小值.通过" Serial"对象收到的所有其他字符将被忽略.当程序确实收到正在处理的值时,它将打印回一条消息,该消息将显示在计算机上.这是更新程序的源代码.
#define LIGHT_PIN 13
int brightness = 0;
int delta = 0;
void setup() {
Serial.begin(9600);
}
void loop() {
if(Serial.available())
{
char c = Serial.read();
if((c == '1')&&(brightness!=255))
{
delta = 1;
Serial.println("Turning Light On");
}
else if((c=='0')&&(brightness!=0))
{
delta = -1;
Serial.println("Turning Light Off");
}
}
brightness+= delta;
analogWrite(LIGHT_PIN, brightness);
if(brightness == 255)
delta = 0;
else if(brightness == 0)
delta = 0;
delay(10);
}
运行该程序,然后打开"串行端口监视器".在监视器中输入值1,然后按Enter.您会收到确认回信,并会看到灯光的亮度开始增加.将其发送为0,您将看到灯光的亮度减小,直到熄灭为止.
Servos
在我们准备与汽车对接之前,只需要介绍另外一个概念.这就是伺服的概念.伺服是伺服机构的缩写.该术语可以指代任何使用负反馈来纠正其性能以实现其内置功能的设备.大多数情况下,有人在Arduino的背景下提到伺服器时,人们指的是伺服电动机.伺服电机接收一个与所需位置对应的信号.伺服器将旋转其轴,直到到达所需位置并保持其位置. RC(远程控制)伺服器将位置信号作为PWM信号接收.虽然您可以使用analogWrite直接将PWM值发送至所需位置,但通常更容易使用<Servo.h>库.它将处理我们想要的角度位置和需要通过该位置的" analogWrite()发送的值之间的映射.就像analogWrite
()仅适用于某些端口一样,<Servo.h>库仅适用于某些端口.还要注意,除了Arduino Mega,使用<Servo.h>库将禁用端口9和10上的analogWrite
(),即使您没有使用这些端口来控制一个伺服器.
RC伺服器有三根线.两根电线将用于供电.红线必须连接到正电源,黑线或棕色线必须接地.第三根线连接到PWM源.如果要驱动多台电动机,则需要将电动机连接到可以处理足够电流的电源.但是我们可以将小型伺服器连接到Arduino,而无需担心.
该程序将非常类似于上一个程序.只是控制亮度,而不是控制亮度.伺服通常具有180度的范围.最小值为0,最大值为180,中间值为90.伺服的确切范围可能会有所不同.不要试图将伺服驱动器超过其物理极限.否则可能导致伺服器过热.告白:当我第一次使用on编程时,我用这种方式烧掉了一些昂贵的伺服器.
在上一个程序中,我使用了增量变量来增加或减少亮度值.尽管可以为伺服器执行此操作,但除非需要控制转弯速率,否则没有必要.对于光亮度,从一个值跳到另一个值将导致亮度看起来同时发生变化.伺服器以有限的速度运动.从一个值跳到另一个值将导致随时间变化的伺服位置.职位没有瞬时过渡.因此,在此程序中,我们不必担心随时间增加位置,而只会跳到所需的位置.因为在绝对左和绝对右之间存在一些我们感兴趣的位置,所以该程序将响应0-9之间的值,而不仅仅是0和1.
//Change the following to the pin number that
//matches the one that you are using.
#include <Servo.h>
#define MOTOR_PIN 13
Servo steeringServo;
void setup() {
Serial.begin(9600);
steeringServo.attach(MOTOR_PIN);
}
void loop() {
if(Serial.available())
{
char c = Serial.read();
switch(c)
{
case '0': steeringServo.write(0); break;
case '1': steeringServo.write(20); break;
case '2': steeringServo.write(40); break;
case '3': steeringServo.write(60); break;
case '4': steeringServo.write(80); break;
case '5': steeringServo.write(100); break;
case '6': steeringServo.write(120); break;
case '7': steeringServo.write(140); break;
case '8': steeringServo.write(160); break;
case '9': steeringServo.write(180); break;
default:
break;
}
}
else
delay(100);
}
将伺服器连接至Arduino.红线应连接到标记为5V的引脚,黑/棕线应连接到标记为GND的引脚之一.其余导线将连接到您正在使用的PWM引脚.运行该程序并打开串行监视器.在发送值时,请密切注意伺服器,以确保其不会超出其物理范围.如果确实如此,则立即将其返回到其范围内的位置.
控制驱动马达
在开始激活车辆上的驱动马达之前,请记住一些注意事项.确保提起驱动轮,以使其不接触任何东西.您想要发生的最后一件事是让汽车在仍连接到计算机或桌子上的情况下全速起飞.视您的汽车而定,可以通过将其倒置来执行此操作.我拿起一些相机架零件,并将它们组装起来以支撑车辆.
我使用的汽车上的驱动马达使用ESC(电子速度控制)单元. ESC做几件事.它包含一个功率调节器,该功率调节器从电池的7.2电压输出5伏特电压,并激活驱动马达. 5伏输出也被路由到转向伺服系统和无线电接收器/控制电路.我们要拔掉汽车的接收器并在其位置连接Arduino.如果我们使用与控制伺服器相同的程序,但是将arduino连接到ESC而不是伺服器,则可以使用它来控制电动机的速度.这次,我们将两者稍有不同. ESC将需要连接到它自己的电源(汽车的电池).那里的Arduino将无法为驱动电机供电.对于此测试,您无需连接红色导线.如果您仍然想连接红色导线,请将其连接到VIN引脚,以使来自ESC的电压通过Arduino的稳压器. 接线完成后,再次运行程序.这次,您应该能够使汽车的驱动马达前进和后退.如果您的电调的行为与我的行为相同,如果您尝试不通过空档而从前进到后退,则电调会解释为您希望车辆制动.因此,您可能需要在更改方向之前选择中立位置. 我正在使用的ESC是Dynamite Tazer 15T.尽管可以在Internet上找到文档(PDF),但该ESC不允许进行任何校准.如果您使用的是其他esc,则可能需要进行校准,以便esc知道要映射到其需要使用的值范围的速度方向.由于我没有支持校准的ESC,因此我无法在本文档中解决.
蓝牙通讯
有许多可与Arduino配合使用的蓝牙解决方案.有些可以作为防护罩.我不想使用屏蔽作为解决方案,因为它会限制我可以用于蓝牙通信的引脚.我使用了需要将其引脚分别连接到arduino的引脚.其中大多数将具有您将关心的4个连接.其中两个是权力的.它们将需要连接到5伏特线和" GND".另外两行用于发送和接收数据.最后,我们将要使用Windows Phone.在开始编码之前,请先连接电源和" GND"以接通电源,然后将手机与其配对.在我正在使用的配对设备的蓝牙设备上,配对代码为" 1234".您需要查看正在使用的蓝牙电路的文档,以了解要使用的代码.
您需要编写代码来完成一些工作
为了获得与手机配对的蓝牙设备的列表,Microsoft提供了PeerFinder
类.您需要向您的应用程序添加Proximity
权限才能使用此类.可以使用两行代码来检索与手机配对的设备列表.
PeerFinder.AlternateIdentities["Bluetooth:Paired"] = "";
var pairedDevices = await PeerFinder.FindAllPeersAsync();
为了改善用户体验,您可能需要给蓝牙接收器起一个独特的名称,应用程序可能会寻找该名称以区别于其他蓝牙设备.在许多蓝牙接收器上,可以更改设备使用的名称.在我方便的那一个上,我注意到生产它的公司似乎已经明确剔除了说明如何更改适配器ID的文档部分(可能是适当的,因为设备没有响应名称更改)命令).我将继续使用似乎已经烧入我的设备的名称" linvor".查看蓝牙适配器中的文档以查看需要使用的名称. 上面代码中的" pairedDevices"集合将包含" PeerInformation"实例.抓住代表您的设备的设备,并使用它创建一个新的" Socket"对象. " Socket"将用于读取和写入蓝牙适配器.
_socket = new StreamSocket();
await _socket.ConnectAsync(_selectedDevice.HostName, "1");
现在,我们可以在Windows Phone和汽车电路之间发送和接收消息.读写操作是异步的.使用套接字的InputStream成员,我们可以启动读取操作.如果没有要读取的内容,则调用将[可感知地]等待,直到有可读取的数据为止(当有趣时,实际发生的细微差别超出了本文档的范围).一旦字节可用并读取,我就将它们转换为字符串以供以后处理.
byte[] bytes = new byte[128];
await socket.InputStream.ReadAsync(bytes.AsBuffer(),(uint)bytes.Length, InputStreamOptions.Partial);
bytes = bytes.TakeWhile((v, index) => bytes.Skip(index).Any(w => w != 0x00)).ToArray();
string str = Encoding.UTF8.GetString(bytes, 0, bytes.Length);
OnMessageReceived(str);
对于该项目的第一个迭代,汽车不需要发回任何重要数据.它只会听有关其应做操作的说明.目前,唯一要发送的指令是节气门和转向伺服器的状态.我正在以看起来像REST请求的字符串形式发送命令.
public void TransmitState()
{
if ((_communicationController != null)&&(_communicationController.IsConnected))
{
_communicationController.Write(String.Format("/car/throttle/{0}\r\n", ThrottleValue));
_communicationController.Write(String.Format("/car/steering/{0}\r\n", SteeringValue));
}
}
这样一来,就涵盖了Windows Phone端与汽车进行通信所需的基本知识.决定应用程序中的用户界面仍然有工作.让我们回到汽车上来实现用于响应收到的命令的代码.
不要让汽车离开!
由于我们接近汽车几乎准备好要坐在地上行驶的地步,因此我想解决一些问题.您必须准备好应对连接断开的情况.如果您不这样做,汽车最终可能会继续做它正在做的最后一件事,直到再次获得连接为止.如果它做的最后一件事是继续全速行驶,那么您将无法在不追赶赛车的情况下重新建立连接.我想到了几种解决方案来解决这个问题.我见过的许多蓝牙接收器上都有一条连接到LED的附加线,该线会更改其闪烁顺序,以指示蓝牙接收器是否已连接.如果表明蓝牙连接已断开,则可以连接至此并将汽车置于空档状态.另一种方法(我将使用的一种方法)是跟踪自收到指令以来已经有多长时间了.如果经过了一定的时间并且没有收到任何指示,请停止汽车.
millis()
函数返回自Arduino启动以来经过的时间(以毫秒为单位).每次收到指令时,我们都可以保存此函数返回的值,并将其用作参考,以了解自收到指令以来已经有多长时间了.每次执行主循环时,都会再次调用millis()
函数并将其与保存的值进行比较.这两个值之间的差是自上一条指令以来经过的时间量.一旦差异超过一定数量,我们就知道收到指示已经有一段时间了.汽车将处于空档状态.如果在此情况发生之前收到指令,则保存的值将被更新,因此保存的时间值与当前时间值之差将不足以达到到期时间.
解析命令
解析很容易,因为所有命令都是在看起来像一个休息请求的情况下发送的. REST类请求中路径的每个段都与一个方法相关联.每次接收到一条指令时,它都会传递给" ProcessCommand(string)",该指令将解析出第一段,并使用它来决定接下来要调用的方法.现在,所有指令都将以/car/
开头.处理调用堆栈中的根级别方法仅检查指令是否以这种方式启动,如果命令是其他指令,则不执行任何操作.
void ProcessCommand(String command)
{
int startPosition, endPosition;
String segment;
startPosition = command.indexOf('/', 0);
endPosition = command.indexOf('/', startPosition+1);
if((startPosition>-1) && (endPosition == -1))
{
segment = command.substring(startPosition + 1, endPosition - startPosition+1);
startPosition = endPosition + 1;
}
if(segment == "car")
ProcessCarCommand(command.substring(endPosition));
}
ProcessCarCommand()方法与此类似.正在测试以查看指令的下一部分是"油门"还是"转向",并分别调用了" ProcessThrottle()“或” ProcessSteering()“方法.
void ProcessCarCommand(String command)
{
int startPosition, endPosition;
String segment;
startPosition = command.indexOf('/', 0);
endPosition = command.indexOf('/', startPosition+1);
if((startPosition>-1) && (endPosition>-1))
{
segment = command.substring(startPosition + 1, endPosition - startPosition);//.
}
if(segment=="throttle")
ProcessThrottleCommand(command.substring(endPosition));
else if (segment=="steering")
ProcessSteeringCommand(command.substring(endPosition));
}
最终的方法是到达橡胶与道路的交汇处.他们正在控制转向和油门线.它们的实现几乎相同.它们将数字值从命令末尾去除(将在-1和1的值之间).油门的值1表示正向全油门,-1表示反向全油门,0表示空档.同样,转向的-1为左全角,1为右全角,而0则为正前方.在将这些值发送到Servo对象之前,将对其进行缩放和移动.需要将-1到+1的连续值范围重新映射为0到180的值.Arduino函数库已经包含用于执行此适当映射的函数.它需要5个参数.第一个是要重新映射的值.接下来的两个是该值的下限和上限范围.第四个和第五个是需要将值映射到的范围.对于我们的特定情况,调用将类似于map
(value,-1,1,0,180).然后将"映射"的值写入转向或油门的伺服对象.这两个函数的代码如下.
float StringToFloat(String str)
{
char buf[str.length()];
str.toCharArray(buf, str.length());
float retVal = atof(buf);
return retVal;
}
void ProcessThrottleCommand(String command)
{
if(command.length() < 2) return;
float newthrottlevalue=StringToFloat(command.substring(1));
if ((newthrottlevalue < -1)&&(newThrottleValue > 1))
return;
int servoValue =(int) map(newThrottleValue,-1,1,0,180);
throttleESC.write( servoValue );
}
void ProcessSteeringCommand(String command)
{
Serial.println("Processing Steering Command");
if(command.length() < 2) return;
float newsteeringvalue=StringToFloat(command.substring(1));
if((newsteeringvalue < -1) && (newSteeringValue > 1))
return;
int servoValue = map(newSteeringValue,-1,1,0,180);
steeringServo.write( servoValue );
}
控制介面
已经介绍了向汽车发送控制信号并解释汽车并对其采取行动所需的所有代码.我跳过了讨论用于控制汽车的用户界面.我见过的其他一些电话控制系统都使用加速度计进行控制.这是个人喜好问题,但是我不喜欢基于加速度计的方法.在我的手机上玩游戏时,首选的控制方法是虚拟操纵杆,该操纵杆的盲区位于手指接触到的任何地方,并与屏幕接触.我最喜欢的控制方法是物理控制器. 我以用户控件的形式为手机制作了一个非常简单的虚拟游戏杆.当某个人首先将其手指放在控件上时,该点将成为死区的新中心点.滑动手指将虚拟操纵杆移出盲区并注册为方向.将手指从控件上移开会将虚拟操纵杆设置回其中立位置.
次优并不坏,但我们可以做得更好.我有一个与Windows Phone兼容的Moga蓝牙游戏控制器.因此,我也增加了对此的支持.通常,我会将代码组织起来,以便有多个逻辑操纵杆(无论是物理操纵杆还是虚拟操纵杆),然后是一些用于确定将听哪个操纵杆的机制.为了简单起见,我在本文中避免使用该组织.来自Moga的代码处理输入与虚拟操纵杆属于同一类,并且如果有人决定同时使用触摸屏和游戏控制器,则绝对没有实现任何解决方案.
Moga网站上有一个SDK,其中包含一个程序集(Moga.Windows.Phone),可简化与Moga控制器的交互.与控制器的交互有两种模式,即轮询和监听.在轮询模式下,每次代码想要知道控制器的状态时,都需要查询控制器.在侦听模式下,随着控制器状态的更改,您的代码上的方法将通过一个界面进行调用,以指示发生了什么更改.基于Xaml的程序通常将希望使用侦听模式,而基于DirectX的程序将使用轮询.为了使用监听模式,我的VirtualJoyStick类实现了IControllerListener接口.可以在[Moga]上找到有关Moga系列控制器的更多开发信息.(http://www.mogaanywhere.com/developers/) 还记得我之前说过的关于应对电话和汽车之间的潜在连接丢失的说法吗?这也适用于电话和游戏控制器之间的连接.如果与控制器的连接断开(电池没电或用户离开电话),则需要准备电话上的代码以开始传输空档状态.否则,电话将继续发送所指示游戏控制器的最新已知状态.为此,使用了IControllerListener事件之一,即OnStateEvent(StateEvent e).
public void OnStateEvent(StateEvent e)
{
if (e.StateKey == ControllerState.Connection)
{
if(e.StateValue == ControllerResult.Disconnected)
{
SetNeutralState();
}
}
}
我决定将左拇指杆用于转向,将右拇指杆用于油门.我一起忽略了所有按钮.
public void OnMotionEvent(MotionEvent e)
{
this.Dispatcher.BeginInvoke(() => {
if(e.Axis==Axis.Z)
{
Y = e.AxisValue;
OnVirtualJoystickUpdated();
}
else if(e.Axis == Axis.X)
{
X = e.AxisValue;
OnVirtualJoystickUpdated();
}
});
}
连接松动
我几乎没有说过成品的物理组装.在某种程度上,我这样做是因为这不是我的最终设计.根据您所拥有的汽车,您也许可以将Arduino固定在汽车上.但是,您不会想要使用松散的电线将伺服器和ESC连接到Arduino;仅有一点点长刺,电线就会松动.而是去当地的电子商店或其他供应商,看看有哪些原型屏蔽可用.使用其中之一,您可以通过焊接或螺钉将连接固定在适当的位置.
下一步是什么
编写额外的代码以使汽车可以由Android手机控制并不费力.相同的概念继续存在.如果时间允许,我可能会更新本文或添加另一篇文章,以解释需要做什么才能用Android手机控制汽车.另外请记住,我的个人目标是使汽车更具自主性.这意味着汽车必须能够了解其环境和位置.在下一个更新中,我计划遍历将安装到汽车上的各种传感器以及如何使用它们.其中包括GPS,距离传感器和运动传感器,例如加速度计和陀螺仪/速率传感器.由于蓝牙收音机的距离范围较小,因此我将使用其他收音机(Wi-Fi),以便可以在手机和汽车之间放置更多距离.我可能最终会增加GSM无线电.
修订记录
- 2014年2月12日 RCCarCode.zip(2.36 mb) [alt下载站点](http://www.codeproject.com/https://skydrive.live.com/redir.aspx?cid=2a0d66a555ad369a&page=self&resid=2A0D66A555AD369A%2112131&parid=2A0D66A555AD369A%213635&authkey=%21Akt58USs-hRA .SkyDrive&Bsrc =Share)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C C# Windows HTML Dev Intermediate Arduino Bluetooth VS2013 新闻 翻译