.NET Micro Framework

嵌入式应用程序中的 Microsoft.NET Framework

Colin Miller

下载代码示例

自二十世纪八十年代初期推出了第一个上下文相关的 C 代码编辑器以来,世界的变化日新月异。最初为桌面应用开发的工具和语言已扩展为将服务器、云和其他环境涵盖在内。Microsoft .NET Framework 和 Visual Studio 提供的先进工具极大地提高了程序员的效率。.NET Framework 工具和语言提供了丰富的库和协作工具,并防止程序员出现最常见的错误。

然而,这些改进尚不可供嵌入式设备的开发人员使用。嵌入式工具的市场规模不足以吸引到像 PC、服务器和云应用程序市场那样的投资。各种各样的“智能设备”的出现正在改变着这种不平衡现象。

借助于 .NET Micro Framework,已开发一系列新产品,其中包括过去常常需要不同的嵌入技能集来开发代码的小型设备。而这些已由拥有 .NET 技能的个人和团队进行了开发。如果您是 .NET 开发人员并设想过如何在您的应用程序中利用小型嵌入式设备,那么希望这篇文章能够使您确信可以使用自己的技能参与到物联网 (IoT) 和智能设备的开发浪潮中。

为什么需要关注?一份最新分析估计,在预计于 2017 年供货的处理器中,94% 的处理器的容量无法支持传统的 .NET 应用程序环境。通过 .NET Micro Framework,您可以扩大覆盖面。

.NET Micro Framework 最初是在 SPOT 手表项目中开发的。在过去七年里,其已成为 .NET 系列的一部分。它运行在 32 位处理器上,该处理器太小以至于无法支持完整的操作系统。实际上,它是在可执行映像中集成了执行引擎和类型系统的硬件上直接运行的 .NET。

.NET Micro Framework 旨在帮助 .NET 开发人员针对嵌入式应用程序创建运行在基于 MCU 的小型 32 位设备上的应用程序。在 2014 年的 Build 大会上,Microsoft 所述的操作系统平台范围包括针对高端领域的带 Windows Embedded Standard 的 IoT 空间和针对小型设备领域的 .NET Micro Framework。

嵌入式开发

智能设备具有本地计算功能,可以与其他设备和潜在云进行通信。在本文中,我创建了一个小型的移动机器人,可以从运行 Windows Phone 操作系统的智能手机进行控制。目的在于向您演示用于 Windows Phone 应用的 .NET 技能是如何同样适用于机器人的。工具和语言支持是这一环境中的新增功能,而且便于在二者之间来回工作的开发环境是无缝的。

遇见 Bert — Bert 是一个具有四个动力轮的移动机器人(请参见图 1)。他包含一个由 Secret Labs 提供的价格大约为 35 美元的 Netduino 2 主板。简单控制器可用于控制机器人每一侧的电机以便其前进和后退。但是,这里没有速度控件。

机器人 Bert
图 1 机器人 Bert

对于通信,Bert 上有一个通过串行接口与 Netduino 通信的蓝牙模块。若要确保您不会使 Bert 撞到东西或让他从高处掉落,在其前后部还配备了红外接近传感器。此外,为了增添些乐趣,他还配备了照明灯和方向灯。

这个简单的 Windows Phone 应用源自 Build 大会上的演示。它连接到正确的设备并使用板载加速计引导 Bert 绕过障碍(通过蓝牙发送命令)。您可以看到如图 2 所示的界面以及如图 3 所示的下拉选择列表。选择 Enumerate 选取要向其发送命令的已连接设备。这些命令以具有方向的字母形式显示在屏幕上。             

Windows Phone 应用界面
图 2 Windows Phone 应用界面

设备选择下拉列表
图 3 设备选择下拉列表

