Bluetooth协议栈学习之SDP(2009-06-08 15:46:15)

本文详细介绍了Linux下Bluetooth的基本概念、协议栈实现、HCI层协议、L2CAP编程方法及设备状态设置等内容,旨在为Linux下Bluetooth编程提供全面的指导。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Sam一年前在Linux下写了一个类似Windows下BTW的库--BTX。现在需要添加新功能时发现很多知识点都忘记了。所以决定在这次学习中,把一些bluez API记录下来。这几天又想,这样还不够,不如把Linux下的Bluetooth编程基础给记录下来吧。

 

 

前言:随着嵌入式系统的飞速发展,很多嵌入式平台上需要Bluetooth设备和应用。但在Linux下如何对Bluetooth编程,一直没有一份很好的中文文档。Sam结合自己的工作,一步一步将一些有用的东西记录下来,希望对其它Linux下Bluetooth编程的朋友有点帮助。

 

一:Bluetooth基本概念:

 

Bluetooth是爱立信、诺基亚、东芝、IBM和Intel 5家公司在1998年联合推出的一项无线网络技术。其宗旨是提供一种短距离、低成本的无线传输应用技术。在行业协会筹备阶段,需要一个极具有表现力的名字来命名这项高新技术。行业组织人员,在经过一夜关于欧洲历史和未来无限技术发展的讨论后,有些人认为用Blatand国王的名字命名再合适不过了。Blatand国王将现在的挪威,瑞典和丹麦统一起来;就如同这项即将面世的技术,将标准不一的短距离无线传输技术统一起来。

Intel负责半导体芯片和传输软件的开发,爱立信负责无线射频和移动电话软件的开发,IBM和东芝负责笔记本电脑接口规格的开发。

 

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

 

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

 

关于bluetooth协议栈,接下来再谈。

 

 

当前已经实现的Bluetooth栈有以下各种:

1. Widcomm:  第一个windows上的协议栈,由Widcomm公司开发,也就是现在的Broadcom.

 
2. Microsoft Windows stack: Windows XP SP2中包括了这个内建的协议栈,开发者也可以调用其API开发第三方软件。

 
3. Toshiba stack: 它也是基于Windows的,不支持第三方开发,但它把协议栈授权给一些laptop商(sony, asus等,我的本本上就是Toshiba的)。它支持的Profile有: SPP, DUN, FAX, LAP, OPP, FTP, HID, HCRP, PAN, BIP, HSP, HFP , A2DP, AVRCP, GAVDP)

 
4. BlueSoleil: 著名的IVT公司的产品.该产品可以用于桌面和嵌入式,他也支持第三方开发,DUN, FAX, HFP, HSP, LAP, OBEX, OPP, PAN SPP, AV, BIP, FTP, GAP, HID, SDAP, and SYNC。

5. Bluez: Linux官方协议栈,该协议栈的上层用Socket封装,便于开发者使用,通过DBUS与其它应用程序通信。

6. Affix: NOKIA公司的协议栈,在Symbian系统上运行.

 

7. BlueDragon:东软公司产品,好像2002年6月就通过了蓝牙的认证,支持的Profile:SDP、Serial-DevB、AVCTP、AVRCP-Controller、AVRCP-Target、Headset-AG、Headset-HS、OPP-Client、OPP-Server、CT-GW、CT-Term、Intercom、FT-Server、FT-Client、GAP、SDAP、Serial-DevA、AVDTP、GAVDP、A2DP-Source、A2DP-Sink.

