写在前面:本实验所有代码都是基于一个已经移植好的平台代码,本人只是做了一些分析和研究罢了。
基础实验一 LED 实验
首先APP层组件即BlinkM.nc代码中使用的Leds接口,然后通过观察相应的配件组件BlinkC.nc可以看到它将Leds接口链接到了一个LedsC组件中(位于/opt/tinyos-2.x/tos/system中,通过后缀C可以知道这是个配件)。
/**********************************************************************
从这里可以看出来,无论你移植到什么系统中,为了减少你的代码量,在写makefile脚本时,尽量链接到从官网上down下来的代码,当然你可以直接放在你目录下面。
**********************************************************************/
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
注意,实现配置组件的符号有两种“->” 、 “=”
“->” : 接口调用
User to provider
“=” : 接口实现
A = B
那B中肯定有很多代码来实现A的接口。与此对应的是箭头则不然
所以为了区分它们,以后说“链接”都 指前者,“实现”指的后者
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
接着看,在组件LedsC配件中,有一个非常重要的语句。(在该目录下中有两有关led组件的,一个是LedsC 和 LedsP)
LedsC = LedsP;
把实现具体函数在了LedsP组件中,这起到了一个很好的保护作用。将用户程序与系统程序隔离。只能通过LedsC组件去访问。而LedsP组件使用了更底层的组件,此时就与开发平台相关了。
LedsP.Init <- PlatformLedsC.Init;
从此可以看出,程序已经进入了硬件相关的了,离开操作系统层了。
有趣的是,你可以再看看该文件夹下的其它组件,凡是配件都是先将它与对应的实现该接口的组件(LedP)链接起来,用的是“=”。然后将实现接口组件用到的更底层的接口链接到相应的代码中。
所以,我们可以看到现在已经进入到了与底层相关的了,所以程序又回到了我们的移植文件中(何谓移值?就是将一佗佗的硬件相关的代码实现)。这里只是到了平台相关,还没到硬件芯片相关,所以进入的文件夹目录是。“\opt\emdoor\tos\platform\zigbem”中的PaltformLedsC.nc配件组件中。(在此目录中,凡是后缀不含“C”或“P”都 是一些与平台相关的接口的定义)
PlatformLedsC.nc文件中,将提供的接口(RedLed、GreenLed其实都是GeneralIO的别名)与 对应的实现组件(HplCC2430GeneralIOC as CC2430IO)对应起来。这过程和原理简单。但值得注意的是,在讲解LedsC组件中我们讨论过了都会有对应的一个LEDSP组件来实现。这里也有
Init = PlatformP.LedsInit;
PlatformP.LedsInit
再回到PlatformLedsC程序中,我们来看,这里已经没有了链接的,全是实现的(即全是“=”)。而且从名字看来已经在硬件层了,所以为了看具体实现的组件代码文件我们可以到“\opt\emdoor\tos\chips\zigbem”中找相应组件(HplCC2430GeneralIOC)。最后我们在该目录下的“pins”子目录中发现了该组件。你可以看到里面都是与硬件相关的函数和命令了
#include <io8051.h>
#include <ioCC2430.h>
这是它包含的头文件,对别名起到一个解释作用。
。最后总结成一个图如图所示
应用层组件模板
(1) 模块 组件 程序框架
#include “xxx.h”
module xxxM{
uses interface Boot;
uses interface Y;
//或者 uses{
// interface X;
// interface Y;
// }
provides interface X;
provides interface Y;
}
implement {
//在相应的配件关联到MainC.Boot接口
event void Boot.booted( ) {
//初始化、提交任务、触发事件
}
}
(2) 配件 组件 程序框架
#include “xxx.h”
Configuration xxxC{
//不建议在此添加“提供/使用接口”代码,会使关系变得复杂不清,所以建议封装成接口和lib组件调用。
uses interface Boot;
uses interface Y;
//或者 uses{
// interface X;
// interface Y;
// }
provides interface X;
provides interface Y;
}
Implement{
Components xxxM; //为自己应用层实现的组件创建联结。
Components MainC; //TINYOS 主模块
Components xxx1;
Componetns xxx2;
xxxM.接口1名 -> xxx1;
xxxM.接口2名 -> xxx2;
xxxM.Boot -> MainC.Boot;
}
基础实验二定时器实验
CC2430中有四个定时器(Timer1、Timer2、Timer3、Timer4)和一个休眠定时器(Sleep Timer)。
Timer1:16位,支持典型的定时/计数功能以及PWM功能。
Timer2:主要用于802.15.4的CSMA/CA算法提供定时,值得注意的是该定时器在节点处于低功耗的状态下仍然运行。
Timer3、Timer4两个8位的定时器,主要用于提供定时/计数功能。
Sleep Timer 主要将节点从超低功耗状态下唤醒。
注意:接口中定义的命令或事件的实现不一定都会在提供者,还有调用者。例如定时器Timer接口的fired事件。
在使用Timer接口的时候须要给参数。
uses interface Timer<TMilli> as Timer1;
Timer接口的定义在 \opt\tinyos-2.x\tos\lib\timer文件夹中。
其实在这里Timer的接口参数<TMilli>就指定了使用的是毫秒级定时器分频什么的就确定了。
在tinyos系统中,定时器组件为通用组件,每种定时器可以通过new来实例化最多达255个。而这个都是由Timer1提供,所以在连结应用层定时器和实际定时器中有一个虚拟化定时器的组件,将实际定时器映射到应用层虚拟定时器。即VirtualizeTimerC组件(位于\opt\tos\lib\timer\VirtualizeTimerC.nc文件VirtualizeTimerC uses a single Timer to create up to 255 virtual timers.)。这个虚拟化组件很有意思,看它的定义:
{
provides interface Timer<precision_tag> as Timer[uint8_t num];
uses interface Timer<precision_tag> as TimerFrom;
}
它既使用又提供Timer。理解好这两个Timer是理解定时器机制的关键所在。从它们的别名中也可以看出Timer[uint8_t num]是提供给应用层程序看的,而TimerFrom是具体指任到哪个硬件上的。
然后看看它们链接到哪个组件上。
在组件HilTimerMiliC(\tos\chip\cc2430\timer)中将TimerFrom接口()链接到AlarmToTimerC组件(位于\opt\tos\lib\timer\)。(好混乱啊,怎么链来链去的)。同时在该组件中我们可以看到,TimerMilli = VirtualizeTimerC。意就实现了与上层的实现,而VirtualizeTimerC.TimerFrom -> AlarmToTimerC;意就实现了与底层的链接。
你也许会以为AlarmToTimerC该是底层的东东了吧,实则不然,它的作用是AlarmToTimerC converts a 32-bit Alarm to a Timer。这样看来,此组件它还没有到硬件层。使用了个接口
provides interface Timer<precision_tag>;
uses interface Alarm<precision_tag,uint32_t>;
此时就终于链接到了Alarm接口,在目录中你可以看到有很多组件都提供了Alarm接口(Alarm接口的定义位于\opt\tinyos-2.x\tos\lib\timer),但你仔细看由于参数不同,所以调用的不同。例如
provides interface Alarm<T32khz,uint16_t> as Alarm;
provides interface Alarm<T32khz,uint32_t> as Alarm;
这是两个完全不同的alarm。
再往下一步步。可以说定时器是所有组件里面最复杂的,关系太多了。
最后基于以上讨论,将其链接赋值关系表示如下。
TimerLed.Timer1 -> TimerMilliC = TimerMilliP = HilTimerMilliC。
在HilTimerMilliC组件中,
TimerMilli = VirtualizeTimerC; 所以就到了VirtualizeTimerC;
而且在该组件中,VirtualizeTimerC.TimerFrom -> AlarmToTimerC。就到了AlarmToTimerC,再根据应用层设计的参数,链接到 AlarmMilli32C组件中,
再根据参数链接到Alarm32khz16C中。在该组件中。
Alarm = HplCC2430Timer1AlarmCounterC.Alarm[ ALARM_ID ];
之后就更复杂了,暂时不讨论。(光找这些都 用了一上午时间)
基础实验四 串口调试试验
这个实验需要区分的就是它与串口。
系统为了大家调试,采用了串口输出的方式。
此组件名称为:components SerialDebugM;
它自身的实现都已经封装好了,我们只需要调用即可。但是需要注意的是
/* SerialDebug模块程序的Boot接口与系统Boot接口关联
* 这样系统启动时会调用SerialDebugM的Boot接口
*/
SerialDebugM.Boot -> MainC.Boot;
一个小技巧,当你需要查看一个组件的位置时,你可以先编译,然后生成app.c文件,在里头寻找。
在此有个问题就是我尝试使用uart1打印调试信息但总是失败?
解决了NB了,2012-09-30
它有使用uart0的模板,你不需要改动的太多,只需ctrl+c\v把0改成1就基本搞定。需要注意的就是两个寄存器。
PERCFG &= ~0x1u;
P0_ALT |= 0x0Cu; // Aka P0SEL
这是原uart0的代码,所以你首先得知道PERCFG作用,它是将你的串口连接到哪个端口。由于2430的强大,每个串口可以选择2个功能输出端口。以uart0为例。
若是PERCFG &= ~0x01; // 配置PERCFG寄存器,选择位置1
若是PERCFG |= 0x01; // 配置PERCFG寄存器,选择位置2
位置1是P0_2为RX,P0_3为TX。位置2是P1_4作为RX,P1_5作为TX引脚。
这就要根据你的具体硬件相关了,我采用的开发板的串口0且配置0。所以是这样的。然后对应的P0_ALT |= 0XC 就是将对应的IO口配置成特殊功能方式。所以经常是联用的即
if(Addr_UART == 1)
{
PERCFG &= ~0x01; // 配置PERCFG寄存器,选择位置1
P0SEL |= 0x0C; // 配置对应的iO引脚为外设功能P0_2为RX,P0_3为TX
}
else if(Addr_UART == 2) // 如果选择位置1即P1_4作为RX,P1_5作为TX引脚则
{
PERCFG |= 0x01; // 配置PERCFG寄存器,选择位置2
P1SEL |= 0x30; // 配置对应的IO引脚为外设功能P1_4作为RX,P1_5作为TX
}
OK,知道这个下面来配置我们自己的UART1。首先查看开发板原理图,是P14、P15作为TXRX。所以根据下面的代码。
if(Addr_UART == 1)
{
PERCFG &= ~0x02; // 配置PERCFG寄存器,选择位置1
P0SEL |= 0x30; // 配置对应的iO引脚为外设功能P0_2为RX,P0_3为TX
}
else if(Addr_UART == 2) // 如果选择位置1即P1_4作为RX,P1_5作为TX引脚则
{
PERCFG |= 0x02; // 配置PERCFG寄存器,选择位置2
P1SEL |= 0xc0; // 配置对应的IO引脚为外设功能P1_4作为RX,P1_5作为TX
}
所以我们需要做的就是将
PERCFG &= ~0x1u;
P0_ALT |= 0x0Cu; // Aka P0SEL
换成,
PERCFG |= 0x02; // 配置PERCFG寄存器,选择位置2
P1_ALT |= 0xc0;
收工。
基础实验五 串口打印
起始以为这次实验很简单,。
先就波特设置谈一点:
昨天我在试验改变波特率时,发现无论我怎么修改SerialIoM.nc中的波特率参数都无法改变波特率(即:
call CC2430UartControl.setBaudRate(9600);
)。只有我在最底层直接操作U0GCR、U0BAUD寄存器才会发生改变。为什么呢?
经过一天的不断试验,通过查看经过PERL脚本处理生成的.c文件,终于找到了。是没有理解好nesC语言参数传递的规范。最后找到了根本原因,不过还是从头来理一遍。
首先你必须理清调用关系。如下简单所示。
(0) 在应用层中,有这几句调用与串口有关。
call CC2430UartControl.setBaudRate(9600);
call UartStdControl.start();
(1)
SerialIoM.CC2430UartControl -> PlatformSerialC.CC2430UartControl;
SerialIoM.UartStdControl -> PlatformSerialC.UartStdControl;
SerialIoM.UartStream -> PlatformSerialC.UartStream;
//即将应用层组件使用的一些接口链接到PlafromSerialC组件的一些接口。位于(\opt\emdoor\tos\plaforms\zigbem\PlatformSerialC)。
(3) 在PlatformSerialC组件中
#ifdef NO_DMA_UART
components HplCC2430Uart0C as SerialC;
#else
components HplCC2430DmaUart0C as SerialC;
#endif
UartStdControl = SerialC;
UartStream = SerialC;
CC2430UartControl = SerialC;
这里就是根据是不是采用DMA方式来转移收发数据,由于没有NO_DMA_UART的宏定义,所以我们采用的是HplCC2430DmaUart0C组件(\opt\emdoor\tos\chips\cc2430\usart\HplCC2430DmaUart0C())。
(4) HplCC2430DmaUart0C
components HplCC2430DmaUartP;
components new HplCC2430DmaC(4) as DmaC;
components new HalCC2430SimpleDmaUartC(UART_BAUDRATE);
UartStdControl = HplCC2430DmaUartP;
UartStream = HplCC2430DmaUartP;
CC2430UartControl = HalCC2430SimpleDmaUartC;
HplCC2430DmaUartP.uart0StdControl -> HalCC2430SimpleDmaUartC;
HplCC2430DmaUartP.uart0 -> HalCC2430SimpleDmaUartC;
HplCC2430DmaUartP.CC2430Dma -> DmaC.CC2430Dma;
这一小段代码非常重要,因为它直接关系到到底应该使用哪种波特率。其中一个参数(UART_BAUDRATE),直接传递给了下层,那这个参数在何处定义的呢?看此文件开头的一段代码包括了一个头文件include“uart.h” (位于\emdor\tos\lib\zigbem\common\uart.h)。所以在这里有两个波特率值出现了,一是应用层传递过来的bauradrate。另一个是系统自身定义的用来调试的波特率UART_BAUDRATE。好我们来分别分析下这两个波特率值如何工作的。
首先是bauradrate:在应用层是这样调用的。
call CC2430UartControl.setBaudRate(9600);
最后要据上述层层调用,直接调用到最底层的是
HalCC2430SimpleDmaUartP组件中的
CC2430UartControl.setBaudRate(baud)
在这个函数中调用的是 doUart0Init(baud);
然后doUart0Init()函数中都是些初始化语句,仔细检查无错。
然后是UART_BAUDRATE。显然如果没有调试模块的话,只有在HplCC2430DmaUart0C这一个组件中才第一次出现。再仔细看看他出现在哪几个接口或者组件中。这个参数貌似直接到了HalCC2430SimpleDmaUartC组件中。看看这个组件的具体实现,在哪用到了
command error_t uart0StdControl.start() {
return doUart0Init(baudrate);
}
所以在这里它就再一次调用了doUart0Init(baudrate)。也就是说如果哪个组件或者接口中连续调用了这两个命令。
(1) CC2430UartControl.setBaudRate(baud) (2) uart0StdControl.start()。那必然导致两次初始化,置2次波特率寄存器。
基于以上分析,我就考虑是不是应用层程序调用了呢。
我们再回到(0)中看我标出的代码。它首先调用的是
call CC2430UartControl.setBaudRate(9600);
这样,程序最后执行到了HalCC2430SimpleDmaUartP组件中的CC2430UartControl.setBaudRate(baud)。参数传过来了,也确实将波特率设置成了9600。
call UartStdControl.start();
于是不停地找执行此命令最后的实现代码。一步步追踪,到了HplCC2430DmaUart0C组件,UartStdControl = HplCC2430DmaUartP;原来在HplCC2430DmaUartP组件。在此组件中实现了UartStdControl.start接口。看似到此结束了,可在这个函数最后的返回值中你会发现一个惊奇的结果。
return call uart0StdControl.start();
我不知道这是意欲何为?又调用一次uart0StdControl.start。这样程序又到了HalCC2430SimpleDmaUartP组件中的
command error_t uart0StdControl.start() {
return doUart0Init(baudrate);
}
这样就又初始化了遍。
应用层的代码是先调用前者,设置波特率,然后调用后者启动串口。所以无论你怎么改前者参数,都会被后者覆盖。
终于是知道原因了,那怎么改呢?方法很多。
(1) 直接调换应用层的执行顺序,即先start然后setBaudrate。
(2) 改变HplCC2430DmaUartP组件代码,即删除最后的return call ….实在不知道它这句话有什么用,这带来的后果就是你可能会出现其它诡异错误。
(3) 应用层代码去除setBaudrate函数。以后如果想改变传输波特率,就直接在uart.h文件中改动,这样就是可读性不是太好。
(4) 由于在uart.h中的宏定义是这样的,
#ifndef UART_BAUDRATE
#define UART_BAUDRATE 115200
#endif
所以你自己定义个UART_BAUDRATE 这样uart.h中就不会有那个定义了就实现统一
还有一个要注意的是,如果你设置的传送波特率和调试波特率不一致很有可能导致无法打印调试信息。所以最好开发开发第二个串口的使用。将调试串口置到第2个串口。
基础实验六 看门狗实验
(^^终于有机会研究看门狗了)
Watchdog定时器有两种运行模式,一种是watchdog模式,还有一种是Timer模式。
(1) watchdog模式 寄存器中的WDCTL.MODE位清零,进入Watchdog模式。当WDCTL.EN置1,Watchdog Timer开始计时,当计数器达到设定的值时,该定时器产生的reset信号,系统重启。如果在WDCTL.CLR[3:0]顺序写入0x0A以及0x5,则watchdog timer的计数值清0。
(2) Timer模式
两者共同点是都是达到一定数值,发生一些事情,区别就在发生什么事,watchdog模式是系统重启。而Timer模式则是系统产生中断。
(1)在看门狗模式下,若看门狗定时器已经使能,则对WDCTL.EN置0是无效的(即此位不能起到停止定时器的作用);所以watchdogc组件中的disable命令没用。
(2)在定时器模式下,可以对WDCTL.CLR[0]写1来对定时器清零;写0到使能位WDCTL.EN将停止定时器,而写1到使能位将重新启动定时器从0x0000开始运行。
至于组件很简单,
直接连接到WatchDogC组件(全部有关看门狗的函数都在\opt\emdoor\tos\chips\cc2430\watchdog文件夹中)
基础实验七 Flash读写实验
实验很简单,用到的组件位于\emdoor\tos\chips\cc2430\flash\HalFlashP.nc文件中。
收获的一点就是原来 节点的IEEE地址就在Flash的0x1fff8。
基础实验八 功耗模式实验
关于诸如此类的宏定义CC2430_SLEEP_POWERMODE_0 在\tos\chips\cc2430\timer\CC2430Timer.h文件中。
如何设置功耗模式?
#define SET_POWER_MODE(mode) { \
SLEEP = (SLEEP & ~CC2430_SLEEP_MODE_MASK) |mode; \
}
操作处理器模式关键看两个寄存器。PCON 和 SLEEP。
通过将SLEEP 的相应位置位或清零使处理器工作在相应的模式下。
然后才是通过PCON = PCON | 0x01; 来启用相应的模式。
具体可参见数据手册。
关于SleepAlarm的相关操作位于\emdoor\tos\lib\lowpower中,此文件夹中还有很多其它种类的低功耗处理组件。
SET_POWER_MODE(mode);
before_sleep();
call SleepAlarm.start(3*32768); //设置休眠时间
__nesc_enable_interrupt(); //开中断
PCON = 0x01; //进入休眠模式
after_wakeup();
这是几行关键的代码。通过SET_POWER_MODE(mode)设置准备工作。然后call SleepAlarm.start()设置时间,再PCON=0X01,直接进入相应的模式。即如果不是PM0,则会休眠,直到设置的时间过后才紧接着执行相关的代码即:after_wakeup();
关于每个模式的介绍可参下链接
http://www.61ic.com/Article/MSP430/ZigBee/201104/33284.html
通信实验一:点对点通信实验
先介绍几个接口的实现和复杂的链接。
uses {
interface Boot;
interface StdControl as UartStdControl;
interface UartStream;
interface SplitControl as RFControl;
interface AMSend;
interface Receive;
interface AMPacket;
interface Packet;
interface Leds;
}
(1) use interface SplitControl as RFControl
P2PM.nc :
P2PM.RFControl -> ActiveMessageC
\emdoor\tos\platforms\zigbem\ActiveMessageC.nc :
#ifdef CC2420_STACK
Components CC2420ActiveMessageC as AM;
#else
Components CC2430ActiveMessageC as AM;
#endif
SplitControl = AM;
\emdoor\tos\chips\cc2420\CC2420ActiveMessageC.nc :
(这个文件非常重要,它定义了各个层次的组件)
Components CC2420ActiveMessageP as AM;
//SplitControl Layers
SplitControl = PowerCycleC;
CcaControl = CsmaC;
\emdoor\tos\chips\cc2420\lpl\PowerCycleC.nc
Components PowerCycleP;
SplitControl = PowerCycleP;
\emdoor\tos\chips\cc2420\lpl\PowerCycleM.nc
该组件中有相关的实现。
Command error_t SplitControl.start(){}
当然在这里面也有很多的其它组件提供的东西。
(2) use interface AMSend;
P2PC.nc
P2PM.AMSend -> ActiveMessageC.AMSend[AM_DATA_TYPE];
\emdoor\tos\platforms\zigbem\ActiveMessageC.nc :
ActiveMessageC.nc
#ifdef CC2420_STACK
Components CC2420ActiveMessageC as AM;
#else
Components CC2430ActiveMessageC as AM;
#endif
AMSend = AM;
\emdoor\tos\tos\chips\cc2420\CC2420ActiveMessageC.nc
components CC2420ActiveMessageP as AM;
AMSend = AM;
\emdoor\tos\tos\chips\cc2420\CC2420ActiveMessageP.nc
command error_t AMSend.send[am_id_t id](am_addr_t addr,
message_t* msg,
uint8_t len)
在这个命令函数中,它调用SubSend接口。可SubSend貌似是链接着自己啊,只好看查看app.c代码。发现它连接到的是PacketLinkP
#define UniqueSendP__SubSend__send(arg_0x7e7e4670,arg_0x7e7e47f8) PacketLinkP__Send__send(arg_0x7e7e4670, arg_0x7e7e47f8)
好吧,我只好继续寻找这个组件。
\emdoor\tos\chips\cc2420\link\PacketLinkC.nc组件
provides {
interface Send;
interface PacketLink;
}
uses {
interface Send as SubSend;
}
说实话, 这里真有点儿晕了,怎么provides interface PacketLink还有uses interface Send as SubSend?
在该文件夹中有一个readme.txt查看TEP127文档(有关数据包链路层的接口和函数)。
(3) s
关于理解各种封包格式以及命令字,可以参看很多头文件。
\emdoor\tos\lib\zigbem\common\packet.h
\emdoor\tos\lib\zigbem\common\hplcc2420.h
\emdoor\tos\platforms\zigbem\platform.h
\emdoor\tos\platforms\zigbem\platform_message.h
\emdoor\tos\chips\cc2420\IEEE802154.h
\emdoor\tos\chips\cc2430\radio\CC2420.h
\emdoor\tos\chips\cc2430\radio\CC2430Radio.h
\emdoor\tos\chips\cc2430\radio\IEEE802154.h
\opt\tinyos-2.x\tos\types\AM.h
\opt\tinyos-2.x\tos\types\message.h
components CC2420ActiveMessageP as AM;
components CC2420CsmaC as CsmaC;
components ActiveMessageAddressC;
components UniqueSendC;
components UniqueReceiveC;
components CC2420TinyosNetworkC;
components CC2420PacketC;
components CC2420ControlC;
components AsyncAdapterC;
components PowerCycleC;
AM.SubSend -> UniqueSendC;
UniqueSendC.SubSend -> LinkC;
LinkC.SubSend -> CC2420TinyosNetworkC.Send;
CC2420TinysosNetworkC.SubSend -> AsyncAdapterC.Send;
AsyncAdapterC.AsyncSend -> CsmaC;
在通信中使用的数据包的帧格式为 message_t
Typedef nx_struct message_t{
Nx_uint8_t header[sizeof(message_header_t)];
Nx_uint8_t _data[114];
Nx_uint8_t footer[sizeof(message_footer_t)];
Nx_uint8_t meta_data[sizeof(message_meta_t)];
}message_t;
/opt/tinyos-2.x/tos/types/message.h
Typedef message_t message_t_xdata_pm0;
需要注意的几个问题
(1) 区分message_t与cc2420_packet_t
typedef nx_struct cc2420_packet_t {
cc2420_header_t packet;
nx_uint8_t _data[42];
} cc2420_packet_t
/opt/emdoor/tos/chips/cc2430/radio/CC2420.h
(2) 数据结构里的每个成员是如何定义的?
定义全在
/opt/emdoor/tos/platforms/zigbem/platform_message.h
typedef union message_header {
cc2420_header_t cc2420;
// serial_header_t serial;
} message_header_t;
typedef union TOSRadioFooter {
cc2420_footer_t cc2420;
} message_footer_t;
typedef union TOSRadioMetadata {
cc2420_metadata_t cc2420;
} message_metadata_t;
而cc2420_metadata_t \ cc2420_footer_t \ cc2420_header_t的定义如下
typedef nx_struct cc2420_header_t {
nxle_uint8_t length;
nxle_uint16_t fcf;
nxle_uint8_t dsn;
nxle_uint16_t destpan;
nxle_uint16_t dest;
nxle_uint16_t src;
nxle_uint8_t type;
} cc2420_header_t;
typedef nx_struct cc2420_footer_t {
nxle_uint8_t i;
} cc2420_footer_t;
typedef nx_struct cc2420_meta_data_t {
nx_uint8_t tx_power;
nx_uint8_t rssi;
nx_uint8_t lqi;
nx_bool crc;
nx_bool ack;
nx_uint16_t time;
nx_uint16_t rxInterval;
nx_uint16_t maxRetries;
nx_uint16_t retryDelay;
} cc2420_meta_data_t;
(3) 这里的114如何计算得来的?
首先告诉你在哪定义的?
/opt/tinyos-2.x/tos/types/message.h中有一条语句:
#ifndef TOSH_DATA_LENGTH
#define TOSH_DATA_LENGTH 28
#endif
这样也许你会以为是28但是,你得首先看头文件有没有define TOSH_DATA_LENGTH;
头文件为#include "platform_message.h"
/opt/emdoor/tos/platforms/zigbem/platform_message.h
没有任何关于宏TOSH_DATA_LENGTH的定义。
但是它不包括了个头文件。#include <CC2420.h>
/opt/emdoor/tos/chips/cc2430/radio/CC2420.h
#ifndef TOSH_DATA_LENGTH
#define TOSH_DATA_LENGTH 114
#endif
所以在message.h文件之前就已经有定义了。
整个P2P发送数据流程组件说明
P2PM (/opt/emdoor/apps/RFDemos) ->
CC2420ActiveMessageP(/opt/emdoor/tos/chips/cc2420) ->
UniqueSendP(/opt/emdoor/tos/chips/cc2420/unique) ->
PacketLinkP(/opt/emdoor/tos/chips/cc2420/link) ->
AsyncSendAdapterP(/opt/emdoor/tos/lib/zigbem/umpa/system/) ->
CC2420CsmaP (/opt/emdoor/tos/chips/cc2420/csma/)
(其实如果有6lowpan的宏定义,那么就会在PacketLinkP和AsyncSendAdapterP之间加了个CC2420TinyosNetworkP组件)
到这里基本结束剩下的就是送入CC2420硬件中
发送流程是这样的,那么数据包分别是怎么封装的呢?
(1)在 “P2PM”
传递给下层的参数有,有效数据长度m_len;
目的地址,dest_address;(该地址为MAC层使用的16位地址-(为什么会是16位的呢?因为后头处理MAC帧控制域时设置 了))
(2)在 “CC2420ActiveMessageP”
完成了对header的部分域的更新(type\dest\destpan)
Type注意这并非FCF中的数据帧、信标帧、命令帧的区别。而是AM的定义。AM type常见于多路复用的相关领域,它在功能上类似于以太网的数据帧IP协议的UDP端口,所有这些都是为了实现多路访问的一个通信服务。它参数来自P2PC
#define AM_DATA_TYPE 220
P2PM.AMSend -> ActiveMessageC.AMSend[AM_DATA_TYPE];
P2PM.Receive -> ActiveMessageC.Receive[AM_DATA_TYPE];
Dest 即帧格式中的目标地址。来自P2PM传递来的参数。
Destpan 即目的PAN标识符,来自CC2420Config.getPanAddr();
而注意的是len并没有改变,但是传递给下层组件时,
len = len+CC2420_SIZE;
CC2420_SIZE = MAC_HEADER_SIZE + MAC_FOOTER_SIZE
(4) 在”UniqueSendP”中
改变的只是dsn,序列号域
call CC2420PacketBody.getHeader(msg))->dsn = localSendId++;
(5) 在“PacketLinkP”中
没有改变任何域,做的处理仅是为了Send the msg with ACK request, if the msg is not acked, the * the msg is resent, the max send times is not big than PackerLink.gerRetries(msg)。也就是说是种为了重传的机制。
(6) 在“AsyncSendAdapterP”中
似乎什么也没有做,而且它直接调用的是这样的
command error_t Send.send(message_t * msg, uint8_t len)
{
return call AsyncSend.send(msg, len);
}
到这里实在是看不懂了,研究对应的配置组件,也没有透露什么信息的。又返回到app.c文件找到了,原来它又转到了对应的CC2420CsmaP。想想到底在哪显示是链接到那儿了呢?唯一答案可能就是CC2420ActiveMessageC组件了。
(7) 在“CC2420CsmaP”中
完成了对header的部分域的更新(len\fcf\src)
以及对metadata的部分域的更新(ack\rssi\lqi\time)
Header 中的len是指的
len = len+CC2420_SIZE;
CC2420_SIZE = MAC_HEADER_SIZE + MAC_FOOTER_SIZE
最重要的fcf域,规定了
header->length = len;
header->fcf = 0;
if(call AMPacket.destination(p_msg) != TOS_BCAST_ADDR)
{
header->fcf |= 1 << IEEE154_FCF_ACK_REQ;
}
header->fcf |= ( ( IEEE154_TYPE_DATA << IEEE154_FCF_FRAME_TYPE ) | ( 1 << IEEE154_FCF_INTRAPAN ) |
( IEEE154_ADDR_SHORT << IEEE154_FCF_DEST_ADDR_MODE ) |
( IEEE154_ADDR_SHORT << IEEE154_FCF_SRC_ADDR_MODE ) );
header->src = call AMPacket.address();
Metadata的域
metadata->ack = FALSE;
metadata->rssi = 0;
metadata->lqi = 0;
metadata->time = 0;
值得注意
TOS_NODE_ID = FlashIEEEP__FlashIEEE__GetShortAddress();
TOS_IEEE_SADDR = FlashIEEEP__FlashIEEE__GetShortAddress();
TOS_IEEE_PANID = FlashIEEEP__FlashIEEE__GetPANID();
至于它们具体值,在初始化就已经完成。如下所示
//# 53 "/opt/emdoor/tos/platforms/zigbem/FlashIEEEP.nc"
static uint16_t FlashIEEEP__FlashIEEE__GetPANID(void)
{
return (FlashIEEEP__m_ieee[4] << 8) | FlashIEEEP__m_ieee[5];
}
static uint16_t FlashIEEEP__FlashIEEE__GetShortAddress(void)
{
return (FlashIEEEP__m_ieee[6] << 8) | FlashIEEEP__m_ieee[7];
}
所以你在call AMPacket.address()返回的是一个16位的地址。
接收讲解:
“P2PM”
Receive_receive <-
“/opt/emdoor/tos/chips/cc2420/CC2420ActiveMessageP.nc”
Receive__receive <-
“/opt/emdoor/tos/chips/cc2420/CC2420ActiveMessageP.nc”
Subreceive_receive <-
#define UniqueReceiveP__Receive__receive(arg_0x7ec10ca0,arg_0x7ec10e40,arg_0x7ec05010) CC2420ActiveMessageP__SubReceive__receive(arg_0x7ec10ca0, arg_0x7ec10e40, arg_0x7ec05010)
"/opt/emdoor/tos/chips/cc2420/unique/UniqueReceiveP.nc "
Receive__receive <-
"/opt/emdoor/tos/chips/cc2420/unique/UniqueReceiveP.nc "
SubReceive__receive <-
根据序列号DSN和源地址,判断是否是重传的包
#define AsyncReceiveAdapterP__Receive__receive(arg_0x7ec10ca0,arg_0x7ec10e40,arg_0x7ec05010) UniqueReceiveP__SubReceive__receive(arg_0x7ec10ca0, arg_0x7ec10e40, arg_0x7ec05010)
/opt/emdoor/tos/lib/zigbem/umpa/system/AsyncReceiveAdapterP.nc”
Receive__receive <-
/opt/emdoor/tos/lib/zigbem/umpa/system/AsyncReceiveAdapterP.nc”
AsyncReceive__receive <-
存储下层传上来的数据并开启一个同步的任务
#define CC2420ReceiveP__Receive__receive(arg_0x7e739850,arg_0x7e7399f0,arg_0x7e739b78) AsyncReceiveAdapterP__AsyncReceive__receive(arg_0x7e739850, arg_0x7e7399f0, arg_0x7e739b78)
“/opt/emdoor/tos/chips/cc2420/receive/CC2420ReceiveP.nc”
Receive <-
“/opt/emdoor/tos/chips/cc2420/receive/CC2420ReceiveP.nc”
receiveDone <-
完成了对metadata域的填充。还有一些基本信息判断
"/opt/emdoor/tos/chips/cc2420/receive/CC2420ReceiveP.nc"
receivedPacketTask__runTask
通讯实验 Sniffer
需要仔细看看HALCC2420接口。
它的具体实现在\opt\emdoor\chips\radio\HalCC2430RadioP.nc
command error_t sendPacket(uint8_t * packet);
async event void sendPacketDone(uint8_t * packet, error_t result);
event uint8_t * receivedPacket(uint8_t * packet);
command error_t setChannel(uint8_t channel);
command error_t setTransmitPower(uint8_t power);
command error_t setAddress(mac_addr_t *addr);
command const mac_addr_t * getAddress();
command const ieee_mac_addr_t * getExtAddress();
command error_t rxEnable();
command error_t rxDisable();
command error_t addressFilterEnable();
command error_t addressFilterDisable();
command error_t setPanAddress(mac_addr_t *addr);
command const mac_addr_t * getPanAddress();
值得注意的是在这里它并没有调用主动消息的格式(即ActiveMessageC)。而是使用较为简单的组件(HalCC2430RadioC)。
需要好好区分这两个组件。因为它提供了两种不同的方式一通信。
简单介绍一下理解
首先由串口接收数据决定是增、减、还是自动?
如果是增或减,那么就直接设置信道即可。如果有数据接收到会自动 触发到HALCC2420.receivedPacket事件。
如果是自动,那么可以置一个全局变量来判断是不是,就不断扫描,但如何确定扫描时间呢,这里就利用到了一个定时器。当定时器一触发就自动往下一个扫。
显然用直接调用简单射频组件HalCC2430RadioC生成的代码比调用主动消息组件少很多。
在Sniffer实验中生成的代码为46KB,而P2P实验生成的102KB。