由浅入深蓝牙40BLE协议栈开发攻略大全3.docx
- 文档编号:14368829
- 上传时间:2023-06-22
- 格式:DOCX
- 页数:23
- 大小:547.91KB
由浅入深蓝牙40BLE协议栈开发攻略大全3.docx
《由浅入深蓝牙40BLE协议栈开发攻略大全3.docx》由会员分享,可在线阅读,更多相关《由浅入深蓝牙40BLE协议栈开发攻略大全3.docx(23页珍藏版)》请在冰点文库上搜索。
由浅入深蓝牙40BLE协议栈开发攻略大全3
本系列教程将结合TI推出的CC254xSoC系列,讲解从环境的搭建到蓝牙4.0协议栈的开发来深入学习蓝牙4.0的开发过程。
教程共分为六部分,本文为第三部分:
第三部分知识点:
第十一节串口通信
第十二节Flash的读写
第十三节BLE协议栈简介
第十四节OSAL工作原理
第十五节BLE蓝牙4.0协议栈启动分析
有关TI的CC254x芯片介绍,可点击下面链接查看:
主流蓝牙BLE控制芯片详解
(1):
TICC2540
同系列资料推荐:
由浅入深,蓝牙4.0/BLE协议栈开发攻略大全
(1)
由浅入深,蓝牙4.0/BLE协议栈开发攻略大全
(2)
有关本文的工具下载,大家可以到以下这个地址:
朱兆祺ForARM
第十一节串口通信
在软件开发过程中调试是一个很关键的过程,而调试用的最多的手段就是打印Log,嵌入式平台很少有显示设备,所以我们需要将信息通过串口打印到PC端。
MT254xboard上已经通过RS232芯片将UART0连接到DB9,我们只需要将DB9连接到电脑即可,UART0对应的外部设备IO引脚关系为:
P0_2------RX,P0_3------TX。
我们需要将这两个IO配置为复用功能,CC2540的USART可以配置为SPI模式或者异步UART模式,这里我们需要配置为异步UART模式。
首先配置IO为UART模式:
PERCFG&=~0x01;//配置UART为位置1
P0SEL=0x3c;//P0_2,P0_3,P0_4,P0_5用作串口功能
P2DIR&=~0XC0;//P0优先作为UART0
配置UART0寄存器,将UART0配置为8N1模式,波特率为115200。
U0CSR|=0x80;//UART方式
U0GCR|=11;//U0GCR与U0BAUD配合
U0BAUD|=216;//波特率设为115200
UTX0IF=0;//清除中断标志
U0CSR|=0X40;//允许接收
IEN0|=0x84;//开总中断,接收中断
这里采用中断方式来接收串口数据,并在中断中回调应用层的接收处理函数。
#pragmavector=URX0_VECTOR
__interruptvoidUART0_ISR(void)
{
uint8ch;
URX0IF=0;//清中断标志
ch=U0DBUF;
if(NULL!
=RecvCb)//调用回调函数
{
RecvCb(ch);
}
}
为了测试串口的通讯功能,这里我们通过串口接收命令的方式来控制LED的亮灭和蜂鸣器的响和停止,并且显示当前的状态。
根据串口输出提示,发送对应字符可以实现相应功能,并且显示状态。
第十二节Flash的读写
嵌入式系统中需要存储数据,而片内的Flash资源很匮乏,所以我们经常需要使用SpiFlash来存储数据,MT254xboard中板载了一个512Kbyte的Flash,下面我们来驱动此Flash。
上一小节中我们用SPI的方式驱动了LCD12864,这节我们继续用SPI来驱动板载的Flash,
下面我们来检测这个Flash,检测的方法为,全部写入0xAA,然后再读出,对比是否为0xAA,如果是,那Flash是没有问题的,否则Flash可能已经有坏块。
具体的代码见例程,这个过程所需要的时间取决于我们需要检测的区域大小,如果完全检测,则可能需要几分钟的时间。
intmain(void)
{
SysStartXOSC();
LCD12864_Init();//LCD初始化
GD25Q40_Init();//Flash初始化
LCD12864_DisStr(0,“FlashCheck.。
。
。
”);
sprintf(LCDBuf,“FlashID:
%04X”,GD25Q40_ReadID());//读取器件ID
LCD12864_DisStr(1,LCDBuf);
GD25Q40_EraseChip();//擦除整片Flash大约需要10S
LCD12864_DisStr(2,“EraseChipComplete”);
uint32iCnt=0;
//全部写入0xAA
constuint8Write=0xAA;
for(iCnt=0;iCnt { GD25Q40_Write(&Write,iCnt,1);//写入0xAA } //读取Flash内部的值,与写入的值对比 uint8Read; for(iCnt=0;iCnt { GD25Q40_Read(&Read,iCnt,1); if(Read! =Write) { LCD12864_DisStr(3,“FlashError”); break; } } //写入的值与读出的值完全一样 if(iCnt>=CHECK_ADDR_RANGE) { LCD12864_DisStr(3,“FlashCheckSuccess”); } GD25Q40_EraseChip();//再次擦除 while (1); return0; } MT254X蓝牙4.0开发板Flash效果: 第十三节BLE协议栈简介 TI的协议栈分为两部分: 控制器和主机。 对于4.0以前的蓝牙,这两部分是分开的。 所有profile和应用都建构在GAP或GATT之上。 根据这张图,我们从底层开始介绍。 TI的这款CC2540器件可以单芯片实现BLE蓝牙协议栈结构图的所有组件,包括应用程序。 1.1.1PHY层 1Mbps自适应跳频GFSK(高斯频移键控),运行在免证的2.4GHz。 1.1.2LL层 LL层为RF控制器,控制设备处于准备(standby)、广播、监听/扫描(scan)、初始化、连接,这五种状态中一种。 五种状态切换描述为: 未连接时,设备广播信息,另外一个设备一直监听或按需扫描,两个设备连接初始化,设备连接上了。 发起聊天的设备为主设备,接受聊天的设备为从设备,同一次聊天只能有一个意见领袖,即主设备和从设备不能切换。 1.1.3HCI层 HCI层为接口层,向上为主机提供软件应用程序接口(API),对外为外部硬件控制接口,可以通过串口、SPI、USB来实现设备控制。 1.1.4L2CAP层 L2CAP层提供数据封装服务,允许逻辑上的点对点通讯。 1.1.5SM层 SM层提供配对和密匙分发,实现安全连接和数据交换。 1.1.6ATT层 ATT层负责数据检索,允许设备向另外一个设备展示一块特定的数据称之为属性,在ATT环境中,展示属性的设备称之为服务器,与它配对的设备称之为客户端。 链路层的主机从机和这里的服务器、客服端是两种概念,主设备既可以是服务器,也可以是客户端。 从设备毅然。 1.1.7GATT层 GATT层定义了使用ATT的服务框架和配置文件(profiles)的结构。 BLE中所有的数据通信都需要经过GATT。 GATT负责处理向上与应用打交道,其关键工作是把为检索工作提供合适的profile结构,而profile由检索关键词(characteristics)组成。 1.1.8GAP层 GAP直接与应用程序或配置文件(profiles)通信的接口,处理设备发现和连接相关服务。 另外还处理安全特性的初始化。 对上级,提供应用程序接口,对下级,管理各级职能部门,尤其是指示LL层控制室五种状态切换,指导保卫处做好机要工作。 1.2TI协议栈源码介绍 在第二章我们讲解了源码的安装,这里我们就来剖析源码的结构。 打开协议栈目录我们可以看到下图: BLE源码: 目录名 内容说明 Accessories一些工具和已经编译好的Hex文件此文件夹中有Btool的安装包、USB-CDC的驱动。 ComponentsHal驱动,OSAL源码、协议栈通用源码此文件夹是OSAL各层组件的实现 Documents帮助文档协议栈说明文档,这是学习BLE最好的资料。 Projects工程文件这里有一些TI的Demo,我们开发一般是在Demo的基础上进行 这里TI给出了很多Demo,这些例程都是经过了SIG评审的,ble文件夹中有很多工程文件,有些是具体的应用,例如BloodPressure、GlucoseCollector、GlucoseSensor、HeartRate、HIDEmuKbd等都为传感器的实际应用,有相应标准的Profile。 其中有4种角色: SimpleBLEBroadcaster、SimpleBLECentral、SimpleBLEObserver、SimpleBLEPeripheral。 他们都有自己的特点。 1.Broadcaster广播员——非连接性的信号装置 2.Observer观察者——扫描得到,但不能链接 3.Peripheral从机——可链接,在单个链路层链接中作为从机 4.Central主机——扫描设备并发起链接,在单链路层或多链路层中作为主机。 我们的讲解将围绕这主机和从机进行。 因为其它的设备都是基于这两种设备扩展开来的。 第十四节OSAL工作原理 蓝牙为了实现同多个设备相连,或实现多功能,也实现了功能扩充,这就产生了调度问题。 因为,虽然软件和协议栈可扩充,但终究最底层的执行部门只有一个。 为了实现多事件和多任务切换,需要把事件和任务对应的应用,并起一个名字OSAL操作系统抽象层。 OSAL管理的实现 如果实现软件和硬件的低耦合,使软件不经改动或很少改动即可应用在另外的硬件上,这样就方便硬件改造、升级、迁移后,软件的移植。 HAL硬件抽象层正是用来抽象各种硬件的资源,告知给软件。 其作用类似于嵌入式系统设备驱动的定义硬件资源的h头文件。 BLE低功耗蓝牙系统架构: OSAL作为调度核心,BLE协议栈、profile定义、所有的应用都围绕它来实现。 OSAL不是传统大家使用的操作系统,而是一个允许软件建立和执行事件的循环。 软件功能是由任务事件来实现的,创建一个任务事件需要以下工作: 1.创建taskidentifier任务ID; 2.编写任务初始化(taskinitializationroutine)进程,并需要添加到OSAL初始化进程中,这就是说系统启动后不能动态添加功能; 3.编写任务处理程序; 4.如有需要提供消息服务。 BLE协议栈的各层都是以OSAL任务方式实现,由于LL控制室的时间要求最为迫切,所以其任务优先级最高。 为了实现任务管理,OSAL通过消息处理(messageprocess),存储管理,计时器定时等附加服务实现。 系统启动流程: 为了使用OSAL,在main函数的最后要启动一个名叫osal_start_system的进程,该进程会调用由特定应用决定的启动函数osalInitTasks(来启动系统)。 osalInitTasks逐个调用BLE协议栈各层的启动进程来初始化协议栈。 随后,设置一个任务的8bit任务ID(taskID),跳入循环等待执行任务,系统启动完成。 1.任务优先级决定于任务ID,任务ID越小,优先级越高 2.BLE协议栈各层的任务优先级比应用程序的高 3.初始化协议栈后,越早调入的任务,任务ID越高,优先级越低,即系统倾向于处理新到的任务 每个事件任务由对应的16bit事件变量来标示,事件状态由旗号(taskflag)来标示。 如果事件处理程序已经完成,但其旗号并没有移除,OSAL会认为事情还没有完成而继续在该程序中不返回。 比如,在SimpleBLEPeripheral实例工程中,当事件START_DEVICE_EVT发生,其处理函数SimpleBLEPeripheral_ProcessEvent就运行,结束后返回16bit事件变量,并清除旗语SBP_START_DEVICE_EVT。 每当OSAL事件检测到了有任务事件,其相应的处理进程将被添加到由处理进程指针构成的事件处理表单中,该表单名叫taskArr(taskarray)。 taskArr中各个事件进程的顺序和osalInitTasks初始化函数中任务ID的顺序是对应的。 有两种,最简单的方法是使用osal_set_event函数(函数原型在OSAL.h文件中),在这个函数中,用户可以像定义函数参数一样设置任务ID和事件旗语。 第二种方法是使用osal_start_timerEx函数(函数原型在OSAL_Timers.h文件中),使用方法同osal_set_event函数,而第三个以毫秒为单位的参数osal_start_timerEx则指示该事件处理必须要在这个限定时间内,通过定时器来为事件处理计时。 类似于Linux嵌入式系统内存分配C函数mem_alloc,OSAL利用osal_mem_alloc提供基本的存储管理,但osal_mem_alloc只有一个用于定义byte数的参数。 对应的内存释放函数为osal_mem_free。 不同的子系统通过OSAL的消息机制通信。 消息即为数据,数据种类和长度都不限定。 消息收发过程描述如下: 接收信息,调用函数osal_msg_allocate创建消息占用内存空间(已经包含了osal_mem_alloc函数功能),需要为该函数指定空间大小,该函数返回内存空间地址指针,利用该指针就可把所需数据拷贝到该空间。 发送数据,调用函数osal_msg_send,需为该函数指定发送目标任务,OSAL通过旗语SYS_EVENT_MSG告知目标任务,目标任务的处理函数调用osal_msg_receive来接收发来的数据。 建议每个OSAL任务都有一个消息处理函数,每当任务收到一个消息后,通过消息的种类来确定需要本任务做相应处理。 消息接收并处理完成,调用函数osal_msg_deallocate来释放内存(已经包含了osal_mem_free函数功能)。 为了实现更好的移植性,协议栈将硬件层抽象出了一个HAL硬件抽象层,当新的硬件平台做好后,只需修改HAL,而不需修改HAL之上的协议栈的其他组件和应用程序。 第十五节BLE蓝牙4.0协议栈启动分析 TI的这款CC2540/CC2541器件可以单芯片实现BLE蓝牙协议栈结构图的所有组件,包括应用程序。 从这章开始我们来剖析协议栈源码,我们选用SimpleBLEPeripheral工程开刀,这是一个从机的例程,基本的工作是对外广播,等待主机来连接,读写展示的属性。 首先打开工程文件,打开后可以看到整个工程的结构。 我们按照系统的启动顺序来一步一步走,我们都知道在C代码中,一般启动的首个函数为main,这个函数在SimpleBLEPeripheral_Main.c中,打开文件,可以看到这个文件只有一个main函数和一个函数的申明,我们暂时不理会那个申明的函数,先看main都做了些什么工作: Intmain(void) { /*Initializehardware*/ HAL_BOARD_INIT();//硬件初始化 //InitializeboardI/O InitBoard(OB_COLD);//板级初始化 /*InitialzetheHALdriver*/ HalDriverInit();//Hal驱动初始化 /*InitializeNVsystem*/ osal_snv_init();//Flash存储SNV初始化 /*InitializeLL*/ /*Initializetheoperatingsystem*/ osal_init_system();//OSAL初始化 /*Enableinterrupts*/ HAL_ENABLE_INTERRUPTS();//使能总中断 //Finalboardinitialization InitBoard(OB_READY);//板级初始化 #ifdefined(POWER_SAVING) osal_pwrmgr_device(PWRMGR_BATTERY);//低功耗管理 #endif /*StartOSAL*/ osal_start_system();//NoReturnfromhere启动OSAL return0; } 通过代码我们可以看到,系统启动的过程,主要是做了一些初始化,如果开启了低功耗,则还需要开启低功耗管理。 我们先不去理会初始化做了什么,但是我们知道在main函数的最后启动了OSAL,那么我们就进去看看OSAL是如何运作的。 在IAR中如果需要跳转到某个函数或变量的定义,可以在此函数名中右击然后选择GoToDefinition……就可以调到相应的定义。 voidosal_start_system(void) { #if! defined(ZBIT)&&! defined(UBIT) for(;;)//ForeverLoop #endif { osal_run_system(); } } 这里看到我们进入了一个死循环,并且一直调用osal_run_system(),那我们再进入此函数。
left;”>voidosal_run_system(void)
{
uint8idx=0;
#ifndefHAL_BOARD_CC2538
osalTimeUpdate();//定时器更新
#endif
Hal_ProcessPoll();//Hal层信息处理
do{
if(tasksEvents[idx])//Taskishighestprioritythatisready.
{
break;
}
}while(++idx if(idx { uint16events; halIntState_tintState; HAL_ENTER_CRITICAL_SECTION(intState);//进入临界区 events=tasksEvents[idx]; tasksEvents[idx]=0;//CleartheEventsforthistask.清除事件标志 HAL_EXIT_CRITICAL_SECTION(intState);//退出临界区 activeTaskID=idx; events=(tasksArr[idx])(idx,events);//执行事件处理函数 activeTaskID=TASK_NO_TASK; HAL_ENTER_CRITICAL_SECTION(intState);//进入临界区 tasksEvents[idx]|=events;//Addbackunprocessedeventstothecurrenttask. HAL_EXIT_CRITICAL_SECTION(intState);//退出临界区 } #ifdefined(POWER_SAVING)//没有事件发生,并且开启了低功耗模式 else//Completepassthroughalltaskeventswithnoactivity? {//系统进入低功耗模式 osal_pwrmgr_powerconserve();//Puttheprocessor/systemintosleep } #endif /*Yieldincasecooperativeschedulingisbeingused.*/ #ifdefined(configUSE_PREEMPTION)&&(configUSE_PREEMPTION==0) { osal_task_yield(); } #endif } 在这里可以看到这个OSAL的核心,整个OSAL通过检测每个任务是否有事件发生,如果有则执行相应的任务,处理相应的事件。 如果没有事件需要处理并且开启了低功耗模式,则系统就会进入低功耗模式。 这里有一个很关键的地方,OSAL是如何知道哪个事件需要哪个任务来处理呢? events=(tasksArr[idx])(idx,events);//执行事件处理函数 我们看这里有一个很关键的数组tasksArr,很显然,这是一个函数指针数组,我们看看它的定义。 constpTaskEventHandlerFntasksArr[]= { LL_ProcessEvent,//task0 Hal_ProcessEvent,//task1 HCI_ProcessEvent,//task2 #ifdefined(OSAL_CBTIMER_NUM_TASKS) OSAL_CBTIMER_PROCESS_EVENT(osal_CbTimerProcessEvent),//task3 #endif L2CAP_ProcessEvent,//task4 GAP_ProcessEvent,//task5 GATT_ProcessEvent,//task6 SM_ProcessEvent,//task7 GAPRole_ProcessEvent,//task8 GAPBondMgr_Pro
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 由浅入深 蓝牙 40 BLE 协议 开发 攻略 大全