8. BlueMagic:美国Open Interface 公司for portable embedded divce的协议栈,iphone(apple),nav-u(sony)等很多电子产品都用该商业的协议栈,BlueMagic 3.0是第一个通过bluetooth 协议栈1.1认证的协议栈,那么我现在就在用它,那么该栈用起来简单,API清晰明了。实现了的profile有:HCI,L2CAP,RFCOMM,A/V,Remote,Control,A/V,Streaming,BIP,BPP,DUN,FAX,FTP,GAP,Hands-Free,and,Headset,HCRP,HID,OBEX,OPP,PAN,BNEP,PBAP,SAP,SPP,Synchronization,SyncML,Telephony,XML.
9. BCHS-Bluecore Host Software: 蓝牙芯片CSR的协议栈,同时他也提供了一些上层应用的Profile的库,当然了它也是为嵌入式产品了,支持的Profile有:A2DP,AVRCP,PBAP,BIP,BPP,CTP,DUN,FAX,FM API,FTP GAP,GAVDP,GOEP,HCRP,Headset,HF1.5,HID,ICP,JSR82,LAP Message Access Profile,OPP,PAN,SAP,SDAP,SPP,SYNC,SYNC ML。

10. Windows CE:微软给Windows CE开发的协议栈,但是windows ce本身也支持其它的协议栈

11. BlueLet:IVT公司for embedded product的清量级协议栈。

 

 

Linux下Bluetooth协议栈的实现. Linux下开放的蓝牙协议栈主要包括IBM公司的BlueDrekar,Nokia公司的Affix, Axis公司的OpenBT和官方协议栈BlueZ。我们主要面对Bluez来探讨。

 

BlueZ基础代码均是由Maxim Krasnyansky完成的。包括:HCI,L2CAP,RFCOMM和基本socket的实现。他就职于Qualcomm(高通)。Marcel Holtmann开发层的协议和应用,包括:BNEP, CMTP等。当然,这些中也有Maxim Krasnyansky的参预。有部分代码由Nokia提供的。

Bluez是如何实现Bluetooth协议栈的呢? 它分2部分实现:

 

1.Kernel层实现:

正如上一篇所谈到的,bluetooth协议栈有多层结构,最底层的硬件协议在硬件中就已经实现了。(例如broadcom的芯片中,底层硬件协议已经包含于芯片之中了)。软件级别的协议实现,从HCI这一层起就可以了。 BlueZ对各层协议的实现是依托于Socket的。BlueZ首先创建了一个新的Socket中的协议--PF_BLUETOOTH (AF_BLUETOOTH=31). (也就是说,Socket()的第一个参数:domain必须是:PF_BLUETOOTH )。这也意味着,地址类型需要使用Bluetooth所定义的。

 

其实很简单,就是在net_families(网络协议列表)中添加了PF_BLUETOOTH这一项。如果对Linux Kernel有了解的话,就知道这个注册动作一定在Bluetooth init部分作的。

同样,各个协议层(如:HCI,L2CAP,HID等)都将自己的行为规范添加到PF_BLUETOOTH协议中。

 

2.应用程序层实现:

虽然Kernel层已经将Bluetooth协议栈完全实现了,但如果要使用起来,还是非常不方便的。毕竟应用程序与kernel最方便的交流通道就是ioctl().这非常不直观。于是,BlueZ又提供了一套API,这个API帮助开发者方便的与Kernel层协议打交道。当然,这些API底层的实现其就是是ioctl.

 

BlueZ的时间,基本就是这样了。下面咱们具体研究如何使用BlueZ所提供的这套API。

 

 

 

1. HCI层协议概述:

HCI提供一套统一的方法来访问Bluetooth底层。如图所示:

 

实战Linux <wbr>Bluetooth编程(三) <wbr>HCI层编程

 

从图上可以看出,Host Controller Interface(HCI)  就是用来沟通Host和Module。Host通常就是PC, Module则是以各种物理连接形式(USB,serial,pc-card等)连接到PC上的bluetooth Dongle。

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

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

 

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

 

1.1. HCI Command:

HCI Command是Host向Modules发送命令的一种方式。HCI Command Packet结构如下:

实战Linux <wbr>Bluetooth编程(三) <wbr>HCI层编程

OpCode用来唯一标识HCI Command.它由2部分组成,10bit的Opcode Command. 6bit的Opcode Group。

 

1.1.1: OpCode Group:

Linux Kernel(BlueZ)中,~/include/net/bluetooth/hci.h中定义了OpCode Group。

#define OGF_LINK_CTL 0x01

#define OGF_LINK_POLICY 0x02  

#define OGF_HOST_CTL 0x03