您可以使用 .NET Micro Framework 移植工具包将 .NET Micro Framework 移植到另一个硬件平台 (bit.ly/1wp57qm)。使用该工具包将低级别接口写入您将在其上运行的特定硬件。这更接近于传统嵌入式编程活动。值得高兴的是,供应商已经为您这样做了。此应用程序中使用的 Netduino 主板就是个很好的例子,其在 Netduino SDK (bit.ly/1CQygzz) 中包含了编码到该主板所需的一切内容。

Netduino 是来自 STMicro 的 Cortex-M3 处理器,具有 192KB 到 384KB 的闪存和 60KB 到 100KB 的 RAM,并与 Arduino 防护罩引脚兼容。您可以在 GHI Electronics、Mountaineer、Sparkfun、Adafruit、Amazon 和其他商店找到其他支持 .NET Micro Framework 的设备。

主板也是开源硬件,因此,一旦您编写了自己的应用程序并想使其商业化,您的选择有很多。对于小批量应用程序,您可以只使用 Netduino 主板,但如果您想要大批量定制或有特定要求(如想在同一 PCB 上集成其他电子设备),则可将开源设计文件带给主板制造商并从那里继续。

因为一开始使用的是 Netduino 主板,所以我安装了 Netduino SDK。其中包括 .NET Micro Framework SDK,它带有 Visual Studio 模板和所有必需的库支持。对于其他开发主板,可能需要单独安装 .NET Micro Framework SDK。SDK 附带一个仿真程序,因此无需获取可与 .NET Micro Framework 一起使用的任何硬件,尽管在您已经知道想要使用哪些传感器的情况下,使用硬件效率会更高。

创建嵌入式应用程序

安装 Netduino SDK(其中包含 .NET Micro Framework SDK)后,启动 .NET Micro Framework 嵌入式应用程序与在 Visual Studio 内启动任何其他项目一样。您选择“新建项目”和想要启动的应用程序类型。如图 4 中所示,我选择了 Netduino 2 应用程序以匹配 Bert 上的主板。

启动 .NET Micro Framework 嵌入式应用程序
图 4 启动 .NET Micro Framework 嵌入式应用程序

启动了项目后,您可以直接跳转到创建机器人和手机之间的通信链接。

与许多嵌入式应用程序一样,Bert 使用电池运行。这意味着您编写代码时必须考虑到节约用电。值得高兴的是,.NET Framework 和 .NET Micro Framework 非常适合实现此目标。如果没有要运行的线程,.NET Micro Framework 可以自动将处理器置于低功率状态。此外,添加第二个有效处理器来监视中断并将主处理器置于更低的功率状态正成为一种常见做法。您很快会看到 .NET Framework 是否非常适合解决这种类型的编程。

此应用程序中的代码将运行以响应外部事件,然后只对需要响应的事件进行响应。因此,您会看到所有执行都驻留在一个事件处理程序中或在大多数情况下挂起的线程中,如下所示:

public class Program
  {
    public static void Main()
    {
      RunRobot myRunRobot = new RunRobot();
      myRunRobot.Start();
      while (true)
      {
        Thread.Sleep(Timeout.Infinite);
      }
    }
  }

RunRobot 构造函数初始化 IO 实例(我将详细介绍),RunRobot.Start 方法初始化触发实际执行的中断:

public void Start()
  {
  // Enable interrupts on BT input on the HC-06 radio
  hc06.DataReceived += hc06_DataReceived;
  // Use onboard button to turn on/off accepting commands
  button = new InterruptPort(Pins.ONBOARD_SW1, false, Port.ResistorMode.Disabled,
    Port.InterruptMode.InterruptEdgeHigh);
  // Start monitoring for button presses
  button.OnInterrupt += new NativeEventHandler(button_OnInterrupt);
  }

让蓝牙发挥作用

有三个步骤,可实现 Bert 和 Windows Phone 应用之间的蓝牙通信:

  1. 绑定机器人上的蓝牙模块
  2. 从手机应用发送命令
  3. 在 Bert 上接收这些命令

由于 Netduino 与 Arduino 兼容,因此,对于许多连接而言,您可能能够找到一个可以插入的防护罩,以便为您实现所有必要的连接。Arduino 是一个受业余爱好者欢迎的平台,它具有丰富的外围设备体系。您可以从现有模块构建出一系列解决方案。

