(网友懒兔子作品)
这段时间折腾了一下SimpliciTI,有些个人理解,和大家讨论一下,希望有这方面经验的人不吝赐教,一个人闷头搞确实听没意思的。同时也在考虑从现有的MSP430向更强的M3平台移植(比如Stellaris),有兴趣的朋友可以一起讨论一下呀。
一、搭建SimpliciTI环境
一个MCU和一个RF模块,这就是搭建SimpliciTI环境的硬件需求(TI的SoC也是在芯片内部集成了RF模块)。当然了,这个协议栈是有版权的,当你用作商业用途时需要向TI缴纳一定的费用,而用于学习时,TI也会要求你至少用他的RF芯片。
这里使用的是TI的eZ430-RF2500套件,板载MSP430F2274和CC2500射频芯片,使用芯片天线,传输距离并不远,如果离开一段距离或者有遮蔽物就会出现丢包,这个我们会在后面进行测试。电路图如下(点击看大图),2274和CC2500之间通过SPI和两个IO(中断方式)通信,一般网上卖的模块也会留出这些接口。如果对RF PCB设计没有信心,淘宝上有10块钱的CC2500模块,虽然是白菜价,但是PCB天线一般都是低于0dB的,通信距离可想而知。
TI官方的开发套件,$49
淘宝上可以找到的CC2500模块,¥10
原理图
同时,此平台也对外开放一个USB调试接口(包括UART和SBW)。
接口定义
二、SimpliciTI协议栈结构与移植
SimpliciTI协议的一大特点就是简单,所以在极大简化开发过程的同时也带了很多限制,比如缺少完备的路由协议,最多4个RE(使通信距离受限),即使低功耗也是在ED关闭RF接收功能的前提下实现的(限制双向通信)。不过即使如此,也丝毫不影响它为无线网络通信带来的便利,你可以根据应用需求在ZigBee和SimpliciTI之间取舍。
想要使用它你需要下载协议栈代码,下面是及针对RF2500套件的演示代码:
通用协议栈SimpliciTI-IAR1.1.1可以在TI官网上下载:
安装后在安装目录下可以找到代码和说明文档,建议看一下Developers Notes、Sample Application User's Guide和API文档,如果需要涉及加密、调频等应用,这里还有相应的Application Note。
SimpliciTI本身的软件分为3层,即BSP、MRFI和NWK:
BSP是一个轻量级板级支持包,包含了MCU、SPI(这个必须有)、LED和Button,简单吧,TI的意思是尽可能的减少协议栈本身的硬件内容,因此如果想要用串口或者ADC,就要自行解决,我们暂且将这些额外添加的硬件控制代码称为Driver吧。
MRFI是最小射频接口,主要是与BSP中的SPI对接以及射频芯片的控制代码。如果要移植协议栈,就需要更改与MCU定义、中断、IO和SPI相关的代码,不过只要你使用MSP430或CC系列SoC作为MCU可以直接在代码中找到相近的Board从而减少工作量。当然啦,如果你是高手或者兼有求知和耐心的美德,甚至可以自己写底层而只保留NWK和APP层,不过这不是我们现在的目的,至少目前不是。
NWK网络层负责数据收发队列,同时包含很多网络层应用,注意区分NWK Applications与用户的应用代码(Customer Applications)的区别,这些网络层应用是以端口作为标识的(如Ping是0x01,Link是0x02),相应的函数以nwk_作为前缀,而我们通常将用户代码称作应用层。SimpliciTI的端口(Port)与TCP/IP的端口神似(similar in spirit ^_^),每个端口对应一种应用,用户也可以定义自己的网络层应用。网络层为用户提供了API函数,以SMPL_为前缀,如SMPL_Link等,我们的工作就是利用这些API实现组网及无线通信。
三、建立第一个简单双向通信程序
(1)配置工程
打开演示代码eZ430-RF2500 WirelessSensorMonitor中的Sensor_Demo_AP_as_Data_Hub工程,如果你使用的是eZ430-RF2500套件,直接修改其中的Application文件就好了,如果不是TI的官方套件,可以选择一个硬件组成相近的工程,修改MCU定义和按键、LED等配置,这里不再深究,以后可以专门开贴讨论。
【Tips】在下载程序时可能出现这个错误,具体描述为:
Fatal error: The Object file contains features not suported by the driver. Session aborted!
Failed to load debugee: …
这是因为在新版本的IAR中同时使用了选项配置和命令行,Debugger的版本较旧不识别,只需要取消命令行即可。
打开工程后可以看到两种器件,AP(Access Point)和ED(End Device)。AP是组网的关键,负责网络的建立和维护,同时负责对外通信,有点类似于ZigBee中的协调器。网络中最多只能有一个AP,在某些模式下(如P2P)可以不需要AP,但是这样的网络需要固定参数而缺少灵活性。ED是终端设备,在TI的演示中负责入网后通过片上温度传感器采集自身的温度,并发送给AP,由AP将接收到的数据转换成字符串后通过串给上位机。
在左侧的WorkSpace栏选择AP或者ED进行选择编译。
和网络相关的设置可以在几个配置文件中修改,其中smpl_config_AP.dat和smpl_config_ED.dat文件定义了设备类型、最大连接数、发送和接收帧队列长度等内容,smpl_nwk_config.dat文件则定义了基本的网络参数如网络和应用层负载长度、最大跳数等,这些配置可以在不同的应用中灵活选择,这里我们采用默认配置。
(2)AP节点
首先,通过BSP_Init函数初始化BSP,然后初始化需要的外围设备如串口、定时器、AD等,这里的串口函数并不包含在SimpliciTI中,而是以virtual_com_cmds.c文件的形式添加到工程中,也可以像我那样写自己的串口函数(参见我的SED430-RF2500一贴),甚至加入printf和scanf。
在设备初始化完成后,需要初始化SimpliciTI网络,AP中对应的函数为:
SMPL_Init(sCB);
这里的sCB是一个回调函数,本例中用来在接收到帧时区分是入网还是数据,并分别处理,它会在CC2500 RX接收中断后调用。想要研究协议结构的童鞋可以一路Go to definition找到函数调用的地方,现在我们只需定义该函数而无须理会调用机制。
回调函数的原型如下,当lid为0时,表示有设备入网,此时sJoinSem++,在主函数的sJoinSem子过程中进行处理,建立连接并为其分配LinkID。如果lid不为零则表示该设备已经入网并分配了LinkID,这个数据帧被作为通信数据在主函数的sPeerFrameSem子过程中通过SMPL_Recieve函数接收。
static uint8_t sCB(linkID_t lid)
{
if (lid)
{
sPeerFrameSem ;
}
else
{
sJoinSem ;
}
return 0;
}
至此整个初始化工作完成,然后AP进入while(1)循环,这个循环中包含几个子过程,这里我们只需要sJoinSem和sPeerFrameSem两个过程。
sJoinSem过程内容如下:
if (sJoinSem && (sNumCurrentPeers < NUM_CONNECTIONS))
{
while (1)
{
if (SMPL_SUCCESS == SMPL_LinkListen(&sLID[sNumCurrentPeers]))
{
break;
}
}
sNumCurrentPeers ;
BSP_ENTER_CRITICAL_SECTION(intState);
sJoinSem--;
BSP_EXIT_CRITICAL_SECTION(intState);
}
一旦进入这个过程(有设备入网),软件将持续通过SMPL_LinkListen函数监听ED发起的链接,在链接建立后会为该链接分配一个LinkID,以后数据收发就考这个LinkID作为标识。该函数的实参就是一个用来存放LinkID的数组。sNumCurrentPeers是当前连接设备计数器,这里要注意的是在处理完入网帧后,需要减少sJoinSem的值,而sJoinSem是一个公共变量,同时受回调函数的控制。sCB是一个在中断中执行的函数,所以要通过BSP_ENTER_CRITICAL_SECTION进行临界保护,参数intState其实是一个unsigned short型变量,在主函数中定义,用于保存相关寄存器,在退出临界状态时恢复。
对接收到的通信数据的处理则要靠sPeerFrameSem:
if (sPeerFrameSem)
{
uint8_t msg[MAX_APP_PAYLOAD], len, i;
for (i=0; i
{
if (SMPL_SUCCESS == SMPL_Receive(sLID, msg, &len))
{
USCI0_SendDataString(msg, len);
BSP_ENTER_CRITICAL_SECTION(intState);
sPeerFrameSem--;
BSP_EXIT_CRITICAL_SECTION(intState);
}
}
}
SMPL_Receive函数读取接收到的数据帧,将APP_PAYLOAD分离出来保存在msg数组中,len作为实参保存msg的长度,最后通过USCI0_SendDataString发送给上位机。
void USCI0_SendDataString(uint8_t *msg, uint8_t len)
{
int i;
for(i=0;i
USCI0_PutChar(msg);
}
其中USCI0_PutChar这个函数需要自己写,如果是带有USCI模块的430可以使用我的SED430-RF2500中的UART.c,若是USART模块的430则参见SED430中的串口文件。传送门上面已经给出。
那么如果要给ED发数据怎么办呢,依然只要一个函数即可:
定义一个msg数组,写入要发送的数据,然后:
SMPL_Send(lid, msg, len);
lid是要目的设备的LinkID,len是要发送数据的长度,这里len和接收函数不同是形参,注意不要超过最大负载长度哦。
这就是AP的部分,可以完成入网,数据接收发送功能,另外还有一些跳频和信号强度检测的功能,可以自己研究一下。
(3)ED节点
修改代码之前,需要给ED设置一个32位地址,这个地址相当于TCP/IP中的IP地址,由于SimpliciTI没有MAC层,所以设备就要靠这个地址识别数据包是不是发给自己的。网络中每一个设备的地址都应当是唯一的,否则会发生冲突。可以在对应设备的.dat配置文件中修改这条语句:
-DTHIS_DEVICE_ADDRESS="{0x7A, 0x56, 0x34, 0x12}"
若不想一个一个设置,可以参考范例共给出的产生随机地址的方法,通过ADC或者VLO采集一个真随机数(虽然是真随机数不过不知道是什么分布的),换算成地址并写入Flash。
ED和AP的设备初始化过程一样,初始化完成后,开始检测是否存在网络并,直到检测到AP建立的网络并入网。__bis_SR_register函数的作用是休眠并等待TA中断,如果不想采用这种方式,也可以通过__delay_cycles函数或自己编写Delay函数实现延时。
//搜寻并加入网络
while (SMPL_SUCCESS != SMPL_Init(sCB))
{
BSP_TOGGLE_LED1(); //LED闪烁
BSP_TOGGLE_LED2();
__bis_SR_register(LPM0_bits GIE);
}
如果ED不需要接收数据,SMPL_Init时可以不要回调函数:SMPL_Init(0)。如果需要接收数据,则应当开启RX:
SMPL_Ioctl( IOCTL_OBJ_RADIO, IOCTL_ACT_RADIO_RXON, 0);
ED的回调函数中只处理数据帧而没有入网帧,其形式如下:
static uint8_t sCB(linkID_t lid)
{
if (lid)
{
sPeerFrameSem ;
}
return 0;
}
ED在加入网络后还不能直接和AP通信,首先需要建立一个连接(Link):
//向AP发起Link
BSP_TOGGLE_LED1();
while (SMPL_SUCCESS != SMPL_Link(&LID_AP))
{
BSP_TOGGLE_LED1();
BSP_TOGGLE_LED2();
__bis_SR_register(LPM0_bits GIE); //替换成你自己的延时函数
}
连接建立好以后,AP的LinkID被保存在LID_AP变量中,因为我们只需要和一个固定的AP通信,所以不需要数组来保存多个LinkID。
至此,ED已经可以和AP自由的交换数据了,在sPeerFrameSem子过程中,我们将接收到的数据发还给AP并翻转一个LED。
//接收到RF数据
if(sPeerFrameSem)
{
uint8_t msg[MAX_APP_PAYLOAD], len;
if (SMPL_SUCCESS == SMPL_Receive(LID_AP, msg, &len))
{
SMPL_Send(LID_AP, msg, len); //发回接收到的数据
BSP_TOGGLE_LED2(); //翻转LED状态
BSP_ENTER_CRITICAL_SECTION(intState);
sPeerFrameSem--;
BSP_EXIT_CRITICAL_SECTION(intState);
}
}
搞定,可以编译运行了。
设置Show build messages显示全部消息,编译……
可以看到软件占用资源情况,想要运行这个协议栈,还要跑一些自己的程序,至少要有10KB的Flash和1KB的RAM,不过比起ZigBee那样的重量级协议,已经非常节省了。
四、运行结果
这是本例的工程文件(IAR5.30),分别将AP和ED的程序下载到模块中。