#define OGF_INFO_PARAM 0x04

#define OGF_STATUS_PARAM 0x05

它们代表了不同的Command Group:

 

OGF_LINK_CTL: Link control,这个Command Group中的Command允许Host控制与其它bluetooth device 的连接。

OGF_LINK_POLICY :Link Policy。这个Command Group中的Command允许调整Link Manager control.

OGF_HOST_CTL: Control and Baseband.

 

1.1.2: Opcode Command: 

用来在同一个Group内唯一识别Command。~/include/net/bluetooth/hci.h中定义。

 

 

1.2: HCI Event:

Modules向Host发送一些信息,使用HCI Event。Event Packet结构如下:

实战Linux <wbr>Bluetooth编程(三) <wbr>HCI层编程

HCI Event分3种:Command complete Event, Command States Event,Command Subsequently Completend.

Command complete Event: 如果Host发送的Command可以立刻有结果,则会发送此类Event。也就是说,如果发送的Command只与本地Modules有关,不与remote设备打交道,则使用Command complete Event。例如:HCI_Read_Buffer_Size.

Command States Event:如果Host发送的Command不能立刻得知结果,则发送此类Event。Host发送的Command执行要与Remote设备打交道,则必然无法立刻得知结果,所以会发送Command States Event.例如:

HCI Connect。

Command Subsequently Completend:Command延后完成Event。例如:连接已建立。

 

下图是一个Command-Event例子:

实战Linux <wbr>Bluetooth编程(三) <wbr>HCI层编程

从这里可以看出,如果Host发送的Command是与Remote device有关的,则会先发送Command States Event 。等动作真正完成了,再发送 Command Subsequently Completend。

 

HCI ACL与SCO数据,这里就不多讲了。只需要明白,l2cap数据是通过ACL数据传输给remote device的。

下图很明白的展示了l2cap数据如何一步一步转化为USB数据并传递给底层协议的。

实战Linux <wbr>Bluetooth编程(三) <wbr>HCI层编程

很明显,一个l2cap包会按照规则先切割为多个HCI数据包。HCI数据包再通过HCI-usb这一层传递给USB设备。每个包又通过USB driver发送到底层。

 

 

2. HCI protocol的实现:

(稍后添加)

 

 

3. HCI 层的编程:

正如上一节所说,HCI是沟通上层协议以及程序与底层硬件协议的通道。所以,通过HCI发送的Command都是上层协议或者应用程序发送给Bluetooth Dongle的。它命令Bluetooth Dongle(或其中的硬件协议)去做什么何种动作。

 

3.0:得到Host上插入Dongle数目以及Dongle信息:

我们先复习一下socket的概念:

使用函数socket()建立一个Socket,就如同你有一部电话.bind()则是把这个电话和某个电话号码(网络地址)对应起来。

类似的,我们可以把Host理解为一个房间,这个房间有多部电话(Dongle)。

当使用socket() 打开一个HCI protocol的socket,表明得到这个房间的句柄。HOST可能会有多个Dongle。换句话说,这个房间可以有多个电话号码。所以HCI会提供一套指令去得到这些Dongle。

 

 

// 0. 分配一个空间给 hci_dev_list_req。这里面将放所有Dongle信息。

struct hci_dev_list_req *dl;
struct hci_dev_req *dr;

struct hci_dev_info di;

int i;

 

 if (!(dl = malloc(HCI_MAX_DEV * sizeof(struct hci_dev_req) + sizeof(uint16_t)))) {
  perror("Can't allocate memory");
  exit(1);
 }
 dl->dev_num = HCI_MAX_DEV;
 dr = dl->dev_req;

 

 

//1. 打开一个HCI socket.此socket相当于一个房间。

if ((ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)) < 0) {
  perror("Can't open HCI socket.");
  exit(1);
 }

 

// 2. 使用HCIGETDEVLIST,得到所有dongle的Device ID。存放在dl中。  

 if (ioctl(ctl, HCIGETDEVLIST, (void *) dl) < 0) {
  perror("Can't get device list");
  exit(1);
 }

 

