TI15.4STACK协议栈解读——基于CC2652R1
虽然TI的官方资料已经很全了,但是受限于资料体系庞大,还都是英文文档,所以想要很快的掌握TI15.4STACK协议栈还是比较困难的,所以笔者也是经过一段时间摸索,才渐渐入门了一些,这里把我理解的一些东西分享出来,希望能帮助大家更快的理解TI15.4STACK协议栈,这也是对自己这段时间学习的总结和知识梳理。
走进协议栈的学习之路
授人以鱼不如授人以渔,我先分享一下自己学习过程中参考的资料和学习方法,以便读者后续自学巩固提高。
后面代码解读需要读者已经拥有TI处理器的开发环境并且有一定的嵌入式开发经验;然后对无线通信技术有基本了解;对嵌入式操作系统有基本了解,像任务,信号量,消息队列等;对C语言能熟练掌握,特别是指针和结构体的理解要到位,因为TI的程序中大多使用结构体和指针进行配置。
官方SDK的获取
SDK 就是 Software Development Kit 的缩写,中文意思就是“软件开发工具包”。
1.TI官方链接: CC2652SDK获取
2.安装后TI的官方SDK后,在你的安装目录下就还有TI的很多辅助开发资料,有说明文档,API文档,例程,库文件等。
3.我们打开SDK文件下的docs\ti154stackti-15.4-stack-users-guide.html
文件,这里推荐使用Google Chrome浏览器,可以一键翻译,对于阅读TI的英文文档很有帮助。
辅助软件
返回至目录
对于TI的PDF格式英文帮助文档,建议使用国产的WPS软件,可以划词翻译,也很方便。
对于代码中的英文,可以使用网易有道词典,划词翻译。
学习渠道
返回至目录
4.TI官方也有学习平台和技术论坛。
TI的中国培训视频: 可直接点击标题进入视频链接。
-
TI 15.4 协议栈,以及低功耗远距离传感器到云端解决方案介绍
TI的英语培训视频: 门类更多,如果英语水平好,可以看看官方的视频
什么是TI15.4STACK协议栈
返回至目录
首先我们要知道什么是TI15.4STACK协议栈?
TI 15.4-Stack 是一种 IEEE 802.15.4e/g 射频通信堆栈。它是 SimpleLink CC13xx/CC26x2 软件开发套件 (SDK) 的主要部分,根据您选择的器件,为低于 1GHz 应用或 2.4GHz 应用的星形拓扑网络提供支持。
协议栈已经实现了网络创建,无线设备的加网退网,数据的加密传输,无线发送的防碰撞检测,数据发送,确认和重传机制,设备地址分配,跳频算法等一系列的功能,可以非常方便的,让用户使用它快速开发1G以下产品
既然你看到这篇博客,估计你也是在做无线通讯项目,而无线通讯中两个节点之间或者网关与子节点之间通讯就要使用到通信协议,协议主要是双方约定以什么样的方式,什么样的时序,什么样的频率进行通信。
就像我们最常用的串口通信协议一样,双方约定好波特率,约定好数据位等,简单的协议就能完成通信了。那么TI的15.4协议则是遵循IEEE 802.15.4e/g标准,实现无线通讯的。
协议栈其实就是封装好的代码,按约定的协议写好的库文件,一个Library,然后很方便的供开发者使用API接口来开发,而不用去管底层驱动就能实现通讯功能。
而TI15.4STACK协议栈就是由TI官方完成了PHY和MAC层的库文件,然后预留了很多API接口,可以让开发者很轻松的基于协议栈,继续开发网络层和应用层的代码。
TI15.4STACK协议栈用户指南——导读
- 下面我们打开SDK文件下的
docs\ti154stackti-15.4-stack-users-guide.html
文件,详细解读一下TI15.4STACK协议栈。
对于上图可以理解为TI的15.4STACK支持对于单设备整体开发
和协同开发
。对于想实现功能不多,数据处理量不大的无线小项目,可以直接将应用层代码和协议栈放在一起开发,直接一块MCU就能解决方案;而如果要实现比较复杂的功能,数据处理量庞大,可以使用协同开发方案,让装有TI15.4协议栈的MCU作为外设,然后与主机通过串口交互数据,这样也简化了无线产品的开发过程。
关于2.4G信道的介绍,请参考这篇知乎博文链接 为什么2.4G信道是13个。
上图介绍的关于无线通讯速率和PHY配置,这在程序中都可以根据具体需求在程序中修改配置。如下图所示在.syscfg文件中可通过RF Design
和TI 15.4 STACK
快速配置。
关于配置完.syscfg文件后,代码中又是发生了什么变化,具体见我用 VISIO 2016 绘制的关系图。
从百度网盘获取链接: 提取码:irol
例程导读
返回至目录
下面开始打开官方提供的示例应用程序,主要是collector
和sensor
两个例程,一个作为网关采集数据,一个作为节点发送传感器数据。
- 先演示一下用CCS导入SDK中的collector例程。(sensor的例程打开同理)
- 然后我们来看应用架构。
在应用架构中,最底层的已经由TI15.4STACK协议栈完成,即MAC和PHY层,这层代码也是被封装起来了,我们看不见源码。但其开放了API接口供我们调用,并且也有API的用户手册可以查阅,所以对于我们用来二次开发,主要是读懂TI15.4STACK的API文档,会通过API函数初始化配置无线通讯,通过API函数发送和接收无线通讯的信息。
由架构图还能看出在API接口之上,应用层还集成了逻辑链路层代码(实线框)(然后自己可以再继续编写网络层以及应用层代码
)、可选的安全密钥功能和空中下载功能(虚线框)及SF程序(采集器或传感器应用例程的定义函数)和CUI程序(用户公用接口,如IIC,SPI,UART,GPIO)。然后整体通过TI的实时操作系统在管理和调度。
- 然后我们展开Sensor 和 Collector 示例应用程序的基本应用程序源树。
更详细的描述见SDK文件下的docs\ti154stackti-15.4-stack-users-guide.html
文件。
当然在例程下还有很多模板可以学习参考。
Main()函数解读
返回至目录
这里以sensor的main函数为例。
int main(void)
{
Task_Params taskParams;
macUser0Cfg[0].pAssertFP = assertHandler;
/*
Initialization for board related stuff such as LEDs
following TI-RTOS convention
*/
Board_init();
/* Initialize CUI UART*/
CUI_params_t cuiParams;
CUI_paramsInit(&cuiParams);
// One-time initialization of the CUI
// All later CUI_* functions will be ignored if this isn't called
CUI_init(&cuiParams);
_macTaskId = macTaskInit(macUser0Cfg);
/* Configure task. */
Task_Params_init(&taskParams);
taskParams.stack = appTaskStack;
taskParams.stackSize = APP_TASK_STACK_SIZE;
taskParams.priority = APP_TASK_PRIORITY;
Task_construct(&appTask, appTaskFxn, &taskParams, NULL);
BIOS_start(); /* enable interrupts and start SYS/BIOS */
return (0);
}
以上相比于源代码,我删去了宏编译部分精简化后,看看主函数中都进行了哪些配置。
- 首先定义了任务参数的结构体变量
Task_Params taskParams
。
结构体里的成员我们暂时不用全部弄清楚,在后面配置中只需要配置堆栈的参数即可。- 是一个结构体数组[0]的一个成员赋值
macUser0Cfg[0].pAssertFP = assertHandler;
该结构体声明是一个全局变量,并给了初值。
具体见我用 VISIO 2016 绘制的关系图。
从百度网盘获取链接: 提取码:d72l
Board_init();
初始化板卡。- CUI初始化(the Combined User Interface)
_macTaskId = macTaskInit(macUser0Cfg);
初始化MAC收发任务,并传入实参macUser0Cfg
即(2)的结构体数组,并返回MAC任务ID。
macTaskFxn
任务的优先级和堆栈配置如下:
appTaskFxn
用户程序的任务初始化。
BIOS_start();
使能中断,开始操作系统任务调度
那么以上就将Main主函数的内容解读完毕,主要是建立了两个任务 appTaskFxn
和macTaskFxn
,即用户程序任务和mac驱动任务,优先级分别是1和3,数字越小,优先级越高。
而至于两个任务主要处理了什么内容,用户怎么通过API发送和获取无线通信的报文呢?接着往下看SDK文件下的docs\ti154stackti-15.4-stack-users-guide.html
文件,来了解OSALPort服务是怎么起到桥梁作用,将两个任务 appTaskFxn
和macTaskFxn
串联起来。
OSALPort 协议栈服务
OSAL为Operating System Abstraction Layer,即操作系统抽象层,支持多任务运行,其中协议栈、配置文件以及所有的应用程序(app)都在其上运行,它并不是一个传统意义上的操作系统,但是实现了部分类似操作系统的功能
OSALPort 协议栈服务可以理解为生存在TIRTOS和TI15.4协议栈之间的调度员,它能结合操作系统的特性很好的将MAC层接收的信息传递给应用层,也能很好的将应用层的消息传递给MAC层,然后无线发送出去。
OSALPort的功能
返回至目录
下面这段我没有用谷歌的翻译,因为它翻译的错误较多,不好说清。这段主要解释,OSALPort作为桥梁或作为调度者,除了初始化还要让那些需要他来管理的任务们(Task)在它那注册一下,我们知道sensor这个例程目前主要有两个任务,分别是 appTaskFxn
和macTaskFxn
,即用户程序任务和mac驱动任务,所以这两个任务在初始化时都执行了OsalPort_registerTask();
这个函数,就是在OSALPort服务中注册一下自身,并绑定了各自的信号量和记录事件。
OSALPort的初始化和注册
那么OSALPort服务在程序中具体怎么注册的,因为截图太小,看不清。
见我用 VISIO 2016 绘制的关系图。
从百度网盘获取链接: 提取码:w7ln
OSALPort线程同步
返回至目录
OSALPort是通过信号量来同步不同任务之间的信号。
示例 OSALPort 用法
返回至目录
上面一段意思是说在APP层即应用层(appTaskFxn
任务)想发送命令给协议栈,就可以通过cllc.c
(collecter例程的)或jdllc.c
(sensor例程的)中调用的ApiMac_mlmeSetReqArray()
函数,直接给协议栈设置一个属性值并等待协议栈配置完属性后的信号量。然后协议栈执行完post一个信号量,通过OSALPort服务返回给APP层。
应用层收发数据
返回至目录
经过前面的学习,我们最终目的是想通过TI15.4STACK协议栈编写自己的应用,那么就涉及到怎么去修改collecter
或sensor
例程,应用层通过哪个API接口函数来收发数据,那么最后让我们结合例程,学习一下例程中数据收发的操作。
这一章学前须知:
- SDK文件下的
docs\ti154stackti-15.4-stack-users-guide.html
文件后面的还有些内容,读者可以接着往下看,继续研究,不过我觉得重点不多了。 - 后面例程主要以
collecter
或sensor
为主,读者可以先用CCS开发平台先导入这两个例程。导入例程前面有演示过程。 - 导入例程后,可以在工程目录下看到README.md文件,这也可以用Google Chrome浏览器打开并翻译,看看例程要实现的功能是什么,这样修改起例程才更得心应手。
首先我们看一下sensor
例程。
在前面我们分析过main函数中的操作,主要是建立了两个任务 appTaskFxn
和macTaskFxn
,到现在还没看到两个任务的庐山真面目。
appTaskFxn
Void appTaskFxn(UArg a0, UArg a1)
{
/* Copy the extended address from the CCFG area */
CCFGRead_IEEE_MAC(ApiMac_extAddr);
/* Check to see if the CCFG IEEE is valid */
if(memcmp(ApiMac_extAddr, dummyExtAddr, APIMAC_SADDR_EXT_LEN) == 0)
{
/* No, it isn't valid. Get the Primary IEEE Address */
memcpy(ApiMac_extAddr, (uint8_t *)(FCFG1_BASE + EXTADDR_OFFSET),
(APIMAC_SADDR_EXT_LEN));
}
/* Setup the NV driver */
NVOCMP_loadApiPtrs(&Main_user1Cfg.nvFps);
if(Main_user1Cfg.nvFps.initNV)
{
Main_user1Cfg.nvFps.initNV( NULL);
}
/* Initialize the application */
Sensor_init(_macTaskId);
/* Kick off application - Forever loop */
while(1)
{
Sensor_process();
}
}
以上相比于源代码,我删去了宏编译部分精简化后,看看appTaskFxn
中都进行了哪些配置。
CCFGRead_IEEE_MAC(ApiMac_extAddr);
首先是从CCFG区域读取IEEE地址。Main_user1Cfg.nvFps.initNV
初始化内部FALSHSensor_init(_macTaskId);
初始化传感器,注意这里传入的实参是macTaskId
Sensor_init(_macTaskId);
void Sensor_init(uint8_t macTaskId)
{
ApiMac_deviceDescriptor_t devInfo;
Llc_netInfo_t parentInfo;
uint32_t frameCounter = 0;
/* Initialize the sensor's structures */
memset(&configSettings, 0, sizeof(Smsgs_configReqMsg_t));
configSettings.frameControl |= Smsgs_dataFields_tempSensor;
configSettings.frameControl |= Smsgs_dataFields_msgStats;
configSettings.frameControl |= Smsgs_dataFields_configSettings;
if(!CERTIFICATION_TEST_MODE)
{
configSettings.reportingInterval = CONFIG_REPORTING_INTERVAL;
}
else
{
/* start back to back data transmission at the earliest */
configSettings.reportingInterval = 100;
}
configSettings.pollingInterval = CONFIG_POLLING_INTERVAL;
/* Initialize the MAC */
sem = ApiMac_init(macTaskId, CONFIG_FH_ENABLE);
/* Initialize the Joining Device Logical Link Controller */
Jdllc_init(&Sensor_macCallbacks, &jdllcCallbacks);
/* Register the MAC Callbacks */
ApiMac_registerCallbacks(&Sensor_macCallbacks);
/* Initialize the platform specific functions */
Ssf_init(sem);
ApiMac_mlmeSetReqUint8(ApiMac_attribute_phyCurrentDescriptorId,
(uint8_t)CONFIG_PHY_ID);
ApiMac_mlmeSetReqUint8(ApiMac_attribute_channelPage,
(uint8_t)CONFIG_CHANNEL_PAGE);
Ssf_getFrameCounter(NULL, &frameCounter);
/* Initialize the MAC Security */
Jdllc_securityInit(frameCounter, NULL);
/* Set the transmit power */
ApiMac_mlmeSetReqUint8(ApiMac_attribute_phyTransmitPowerSigned,
(uint8_t)CONFIG_TRANSMIT_POWER);
/* Set Min BE */
ApiMac_mlmeSetReqUint8(ApiMac_attribute_backoffExponent,
(uint8_t)CONFIG_MIN_BE);
/* Set Max BE */
ApiMac_mlmeSetReqUint8(ApiMac_attribute_maxBackoffExponent,
(uint8_t)CONFIG_MAX_BE);
/* Set MAC MAX CSMA Backoffs */
ApiMac_mlmeSetReqUint8(ApiMac_attribute_maxCsmaBackoffs,
(uint8_t)CONFIG_MAC_MAX_CSMA_BACKOFFS);
/* Set MAC MAX Frame Retries */
ApiMac_mlmeSetReqUint8(ApiMac_attribute_maxFrameRetries,
(uint8_t)CONFIG_MAX_RETRIES);
/* Initialize the app clocks */
initializeClocks();
if(Ssf_getNetworkInfo(&devInfo, &parentInfo) == true)
{
/* Update Channel Mask to show the previous network channel */
if (!CONFIG_FH_ENABLE)
{
uint8_t channelMask[APIMAC_154G_CHANNEL_BITMAP_SIZ] = {0};
uint8_t idx = parentInfo.channel / 8;
uint8_t shift = (parentInfo.channel % 8);
uint8_t chan = (0x01) << shift;
channelMask[idx] = chan;
Jdllc_setChanMask(channelMask);
}
/* Start the device */
Util_setEvent(&Sensor_events, SENSOR_START_EVT);
}
else if (CONFIG_AUTO_START)
{
/* Start the device */
Util_setEvent(&Sensor_events, SENSOR_START_EVT);
}
}
以上相比于源代码,我删去了宏编译部分精简化后,看看
Sensor_init
中都进行了哪些配置。
其实每行都有英文注释,主要是和传感器相关的数据初始化和外设初始化。我们注意这几行:/* Initialize the MAC */ sem = ApiMac_init(macTaskId, CONFIG_FH_ENABLE); /* Initialize the Joining Device Logical Link Controller */ Jdllc_init(&Sensor_macCallbacks, &jdllcCallbacks); /* Register the MAC Callbacks */ ApiMac_registerCallbacks(&Sensor_macCallbacks); /* Initialize the platform specific functions */ Ssf_init(sem);
分别初始化了与MAC任务的联系,逻辑链路层的控制,MAC的回调函数配置和外设接口配置(按钮,LED,串口)。
然后看看sem = ApiMac_init(macTaskId, CONFIG_FH_ENABLE);
中执行了哪些内容。
4.Sensor_process();
传感器函数的主要进程。在这里主要通过事件掩码MASK来检索发生的事件,也是在该进程里可以看到无线数据的收发过程。所以比较重要,也是比较难理解的地方,了解了这块后面便可以尝试自己修改程序,完成自己想要的功能。
Sensor_process()
返回至目录
首先,在Sensor_process()
通过各类事件掩码,触发对应程序,其中列举了三个事件SENSOR_START_EVT
SENSOR_READING_TIMEOUT_EVT
和 0
在SENSOR_READING_TIMEOUT_EVT
条件中,有个发送传感器温度的函数,看它怎么发送数据的。
所以最终是通过api_mac.h
中官方封装的API函数ApiMac_mcpsDataReq()
把传感器的数据发送出去的。
然后发送完数据,传感器还要接收网关的回复数据,那么看传感器是怎么接收数据的。
所以接收数据,是通过系统监测信号量appSemHandle
,然后通过回调函数dataIndCB()
处理数据,而数据存放在msdu.p
这个指针下。
而在collector
例程中,主要不同就是接收处理的函数dataIndCB()
里面处理的数据内容更多。
更多数据收发的细节见我用 VISIO 2016 绘制的关系图。
从百度网盘获取链接: 提取码:87r8
以上就是我学习整理的一些内容,希望能给正遇到此类问题的你带来一些帮助,也欢迎大家在评论区留言,我也会继续和大家交流分享。