精华内容
下载资源
问答
  • AndroidBluetooth架构3 2.1Bluetooth架构图3 2.2Bluetooth代码层次结构3 三Bluetooth协议栈分析4 3.1蓝牙协议栈4 3.2Android蓝牙协议栈的关系6 四Bluetooth之HCI层分析6 4.1HCI层与基带的通信方式6 4.2...

    Bluetooth基本概念 3

    AndroidBluetooth架构 3

    2.1Bluetooth架构图 3

    2.2Bluetooth代码层次结构 3

    Bluetooth协议栈分析 4

    3.1蓝牙协议栈 4

    3.2Android与蓝牙协议栈的关系 6

    BluetoothHCI层分析 6

    4.1HCI层与基带的通信方式 6

    4.2包的分析及研究 7

    4.3通信过程的研究与分析 8

    Bluetooth之编程实现 8

    5.1HCI层编程 8

    5.2L2CAP层编程 11

    5.3SDP层编程 13

    Bluetooth之启动过程实现 14

    6.1Bluetooth启动步骤 14

    6.2Bluetooth启动流程 15

    6.3Bluetooth数据流向 15

    6.4Bluez控制流程 15

    6.5Bluetooth启动过程分析 16

    Bluetooth之驱动移植 17

    7.1android系统配置 17

    7.2启动项修改 17

    7.3电源管理rfkill驱动 17

    7.4Rebuild Android image and reboot 18

    7.5实现BT睡眠唤醒机制 18

    7.6系统集成 19

    Bluetooth之调试与编译 19

    8.1Bluetooth驱动调试 19

    8.2Bluetooth调试工具 20

    Bluetooth之应用程序开发 20

    9.1BluetoothAPI开发 20

    9.2The Basics开发 21

    9.3Bluetooth Permissions开发 22

    9.4Setting Up Bluetooth服务 22

    9.5Finding Devices服务 23

    9.6Connecting Devices服务 25

    9.7Managing a Connection服务 31

    9.8Working with Profiles服务 34

    十总结与疑问 36

     

    一Bluetooth基本概念

    蓝牙是无线数据和语音传输的开放式标准,它将各种通信设备、计算机及其终端设备、各种数字数据系统、甚至家用电器采用无线方式联接起来。它的传输距离为10cm~10m,如果增加功率或是加上某些外设便可达到100m的传输距离。它采用2.4GHzISM频段和调频、跳频技术,使用权向纠错编码、ARQ、TDD和基带协议。TDMA每时隙为0.625μs,基带符合速率为1Mb/s。蓝牙支持64kb/s实时语音传输和数据传输,语音编码为CVSD,发射功率分别为1mW、2.5mW和100mW,并使用全球统一的48比特的设备识别码。由于蓝牙采用无线接口来代替有线电缆连接,具有很强的移植性,并且适用于多种场合,加上该技术功耗低、对人体危害小,而且应用简单、容易实现,所以易于推广。

    蓝牙技术的系统结构分为三大部分:底层硬件模块、中间协议层和高层应用。底层硬件部分包括无线跳频(RF)、基带(BB)和链路管理(LM)。无线跳频层通过2.4GHz无需授权的ISM频段的微波,实现数据位流的过滤和传输,本层协议主要定义了蓝牙收发器在此频带正常工作所需要满足的条件。基带负责跳频以及蓝牙数据和信息帧的传输。链路管理负责连接、建立和拆除链路并进行安全控制。

    二AndroidBluetooth架构

    2.1 Bluetooth架构图

    Android蓝牙系统分为四个层次,内核层、BlueZ库、BlueTooth的适配库、BlueTooth的JNI部分、Java框架层、应用层。下面先来分析Android的蓝牙协议栈。

    图1面向库的架构视图

    Linuxkernel层:

    bluez协议栈、uart驱动,h4协议,hci,l2cap, sco, rfcomm

    bluez层:

    这是bluez用户空间的库,开源的bluetooth代码,包括很多协议,生成libbluetooth.so。

    library层:

    libbluedroid.so等

    framework层:

    实现了Headset/Handsfree和A2DP/AVRCPprofile,但其实现方式不同Handset/Handfree是直接在bluez的RFCOMMSocket上开发的,没有利用bluez的audioplugin,而A2DP/AVRCP是在bluez的audioplugin基础上开发的,大大降低了实现的难度。

    Android的蓝牙协议栈采用BlueZ来实现,BlueZ分为两部分:内核代码和用户态程序及工具集。

     

    图2面向进程的架构视图

    2.2 Bluetooth代码层次结构

    (1)JAVA层

    frameworks/base/core/java/android/bluetooth/

    包含了bluetooth的JAVA类。

    (2)JNI层

    frameworks/base/core/jni/android_bluetooth_开头的文件

    定义了bluez通过JNI到上层的接口。

    frameworks/base/core/jni/android_server_bluetoothservice.cpp

    调用硬件适配层的接口system/bluetooth/bluedroid/bluetooth.c

    (3)bluez库

    external/bluez/

    这是bluez用户空间的库,开源的bluetooth代码,包括很多协议,生成libbluetooth.so。

    (4)硬件适配层

    system/bluetooth/bluedroid/bluetooth.c

    包含了对硬件操作的接口

    system/bluetooth/data/*一些配置文件,复制到/etc/bluetooth/。

    还有其他一些测试代码和工具。

    内核代码主要由BlueZ核心协议和驱动程序组成;蓝牙协议实现在内核源代码net/bluetooth中,驱动程序位于内核源代码目录driver/bluetooth中。用户态程序及工具集主要包括应用程序接口和BlueZ工具集,位于Android源代码目录externel/bluetooth(注:Android版本不一样,有的在externel/bluez目录下)中。

    三Bluetooth协议栈分析

    3.1蓝牙协议栈

    蓝牙协议栈的体系结构由底层硬件模块、中间协议层和高端应用层三部分组成。

    (1)底层硬件模块

    组成:

    链路管理协议(LinkManagerProtocol,LMP);

    基带(BaseBand,BB);

    射频(RadioFrequency,RF)。

    功能:

    射频(RF)通过2.4GHz的ISM频段实现数据流的过滤和传输。

    基带(BB)提供两种不同的物理链路,即同步面向连接链路(SynchronousConnection Oriented,SCO)和异步无连接链路(AsynchronousConnectionLess,ACL),负责跳频和蓝牙数据,及信息帧的传输,且对所有类型的数据包提供不同层次的前向纠错码(FrequencyError Correction,FEC)或循环冗余度差错校验(CyclicRedundancyCheck,CRC)。

    链路管理协议(LMP)负责两个或多个设备链路的建立和拆除,及链路的安全和控制,如鉴权和加密、控制和协商基带包的大小等,它为上层软件模块提供了不同的访问入口。

    主机控制器接口(HostControllerInterface,HCI)是蓝牙协议中软硬件之间的接口,提供了一个调用下层BB、LMP、状态和控制寄存器等硬件的统一命令,上下两个模块接口之间的消息和数据的传递必须通过HCI的解释才能进行。

    (2)中间协议层

    组成:

    逻辑链路控制和适配协议(LogicalLinkControl and Adaptation Protocol,L2CAP);

    服务发现协议(ServiceDiscoveryProtocol,SDP);

    串口仿真协议(或称线缆替换协议RFCOMM);

    二进制电话控制协议(TelephonyControlprotocolSpectocol,TCS)。

    功能:

    L2CAP位于基带(BB)之上,向上层提供面向连接的和无连接的数据服务,它主要完成数据的拆装、服务质量控制、协议的复用、分组的分割和重组,及组提取等功能。

    SDP是一个基于客户/服务器结构的协议,它工作在L2CAP层之上,为上层应用程序提供一种机制来发现可用的服务及其属性,服务的属性包括服务的类型及该服务所需的机制或协议信息。

    RFCOMM是一个仿真有线链路的无线数据仿真协议,符合ETSI标准的TS07.10串口仿真协议,它在蓝牙基带上仿真RS-232的控制和数据信号,为原先使用串行连接的上层业务提供传送能力。

    TCS定义了用于蓝牙设备之间建立语音和数据呼叫的控制信令(CallControl Signalling),并负责处理蓝牙设备组的移动管理过程。

    (3)高端应用层

    组成:

    点对点协议(Point-to-PointProtocol,PPP);

    传输控制协议/网络层协议(TCP/IP);

    用户数据包协议(UserDatagramProtocol,UDP);

    对象交换协议(ObjectExchangProtocol,OBEX);

    无线应用协议(WirelessApplicationProtocol,WAP);

    无线应用环境(WirelessApplicationEnvironment,WAE);

    功能:

    PPP定义了串行点对点链路应当如何传输因特网协议数据,主要用于LAN接入、拨号网络及传真等应用规范。

    TCP/IP、UDP定义了因特网与网络相关的通信及其他类型计算机设备和外围设备之间的通信。

    OBEX支持设备间的数据交换,采用客户/服务器模式提供与HTTP(超文本传输协议)相同的基本功能。可用于交换的电子商务卡、个人日程表、消息和便条等格式。

    WAP用于在数字蜂窝电话和其他小型无线设备上实现因特网业务,支持移动电话浏览网页、收取电子邮件和其他基于因特网的协议。

    WAE提供用于WAP电话和个人数字助理(PersonalDigitalAssistant,PDA)所需的各种应用软件。

    3.2 Android与蓝牙协议栈的关系

    蓝牙系统的核心是BlueZ,因此JNI和上层都围绕跟BlueZ的沟通进行。JNI和Android应用层,跟BlueZ沟通的主要手段是D-BUS,这是一套被广泛采用的IPC通信机制,跟Android框架使用的Binder类似。BlueZ以D-BUS为基础,给其他部分提供主要接口。

    四Bluetooth之HCI层分析

    蓝牙系统的HCI层是位于蓝牙系统的L2CAP(逻辑链路控制与适配协议)层和LMP(链路管理协议)层之间的一层协议。HCI为上层协议提供了进入LM的统一接口和进入基带的统一方式。在HCI的主机(Host)和HCI主机控制器(HostController)之间会存在若干传输层,这些传输层是透明的,只需完成传输数据的任务,不必清楚数据的具体格式。目前,蓝牙的SIG规定了四种与硬件连接的物理总线方式:USB、RS232、UART和PC卡。其中通过RS232串口线方式进行连接具有差错校验。蓝牙系统的协议模型如图3所示。

    图3 Bluetooth协议模型

    4.1 HCI层与基带的通信方式

    HCI是通过包的方式来传送数据、命令和事件的,所有在主机和主机控制器之间的通信都以包的形式进行。包括每个命令的返回参数都通过特定的事件包来传输。HCI有数据、命令和事件三种包,其中数据包是双向的,命令包只能从主机发往主机控制器,而事件包始终是主机控制器发向主机的。主机发出的大多数命令包都会触发主机控制器产生相应的事件包作为响应。

    图4底层协议通信图

    命令包分为六种类型:

    *链路控制命令;

    *链路政策和模式命令;

    *主机控制和基带命令;

    *信息命令;

    *状态命令;

    *测试命令。

    事件包也可分为三种类型:

    *通用事件,包括命令完成包(CommandComplete)和命令状态包(CommandStatus);

    *测试事件;

    *出错时发生的事件,如产生丢失(FlushOccured)和数据缓冲区溢出(DataBuffer Overflow)。

    数据包则可分为ACL和SCO的数据包。包的格式如图5所示。

    图5HCI包格式

    4.2包的分析及研究

    命令包:命令包中的OCF(OpcodeCommand Field)和OGF(OpcodeGroup Field)是用于区分命令种的。ParameterLength表示所带参数的长度,以字节数为单位,随后就是所带的参数列表。下面以Inquiry命令为例对HCI的命令包做具体说明。

    在Inquiry命令中,OGF=0x01表示此命令属于链路控制命令,同时OCF=0x0001则表示此命令为链路控制命令中的Inquiry命令。OCF与OGF共占2字节,又由于底位字节在前,则它们在命令包为0x0104。在Inquiry命令中,参数ParameterLength为5。Inquiry命令带3个参数,第一个参数为LAP(lowaddress part), 它将用来产生Baseband中查询命令包的包头中的AccessCode。第二个参数为Inquiry_Length,它时表示在Inquiry命令停止前所定义的最大时间,超过此时间,Inquiry命令将终止。第三个参数为NUM_Response,它的值为0X00表示设备响应数不受限制,只为0x00-0xff则表示在Inquiry命令终止前最大的设备响应数。因此,若LAP=0x9e8b00,Inquiry_Length=0x05,NUM_Response=0x05,则协议上层调用Inquiry命令是HCI向基带发的明令包将为:0x0104 05 00 8b 9e 05 05。

    事件包:事件包的EventCode用来区分不同的事件包,ParameterLength表示所带参数的长度,以字节数为单位,随后就是所带的参数列表。以CommandStatus Event事件包为例对HCI的事件包进行具体说明。

    当主机控制器收到主机发来的如上面所提到的Inquiry命令包并开始处理时,它就会向主机发送CommandStatus Event事件包,此事件包为:0x0f04 00 0a 01 04。0xOf表示此事件包为CommandStatusEvent事件包,0x04表示此事件包带4字节长度的参数,0x00为此事件包的第一个参数即Status,表示命令包正在处理。0x0a为事件包的第二个参数NUM_HCI_Command_Packets,表示主机最多可在向主机控制器发10个命令包。0x0104为第三个参数Command_Opcode,表示此事件包是对Inquiry命令包的响应。

    数据包:ACL和SCO数据包中的ConnectionHandle即连接句柄是一个12比特的标志符,用于唯一确认两台蓝牙设备间的数据或语音连接,可以看作是两台蓝牙设备间唯一的数据通道的标识。两台设备间只能有一条ACL连接,也就是只有一个ACL的连接句柄,相应L2CAP的信道都是建立在这个连接句柄表示的数据通道上;两台设备间可以有多个SCO的连接,则一对设备间会有多个SCO的连接句柄。连接句柄在两设备连接期间一直存在,不管设备处于什么状态。在ACL数据包中,Flags分为PBFlag和BCFlag,PBFlag为包的界限标志,PBFlag=0x00表示此数据包为上层协议包(如L2CAP包)的起始部分;PBFlag=0x01表示此数据包为上层协议包(如L2CAP包)的后续部分。BCFlag为广播发送的标志,BCFlag=0x00表示无广播发送,只是点对点的发送;BCFlag=0x01表示对所有处于激活状态的从设备进行广播发送,BCFlag=0x02表示对所有的从设备包括处于休眠状态的从设备进行广播发送。ACL和SCO数据包中的DataTotal Length 都表示所载荷的数据的长度,以字节位单位。

    4.3通信过程的研究与分析

    当主机与基带之间用命令的方式进行通信时,主机向主机控制器发送命令包。主机控制器完成一个命令,大多数情况下,它会向主机发出一个命令完成事件包(CommandComplete Packet),包中携带命令完成的信息。有些命令不会收到命令完成事件,而会收到命令状态事件包(CommandStatusPacket),当收到该事件则表示主机发出的命令已经被主机控制器接收并开始处理,过一段时间该命令被执行完毕时,主机控制器会向主机发出相应的事件包来通知主机。如果命令参数有误,则会在命令状态事件中给出相应错误码。假如错误出现在一个返回CommandComplete事件包的命令中,则此CommandComplete事件包不一定含有此命令所定义的所有参数。状态参数作为解释错误原因同时也是第一个返回的参数,总是要返回的。假如紧随状态参数之后是连接句柄或蓝牙的设备地址,则此参数也总是要返回,这样可判别出此CommandComplete事件包属于那个实例的一个命令。在这种情况下,事件包中连接句柄或蓝牙的设备地址应与命令包种的相应参数一致。假如错误出现在一个不返回CommandComplete事件包的命令中,则事件包包含的所有参数都不一定是有效的。主机必须根据于此命令相联系的事件包中的状态参数来决定它们的有效性。

    五Bluetooth之编程实现

    5.1 HCI层编程

    HostController Interface(HCI)是用来沟通Host和Module。Host通常就是PC,Module则是以各种物理连接形式(USB,serial,pc-card等)连接到PC上的bluetoothDongle。

    在Host这一端:application,SDP,L2cap等协议都是软件形式提出的(Bluez中是以kernel层程序)。在Module这一端:LinkManager, BB, 等协议都是硬件中firmware提供的。

    而HCI则比较特殊,它一部分在软件中实现,用来给上层协议和程序提供访问接口(Bluez中,hci.chci_usb.c,hci_sock.c等).另一部分也是在Firmware中实现,用来将软件部分的指令等用底层协议明白的方式传递给底层。

    居于PC的上层程序与协议和居于Modules的下层协议之间通过HCI沟通,有4种不同形式的传输:Commands,Event, ACL Data, SCO/eSCO Data。

    1. 打开一个HCISocket---int hci_open_dev(int dev_id):

    这个function用来打开一个HCISocket。它首先打开一个HCIprotocol的Socket(房间),并将此Socket与deviceID=参数dev_id的Dongle绑定起来。只有bind后,它才将Socket句柄与Dongle对应起来。

    注意,所有的HCICommand发送之前,都需要使用hci_open_dev打开并绑定。

    1. 关闭一个HCISocket--- int hci_close_dev(int dd)

    简单的关闭使用hci_open_dev打开的Socket。

    1. 向HCISocket发送request---inthci_send_req(int dd, struct hci_request *r, int to)

    BlueZ提供这个function非常有用,它可以实现一切Host向Modules发送Command的功能。

    参数1:HCISocket。

    参数2:Command内容。

    参数3:以milliseconds为单位的timeout.

    下面详细解释此function和用法:

    当应用程序需要向Dongle(对应为一个bind后的Socket)发送Command时,调用此function.

    参数一dd对应一个使用hci_open_dev()打开的Socket(Dongle)。

    参数三to则为等待Dongle执行并回复命令结果的timeout.以毫秒为单位。

    参数二hci_request* r最为重要,首先看它的结构:

    struct hci_request {

    uint16_t ogf; //Opcode Group

    uint16_t ocf; //Opcode Command

    int event; //此Command产生的Event类型。

    void *cparam;//Command参数

    int clen; //Command参数长度

    void *rparam; //Response参数

    int rlen; //Response参数长度

    };

    ogf,ocf不用多说,对应前面的图就明白这是GroupCode和CommandCode。这两项

    先确定下来,然后可以查HCISpec。察看输入参数(cparam)以及输出参数(rparam)

    含义。至于他们的结构以及参数长度,则在~/include/net/bluetooth/hci.h中有定义。

    至于event.如果设置,它会被setsockopt设置于Socket。

    1. 得到指定DongleBDAddr---int hci_read_bd_addr(int dd, bdaddr_t *bdaddr, int to);

    参数1:HCISocket,使用hci_open_dev()打开的Socket(Dongle)。

    参数2:输出参数,其中会放置bdaddr.

    参数3:以milliseconds为单位的timeout.

    1. 读写DongleName:

    inthci_read_local_name(int dd, int len, char *name, int to)

    inthci_write_local_name(int dd, const char *name, int to)

    参数1:HCISocket,使用hci_open_dev()打开的Socket(Dongle)。

    参数2:读取或设置Name。

    参数3:以milliseconds为单位的timeout.

    注意:这里的Name与IOCTLHCIGETDEVINFO得到hci_dev_info中的name不同。

    1. 得到HCIVersion:

    inthci_read_local_version(int dd, struct hci_version *ver, int to)

    1. 得到已经UP的DongleBDaddr--- int hci_devba(int dev_id, bdaddr_t *bdaddr);

    dev_id:Dongle Device ID.

    bdaddr:输出参数,指定Dongle如果UP,则放置其BDAddr。

    1. inquiry远程BluetoothDevice:

    inthci_inquiry(int dev_id, int len, int nrsp, const uint8_t *lap,inquiry_info **ii, long flags)

    hci_inquiry()用来命令指定的Dongle去搜索周围所有bluetoothdevice.并将搜索到的BluetoothDevice bdaddr 传递回来。

    参数1:dev_id:指定DongleDevice ID。如果此值小于0,则会使用第一个可用的Dongle。参数2:len:此次inquiry的时间长度(每增加1,则增加1.25秒时间)

    参数3:nrsp:此次搜索最大搜索数量,如果给0。则此值会取255。

    参数4:lap:BDADDR中LAP部分,Inquiry时这块值缺省为0X9E8B33.通常使用NULL。则自动设置。

    参数5:ii:存放搜索到BluetoothDevice的地方。给一个存放inquiry_info指针的地址,它会自动分配空间。并把那个空间头地址放到其中。

    参数6:flags:搜索flags.使用IREQ_CACHE_FLUSH,则会真正重新inquiry。否则可能会传回上次的结果。

    返回值是这次Inquiry到的BluetoothDevice数目。

    注意:如果*ii不是自己分配的,而是让hci_inquiry()自己分配的,则需要调用bt_free()来帮它释放空间。

    1. 得到指定BDAddr的reomtedevice Name:

    inthci_read_remote_name(int dd, const bdaddr_t *bdaddr, int len, char*name, int to)

    参数1:使用hci_open_dev()打开的Socket。

    参数2:对方BDAddr.

    参数3:name长度。

    参数4:(out)放置name的位置。

    参数5:等待时间。

    1. 读取连接的信号强度:

    inthci_read_rssi(int dd, uint16_t handle, int8_t *rssi, int to)

    注意,所有对连接的操作,都会有一个参数,handle.这个参数是连接的Handle。前面讲过如何得到连接Handle的。

    5.2 L2CAP层编程

    逻辑连接控制和适配协议(L2CAP)为上层协议提供面向连接和无连接的数据服务,并提供多协议功能和分割重组操作。L2CAP充许上层协议和应用软件传输和接收最大长度为64K的L2CAP 数据包。

    L2CAP基于 通道(channel)的概念。 通道(Channel)是位于基带(baseband)连接之上的逻辑连接。每个通道以多对一的方式绑定一个单一协议(singleprotocol)。多个通道可以绑定同一个协议,但一个通道不可以绑定多个协议。每个在通道里接收到的L2CAP数据包被传到相应的上层协议。多个通道可共享同一个基带连接。

    L2CAP处于Bluetooth协议栈的位置如下:

    图6L2CAP协议

    L2CAP使用L2CAP连接请求(ConnectionRequest)命令中的PSM字段实现协议复用。L2CAP可以复用发给上层协议的连接请求,这些上层协议包括服务发现协议SDP(PSM= 0x0001)、RFCOMM(PSM= 0x0003)和电话控制(PSM= 0x0005)等。

    图7PSM协议字段

    L2CAP编程非常重要,它和HCI基本就是LinuxBluetooth编程的基础了。几乎所有协议的连接,断连,读写都是用L2CAP连接来做的

    1. 创建L2CAPSocket:

    socket(PF_BLUETOOTH,SOCK_RAW, BTPROTO_L2CAP);

    domain=PF_BLUETOOTH,type可以是多种类型。protocol=BTPROTO_L2CAP

    1. 绑定

    memset(&addr,0, sizeof(addr));

    addr.l2_family= AF_BLUETOOTH;

    bacpy(&addr.l2_bdaddr,&bdaddr); //bdaddr为本地DongleBDAddr

    if(bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0)

    {

    perror("Can'tbind socket");

    gotoerror;

    }

    1. 连接

    memset(&addr,0, sizeof(addr));

    addr.l2_family= AF_BLUETOOTH;

    bacpy(addr.l2_bdaddr,src);

    addr.l2_psm= xxx;

    if(connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0)

    {

    perror("Can'tconnect");

    gotoerror;

    }

    注意:

    structsockaddr_l2

    {

    sa_family_tl2_family; //必须为AF_BLUETOOTH

    unsignedshort l2_psm; //与前面PSM对应,这一项很重要

    bdaddr_tl2_bdaddr; //Remote Device BDADDR

    unsignedshort l2_cid;

    };

    1. 发送数据到RemoteDevice:

    send()或write()都可以。

    1. 接收数据

    revc()或read()都可以

    5.3 SDP层编程

    服务发现协议(SDP或BluetoothSDP)在蓝牙协议栈中对蓝牙环境中的应用程序有特殊的含意,发现哪个服务是可用的和确定这些可用服务的特征。SDP定义了bluetoothclient发现可用bluetoothserver服务和它们的特征的方法。这个协议定义了客户如何能够寻找基于特定属性的服务而不让客户知道可用服务的任何知识。SDP提供发现新服务的方法,在当客户登录到正在操作的蓝牙服务器的一个区域时是可用的时。

    Servicediscovery机制提供client应用程序侦测server应用程序提供的服务的能力,并且能够得到服务的特性。服务的品质包含服务type或服务class.

    SDP也提供SDPserver与SDPclient之间的通讯。SDPserver维护着一个服务条目(servicerecord)列表.每个服务条目描述一个单独的服务属性。SDPclient可以通过发送SDPrequest来得到服务条目。

    如果一个client或者依附于client之上的应用程序决定使用某个service.它创建一个单独的连接到service提供者。SDP只提供侦测Service的机制,但不提供如何利用这些Service的机制。Sam觉得,这里其实是说:SDP只提供侦测Service的办法,但如何用,SDP不管。

    每个BluetoothDevice最多只能拥有一个SDPServer。如果一个BluetoothDevice只担任Client,那它不需要SDPServer。但一个BluetoothDevice可以同时担当SDPServer和SDPclient.

    (1)ServiceRecord(Service条目):

    一个service是一个实体为另一个实体提供信息,执行动作或控制资源。一个service可以由软件,硬件或软硬件结合提供。

    图8Service Record

    所有的Service信息都包含于一个ServiceRecord内。一个ServiceRecord包含一个Serviceattribute(Service属性)list.

    (2)ServiceAttribute(Service属性):

    每个Service属性描述servcie的特性.一个ServiceAttribute由2部分:

    AttributeID + Attribute Value。

    图9Service Attribute

    (3)ServiceClass:

    每个Service都是某个ServiceClass的实例.Service Class定义了ServiceRecord中包含的Service属性。属性ID,属性值都被定义好了。

    每个ServiceClass也有一个独特ID。这个ServiceClass标识符包含在属性值ServiceClassIDList属性中。并描绘为UUID。自从ServiceRecord中的属性格式以及含义依赖于ServiceClass后,ServiceClassIDList属性变得非常重要。

    (4)SearchingFor Service:

    ServiceSearch transaction(事务?)允许client得到ServiceRecord Handle。一旦SDPClient得到ServiceRecord Handle,它就可以请求这个Record内具体属性的值。

    如果某个属性值UUID,则可以通过查找UUID查到这个属性。

    UUID:universally uniqueidentifier.(唯一性标识符)

    总之,DP协议栈使用request/response模式工作,每个传输过程包括一个requestprotocol data unit(PDU)和一个responsePDU. SDP使用L2CAP连接传输数据。在发送RequestPDU但未收到ResponsePDU之前,不能向同一个server再发送RequestPDU。

    六Bluetooth之启动过程实现

    对于蓝牙无论最底层的硬件驱动如何实现,都会在HCI层进行统一。也就是说,HCI在主机端的驱动主要是为上层提供统一接口,让上层协议不依赖于具体的硬件实现。HCI在硬件中的固件与HCI在主机端的驱动通信方式有多种,比如UART,USB和SDIO等。

    HCI层在所有的设备面前都被抽象为一个hci_dev结构体,因此,无论实际的设备是哪种蓝牙设备、通过什么方式连接到主机,都需要向HCI层和蓝牙核心层注册一个hci_dev设备,注册过程由hci_registe_dev()函数来完成,同时也可以通过hci_unregister_dev()函数卸载一个蓝牙设备。

    具体的蓝牙驱动有很多,常用的在linux内核都自带有驱动。比如:hci_vhci.c为蓝牙虚拟主控制器驱动程序,hci_uart.c(或者hci_ldisc.c)为串口接口主控制器驱动程序,btusb.c为USB接口主控制器驱动程序,btsdio.c为SDIO主控制器驱动程序。

    6.1 Bluetooth启动步骤

    (1)串口驱动必须要先就绪(uart蓝牙而言),这是cpu和蓝牙模块之间的桥梁。

    (2)蓝牙初始化,模块上电和PSKEY的设置。

    (3)通过hciattach建立串口和蓝牙协议层之间的数据连接通道。

    6.2 Bluetooth启动流程

    (1)打开蓝牙电源,通过rfkill来enable;(system/bluetooth/bluedroid/bluetooth.c)

    (2)启动servicehciattch -n -s 115200 /dev/ttyS2 bcm2035 115200;

    (3)检测HCI是否成功(接受HCIDEVUPsocket来判断或hciconfighci0 up);

    (4)hcid deamon start up。

    6.3 Bluetooth数据流向

    (1)uart口取得蓝牙模块的数据;

    (2)uart口通过ldisc传给hci_uart;

    (3)hci_uart传给在其上的bcsp;

    (4)bcsp传给hci层;

    (5)hci层传给l2cap层

    (6)l2cap层再传给rfcomm;

    蓝牙模块上电:

    一般是通过一个GPIO来控制的,通常是先高再低再高;

    PSKEY的设置:

    通过串口发送命令给蓝牙模块,对于串口必须要知道的是要能通讯,必须得设好波特率,另外一方面蓝牙模块的晶振频率也必须要设,否则它不知道该怎么跳了;当然不同的芯片可能初始化的过程也不一样,也许还要下载firmware等等,一般是通过bccmd来完成的。

    经过上面的设置基本上蓝牙模块以及可以正常工作了;

    但是还没有和上面的协议层建立纽带关系,也就是说从uart收到的数据还没有传给hci层;如何把uart也就是蓝牙模块传上来的数据交给hci层,在驱动里面是通过一个叫做disc的机制完成的,这个机制本意是用来做过滤或者限制收上来的字符的,但是在蓝牙驱动里面则直接把数据传给了蓝牙协议层,再也不回到串口的控制了;

    6.4 Bluez控制流程

    classbluetoothsetting是UI的入口,通过按buttonscan进入搜索状态,

    applicaton层调用bluetoothdevice,接着就是bluetoothservice的调用,

    bluetoothservice调用native方法,到此全部的java程序结束了。

    下面的调用都是JNI,cpp实现的。android_server_bluetoothservice.cpp里面实现了native

    方法,最终通过dbus封装,调用HCID deamon的functionDiscoverDevice。

    6.5 Bluetooth启动过程分析

    1.各协议层的注册

    (1)在af_bluetooth.c中首先调用bt_init()函数完成初始化,打印信息Bluetooth:Core ver 2.16随后调用函数sock_register()注册sock,打印信息Bluetooth:HCI device and connection manager initialized

    (2)接着调用函数hci_sock_init(),l2cap_init(),sco_init()实现各个协议的初始化

    (3)在hci_sock.c中,注册bt_sock协议,打印信息Bluetooth:HCI socket layer initialized

    (4)在L2cap_core.c中,调用l2cap_init_socks()函数,完成初始化,打印信息Bluetooth:L2CAP socket layer initialized

    (5)在sco.c中,注册BTPROTO_SCO协议,完成初始化,打印信息Bluetooth:SCO socket layer initialized

    2.各硬件的初始化

    (1)在Hci_ldisc.c中,完成hci_uart_init的初始化,打印信息Bluetooth:HCI UART driver ver 2.2

    (2)在Hci_h4.c中,完成h4_init的初始化,打印信息Bluetooth:HCI H4 protocol initialized

    (3)在Hci_ath.c中,完成ath_Init的初始化,打印信息Bluetooth:HCIATH3Kprotocol initialized

    (4)在hci_ibs.c中,完成ibs_init的初始化,打印信息Bluetooth:HCI_IBSprotocol initialized

    (5)在Core.c中,完成rfcomm_init的初始化,其中调用函数rfcomm_init_ttys()、rfcomm_init_sockets(),实现rfcomm的初始化,打印信息Bluetooth:RFCOMMver 1.11

    (6)在TTY.C中,完成rfcomm_init_ttys的初始化,实现rfcomm_tty_driver的注册,打印信息Bluetooth:RFCOMMTTY layer initialized

    (7)在Sock.c中,完成rfcomm_init_sockets的初始化,实现BTPROTO_RFCOMM的注册,打印信息Bluetooth:RFCOMMsocket layer initialized

    (以上通过rfcomm完成uart串口的初始化,在G:\M8960\M8960AAAAANLYA1023\kernel\net\bluetooth\rfcomm)

    3. bnep-蓝牙网络封装协议

    在Core.c中,完成bnep_init的初始化,实现bnep的初始化,打印信息Bluetooth:BNEP(Ethernet Emulation) ver 1.3/Bluetooth:BNEP filters: protocol

    (以上完成蓝牙网络封装协议的初始化G:\M8960\M8960AAAAANLYA1023\kernel\net\bluetooth\bnep)

    七Bluetooth之驱动移植

    7.1 android系统配置

    build\target\board\generic下面的generic.mk增加:

    BOARD_HAVE_BLUETOOTH:= true

    这个是由于编译相关蓝牙代码时需要这个宏,请看:\system\bluetooth\android.mk

    ifeq($(BOARD_HAVE_BLUETOOTH),true)

    include$(all-subdir-makefiles)

    endif

    在 external\bluetooth也同样存在此宏起作用

    7.2启动项修改

    system\core\rootdir下init.rc文件增加:

    servicehciattach /system/bin/hciattach -n -s 115200 /dev/ttyS2 bcm2035115200

    userbluetooth

    groupbluetooth net_bt_admin

    disabled

    oneshot

    请放在 servicebluetoothd /system/bin/bluetoothd -n类似这种语句的后面任意位置即可。

    7.3电源管理rfkill驱动

    Kernel/driver/bluetooth/bluetooth-power.c

    高通的这个文件基本上不用动。

    在kernel\arch\arm\mach_msm7x27.c:static int bluetooth_power(int on)中

    实现:上电:把bt_resetpin和bt_reg_onpin拉低

    mdelay(10);

    把bt_resetpin和bt_reg_onpin拉高

    mdelay(150)

    下电:把bt_resetpin和bt_reg_onpin拉低

    7.4 Rebuild Androidimage and reboot

    命令行测试:

    echo0 >/sys/class/rfkill/rfkill0/state //BT下电

    echo1 >/sys/class/rfkill/rfkill0/state //BT上电

    brcm_patchram_plus-d--patchram/etc/firmware/BCM4329B1_002.002.023.0061.0062.hcd/dev/ttyHS0

    hciattach-s115200 /dev/ttyHS0 any

    没任何错误提示是可以用以下测试

    hciconfighci0up

    hcitoolscan

    7.5实现BT睡眠唤醒机制

    Kernel\drivers\bluetooth\bluesleep.c一般来说这个文件改动比较少,但可能逻辑上会有些问题。需要小的改动。

    在kernel\arch\arm\mach_xxx/board_xxx.c:bluesleep_resources中定义gpio_host_wake(BT唤醒host脚)、gpio_ext_wake(host唤醒BT脚)、host_wake(BT唤醒host的中断号)。

    注:各个平台的board_xxx.c文件名字不同,请客户确认

    7.6系统集成

    1)在init.qcom.rc中确认有下面的内容:

    servicehciattach/system/bin/sh /system/etc/init.qcom.bt.sh

    userbluetooth

    groupqcom_oncrpc bluetooth net_bt_admin

    disabled

    oneshot

    2)修改init.qcom.bt.sh

    确认有:

    BLUETOOTH_SLEEP_PATH=/proc/bluetooth/sleep/proto

    echo1 >$BLUETOOTH_SLEEP_PATH

    /system/bin/hciattach-n/dev/ttyHS0 any 3000000 flow &改为:

    ./brcm_patchram_plus--enable_lpm–enable_hci --patchram /system/etc/wifi/BCM4329BT.hcd--baudrate3000000 /dev/ttyHS0 &

    注掉:高通下载firmware的命令。

    最后,重新编译system。此时BT应该能运行了。

    八Bluetooth之调试与编译

    8.1 Bluetooth驱动调试

    调试你的蓝牙实现,可以通过读跟蓝牙相关的logs(adblogcat)和查找ERROR和警告消息。Android使用Bluez,同时会带来一些有用的调式工具。下面的片段为了提供一个建议的例子:

    1 hciconfig -a # print BT chipset address and features.Useful to check if you can communicate with your BT chipset.

    2 hcidump -XVt # print live HCI UART traffic.

    3 hcitool scan # scan for local devices. Useful to check ifRX/TX works.

    4 l2ping ADDRESS # ping another BT device. Useful to check ifRX/TX works.

    5 sdptool records ADDRESS # request the SDP records of another BTdevice.

    守护进程日志

    hcid(STDOUT)和hciattach(STDERR)的守护进程日志缺省是被写到/dev/null。编辑init.rc和init.PLATFORM.rc在logwrapper下运行这些守护进程,把它们输出到logcat。

    hciconfig-a和 hcitool

    如果你编译你自己的system.img,除了hcitool扫描不行,hciconfig-a是可以工作的,尝试安装固件到蓝牙芯片。

    8.2 Bluetooth 调试工具

    BlueZ为调试和与蓝牙子系统通信提供很多设置命令行工具,包含下面这些:

    (1)hciconfig

    (2)hcitool

    (3)hcidump

    (4)sdptool

    (5)dbus-send

    1. dbus-monitor

    九Bluetooth之应用程序开发

    9.1 Bluetooth的API开发

    Android平台包含了对Bluetooth协议栈的支持,允许机器通过Bluetooth设备进行无线数据交换。应用框架通过AndroidBluetoothAPI访问Bluetooth功能模块。这些API能让应用无线连接其他Bluetooth设备,实现点对点和多点之间的通信。

    运用蓝牙API,Android应用程序可以完成如下操作:

    (1)扫描其他Bluetooth设备。

    (2)查询配对Bluetooth设备的本地Bluetooth适配器。

    (3)建立RFCOMM通道。

    (4)通过服务探索连接到其他设备。

    (5)与其他设备进行数据传输。

    (6)管理多个连接

    9.2 The Basics开发

    本文描述如何使用AndroidBluetoothAPIs完成Bluetooth通讯的4个必要任务:设置Bluetooth,搜寻本地配对或者可用的Bluetooth设备,连接Bluetooth设备,与Bluetooth设备进行数据传输。

    所有可用的BluetoothAPIs都包含在android.bluetooth包中。下面是建立Bluetooth连接需要用到的类和接口的总结:

    (1)BluetoothAdapter

    描述本地Bluetooth适配器(Bluetooth接收器)。BluetoothAdapter是所有Bluetooth相关活动的入口。运用BluetoothAdapter可以发现其他Bluetooth设备,查询连接(或配对)的设备列表,用已知MAC地址实例化一个BluetoothDevice对象,创建一个BluetoothServerSocket对象侦听其他设备的通信。

    (2)BluetoothDevice

    描述一个远程Bluetooth设备。可以用它通过一个BluetoothSocket请求一个远程设备的连接,或者查询远程设备的名称、地址、类、连接状态等信息。

    (3)BluetoothSocket

    描述一个BluetoothSocket接口(类似于TCPSocket)。应用通过InputStream和OutputStream与另外一个Bluetooth设备交换数据,即它是应用与另外一个设备交换数据的连接点。

    (4)BluetoothServerSocket

    BluetoothServerSocket是一个开放的socket服务器,用来侦听连接进来的请求(类似于RCPServerSocket)。为了连接两个Android设备,一个设备必须使用该类来开启一个socket做服务器,当另外一个设备对它发起连接请求时并且请求被接受时,BluetoothServerSocket会返回一个连接的BluetoothSocket对象。

    (5)BluetoothClass

    BluetoothClass是用来定义设备类和它的服务的只读属性集。然而,它并不是可靠的描述设备支持的所有Bluetooth配置和服务,而只是一些设备类型的有用特征。

    (6)BluetoothProfile

    BluetoothProfile是两个设备基于蓝牙通讯的无线接口描述。Profile定义了设备如何实现一种连接或者应用,你可以把Profile理解为连接层或者应用层协议。比如,如果一家公司希望它们的Bluetooth芯片支援所有的Bluetooth耳机,那么它只要支持HeadSetProfile即可,而无须考虑该芯片与其它Bluetooth设备的通讯与兼容性问题。如果你想购买Bluetooth产品,你应该了解你的应用需要哪些Profile来完成,并且确保你购买的Bluetooth产品支持这些Profile。

    (7)BluetoothHeadset

    提供移动电话的Bluetooth耳机支持。包括Bluetooth耳机和Hands-Free(v1.5) profiles。

    (8)BluetoothA2dp

    定义两个设备间如何通过Bluetooth连接进行高质量的音频传输。A2DP(AdvancedAudio Distribution Profile):高级音频传输模式。

    (9)BluetoothProfile.ServiceListener

    一个接口描述,在与服务连接或者断连接的时候通知BluetoothProfileIPC(这是内部服务运行的一个特定的模式<profile>)。

    9.3 BluetoothPermissions开发

    要使用Bluetooth功能,至少需要2个Bluetooth权限:BLUETOOTH和BLUETOOTH_ADMIN.

    BLUETOOTH:用来授权任何Bluetooth通信,如请求连接,接受连接,传输数据等。

    BLUETOOTH_ADMIN:用来授权初始化设备搜索或操作Bluetooth设置。大多数应用需要它的唯一场合是用来搜索本地Bluetooth设备。本授权的其他功能不应该被使用,除非是需要修改Bluetooth设置的“powermanager(电源管理)”应用。

    注意:需要BLUETOOTH_ADMIN权限的场合,BLUETOOTH权限也是必需的。需要在manifest文件中声明Bluetooth权限,示例如下:

    <manifest... >

    <uses-permissionandroid:name="android.permission.BLUETOOTH" />

    ...

    </manifest>

    9.4 Setting UpBluetooth服务

    在用Bluetooth通讯之前,需要确认设备是否支持Bluetooth,如果支持,还得确保Bluetooth是可用的。

    如果设备不支持Bluetooth,需要优雅的将Bluetooth置为不可用。如果支持Bluetooth,但没有开启,可以在应用中请求开启Bluetooth。该设置使用BluetoothAdapter.通过两个步骤完成。

    (1)获取BluetoothAdapter

    BluetoothAdapter是每个Bluetooth的Activity都需要用到的。用静态方法getDefaultAdapter()获取BluetoothAdapter,返回一个拥有Bluetooth适配器的BluetoothAdapter对象。如果返回null,说明设备不支持Bluetooth,关于Bluetooth的故事到此就结束了(因为你干不了什么了)。示例:

    BluetoothAdaptermBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

    if(mBluetoothAdapter == null) {

    //Device does not support Bluetooth

    }

    (2)EnableBluetooth

    接下来,就是确保Bluetooth功能是开启的。调用isEnabled()来检查Bluetooth当前是否是开启的。用ACTION_REQUEST_ENABLEactionIntent调用startActivityForResult()来请求开启Bluetooth,这会通过系统设置发出一个Bluetooth使能请求(并且不会停止本应用程序)。示例:

    if(!mBluetoothAdapter.isEnabled()) {

    IntentenableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);

    startActivityForResult(enableBtIntent,REQUEST_ENABLE_BT);

    }

    用户请求使能Bluetooth时,会显示一个对话框。选择“Yes”,系统会使能Bluetooth,并且焦点会返回你的应用程序。

    如果使能Bluetooth成功,你的Activity会在onActivityResult()回调函数中收到RESULT_OK的结果码。如果Bluetooth使能因发生错误(或用户选择了“No”)而失败,收到的结果码将是RESULT_CANCELED。

    作为可选项,应用也可以侦听ACTION_STATE_CHANGEDbroadcastIntent,这样无论Bluetooth状态何时被改变系统都会发出broadcast(广播)。该广播包含附加的字段信息EXTRA_STATE和EXTRA_PREVIOUS_STATE分别代表新的和旧的Bluetooth状态,该字段可能的值为STATE_TURNING_ON,STATE_ON, STATE_TURNING_OFF,和STATE_OFF。应用运行时,侦听ACTION_STATE_CHANGED广播来检测Bluetooth状态的改变是很有用的。

    提示:启用Bluetooth可被发现功能能够自动开启Bluetooth。如果在完成Activity之前需要持续的使能Bluetooth可被发现功能,那么上面的第2步就可以忽略。

    9.5 Finding Devices服务

    使用BluetoothAdapter可以通过设备搜索或查询配对设备找到远程Bluetooth设备。

    Devicediscovery(设备搜索)是一个扫描搜索本地已使能Bluetooth设备并且从搜索到的设备请求一些信息的过程(有时候会收到类似“discovering”,“inquiring”或“scanning”)。但是,搜索到的本地Bluetooth设备只有在打开被发现功能后才会响应一个discovery请求,响应的信息包括设备名,类,唯一的MAC地址。发起搜寻的设备可以使用这些信息来初始化跟被发现的设备的连接。

    一旦与远程设备的第一次连接被建立,一个pairing请求就会自动提交给用户。如果设备已配对,配对设备的基本信息(名称,类,MAC地址)就被保存下来了,能够使用BluetoothAPI来读取这些信息。使用已知的远程设备的MAC地址,连接可以在任何时候初始化而不必先完成搜索(当然这是假设远程设备是在可连接的空间范围内)。

    需要记住,配对和连接是两个不同的概念:

    配对意思是两个设备相互意识到对方的存在,共享一个用来鉴别身份的链路键(link-key),能够与对方建立一个加密的连接。连接意思是两个设备现在共享一个RFCOMM信道,能够相互传输数据。

    目前AndroidBluetooth API's要求设备在建立RFCOMM信道前必须配对(配对是在使用BluetoothAPI初始化一个加密连接时自动完成的)。

    下面描述如何查询已配对设备,搜索新设备。

    注意:Android的电源设备默认是不能被发现的。用户可以通过系统设置让它在有限的时间内可以被发现,或者可以在应用程序中要求用户使能被发现功能。

    (1)Queryingpaired devices

    在搜索设备前,查询配对设备看需要的设备是否已经是已经存在是很值得的,可以调用getBondedDevices()来做到,该函数会返回一个描述配对设备BluetoothDevice的结果集。例如,可以使用ArrayAdapter查询所有配对设备然后显示所有设备名给用户:

    Set<BluetoothDevice>pairedDevices = mBluetoothAdapter.getBondedDevices();

    //If there are paired devices

    if(pairedDevices.size() > 0) {

    //Loop through paired devices

    for(BluetoothDevice device : pairedDevices) {

    //Add the name and address to an array adapter to show in a ListView

    mArrayAdapter.add(device.getName()+ "\n" + device.getAddress());

    }

    }

    BluetoothDevice对象中需要用来初始化一个连接唯一需要用到的信息就是MAC地址。

    (2)Discoveringdevices

    要开始搜索设备,只需简单的调用startDiscovery()。该函数时异步的,调用后立即返回,返回值表示搜索是否成功开始。搜索处理通常包括一个12秒钟的查询扫描,然后跟随一个页面显示搜索到设备Bluetooth名称。

    应用中可以注册一个带CTION_FOUNDIntent的BroadcastReceiver,搜索到每一个设备时都接收到消息。对于每一个设备,系统都会广播ACTION_FOUNDIntent,该Intent携带着而外的字段信息EXTRA_DEVICE和EXTRA_CLASS,分别包含一个BluetoothDevice和一个BluetoothClass。下面的示例显示如何注册和处理设备被发现后发出的广播:

    //Create a BroadcastReceiver for ACTION_FOUND

    privatefinal BroadcastReceiver mReceiver = new BroadcastReceiver() {

    publicvoid onReceive(Context context, Intent intent) {

    Stringaction = intent.getAction();

    //When discovery finds a device

    if(BluetoothDevice.ACTION_FOUND.equals(action)) {

    //Get the BluetoothDevice object from the Intent

    BluetoothDevicedevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

    //Add the name and address to an array adapter to show in a ListView

    mArrayAdapter.add(device.getName()+ "\n" + device.getAddress());

    }

    }

    };

    //Register the BroadcastReceiver

    IntentFilterfilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);

    registerReceiver(mReceiver,filter); // Don't forget to unregister during onDestroy

    警告:完成设备搜索对于Bluetooth适配器来说是一个重量级的处理,要消耗大量它的资源。一旦你已经找到一个设备来连接,请确保你在尝试连接前使用了cancelDiscovery()来停止搜索。同样,如果已经保持了一个连接的时候,同时执行搜索设备将会显著的降低连接的带宽,所以在连接的时候不应该执行搜索发现。

    (3)Enablingdiscoverability

    如果想让本地设备被其他设备发现,可以带ACTION_REQUEST_DISCOVERABLEaction Intent调用startActivityForResult(Intent,int)方法。该方法会提交一个请求通过系统刚设置使设备出于可以被发现的模式(而不影响应用程序)。默认情况下,设备在120秒后变为可以被发现的。可以通过额外增加EXTRA_DISCOVERABLE_DURATIONIntent自定义一个值,最大值是3600秒,0表示设备总是可以被发现的(小于0或者大于3600则会被自动设置为120秒)。下面示例设置时间为300:

    IntentdiscoverableIntent = new

    Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);

    discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,300);

    startActivity(discoverableIntent);

    询问用户是否允许打开设备可以被发现功能时会显示一个对话框。如果用户选择“Yes”,设备会在指定时间过后变为可以被发现的。Activity的onActivityResult()回调函数被调用,结果码等于设备变为可以被发现所需时长。如果用户选择“No”或者有错误发生,结果码会是Activity.RESULT_CANCELLED。

    提示:如果Bluetooth没有启用,启用Bluetooth可被发现功能能够自动开启Bluetooth。

    在规定的时间内,设备会静静的保持可以被发现模式。如果想在可以被发现模式被更改时受到通知,可以用ACTION_SCAN_MODE_CHANGEDIntent注册一个BroadcastReceiver,包含额外的字段信息EXTRA_SCAN_MODE和EXTRA_PREVIOUS_SCAN_MODE分别表示新旧扫描模式,其可能的值为SCAN_MODE_CONNECTABLE_DISCOVERABLE(discoverablemode),SCAN_MODE_CONNECTABLE(notin discoverable mode but still able to receiveconnections),SCAN_MODE_NONE(notin discoverable mode and unable to receive connections)。

    如果只需要连接远程设备就不需要打开设备的可以被发现功能。只在应用作为一个服务器socket的宿主用来接收进来的连接时才需要使能可以被发现功能,因为远程设备在初始化连接前必须先发现了你的设备。

    9.6 ConnectingDevices服务

    为了建立两个设备之间的应用的连接,需要完成服务器端和客户端,因为一个设备必须打开一个服务器socket而另外一个设备必须初始化连接(用服务器端的MAC地址)。服务器和客户端在各自获得一个基于同一个RFCOMM信道的已连接的BluetoothSocket对象后就被认为连接已经建立。这个时候,双方设备可以获取输入输出流,数据传输可以开始了。本节描述如何在两个设备之间初始化连接。

    服务器设备和客户端设备用不同的方式获取各自需要的BluetoothSocket对象。服务器端的在接收一个进来的连接时获取到,客户端的在打开一个与服务器端的RFCOMM信道的时候获取到。

    一个实现技巧是自动把每个设备作为服务器,这样就拥有了一个打开的socket用来侦听连接。然后任一设备就能够发起与另一个设备的连接,并成为客户端。另外,一个设备也可以明确的成为“host”,并打开一个服务端socket,另一个设备可以简单的发起连接。

    注意:如果两个设备之前没有配对,那么在连接处理过程中Android应用框架会自动显示一个配对请求的通知或对话框给用户。因此,当尝试连接设备时,应用不需要关心设备是否已经配对。RFCOMM连接会阻塞直到用户成功将设备配对(如果用户拒绝配对或者配对超时了连接会失败)。

    (1)Connectingas a server

    如果要连接两个设备,其中一个必须充当服务器,通过持有一个打开的BluetoothServerSocket对象。服务器socket的作用是侦听进来的连接,如果一个连接被接受,提供一个连接好的BluetoothSocket对象。从BluetoothServerSocket获取到BluetoothSocket对象之后,BluetoothServerSocket就可以(也应该)丢弃了,除非你还要用它来接收更多的连接。

    下面是建立服务器socket和接收一个连接的基本步骤:

    ①通过调用listenUsingRfcommWithServiceRecord(String,UUID)得到一个BluetoothServerSocket对象。

    该字符串为服务的识别名称,系统将自动写入到一个新的服务发现协议(SDP)数据库接入口到设备上的(名字是任意的,可以简单地是应用程序的名称)项。UUID也包括在SDP接入口中,将是客户端设备连接协议的基础。也就是说,当客户端试图连接本设备,它将携带一个UUID用来唯一标识它要连接的服务,UUID必须匹配,连接才会被接受。

    ② 通过调用accept()来侦听连接请求。

    这是一个阻塞的调用,知道有连接进来或者产生异常才会返回。只有远程设备发送一个连接请求,并且携带的UUID与侦听它socket注册的UUID匹配,连接请求才会被接受。如果成功,accept()将返回一个连接好的BluetoothSocket对象。

    ③ 除非需要再接收另外的连接,否则的话调用close()。

    close()释放serversocket和它的资源,但不会关闭连接accept()返回的连接好的BluetoothSocket对象。与TCP/IP不同,RFCOMM同一时刻一个信道只允许一个客户端连接,因此大多数情况下意味着在BluetoothServerSocket接受一个连接请求后应该立即调用close()。

    accept()调用不应该在主ActivityUI线程中进行,因为这是个阻塞的调用,会妨碍其他的交互。经常是在在一个新线程中做BluetoothServerSocket或BluetoothSocket的所有工作来避免UI线程阻塞。注意所有BluetoothServerSocket或BluetoothSocket的方法都是线程安全的。

    示例:

    下面是一个简单的接受连接的服务器组件代码示例:

    privateclass AcceptThread extends Thread {

    privatefinal BluetoothServerSocket mmServerSocket;

     

    publicAcceptThread() {

    //Use a temporary object that is later assigned to mmServerSocket,

    //because mmServerSocket is final

    BluetoothServerSockettmp = null;

    try{

    //MY_UUID is the app's UUID string, also used by the client code

    tmp= mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME,MY_UUID);

    }catch (IOException e) { }

    mmServerSocket= tmp;

    }

     

    publicvoid run() {

    BluetoothSocketsocket = null;

    //Keep listening until exception occurs or a socket is returned

    while(true) {

    try{

    socket= mmServerSocket.accept();

    }catch (IOException e) {

    break;

    }

    //If a connection was accepted

    if(socket != null) {

    //Do work to manage the connection (in a separate thread)

    manageConnectedSocket(socket);

    mmServerSocket.close();

    break;

    }

    }

    }

     

    /**Will cancel the listening socket, and cause the thread to finish */

    publicvoid cancel() {

    try{

    mmServerSocket.close();

    }catch (IOException e) { }

    }

    }

    本例中,仅仅只接受一个进来的连接,一旦连接被接受获取到BluetoothSocket,就发送获取到的BluetoothSocket给一个单独的线程,然后关闭BluetoothServerSocket并跳出循环。

    注意:accept()返回BluetoothSocket后,socket已经连接了,所以在客户端不应该呼叫connnect()。

    manageConnectedSocket()是一个虚方法,用来初始化线程好传输数据。

    通常应该在处理完侦听到的连接后立即关闭BluetoothServerSocket。在本例中,close()在得到BluetoothSocket后马上被调用。还需要在线程中提供一个公共的方法来关闭私有的BluetoothSocket,停止服务端socket的侦听。

    (2)Connectingas a client

    为了实现与远程设备的连接,你必须首先获得一个代表远程设备BluetoothDevice对象。然后使用BluetoothDevice对象来获取一个BluetoothSocket来实现来接。

    下面是基本的步骤:

    ①用BluetoothDevice调用createRfcommSocketToServiceRecord(UUID)获取一个BluetoothSocket对象。

    这个初始化的BluetoothSocket会连接到BluetoothDevice。UUID必须匹配服务器设备在打开BluetoothServerSocket时用到的UUID(用listenUsingRfcommWithServiceRecord(String,UUID))。可以简单的生成一个UUID串然后在服务器和客户端都使用该UUID。

    ② 调用connect()完成连接

    当调用这个方法的时候,系统会在远程设备上完成一个SDP查找来匹配UUID。如果查找成功并且远程设备接受连接,就共享RFCOMM信道,connect()会返回。这也是一个阻塞的调用,不管连接失败还是超时(12秒)都会抛出异常。

    注意:要确保在调用connect()时没有同时做设备查找,如果在查找设备,该连接尝试会显著的变慢,慢得类似失败了。

    实例:

    下面是一个完成Bluetooth连接的样例线程:

    privateclass ConnectThread extends Thread {

    privatefinal BluetoothSocket mmSocket;

    privatefinal BluetoothDevice mmDevice;

     

    publicConnectThread(BluetoothDevice device) {

    //Use a temporary object that is later assigned to mmSocket,

    //because mmSocket is final

    BluetoothSockettmp = null;

    mmDevice= device;

     

    //Get a BluetoothSocket to connect with the given BluetoothDevice

    try{

    //MY_UUID is the app's UUID string, also used by the server code

    tmp= device.createRfcommSocketToServiceRecord(MY_UUID);

    }catch (IOException e) { }

    mmSocket= tmp;

    }

     

    publicvoid run() {

    //Cancel discovery because it will slow down the connection

    mBluetoothAdapter.cancelDiscovery();

     

    try{

    //Connect the device through the socket. This will block

    //until it succeeds or throws an exception

    mmSocket.connect();

    }catch (IOException connectException) {

    //Unable to connect; close the socket and get out

    try{

    mmSocket.close();

    }catch (IOException closeException) { }

    return;

    }

     

    //Do work to manage the connection (in a separate thread)

    manageConnectedSocket(mmSocket);

    }

     

    /**Will cancel an in-progress connection, and close the socket */

    publicvoid cancel() {

    try{

    mmSocket.close();

    }catch (IOException e) { }

    }

    }

    注意到cancelDiscovery()在连接操作前被调用。在连接之前,不管搜索有没有进行,该调用都是安全的,不需要确认(当然如果有要确认的需求,可以调用isDiscovering())。

    manageConnectedSocket()是一个虚方法,用来初始化线程好传输数据。

    在对BluetoothSocket的处理完成后,记得调用close()来关闭连接的socket和清理所有的内部资源。

    9.7 Managing aConnection服务

    如果已经连接了两个设备,他们都已经拥有各自的连接好的BluetoothSocket对象。那就是一个有趣的开始,因为你可以在设备间共享数据了。使用BluetoothSocket,传输任何数据通常来说都很容易了:

    (1)通过socket获取输入输出流来处理传输(分别使用getInputStream()和getOutputStream())。

    (2)用read(byte[])和write(byte[])来实现读写。

    仅此而已。

    当然,还是有很多细节需要考虑的。首要的,需要用一个专门的线程来实现流的读写。只是很重要的,因为read(byte[])和write(byte[])都是阻塞的调用。read(byte[])会阻塞直到流中有数据可读。write(byte[])通常不会阻塞,但是如果远程设备调用read(byte[])不够快导致中间缓冲区满,它也可能阻塞。所以线程中的主循环应该用于读取InputStream。线程中也应该有单独的方法用来完成写OutputStream。

    示例:

    下面是一个如上面描述那样的例子:

    privateclass ConnectedThread extends Thread {

    privatefinal BluetoothSocket mmSocket;

    privatefinal InputStream mmInStream;

    privatefinal OutputStream mmOutStream;

     

    publicConnectedThread(BluetoothSocket socket) {

    mmSocket= socket;

    InputStreamtmpIn = null;

    OutputStreamtmpOut = null;

     

    //Get the input and output streams, using temp objects because

    //member streams are final

    try{

    tmpIn= socket.getInputStream();

    tmpOut= socket.getOutputStream();

    }catch (IOException e) { }

     

    mmInStream= tmpIn;

    mmOutStream= tmpOut;

    }

     

    publicvoid run() {

    byte[]buffer = new byte[1024]; // buffer store for the stream

    intbytes; // bytes returned from read()

     

    //Keep listening to the InputStream until an exception occurs

    while(true) {

    try{

    //Read from the InputStream

    bytes= mmInStream.read(buffer);

    //Send the obtained bytes to the UI Activity

    mHandler.obtainMessage(MESSAGE_READ,bytes, -1, buffer)

    .sendToTarget();

    }catch (IOException e) {

    break;

    }

    }

    }

     

    /*Call this from the main Activity to send data to the remote device */

    publicvoid write(byte[] bytes) {

    try{

    mmOutStream.write(bytes);

    }catch (IOException e) { }

    }

     

    /*Call this from the main Activity to shutdown the connection */

    publicvoid cancel() {

    try{

    mmSocket.close();

    }catch (IOException e) { }

    }

    }

    构造函数中得到需要的流,一旦执行,线程会等待从InputStream来的数据。当read(byte[])返回从流中读到的字节后,数据通过父类的成员Handler被送到主Activity,然后继续等待读取流中的数据。

    向外发送数据只需简单的调用线程的write()方法。

    线程的cancel()方法时很重要的,以便连接可以在任何时候通过关闭BluetoothSocket来终止。它应该总在处理完Bluetooth连接后被调用。

    9.8 Working withProfiles服务

    从Android3.0开始,BluetoothAPI就包含了对Bluetoothprofiles的支持。Bluetoothprofile是基于蓝牙的设备之间通信的无线接口规范。例如Hands-Freeprofile(免提模式)。如果移动电话要连接一个无线耳机,他们都要支持Hands-Freeprofile。

    你在你的类里可以完成BluetoothProfile接口来支持某一Bluetoothprofiles。AndroidBluetooth API完成了下面的Bluetoothprofile:

    Headset:Headsetprofile提供了移动电话上的Bluetooth耳机支持。Android提供了BluetoothHeadset类,它是一个协议,用来通过IPC(interprocesscommunication)控制BluetoothHeadset Service。BluetoothHeadset既包含BluetoothHeadset profile也包含Hands-Freeprofile,还包括对AT命令的支持。

    A2DP:AdvancedAudio Distribution Profile (A2DP)profile,高级音频传输模式。Android提供了BluetoothA2dp类,这是一个通过IPC来控制BluetoothA2DP的协议。

    下面是使用profile的基本步骤:

    ① 获取默认的Bluetooth适配器。

    ②使用getProfileProxy()来建立一个与profile相关的profile协议对象的连接。在下面的例子中,profile协议对象是BluetoothHeadset的一个实例。

    ③设置BluetoothProfile.ServiceListener。该listener通知BluetoothProfileIPC客户端,当客户端连接或断连服务器的时候。

    ④在onServiceConnected()内,得到一个profile协议对象的句柄。

    ⑤一旦拥有了profile协议对象,就可以用它来监控连接的状态,完成于该profile相关的其他操作。

    例如,下面的代码片段显示如何连接到一个BluetoothHeadset协议对象,用来控制Headsetprofile:

    BluetoothHeadsetmBluetoothHeadset;

     

    //Get the default adapter

    BluetoothAdaptermBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

     

    //Establish connection to the proxy.

    mBluetoothAdapter.getProfileProxy(context,mProfileListener, BluetoothProfile.HEADSET);

     

    privateBluetoothProfile.ServiceListener mProfileListener = newBluetoothProfile.ServiceListener() {

    publicvoid onServiceConnected(int profile, BluetoothProfile proxy) {

    if(profile == BluetoothProfile.HEADSET) {

    mBluetoothHeadset= (BluetoothHeadset) proxy;

    }

    }

    publicvoid onServiceDisconnected(int profile) {

    if(profile == BluetoothProfile.HEADSET) {

    mBluetoothHeadset= null;

    }

    }

    };

     

    //... call functions on mBluetoothHeadset

     

    //Close proxy connection after use.

    mBluetoothAdapter.closeProfileProxy(mBluetoothHeadset);

    十 总结与疑问

    1.蓝牙驱动的上下电是怎么完成的?是通过操作寄存器,还是其他的方式?

    2.bccmd命令主要用来初始化蓝牙,比如说上电,设置波特率,下载firmware,它是如何实现的?

    3.如何设置PSKEY键值?

    4.蓝牙协议栈bluez是如何通过dbus总线与framework层进行数据交互的?

    展开全文
  • 关键词:蓝牙blueZ UART HCI_UART H4 HCI L2CAP RFCOMM 版本:基于android4.2之前版本 bluez内核:linux/linux3.08系统:android/android4.1.3.4作者:xubin341719(欢迎转载,请注明作者,请尊重版权谢谢)欢迎...

    关键词:蓝牙blueZ  UART  HCI_UART H4  HCI  L2CAP RFCOMM 
    版本:基于android4.2之前版本 bluez
    内核:linux/linux3.08
    系统:android/android4.1.3.4
    作者:xubin341719(欢迎转载,请注明作者,请尊重版权谢谢)
    欢迎指正错误,共同学习、共同进步!!

    Android bluetooth介绍(一):基本概念及硬件接口
    Android bluetooth介绍(二): android 蓝牙代码架构及其uart 到rfcomm流程
    Android bluetooth介绍(三): 蓝牙扫描(scan)设备分析
    Android bluetooth介绍(四): a2dp connect流程分析

    一、Android Bluetooth Architecture蓝牙代码架构部分(google 官方蓝牙框架)


    Android的蓝牙系统,自下而上包括以下一些内容如上图所示:
    1、串口驱动
    Linux的内核的蓝牙驱动程、Linux的内核的蓝牙协议的层
    2、BlueZ的适配器
    BlueZ的(蓝牙在用户空间的函式库)

    bluez代码结构
    Bluetooth协议栈BlueZ分为两部分:内核代码和用户态程序及工具集。
    (1)、内核代码:由BlueZ核心协议和驱动程序组成
    Bluetooth协议实现在内核源代码 kernel/net/bluetooth中。包括hci,l2cap,hid,rfcomm,sco,SDP,BNEP等协议的实现。
    (2)、驱动程序:kernel/driver/bluetooth中,包含Linuxkernel对各种接口的
    Bluetooth device的驱动,如:USB接口,串口等。
    (3)、用户态程序及工具集:
    包括应用程序接口和BlueZ工具集。BlueZ提供函数库以及应用程序接口,便于程序员开发bluetooth应用程序。BlueZ utils是主要工具集,实现对bluetooth设备的初始化和控制。

    3、蓝牙相关的应用程序接口
    Android.buletooth包中的各个Class(蓝牙在框架层的内容-----java)

    类名

    作用

    BluetoothAdapter

    本地蓝牙设备的适配类,所有的蓝牙操作都要通过该类完成

    BluetoothClass

    用于描述远端设备的类型,特点等信息

    BluetoothDevice

    蓝牙设备类,代表了蓝牙通讯过程中的远端设备

    BluetoothServerSocket

    蓝牙设备服务端,类似ServerSocket

    BluetoothSocket

    蓝牙设备客户端,类似Socket

    BluetoothClass.Device

    蓝牙关于设备信息

    BluetoothClass.Device.Major

    蓝牙设备管理

    BluetoothClass.Service

    蓝牙相关服务

    同样下图也是一张比较经典的蓝牙代码架构图(google官方提供)


    二、蓝牙通过Hciattach启动串口流程:
    1、hciattach总体流程


    2、展讯hciattach代码实现流程:

    三、具体代码分析
    1、initrc中定义

    idh.code\device\sprd\sp8830ec_nwcn\init.sc8830.rc

    service hciattach /system/bin/hciattach -n /dev/sttybt0 sprd_shark
        socket bluetooth stream 660 bluetooth bluetooth
        user bluetooth
        group wifi bluetooth net_bt_admin net_bt inet net_raw net_admin system
        disabled
    oneshot
    

    adb 下/dev/ttybt0(不同平台有所不同)

    PS 进程中:hicattch

    2、/system/bin/hciattach 执行的Main函数
    idh.code\external\bluetooth\bluez\tools\hciattach.c
    service hciattach /system/bin/hciattach -n /dev/sttybt0 sprd_shark
    传进两个参数,/dev/sttybt0 和 sprd_shark

    nt main(int argc, char *argv[])
    {
    ………………
    	for (n = 0; optind < argc; n++, optind++) {
    		char *opt;
    
    		opt = argv[optind];
    
    		switch(n) {
    		case 0://(1)、解析驱动的位置;
    			dev[0] = 0;
    			if (!strchr(opt, '/'))
    				strcpy(dev, "/dev/");
    			strcat(dev, opt);
    			break;
    
    		case 1://(2)、解析串口的配置相关参数;
    			if (strchr(argv[optind], ',')) {
    				int m_id, p_id;
    				sscanf(argv[optind], "%x,%x", &m_id, &p_id);
    				u = get_by_id(m_id, p_id);
    			} else {
    				u = get_by_type(opt);
    			}
    
    			if (!u) {
    				fprintf(stderr, "Unknown device type or id\n");
    				exit(1);
    			}
    
    			break;
    
    		case 2://(3)、通过对前面参数的解析,把uart[i]中的数值初始化;
    			u->speed = atoi(argv[optind]);
    			break;
    
    		case 3:
    			if (!strcmp("flow", argv[optind]))
    				u->flags |=  FLOW_CTL;
    			else
    				u->flags &= ~FLOW_CTL;
    			break;
    
    		case 4:
    			if (!strcmp("sleep", argv[optind]))
    				u->pm = ENABLE_PM;
    			else
    				u->pm = DISABLE_PM;
    			break;
    
    		case 5:
    			u->bdaddr = argv[optind];
    			break;
    		}
    	}
    
    ………………
    	if (init_speed)//初始化串口速率;
    		u->init_speed = init_speed;
    ………………
    	n = init_uart(dev, u, send_break, raw);//(4)、初始化串口;
    ………………
    
    	return 0;
    }
    

    (1)、解析驱动的位置;

    			if (!strchr(opt, '/'))
    				strcpy(dev, "/dev/");
    service hciattach /system/bin/hciattach -n /dev/sttybt0 sprd_shark
    dev = /dev/ttyb0
    

    (2)、解析串口的配置相关参数;获取参数对应的结构体;

    	u = get_by_id(m_id, p_id);
    static struct uart_t * get_by_id(int m_id, int p_id)
    {
    	int i;
    	for (i = 0; uart[i].type; i++) {
    		if (uart[i].m_id == m_id && uart[i].p_id == p_id)
    			return &uart[i];
    	}
    	return NULL;
    }
    

    这个函数比较简单,通过循环对比,如传进了的参数sprd_shark和uart结构体中的对比,找到对应的数组。如果是其他蓝牙芯片,如博通、RDA、BEKN等着到其相对应的初始化配置函数。

    struct uart_t uart[] = {
    	{ "any",        0x0000, 0x0000, HCI_UART_H4,   115200, 115200,
    				FLOW_CTL, DISABLE_PM, NULL, NULL     },
    	{ "sprd_shark",        0x0000, 0x0000, HCI_UART_H4,   115200, 115200,
    				FLOW_CTL, DISABLE_PM, NULL, init_sprd_config     },
    
    	{ "ericsson",   0x0000, 0x0000, HCI_UART_H4,   57600,  115200,
    				FLOW_CTL, DISABLE_PM, NULL, ericsson },
    
    ………………
    	{ "bk3211",    0x0000, 0x0000, HCI_UART_BCSP,   115200, 921600, 0, DISABLE_PM,   NULL, beken_init, NULL},
    	{ NULL, 0 }
    };
    

    注意:init_sprd_config这个函数在uart_init中用到,这个函数其实对我们具体芯片的初始化配置。
    注释:HCI_UART_H4和HCI_UART_BCSP的区别如下图。

    (3)、通过对前面参数的解析,把uart[i]中的数值初始化;

    			u->speed = atoi(argv[optind]);
    			break;
    

    (4)、初始化串口;

    n = init_uart(dev, u, send_break, raw);
    idh.code\external\bluetooth\bluez\tools\hciattach.c
    /* Initialize UART driver */
    int init_uart(char *dev, struct uart_t *u, int send_break)
    {
     struct termios ti;
     int  fd, i;
     fd = open(dev, O_RDWR | O_NOCTTY);//打开串口设备,其中标志
    //O_RDWR,可以对此设备进行读写操作;
    //O_NOCTTY:告诉Unix这个程序不想成为“控制终端”控制的程序,不说明这个标志的话,任何输入都会影响你的程序。
    //O_NDELAY:告诉Unix这个程序不关心DCD信号线状态,即其他端口是否运行,不说明这个标志的话,该程序就会在DCD信号线为低电平时停止。
    //但是不要以控制 tty 的模式,因为我们并不希望在发送 Ctrl-C
     后结束此进程
     if (fd < 0) {
      perror(“Can’t open serial port”);
      return -1;
     }
     //drop fd’s data;
     tcflush(fd, TCIOFLUSH);//清空数据线
     if (tcgetattr(fd, &ti) < 0) {
      perror(“Can’t get port settings”);
      return -1;
     }
     cfmakeraw(&ti);
    cfmakeraw sets the terminal attributes as follows://此函数设置串口终端的以下这些属性,
    termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP
    |INLCR|IGNCR|ICRNL|IXON);
    termios_p->c_oflag &= ~OPOST;
    termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
    termios_p->c_cflag &= ~(CSIZE|PARENB) ;
    termios_p->c_cflag |=CS8;
     ti.c_cflag |= CLOCAL;//本地连接,无调制解调器控制
     if (u->flags & FLOW_CTL)
      ti.c_cflag |= CRTSCTS;//输出硬件流控
     else
      ti.c_cflag &= ~CRTSCTS;
     if (tcsetattr(fd, TCSANOW, &ti) < 0) {//启动新的串口设置
      perror(“Can’t set port settings”);
      return -1;
     }
     /* Set initial baudrate */
     if (set_speed(fd, &ti, u->init_speed) < 0) {//设置串口的传输速率bps, 也可以使
    //用 cfsetispeed 和 cfsetospeed 来设置
      perror(“Can’t set initial baud rate”);
      return -1;
     }
     tcflush(fd, TCIOFLUSH);//清空数据线
     if (send_break)
      tcsendbreak(fd, 0);
    //int tcsendbreak ( int fd, int duration );Sends a break for
    //the given time.在串口线上发送0值,至少维持0.25秒。
    //If duration is 0, it transmits zero-valued bits for at least 0.25 seconds, and
    //not more than 0.5seconds.
     //where place register u’s init function;
     if (u->init && u->init(fd, u, &ti) < 0)
    //所有bluez支持的蓝牙串口设备类型构成了一个uart结构数组,通过
    //查找对应的uart类型,这个uart的init成员显示了它的init调用方法;
    struct uart_t uart[] = {
    { "any", 0x0000, 0x0000, HCI_UART_H4, 115200, 115200,FLOW_CTL, DISABLE_PM, NULL, NULL     },
    { "sprd_shark", 0x0000, 0x0000, HCI_UART_H4, 115200, 115200,FLOW_CTL, DISABLE_PM, NULL, init_sprd_config     },
    
    { "ericsson", 0x0000, 0x0000, HCI_UART_H4,   57600,  115200,FLOW_CTL, DISABLE_PM, NULL, ericsson },
    ………………
    	{ "bk3211",    0x0000, 0x0000, HCI_UART_BCSP,   115200, 921600, 0, DISABLE_PM,   NULL, beken_init, NULL},
    	{ NULL, 0的init函数名为bcsp,定义在本文件中**;
      return -1;
     tcflush(fd, TCIOFLUSH);//清空数据线
     /* Set actual baudrate */
     if (set_speed(fd, &ti, u->speed) < 0) {
      perror(“Can’t set baud rate”);
      return -1;
     }
     /* Set TTY to N_HCI line discipline */
     i = N_HCI;
     if (ioctl(fd, TIOCSETD, &i) < 0) {//
    TIOCSETD int *ldisc//改变到 i 行规,即hci行规
    Change to the new line discipline pointed to by ldisc. The available line disciplines are listed in 
    /* ioctl (fd, TIOCSERGETLSR, &result) where result may be as below */
    /* line disciplines */
    #define N_TTY  0
    ……
    #define N_HCI  15  /* Bluetooth HCI UART */
    
      perror(“Can’t set line discipline”);
      return -1;
     }
     if (ioctl(fd, HCIUARTSETPROTO, u->proto) < 0) {
    //设置hci设备的proto操作函数集为hci_uart操作集;
      perror(“Can’t set device”);
      return -1;
     }
     return fd;
    }
    

    这里一个重要的部分是:u->init指向init_sprd_config
    4、uart具体到芯片的初始化init_sprd_config(这部分根据不同的芯片,对应进入其相应初始化部分)
    idh.code\external\bluetooth\bluez\tools\hciattach_sprd.c

    int sprd_config_init(int fd, char *bdaddr, struct termios *ti)
    {
    	int i,psk_fd,fd_btaddr,ret = 0,r,size=0,read_btmac=0;
    	unsigned char resp[30];
    	BT_PSKEY_CONFIG_T bt_para_tmp;
    	char bt_mac[30] = {0};
    	char bt_mac_tmp[20] = {0};
    	uint8 bt_mac_bin[32]     = {0};
    
    	fprintf(stderr,"init_sprd_config in \n");
    //(1)、这部分检查bt_mac,如果存在,从文件中读取,如果不存在,随机生成,并写入相应文件;
    	if(access(BT_MAC_FILE, F_OK) == 0) {//这部分检查bt_mac
    		LOGD("%s: %s exists",__FUNCTION__, BT_MAC_FILE);
    		fd_btaddr = open(BT_MAC_FILE, O_RDWR);// #define BT_MAC_FILE		"/productinfo/btmac.txt"
    		if(fd_btaddr>=0) {
    			size = read(fd_btaddr, bt_mac, sizeof(bt_mac));//读取BT_MAC_FILE中的地址;
    			LOGD("%s: read %s %s, size=%d",__FUNCTION__, BT_MAC_FILE, bt_mac, size);
    			if(size == BT_RAND_MAC_LENGTH){
    						LOGD("bt mac already exists, no need to random it");
    						fprintf(stderr, "read btmac ok \n");
    						read_btmac=1;
    			}
    …………
    	}else{//如果不存在,就随机生成一个bt_mac地址,写入/productinfo/btmac.txt
    		fprintf(stderr, "btmac.txt not exsit!\n");
    		read_btmac=0;
    		mac_rand(bt_mac);
    		LOGD("bt random mac=%s",bt_mac);
    		printf("bt_mac=%s\n",bt_mac);
    		write_btmac2file(bt_mac);
    
    		fd_btaddr = open(BT_MAC_FILE, O_RDWR);
    		if(fd_btaddr>=0) {
    			size = read(fd_btaddr, bt_mac, sizeof(bt_mac));
    			LOGD("%s: read %s %s, size=%d",__FUNCTION__, BT_MAC_FILE, bt_mac, size);
    			if(size == BT_RAND_MAC_LENGTH){
    						LOGD("bt mac already exists, no need to random it");
    						fprintf(stderr, "read btmac ok \n");
    						read_btmac=1;
    			}
    			close(fd_btaddr);
    …………
    	}
    
    	/* Reset the BT Chip */
    
    	memset(resp, 0, sizeof(resp));
    	memset(&bt_para_tmp, 0, sizeof(BT_PSKEY_CONFIG_T) );
    	ret = getPskeyFromFile(  (void *)(&bt_para_tmp) );//ret = get_pskey_from_file(&bt_para_tmp);//(2)、PSKey参数、射频参数的设定;
           if(ret != 0){//参数失败处理
    			fprintf(stderr, "get_pskey_from_file faill \n");
    			/* Send command from hciattach*/
    			if(read_btmac == 1){
    				memcpy(bt_para_setting.device_addr, bt_mac_bin, sizeof(bt_para_setting.device_addr));// (3)、读取失败,把bt_para_setting中defaut参数写入;
    			}
    			if (write(fd, (char *)&bt_para_setting, sizeof(BT_PSKEY_CONFIG_T)) != sizeof(BT_PSKEY_CONFIG_T)) {
    				fprintf(stderr, "Failed to write reset command\n");
    				return -1;
    			}
            }else{//getpskey成功处理
    			/* Send command from pskey_bt.txt*/
    			if(read_btmac == 1){
    				memcpy(bt_para_tmp.device_addr, bt_mac_bin, sizeof(bt_para_tmp.device_addr));
    			}
    …………
    	return 0;
    }
    

    (1)、这部分检查bt_mac,如果存在,从文件中读取,如果不存在,随机生成,并写入相应文件/productinfo/btmac.txt
    (2)、PSKey参数、射频参数的设定;
    get_pskey_from_file(&bt_para_tmp);这个函数后面分析;
    (3)、读取失败,把bt_para_setting中defaut参数写入;频率、主从设备设定等……

    // pskey file structure default value
    BT_PSKEY_CONFIG_T bt_para_setting={
    5,
    0,
    0,
    0,
    0,
    0x18cba80,
    0x001f00,
    0x1e,
    {0x7a00,0x7600,0x7200,0x5200,0x2300,0x0300},
    …………
    };
    

    5、get_pskey_from_file 解析相关射频参数
    idh.code\external\bluetooth\bluez\tools\pskey_get.c

    int getPskeyFromFile(void *pData)
    {
    …………
            char *BOARD_TYPE_PATH = "/dev/board_type";//(1)、判断PCB的版本;
            int fd_board_type;
            char board_type_str[MAX_BOARD_TYPE_LEN] = {0};
            int board_type;
            char *CFG_2351_PATH_2 = "/productinfo/2351_connectivity_configure.ini";//(2)、最终生成ini文件存储的位置;
            char *CFG_2351_PATH[MAX_BOARD_TYPE];
    		(3)、针对不同PCB版本,不同的ini配置文件;
            CFG_2351_PATH[0] = "/system/etc/wifi/2351_connectivity_configure_hw100.ini";
            CFG_2351_PATH[1] = "/system/etc/wifi/2351_connectivity_configure_hw102.ini";
            CFG_2351_PATH[2] = "/system/etc/wifi/2351_connectivity_configure_hw104.ini";
    

    (4)、下面函数就不做具体分析,大致意识是,根据/dev/board_type中,读取的PCB类型,设置不同的ini文件。   

    ………………
    	ret = chmod(CFG_2351_PATH_2, 0644);
    	ALOGE("chmod 0664 %s ret:%d\n", CFG_2351_PATH_2, ret);	
    	if(pBuf == pBuf2)
    		free(pBuf1);
    ………………
    }
    

    (1)、判断PCB的版本;
    char *BOARD_TYPE_PATH = "/dev/board_type";

    (2)、最终生成ini文件存储的位置,就是系统运行时读取ini文件的地方;
    char *CFG_2351_PATH_2 ="/productinfo/2351_connectivity_configure.ini";
    (3)、针对不同PCB版本,不同的ini配置文件;

            CFG_2351_PATH[0] = "/system/etc/wifi/2351_connectivity_configure_hw100.ini";
            CFG_2351_PATH[1] = "/system/etc/wifi/2351_connectivity_configure_hw102.ini";
            CFG_2351_PATH[2] = "/system/etc/wifi/2351_connectivity_configure_hw104.ini";
    

    (4)、下面函数就不做具体分析,大致意识是,根据/dev/board_type中,读取的PCB类型,设置不同的ini文件。         覆盖到(2)中的文件。
    四、HCI_UART_H4和H4层的加入

    uart->hci_uart->Uart-H4->hci:从uart开始分析,介绍整个驱动层数据流(涉及tty_uart中断,   线路层ldisc_bcsp、tasklet、work queue、skb_buffer的等)

    这是数据的流动过程,最底层的也就是和硬件打交道的是uart层了,它的存在和起作用是通过串口驱动来保证的,这个请参阅附录,但是其它的层我们都不知道什么时候work的,下面来看。

    1、idh.code\kernel\drivers\bluetooth\hci_ldisc.c

    static int __init hci_uart_init(void)
    {
    	static struct tty_ldisc_ops hci_uart_ldisc;
    	int err;
    	/* Register the tty discipline */
    
    	memset(&hci_uart_ldisc, 0, sizeof (hci_uart_ldisc));
    	hci_uart_ldisc.magic		= TTY_LDISC_MAGIC;
    	hci_uart_ldisc.name		= "n_hci";
    	hci_uart_ldisc.open		= hci_uart_tty_open;
    	hci_uart_ldisc.close		= hci_uart_tty_close;
    	hci_uart_ldisc.read		= hci_uart_tty_read;
    	hci_uart_ldisc.write		= hci_uart_tty_write;
    	hci_uart_ldisc.ioctl		= hci_uart_tty_ioctl;
    	hci_uart_ldisc.poll		= hci_uart_tty_poll;
    	hci_uart_ldisc.receive_buf	= hci_uart_tty_receive;
    	hci_uart_ldisc.write_wakeup	= hci_uart_tty_wakeup;
    	hci_uart_ldisc.owner		= THIS_MODULE;
    
    	if ((err = tty_register_ldisc(N_HCI, &hci_uart_ldisc))) {//(1)、这部分完成ldisc的注册;
    		BT_ERR("HCI line discipline registration failed. (%d)", err);
    		return err;
    	}
    
    #ifdef CONFIG_BT_HCIUART_H4
    	h4_init();//(2)、我们蓝牙芯片用的是H4,这部分完成H4的注册;
    #endif
    #ifdef CONFIG_BT_HCIUART_BCSP
    	bcsp_init();
    #endif
    ………………
    	return 0;
    }
    

    (1)、这部分完成ldisc的注册;
    tty_register_ldisc(N_HCI,&hci_uart_ldisc)
    注册了一个ldisc,这是通过把新的ldisc放在一个ldisc的数组里面实现的,tty_ldiscs是一个全局的ldisc数组里面会根据序号对应一个ldisc,这个序号就是上层通过ioctl来指定的,比如我们在前面已经看到的:
    i = N_HCI;
    ioctl(fd, TIOCSETD, &i) < 0
    可以看到这里指定的N_HCI刚好就是这里注册的这个号码15;
    (2)、蓝牙芯片用的是H4,这部分完成H4的注册;
             h4_init();
    hci_uart_proto结构体的初始化:

    idh.code\kernel\drivers\bluetooth\hci_h4.c

    static struct hci_uart_proto h4p = {
    	.id		= HCI_UART_H4,
    	.open		= h4_open,
    	.close		= h4_close,
    	.recv		= h4_recv,
    	.enqueue	= h4_enqueue,
    	.dequeue	= h4_dequeue,
    	.flush		= h4_flush,
    };
    

    H4的注册:
    idh.code\kernel\drivers\bluetooth\hci_h4.c

    int __init h4_init(void)
    {
    	int err = hci_uart_register_proto(&h4p);
    
    	if (!err)
    		BT_INFO("HCI H4 protocol initialized");
    	else
    		BT_ERR("HCI H4 protocol registration failed");
    
    	return err;
    }
    

    这是通过hci_uart_register_proto(&bcsp)来完成的,这个函数非常简单,本质如下:
    hup[p->id]= p;其中static struct hci_uart_proto*hup[HCI_UART_MAX_PROTO];也就是说把对应于协议p的id和协议p连接起来,这样设计的好处是hci uart层本身可以支持不同的协议,包括h4、bcsp等,通过这个数组连接这些协议,等以后有数据的时候调用对应的协议来处理,这里比较关键的是h4里面的这些函数。
    五、HCI层的加入
    hci的加入是通过hci_register_dev函数来做的,这时候用户通过hciconfig就可以看到有一个接口了,通过这个接口用户可以访问底层的信息了,hci0已经生成;至于它在何时被加入的,我们再看看hciattach在内核里面的处理过程;

    1、TIOCSEATD的处理流程

    Ioctl的作用是设置一个新的ldisc;
    2、HCIUARTSETPROTO的处理流程:

    这部分比较重要,注册生成hci0, 初始化3个工作队列,hci_rx_work、hci_tx_work、hci_cmd_work;完成hci部分数据、命令的接收、发送。
    六、数据在驱动的传递流程
    1、uart数据接收
             这部分流程比较简单,其实就是注册一个tty驱动程序和相对应的函数,注册相应的open\close\ioctl等方法,通过应用open /dev/ttyS*操作,注册中断接收函数,接收处理蓝牙模块触发中断的数据。

    在这个中断函数里面会接受到来自于蓝牙模块的数据;在中断函数里面会先读取串口的状态寄存器判断是否是data准备好,如果准备好就调用serial_sprd_rx_chars函数来接收数据,下面看看这个函数是如何处理的:

    那就是把数据一个个的加入到uart层的缓冲区,直到底层不处于dataready状态,或者读了maxcount个数,当读完后就调用tty层的接口把数据传递给tty层,tty层则把数据交给了ldisc,于是控制权也就交给了hci_uart层;

    七、Hci_uart的数据接收
    它基本上就是要个二传手,通过:

             spin_lock(&hu->rx_lock);
             hu->proto->recv(hu,(void *) data, count);
             hu->hdev->stat.byte_rx+= count;
             spin_unlock(&hu->rx_lock);
    把数据交给了在它之上的协议层,对于我们的设置来说实际上就交给了h4层;
    八、H4层处理
    这层主要是通过函数h4_recv来处理的,根据协议处理包头、CRC等,然后调用更上层的hci_recv_frame来处理已经剥去h4包头的数据;

    如图:

    九、HCI以上的处理

    这里的hci_rx_work前面已经看到它了,它是一个工作队列用来处理hci层的数据接收的;先看是否有进程打开hci的socket用来监听数据,如果有的话,就把数据的一个copy发送给它,然后根据包的类型调用不同的处理函数,分别对应于event、acl、sco处理;
    hci_event_packet是对于事件的处理,里面包含有包括扫描,信号,授权,pin码,总之基本上上层所能收到的事件,基本都是在这里处理的,它的很多信息都是先存起来,等待上层的查询然后才告诉上层;
    hci_acldata_packet是一个经常的情况,也就是说上层通常都是使用的是l2cap的接口,而l2cap就是基于这个的,如下图所示:

    到这里如果有基于BTPROTO_L2CAP的socket,那么这个socket就可以收到数据了;再看看BTPROTO_RFCOMM的流程:

    十、 数据流程的总结
    简单总结一下,数据的流程,
    |基本上是:
    1, uart口取得蓝牙模块的数据;
    2, uart口通过ldisc传给hci_uart;
    3, hci_uart传给在其上的h4;
    4, h4传给hci层;
    5, hci层传给l2cap层
    6, l2cap层再传给rfcomm;

    展开全文
  • Android Bluetooth架构

    千次阅读 2020-11-20 15:34:08
    Android系统蓝牙的整体架构,bluedroid作为蓝牙协议栈,实现了蓝牙的所有操作。 整个系统的架构很简单,因为大多的事情都是在Bluedroid层里面做的,上层只管去Call和Callback就是了,这部分我们在后面分析代码做...

    Android Bluetooth架构

    Android系统蓝牙的整体架构

    Applications:Android蓝牙应用程序,就是使用蓝牙的API的程序;
    Framework:提供给应用使用的API,我们平时使用的BluetoothAdapter,BluetoothDevice,BluetoothSocket等;
    Bluetooth Service:即Bluetooth APP(Bluetooth.apk)这个应该也是属于java framework范畴,不过由于它比较特殊,所以独立出来,提供所有的上层服务以及与Bluedroid底层进行交互。其中btAdapter主要提供蓝牙的基本操作,比如enable, disable, discovery, pair, unpair, createRfcomm等,其他的就都是Profile的各自的Service了;
    Bluedroid:蓝牙协议栈,提供所有蓝牙的实际操作,开关蓝牙,蓝牙的管理,搜索管理,链路管理,各种profile的实现,包括HCI,ACL,SCO,L2CAP,各种profile等;

    整个系统的架构很简单,因为大多的事情都是在Bluedroid层里面做的,上层只管去Call和Callback就是了,这部分我们在后面分析代码做详细分析。

    Bluetooth Framework层介绍

    Bluetooth framework层的作用只要是连接bluetooth service,为其它应用提供使用蓝牙的接口,起连接上下层的作用,没有太多的逻辑,下面只给出概要框图,不做过多描述。
    在这里插入图片描述
    From: https://blog.csdn.net/zjli321/article/details/52402968

    BlueDroid架构介绍

    在这里插入图片描述
    From: https://blog.csdn.net/zjli321/article/details/52402968

    BlueDroid主要分为3个部分:BTIF, BTA, Stack
    BTIF:作为bluedroid与上层java framework的通道,提供bluedroid对外的接口,提供所有Bluetooth.apk需要的API
    BTA:提供各种profile的实现和处理
    Stack:协议实现与连接管理
    BTE:提供具体芯片相关的fops
    BTU:承接BTA与HCI
    BTM:蓝牙配对与链路管理
    HCI:读取或写入数据到蓝牙hw

    下文未标注部分均来自: https://segmentfault.com/a/1190000002982295

    Java Application/JNI

    Bluetooth app运行于Java层,并通过JNI和协议栈的接口(stack’s Interface Layer,BTIF)进行如下交互:
    JNI层的作用是将Java调用转化为C函数的调用,同时为应用和协议栈提供了调用/回调的交互能力。

    BTIF (Bluetooth Profile Interface)

    作为bluedroid与上层java framework的通道,提供了操作接口.
    Bluetooth Profile Interface在Bluetooth Application task (BTA)和JNI层之间充当了胶水层的角色,对上层(JNI)提供了所有profile功能性的接口。这一层上有一个Bluetooth Interface Instance,所有Profile的操作函数都注册在其中(GAP, AV, DM, PAN, HF,HH, HL, Storage, Sockets)。Client应用通过操作这个Instance来操作Profile。

    BTA

    BTA用于和Bluetooth Application层交互,实现蓝牙设备管理、状态管理以及一些Profile的操作和状态机。
    BTA是Bluetooth Application的缩写,从上层下来的请求都要经过这个BTA层,通过发送消息的方式将请求丢到BTA层中处理。
    所有BTA消息送到BTU_TASK中,由bta_sys_event来处理;如果是Gatt相关的消息,由bta_gatt_hdl_event处理。

    Profile状态机包含以下几个主要组成部分:
    BTA_profilexx_act.c:包含对应Profile的“Action”函数,一般来说由Profile状态机调用。
    BTA_profilexx_api.c: 对应Profile的API的具体实现。通常它们是提供给用户使用,完成usecase的函数和回调
    BTA_profilexx_ci.c:对应Profile的“call-in”函数的实现(供Profile以外的模块调用)
    BTA_profilexx_co.c: 对应Profile的“call-out”函数的实现(调用Profile以外的模块)
    BTA_profilexx_main.c: 对应Profile的状态机和处理协议栈上传消息的handler的具体实现。主要负责维护Profile状态的变化及其引起的“Action”

    BTE

    BTE提供具体芯片相关的fops。通过HCI与厂商蓝牙芯片交互以实现了蓝牙协议栈的通用功能和相关协议。BTE还包括一个统一内核接口(GKI)。

    HCI and HAL Layers

    HCI层由libbt-hci共享库组成,负责通讯层(transport layer,如UART H4或者SMD channel)和协议栈之间的交互。HCI层抽象了BTE的ops,将BTE的ops抽象成bluedroid的接口。
    HAL层libbt-vendor共享库组成,是各厂商的解决方案的特定实现。HAL层实现了各厂商对OPCODE的处理逻辑,这些OPCODE声明在HCI层的bt_vendor_lib.h中。

    Core Stack

    核心协议栈(Core Stack)会被编译成一个linux动态库,其中包含的模块如下图所示。每一个模块都由api函数和函数回调接口组成。
    在这里插入图片描述

    TASK管理

    Bluedroid中,协议栈,蓝牙规范和蓝牙应用都运行在一个用户进程"com.android.bluetooth"之中。
    蓝牙代码在以下四种task代表的上下文(context)中运行:

    • BTIF_TASK
    • BTU_TASK
    • A2DP_MEDIA_TASK
    • GKI_TIMER_TASK
      task之间通过消息(message)来交换信息。
      调用JNI层的API函数会通过消息转发机制,在BTIF_TASK中执行。
      JNI,HAL回调在BTIF_TASK中执行。
      有需要时,BTIF_TASK中的调用可切换到BTU_TASK中执行。
      蓝牙规范(Profiles)和协议的实现代码在BTU_TASK中执行。
      蓝牙传输驱动程序中有一条rx线程(bt_hc_worker_thread)负责从UART/SMD中读取数据。
    GKI模块

    内核统一接口。该层是一个适配层,适配了OS相关的进程、内存相关的管理,还可以用于线程间传递消息 。主要通过变量gki_cb实现对进程的统一管理。GKI模块在Bluedroid中主要用于线程间通信。

    蓝牙总体流程图

    从流程图我们可以看出来 蓝牙应用通过binder和系统蓝牙Service进行通讯 ,然后通过Jin与蓝牙HAL层进行通讯.
    From: https://blog.csdn.net/ChaoLi_Chen/article/details/108285847
    在这里插入图片描述

    Bluetooth各模块总图

    From: https://blog.csdn.net/javon_hzw/article/details/48731281
    在这里插入图片描述

    bluedroid的通用架构框图

    From: https://blog.csdn.net/xiaojsj111/article/details/12647923
    在这里插入图片描述

    名词解释

    HAL :HardwareAbstraction Layer
    Btif :Bluetooth interface
    Bta :Bluetooth application
    Btu :Bluetooth uper layer
    Bte :Bluetooth embedded layer
    Btm :Bluetooth device manager
    CO : callout\CI: call in
    HF : Handsfree Profile
    HH :HID Host Profile
    HL :Health Device Profile
    AV :audio\vidio
    Ag :audiogateway
    Ar :audio/videoregi stration
    Gattc :GATT client
    Gatts :GATT server
    BLE :Bluetooth Low Energy

    展开全文
  • Android Bluetooth Architecture蓝牙代码架构部分(google 官方蓝牙框架)

    原文出处:http://blog.csdn.net/xubin341719/article/details/38519739

    感谢本文作者xubin341719的技术分享。

    一、Android Bluetooth Architecture蓝牙代码架构部分(google 官方蓝牙框架)


    Android的蓝牙系统,自下而上包括以下一些内容如上图所示:
    1、串口驱动
    Linux的内核的蓝牙驱动程、Linux的内核的蓝牙协议的层
    2、BlueZ的适配器
    BlueZ的(蓝牙在用户空间的函式库)

    bluez代码结构
    Bluetooth协议栈BlueZ分为两部分:内核代码和用户态程序及工具集。
    (1)、内核代码:由BlueZ核心协议和驱动程序组成
    Bluetooth协议实现在内核源代码 kernel/net/bluetooth中。包括hci,l2cap,hid,rfcomm,sco,SDP,BNEP等协议的实现。
    (2)、驱动程序:kernel/driver/bluetooth中,包含Linuxkernel对各种接口的
    Bluetooth device的驱动,如:USB接口,串口等。
    (3)、用户态程序及工具集:
    包括应用程序接口和BlueZ工具集。BlueZ提供函数库以及应用程序接口,便于程序员开发bluetooth应用程序。BlueZ utils是主要工具集,实现对bluetooth设备的初始化和控制。

    3、蓝牙相关的应用程序接口
    Android.buletooth包中的各个Class(蓝牙在框架层的内容-----java)

    类名

    作用

    BluetoothAdapter

    本地蓝牙设备的适配类,所有的蓝牙操作都要通过该类完成

    BluetoothClass

    用于描述远端设备的类型,特点等信息

    BluetoothDevice

    蓝牙设备类,代表了蓝牙通讯过程中的远端设备

    BluetoothServerSocket

    蓝牙设备服务端,类似ServerSocket

    BluetoothSocket

    蓝牙设备客户端,类似Socket

    BluetoothClass.Device

    蓝牙关于设备信息

    BluetoothClass.Device.Major

    蓝牙设备管理

    BluetoothClass.Service

    蓝牙相关服务

    同样下图也是一张比较经典的蓝牙代码架构图(google官方提供)


    二、蓝牙通过Hciattach启动串口流程:
    1、hciattach总体流程


    2、展讯hciattach代码实现流程:

    三、具体代码分析
    1、initrc中定义

    idh.code\device\sprd\sp8830ec_nwcn\init.sc8830.rc

    [html]  view plain  copy
    1. service hciattach /system/bin/hciattach -n /dev/sttybt0 sprd_shark  
    2.     socket bluetooth stream 660 bluetooth bluetooth  
    3.     user bluetooth  
    4.     group wifi bluetooth net_bt_admin net_bt inet net_raw net_admin system  
    5.     disabled  
    6. oneshot  

    adb 下/dev/ttybt0(不同平台有所不同)

    PS 进程中:hicattch

    2、/system/bin/hciattach 执行的Main函数
    idh.code\external\bluetooth\bluez\tools\hciattach.c
    service hciattach /system/bin/hciattach -n /dev/sttybt0 sprd_shark
    传进两个参数,/dev/sttybt0 和 sprd_shark

    [html]  view plain  copy
    1. nt main(int argc, char *argv[])  
    2. {  
    3. ………………  
    4.     for (n = 0; optind < argc; n++, optind++) {  
    5.         char *opt;  
    6.   
    7.         opt = argv[optind];  
    8.   
    9.         switch(n) {  
    10.         case 0://(1)、解析驱动的位置;  
    11.             dev[0] = 0;  
    12.             if (!strchr(opt, '/'))  
    13.                 strcpy(dev, "/dev/");  
    14.             strcat(dev, opt);  
    15.             break;  
    16.   
    17.         case 1://(2)、解析串口的配置相关参数;  
    18.             if (strchr(argv[optind], ',')) {  
    19.                 int m_id, p_id;  
    20.                 sscanf(argv[optind], "%x,%x", &m_id, &p_id);  
    21.                 u = get_by_id(m_id, p_id);  
    22.             } else {  
    23.                 u = get_by_type(opt);  
    24.             }  
    25.   
    26.             if (!u) {  
    27.                 fprintf(stderr, "Unknown device type or id\n");  
    28.                 exit(1);  
    29.             }  
    30.   
    31.             break;  
    32.   
    33.         case 2://(3)、通过对前面参数的解析,把uart[i]中的数值初始化;  
    34.             u->speed = atoi(argv[optind]);  
    35.             break;  
    36.   
    37.         case 3:  
    38.             if (!strcmp("flow", argv[optind]))  
    39.                 u->flags |=  FLOW_CTL;  
    40.             else  
    41.                 u->flags &= ~FLOW_CTL;  
    42.             break;  
    43.   
    44.         case 4:  
    45.             if (!strcmp("sleep", argv[optind]))  
    46.                 u->pm = ENABLE_PM;  
    47.             else  
    48.                 u->pm = DISABLE_PM;  
    49.             break;  
    50.   
    51.         case 5:  
    52.             u->bdaddr = argv[optind];  
    53.             break;  
    54.         }  
    55.     }  
    56.   
    57. ………………  
    58.     if (init_speed)//初始化串口速率;  
    59.         u->init_speed = init_speed;  
    60. ………………  
    61.     n = init_uart(dev, u, send_break, raw);//(4)、初始化串口;  
    62. ………………  
    63.   
    64.     return 0;  
    65. }  

    (1)、解析驱动的位置;

    [html]  view plain  copy
    1.             if (!strchr(opt, '/'))  
    2.                 strcpy(dev, "/dev/");  
    3. service hciattach /system/bin/hciattach -n /dev/sttybt0 sprd_shark  
    4. dev = /dev/ttyb0  

    (2)、解析串口的配置相关参数;获取参数对应的结构体;

    [html]  view plain  copy
    1.     u = get_by_id(m_id, p_id);  
    2. static struct uart_t * get_by_id(int m_id, int p_id)  
    3. {  
    4.     int i;  
    5.     for (i = 0; uart[i].type; i++) {  
    6.         if (uart[i].m_id == m_id && uart[i].p_id == p_id)  
    7.             return &uart[i];  
    8.     }  
    9.     return NULL;  
    10. }  

    这个函数比较简单,通过循环对比,如传进了的参数sprd_shark和uart结构体中的对比,找到对应的数组。如果是其他蓝牙芯片,如博通、RDA、BEKN等着到其相对应的初始化配置函数。

    [html]  view plain  copy
    1. struct uart_t uart[] = {  
    2.     { "any",        0x0000, 0x0000, HCI_UART_H4,   115200, 115200,  
    3.                 FLOW_CTL, DISABLE_PM, NULL, NULL     },  
    4.     { "sprd_shark",        0x0000, 0x0000, HCI_UART_H4,   115200, 115200,  
    5.                 FLOW_CTL, DISABLE_PM, NULL, init_sprd_config     },  
    6.   
    7.     { "ericsson",   0x0000, 0x0000, HCI_UART_H4,   57600,  115200,  
    8.                 FLOW_CTL, DISABLE_PM, NULL, ericsson },  
    9.   
    10. ………………  
    11.     { "bk3211",    0x0000, 0x0000, HCI_UART_BCSP,   115200, 921600, 0, DISABLE_PM,   NULL, beken_init, NULL},  
    12.     { NULL, 0 }  
    13. };  

    注意:init_sprd_config这个函数在uart_init中用到,这个函数其实对我们具体芯片的初始化配置。
    注释:HCI_UART_H4和HCI_UART_BCSP的区别如下图。

    (3)、通过对前面参数的解析,把uart[i]中的数值初始化;

    [html]  view plain  copy
    1. u->speed = atoi(argv[optind]);  
    2. break;  

    (4)、初始化串口;

    [html]  view plain  copy
    1. n = init_uart(dev, u, send_break, raw);  
    2. idh.code\external\bluetooth\bluez\tools\hciattach.c  
    3. /* Initialize UART driver */  
    4. int init_uart(char *dev, struct uart_t *u, int send_break)  
    5. {  
    6.  struct termios ti;  
    7.  int  fd, i;  
    8.  fd = open(dev, O_RDWR | O_NOCTTY);//打开串口设备,其中标志  
    9. //O_RDWR,可以对此设备进行读写操作;  
    10. //O_NOCTTY:告诉Unix这个程序不想成为“控制终端”控制的程序,不说明这个标志的话,任何输入都会影响你的程序。  
    11. //O_NDELAY:告诉Unix这个程序不关心DCD信号线状态,即其他端口是否运行,不说明这个标志的话,该程序就会在DCD信号线为低电平时停止。  
    12. //但是不要以控制 tty 的模式,因为我们并不希望在发送 Ctrl-C  
    13.  后结束此进程  
    14.  if (fd < 0) {  
    15.   perror(“Can’t open serial port”);  
    16.   return -1;  
    17.  }  
    18.  //drop fd’s data;  
    19.  tcflush(fd, TCIOFLUSH);//清空数据线  
    20.  if (tcgetattr(fd, &ti) < 0) {  
    21.   perror(“Can’t get port settings”);  
    22.   return -1;  
    23.  }  
    24.  cfmakeraw(&ti);  
    25. cfmakeraw sets the terminal attributes as follows://此函数设置串口终端的以下这些属性,  
    26. termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP  
    27. |INLCR|IGNCR|ICRNL|IXON);  
    28. termios_p->c_oflag &= ~OPOST;  
    29. termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);  
    30. termios_p->c_cflag &= ~(CSIZE|PARENB) ;  
    31. termios_p->c_cflag |=CS8;  
    32.  ti.c_cflag |= CLOCAL;//本地连接,无调制解调器控制  
    33.  if (u->flags & FLOW_CTL)  
    34.   ti.c_cflag |= CRTSCTS;//输出硬件流控  
    35.  else  
    36.   ti.c_cflag &= ~CRTSCTS;  
    37.  if (tcsetattr(fd, TCSANOW, &ti) < 0) {//启动新的串口设置  
    38.   perror(“Can’t set port settings”);  
    39.   return -1;  
    40.  }  
    41.  /* Set initial baudrate */  
    42.  if (set_speed(fd, &ti, u->init_speed) < 0) {//设置串口的传输速率bps, 也可以使  
    43. //用 cfsetispeed 和 cfsetospeed 来设置  
    44.   perror(“Can’t set initial baud rate”);  
    45.   return -1;  
    46.  }  
    47.  tcflush(fd, TCIOFLUSH);//清空数据线  
    48.  if (send_break)  
    49.   tcsendbreak(fd, 0);  
    50. //int tcsendbreak ( int fd, int duration );Sends a break for  
    51. //the given time.在串口线上发送0值,至少维持0.25秒。  
    52. //If duration is 0, it transmits zero-valued bits for at least 0.25 seconds, and  
    53. //not more than 0.5seconds.  
    54.  //where place register u’s init function;  
    55.  if (u->init && u->init(fd, u, &ti) < 0)  
    56. //所有bluez支持的蓝牙串口设备类型构成了一个uart结构数组,通过  
    57. //查找对应的uart类型,这个uart的init成员显示了它的init调用方法;  
    58. struct uart_t uart[] = {  
    59. { "any", 0x0000, 0x0000, HCI_UART_H4, 115200, 115200,FLOW_CTL, DISABLE_PM, NULL, NULL     },  
    60. { "sprd_shark", 0x0000, 0x0000, HCI_UART_H4, 115200, 115200,FLOW_CTL, DISABLE_PM, NULL, init_sprd_config     },  
    61.   
    62. { "ericsson", 0x0000, 0x0000, HCI_UART_H4,   57600,  115200,FLOW_CTL, DISABLE_PM, NULL, ericsson },  
    63. ………………  
    64.     { "bk3211",    0x0000, 0x0000, HCI_UART_BCSP,   115200, 921600, 0, DISABLE_PM,   NULL, beken_init, NULL},  
    65.     { NULL, 0的init函数名为bcsp,定义在本文件中**;  
    66.   return -1;  
    67.  tcflush(fd, TCIOFLUSH);//清空数据线  
    68.  /* Set actual baudrate */  
    69.  if (set_speed(fd, &ti, u->speed) < 0) {  
    70.   perror(“Can’t set baud rate”);  
    71.   return -1;  
    72.  }  
    73.  /* Set TTY to N_HCI line discipline */  
    74.  i = N_HCI;  
    75.  if (ioctl(fd, TIOCSETD, &i) < 0) {//  
    76. TIOCSETD int *ldisc//改变到 i 行规,即hci行规  
    77. Change to the new line discipline pointed to by ldisc. The available line disciplines are listed in   
    78. /* ioctl (fd, TIOCSERGETLSR, &result) where result may be as below */  
    79. /* line disciplines */  
    80. #define N_TTY  0  
    81. ……  
    82. #define N_HCI  15  /* Bluetooth HCI UART */  
    83.   
    84.   perror(“Can’t set line discipline”);  
    85.   return -1;  
    86.  }  
    87.  if (ioctl(fd, HCIUARTSETPROTO, u->proto) < 0) {  
    88. //设置hci设备的proto操作函数集为hci_uart操作集;  
    89.   perror(“Can’t set device”);  
    90.   return -1;  
    91.  }  
    92.  return fd;  
    93. }  

    这里一个重要的部分是:u->init指向init_sprd_config
    4、uart具体到芯片的初始化init_sprd_config(这部分根据不同的芯片,对应进入其相应初始化部分)
    idh.code\external\bluetooth\bluez\tools\hciattach_sprd.c

    [html]  view plain  copy
    1. int sprd_config_init(int fd, char *bdaddr, struct termios *ti)  
    2. {  
    3.     int i,psk_fd,fd_btaddr,ret = 0,r,size=0,read_btmac=0;  
    4.     unsigned char resp[30];  
    5.     BT_PSKEY_CONFIG_T bt_para_tmp;  
    6.     char bt_mac[30] = {0};  
    7.     char bt_mac_tmp[20] = {0};  
    8.     uint8 bt_mac_bin[32]     = {0};  
    9.   
    10.     fprintf(stderr,"init_sprd_config in \n");  
    11. //(1)、这部分检查bt_mac,如果存在,从文件中读取,如果不存在,随机生成,并写入相应文件;  
    12.     if(access(BT_MAC_FILE, F_OK) == 0) {//这部分检查bt_mac  
    13.         LOGD("%s: %s exists",__FUNCTION__, BT_MAC_FILE);  
    14.         fd_btaddr = open(BT_MAC_FILE, O_RDWR);// #define BT_MAC_FILE        "/productinfo/btmac.txt"  
    15.         if(fd_btaddr>=0) {  
    16.             size = read(fd_btaddr, bt_mac, sizeof(bt_mac));//读取BT_MAC_FILE中的地址;  
    17.             LOGD("%s: read %s %s, size=%d",__FUNCTION__, BT_MAC_FILE, bt_mac, size);  
    18.             if(size == BT_RAND_MAC_LENGTH){  
    19.                         LOGD("bt mac already exists, no need to random it");  
    20.                         fprintf(stderr, "read btmac ok \n");  
    21.                         read_btmac=1;  
    22.             }  
    23. …………  
    24.     }else{//如果不存在,就随机生成一个bt_mac地址,写入/productinfo/btmac.txt  
    25.         fprintf(stderr, "btmac.txt not exsit!\n");  
    26.         read_btmac=0;  
    27.         mac_rand(bt_mac);  
    28.         LOGD("bt random mac=%s",bt_mac);  
    29.         printf("bt_mac=%s\n",bt_mac);  
    30.         write_btmac2file(bt_mac);  
    31.   
    32.         fd_btaddr = open(BT_MAC_FILE, O_RDWR);  
    33.         if(fd_btaddr>=0) {  
    34.             size = read(fd_btaddr, bt_mac, sizeof(bt_mac));  
    35.             LOGD("%s: read %s %s, size=%d",__FUNCTION__, BT_MAC_FILE, bt_mac, size);  
    36.             if(size == BT_RAND_MAC_LENGTH){  
    37.                         LOGD("bt mac already exists, no need to random it");  
    38.                         fprintf(stderr, "read btmac ok \n");  
    39.                         read_btmac=1;  
    40.             }  
    41.             close(fd_btaddr);  
    42. …………  
    43.     }  
    44.   
    45.     /* Reset the BT Chip */  
    46.   
    47.     memset(resp, 0, sizeof(resp));  
    48.     memset(&bt_para_tmp, 0, sizeof(BT_PSKEY_CONFIG_T) );  
    49.     ret = getPskeyFromFile(  (void *)(&bt_para_tmp) );//ret = get_pskey_from_file(&bt_para_tmp);//(2)、PSKey参数、射频参数的设定;  
    50.        if(ret != 0){//参数失败处理  
    51.             fprintf(stderr, "get_pskey_from_file faill \n");  
    52.             /* Send command from hciattach*/  
    53.             if(read_btmac == 1){  
    54.                 memcpy(bt_para_setting.device_addr, bt_mac_bin, sizeof(bt_para_setting.device_addr));// (3)、读取失败,把bt_para_setting中defaut参数写入;  
    55.             }  
    56.             if (write(fd, (char *)&bt_para_setting, sizeof(BT_PSKEY_CONFIG_T)) != sizeof(BT_PSKEY_CONFIG_T)) {  
    57.                 fprintf(stderr, "Failed to write reset command\n");  
    58.                 return -1;  
    59.             }  
    60.         }else{//getpskey成功处理  
    61.             /* Send command from pskey_bt.txt*/  
    62.             if(read_btmac == 1){  
    63.                 memcpy(bt_para_tmp.device_addr, bt_mac_bin, sizeof(bt_para_tmp.device_addr));  
    64.             }  
    65. …………  
    66.     return 0;  
    67. }  

    (1)、这部分检查bt_mac,如果存在,从文件中读取,如果不存在,随机生成,并写入相应文件/productinfo/btmac.txt
    (2)、PSKey参数、射频参数的设定;
    get_pskey_from_file(&bt_para_tmp);这个函数后面分析;
    (3)、读取失败,把bt_para_setting中defaut参数写入;频率、主从设备设定等……

    [html]  view plain  copy
    1. // pskey file structure default value  
    2. BT_PSKEY_CONFIG_T bt_para_setting={  
    3. 5,  
    4. 0,  
    5. 0,  
    6. 0,  
    7. 0,  
    8. 0x18cba80,  
    9. 0x001f00,  
    10. 0x1e,  
    11. {0x7a00,0x7600,0x7200,0x5200,0x2300,0x0300},  
    12. …………  
    13. };  

    5、get_pskey_from_file 解析相关射频参数
    idh.code\external\bluetooth\bluez\tools\pskey_get.c

    [html]  view plain  copy
    1. int getPskeyFromFile(void *pData)  
    2. {  
    3. …………  
    4.         char *BOARD_TYPE_PATH = "/dev/board_type";//(1)、判断PCB的版本;  
    5.         int fd_board_type;  
    6.         char board_type_str[MAX_BOARD_TYPE_LEN] = {0};  
    7.         int board_type;  
    8.         char *CFG_2351_PATH_2 = "/productinfo/2351_connectivity_configure.ini";//(2)、最终生成ini文件存储的位置;  
    9.         char *CFG_2351_PATH[MAX_BOARD_TYPE];  
    10.         (3)、针对不同PCB版本,不同的ini配置文件;  
    11.         CFG_2351_PATH[0] = "/system/etc/wifi/2351_connectivity_configure_hw100.ini";  
    12.         CFG_2351_PATH[1] = "/system/etc/wifi/2351_connectivity_configure_hw102.ini";  
    13.         CFG_2351_PATH[2] = "/system/etc/wifi/2351_connectivity_configure_hw104.ini";  

    (4)、下面函数就不做具体分析,大致意识是,根据/dev/board_type中,读取的PCB类型,设置不同的ini文件。   

    [html]  view plain  copy
    1. ………………  
    2.     ret = chmod(CFG_2351_PATH_2, 0644);  
    3.     ALOGE("chmod 0664 %s ret:%d\n", CFG_2351_PATH_2, ret);    
    4.     if(pBuf == pBuf2)  
    5.         free(pBuf1);  
    6. ………………  
    7. }  

    (1)、判断PCB的版本;
    char *BOARD_TYPE_PATH = "/dev/board_type";

    (2)、最终生成ini文件存储的位置,就是系统运行时读取ini文件的地方;
    char *CFG_2351_PATH_2 ="/productinfo/2351_connectivity_configure.ini";
    (3)、针对不同PCB版本,不同的ini配置文件;

    [html]  view plain  copy
    1. CFG_2351_PATH[0] = "/system/etc/wifi/2351_connectivity_configure_hw100.ini";  
    2. CFG_2351_PATH[1] = "/system/etc/wifi/2351_connectivity_configure_hw102.ini";  
    3. CFG_2351_PATH[2] = "/system/etc/wifi/2351_connectivity_configure_hw104.ini";  

    (4)、下面函数就不做具体分析,大致意识是,根据/dev/board_type中,读取的PCB类型,设置不同的ini文件。         覆盖到(2)中的文件。
    四、HCI_UART_H4和H4层的加入

    uart->hci_uart->Uart-H4->hci:从uart开始分析,介绍整个驱动层数据流(涉及tty_uart中断,   线路层ldisc_bcsp、tasklet、work queue、skb_buffer的等)

    这是数据的流动过程,最底层的也就是和硬件打交道的是uart层了,它的存在和起作用是通过串口驱动来保证的,这个请参阅附录,但是其它的层我们都不知道什么时候work的,下面来看。

    1、idh.code\kernel\drivers\bluetooth\hci_ldisc.c

    [html]  view plain  copy
    1. static int __init hci_uart_init(void)  
    2. {  
    3.     static struct tty_ldisc_ops hci_uart_ldisc;  
    4.     int err;  
    5.     /* Register the tty discipline */  
    6.   
    7.     memset(&hci_uart_ldisc, 0, sizeof (hci_uart_ldisc));  
    8.     hci_uart_ldisc.magic        = TTY_LDISC_MAGIC;  
    9.     hci_uart_ldisc.name     = "n_hci";  
    10.     hci_uart_ldisc.open     = hci_uart_tty_open;  
    11.     hci_uart_ldisc.close        = hci_uart_tty_close;  
    12.     hci_uart_ldisc.read     = hci_uart_tty_read;  
    13.     hci_uart_ldisc.write        = hci_uart_tty_write;  
    14.     hci_uart_ldisc.ioctl        = hci_uart_tty_ioctl;  
    15.     hci_uart_ldisc.poll     = hci_uart_tty_poll;  
    16.     hci_uart_ldisc.receive_buf  = hci_uart_tty_receive;  
    17.     hci_uart_ldisc.write_wakeup = hci_uart_tty_wakeup;  
    18.     hci_uart_ldisc.owner        = THIS_MODULE;  
    19.   
    20.     if ((err = tty_register_ldisc(N_HCI, &hci_uart_ldisc))) {//(1)、这部分完成ldisc的注册;  
    21.         BT_ERR("HCI line discipline registration failed. (%d)", err);  
    22.         return err;  
    23.     }  
    24.   
    25. #ifdef CONFIG_BT_HCIUART_H4  
    26.     h4_init();//(2)、我们蓝牙芯片用的是H4,这部分完成H4的注册;  
    27. #endif  
    28. #ifdef CONFIG_BT_HCIUART_BCSP  
    29.     bcsp_init();  
    30. #endif  
    31. ………………  
    32.     return 0;  
    33. }  

    (1)、这部分完成ldisc的注册;
    tty_register_ldisc(N_HCI,&hci_uart_ldisc)
    注册了一个ldisc,这是通过把新的ldisc放在一个ldisc的数组里面实现的,tty_ldiscs是一个全局的ldisc数组里面会根据序号对应一个ldisc,这个序号就是上层通过ioctl来指定的,比如我们在前面已经看到的:
    i = N_HCI;
    ioctl(fd, TIOCSETD, &i) < 0
    可以看到这里指定的N_HCI刚好就是这里注册的这个号码15;
    (2)、蓝牙芯片用的是H4,这部分完成H4的注册;
             h4_init();
    hci_uart_proto结构体的初始化:

    idh.code\kernel\drivers\bluetooth\hci_h4.c

    [html]  view plain  copy
    1. static struct hci_uart_proto h4p = {  
    2.     .id     = HCI_UART_H4,  
    3.     .open       = h4_open,  
    4.     .close      = h4_close,  
    5.     .recv       = h4_recv,  
    6.     .enqueue    = h4_enqueue,  
    7.     .dequeue    = h4_dequeue,  
    8.     .flush      = h4_flush,  
    9. };  

    H4的注册:
    idh.code\kernel\drivers\bluetooth\hci_h4.c

    [html]  view plain  copy
    1. int __init h4_init(void)  
    2. {  
    3.     int err = hci_uart_register_proto(&h4p);  
    4.   
    5.     if (!err)  
    6.         BT_INFO("HCI H4 protocol initialized");  
    7.     else  
    8.         BT_ERR("HCI H4 protocol registration failed");  
    9.   
    10.     return err;  
    11. }  

    这是通过hci_uart_register_proto(&bcsp)来完成的,这个函数非常简单,本质如下:
    hup[p->id]= p;其中static struct hci_uart_proto*hup[HCI_UART_MAX_PROTO];也就是说把对应于协议p的id和协议p连接起来,这样设计的好处是hci uart层本身可以支持不同的协议,包括h4、bcsp等,通过这个数组连接这些协议,等以后有数据的时候调用对应的协议来处理,这里比较关键的是h4里面的这些函数。
    五、HCI层的加入
    hci的加入是通过hci_register_dev函数来做的,这时候用户通过hciconfig就可以看到有一个接口了,通过这个接口用户可以访问底层的信息了,hci0已经生成;至于它在何时被加入的,我们再看看hciattach在内核里面的处理过程;

    1、TIOCSEATD的处理流程

    Ioctl的作用是设置一个新的ldisc;
    2、HCIUARTSETPROTO的处理流程:

    这部分比较重要,注册生成hci0, 初始化3个工作队列,hci_rx_work、hci_tx_work、hci_cmd_work;完成hci部分数据、命令的接收、发送。
    六、数据在驱动的传递流程
    1、uart数据接收
             这部分流程比较简单,其实就是注册一个tty驱动程序和相对应的函数,注册相应的open\close\ioctl等方法,通过应用open /dev/ttyS*操作,注册中断接收函数,接收处理蓝牙模块触发中断的数据。

    在这个中断函数里面会接受到来自于蓝牙模块的数据;在中断函数里面会先读取串口的状态寄存器判断是否是data准备好,如果准备好就调用serial_sprd_rx_chars函数来接收数据,下面看看这个函数是如何处理的:

    那就是把数据一个个的加入到uart层的缓冲区,直到底层不处于dataready状态,或者读了maxcount个数,当读完后就调用tty层的接口把数据传递给tty层,tty层则把数据交给了ldisc,于是控制权也就交给了hci_uart层;

    七、Hci_uart的数据接收
    它基本上就是要个二传手,通过:

    [html]  view plain  copy
    1. spin_lock(&hu->rx_lock);  
    2. hu->proto->recv(hu,(void *) data, count);  
    3. hu->hdev->stat.byte_rx+= count;  
    4. spin_unlock(&hu->rx_lock);  
    把数据交给了在它之上的协议层,对于我们的设置来说实际上就交给了h4层;
    八、H4层处理
    这层主要是通过函数h4_recv来处理的,根据协议处理包头、CRC等,然后调用更上层的hci_recv_frame来处理已经剥去h4包头的数据;

    如图:

    九、HCI以上的处理

    这里的hci_rx_work前面已经看到它了,它是一个工作队列用来处理hci层的数据接收的;先看是否有进程打开hci的socket用来监听数据,如果有的话,就把数据的一个copy发送给它,然后根据包的类型调用不同的处理函数,分别对应于event、acl、sco处理;
    hci_event_packet是对于事件的处理,里面包含有包括扫描,信号,授权,pin码,总之基本上上层所能收到的事件,基本都是在这里处理的,它的很多信息都是先存起来,等待上层的查询然后才告诉上层;
    hci_acldata_packet是一个经常的情况,也就是说上层通常都是使用的是l2cap的接口,而l2cap就是基于这个的,如下图所示:

    到这里如果有基于BTPROTO_L2CAP的socket,那么这个socket就可以收到数据了;再看看BTPROTO_RFCOMM的流程:

    十、 数据流程的总结
    简单总结一下,数据的流程,
    |基本上是:
    1, uart口取得蓝牙模块的数据;
    2, uart口通过ldisc传给hci_uart;
    3, hci_uart传给在其上的h4;
    4, h4传给hci层;
    5, hci层传给l2cap层
    6, l2cap层再传给rfcomm;

    展开全文
  • 1. 前言 自1994年由爱立信推出至今,蓝牙技术已经走过了20个岁月。...说实话,如今的蓝牙4.0,简直是一个大杂烩:BR/EDR沿用旧的蓝牙规范;LE抄袭802.15.4;AMP直接使用802.11。而这一切的目的,就是以兼容性
  • Android系统驱动介绍

    千次阅读 2019-08-12 09:02:37
    Android在Linux内核中增加的主要是一些驱动程序,这些驱动程主要分为两种:Android专用驱动Android使用的设备驱动Android专用驱动    Android专用驱动程序不是Linux的标准驱动,它们的作用是辅助系统运行,...
  • 第一篇:蓝牙综合介绍 ,主要介绍蓝牙的一些概念,产生背景,发展轨迹,市面蓝牙介绍,以及蓝牙开发板介绍。 第二篇:Transport层介绍,主要介绍蓝牙协议栈跟蓝牙芯片之前的硬件传输协议,比如基于UART的H4,H5,BCSP,...
  • ANDROID的 BLUETOOTH 实现机制与架构

    千次阅读 2016-11-30 10:02:53
    目前ANDROID4.0的蓝牙API提供了五种蓝牙无线接口规范(Bluetooth profile)的支持,用来在设备之间通过蓝牙实现特定功能:包括 Headset和Hands-Freeprofile(实现蓝牙耳机功能),A2dpprofile(第二代蓝牙声音设备...
  • android 音频系统/声卡驱动 codec

    万次阅读 2017-04-19 00:11:31
    2. 系统架构   本文基于Freescale IMX平台Codec ALC5625为例。 0. 专用术语  ASLA - Advanced Sound Linux Architecture  OSS - 以前的Linux音频体系结构,被ASLA取代并兼容  Codec - Coder/Decoder ...
  • Android TV蓝牙模块

    千次阅读 2017-08-07 13:52:28
    一、蓝牙总体架构    二、代码分布 1.应用 /packages/apps/Settings(原生的设置应用) /packages/apps/Bluetooth(原生的蓝牙应用) 2.蓝牙java框架 /frameworks/base/core/java/android/...
  • android 蓝牙 bluetooth.apk

    千次阅读 2020-07-21 23:06:22
    首先我们先熟悉一下蓝牙协议的基本规范 1)A2DP(Advanced Audio Distribution Profile) 高级音频、立体声规范,包括A2DP Sink和A2DP Source; 2)AVRCP(Audio/Video Remote Control Profile) 音视频远程控制...
  • Android 操作系统架构

    千次阅读 2020-04-12 06:12:38
    版权声明: 本站所有博文内容均为原创,转载请务必注明作者与原文链接,且不得篡改原文内容。 为便于日常查阅本博客,可通过 Gityuan博客导航 方便检索文章 ...我的公号《Android 技术架构演...
  • imx6ul之8723BU蓝牙驱动移植

    千次阅读 2019-05-22 17:05:32
    主控芯片/架构 imx6ul/cortex-a7 平台信息+平台厂家信息 nxp WIFI模块/芯片型号 RTL8723BU 作业系统(linux/android/...) linux Kernel Version 4.1.15 关于RTL8723bu wifi+BT的驱动移植请参考 ...
  • 文章目录前言一、Android系统架构体系总述二、使用步骤1.引入库2.读入数据总结 前言 了解和学习Android的五层架构体系,加深自己对Android理解,为更好地工作做准备。 一、Android系统架构体系总述 了解一下Android...
  • 本文以Android 7.1为基础 最近在做蓝牙开发,研究了一下蓝牙的启动流程,总结一下 ...对我们工程师来说这也是个不错的福利,清晰、简洁的架构使我们在debug过程中思路更清晰; 蓝牙的总体流程图 从流程图我们可
  • android 设备驱动

    千次阅读 2019-06-25 16:59:39
    说到 android 驱动是离不开 Linux 驱动的。Android 内核采用的是 Linux2.6 内核 (最近Linux 3.3 已经包含了一些 Android 代码)。但 Android 并没有完全照搬 Linux 系统内核,除了对Linux 进行部分修正,还增加了...
  • Android体系架构

    万次阅读 多人点赞 2018-08-29 08:52:54
    Android 是一种基于 ...与其他操作系统一样,Android也采用分层的架构设计,从高到低分别是系统应用层(System Apps),Java API 框架层(Java API Framework),Android系统运行层(包括Android Runtime和原生态...
  • 上文介绍了蓝牙基本原理,但实现部分介绍不多,本文以 Android 中的蓝牙协议栈为例学习在实际系统中蓝牙的工程实现。
  • 蓝牙本质是一种短程无线电通讯技术;目的是代替便携式设备和固定电子设备之间的缆线。蓝牙的关键特点是稳定性、低功耗、低成本。 蓝牙技术包括两种:BR和LE,即经典蓝牙和低功耗蓝牙。两种技术都包括设备发现、建立...
  • 关键词:蓝牙blueZ UART HCI_UART H4 HCI L2CAP RFCOMM 版本号:基于android4.2先前版本 bluez内核:linux/linux3.08系统:android/android4.1.3.4作者:xubin341719(欢迎转载,请注明作者。请尊重版权谢谢)...
  • Android系统蓝牙HAL分析

    千次阅读 2017-03-19 14:35:46
    关键词:android, 蓝牙,中间件,驱动 应用层API Android中为蓝牙的使用提供了一整套API:  搜索其他蓝牙设备。查询适配设备。建立RFCOMM通道。 通过发现服务连接其他蓝牙设备。 交换数据。管理多...
  • Android 系统架构

    万次阅读 多人点赞 2020-04-09 00:33:11
    虽然 Android 系统非常庞大且错综复杂,需要具备全面的技术栈,但整体架构设计清晰。Android 底层内核空间以 Linux Kernel 作为基石,上层用户空间由 Native系统库、虚拟机运行环境、框架层组成,通过系统调用...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,615
精华内容 1,846
关键字:

安卓蓝牙驱动架构