// 3 使用HCIGETDEVINFO,得到对应Device ID的Dongle信息。

di.dev_id = (dr+i)->dev_id;
ioctl(ctl, HCIGETDEVINFO, (void *) &di);

 

这样就能得到所有Dongle信息。

struct hci_dev_info {
 uint16_t dev_id;   //dongle Device ID
 char     name[8];  //Dongle name

 bdaddr_t bdaddr;   //Dongle bdaddr

 uint32_t flags;    //Dongle Flags:如:UP,RUNING,Down等。
 uint8_t  type;   //Dongle连接方式:如USB,PC Card,UART,RS232等。

 uint8_t  features[8];

 uint32_t pkt_type;
 uint32_t link_policy;
 uint32_t link_mode;

 uint16_t acl_mtu;
 uint16_t acl_pkts;
 uint16_t sco_mtu;
 uint16_t sco_pkts;

 struct   hci_dev_stats stat;  //此Dongle的数据信息,如发送多少个ACL Packet,正确多少,错误多少,等等。
};

 

3.0.1: UP和Down Bluetooth Dongle:

ioctl(ctl, HCIDEVUP, hdev)

ioctl(ctl, HCIDEVDOWN, hdev)

ctl:为使用socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)打开的Socket.

hdev: Dongle Device ID.(所以上面的Socket不需要bind,因为这边指定了)

 

 

 

 

 

3.1 BlueZ提供的HCI编程接口一(针对本地Dongle的API系列):

3.1。1 打开一个HCI Socket---int hci_open_dev(int dev_id):

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

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

 

3.1.2: 关闭一个HCI Socket:

int hci_close_dev(int dd) //简单的关闭使用hci_open_dev打开的Socket。

 

3.1.3: 向HCI Socket(对应一个Dongle)发送 request:

int hci_send_req(int dd, struct hci_request *r, int to)

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

参数1:HCI Socket。

参数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不用多说,对应前面的图就明白这是Group Code和Command Code。这两项先确定下来,然后可以查HCI Spec。察看输入参数(cparam)以及输出参数(rparam)含义。至于他们的结构以及参数长度,则在~/include/net/bluetooth/hci.h中有定义。

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

 

例1:得到某个连接的Policy Setting.

HCI Spec以及~/include/net/bluetooth/hci.h中均可看到,OGF=OGF_LINK_POLICY(0x02). OCF=OCF_READ_LINK_POLICY(0x0C).

因为这个Command用来读取某个ACL连接的Policy Setting。所以输入参数即为此连接Handle.

返回参数则包含3部分,status(Command是否顺利执行), handle(连接Handle)。 policy(得到的policy值)

这就又引入了一个新问题,如何得到某个ACL连接的Handle。

可以使用ioctl HCIGETCONNINFO得到ACL 连接Handle。

ioctl(dd, HCIGETCONNINFO, (unsigned long) cr);

Connect_handle = htobs(cr->conn_info->handle);

 

所以完整的过程如下:

 

struct hci_request HCI_Request;
 read_link_policy_cp Command_Param;
 read_link_policy_rp Response_Param;

// 1.得到ACL Connect Handle

if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) 
 {  
  return -1;
 }
 Connect_handle = htobs(cr->conn_info->handle);

 

memset(&HCI_Request, 0, sizeof(HCI_Request));
 memset(&Command_Param, 0 , sizeof(Command_Param));
 memset(&Response_Param, 0 , sizeof(Response_Param));

 

// 2.填写Command输入参数
 Command_Param.handle = Connect_handle;

 

 

HCI_Request.ogf = OGF_LINK_POLICY;  //Command组ID
 HCI_Request.ocf = OCF_READ_LINK_POLICY; //Command ID
 HCI_Request.cparam = &Command_Param;
 HCI_Request.clen = READ_LINK_POLICY_CP_SIZE;
 HCI_Request.rparam = &Response_Param;
 HCI_Request.rlen = READ_LINK_POLICY_RP_SIZE;

 

if (hci_send_req(dd, &HCI_Request, to) < 0)
 {
  perror("/nhci_send_req()");
  return -1;
 }

