目录
4.2 BlinkTimerM中主要函数功能流程图及文字说明
一、blink组件的功能
Blink组件的主要功能是控制LED灯的闪烁。通过调用命令函数和实现事件函数,我们可以控制LED灯的开关、亮度、闪烁频率和闪烁模式等。Blink组件的实现基于状态机,通过改变状态机的状态来控制LED灯的闪烁方式和频率。
具体来说,Blink组件包含了以下命令函数和事件函数:
命令函数:
void BlinkC.set(uint8_t val): 设置LED灯的亮度,val为亮度值,取值范围为0-255。
void BlinkC.on(void): 打开LED灯。
void BlinkC.off(void): 关闭LED灯。
void BlinkC.toggle(void): 切换LED灯的状态。
事件函数:
event void BlinkC.timer.fired(void):定时器事件函数,用于控制LED灯的闪烁频率和模式。
event void BlinkC.startDone(error_t err):启动事件函数,用于启动Blink组件。
event void BlinkC.stopDone(error_t err):停止事件函数,用于停止Blink组件。
通过调用这些命令函数和实现这些事件函数,我们可以实现各种LED灯的控制和闪烁效果。例如,我们可以通过调用set命令函数设置LED灯的亮度,通过调用timer.fired事件函数控制LED灯的闪烁频率和模式,从而实现各种闪烁效果。
总之,Blink组件的功能主要是控制LED灯的闪烁,通过调用命令函数和实现事件函数来实现LED灯的控制和闪烁效果。
二、Blink组件相关关系
TinyOS的编程方式采用nesc语言,这是一种类C语言,nesc语言有几个最重要概念:组件,接口,模块。如下图BlinkC程序组件图:
- 每一个程序都是由若干组件(component)组成;
- 组件有两种类型,一种是模块(module),另一种是配置(configuration);
- 配置文件的作用是表明组件之间的关系。模块文件的作用是将程序的具体实现放在其中;
- 每个程序都需要一个顶层的配置文件,它的名字是用程序名字命名。
从上图可以发现,BlinkC这个组件是由MainC,TimeMilliC,LedsC组件组成。
- 每个component都提供以及使用 interface;
- Component提供的interface说明了该组件为使用者提供的功能;
- Component使用的interface说明了该组件需要用到的由其他组件提供的功能。
- 接口常使用的关键词是command和event。
主要组件及接口的功能介绍:
MainC是blink组件的主要控制器,负责协调各个组件之间的通信和数据传输。
BlinkMilliC是一个定时器组件,用于控制数据采集和传输的时间间隔。
Leds组件用于控制传感器和无线模块的LED指示灯。
boot是一个启动组件,用于初始化blink组件和相关硬件设备。
Timer<TMilli>接口用于控制数据采集和传输的
从图1.1中,箭头的名字表示组件与组件之间访问使用的接口。下面我们结合程序来看下组件、接口、模块的概念。时间间隔。这些接口可以帮助用户构建高效、安全的无线传感网系统。
三、blink目录下三个文件及其源码
Blink目录下面包含Makefile、BlinkAppC.nc、BlinkC.nc三个文件。
BlinkAppC.nc
源码:
configurationBlinkAppC//表示这是一个名为BlinkAppC的配置
{
}
implementation
{//组件申明
componentsMainC,BlinkC,LedsC;
componentsnewTimerMilliC()asTimer0;
componentsnewTimerMilliC()asTimer1;
componentsnewTimerMilliC()asTimer2;
//组件间使用关系定义
BlinkC->MainC.Boot;
BlinkC.Timer0->Timer0;
BlinkC.Timer1->Timer1;
BlinkC.Timer2->Timer2;
BlinkC.Leds->LedsC;
}
在implementation关键字后面的括号内是配置的具体实现。components关键字后面表明了这个配置文件所引用的组件,在这里分别是Main、BlinkC、LedsC以及三个TimerMilliC组件。最后五行表明了各组件间的provider和user的关系。A->B表示了一种关系,其中A为使用方(user),而B为提供方(provider)。命令(command)就是接口提供方已经实现的函数。事件(event)就是需要接口使用方实现的函数。
BlinkC.nc
源码
module BlinkC @safe() //声明了使用到的接口名称,具体语法可以详细查看Nesc语法。
{
uses interface Timer<TMilli> as Timer0;
uses interface Timer<TMilli> as Timer1;
uses interface Timer<TMilli> as Timer2;
uses interface Leds;
uses interface Boot;
}
implementation
{
event void Boot.booted()
{
call Timer0.startPeriodic( 250 ); //每250ms触发一次event void Timer0.fired()
call Timer1.startPeriodic( 500 );
call Timer2.startPeriodic( 1000 );
}
event void Timer0.fired()
{
dbg(“BlinkC”, “Timer 0 fired @ %s.\n”, sim_time_string());
call Leds.led0Toggle(); //使Led状态反转
}
event void Timer1.fired()
{
dbg(“BlinkC”,“Timer 1 fired @ %s \n”, sim_time_string());
call Leds.led1Toggle();
}
event void Timer2.fired()
{
dbg(“BlinkC”, “Timer 2 fired @ %s.\n”, sim_time_string());
call Leds.led2Toggle();
}
}
注意这里面定义的接口我们可以从BlinkC组件图里面看到,箭头上的接口名称就是在这个区域定义的。
第一行内容表明这是一个名为BlinkC的module,而后括号的内容表明了该module使用的接口(interface)。[注意这个module没有提供接口(provide)]
由于使用方必须实现接口中的event函数。因此我们可以看到该文件中的implement中包含了初始化Boot.booted,以及三个timer计时的event函数的具体实现。在每个timer的触发event函数内容中写明了其需要触发的内容。详见示例程序。
在模块文件的具体实现里面涉及到了TinyOS执行模型,该模块使用了MainC组件的boot接口,以事件的响应的方式作为节点上电后的响应事件。在这里涉及到了接口中常见的用法:命令(command)和事件(event)。在BlinkC模块具体实现中,从节点上电开发,触发Boot接口事件,在该事件中,调用了TimerMilliC组件的Timer接口。
四、用户模块组件BlinkTimerM
4.1 BlinkTimerM的功能
BlinkTimerM是TinyOS中的一个用户模块组件,其主要功能是实现定时器定时事件的产生和处理,在应用程序中常用于实现周期性的数据采集、任务调度等功能。
BlinkTimerM组件主要提供了定时器的功能,通过它可以实现定时事件的产生和处理。在TinyOS中,定时器是一种非常常见的功能,它可以用于周期性任务调度、数据采集、实时控制等各种场景。因此,BlinkTimerM组件在很多应用中都有着广泛的应用。
程序中进行实例化并使用。具体来说,应用程序需要在使用BlinkTimerM组件之前,先定义一个回调函数来处理定时器触发的事件;然后在应用程序初始化中实例化一个BlinkTimerM组件;最后,在需要使用定时器的地方调用BlinkTimerM组件的start()函数即可开始定时器的运行。
此外,BlinkTimerM组件还提供了其他一些API函数,如setTimerPeriod()函数用于设置定时器的重复周期,getTimerPeriod()函数用于获取定时器的重复周期,isRunning()函数用于确定定时器是否正在运行,等等。
总之,BlinkTimerM组件是TinyOS中的一个非常重要的用户模块组件,通过它可以实现周期性的数据采集、任务调度等功能、可以方便地实现定时器功能,并且可以根据实际需求进行灵活的设置和控制、通过它还可以实现周期性任务的调度、实时数据采集和控制等功能,同时还提供了丰富的API函数来满足不同应用的需求。
4.2 BlinkTimerM中主要函数功能流程图及文字说明
4.2.1 流程图
Strt()函数流程图:
fired()函数:
4.2.2函数文字说明
BlinkTimerM组件中的核心函数之一是start()函数,其功能是启动一个定时器,并设置定时器的触发时间和重复周期。该函数的代码流程如下:
- 首先检查定时器是否已经启动,如果已经启动则不执行任何操作,直接返回。
- 如果定时器未启动,则初始化定时器,并设置定时器的触发时间和重复周期。
- 启动定时器并返回。
另一个核心函数是fired()函数,其功能是在定时器触发时调用应用程序中的回调函数来处理定时事件。该函数的代码流程如下:
- 首先检查定时器是否已经运行,如果未运行则不执行任何操作,直接返回。
- 如果定时器已经运行,则根据定时器设置的触发时间和重复周期计算下一个定时时间。
- 如果当前时间大于等于下一个定时时间,则调用应用程序中注册的回调函数来处理定时事件。
- 如果定时器设置为只运行一次,则停止定时器并返回。
- 如果定时器设置为重复运行,则更新定时器的触发时间并重新启动定时器,然后返回。
在BlinkTimerM组件中,还有一些其他的函数,如stop()函数用于停止定时器的运行,setTimerPeriod()函数用于设置定时器的重复周期,等等。这些函数也都是实现定时器的关键部分,但与start()和fired()函数相比,它们的实现逻辑较为简单,主要是对已有定时器进行设置和控制。
五、用户代码的启动过程、执行过程
假设现在我们需要在TinyOS应用程序中使用BlinkTimerM组件来实现每隔1秒钟闪烁一下LED灯的功能,具体实现启动和执行过程如下:
在应用程序目录下的AppC.nc文件中引入BlinkTimerM组件,并将LEDC.nc作为组件的一个子模块引入。
#include “BlinkTimerM.h”
#include “LEDC.h”
在AppC.nc文件中定义回调函数,用于处理定期事件,在定期事件发生时把LED灯翻转一下。
// 定义回调函数,用于处理定时事件
event void BlinkTimerM.fired() {
LEDC.toggle();
}
在AppC.nc文件中实例化BlinkTimerM组件,并设置定时器的触发时间和重复周期。
// 实例化BlinkTimerM组件
BlinkTimerMC.TimerInterface timer = TimerM.Timer0;
BlinkTimerM.Timer<TimerM.Timer0> blinkTimer;
// 设置定时器的触发时间和重复周期
blinkTimer.setTimerPeriod(1000);
在硬件初始化之后,启动定时器。
// 在硬件初始化之后,启动定时器
call blinkTimer.start();
等待事件发生。
在硬件初始化之后,BlinkTimerM组件启动了一个定时器,每隔1秒钟就会产生一个定时事件,并调用之前定义的回调函数进行处理。在应用程序中等待该事件的发生即可。
综上所述,使用BlinkTimerM组件实现定时器功能的过程主要包括:在应用程序中引入BlinkTimerM组件并定义回调函数、实例化BlinkTimerM组件并设置定时器的触发时间和重复周期、启动定时器,最后等待事件发生并处理。这些步骤将在用户代码启动时依次执行,以达到实现定时器功能的目的。
六、mainC及RealMainP模块组件
6.1 mainC及RealMainP的功能
在TinyOS应用程序中,mainC配置组件及RealMainP模块组件是十分重要的部分,它们实现了整个应用程序的初始化工作和数据处理流程。
mainC配置组件:
mainC配置组件是TinyOS中的应用配置组件之一,它使用配置语言(全称是NesC Configuration Language)来定义应用程序所需的组件和模块。mainC配置组件功能通常包含如下几个部分:
- 引入库文件:通过引入TinyOS库文件,如Ctp、Sense、Storage等,来使用TinyOS中的一些常用功能。
- 引入用户组件和模块:通过引入用户自定义的组件和模块,实现特定的功能和处理流程。
- 配置组件参数:配置某些组件的参数,如系统时钟频率、网络层协议等。
- 实例化组件:将组件具体定义为某个硬件接口的实例。
mainC配置组件是整个TinyOS应用程序的入口,它会在用户程序启动时进行初始化,对所有的组件进行实例化,并将其关联到对应的硬件接口上,以便在后续的执行过程中使用。
RealMainP模块组件:
RealMainP模块组件是小型控制器的主函数实现组件,在TinyOS的应用程序中负责定义启动时的执行过程,并启动组件之间的消息传递和数据处理流程。在RealMainP模块组件中,用户可以编写自己的主程序代码,并实例化所需的组件并将其连接起来。
RealMainP模块组件功能通常包含如下几个部分:
- 引入需要使用的组件和模块:用户可以引入需要使用的硬件驱动、协议栈、传感器等组件和模块。
- 定义启动时的执行过程:用户可以定义应用程序启动时需要进行的各种操作,如硬件接口的初始化、网络协议栈的启动等。
- 实例化组件:用户可以根据需要实例化需要使用的组件,并将其绑定到合适的消息通道上。
- 消息处理流程:用户可以根据具体需求,编写消息处理函数,对从消息通道中接收到的数据进行处理。
总之,RealMainP模块组件是整个TinyOS应用程序的核心之一。在用户程序的执行过程中,RealMainP模块主要负责:定义启动时的执行过程,实例化所需的组件并将其连接起来,启动组件之间的消息传递和数据处理流程,以及对特定事件的响应等
6.2 RealMainP中main函数功能流程及说明
在RealMainP组件中,main函数通常作为整个TinyOS应用程序的入口,它定义了应用程序的启动过程和执行流程,主要包括如下几个部分:
- 引入需要使用的组件和模块:在main函数中,用户需要引入需要使用的硬件驱动、协议栈、传感器等组件和模块。
- 定义启动时的执行过程:在main函数中,用户需要定义启动时需要进行的各种操作,如硬件接口的初始化、网络协议栈的启动等。这些操作可以通过TinyOS提供的接口函数来完成。例如,以下代码将硬件接口初始化为标准参数:
call Hardware.init();
- 实例化需要使用的组件:在main函数中,用户可以根据需要实例化需要使用的组件,并进行必要的参数设置。例如,对于一个使用Blink组件驱动LED灯的应用程序,可以使用以下代码对Blink组件进行实例化并设置闪烁的时间为500MS:
BlinkC.HardwareInit = true;
BlinkC.TimerCycle = 500;
component BlinkC Blink;
- 连接组件之间的消息通道:在main函数中,用户需要连接组件之间的消息通道,以便实现数据传输和处理。这可以通过使用TinyOS提供的接口函数来完成。例如,以下代码将Blink组件与LEDNC组件绑定到一起:
Blink.Timer -> Timer0;
Blink.LED -> LEDC;
- 启动组件之间的消息传递和数据处理流程:在main函数中,用户需要对组件之间的消息传递和数据处理流程进行启动和管理。这可以通过使用TinyOS提供的接口函数来完成。例如,以下代码启动了Blink组件中的定时器,并等待LED组件进行处理:
call Blink.start();
// 等待LED组件处理完毕
event result_t LEDC.waitForActive();
综上所述,RealMainP组件中的main函数主要负责定义应用程序的启动过程和执行流程,包括引入需要使用的组件和模块、定义启动时的执行过程、实例化需要使用的组件、连接组件之间的消息通道以及启动组件之间的消息传递和数据处理流程。这些操作都是在用户程序启动时被依次执行的。
七、TinyScheduler配置组件的功能
TinyScheduler配置组件是一个用于配置和管理TinyOS系统中定时器计时器的组件。其具体的功能包括:
- 配置TinyOS系统中定时器计时器的重载值和中断触发策略;
- 配置TinyOS系统中各个任务线程的执行时间和执行优先级,以便更好地利用系统资源;
- 提供定时器管理策略,优化系统性能和稳定性,降低定时器的误差和抖动,减少任务调度延迟;
- 提供系统状态查询功能,可以查询TinyOS系统中各个任务线程和定时器的执行状态和运行状态。
具体来说,TinyScheduler配置组件提供了如下几个常用的接口函数:
- void configureTimer(uint32_t reload, uint8_t prescale):用于配置系统中定时器计时器的重载值和中断触发策略。具体来说,reload参数是一个32位无符号整数,表示定时器计时器在计数到reload值时需要重新装载,并触发一个定时器中断。prescale参数是一个8位无符号整数,用于设置定时器计时器的分频系数,在TinyOS中默认为8。
- void configureTask( uint8_t taskID, uint8_t priority, uint32_t deadline ):用于配置TinyOS系统中的各个任务线程。具体来说,taskID参数是一个8位无符号整数,表示任务线程的ID号;priority参数是一个8位无符号整数,表示任务线程的执行优先级;deadline参数是一个32位无符号整数,表示任务线程的执行时间。
- void start(void):用于启动TinyScheduler配置组件,开始运行系统任务线程和定时器计时器。
- error_t postTask(uint8_t taskID):用于向系统任务线程队列中提交一个任务。具体来说,taskID参数是一个8位无符号整数,表示待提交任务线程的ID号。
总的来说,TinyScheduler配置组件主要是通过配置系统中定时器计时器和任务线程,来提供任务调度和定时器管理等功能,从而实现更加高效稳定的系统运行。
八、SchedulerBasicP模块组件
8.1 SchedulerBasicP模块组件的功能
SchedulerBasicP是TinyOS系统中的一个任务调度器组件。
其主要功能是调度和管理TinyOS系统中的所有任务线程,并实现任务调度的优先级控制、时间片分配和任务切换等功能,从而实现高效的任务执行和系统性能优化。
任务调度器的工作原理是,通过循环调用各个任务线程的run()函数,实现任务执行的优先级控制和时间片分配。当一个线程的执行时间片用完或者有更高优先级的线程准备好执行时,任务调度器就会进行任务切换,将 CPU 设备分配给优先级更高的任务,然后再根据任务队列的优先级进行任务线程的调度和分配。
任务调度器在TinyOS系统中发挥着重要的作用,通过有效地控制任务线程的执行,提高了系统的性能和稳定性。同时,任务调度器还保证了任务的正确执行顺序,避免了各个任务之间发生冲突。
8.2各函数功能实现、用到的数据结构及用法
SchedulerBasicP主要提供了以下几个重要的接口函数:
void taskLoop():任务调度器的主循环函数,用于实现任务优先级切换和时间片分配。在循环中,任务调度器会循环调用各个任务线程的run()函数,然后根据其优先级和当前时间片是否用完,决定是否切换到下一个任务线程。
void postTask(Task& t):将一个新的任务提交到任务队列中,等待后续调度执行。在提交任务时,需要指定任务线程的ID号和优先级等信息。
void resetTaskArgs():重置任务参数。在循环调用任务线程的run()函数时,需要将其之前的参数进行重置,以避免出现潜在的错误。
void setPriority(int8_t priority):设置当前任务线程的优先级。优先级越高的任务线程将会先被调度执行。
bool hasReadyTask():检查是否有任务线程准备就绪,是否需要进行任务切换和时间片分配等操作。
SchedulerBasicP组件主要使用了队列等数据结构,以及继承等面向对象编程方式。在该组件中,将任务线程看作一个对象,通过继承机制扩展出了run()等基本函数,从而实现了任务执行的控制和任务状态的管理。同时,任务调度器也使用了队列等数据结构来管理各个任务线程的执行顺序和优先级,实现了高效稳定的任务调度和时间分配等功能。
九、其他组件CTP的功能介绍
CTP(Collection Tree Protocol)是TinyOS中的一种组件,用于无线传感网络中数据收集和传输,主要用作数据聚集、时间同步、路由和排队等方面。该协议在TinyOS中有多个不同版本的实现,包括原生版本、Adaptive CTP和Collection Tree Protocol Lite等。
以下是CTP组件中一些重要的功能介绍:
**数据聚集:**CTP允许无线传感节点将数据聚集到父节点或协调节点,从而减少网络流量和能源消耗。在数据传输过程中,CTP会根据路由表和邻居信息等动态调整数据流向,从而保证数据可以顺利聚集到目的节点。
**时间同步:**CTP支持时钟同步功能,使得所有节点可以在同步的时间轴上进行运行。在时钟同步过程中,CTP会将网络中的时钟误差调整到最小,并保证所有节点以相似的时间运行,从而避免数据传输和处理时在时间上出现不一致的情况。
**路由和排队:**CTP允许传感节点以分层的方式进行路由和排队。当数据需要传输到下层节点时,CTP会选择最近的父节点进行转发;当数据需要传输到远程节点时,CTP会进行多跳路由。在数据传输过程中,CTP还会对数据包进行缓存和排队,从而保证数据能够按照正确的顺序被传输到目标节点。
**自适应调整:**CTP支持自适应调整功能,可以根据节点的运行状态和网络拓扑结构等动态调整协议的参数和行为。在网络拓扑结构发生变化时,CTP可以自动重新计算路由表和邻居信息,从而使得网络能够更加灵活和可靠地运行。
CTP是一种能够有效解决无线传感网络中数据收集和传输问题的协议。通过支持数据聚集、时间同步、路由和排队等功能,CTP可以帮助无线传感节点构建高效稳定的通信网络,并实现多种应用场景。
十、NesC语言与C语言
10.1NesC与C语言的分析比较
- 强制类型:C语言是一种强制类型的编程语言,变量必须声明并指定类型。但是,NesC语言是一种弱类型的编程语言,可以简单地使用变量而不指定类型来定义变量,编译器根据上下文自动推断类型。
- 模块化编程:NesC语言是一种面向组件编程的语言。组件是高度模块化的,可以相对独立地开发、测试和维护。C语言也是支持模块化编程的,但是它缺乏内置的模块化框架,需要使用头文件和库文件进行组件化编程。
- 事件驱动:NesC语言和TinyOS框架的本意是专门为无线传感器网络开发的。因此,NesC语言中包含了事件驱动的特性,可以支持高效的异步和并发编程。C语言也可以进行事件驱动的编程,但是需要手动实现定时器、中断等功能。
- 平台无关性:NesC语言的设计是为了支持可移植性和跨平台开发。NesC语言编写的代码可以在不同的硬件、嵌入式系统和操作系统上运行。C语言也支持跨平台和移植性,但是需要硬件抽象层的支持。
- 语法:NesC语言的语法和C语言有很多相似之处,但是NesC中的源码中包含了控制程序行为的注释指令等非标准语法。
总的来说,NesC语言相对于C语言而言,具有更强的面向对象编程风格和更多的系统功能集成,支持高效的事件驱动编程,并能够快速实现嵌入式组件化的开发。而C语言则更加适用于多平台的系统编程,在性能和灵活性方面更加突出。
10.2体会
从比较中可以发现,NesC语言和C语言在很多方面存在一定的不同。首先,NesC语言是一种更面向组件化编程的语言,注重模块化和事件驱动编程,而C语言则着重于底层硬件编程和多平台系统开发。其次,尽管两种语言在语法上有相似之处,但NesC语言引入了许多非标准语法,以支持编写更高效的系统编程,这对开发人员来说是个挑战。
我认为,熟练掌握这两种语言可以开发出高效、高性能的嵌入式系统。在学习和实践中,我们应该不断地比较和分析语言的各种特点,选择最适合实际需求的工具和技术方案,以切实提高开发效率和代码可维护性。尤其是对于开发嵌入式系统的开发人员来说,深入了解各种语言的特性和应用场景,不仅能够扩展开发思路,更能够做到自己心中有数,更好地高效完成开发任务。
十一、学习收获
学习Blink组件,我收获了以下知识和技能:
了解了TinyOS的编程结构:通过学习Blink组件,我了解了TinyOS中主函数入口的编程结构,学习了如何使用TinyOS编写最基本的程序,学会了使用跑马灯方式示例来了解TinyOS的编程结构。
掌握了组件的使用方法:学习Blink组件,我掌握了如何使用组件来编写程序,学会了如何创建和初始化组件,如何连接不同的组件,以及如何配置组件的属性和功能。
实现LED灯的闪烁:通过Blink组件的学习, 我学习了如何控制以固定频率闪烁的LED灯。这是编写无线传感网络程序中最基本的功能之一,因此这个技能对我今后的学习和研究无线传感网络技术来说非常重要。
状态和事件的理解:学习Blink组件,我了解了如何使用状态位和事件来实现控制LED灯的闪烁,同时也学习了TinyOS中事件驱动编程理念。
护性。尤其是对于开发嵌入式系统的开发人员来说,深入了解各种语言的特性和应用场景,不仅能够扩展开发思路,更能够做到自己心中有数,更好地高效完成开发任务。