TinyOS Programming
4.1 模块实现
SenseC.nc
#include \
module SenseC {
uses {
interface Boot; interface Leds;
interface Timer
implementation {
// 采样周期100ms
#define SAMPLING_FREQUENCY 100
event void Boot.booted() {
call Timer.startPeriodic(SAMPLING_FREQUENCY);//启动计数器 }
event void Timer.fired() {
call Read.read(); //计时器到,开始读取数据 }
//读取数据完毕,处理数据,即将数据用LED显示
event void Read.readDone(error_t result, uint16_t data) {
if (result == SUCCESS){ if (data & 0x0004) call Leds.led2On(); else
call Leds.led2Off(); if (data & 0x0002) call Leds.led1On(); else
call Leds.led1Off(); if (data & 0x0001) call Leds.led0On(); else
call Leds.led0Off(); } } }
event result_t Timer.fired() { return call ADC.getData(); }
async event result_t ADC.dataReady(uint16_t data) { display(7-((data>>7) &0x7));
Page 28 of 94
TinyOS Programming
return SUCCESS; } }
像BlinkC模块一样,SenseC组件使用Boot,Timer和Leds接口。此外,SenseC组件还使用了Read
1. 首先系统初始化,调用Timer.startPeriodic(SAMPLING_FREQUENCY)启动周期性计数器 2. 计数器到,触发事件Timer.fired(),调用Read
据 3. 当读取数据完成,触发事件Read.readone(),然后将数据通过LED显示出来
可以看出,SenseC应用程序是事件驱动的,即每隔一段时间触发一次定时器事件,然后在事件中执行采集数据的任务。这种编程的模式是tinyos编程普遍的做法,几乎每个TinyOS都是采用事件驱动模式的,在这里是采样事件,在另外一些应用程序可能是串口事件和无线电收发事件。
现在你可能有这样的疑问:Sense程序中ADC访问的究竟Telosb中的哪个传感器,是温度传感器,电压传感器还是光电传感器呢?我们通过查看Sense配件可以得到答案。 Sense.nc
configuration SenseAppC {
//该模块不提供接口 }
implementation {
components SenseC, MainC, LedsC, new TimerMilliC(), new DemoSensorC() as Sensor;
SenseC.Boot -> MainC; SenseC.Leds -> LedsC;
SenseC.Timer -> TimerMilliC; SenseC.Read -> Sensor; }
SenseAppC.nc组件和BlinkAppC组件类似。其中Read接口是由DemoSensorC提供的。
DemoSensorC.nc
generic configuration DemoSensorC() {
provides interface Read
implementation {
components new VoltageC() as DemoSensor; Read = DemoSensor; }
Page 29 of 94
TinyOS Programming
由DemoSensorC的配件可知,DemoSensorC提供的Read接口是实际上由VoltageC()提供的,即电压传感器。
VoltageC.nc
generic configuration VoltageC() { provides interface Read
implementation {
components new Msp430InternalVoltageC(); Read = Msp430InternalVoltageC.Read; }
在telosb平台,因此VoltageC()组件调用的是Msp430InternalVoltageC组件提供的Read接口。
下面我们向下更深层次的分析,这就直接和硬件相关了。 4.2 ADC
下图为MSP4301611芯片的内部结构,左侧为16路ADC输入通道,A0-A7为8个可以外接的输入通道,由具体的硬件平台决定。参考telosb节点的数据手册,外置的温湿度传感器和光电传感器分别对于A3和A4通道。
16路输入通道
Msp430Adc12.h
Page 30 of 94
TinyOS Programming
enum inch_enum {
// see device specific data sheet which pin Ax is mapped to INPUT_CHANNEL_A0 = 0, // input channel A0 INPUT_CHANNEL_A1 = 1, // input channel A1 INPUT_CHANNEL_A2 = 2, // input channel A2 INPUT_CHANNEL_A3 = 3, // input channel A3 INPUT_CHANNEL_A4 = 4, // input channel A4 INPUT_CHANNEL_A5 = 5, // input channel A5 INPUT_CHANNEL_A6 = 6, // input channel A6 INPUT_CHANNEL_A7 = 7, // input channel A7
EXTERNAL_REF_VOLTAGE_CHANNEL = 8, // VeREF+ (input channel 8) REF_VOLTAGE_NEG_TERMINAL_CHANNEL = 9, // VREF-/VeREF- (input channel 9)
TEMPERATURE_DIODE_CHANNEL = 10, // Temperature diode (input channel 10)
SUPPLY_VOLTAGE_HALF_CHANNEL = 11, // (AVcc-AVss)/2 (input channel 11-15)
INPUT_CHANNEL_NONE = 12 // illegal (identifies invalid settings) };
内部电压传感器Msp430InternalVoltage的实现如下所示: Msp430InternalVoltageP.nc
module Msp430InternalVoltageP {
provides interface AdcConfigure
implementation {
const msp430adc12_channel_config_t config = {
inch: SUPPLY_VOLTAGE_HALF_CHANNEL, //输入通道 sref: REFERENCE_VREFplus_AVss, //参考电压 ref2_5v: REFVOLT_LEVEL_1_5, adc12ssel: SHT_SOURCE_ACLK, adc12div: SHT_CLOCK_DIV_1, sht: SAMPLE_HOLD_4_CYCLES,
sampcon_ssel: SAMPCON_SOURCE_SMCLK, sampcon_id: SAMPCON_CLOCK_DIV_1 };
async command const msp430adc12_channel_config_t* AdcConfigure.getConfiguration() {
return &config; } }
内部电压的输入通道inch:SUPPLY_VOLTAGE_HALF_CHANNEL对于为ADC11,如果将输入通道设为INPUT_CHANNEL_A3(INPUT_CHANNEL_A4),即为温度传感器(光电传感器)。
Page 31 of 94
TinyOS Programming
第5章 TinyOS任务及应用举例
本章以实例的方式介绍TinyOS的一个重要概念:任务。所举例子:SenseTask,是Sense应用程序的另一个版本。
5.1 任务的创建和调度
TinyOS 提供任务和硬件事件句柄组成的两层调度策略。如前所述,过关键字 async 声明了可被硬件事件句柄执行的命令或事件。这意味着它可以在任何时候执行(可能会抢占其它代码的执行)。因此,用 async 声明的命令和事件所做的工作应该做尽量少,且要快速完成。此外,还得注意被异步命令或事件访问的数据可能存在的数据竞争。任务则被用来处理一些较长时间的操作,例如:后台数据处理,但任务可以被硬件事件句柄抢占。
一个任务可以用以下语法在你的实现模块中声明: task void taskname(){ //?? } 其中,taskname 是程序员任意指定的任务的标识,也就是“函数名”。一个任务的返回值类型必须是 void,并且不能有任何参数。而向操作系统提交任务则可以用以下语法: post taskname(); 一个任务可以在命令、事件或其它任务内部向操作系统提交。
post 操作把任务放置到一个以先进先出为处理方式的内部任务队列中去。当一个任务开始执行的时候,只有它运行结束,下一个任务才能开始运行;因此,一个任务不应该占用或阻塞太长时间。任务之间不可以互相抢占,但是会被硬件事件句柄抢占。如果你的任务需要执行一系列长时间的操作,最好把任务分成几个而不是使用一个大的任务。
Page 32 of 94