//如果返回值状态不对

 if (Response_Param.status) {
  return -1;
 }
 

//得到当前policy
 *policy = Response_Param.policy;

 

 

3.1.4:几个更基础的function:

static inline void bacpy(bdaddr_t *dst, const bdaddr_t *src) //bdaddr copy

static inline int bacmp(const bdaddr_t *ba1, const bdaddr_t *ba2)//bdaddr 比较

 

 

3.1.5: 得到指定Dongle BDAddr:

int hci_read_bd_addr(int dd, bdaddr_t *bdaddr, int to);

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

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

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

 

 

 

3.1.6: 读写Dongle Name:

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

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

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

参数2:读取或设置Name。

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

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

 

3.1.7:得到HCI Version:

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

 

 

3.1.8:得到已经UP的Dongle BDaddr:

int hci_devba(int dev_id, bdaddr_t *bdaddr);

dev_id: Dongle Device ID.

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

 

3.1.9: 得到Dongle Info:

int hci_devinfo(int dev_id, struct hci_dev_info *di)

dev_id: Dongle Device ID.

di: 此Dongle信息。

出错返回 -1。

注意,这个Function的做法与3.0的方法完全一致。

 

3.1.10:从hciX中得到X:

int hci_devid(const char *str)

str: 类似 hci0这样的字串。

如果hciX对应的Device ID(X)是现实存在且UP。则返回此设备Device ID。 

 

3.1.11:得到BDADDR不等于参数bdaddr的Dongle Device ID:

int hci_get_route(bdaddr_t *bdaddr)

查找Dongle,发现Dongle Bdaddr不等于参数bdaddr的第一个Dongle,则返回此Dongle Device ID。

所以,如果: int hci_get_route(NULL),则得到第一个可用的Dongle Device ID。

 

 

3.1.12: 将BDADDR转换为字符串:

int ba2str(const bdaddr_t *ba, char *str)

 

3.1.13: 将自串转换为BDADDR:

int str2ba(const char *str, bdaddr_t *ba)

 

 

3.2 BlueZ提供的HCI编程接口二(针对Remote Device的API系列):

3.2.1  inquiry 远程Bluetooth Device:

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

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

参数1:dev_id:指定Dongle Device ID。如果此值小于0,则会使用第一个可用的Dongle。

参数2:len: 此次inquiry的时间长度(每增加1,则增加1.25秒时间)

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

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

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

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

 

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

 

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

 

 

3.2.2:得到指定BDAddr的reomte device Name:

int hci_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:等待时间。

 

3.2.3: 读取连接的信号强度:

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

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

 

 

 

一:L2CAP协议简介:

Logical Link Control and Adaptation Protocol(L2CAP)

 

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

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

 

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

实战Linux <wbr>Bluetooth编程(四) <wbr>L2CAP层编程

也就是说,所有L2CAP数据均通过HCI传输到Remote Device。且上层协议的数据,大都也通过L2CAP来传送。

 

 

L2CAP可以发送Command。例如连接,断连等等。

实战Linux <wbr>Bluetooth编程(四) <wbr>L2CAP层编程

 

下面看Command例子:Connection Request:

实战Linux <wbr>Bluetooth编程(四) <wbr>L2CAP层编程

 

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

 

 

ProtocolPSMReference
SDP0x0001See Bluetooth Service Discovery Protocol (SDP), Bluetooth SIG.
RFCOMM0x0003See RFCOMM with TS 07.10, Bluetooth SIG.
TCS-BIN0x0005See Bluetooth Telephony Control Specification / TCS Binary, Bluetooth SIG.
TCS-BIN-CORDLESS0x0007See Bluetooth Telephony Control Specification / TCS Binary, Bluetooth SIG.
BNEP0x000FSee Bluetooth Network Encapsulation Protocal, Bluetooth SIG.
HID_Control0x0011See Human Interface Device , Bluetooth SIG.
HID_Interrupt0x0013See Human Interface Device, Bluetooth SIG.
UPnP0x0015See [ESDP] , Bluetooth SIG.
AVCTP0x0017See Audio/Video Control Transport Protocol , Bluetooth SIG.
AVDTP0x0019See Audio/Video Distribution Transport Protocol , Bluetooth SIG.
AVCTP_Browsing0x001BSee Audio/Video Remote Control Profile, Bluetooth SIG
UDI_C-Plane0x001DSee the Unrestricted Digital Information Profile [UDI], Bluetooth SIG

 

 

 

 

