[原创] 深入浅出解读BBC Micro:bit - Runtime(1)
1126 查看
5 回复
 楼主 | 发布于 2019-06-26 | 只看楼主
分享到:

上回我们了解了micro:bit的软件生态,还记得那张软件关系的分层图么? 

今天我们就来看看micro:bit runtime是如何工作的。 


请注意,我们今天关心的问题不是基于最顶层的高级语言的,而是看看在runtime基础上采用C/C++来开发程序的基本问题。runtime包含有与micro:bit所有硬件相关的驱动,同时提供了运行机制来确保micro:bit的编程变得简单和具有柔性。runtime包含有从LED阵列显示的控制到端对端无线通信以及安全蓝牙低功耗服务,这些驱动、机制、类型被集中包在microbit-dal目录下成为runtime的基础。micro:bit runtime是建立在ARM mbed和nrf51平台上的。 



编译开发环境的准备 

开始runtime的C/C++编程之前,我们需要有基本的开发环境,开发者可以使用在线开发和离线开发两种方式。 

在线开发以基于Web基础的ARMmbed提供的C/C++集成开发环境为主。BBC microbit是ARMmbed官方免费支持的平台之一。在线集成开发环境基于mbed SDK提供了一个简单的编写、编译和分享工程的交互界面。使用很简单,登陆ide.mbed.com/complier然后就可以开始了。 

离线开发是基于Yotta来实现的。在Windows下安装yotta有一系列步骤包含: 

1.安装python(版本要求高于2.7.9) 

2.安装CMake 

3.安装 Ninja并添加Ninja到你的路径 

4.安装arm-non-eabi-gcc交叉编译环境,这里需要注意,ARM网站上有各个时间节点发布的gcc编译环境安装程序,最新的已经到到eabi-8-2018-q4,然鹅,我在采用这个版本的GCC运行dgb时会报错(64bit address out of Intel hex file),最终选择的客运型版本为gcc-arm-none-eabi-6-2017-q1-update-win32。 

5.最后在cmd窗口下运行 pip install –U yotta来安装yotta 



Runtime的基本理论-组件为基础的设计 


不论何时,计算机科学家们攻克大型编程问题时,通常采用分割大问题为彼此相互独立小问题来各个击破的解决。microbit runtime也不例外,兰卡斯特大学团队将runtime分解为众多小的组件。每一个组件解决一个在microbit上的特定工作。 

组件化的软件设计方法为软件代码的维护提供了很好的帮助。比方说,一个名为MicroBitDisplay的组件用来控制microbit的LEDs,让编程者可以显示图像、动画和消息。而MicroBitIO组件则通过microbit低边的扩展引脚控制应用处理器的输入和输出。microbit runtime是面向对象的,每一组件都是一个C++类。在runtime中包含有超多30个组件(类),开发者可以通过Lancaster-university.GitHub.io来获得相关文档。 

为使runtime尽可能地易用,有一个将最常用组件集合起来的对象被称为uBit(u在希腊语中叫谬,在计量单位中u常被用来指代micro)。其实对象是Microbit,uBit是Microbit对象的一个实例。那么uBit对象包含有那些组建呢? 

