BTM02E-08蓝牙模块
第一部分 BTM02E-08蓝牙模块
一、模块的基本参数
1、 特性
支持蓝牙
1.1、1.2
输出功率等级为class2
支持USB1.1和UART串口
低电压供电2.7V~3.6V
供电电压3.3V
内置8MbitFLASH存储器
尺寸:mm 14.525.02.3,,
2、 描述
采用CSR公司的蓝牙芯片作为核心芯片,并配以8MbitFLASH存储器,SPI接口,UART接口,USB接口,PCM音频接口,16MHz晶振,带通滤波器,天线匹配电路等。 并不是所有的应用都适合嵌入式应用,因为应用程序和固件栈必需共享片上RAM,以RFCOMM为基础的应用可用的RAM大概为几百字节。应用代码必需控制在32K字以内。 二、BlueCore2-External蓝牙芯片工作原理
8MBitFLASH
RAMSPI
天线2.4GHz RFDSPI/OUSB
UARTMCU
BlueCore2-External内
部资源
PI/O
BTM02E-08板晶振
上资源
1、 关键特性
片外8Mbit的存储器可以灵活的用于系统解决方案。
片上32Kbit的RAM用来实现蓝牙无线传输中数据交换的缓冲
4M波特率的异步串行接口
片上可运行多种协议栈:标准HCI协议栈,全部内嵌的RFCOMM协议栈等 96Ball-VFBGA封装
2、管脚使用
USB接口不用时应接地
所有IO管脚被配置成输入时,复位后将变成弱下拉
不用的AIO(可编程模拟IO)管脚不使用时应该悬空
3、 内部功能模块
USB:用来与其他数字设备的通用接口,BlueCore02扮演外围设备角色,来回应主机的请求 SPI(异步串行接口):用来与其它数字设备的接口,SPI口可以用来调试软件和烧写外部
FLASH存储器
UART:与其他设备通信的通用同步串口
可编程IO:共有15个可编程管脚(12个数字的和3个模拟的),有运行在片上的固件来控制
4、 CSR 软件栈
CSR为BlueCore2提供了片上运行的固件(Firmware),片上运行的协议栈仍需HCI的支持 1)HCI Stack
该Firmware提供了一组特性及支持相应特性的HCI
在标准蓝牙协议上做了扩展:
, 支持蓝牙串口协议——BCSP,一种以UART为物理层,可靠的特有协议(可选) , 提供了额外的50个系列HCI指令
, 可以访问可编程IO端口
, 访问蓝牙时钟
, 访问固件上的随机数产生器
, 动态配置UART(波特率,校验,数据位)
, 无线传输使能:一个简单的命令,与一个专门的物理开关有关,确定radio是否可以传
输数据
, 可以读取芯片外部管脚上的电压,可以运行在VM或主机上来电池的监测 , 提供一组BCCMD命令,来访问片上的“persist store”,该数据库存储了器件的蓝牙地
址,器件class,radio配置,USB/DFU常数等(Device Firmware Upgrade)
, UART上break命令的应用:用于重启,开机进入低功耗,唤醒主机 , 提供一组“radio test”或者BIST 指令,直接控制片上的radio , 虚拟机。固件提供虚拟机环境,来运行应用代码。虚拟机需要BlueLab和内嵌协议
RFCOMM(或者L2CAP,SDP),运行在VM上的协议可以完成通过可编程IO点亮LED
的简单工作
2)BlueCore RFCOMM Stack软件栈(将以Firmware的形式转载到片上) 直到RFCOMM层的协议都运行在片上
, 与主机接口:RS232,RFCOMM协议(如何建立无线串口连接,) , 服务发现协议
3)虚拟机栈
, 所有代码都在片上运行,代码在固件旁边运行(代码与固件并行运行) , 用户可以呼叫固件完成各种操作
, 用户的应用代码与堆栈是分开的,防止对堆栈造成影响,可以随时修改应用代码 4) 面向应用的应用软件的编写
当更高层的蓝牙协议栈像固件一样在片上运行时,底层将会通过片上运行的一个UART的驱动程序提供L2CAP,RFCOMM,SDP的API给高层
5、片外存储器
提供了8M位的存储空间,字长为16位。用来存储代码和配置数据
6、UART接口
, 信号机制。UART接口为与其他串口器件提供了一种简单的机制。采用RS232协议电
平为0V~VDD_PADS(IO电路的电压),要求额外的电平转换芯片。
四种信号中,RX和TX用来传输数据,RTS和CTS可以用来做流控(低电平有效),采用CMOS工艺。UART的波特率等参数设置有软件来完成。
, 复位。UART接口可以发送一BREAK信号复位BlueCore芯片,即RX信号线被置低的
时间超过在PS 中的Key:PSKEY_HOST_IO_UART_RESET_TIMEOUT设定的值,可
以用来主机复位系统。同样道理,芯片也可以发送一BREAK信号来唤醒主机。
, 波特率。可以通过公式计算结果设置任意波特率,也可以设置标准波特率。
PSKEY_UART_BAUD_RATE(0X204)
7、SPI(串行外围设备接口)
BlueCore使用有16位数据位和16位地址位的SPI,当处理器运行时或被终止时将会通过SPI交换数据
, 指令周期。芯片为从设备,从SPI_MOSI接受命令,从SPI_MISO发送数据
SPI复位后,CSB必需置低。写入时,MOSI上的数据在时钟的上升沿写入BlueCore。读出时,在CLK的下降沿读出。CSB变高时结束。
, 写入过程。线串行写入8位的命令字(写指令为00000010),接着是16位地址,然后
16位的数据写入该地址中。芯片内地址自动加1,接下来的数据将连续的存储于FLASH
中。
, 读出过程。同上,先写入读命令字C[15:0](00000011),然后写入读地址A[15:0],在
MISO上将输出16位的校验字T[15:0],跟着输出16位数据D[15:0]。校验字包括写入
的命令字[7:0],和读出数据对应的地址[15:8],可以有效克服读出错误地址的内容。读
过程地址自动加1.
8、PIO
可由片上运行的应用程序或者HCI来访问。
, PIO[0]/RXEN。复用端。由PS KEY 的PSKEY_TX/RX_PIO_CONTROL(0x209)来决定,
可以用来作为radio前端的传输开关。
, PIO[2]/USB_PULL_UP(1)。复用端。UART中,可用来做PIO。USB中,用来做上拉。
外部RAM应用中,用来做片选。
, PIO[3]/USB_WAKE_UP。USB中,由PS KEY 中的PSKEY_USB_PIO_WAKEUP(0x2cf)
, PIO[4]/USB_ON(1)。
, PIO[5]/USB_DETACH(1)
, PIO[6]/CLK_REQ。由PS Key中的 PSKEY_CLOCK_REQUEST_ENABLE(0x246)决定。
芯片处于深度睡眠时,管脚可以接低,当有一个时钟请求时接高,时钟必须有4ms的
上升沿。
9、USB
芯片包括一全速的USB接口(12Mbits/s),能够直接驱动USB电缆而无需USB收发芯片。USB的数据线D+和D-连接到USB内部的USB ,,缓冲器中,因此输出阻抗较低。为了与,,,电缆的特性阻抗相匹配,在,,和,,上必须串联电阻。
,,,协议规定,,,数据线输出的最小高电平为,.,,,为满足此要求,
VDD_USB供电端至少为,.,,
自供电模式。电路将由自己的供电系统供电,而不是来自于,,,电缆的,,,,
(,,),这种模式,,,,只会提供很小的电流(小于,.,,,)。要求,,,
,要经过一电阻网络分压,以便于当,,,,变高时(连接到了,,),,,,,
,,,可以检测到。
USB_ON端可以由任一PIO来充当,但是必需在PSKEY_USB_PIO_VBUS中注明所选
的PIO号
Detach和Wake_up信号
BlueCore可以提供两个额外的信号给主机:USB_DETACH和USBWAKE_UP。这两个
而外的信号在USB电缆和协议中是不存在的,但是在嵌入式系统应用中却非常有用,
因为嵌入式应用中实际的USB电缆是不可见的(直接用PCB布线相连),所以不知道
什么时候USB电缆应该建立连接。
Usb_detach:置高时将会使USB_D+和USB_D-变为高组态,相当于断开总线连接。置
低时将恢复连接,并等待主机恢复。
USB_WAKE_UP:用来唤醒主机。只有USB_DETACH有效时才有意义,置为高电平时
用来唤醒主机,以重新开始传输数据。它是用来替换软件中USB WAKE_UP消息的,
当断开连接时不能使用。
三、外部Flash存储器
蓝牙的配置参数被存储到Flash的PS区,可以通过程序包里的PSTool工具来对其进行操作。
四、RS232电平转换芯片
为主机与BlueCore的3.3V逻辑电平提供接口
五、复位电路
高电平复位。上电时,复位管脚将置高。可以确保电压稳定在3.3V之前芯片处于复位状态。否则,当电压从1.8V降至1.6V时,芯片将尝试着从外部存储器获得指令,而此时Flash(针对3.3V供电的Flash)的供电可能没有准备好,Flash将返回错误的指令给BlueCore,这将会导致固件的配置失败。(电压升至1.6V以上时芯片开始工作,而3.3V还未达到,Flash还不能正常工作)
当使用1.8V Flash时,复位电路可以取消,把复位管脚直接接地,因为Flash和BlueCore将同时达到1.8V 电平(3.3V和1.8V 可能不会同时达到)
(根据CSR公司提供的模块资料,在复位管脚处模块已经通过一10K的下拉电阻接地。根据复位要求,应尽量延长复位时间,以确保电源供电稳定。应接一大点电容) 平时可以直接接地也可以通过一个1K电阻,要保证接地可靠。
六、开关
开关的反跳在软件中实现。
七、PIO
不用的IO管脚可以悬空,因为在IC内部有弱下拉电阻。在模块中,未用的AIO通过一100nF电容接地。
八、LED
可以由芯片的管脚直接驱动
九、PCB
SPI应留出相应的监测点,以便在卡过程中编程,校准,和查询芯片。
十、数据速率
最大数据传输速率受到rs232电平转换速率限制,比特率的设置在PS KEY中。要根据所使用的 RS232转换器的实际速率来设置PS_KEY中的速率值。
十一、低功耗
节点应用。电缆替代例子的底层固件层中不能应用深度睡眠模式。
MAX3243和MAX3228当没有有效的RS232信号时会自动进入低功耗模式关闭器件。将FORCEON(232转换芯片的引脚)与3.3V相连可以关闭低功耗特性(此处加入跳线使该引脚可以接到0V提供选择)。当将引脚FORCEOFF接3.3V时,将会使能自动低功耗模式。在RS232芯片输入端没有有效数据输入时232芯片将会被关闭进入低功耗模式。(将FORCE_OFF接3.3VCC使能自动低功耗模式,而通过控制FORCEON的高低来最终确定是否真正的使用低功耗模式)。
232芯片输出的信号INVALID像BlueCore指明是否有有效信号,从而可以唤醒BlueCore。INVALID为高电平时表明输入端输入了有效数据。INVALID连接到了IO7,向BlueCore提供一个高电平来唤醒BlueCore。
如果一个PIO被设置为输入,输入的低电平必须是一个有效地低电平,RS232芯片输出的INVALID信号有可能不是一个有效地低电平,所以当没有有效信号输入时,RS232芯片的输出端变为高组态,需要加入下拉电阻,以确保PIO7输入为低电平,是芯片进入睡眠状态(PIO中有弱下拉)。
为能使PIO能产生中断,需将其设置为输入并合理设置PSKEY_DEEP_SLEEP_PIO_WEEK,如果使用VM可以使用VmDeepSleepEnable(1)来设置(VM应支持对PS的修改)。
十二、软件栈
LM(Link Manager),LC(Link Controller),L2CAP,SDP和RFCOMM将以二进制文件的形式提供(是固件),Connection manager library 和串口应用以C代码的形式提供,在BlueLab中编译通过后,在VM中运行(VM基于的是解释机制,编译一条执行一条,)。 1、 将要下载的固件编译成二进制文件并下载到Flash中,通过SPI下载。 2、 设置PS,通过PSTool
十三、配对过程
1、 RS232上电
2、 如果已经配对,它将自动尝试建立连接,应用里存储了配对的设备地址。 3、 如果上述过程失败,或者没有地址入口,从设备将会被动地等待连接请求,而主设备将
会寻找从设备。一旦连接成功的建立,另一方的地址将会存入PS。
第二部分 BlueLab软件编程
从功能上可以把文件库大致分为:操作系统、驱动程序、用户接口API、蓝牙协议栈、连接管理库和一些具体的应用程序。
理解Tasks,Timers,Schedulers
在一个蓝牙系统中,有很多工作需要维护,连接管理的消息需要处理,输入数据必须处理,输出的数据必需发送到基带管理器上,如果要通过主机控制器与主机交互还必须编址等,所以处理器要分出不同的时间来处理不同的任务(即多线程)。
每一个任务有他自己的调用堆栈,自己的IO队列,都可以从处理器得到回应。有一个任务用来协调其他任务,叫做核心任务(kenel),也被看成调度(Schedulers)。BlueCore系统采取轮询的运行方式,处理器轮流运行任务(而非抢占式)。
当任务阻塞时,调度停止运行该任务(当一个任务调用一个用于等待事件发生的系统调用时,任务将进入阻塞状态)。所以当一个任务永远不被阻塞时,其他的任务将永远不会有机会运行。
遇到上面的情况,如果你的应用程序不能够被阻塞,就需要关掉整个蓝牙功能,但是在蓝牙堆栈里有很多实时性的操作要求运行,所以不能够这样做。为了解决这个问题,BlueCore提供了一种环境叫做VM,来保护堆栈的正常运行,而不会受到占用大量时间的应用程序的影响。调度并不是直接调用应用程序,而是调用虚拟机,然后通过虚拟机解释器的解释,虚拟机可以执行很多操作。这样应用代码如果是一个无限循环虚拟机仍旧可以运行应用程序中预置的指令,所以无限循环不会无限运行下去。
虚拟机调度
片上的调度器在给其他待处理过程分配处理时间之前仅允许处理有限的虚拟机命令。这意味着不能够指望运行在虚拟机上的应用程序能快速的作出反应。
一个无限的while循环会阻止芯片进入休眠状态而增加功耗。而且虚拟机并不会一直运行该循环,而是会在运行一段时间以后转而去处理蓝牙协议栈和应用程序的其他任务。(虚拟机调度是由scheduler实现的,调度程序的代码可见,虚拟机则只提供了接口函数)
for(;;)
{
uint16 del = sched1() ;//有事件发生处理事件,没有事件发生返回
VmWait(del) ;//应用程序运行在虚拟机上,调用此函数时,虚拟机进入等待状态,上面运行的所用程序都将被阻塞
//从而使蓝牙堆栈上的实时性较强的操作的以进行,而不受应用程序的影响
}
使用bluelab库
Bluelab提供了各种库,库里提供了支持basic C的函数。当连接时,所有目标文件都会被用到,而且缺少的符号将从库中导入。每一个符号都是从最开始的库(按照命令指定的顺序)获得的。也就是说应用程序的makefile必需位于库前重写。
Scheduler依赖于message和timer库。一些应用程序要求scheduler,但是并不一定这两个库东需要。这种情况可以用这两个库的简化版来代替,将会占用更少的代码和数据空间。 应用程序框架、连接管理器、调度器、Timer、BlueStack、I2C、消息库和服务发现库需要被解释执行,而剩余的库将按原有模式运行,不必经过VM的解释器。
库分为三种:
1) 基本库。提供支持运行和调试C代码的功能。
2) CSR库。
3) 应用程序库。提供对运行在BlueCore上的应用程序的支持。这些库的源代码在src/lib
中。可以通过在当前目录下输入make install命令来重建和安装。这就可以使库代码的
调试可以像应用程序代码调试那样进行源码级的调试。
BlueCore上运行的程序
为了在片上运行最终的应用程序,必须将它和一个完整的蓝牙协议栈合并在一起。开发工具包预带了固件映像,它允许芯片运行底层的蓝牙协议栈。
图中显示了一个应用程序映像与缺省的Casira映像的不同。应用程序映像有额外的协议栈层:L2CAP,RFCOMM和SDP。这些是支持串行端口
所需的协议栈层,也用于支持基于串行端口规范的简单规范。这些协议栈层是由Mwzoe编写的,统称为BlueStack。在BlueLab层之上,用一个连接管理器来处理RFCOMM连接的管理。BlueLab中带有连接管理器程序库,使连接管理更加方便,但也不是非用他不可,可以自己别写连接管理器程序。
在应用程序顶层是VM。VM使得连接管理器、库和应用程序软件运行在受保护的内存空间中,应用程序软件被编译成虚拟机执行代码。当代码运行时,虚拟机检查每条指令是否有非法的内存管访问行为。通过这种方式,VM确保了应用程序软件不会干扰蓝牙协议栈的正常运行。
在调试器下运行程序时,必须将RFCOMM载入到芯片中,芯片才能驱动无线电。可是此时的应用程序是运行在PC机上的调试器下,所以也就不可能将应用程序的映像加载到芯片中。解决办法是向芯片中下载一个空的映像——这是一个包含虚拟机的固件映像,但是不包含有效的应用程序。
主蓝牙协议栈的运行总是优先进行并连续不可间断,直到有多余的处理带宽VM才被调用。BlueStack协议栈基于消息驱动,层与层之间使用消息传递方法,应用程序调度执行消息不为空的任务。事件驱动中的事件包括BlueStack事件、PIO事件、RFCOMM源端及目的端事件等。所有事件的处理以及消息队列中的消息处理均由调度程序来执行。调度程序由高层应用程序启动,而个事件及消息处理分布在各软件层中。在2.1版本以后,可以向片上加载应用程序,当连接到调试器时,片上应用程序将自动关闭。
应用程序库
为运行在BlueCore上的应用程序提供了支持。这些库的源码位于src/lib目录下 连接管理器
连接管理器处理蓝牙协议栈中RFCOMM层以下所有的层。如果没有连接管理器,则需要建立ACL链路,为RFCOMM配置链路,建立并配置L2CAP链路,最后才能建立RFCOMM链路。使用连接管理器,只需一个调用就可以让所需的所有层完成配置。 大多数发送数据的应用程序都愿意使用RFCOMM连接,但是对于那些需要从更底层级别进入的程序,BlueLab连接管理器除了允许他们发送RFCOMM包以外,还允许他们发送L2CAP包。L2CAP是蓝牙协议栈中应用程序可以将数据送往的最低级别,因为蓝牙链路中所有的用户数据都必须作为L2CAP包发送。
数据包是通过连接来发送的么让每个连接必需指向某个对等的设备,所以很自然,在发送任何数据包之前,连接管理器必须与对等设备配对。
在mssage.h中,任务/消息标识符0视为连接管理器保留的,而标识符1是为应用程序框架保留的。其实际效果是,无论程序何时向连接管理器发送消息,他都是向消息队列0发送,而从消息管理器返回的消息总是返回到消息队列1。不管消息的内容是控制信息还是数据包,这个关于消息队列数字的规则不变。
连接管理器的消息都在cm_rfcomm.h中声明。连接管理器本身在CM_RFCOMM库libcm_rfcom.a中实现。
初始化并打开连接管理器
组成BlueStack和应用蓝牙协议的库必需包含进系统中。所以这些库将自动启动以确保协议栈的正常运行。而连接管理器不是蓝牙协议栈的组成部分,是可选的独立库,所以他不会自动启动。如果想使用连接管理器,必需通过一些调用初始化并打开连接管理器。首先,通过发送CM_INIT_REQ消息来初始化连接管理器。如果连接管理器成功注册了BlueStack,它将返回CM_INIT_CFM消息。这些消息仅仅是启动连接管理器的运行,所以两个消息都没有任何参数。
连接管理器运行以后,需要将应用程序的相关信息告诉连接管理器。BlueCore经常将设备等级(class of device CoD)设置成混杂型。这可能并不适用于当前应用。例如耳机应用程序,要把CoD中Major Device Class设置为Audio,并使耳机规范适应次要设备。这样正确的设置就很重要,因为CoD作为查询的回复被发送出去,然后被其他发现设备的应用程序利用,这时包含CoD的查询回复很有可能被该查询设备所屏蔽。所以,如果CoD不能很好的反应应用程序所具有的功能,其他的应用程序很可能甚至不会向使用者报告这个设备的存在。 还需要让连接管理器知道要用到的服务列表,来描述应用程序中所提供的服务。这项工作处理完以后,连接管理器将会专注于处理关于这些服务的询问,而不用应用程序进行额外的干预。
应用程序在CM_OPEN_CFM(这个消息已经不推荐使用,详见/docs/cm_rfcomm)中向连接管理器传递CoD和服务
信息。
typedef struct
{
uint8 * serviceRecord; /* pointer to service record */
uint16 sizeServiceRecord;
uint32 classOfDevice;
} CM_SERVICE_REGISTER_REQ_T; serviceRecord参数是指向一个动态分配的内存的指针,这个内存用于描述应用程序服务的服务记录。服务记录必须包含一个RFCOMM信道(channell)的空入口,供应用程序的服务使用。换句话说,UUID(universal unique identifier)是由后面的三个UNIT型数来确定的。信道会由连接管理器进行填写。sizeServiceRecord参数是serviceRecord完整的大小,classOfDevice参数指明了设备的种类,用来回复别的设备针对本设备的查询操作。在BlueLab28中classOfDevice参数已不适用,仅赋给值0,取而代之的是由CM_WRITE_COD_REQ
消息来注册CoD信息。这种变化是由于当用户放送多个
CM_SERVICE_REGISTER_REQ消息来注册多个服务时,如果提供了不同的CoD信息,当其他设备查询时本地连接管理器将不知道回复哪个CoD信息,所以有一个单独的消息注册CoD信息。
在打开连接管理器以后,可以把应用程序提到这种状态,但是也可以继续进行操作使用
等特性(Security Manager)。可以告诉安全管理器信任的设备,
安全管理器将会把关于这些设备的信息存放在新人设备数据库中(Trusted Device database)。在数据库中登记的信任设备将可以不经过应用程序的授权与本地设备进行连接并执行操作(已经授权无需再授权了,但是否还需授权并不是自动完成,而是有应用程序来实现的)。
要使用安全管理器(SM),应用程序可以发送CM_ADD_SM_DEVICE_REQ消息,消息中要包含将往SM中添加的可信任设备的详细信息。
typedef struct
{
BD_ADDR_T addr;
uint8 link_key[SIZE_LINK_KEY];
bool_t trust;
} CM_ADD_SM_DEVICE_REQ_T;
Addr参数是将要添加进SM中的设备的地址。Link_key参数同时给出了设备的连接关键字(link key),trust参数如果为TRUE这名设备被信任,FALSE为不信任。如果这一阶段没有连接关键字,将不得不跳过这一步骤。稍后可以通过配对来获得连接关键字,然后调用SM。
除处理消息之外,还需要启动timer系统和scheduler。这些调用应该在配对的两方都要进行。 要知道收到CM_INIT_CFM消息后才能发送CM_OPEN_REQ消息,所以要等待消息的到来。这样也就需要一个消息管理者来检查消息队列并处理事件。
查询
在初始化一个连接之前,可能会需要周围的蓝牙设备。在用户端一般叫做发现设备,而在蓝牙核心协议中叫做查询。查询由CM_INQUIRY_REQ来完成。应用程序中应该指明查询的全部长度(超时)和要求回复的最大数量。在超时(timeout)的范围内连接管理器可能会发起不止一个查询操作。如果达到了设定的最大回复数,查询将被中止,然后向应用程序发送消息,消息中的某些位表明了查询的完成。
查询操作可以得到像蓝牙设备地址和设备类型(CoD)等信息,但是如果想在设备上为用户显示这些信息,就得了解一些关于它们的知识。
(对于电缆替代,没有什么CoD信息能够很好地适合这种应用,所以CoD值用一个无符号的低字节的值是0的数值来表示)
为了让蓝牙芯片发起查询操作,要使用:
typedef struct
{
uint8 max_responses;
Delay inq_timeout;
uint32 class_of_device;
uint16 remote_name_request_enabled; } CM_INQUIRY_REQ_T;
max_responses参数给出了可以接受的最大查询回复数。inq_timeout参数以秒计算的查询过程超时。class_of_device参数作为一个过滤器:连接管理器把仅有具有此CoD的查询回复返回给应用程序。remote_name_request_enabled参数是一个标志,指明对于没有见过的查询结果是否对该未知设备进行设备名查询。 应用程序可以等待CM_INQUIRY_RESULT_IND或CM_INQUIRY_COMPLETE_CFM。在等待事件发生的过程中,应用程序可以允许调度器分配所有时间给其他的任务知道查询制式事件发生。
typedef struct
{
HCI_INQ_RESULT_T inq_result;
uint8 *handles[HCI_LOCAL_NAME_BYTE_PACKET_PTRS];
} CM_INQUIRY_RESULT_IND_T;
Handles参数是一个句柄数组与通过远端名字查询操作得到的远端设备名的指针有关。
struct typedef
{
BD_ADDR_T bd_addr;
page_scan_rep_mode_t page_scan_rep_mode;
uint8_t page_scan_period_mode;
page_scan_mode_t page_scan_mode;
uint24_t dev_class; /* Lower 3 bytes only used */
bt_clock_offset_t clock_offset; } HCI_INQ_RESULT_T;
这些参数直接来源于蓝牙核心协议(在bulelab中核心协议位于D:\BlueLab28\mnt\include\app
\bluestack中)用于HCI查询结果事件。
当所有的查询结果到达后,应用程可以得到CM_INQUIRY_COMPLETE_CFM_T消息 typedef struct
{
inquiry_status_t status;
} CM_INQUIRY_COMPLETE_CFM_T;
typedef enum
{
CmInquiryComplete,
CmInquiryCancelled,
CmInquiryMaxResponsesReached } inquiry_status_t;
从Sutatus参数中可以得知为什么查询完成。如果该参数被设置成CmInquiryComplete,表明查询超过超时(timeout)设定。如果被设置成CmInquiryCancelled表明查询是被中途取消。如果被设置成CmInquiryMaxResponsesReached表明达到了指定的最大回复数。
在这里,由于资源的限制不得不对超时和最大回复数进行设定。第一,设定超时是避免永久
的查询耗费电能。第二,设定最大回复数避免过多的回复和处理对有限的系统内存的占用。
配对
在查询过程之后,应用程序会找到可供连接的设备,在建立实际连接之前,还要经过一个过程:配对。配对过程建立了一个连接关键字(link key),又来加密蓝牙连接上交流的数据。连接关键字也可以用来授权其他的设备,也就是保证连接到想要的设备。
首先,要用CM_PAIR_REQ消息让连接管理器与一个设备配对。
typedef struct
{
role_t role; /* are we going to be master or slave? */
Delay timeout;
bool_t authentication;
BD_ADDR_T bd_addr;
} CM_PAIR_REQ_T;
typedef enum
{
CmMaster,
CmSlave
} role_t;
Role参数被设置成CmMaster或者CmSlave,以确定本地设备扮演的是什么角色。Timeout参数给出取消配对给出的时间限制。Authentication参数是一个布尔型标志,TRUE表明在连接中将要使用授权功能。bd_addr参数是欲与之配对的远端蓝牙设备地址,仅在初始配对以尝试建立连接时应用。
共享的连接关键字用一个PIN码来建立,PIN码必须由连接的两端分别单独输入。对于没有用户交互接口的设备,PIN码可以通过编程设置,这种PIN码将是固定的。带有固定PIN码的设备交给用户使用时必需附带还有PIN码的文档,以便与其他设备互联时可以输入同样的PIN码。
连接管理器需要从应用程序中得到PIN码,为了实现这一点,连接管理器将会向应用程序发送PIN码请求CM_OIN_CODE_REQ消息:
typedef struct
{
BD_ADDR_T addr;
} CM_PIN_CODE_REQ_T;
在该消息中,包含有一个蓝牙设备地址,当应用程序中含有多种设备的不同PIN码时,应用程序可以根据这个地址来查询的到相应的PIN码。这个地址还可以用来让用户知道是那个设备在要求与之连接。
应用程序要发送CM_PIN_CODE_RES作为上面消息的回应。
struct typedef
{
BD_ADDR_T addr;
uint8 pin_length;
uint8 pin[HCI_MAX_PIN_LENGTH]; } CM_PIN_CODE_RES_T;
Addr参数是欲与之建立连接设备的地址,pin_length是PIN码按位的长度,pin是一个包含PIN码的数组。通过pin_length设置为0可以拒绝PIN码回复。这是因为蓝牙协议中不允许将PIN码长度为0,所以这个非法的字符可以用来表明本地设备不愿意提供PIN码。
如果成功配对,连接管理器将会存储配对设备的地址和连接关键字,并会向应用程序发送配对操作的确认信息CM_PAIR_CFM。
struct typedef
{
pair_status_t status;
BD_ADDR_T bd_addr;
uint8 link_key[SIZE_LINK_KEY]; } CM_PAIR_CFM_T;
typedef enum
{
CmPairingComplete,
CmPairingTimeout,
CmPairingCancelled,
CmPairingFail,
CmPairingNotFinished
} pair_status_t;
如果配对成功Status参数被设置成为CmPairingComplete,不成功被设置为CmPairingTimeout。bd_addr参数被设置为配对蓝牙设备的地址。link_key参数是和对方设备将会用到的连接关键字。
连接关键字将会在授权和编码中应用。可以将连接关键字存储在应用程序中,但是用CM_ADD_SM_DEVICE_REQ消息将连接关键字和设备的相关细节信息传给SM(安全管理器)将会更加有效。
也可以跳过配对过程而直接建立连接。但是,这样就不会创建一个连接关键字,也就不能够使用编码和授权功能,所以这种连接并不安全,传输的数据很容易被其他设备截取。
连接
最后,应用程序请求建立一个数据连接。
如果应用程序是以主设备的身份建立的连接,需要发送一个CM_CONNECT_AS_MASTER_REQ
消息。
typedef struct
{
uint16 max_framesize ;
cm_auth_config_t use;
BD_ADDR_T bd_addr;
uint16 target; /* for sdp search */
Delay timeout;
cm_park_config_t park;
cm_sniff_config_t sniff;
uint16 profile_server_chan; } CM_CONNECT_AS_MASTER_REQ_T; Use参数配置授权和编码。Addr参数提供了将要连接的蓝牙设备的地址。Target参数提供了应用程序想使用的服务的UUID,这个信息将被SDP服务所使用(UUID对应服务列表中的一个值,如果UUID的只存在说明提供此项服务)。Timeout参数给出了在放弃连接尝试前的超时。Park参数配置了用于连接的park参数。Sniff参数配置了用于连接的sniff参数。 CM_CONNECT_AS_SLAVE_REQ用来配置蓝牙芯片接收连接作为从设备。这将开启页扫描(page scanning)。
在上面两个消息中都用到了用于配置授权的结构参数,park和sniff。Park和sniff是两种蓝牙工作模式,sniff——呼吸模式,park——等待模式。
typedef struct
{
uint16 authentication : 1; //1连接授权,0未授权
uint16 encryption : 1; //1使能加密,0关闭加密
} cm_auth_config_t;
typedef struct
{
uint16 max_intval;
uint16 min_intval;
} cm_park_config_t;
typedef struct
{
uint16 max_intval;
uint16 min_intval;
uint16 attempt;
uint16 timeout;
} cm_sniff_config_t;
如果use参数设置成了要求授权和加密,哪么连接关键字是必要的。如果应用程序已经调用CM_ADD_SM_DEVICE_REQ消息注册了连接的另一端设备,这样安全管理器(SM)就已经有了连接关键字,不需要应用程序的额外参与就可以处理授权和加密。 上图中显示了需要连接关键字的一种情况,但是应用程序并没有通过CM_ADD_SM_ DEVICE_REQ来传送连接关键字和设备细节给安全管理器(,,)。在这种情况下,连接管理器不得不从应用程序的到连接关键字,,,向应用程序发送CM_LINK_KEY_REQ消息: typedef struct
{
BD_ADDR_T addr;
} CM_LINK_KEY_REQ_T;
Addr参数是准备给其授权的蓝牙设备地址。而应用程序有这个设备的连接关键字,所以应用程序应该向连接管理器发送CM_LINK_KEY_RES消息,将link key传给CM。 typedef struct
{
bool_t accept;
BD_ADDR_T addr;
uint8 key_val[SIZE_LINK_KEY]; } CM_LINK_KEY_RES_T;
在配对完成以后,就可以开始建立连接。若果使能了授权和加密功能,而且远端设备没有在SM中注册本地设备,本地设备就会收到一个CM_LINK_KEY_REQ 消息,这样就需要用CM_LINK_KEY_RES 消息来向远端设备发送连接关键字。(同理,如果在本地设备中注册了某远端设备(包含其地址和连接关键字),若远端设备发起连接将本地设备就不会向原端设备发送CM_LINK_KEY_REQ 消息)。Accept参数是布尔型标志,确定是接受还是拒绝连接关键字的请求。Addr参数将要被授权的蓝牙设备的地址,key_val参数是对方设备的连接关键字。
如果没有连接关键字,可以启动配对过程产生连接关键字,也可以把accept参数设为FALSE,拒绝建立连接。
连接成功建立或者失败后,CM将发送CM_CONNECT_CFM消息用来通知应用程序连接建立的状态。
typedef struct
{
connect_status_t status;
BD_ADDR_T addr;
uint16 rfc_frame_size;
Source source ;
Sink sink ;
uint16 conn_server_chan; } CM_CONNECT_CFM_T;
typedef enum
{
CmConnectComplete,
CmConnectTimeout,
CmConnectCancelled,//有错误发生
CmConnectDisconnect,//连接后又断开连接
CmConnectDisconnectAbnormal,
CmConnectRemoteRefusal,
CmConnectServiceNotSupported,
CmConnectFailed
} connect_status_t;
Addr参数是目标蓝牙设备的地址。在成功建立基本的ACL连接后,应用程序可以用CM_SCO_CONNECT_REQ消息来添加一个SCO连接。
CM_DISCONNECT_REQ消息用来断开一个连接。如果断开的是SCO连接,则底层的ACL连接仍就存在(在建立SCO连接之前应先建立ACL连接)。 typedef struct
{
link_type_t link_type;
BD_ADDR_T addr;
} CM_DISCONNECT_REQ_T;
link_type参数是将要断开的连接类型,可以是ScoConnection或者RfcommConnection。Addr参数连接另一端设备的地址。
发送数据
连接建立以后就可以发送和接收数据。在CM_CONNECT_CFM中返回的数据源和接受端可以由stream library库中的函数利用,来收发数据。当数据已经到达源端或者离开接收端将产生VM_EVENT_SOURCE和VM_EVENT_SINK事件,调度器将会调用handleSourceEvent
和handleSinkEven t予以处理。
其他消息和事件的使用
连接管理器支持三种指示消息,这三种消息用来异步指示连接状态的变化,或者错误的发生。 连接管理器用CM_CONNECT_STATUS_IND消息通知用户RFCOMM连接状态的变化。 typedef struct
{
connect_status_t status;
BD_ADDR_T addr;
} CM_CONNECT_STATUS_IND_T;
typedef enum
{
CmConnectComplete,
CmConnectTimeout,
CmConnectCancelled,
CmConnectDisconnect,
CmConnectDisconnectAbnormal,
CmConnectRemoteRefusal,
CmConnectServiceNotSupported,
CmConnectFailed
} connect_status_t;
status参数可被设置为各种状态。addr参数是当前指示的连接状态的设备的地址。 typedef struct
{
cm_error_t error;
BD_ADDR_T addr;
} CM_ERROR_IND_T;
error参数是与addr地址对应设备操作过程中发生的错误。错误可能会发生在以下几种情况:
1) 连接管理器繁忙时提出连接请求。
2) 连接管理器繁忙时提出配对请求
3) 连接建立前发送数据
连接管理器提供了一个“取消”请求。用来取消任何配对或者有效连接,所以也不需要任何参数。这个请求消息没有回复消息。但是对配对或连接的回复消息中会包含状态CM_cancelled。
CM_CANCEL_REQ();
Message Library Message库是消息管理模块,负责消息的动态分配、消息的动态管理、消息的提取和动态处理,对应的实现函数为Mssagesched()。所有的消息都可以划分为多个消息队列,每个消息队列成为一个任务。对每个任务的处理在DECLARE_TASK()中说明。 任务的标志符中0和1是保留值,分别代表CM和框架任务(framework)。任何任务可以用sendMsg()向framewok(task 1)发送数据。
Timer
timer库允许应用程序在将来的一段时间里可以自动的调用一系列排队的函数。这些回调可以发生在从函数进入排队起的四个小时内,并且可以周期执行或者执行一次。一次最多可以同时应用30个timer。
指定延时
D_SEC(n)创建了一个以秒为单位的延时。这个延时可以达到四个小时,但是计时是非常粗糙的。
D_mSEC(n)创建了以毫秒为单位的延时。这个延时最大长度为30秒,但是精度高。
D_NEVER制定了一个非常长的延时并且不会再调用。
D_IMMEDIATE指定了一个0延时。为一个行为指定该延时时,表明这个行为应该尽快被执行。
创建一个Timer
Delay oneShotTimer(TimerHandle h)
{
printf("oneShotTimer called - handle %d!\n",h);
return D_NEVER ;
}
...
TimerAdd(D_SEC(10),oneShotTimer) ;
...
TimerAdd(D_SEC(10),oneShotTimer):在延时10秒之后调用oneShotTimer函数,oneShotTimer函数的返回值类型为Delay并返回给TimerAdd函数,以指明下一次oneShotTimer函数运行的时间。如果oneShotTimer函数返回D_NEVER则表明下一次这个函数将不会再被调用。回调函数的返回值也不一定是常数。
Delay accelerate(TimerHandle h)
{
static delay =10 ;
delay -- ;
h = h; /* Unused */
if (!delay)
{
printf("This was the last call\n") ;
return D_NEVER ;
}
printf("The next call to this function will occur
in around %d seconds\n",delay ) ;
return D_SEC(delay) ;
}
Timer句柄和取消(cancel)一个timer
上一个例子中没有用到timer的句柄。TimerAdd函数返回了一个timer的句柄。这个句柄被传给回调函数并且当不同的timer共用同一个回调函数时,这个具并可以作为回调函数区分不同timer的依据。这个句柄的另一个应用就是在一个timer到期之前取消这个timer。这种情况经常发生在一个指定的周期内等待用户进行操作的情况,对应的函数是TimerCancel。但是同时也要注意
TimerHandles,应为当前的句柄被取消后,这个句柄值可能被其他激活的timer所使用。
TimerHandle timeout ;
Delay unsafeTimeout(TimerHandle h)
{
h = h; /* Unused */
print("Timed out!\n") ;
return D_NEVER;
}
void onUserAction(void)
{
TimerCancel(timeout) ;
}
timeout = TimerAdd(TIMEOUT,unsafeTimeout) ;
上面的代码是失败的。因为有可能onUserAction函数在unsafeTimeout之后取消了timeout中的句柄,而unsafeTimeout函数中通过返回D_NEVER已经取消了timeout指向的timer,所以在onUserAction中取消的句柄就不是所要取消的timer,而有可能是其他的timer。解决办法是可以在回调函数中将timerout赋值为NULL_TIMER。
TimerHandle timeout ;
Delay safeTimeout(TimerHandle h)
{
h = h; /* Unused */
print("Timed out!\n") ;
timeout=NULL_TIMER ;
return D_NEVER ;
}
void onUserAction(void)
{
TimerCancel(timeout) ;
}
timeout = TimerAdd(TIMEOUT,safeTimeout) ;
另一个应该知道的事情是timer一定不要在自己的回调函数中被取消。如果这样做将会导致难以预测的结果。从自己的回调函数中返回D_NEVER会得到同样的结果。解决方法是可以使用TimerCancelCallback来取消所有与当前回调函数相联系的timer。如果每一个回调函数仅使用一次而不是重复利用并且通过句柄来告诉回调函数调用者,那么这种取消timer的方式是最为方便的。
复位timer
当等待包含超时信息(timeout)的数据到来时使用timer是最为实用的。这种情况下当有更多的数据被收到时,有的时候可能需要重新启动timeout。TimerReset函数可以用来完成这种功能,而不是同时调用TimerCancel和TimeAdd函数。
TimerHandle waiting = NULL_TIMER;
#define WAIT D_mSEC(500)
static Delay finished(TimerHandle h)
{
h = h;
return D_NEVER;
}
void handleSourceEvent(void)
{
...
TimerReset(waiting, WAIT);
}
...
waiting = TimerAdd(WAIT, finished);
上面的程序中,如果在半个周期内没有source event产生,finished函数将会被调用一次。
#define WAIT D_mSEC(500)
static Delay finished(TimerHandle h)
{
h = h;
return D_NEVER;
}
void handleSourceEvent(void)
{
...
TimerResetCallback(finished, WAIT);
}
...
(void) TimerAdd(WAIT, finished); 在上面的程序中,利用TimerResetCallback函数而省去了timer句柄的存储,从而简化了程序。
32位时钟
除了由VmGetTick 返回的16位的值外,timer库维护着一个40位的内部毫秒时钟。
uint32 TimerFineTick(void) 毫秒精度,取40位的低32位
uint32 TimerCoarseTick(void) 1/4秒精度,取40位的高32位
(由timer库中的上面的函数创建的timer统一由函数TimerCheckWithChanged
管理,当有timer到期时,TimerCheckWithChanged函数会去调用timer指定的回调函数。也就是上述功能的实现需要TimerCheckWithChanged函数的支撑TimerCheckWithChanged在Sched中使用。)
CM_RFCOMM Library 连接管理器(CM)可以用来做简单的地传输速率的RFCOMM连接。这种连接是异步的,所以在连接之前必需在同等设备之间进行配对。
这个库提供了连接管理器所要用到的消息,包括客户从连接管理器接收到的和客户向连接管理器发送的消息。连接管理器用消息队列(MessageQueue)0来接收消息,而将要发给客户的消息传到消息队列1.
应用程序和库之间是以消息为基础进行交互的。
在初始化连接管理器之后,用户必需通过CM_SERVICE_REGISTER_REQ注册至少一个服务。然后系统返回CM_SERVICE_REGISTER_CFM给用户表明注册完成。以同样方法可以注册更多的服务。一旦用户注册了至少一个服务,用户可以通过发送
CM_ADD_SM_DEVICE_REQ请求来指定要加入Security Manager可信设备数据库的设备。只要前一个请求得到确定,可以以同样方法注册更多的设备。
调度程序库Scheduler Library 用来管理应用程序事件。在应用程序中只需简单地调用Sched(处于管理event,timer,message的无限循环中)即可使程序具有处理事件的功能。
调度程序依赖于消息和定时器库。默写应用程序需要用到调度程序,但是可能并不是这两个库都需要。在这种情况下,这些库可以被其占位程序代替,这样可以减少代码和数据空间。如果消息和定时器被占位程序代替,就不能使用他们了。
调度程序库负责检查是否有EVENT触发,是否有Timer溢出,以及应用程序是否有Message发出,如果有,调用相应的处理程序,如果没有则计算定时器的间隔时间,然后让芯片在这段时间处于休眠状态。用户想要实现某种功能,只要自行编写相应的处理程序,将Schedular提供的默认处理程序加以覆盖,便可以实现自定义功能。Schedular是一个简单的非抢占式多任务内核。
使用事件
事件VM用来通知应用程序某些事情发生了改变,如是否有命令从蓝牙堆栈到达,AD转换的完成,或者另外一个连接的建立。有两种情况可以发生一个事件:第一,事件计数器被更新以反映新的状态;第二,如果事件功能被使能,当前(或者接下来)调用VmWait的返回。
大部分事件功能由scheduler库进行管理。在该库中,会使能大部分常用的事件,并且当它监测到事件发生时将会调用对应的处理函数。
在系统的核心是一些记录是否有时间发生的计数器。一些计数器记录某一事件发生的次数,但是大多数仅是记录是否有事件发生的标志位。一些仅仅是当事件发生时在0和1之间跳动。计数器分为三层的结构:
1、 主要事件,来源于与VM接口的主要功能模块,如与HOST,或者BlueStack的交流。
读完自动复位。主要事件都是布尔型。
2、 次要事件,涉及到射频传输的一些事件,和IO事件,AD时间。读这些计数器的同时也
会自动复位计数器。都是counter型
3、 细节事件,这些事件计数器主要应用于运行于HCI固件之上的应用;这些应用不能自己
建立连接,但是这些应用可以更新基于片外连接的LEDS。有BOOLEAN和TOGGLE
型。读完不复位
声明的一些函数,这些函数的定义在、中
uint16 EventCounter(vm_event_source) 返回对应事件计数器的指,调用此函数时自动清除该计数器。
void EventEnable(vm_event_source event, uint16 enable) 决定当计数器的值改变时(有事件发生),是否唤起来源于VmWait的应用。
uint16 EventDetailScoLinks(void) 返回有效地SCO的链接数。
EventDetailTotal(uint16 *total) total指向具有VM_HOW_MANY_DETAILSvoid
长度数组的指针,函数将会填充链接数。
void EventDetailMasterSlave(uint16 *master, uint16 *slave) 可以得到作为主机的链接数和作为从机的链接数。
Waking Up
当任何一个使能的事件发生时,VM都将从VmWait中提前返回。
如果对minor events感兴趣,需要使能VM_EVENT_MUX,添加感兴趣的事件。
当有minor events事件发生时, vm_event_mux将会被触发,该事件将强迫VM
提前返回。同样道理,如果对detail events感兴趣,需要使能VM_EVENT_MUX
和VM_EVENT_BCSTATUS。
注意事项。存在事件丢失的可能性,如在很短的时间内同一信道接收到两个
事件,只有第一个能够wake up应用程序。所以,在读任何消息之前要清空
事件计数器,然后应用程序读所有的相关消息。尽管对Host 或BlueStackPrim消
息不关心,程序中也要响应并移除它们,因为他们经常被收到,会占用存储空间。如果
使用了scheduler库这一过程将会自动完成。一从Vmwait中返回,scheduler将会自动识别
发生的是什么事件,并调用合适的处理函数。
PIO口相关操作
PIO口的一个重要应用就是作为外部中断源,触发EVENT库中定于的
VM_EVENT_PIOINT事件。在sched()函数中查询到该事件后进入中断处理程序。中断处理程序的入口是通过SchedPioEntry类型的三个常量型
applicatioPioEntry、handshakePioEntry和buttonPioEntry定义的。 设置好外部中断的PIO和对应的中断处理函数之后,要使希望的中断生效或者更换了中断处理函数,必须在进入sched()之前调用SchedPioEntryChanged()函数通知系统打开所设置的中断。在SchedPioEntryChanged()中包含了EventEnable(VM_EVENT_PIOINT, bits ? 1 : 0) SchedPioEntryChanged()的作用是将相应管脚设为输入,并使能VM_EVENT _PIOINT事件。
IO产生的中断事件VM_EVENT_INT时先通知Schedular,再由Schedualar负责调用相应的处理函数去处理。
Stream
stream库提供了有效处理8位数据流的函数。可用的数据流包括RFCOMM,L2CAP,TCP和UDP连接;串口;USB终端;和文件流。
流被分为源端source(被读)和目的端sink(被写)。这些函数被分为4类: 1) 获得源端和目的端
函数返回源端和目的端,当返回0时表示失败。
Source StreamUartSource(void) Sink StreamUartSink(void)
Source和Sink类型是指针类型。如果串口可用,这两个指针与串口相关联。 Source StreamRfcommSource(uint8 mux_id, uint8 server_chan)
Sink StreamRfcommSink(uint8 mux_id, uint8 server_chan)
查找与给定RFCOMM通道相关联的源和端,如果通道已经成功构建(已经收到RFC _ESTABLISH_CFM)
如果通道未知、未建立或者基于RFCOMM的流没有使能(见StreamConfigure)返回0。 一般说来没有必要使用这些函数,因为源和端已经在连接管理器返回的CM_ CONNECT_CFM中提供。
2) 通用数据流操作
uint16 StreamConnect(Source source, Sink sink) 使能一个在源端和目的端的自动连接。数据到达源端后如果空间允许将会自动复制给目的端。这里也可以用StreamMove来实现上述操作,会具有跟好的灵活性,但是效率不高。因为对于已经包含在连接中的源端和目的端是不会产生事件的。 如果连接建立,函数返回一个非0值。
源端和目的端必需同时存在,并且没有包含在已有的连接中。如果源端和目的端的任何一方变成无效,连接将会被打断。
对于包含在连接中的源端和目的端,所有的操作都是无效的(除StreamDisconn ect)。如果向往连接中的目的端插入一些数据,必需现将连接断开,写完数据后在恢复连接。
Vm
VM可以通过将PS区的键值PSKEY_VM_DISABLE设为TRUE来关闭VM功能。可以从命令行来实现,“psci @vm_disable”。否则,在BlueCore配置的过程中,VM将会被加载,应用程序使用的堆栈和全局变量的内存被分配,应用程序开始运行。在初始化全局变量以后,控制权交给主函数main()。应用程序一直运行知道从主函数中返回,或者显示地调用了exit函数。知道BlueCore复位时应用程序才会重新启动。
为其他库提供了一些低层函数。读取毫秒定时器。
void VmWait(uint16 t) 把虚拟机执行挂起,直到过了指定的时间t,或者有事件发生。当 t 被赋予0时,将会一直等待事件的发生而不会等到确定的时间。在应用程序中不建议直接使用此函数,而应由sched库或timer库间接使用。 该库也支持发送和接受BlueStack原语。
HandShake
这个库使用PIO来模仿串口握手信号。串口仿真应用程序可以使用它与连接管理器相关连来传输端到端的DCD,DSR,和DTR信号。
RTS和CTS信号常用来本地的硬件流控而不进行传输。
要改变每个信号对应的PIO需要重新编译库。默认是:
Signal PIO
RI 3
DTR 6
DCD 5
DSR 2
如果这个库被初始化来与一个DCE设备通信,那么DSR,RI和DCD是输入,DTR是输出。如果驱动一个DTE设备信号的方向相反。
uint16 HandshakeInit(HandshakeRole role) 参数role指明BlueCore驱动的是DTE还是DCE设备。如果role是HandshakeToDTE或者用来模拟输入信号DSR的连接到本地的设备已经声明低电平表示准备好,则返回非0值。 库中与控制信号相映射的信号是依据GSM07.10 。这个版本中RTS和CTS总是用来作为本地的流控。
void HandshakeControl(const CM_CONTROL_IND_T *ind)
更新输出管脚的状态来回应在连接管理器中的modem信号。
void HandshakeSend(void)
强制产生基于当前控制管脚的CM_CONTROLL_REQ消息。只要管脚状态改变就会发送这个消息。
蓝牙固件
App Supported features Transports Size HardwaSize Firmware (MbiHCDFL2RFEFILPABCSHNONUSEUSre (Kwordt) I U C C 2 E N P 4 E R B s)
BC2-exhci_bc02 8 32 X X X X X t
hci_kato_bcsKato 4 32 X X X X X p
BC2-exhid_bc02 4 20 X X X X t
rfcomm_kato_Kato 4 24 X X X X X bcsp
rfcomm_kato_Kato 4 24 X X X X X h4
rfcomm_kato_Kato 4 23 X X X X X X X rom
BC2-exunified_bc02 8 64 X X X X X X X X X X t
Kato是BlueCore-audio和flash。不同的BlueCore必须选择何时的蓝牙固件版本。(X应该代表支持的特性),对于Kato器件可能还需要额外的PS设置。用在Casira上的应用于kato器件的固件假设外部时钟是26MHz而不是16MHz。 BlueStack(基于高层软件栈的蓝牙实现) 在SIG组织负责制定的蓝牙技术规范中给出了一个蓝牙协议栈。但此协议栈是一个不可见的协议栈,且没有给出API,很难开发基于蓝牙的应用。因此,目前有很多公司开发出了高层协议栈及相应的API,如东芝蓝牙协议栈、BlueStack、BTSWS蓝牙协议栈和T-BTS协议栈。开发者基于这些高级协议栈,可不必对蓝牙技术作深入研究就可以方便地开发蓝牙应用。 BlueStack使用C语言实现的蓝牙协议栈软件。它遵循由SIG提出的分层模型。支持在l2cap上使用RFCOMM作为传输层的规范。
链路管理器(Link Manager):实现链路管理协议(LMP)。BlueStack不支持提供链路
管理器控制API应用,而是通过设备管理器(DM)API使用HCI调用来达到对链路管
理器的控制应用。
L2CAP
设备管理器(DM)在BlueStack的协议中引入了一个新的软件元素——设备管理器。
是有关蓝牙管理的一个功能集。这些功能包括:资源管理;请求独立;获取资源。此引
入填补了HCI层之上管理的缺乏。所提供的DM API可对蓝牙栈进行更复杂的控制。
利用此API可开发蓝牙所有功能。
这个BlueStack使显示消息驱动的。由调度程序协同协议栈各层的执行。曾于曾志坚使
用消息,在通过调度被传送到特定的模块。
调试
可以在PC机上的调试器下运行BlueLab应用程序。这种情况可以使用常用的调试的所有功能来开发和调试应用程序。当应用程序在片上运行时,VM Spy可以用BCSP Channel 13 来监视片上的运行情况,这事片上调试的唯一方法。 /apps/下的flashsimple是控制IO的简单测试程序。
调试信息的输出
BlueLab应用程序使用16个通道作调试输出(从0到5)。可以用以下函数输出诊断信息:
1) printf,putchar,cprintf——在标准库中声明
2) VmPutChar——在VM库中声明
3) print,cprint——在print库中声明
printf和putchar总是使用通道0,而cprintf会把输入参数中的第一个参数作为通道。所有的这些途径最终都是调用VmPutChar,这个函数向诊断通道发送一个单独的字符,使用该函数也可以将通道关闭。
当程序运行在PC机调试器中时,这些通道显示在主窗口的下端。当在片上运行时,调试信息将会通过BCSP的通道13发送(这不会与host library相冲突)并可以用像VMSpy这样的应用程序显示。
运行在片上的应用程序如果使用调试通道,在PC机上必须有主机应用程序运行来接受片上应用程序发送的调试信息,否则片上的应用程序将停止运行。正因为如此,当应用程序完成调试准备投入使用时,最好将调试用的代码去掉。最简单的方法就是使用print library中的宏来输出调试信息,因为这些宏受到DEBUG_P RINT_ENABLED宏的控制。(在makefile中的好像也可以用一个开关来控制诊断信息)
主机测试
1) BCSP。蓝牙协议提供了两个串行接口协议(物理接口不变):UART(H4)
和RS232(H3)。Casira可以配置成使用UART,但是在出厂时被配置成使
用BCSP(BlueCore Serial Interface),这种模式提供错误校验从而更加可
靠。BCSP提供单独的语音、控制和数据信道。为了与不支持BCSP的提
供商相兼容,Casira也可以设置为支持UART(H4)接口。串口设置存储
在PS中。可以使用PSTool工具改变设置。(BCSP还是遵守串口的物理特
性,只是协议上做了增强。它提供了对调试功能的支持VM Spy等)
2) HOST Library。VM上运行的应用程序可以使用BCSP的13通道与外界连
接。而这个库提供了应用程序的简单接口(在调度器,有处理主机事件
的处理函数)。不使用主机接口传输的应用程序可以直接使用USB或者
UART,这种应用就是基于stream库了。可以运行BlueLab中自带的Host
Test应用程序进行测试。
SPI的作用。SPI用来将映像文件下载到Flash中。
用VM Spy调试
从应用程序的调试输出可以从VM Spy功能中观察到。在开始使用VM Spy之前执行以下步骤:
1、 确保调试没有运行,没有其他程序在使用PC机的串口
2、 Casira的串行电缆已经连接到PC机上
3、 Casira配置成BCSP
VMSpy.exe 4、 运行
编译过程
PC机上的编译过程
在appdebug调试器中打开.sys进行上位机调试。(这时程序运行在上位机上,并没用下载到flash中)
片上调试
.app文件依据makefile产生,适合于在debugger上运行,但是作为最终的应用,需要全部必要堆栈。makefile将会做这件事情,并将初始化到Casira的一个下载。 在Cygwin中输入: $make bc02
注意:CSRSDKBIN和CSRSDKSTACK必须正确设置。
(相关内容在docs/Getting started中)
makefile
编译的复杂性隐藏在Makefile.inc中。
OUTPUT:输出文件的名字
OBJS:工程中从C文件产生的一系列目标文件
LIBS:工程中将要用到的一系列库;每一个库必须前缀“-l”。(注意库的加载顺序,有的库中的符号依赖于其他库)
STACKSIZE:应用程序要求的堆栈大小。至多指定82个字。
TRANSPORT:当调试应用程序和固件时指定传输用的端口。必须是raw,h4或者bcsp的一种。缺省情况是bcsp。
DEFS:传给gcc的标志,典型宏的定义。
DOCS
GIFS:偶尔被CSR所使用。
FILES:指定文件应用中要使用的目录
FIRMWARE:可以选择使用不同的固件,例如可以设置FIRMWARE=pan,则makefile将使用CSRSDKSTACK指定路径下的pan固件。
使用makefiles时,仅输入make将会编译应用程序,产生调试器debugger使用的.app文件和.sym文件。
输入“make image”时,将会把由CSRSD序相连接。KSTACK环境变量指定的固件和应用程
输入“make flash”时,将会编译镜像文件,并把它下载到芯片中。它也会配置器件,使器件使用makefile中由TRANSPORT指定的传输端口。
在使用前,一定要利用BlueFlash中的Dump备份。在这个备份中包含芯片的
固件版本号等重要信息。
故障排除
(由于烧写时人为将烧写程序关闭,造成内部信息不完整,可能导致PS工具出现Failed
痛to send or process command错误信息。可以用BlueFlash工具将备份烧入其中。)
BlueFlash不工作。检查是否安装了驱动;检查SPI电缆是否插好;在设置键值之前要
确保stack正在运行(在stack运行时驱动电流会增长一倍左右)
不能运行调试器。复位芯片;VMSpy不能运行;检查波特率;检查芯片是否包含一个
合适的stack(在使用BlueSuit工具软件时应烧入hci_bc02固件,debugger可能也需要
此固件作为接口而不是所认为通过make产生的固件,再烧入之前要将flash上的内容
全部清除);硬件上需要运行一个VM兼容的协议栈,用来调试程序,rfcomm_bcsp固
件映像适合使用(在bluelab25中可以找到)。
串口可能一直没有工作。所以程序中可能是由于InitTransPort()函数初始化失败造成
程序的直接退出。应将这句去掉,编译下载。串口的初始化失败可能是由于没有配置合
适的串口。
输入make image程序会与CSRSDKSTACK环境变量指定的固件连接。输入make flash
将构建映像并写入到设备中,他同时也会将设备配置为makefile中TRANSPORT指定
的传输层。
串口协议
BlueLab中串口应用包含spp_slave和spp_master应用。
slave
spp_slave应用利用串口协议栈,接收一个蓝牙连接并且传递数据。
spp_master
此应用可以演示怎样从一个VM应用程序连接到spp_slave。它自己不会利用串口协议。
行为
如果他们事先建立了连接,那么只要连接丢失应用程序都将会尝试恢复连接。如果连在PIO5上的按键按下,哪么应用程序将会清除原来的连接并且开始尝试与
一个新的设备相连接。
在这个过程中,从机将会使自己可见并且等待连接请求。主机则去扫描器件并发起连接。在尝试建立连接的过程中由PIO7控制的LED将会闪烁。连接建立以后LED将一直点亮并可以传送数据。
PS配置
为了使应用程序能够访问UART和USB接口,BlueCore必需正确配置。 1、 UART 传输
(用户传输)。 1)PSKEY_HOST_INTERFACE:必需设置成4
2)PSKEY_UART_CONFIG:使用H4时设置成168。这将使能硬件的流控,关闭BCSP,并且无校验。
3)PSKEY_HQ_ACTIVE:设置成False。以防止在没有传输端可用时,芯片会尝试储存BCSP上的硬件调试信息。如果不这样做,将会导致片上调试信息的堆积,最终导致崩溃。
4)PSKEY_UART_BAUD_RATE:波特率设置成合适的值。
5)PSKEY_DEEP_SLEEP_STATE:设置成不使用深度睡眠。如果任何其他的设置被使用那么UART上输入的数据将会丢失。当需要输入数据时,为防止这种情况发生,需要额外的信号和逻辑来强迫唤醒BlueCore。提供的代码不包括这种功能。用户若想使用深度睡眠来减小功耗需要自己设计一个合理的机制。
CSP之间的切换 6)注意H4和B
BlueLab带的标准的makefiles会通过在其中提供TRANSPORT=raw来确保这些关键字的头两个会被正确的设置。不过这种缺省设置会把BCSP设为transport。使用make bc02或者make flash时,芯片会按照makefile配置。
应用程序设置
1、缺省情况下应用程序被设置成使用UART作为传输手段。通过设置TRANSPORT宏来实现。makefile将会根据TRANSPORT来确定编译usb.c,uart.c中的哪一个。
2、握手信号。缺省情况下,应用程序配置成使用UART的握手信号,这时BlueCore2将能够从PIO管脚传递DTR/DSR信号。如果不希望使用这些信号,必须通过编辑makefile中合适的行移除USE_HANDSHAKE的宏定义。(许多宏定义都在makefile中)
3、DCE/DTE。缺省情况下,主机/从机应用程序都假设它们连接到一个DTE设备。为了改变这种情况,需要修改main.c中对HandshakeInit的调用。 注意:应用程序使用连接管理器库,并且比较脆弱:如果应用程序从连接管理器接收到不期望的消息,这两个应用程序将会出现故障。
第三部分 程序分析
一、消息机制分析
在cm_rfcomm.h头文件中,定义了一系列在RFCOMM应用中会使用到的数据结构。CM_INIT_REQ是在cm_rfcomm.h中定义的一个消息,其类型为枚举类型,值为0。同时,在cm_rfcomm.h中还定义了struct CM_INIT_REQ_T的消息结构,其中包含有消息净荷int dummy。通过宏MAKE_MSG(CM_INIT_REQ)生成了含有CM_INIT_REQ_T结构的消息句柄。消息句柄MessageHandle是void *类型的指针,在宏的内部将其强转为指向CM_INIT_REQ_T结构的指针。将结构体中成员赋予相应的值后通过MessagePut(CM, msg)将消息发送到connection manager(CM)。
二、事件机制分析
在event.h中声明了处理事件的基本函数,函数的定义在libcsr.a中不可见。而sched.h中声明的函数提供了事件处理方法的基本结构,不同事件的处理函数也声明在其中(但函数定义不在)。事件的基本处理结构为:
1、使能事件EventEnable(vm_event_source event, uint16 enable) 2、监测事件EventCounter(vm_event_source)
3、处理事件
void handleBlueStack(void);
handleHostMessage(void); void
handleRadioEvent(void); void
void handleAdcEvent(void);
void handleAudioEvent(void);
handleSourceEvent(void); void
void handleSinkEvent(void);
void handleDisconnectEvent(void);
void handleEnergyEvent(void);
......
三、消息与事件的综合应用
#include /* printf */
#include /* free */
#include /* EventCounter */
#include /* HostGetMessage */
#include /* VmWait */
...
EventEnable(VM_EVENT_HOST, 1);
/* Loop forever */
for(;;)
{
/* Sleep until an event */
VmWait(0);
if(EventCounter(VM_EVENT_HOST))
{
uint16 *hm;
while((hm = HostGetMessage()) != NULL)
{
printf("Message from host\n");//调试用
free(hm);//释放消息所占的空间
}
}
}
程序为对VM_EVENT_HOST事件的处理。消息地接受会触发相应的事
件,进而启动应用程序对其进行处理。
第四部分 蓝牙协议栈
二、 蓝牙串口仿真协议栈
RFCOMM采用了ETSI(欧洲电信标准化组织)的TS07.10标准的一个子集,并做了适当修改。
应用
端口接口
端口仿真实体
一般控制参数和
端口参数设置
RFCOMM服务接口
SDPRFCOMM
L2CAP
BaseBand
上图是实际设备中一个RFCOMM的参考模型。其中端口仿真实体与RFCOMM一同构成了端口驱动器。RFCOMM支持两种实际应用设备,分别对应于一般串行通信中的数据终端DTE和数据通信设备DCE。DTE包括计算机和打印机等外围设备,是数据通信的发起端;DCE为调试解调器或类似设备,起到连接不同通信媒介的作用。RFCOMM在应用上并不区分这两种设备,也就是说RFCOMM实体并不知道自己和对方到底属于哪一类设备,这需要由RFCOMM高层的执行者来判断。因此,虽然两个RFCOMM实体间传输的信息有一部分仅对DCE有用,但每种类型的设备都可以传输所有的信息,至于信息是否需要则有执行者来判断。
阻塞连接管理器停止阻塞状态
VmWait()Timer消息
TimerCheckWithChanged()TimerCheckWithChanged()定时器Timer回调函数EventCounter()IO中断schedPioEvent()PIO中断处理函数EventschedBlueStack原语MessageSched()Task(1)消息处理
MessageSched()
Message
此部分的程序由用户编写
主程序
AT指令
上网功能是通过连接的不同层实现的。连接的每一个层为上层提供基本的连接。层越高,提供的功能越多。
这三个连接层是:
, 物理连接
, 点对点连接
, TCP/UDP连接
当建立TCP/IP连接时,手机仅仅是一个初始者。TCP特性使手机成为TCP/IP套接字的一个终端。
一、 创建TCP/IP连接
下面是创建一个从手机到Web的TCP/IP连接的过程:
1、手机连接到GPRS网络并且受到一个IP地址(使用+MIPCALL指令) 2、手机打开一个TCP/IP堆栈作为它的一个套接字(必须知道目的端的IP地址
和端口号)
3、链接一旦建立,数据可以双向的的自由传输(上载和下载) 二、 创建UDP/IP连接
1、A端:
, 手机连接到GPRS网络并且受到一个IP地址(使用+MIPCALL指令) , 手机打开UDP/IP堆栈作为它的一个套接字(使用+MIPOPEN并且选择UDP
协议)
2、B端:
, 手机连接到GPRS网络并且受到一个IP地址(使用+MIPCALL指令) , 手机打开UDP/IP堆栈作为它的一个套接字(使用+MIPOPEN并且选择UDP
协议)
3、A端和B端事先商议好端口号,并且通过其他的连接方式交换他们给定的IP地址。
4、因为手机是到了IP地址和端口号,它可以从目的站点发送接收数据 5、发送数据使用+MIPSEND指令
6、实际的发送是通过使用+MIPPUSH指令,同时指定IP地址和目的端端口号 ※每一个+MIPPUSH都要为现在和将来的传输设定目的端IP地址和目的端端口号
三、 手机连接到WEB
2、客户端
, 手机客户端连接到GPRS网络并且得到一个IP地址(使用+MIPCALL
指令)
, 手机打开UDP/IP堆栈作为它的一个套接字(使用+MIPOPEN并且选择
UDP协议)
2、手机发送数据到Web站点上,因为Web站点的IP地址是已知的并且是
公开的,而且端口也是事先协商好的
3、发送数据使用+MIPSEND指令
4、实际的发送是通过使用+MIPPUSH指令,同时指定IP地址和目的端端口
号
5、服务器端:
, 在从客户端受到第一个包以后,服务器知道了IP地址和手机的端口号
, 该手机的IP地址和端口号会被保存在DB中
四、 特性
, 达四种的同步连接
, 可以使用AT指令通过协议栈传送数据。使终端不必在将RS232转换为
二进制模式并且在转换回指令模式
, 可以同步使用UDP和TCP
五、AT指令结构和配置
AT指令的信息流和结构可以被终端配置。手机可以配置成指令是否有回应;
完全信息还是简约信息:
S3=[] Command line termination character (default setting 0x13).
S4=[] Response formatting character (default 0x10). S5=[] Command line editing character (default 0x 8). E[] Command echo (default 0, meaning the g20 does not echo commands).
Q[] Result code suppression (default 0, meaning the g20 transmits result
codes).
V[] g20 response format (default 1, meaning verbose format). X[] Defines CONNECT result code forma
登陆GPRS网络
登陆GPRS网络的过程中手机将作为调制解调器使用,使用PPP协议建立点对点的连接,
连接后再在该连接上使用TCP/IP协议传输数据或登陆网站。超级终端没有提供PPP协议,
所以就不能利用其实现登录操作。所以,要使用Windows提供的拨号连接,该对话框提供
了网络连接所需要的协议。在使用对话框之前要通过AT指令:AT+CGDOUT来配置要登陆
的网络。然后要释放传输AT指令时所占用的串口句柄,因为该对话框在建立连接过程中要
使用该串口,如果不释放,就会出现硬件上的错误。
由于手机扮演调制解调器的角色,由于是虚拟出的设备所以PC就不能检测到这个设备,所
以首先要在设备管理器中添加标准调制解调器设备,并将其对应的串口设置为所使用的串
口。而对于蓝牙部分它接收到的信号将会包含调制解调器的所有7个信号,所以在硬件上要
将其余3个信号线接到PIO引脚上,并在程序中按照串口协议添加对应的信号线设置代码。
这样,在进行PC机上进行拨号时发出的信号才会得到准确的回应并检测到设备。
1、删除了一个调制解调器
2、在调制解调属性中输入了初始化命令
3、将网络改为NET网
4、使用DUN登录时出现端口占用情况
5、在使用蓝牙连接登录时出现请求串口连接
6、有时需要登陆两回
7、ppp协议中断与调制解调器中的“属性”里“高级”选项卡没有输入初始化命令直接有
关。