二:L2CAP编程方法:

 

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

 

1.创建L2CAP Socket:

socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP);

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

 

2.绑定:

// Bind to local address
 memset(&addr, 0, sizeof(addr));
 addr.l2_family = AF_BLUETOOTH;
 bacpy(&addr.l2_bdaddr, &bdaddr); //bdaddr为本地Dongle BDAddr

 if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
  perror("Can't bind socket");
  goto error;
 }

 

3.连接

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't connect");
  goto error;
 }

 

注意:

struct sockaddr_l2 {
 sa_family_t l2_family;  //必须为 AF_BLUETOOTH
 unsigned short l2_psm;  //与前面PSM对应,这一项很重要
 bdaddr_t l2_bdaddr;     //Remote Device BDADDR
 unsigned short l2_cid; 
};

 

4. 发送数据到Remote Device:

send()或write()都可以。

 

5. 接收数据:

revc() 或read()

 

 

以下为实例:

注:在Bluetooth下,主动去连接的一端作为主机端。被动等别人连接的作为Client端。

 

 

 

 

 

 

背景知识1:Bluetooth设备的状态

之前HCI编程时,是用 ioctl(HCIGETDEVINFO)得到某个Device Info(hci_dev_info).其中flags当时解释的很简单。其实它存放着Bluetooth Device(例如:USB Bluetooth Dongle)的当前状态:

其中,UP,Down状态表示此Device是否启动起来。可以使用ioctl(HCIDEVUP)等修改这些状态。

另外:就是Inquiry Scan, PAGE Scan这些状态:

Sam在刚开始自己做L2CAP层连接时,使用另一台Linux机器插USB Bluetooth Dongle作Remote Device。怎么也没法使用inquiry扫描到remote设备,也没法连接remote设备,甚至无法使用l2ping ping到remote设备。觉得非常奇怪,后来才发现Remote Device状态设置有问题。没有设置PSCAN和ISCAN。

Inquiry Scan状态表示设备可被inquiry. Page Scan状态表示设备可被连接。

#hciconfig hci0 iscan

#hciconfig hci0 pscan

或者:#hciconfig hci0 piscan

就可以设置为PSCAN或者iSCAN状态了。

编程则可以使用ioctl(HCISETSCAN) . dev_opt = SCAN_INQUIRY;dr.dev_opt = SCAN_PAGE;dr.dev_opt = SCAN_PAGE | SCAN_INQUIRY;

则可以inquiry或者connect了。

 

 