uBit { 
  .i2c
  .storage
  .serial
  .MessageBus
  .buttonA
  .buttonB
  .buttonAB
  .display
  .accelerometer
  .compass
  .thermometer
  .io
  .radio




编程时要使用起来也很方便,比方说要让microbit显示“Hello!”,你只需要简单的写下面的一行代码就可以了: 

uBit.display.scroll(“Hello!”); 



类似的,如果我们想要通过串口发送信息,可以用 uBit.serial.send(“Hello World!”); 





Runtime含有调度机制,采用轻量的线程(称为fibers)来控制执行的比率。为了令当前fiber进入低功耗睡眠模式可以用: 


uBit.sleep(X);//X是整型变量,单位为ms 



大家可能会很关心uBit.radio组件,起初我以为这就是蓝牙的组件,结果发现uBit包含的radio其实不是蓝牙而是nRF51822的2.4G私有协议的无线组件,蓝牙的组件单独在另一个对象里。这里我还是简单把uBit.radio组件展开来看一下。 


UBit.radio组件提供了一个十分简便、柔性的广播通道。你可以从一个microbit发送任何信息给到附近的其他microbit。该组件的核心是基于硬件本身的“私有”协议。所以当你发送任何数据时,在协议传输过程中不会添加任何用以判明身份的信息,所有的器件都是一样的。因此如果你要想让自己被辨识,你需要在发送的数据中添加你的身份信息。 


比方说我希望通过uBit.radio在一个microbit作为控制器发送控制指令,另一个作为接收器接收指令后对电机进行相应操作,那我的代码应该是这样子的: 


#include "MicroBit.h" 

MicroBit uBit;  


int main()  

{  

uBit.init();  

uBit.radio.enable();  

while(1) 

 {  

if (uBit.buttonA.isPressed())  

uBit.radio.datagram.send("1");  

else if (uBit.buttonB.isPressed())  

uBit.radio.datagram.send("2");  

uBit.sleep(100);  

}  

舵机这边的代码是这样的: 

#include "MicroBit.h" 

MicroBit uBit;  

void onData(MicroBitEvent e)  

{  

ManagedString s = uBit.radio.datagram.recv();  

if (s == "1")  

{  

uBit.io.P0.setServoValue(0);  

uBit.display.print("A");  

}  

if (s == "2")  

{  

uBit.io.P0.setServoValue(180);  

uBit.display.print("B");  

 }  

int main()  

{  

uBit.init();  


uBit.messageBus.listen(MICROBIT_ID_RADIO, MICROBIT_RADIO_EVT_DATAGRAM, onData); 

uBit.radio.enable();  


while(1)  

uBit.sleep(1000);  




Events 

处理器执行代码是顺序执行 -- 一行接一行,依照你在程序中设计的逻辑顺序。但有时,我们也希望知晓某些事件是否正在发生,同时希望写代码来决定在这样的情况下应该做怎样的处理。 

比方说,可能你想要直到按键是否被按下,你的microbit是否被晃动,或者是否有数据通过器件的无线通路发送给你。为了处理这一类情况,我们生成了MicroBitEvent。 

生成Events 

许多组件会在相关情形发生时触发事件。比方,MicroBitAccelerometer在microbit被摇动或者在自由落体时触发事件,MicroBitButton会在按键被按下、弹起、点击和长按时触发事件。程序员也可以自由的随时将他们认为有用的情况触发事件。MicroBitEvent很简单,只包含两个数字: 

Source - 用来判明生成该事件的组件 

Value - 与来源相对应的特定数字用来识别事件 

比方说,在按键相关的说明文档中source MICROBIT_ID_BUTTON_A具有数值“1”,而在按键被点击时一个名为MICROBIT_BUTTON_EVT_CLICK具有数值“3”的事件被触发了。 

生成事件很简单-- 只需要创建一个具有你需要的source和value的MicroBitEvent,剩下就是runtime来负责处理了: 

MicroBitEvent(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK); 

开发者可以随便生成他们自己的事件,只需要注意避免采用任何已经在runtime中使用过的source ID就好。 

Detecting Events 

Microbit runtime有一个组件叫做MicroBitMessageBus,该组件用来记住哪些事件是你的程序关心的,同时在事件发生时将MicroBitEvent送到你的程序中。 

当事件发生时,你需要在程序中生成一个函数,然后告诉message Bus某一个事件发生时你希望这个函数被执行。这就是所谓事件句柄。 

你的事件句柄通过MicroBitMessageBus listen函数来实现的。 

void onButtonA(MicroBitEvent e)  

{  

uBit.display.print("A");  

}  

int main()  

{  

uBit.messageBus.listen(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK, onButtonA);  

上面的代码表明,无论何时通过MICROBIT_ID_BUTTON_A(source)触发的MICROBIT_BUTTON_EVT_CLICK事件发生,你的函数‘onButtonA’都将自动被执行。 

你可以不断地监听并在你认为有用的每一个事件发生时触发函数。事实上在Microsoft Block中的下面的block图直接翻译成C后就是上面我们示例的代码。 

 

Wildcard Events 

还有些时候,我们希望捕获由特定组件产生的全部事件。比方说,你或许希望在按键发生任何变化时都被通知到。在这类情形下,有一个叫做“MICROBIT_EVT_ANY”的特殊的事件值可以派上用场。如果你用这个值调用listen函数,那么由给定source组件触发的任何事件都将被传送到你的函数中。 

你也能通过查看传递给你函数的MicroBitEvent找出具体的事件。 

比方说,你可以写一个如下的代码: 

Void onButtonA(MicroBitEvent e) 

If (e.value == MICROBIT_BUTTON_EVT_CLICK) 

uBit.display.scroll(“CLICK”); 

If (e.value == MICROBIT_BUTTON_EVT_DOWN) 

uBit.display.scroll(“DOWN”); 

int main() 

uBit.messageBus.listen(MICROBIT_ID_BUTTON_A, MICROBIT_EVT_ANY, onButtonA); 

更进一步,如果你实在想要在任何事件发生时都知晓,runtime提供了一个source值为MICROBIT_ID_ANY,它允许你的函数在任何组件产生的任何事件发生时被触发。 

下面的代码将在任何事件发生时触发onEvent的函数运行: 

Void onEvent(MicroBitEvent e) 

uBit.display.scroll(“SOMETHING HAPPENED”); 

Int main() 

UBit.messageBus.listen(MICROBIT_ID_ANY, MICROBIT_EVT_ANY, onEvent); 

 

Queued Events 

当你写一个事件句柄时,你的函数将在每一次相关事件发生时被调用。但是如果你的句柄要很长时间来执行的话会发生什么情况呢? 

上面的示例中无论任何事件将触发滚动显示“SOMETHING HAPPENED” …..但是滚动显示会需要数秒才能完成! 

如果显示尚未完成前,另一个事件又发生了那程序该怎么做呢?默认情况下,runtime会将为你的事件句柄创建队列直到队列的事件对应的句柄全部运行完毕。 

一旦你的句柄完成了此次事件,下一个会被执行(其他事件的句柄并不会受到影响 -- 因为一个事件句柄繁忙并不意味着另一个句柄将不能收到它的事件!) 

Runtime也允许你按照你想要的方式改变这个行为,具体方法在MicroBitMessageBus中会有描述。 

Concurrency 

希望代码能在同一时间处理多项任务这类需求在编程时并不少见。比方说,在LED阵列滚动显示消息时会占用很多时间,如果恰好这时其他事件触发而你希望在这时去处理该事件? 

同一时间同时处理多个事件的程序叫做concurrent并发程序。 

Microbit runtime提供了两种途径来实现并发: 

  • 那些会占用较多时间完成的函数(比如 display.scroll)通常具有一个“Async”异步的版本(比方display.scrollAsync) 

这些函数与它们的对应函数具有完全相同的功能,但是它不会等到函数全部执行完毕才允许用户代码执行。 

相反,只要函数被调用,用户程序继续执行(原任务在背景下运行同时可以运行其他任务) 

  • 用户也可以采用runtime的fiber scheduler调度器。这将允许你在背景中运行你的代码,同时在各部分代码之间按需分享处理器资源。 

事实上,无论何时你写一个事件句柄,runtime通常都会在背景中以这样的方式运行,从而降低你的代码对于其余代码的影响。 

  • 调度器采用non-preemptive scheduler非先发式的调度。这意味着runtime将不会剥夺你的程序控制权 -- 它会等待它被一个runtime 函数blocking阻塞调用后才接管控制。 

所有具有blocking阻塞功能的函数都在文档中说明。你可以在任何时间生成fibers。 

Fibers是runtime用来异步运行的轻量线程。在main函数末尾建议调用release_fiber();来释放main fiber,同时将控制权交由调度器来保证其他fibers代码的运行。如果没有其他进程运行的话它也同时意味着处理器进入睡眠模式。如果这句release代码隐去,你的程序将停止所有在运行的进程。 

<未完待续> 

(0 ) (0 )
回复 举报

回复于 2019-06-26 沙发

感谢分享
(0 )
评论 (0) 举报

回复于 2019-06-26 2#

谢谢分享!!!
(0 )
评论 (0) 举报

回复于 2019-06-26 3#

谢谢分享
(0 )
评论 (0) 举报

回复于 2019-06-30 4#

支持下,谢谢分享!
(0 )
评论 (0) 举报

回复于 2019-07-03 5#

感谢分享
(0 )
评论 (0) 举报
  • 发表回复
    0/3000





    举报

    请选择举报类别

    • 广告垃圾
    • 违规内容
    • 恶意灌水
    • 重复发帖

    全部板块

    返回顶部