如果不想在接线上花费时间,您可以了解下 GHI Electronics 的 Gadgeteer 产品。在这些主板和组件上,您可以使用向导确定将组件插入标准连接的位置(请参见图 5),因此您无需任何接线连接知识。模块接口具有同样高的水平。与 Arduino 类似,Gadgeteer 有一套丰富的现有模块,可用于创建您的解决方案。连接了模块后,您仍要在 .NET Micro Framework 中进行编码。这一点对于快速成型您的想法特别有用。

显示连接的 Gadgeteer 向导
图 5 显示连接的 Gadgeteer 向导

对于 Bert,我没有使用 Gadgeteer,但收集了我自己的模块。蓝牙模块 (HC-06) 使用具有四个连接(VIN、GND、RX 和 TX)的串行接口。将 VIN(输入电压)连接到 Netduino 的 5 V 或 3.3 V 输出,然后将 GND 连接到其中一个 GND 连接。查看图 6 中 Netduino 的 I/O 表,您会看到将 BT 模块 RX(接收)连接到 Netduino TX (传输),将 BT 模块 TX 连接到 COM1 串行端口的 Netduino RX。如果需要,有大量将这些模块连接到 Web 上常见微控制器的示例。

定义的 Netduino IO 引脚
图 6 定义的 Netduino IO 引脚

完成这些连接后,可为 Netduino(其为 BT 模块供电)通电,并将设备与手机配对。BT 模块的默认名称是 HC-06,因此可与其配对。我使用 AT 命令将该名称更改为 Bert。发送其中包含“AT+NAMEBert”字符的字节数组以更改 BT 模块的名称。您还可以使用此接口配置蓝牙模块,其中包括诸如通信波特率等。

现在,可以像与任何其他 BT 设备连接一样使用手机连接到 Bert,并开始发送命令。首先,我访问板载加速计,设置合理的报告间隔,以免 Bert 反应不佳:

accelerometer = Accelerometer.GetDefault();
if (accelerometer != null)
{
  uint minReportInterval = accelerometer.MinimumReportInterval;
  desiredReportInterval = minReportInterval > 250 ? minReportInterval : 250; 
    // Slowing this way down to prevent flooding with commands
  accelerometer.ReportInterval = desiredReportInterval;
    // Add event for accelerometer readings
  accelerometer.ReadingChanged += new TypedEventHandler<Accelerometer,
    AccelerometerReadingChangedEventArgs>(ReadingChanged);
}

ReadingChanged 事件处理程序将发送命令,如图 7 中所示。

图 7 将命令发送到 Bert 的事件处理程序