摘 要:基于对Linux 下蓝牙协议栈BlueZ 源代码的分析,给出BlueZ的组织结构和特点。分析蓝牙USB 传输驱动机制和数据处理过程, 给出实现蓝牙设备驱动的重要数据结构和流程,并总结Linux 下开发蓝牙USB 设备驱动的一般方法和关键技术。 关键词:Linux 系统;蓝牙协议栈;设备驱动 USB Device Driver for Linux Bluetooth Stack LIANG Jun-xue, YU Bin (Institute of Electronic Technology, PLA Information Engineering University, Zhengzhou 450004) 【Abstract】This paper depicts the structure and characteristics of BlueZ based on analyzing the source code of Linux bluetooth stack BlueZ. It analyzes the implementation of bluetooth USB transport driver scheme and data processing procedure in detail, and gives the key data structure and implementation of bluetooth device driver. It summarizes the approach of developing Linux bluetooth USB device driver and the key technology. 【Key words】Linux system; bluetooth stack; device driver 计 算 机 工 程 Computer Engineering 第 34 卷 第 9 期 Vol.34 No.9 2008 年 5 月 May 2008 ·开发研究与设计技术· 文章编号:1000—3428(2008)09—0273—03 文献标识码:A 中图分类号:TP391 1 概述 蓝牙技术是开放式通信规范,而 Linux 是开放源码的操 作系统。廉价设备与免费软件的结合,促进了蓝牙技术和 Linux 的发展与融合。 Linux最早的蓝牙协议栈是由Axis Communication Inc在 1999 年发布的 OpenBT 协议栈。 随后, IBM 发布了 BlueDrekar 协议栈,但没有公开其源码。Qualcomm Incorporated 在 2001 年发布的 BlueZ 协议栈被接纳为 2.4.6 内核的一部分。此外, Rappore Technology 及 Nokia 的 Affix Bluetooth Stack 都是 Linux 系统下的蓝牙协议栈,应用在不同的设备和领域中。 BlueZ 是 Linux 的官方蓝牙协议栈,也是目前应用最广 泛的协议栈,几乎支持所有已通过认证的蓝牙设备。对于基 于主机的蓝牙应用,目前常见的硬件接口有 UART, USB 和 PC 卡等,USB 作为 PC 的标准外设接口,具有连接方便、兼 容性好和支持高速设备等特点,已广泛应用于蓝牙设备。 目前对 Linux 下 USB 设备驱动的研究已较为广泛而深 入[1-4] ,但对 Linux 下的蓝牙设备驱动还没有专门的研究。本 文在分析 USB 设备驱动和蓝牙协议栈的基础上,总结了 Linux 下开发蓝牙 USB 驱动程序的一般方法,并深入剖析了 其关键技术。 2 Linux 蓝牙协议栈 BlueZ 简介 BlueZ 目前已成为一个开放性的源码工程。它可以很好 地在 Linux 支持的各种体系的硬件平台下运行,包括各种单 处理器平台、多处理器平台及超线程系统。 BlueZ 由多个独立的模块组成,内核空间主要包括设备 驱动层、蓝牙核心及 HCI 层、L2CAP 与 SCO 音频层、 RFCOMM, BNEP, CMTP 与 HIDP 层、通用蓝牙 SDP 库和后 台服务及面向所有层的标准套接字接口;在用户空间提供了 蓝牙配置、测试及协议分析等工具。其组织结构如图 1 所示, BlueZ 没有实现专门的 SDP 层,而是将其实现为运行在后台 的蓝牙服务库例程(图 1 没有描述该后台服务)。 RFOMM 层支 持标准的套接口,并提供了串行仿真 TTY 接口,这使串行端 口应用程序和协议可以不加更改地运行在蓝牙设备上,例如 通过点对点协议 PPP 可实现基于 TCP/IP 协议簇的所有网络 应用。BNEP 层实现了蓝牙的以太网仿真,TCP/IP 可以直接 运行于其上。 USB设备驱动 (hci_usb.o) L2CAP层(l2cap.o) RFCOMM层 (rfcomm.o) BNEP层 (bnep.o) CMTP层 (cmtp.o) 串口设备驱动 (hci_uart.o) 虚拟串口设备驱动 (hci_vhci.o) 音频 socket RFCOMM socket BNEP socket CMTP socket L2CAP socket HCI socket 内核 空间 用户 空间 串口设备 CAPI设备 输入设备 网络设备 HDIP socket 音频设备 AF_BLUETOOTH socket 音频层(sco.o) PPP TCP/IP AF_INET socket BNEP层 (bnep.o) 其他设备驱动 (bluecard_cs.o等) BlueZ工具和实用程序 HDIP层 (hdip.o) BlueZ核心 及HCI层(bluez.o/bluetooth.o) 图 1 BlueZ 组织结构 3 蓝牙 USB 设备驱动 设备驱动程序在 Linux 内核中起着重要作用,它使某个 硬件能响应一个定义良好的内部编程接口。这些接口隐藏了 设备的工作细节,用户通过一组独立于特定驱动程序的标准 调用来操作设备。而将这些调用映射到作用于实际硬件设备 的特有操作上,则是驱动程序的任务。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值