AccelerometerReading reading = args.Reading;
if (reading.AccelerationX > .4)  // These are arbitrary levels
                                 // set to what seemed easy
{
                        // Send Right
  if( cmdSent != 'R')   // Don’t resend the same command over and over
  {
      // Update the phone display
    txtRight.Text = "On";
    txtLeft.Text = " L";
    txtForward.Text = " F";
    txtBack.Text = " B";
      // Send the command
    packet = Encoding.UTF8.GetBytes("R");
    cmdSent = 'R';
    SendData(packet);
  }

接收和执行命令

现在,您可以考虑在机器人端接收命令。BT 模块被绑定到 Netduino 上的第一个串行端口。创建一个 SerialPort 实例和消息缓冲区,就像在其他版本的 .NET Framework 上那样:

SerialPort hc06;                 // The BT radio
byte[] cmdBuffer = new byte[32]; // The input buffer for receiving commands

然后,配置串行端口以与蓝牙模块进行通信(9600 波特是默认设置;我可以使用 AT 命令重置此值,但这将是非常稀疏的通信,以简化该程序,因此更快的速度是不必要的):

// Set up the Bluetooth radio communication
hc06 = new SerialPort(SerialPorts.COM1, 9600, 
  Parity.None, 8, StopBits.One);
hc06.ReadTimeout = 1000;
hc06.Open();

然后,我将设置触发应用内大部分处理的 DataReceived 事件处理程序。换言之,Bert 将处于睡眠状态,直到他收到命令。处理器将保持时间足够长的不休眠状态以将电机设为正确状态,而后返回睡眠状态:

// Enable interrupts on BT input
hc06.DataReceived += hc06_DataReceived;

传入命令在事件处理程序中进行处理,如图 8 中所示。

图 8 启动 Bert 移动的入站命令

do
{
  try
  {
    count = hc06.Read(cmdBuffer, 0, 1);  // Should read one byte at a time
  }
  catch (Exception) { }        // Just eat exceptions
  if (count > 0)
  {
    cmd = (char)cmdBuffer[0];
    executeCmd(cmd);   // Execute command immediately
  }
} while (count > 0);

该代码与您将在桌面上编写的内容没有任何不同。

调动 Bert

我使用的电机控制器只需我以特定逻辑状态设置四个输入来确定各个轮子的转向。逻辑是:

/*        
A2  A1  Motor 1         B2  B1  Motor 2
0   0   Shaft locked    0   0   Shaft locked
0   1   Clockwise       0   1   Clockwise
1   0   Anti-clockwise  1   0   Anti-clockwise
1   1   Shaft locked    1   1   Shaft locked 
*/

为了编写这些内容,我创建了 OutputPort 并将其连接到 Netduino 数字引脚之一,如图 6 所示。OutputPort 是 .NET Framework 中的唯一类,因为运行完整 .NET Framework 的设备通常不能解决单个数字 I/O 端口。.NET Micro Framework 中有少量新类,可用于处理此应用程序领域(如电源管理)所特有的其他 I/O 类型和其他问题:

// Motor controller output
A1 = new OutputPort(Pins.GPIO_PIN_D2, false);  
  // A is the left tires when viewed from the front
A2 = new OutputPort(Pins.GPIO_PIN_D3, false);
B1 = new OutputPort(Pins.GPIO_PIN_D4, false);  
  // B is the right tires when viewed from the front
B2 = new OutputPort(Pins.GPIO_PIN_D5, false);
Setting them is as simple as writing true or false:
switch (cmd)
{
  case 'F':
  {
    Debug.Print("Forward");
    A1.Write(false);   // Left forward = false/true
    A2.Write(true);
    B1.Write(true);    // Right forward = true/false
    B2.Write(false);
    break;
  }

谨慎驱动

考虑到 Bert 自身及其周围人员和事物的安全,我不希望他出事,因此我在他的前后部安装了来自 Sharp 的红外接近传感器。这些传感器返回的值介于 0.0 到 1.0 之间,具体取决于与反射投射红外线的最近物体之间的距离。

前面的传感器沿对角线向下感知 Bert 前方 18 英寸左右的地面。这使得我可以使用一个传感器来探测物体,例如,如果它正在接近楼梯,地面会消失。这些传感器返回的值是模拟的,这意味着我在 .NET Framework 中创建了对于 .NET Micro Framework 唯一的另一个类的实例 — AnalogInput 类:

AnalogInput foreRange;    // Forward range finder input
AnalogInput backRange;    // Rear range finder input
// Set up range finder for collision and edge avoidance
foreRange = new AnalogInput(Cpu.AnalogChannel.ANALOG_5, 100, 0, -1);
backRange = new AnalogInput(Cpu.AnalogChannel.ANALOG_4, 100, 0, -1);

此构造函数将识别我连接到的特定模拟端口、我可以用来将输出转换到更方便范围内的乘数、可用于修改输出以方便使用的偏移量,以及为 -1 的输出精度(表示最大精度)。为了定期获取这些度量并尽可能多地让处理器进入睡眠,这些读数会由计时器来触发。当我开始接收来自手机的命令时进行设置,而在我不接收时弃之不理:

// Start timer for rangeFinders
Timer rangeReader = new Timer(new TimerCallback(rangeTimerHandler), 
  null, 0, 100);

通用中断

我需要引入的最后一个新类是 InterruptPort 类。这允许我监视数字输入中的更改,并在更改发生初期(例如从低到高)触发。我将该类用于 Netduino 主板上的按钮,以使 Bert 对来自手机的命令作出响应。这意味着他会忽略命令直至该按钮按下,并在该按钮再次按下后忽略命令:

// Use onboard button to turn on/off accepting commands
button = new InterruptPort(Pins.ONBOARD_SW1, false,
  Port.ResistorMode.Disabled,
  Port.InterruptMode.InterruptEdgeHigh);

Pins.ONBOARD_SW1 是 Netduino 主板上用于开关的内部连接。您可以将开关连接至图 6 列出的任何一个数字端口。第二个参数是我是否需要对此开关输入应用故障筛选器,以避免对单个按钮按压出现多个触发。

下一个参数是开关上是否存在上拉或下拉电阻器。这将定义按钮未按下时的默认端口位置(高或低)。将下一个参数设置为触发转换器上的中断返回“高”,以便您的手指在 Bert 突然启动后能移开。使用创建的 InterruptPort 实例,我可以分配中断处理程序:

// Start monitoring for button presses
button.OnInterrupt += new NativeEventHandler(button_OnInterrupt);

中断处理程序将启动 Bert 监视来自手机的任何命令,如图 9 中所示。

图 9 中断处理程序激活命令监视

private void button_OnInterrupt(uint data1, uint data2, DateTime time)
{
  cmdListenState = !cmdListenState;
  if (cmdListenState)
  {
    // Set up timer for rangeFinders
    rangeReader = new Timer(new TimerCallback(rangeTimerHandler), 
      null, 0, 100);
  }
  else
  {
    rangeReader.Dispose(); // Drop the timer because Bert isn't processing
                          // commands so he shouldn't be moving
  }
  led.Write(cmdListenState);  // The onboard LED indicates whether
                               // Bert is accepting commands
}

嵌入式应用程序和 .NET Micro Framework

嵌入式应用程序通常在硬件上执行构建的一组目标功能以满足最低要求。构建具有针对性和优化的嵌入式应用程序需要更多的初始投资。该投资通过更长的产品生命周期得到了补偿。

对于传统的嵌入式开发而言,最大的挑战之一是完成项目所花费的时间。有很大一部分嵌入式项目的放弃原因就是成本过大,或者是在上市之前要求发生了变化。

嵌入式应用程序的动态受许多因素的影响。当 Microsoft 七年前首次发布 .NET Micro Framework 时,大部分嵌入式应用程序基于 8 位处理器构建,以降低成本、达到所需性能要求以及满足电源要求(例如,在使用电池的基础上实现长时间运行)。

同时,嵌入领域中的大部分增长锁定了 32 位处理器,如 ARM7。这些处理器持续降价并能够为许多应用程序提供足够的性能,耗电量也得到了改善。较新的 Cortex 处理器正在不断吸引众多新的嵌入式应用程序加入到 32 位大家庭中。

最近,有更多的设备成为更广泛的应用程序不可或缺的组成部分,包括嵌入式设备、网关、云连接、数据库、业务智能应用程序等等。这些新的应用程序改变了嵌入式应用程序的经济状况。

遵循常见通信协议日渐重要。这些新设备更多是处于消费者领域,因此,期待长开发周期意味着产品风险增加。对于这些应用程序以及接下来的“智能设备”激增期间,上市时间至关重要。

其他对这些新类型应用程序的影响只有广度。它们可能会将桌面开发人员及其在大规模通信、数据库和业务分析以及云协议方面的专业知识与嵌入式开发人员及其在硬件和低级编程方面的专业知识结合在一起。这些工具和语言将有助于相同技能集跨整个应用程序工作,以使项目效率更高。

这就是 .NET Micro Framework 的开发背景。刚开始,此平台是作为开发 Microsoft SPOT 手表应用程序(仅领先于市场九年)的内部工具而创建的。当该平台对于其他内部项目变得非常有用后,此产品作为常规平台在 2007 年得到了完善。

成本、性能和电源效率等这些嵌入式应用程序驱动因素还未得到解决。如果您谈论的应用程序涉及到要在某个大厦中部署数以千计的传感器等,那么成本将变得尤为重要。性能和电源要求也会随着应用程序的不同而有所不同。持续不断地改进可以通过 .NET Micro Framework 达到的应用程序范围是 Microsoft 开发团队目前正在关注的领域。

部署和调试

将代码部署到您的设备很简单,进行连接并按 F5,就像使用 Windows Phone 应用一样。但是,安装程序稍有不同。在“项目|属性”对话框中,有一个 .NET Micro Framework 选项卡,您可以在其中指定传输(USB、串行或 TCP/IP)并识别设备。默认为 USB。它将自动识别 .NET Micro Framework 设备,因此除非您连接到了多台设备,否则可能永远不需要此对话框。此时,会生成解决方案并将程序集部署到设备。您可以在 Visual Studio 中的“输出”窗口中执行所有这些任务。执行立即开始,您可以开始调试设备上运行的代码。

您还可以将该应用部署到在台式计算机上运行的仿真程序。不同之处在于,Windows Phone 具有数量相对有限的排列,因此您可以从 Visual Studio 提供的一组选择中选取最接近的仿真程序。对于 .NET Micro Framework,有一个通用的 Microsoft 仿真程序。您很可能需要创建自己的仿真程序,以匹配您的硬件。这绝对值得您深入研究,如果您想要在硬件可用之前开始编码。

在过去,机器人和 Windows Phone 应用之间的通信调试方案可能已通过 JTAG 调试器进行。这独立于开发环境和 .NET 开发人员,因此,您可能需要让一名开发人员监视手机上发生的情况,而让另一名开发人员(以单独的工具)监视嵌入式设备上发生的情况。您可以在 Visual Studio 中同时对二者进行监视。您可以逐步执行手机代码并中断命令传输,然后了解 Bert 针对此命令会发生什么情况。

总结

若要详细介绍如何在 .NET Micro Framework 上创建任何重要的嵌入式应用程序,大概需要一整本书。本文的目的是为了向您演示,将您的 .NET 技术应用于嵌入式应用程序和 .NET Micro Framework 的学习过程不是很难。

此项目需要的几个类并未包含在完整的 .NET 库内。此外,会有一些您在完整的 .NET Framework 上已熟悉但却无法用于 .NET Micro Framework 的事项,但 Visual Studio 环境完全支持 IntelliSense、断点、监视点、调用堆栈和您所习惯的其他任何内容。对于其中一些嵌入式应用程序,.NET Micro Framework 并不是合适的解决方案。但是,对于大多数而言,其效果不错。我们正在努力继续扩大该平台的适用性,以从更大范围上覆盖计算机科学中不断增长且日益重要的部分。

因此,您现在可以作为嵌入式程序员尝试小项目了。

看看 Bert 的实际运行

现在,您已了解如何正确利用自己的开发技能寻求一些乐趣。您开发了一个应用,通过它,您可以使用运行 Windows Phone 操作系统的智能手机控制一个小机器人。开发 Windows Phone 应用所使用的 .NET 技能同样适用于机器人。您可以在二者之间无缝地来回工作。工作完成后,就可以看到 Bert 的实际运行了:youtu.be/ypuqTju2w4c


Colin Miller 是 Microsoft 的物联网团队的首席项目经理。您可以通过 colin.miller@microsoft.com 与他联系。他已在 Microsoft 工作了 20 年,从事物联网、嵌入式、开发人员工具、研究、MSN、Internet Explorer、Word 和其他项目方面的工作。

衷心感谢以下 Microsoft 技术专家对本文的审阅:Lorenzo Tessiore