精华内容
下载资源
问答
  • 三、Bluetooth 技术的工作原理........................................................................................................... 4 1. 操作概览......................................................
  • 蓝牙可以分为经典蓝牙和低功耗蓝牙,本文重点介绍低功耗蓝牙(BLE). 一、BLE协议栈结构 以TI的CC26XX芯片为例,BLE协议栈可以由如下图所示部分组成: 1、物理层: 物理层是BLE协议栈最底层,规定了BLE通信的...

    蓝牙可以分为经典蓝牙和低功耗蓝牙,本文重点介绍低功耗蓝牙(BLE).

    一、BLE协议栈结构

    以TI的CC26XX芯片为例,BLE协议栈可以由如下图所示部分组成:

    1、物理层:

            物理层是BLE协议栈最底层,规定了BLE通信的基础射频参数,包括信号频率、调制方案等。BLE4的物理层是1Mbps的GFSK调制。在BLE 5的物理层除了兼容原来1Mbps 的GFSK外,还增加了2Mbps GFSK调制,这样做的好处是单位时间内数据传输量增加一倍或者传输相同数据量仅用一半的时间,就可以有效降低功耗;

    2、LL层:

            用于控制设备的射频状态,可工作于advertising、sacnning、Initiating、connecting四中状态。advertising和sacnning状态是配对出现的,一个设备处于advertising状态,像外部设备发送广播包,处于scanning状态的设备可以接受广播包。如果处于sacnning状态的设备通过发送连接请求来回应advertising设备,如果广播设备接受连接请求,那么广播设备与发起连接的设备将会进入连接状态。

    3、HCI层:

             为主机和控制器之间提供标准通信接口。这一层可以是软件或者硬件接口,如UART、SPI、USB等。  

    4、L2CAP层

            L2CAP对LL进行了一次简单封装。

    5、ATT/GATT

            GATT负责主从设备之间的应用数据交换。GATT作为使用的ATT的子流程的一个服务型框架。为主从设备交互数据提供Profile、Service、Characteristic等概念的抽象、管理。当两个设备建立连接后,就处于GATT服务器或者GATT客户端的角色。

    6、GAP

            GAP是对LL层的一部分进行封装,主要用来广播,扫描和发起连接等。

    7、SM

            定义了配对和秘钥分配方式,并为协议栈其他层与另一个设备之间的安全连接和数据交换提供服务。

    GAP层的角色有广播者,观察者,外设和集中器四种。处于advertising状态的设备称为广播者,等待scanning设备称为观察者,当scanning设备发送连接请求,连接建立后,advertising状态的设备作为从机,scanning设备作为主机。

    GATT层的角色有服务器和客户端。为GATT客户端提供数据服务的设备称为服务器,从GATT服务器读写应用数据的设备称为客户端。

    二、BLE5的新特性

    1、2X的数据传输速度:蓝牙5的数量提升了一倍是因为PHY层支持2M数据传输率。

    2、4X的传输距离:得益于信道传输中编码方式的改变,详见下面链接:

    https://blog.csdn.net/weixin_40204595/article/details/83069007

    3、8X的大广播数据包:相比BLE4.x及以前协议只支持37、38、39、信道广播最多31字节数据,BLE5扩展了除37、38、39外的其他信道作为第二广播信道,最多可传输255字节数据。这解决了beacon应用中广播数据太少的问题。

    三、蓝牙工作状态:

    1、广播状态:针对BLE5.0协议栈,广播设备利用37、38、39主信道和其他的信道作为第二信道向外部发送广播数据,此时并没有指定接收者,所有的处于scanning状态的设备都可以接收到该广播数据。广播状态的数据包pdu结构如下图所示:

    由16bit Header+payload组成。首先4bits PDU Type决定了广播状态,可以分为如下表格种类:

    2、连接状态:

    当两个设备建立连接后,GATT开始发挥作用,一个设备作为服务器,另一设备作为客户端。

    五、BLE协议栈应用开发

            利用协议栈进行蓝牙通信,当连接建立后,主要使用的就是GATT。一个GATT服务器中可包含一个或多个GATT服务,GATT服务是完成特定功能的一系列数据的集合。

            “特性”(Characteristic)是服务用到的值,以及其内容和配置信息。GATT定义了在BLE连接中发现、读取和写入属性的子过程。GATT服务器上的特性值及其内容和配置信息(称为描述符)存储于属性表中。属性表是一个数据库,包含了成为属性的小块数据,除了值本身,每个属性都包含句柄,类型和权限三个部分,句柄就是属性在表中的地址,每个属性有唯一的句柄;类型通常是UUID;权限代表GATT客户端对属性的访问权限;

    GATT定义了若干在GATT服务器和客户端之间的通信的子过程:

    (1)读特性值:客户端设备请求读取句柄处的特性值,服务器将此值回应给客户端(假定属性有读权限)。

    (2)使用特性的UUID读:客户端请求读基于一个特定类型的所有特性值,服务器将所有与指定类型匹配的特性的句柄和值回应给客户端设备(假设属性有读权限)。

    (3)读多个特性值:客户端一次请求中读取几个句柄的特性值,服务器将这些特性值回应给客户端(假设属性有读权限),客户端需要知道如何解析这些不同的特性值数据。

    (4)读特性描述符:客户端请求读特定句柄处的特性描述符,服务器将特性描述符的值回应给客户端设备(假设属性有读权限)。

    (5)使用UUID发现特性:客户端通过发送“特性”的类型(UUID)来请求发现这个“特性”的句柄。服务器将这个”特性”的声明回应给客户端设备,其中包括特性值的句柄以及“特性”的权限。

    (6)写特性值:客户端设备请求向服务器特定的句柄处写入特性值,服务器将数据是否写入成功的信息反馈给客户端(假设特性有写权限,另外有一种特殊的写类型是不需要服务器来反馈是否写入成功的信息的,使用的时候根据具体应用来具体分析使用)。

    (7)写特性描述符:客户端设备请求向服务器特定的句柄处写入特性描述符,服务器将特性描述符是否写入成功的信息反馈给客户端(假设特性描述有写权限)。

    (8)特性值通知:服务器将一个特性值通知给客户端,客户端设备不需要向服务器请求这个数据,客户端收到这个数据时,不需要属性协议层确认特性值是否被成功接收。

    (9)特性值指示:服务器将一个特性值指示给客户端,客户端设备同样不需要向服务器请求这个数据,但是跟通知不一样的是,客户端收到这个数据之后,属性协议层必须确认特性值被成功接收。
    (注:该部分引用自https://blog.csdn.net/zzfenglin/article/details/51706290)

    六、重点问题关注

    1、属性表中GATT_PROP_READ和GATT_PERMIT_READ辨析

    打个比方说明,属性表是一列火车,它有SERVAPP_NUM_ATTR_SUPPORTED这么多节车厢,GATT_PERMIT_READ是每节车厢的钥匙。此时第18节~21节车厢装的是宝箱char6,GATT_PROP_READ是宝箱char6的钥匙。虽然两把都是钥匙,但是作用的对象不一样。实际上GATT_PERMIT_READ是针对属性表使用的,而GATT_PROP_READ是针对特征值使用的。

    2、什么是CCC?

    答:

    Client Characteristic Configuration,俗称CCC。

    notify属性的特征值,会多读、写属性的特征值多一个CCC。

    从机要想使用notify函数时能正常发送出数据,就必须保证CCC是被打开的。

     

    3、CCC如何打开?

    答:notify开关可由主机端或者从机端打开,但应尽量保证由主机来打开比较合适,毕竟它是“主机”,“主机“就该有主动权。

    1)主机端打开(推荐)

    先获取到CCC的特征值句柄,然后利用CCC的特征值句柄往CCC的特征值中写入0x0001。

    参考本博客博文《CC2541之主机端获取notify数据》。

     

    2)从机端打开(不推荐)

    GATTServApp_WriteCharCfg(connHandle, simpleProfileChar4Config, 0x0001);

    七、最大可发送数据包长

    详见https://blog.csdn.net/zzfenglin/article/details/51706290

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    展开全文
  • 蓝牙通信协议

    2012-09-22 16:35:52
    蓝牙协议:介绍蓝牙通信原理,已经蓝牙协议结构。
  • 本帖最后由 太空虫虫 于 2016-7-13 22:47 编辑写在最前面:本帖是介绍与EV3进行蓝牙通信的基本原理,所以可能需要一点计算机及编程语言方法的知识来帮助理解。另外,本帖不涉及如何编写手机APP等方面的知识,也没有...

    本帖最后由 太空虫虫 于 2016-7-13 22:47 编辑

    写在最前面:本帖是介绍与EV3进行蓝牙通信的基本原理,所以可能需要一点计算机及编程语言方法的知识来帮助理解。另外,本帖不涉及如何编写手机APP等方面的知识,也没有现成的软件供下载,如果是有这方面需求的小伙伴,可以去搜搜论坛的其他帖子。谢谢~-----------------------------------------------------------------------------------------------------------------------最近,想实现通过手机蓝牙控制EV3。在论坛找了一圈,发现掌握这方面知识的前辈蛮多了,但是无一例外都是在介绍自己编写的手机APP,对于如何与EV3进行蓝牙通信均没有介绍,虽然有些前辈愿意开源自己的APP源码,但如果没有一定的Java编程经验以及相应的开发软件的话,要解读起来还挺麻烦,所以,小弟通过官方的文档和反编译其他前辈的APP,了解了一点EV3进行蓝牙通信的基本原理。和大家分享一下,希望对大家有用。

    直接上图,我们先拿EV3间的通信为对象来进行研究。下图就是EV3间通信的基本框图

    d6eb565c07c6485859926bf70b4b2efe.gif

    图片1.jpg (8.96 KB, 下载次数: 3)

    2016-7-11 22:53 上传

    通过图大家可以发现一个叫mailbox的东西,MailBox是EV3自身底层操作系统虚拟出来的东西。 根据我的研究EV3上跑的用户程序要和外界通信,无论发送或接收都是通过这个MailBox来完成的。

    d6eb565c07c6485859926bf70b4b2efe.gif

    图片2.jpg (13.13 KB, 下载次数: 4)

    2016-7-11 22:52 上传

    写过EV3之间蓝牙通信的小伙伴都应该用过上面这个指令,这个命令就是对MailBox进行读或写操作的指令。

    而一个比较麻烦的问题是,在EV3的眼中,外界的其他设备也都是EV3。也就是说,当它想发送信息给目标设备时,也只会把这个数据往自己的“MailBox”里面丢,当目标设备想发送信息给EV3时,也必须让信息能进入到它的MailBox里才行。

    那怎么样实现非EV3设备与EV3设备的通信呢?

    我们先根据EV3系统的结构对第一幅图进行展开,可以得到下面这幅图。

    d6eb565c07c6485859926bf70b4b2efe.gif

    图片3.jpg (25.44 KB, 下载次数: 3)

    2016-7-11 22:52 上传

    为了更具体的说明上副图的含义,我们现在假设EV3甲的用户程序需要发送一个数据到EV3乙的用户程序中,那么在EV3甲中数据会先被丢入MailBox中,但是通过图我们可以看到,实际上MailBox是没办法直接把数据发送出去,而是把数据交给了一个叫“EV3协议处理程序”的东西。它是底层操作系统的一个后台程序,作用就是对需要发送出去的信息进行“打包“,对接收到的信息进行“拆包”。“EV3协议“是与EV3通信的关键,本帖后面会重点介绍。 那如上所说,EV3协议处理程序对数据进行打包完成后,就会通过CPU的数据接口把数据发送给EV3上的蓝牙模块(具体是什么接口我没有研究过,理论上是串口)。从这里开始,数据的发送就交由蓝牙模块负责了。

    EV3蓝牙模块内的蓝牙芯片会对要发送的数据再按照一个叫SPP的协议进行打包。SPP协议是蓝牙的子协议之一,有兴趣的小伙伴可以自己去百度一下,这里不做介绍,大家只需要知道EV3的蓝牙模块支持这个协议就好(好像也只支持这一个)。打包完成后数据终于能通过蓝牙模块的射频电路真正的被发送出去了。

    当数据到达EV3乙后,经历的就是在EV3甲中的逆过程。数据在蓝牙模块按照SPP拆包后,又在CPU里被按照“EV3协议“再拆一次包后,数据最终到达了EV3乙的MailBox里。

    讲了这么一大段EV3与EV3间的通信,那EV3如何与非EV3的设备进行通信呢?总结起来就是一句话:找一个带蓝牙且支持SPP的设备,按照EV3协议收发数据即可。而支持SPP的蓝牙设备满地都是,所以说掌握EV3协议就成为了与EV3通信的关键。

    --------------------------------------------------------------------------------------------------------------

    EV3协议,乐高官方学名为direct commands,也就是“直接命令”,在乐高的官方文档“LEGO MINDSTORMS EV3 Communication Developer Kit”中有介绍。(有兴趣的小伙伴可以去官网http://www.lego.com/zh-cn/mindstorms/downloads下来看看)。“直接命令”类似与Windows里面的命令控制台命令,是内建在EV3底层操作系统里的,所以可以直接执行这些命令而无需编写任何用户程序。“直接命令”十分强大,可以实现命令EV3执行某个程序、直接读取EV3的输入口、直接控制输出口、下载、上传和删除文件、写MailBox等等的功能, 其中写MailBox就是我们所要介绍的。下达命令的实质就是按照官方文档里规定的协议格式,把数据打包后发送给EV3,EV3拿到数据交由协议处理程序拆包后,按照数据的内容执行相应的动作。下达“直接命令”有3个渠道,WIFI、USB和蓝牙,乐高的编程软件就是通过USB口给EV3下达“直接命令”来实现程序的下载上传等功能的,我们这里只需要通过蓝牙向EV3发送写MailBox命令就可以实现我们的目标了。

    *******************************************************

    下面就是重点了,关于“写MailBox命令”协议格式。

    ****************************************************************

    官方的文档中定义的格式如下:

    d6eb565c07c6485859926bf70b4b2efe.gif

    图片4.jpg (39.59 KB, 下载次数: 5)

    2016-7-11 22:58 上传

    它由8个部分组成:

    代号     长度             含义

    bbbb:    2字节         整个包从mmmm到ppp的数据长度,(小端模式保存)

    mmmm     2字节         包计数器,目前还不知道作用,直接填0就好。

    tt       1字节         包类型,直接填0x81就好。

    ss       1字节         命令类型,0x9e代表写mailbox

    ll        1字节mailbox名称字符串的长度+ 1,比如“abc” 的长度为4.

    aa…      ll字节mailbox名称+0, 内容为字符的ASCII码+ 0x00

    LLLL     2字节         包有效数据长度,(小端模式保存)

    ppp…    LLLL字节       包有效数据,我们要丢到EV3 mailbox里的东西就放在这里

    (小端模式保存:就是在保存超过一个字节的数据时把低位数据放在低地址,高位放高地址的保存方法,相应的还有大端模式)

    额。。看完上面这堆东西大家还一头雾水,搞不懂是什么意思? 那就对了,要不然大家直接看文档就行了,我也用不着发这个帖子了。下面我尽量用简单的语音说明一下上诉内容的意思。

    首先说一下Mailbox支持放3种数据,逻辑、数值、字符串。

    d6eb565c07c6485859926bf70b4b2efe.gif

    捕获.JPG (17.78 KB, 下载次数: 3)

    2016-7-11 23:07 上传

    我们先拿逻辑类型的数据来举例。逻辑只有真和假两种状态,所以EV3协议规定用1字节来表示,真是1,假是0。 那么我们要给EV3的’abc’ mailbox发一个逻辑真的话,数据按如下步骤打包

    1. tt、ss、mmmm的内容按照开头的介绍填入规定值即可

    2. mailbox名称是用字符串表示的,字符串是由字符和结尾的0终止标识组成的,所以’abc’ 字符串的长度为 3+1 = 4,即ll为4,aaa部分的内容为0x61,0x62,0x63,0x00 (0x61为’a’的ASCII码,依次类推,字符最后加0x00为终止标识)

    3. 如上所说逻辑的数据使用一个字节表示,所以LLLL有效数据长度为1, 所以内容为0x01,0x00

    4. 因为要发送的是逻辑真,所以ppp的部分内容是0x01。

    5. 最后,通过计算,整个包的长度为 1(ppp) + 2(LLLL) + 4(aaa.) + 1(II) + 1(tt) + 1(ss) + 2(mmmm) = 12.所以bbbb填入0x0c, 0x00

    所以,整个包是这样的(16进制表示),

    0x0c, 0x00, 0x00, 0x00, 0x81, 0x9e, 0x04, 0x61, 0x62, 0x63, 0x00, 0x01, 0x00, 0x01

    bbbb          mmmm            tt    ss         ll                  aaa..                           LLLL      ppp

    数值、字符串和逻辑只是LLLL和ppp两个部分不一样,其余都相同。

    字符串的规则比较简单。比如要给“abc” mailbox发送“1234” 这个字符串的话。 LLLL内容为0x05, 0x00,ppp的内容就是0x31, 0x32, 0x33, 0x34,0x00.

    数值EV3协议是用32位浮点数表示的,所以LLLL为4,ppp采用小端模式存放32位浮点数的4个字节。 具体比如50.5这样的数字怎么转换为4个字节的数据,是有一套规则的,但是我也不清楚,也没有必要搞清楚,因为使用C语言里的union的数据类型可以很方便的得到,这个后续实际应用篇再讲。

    不好意思,由于不可描述的原因Delay一天了,现在一楼先把前天的知识补充完整

    (接上文)

    以上说的是我们自己的蓝牙设备发送数据给EV3,如果是EV3发送数据给我们的设备的话,从我们自己的蓝牙模块数据口输出的也同样是按照上诉规则打包的数据,我们只需要按照规则拆包就好。

    看完上诉的内容,可能会有小伙伴有这样的疑问,在解析包时EV3是如何区分这3种数据类型的? 如果发送的是由3个字符组成的字符串数据,那么就和发送数值类型数据的包的效数据长度一样都是4了,那EV3如何区分,答案是EV3不会区分,对于采用何种数据类型来解析包,是由用户程序决定的,比如用户程序里有如下一个命令,那么对于Mailbox名称为“abc”的包,EV3就会用数值类型来解析,哪怕发送方的本意是要发送3个字符的字符串,当然,最后解析出来的数值肯定是错误的。

    d6eb565c07c6485859926bf70b4b2efe.gif

    图片5.jpg (10.63 KB, 下载次数: 3)

    2016-7-13 21:49 上传

    另外,小弟做过如下这样的实验,本来是想让同一个mailbox名称能同时接收两种数据类型,结果,无论用何种数据类型往“abc”里发送,都收不到任何的东西。具体原因可能和EV3底层操作系统对于包解析的运作规则有关,所以请保证一个mailbox名称在程序里只接收一种数据类型。

    d6eb565c07c6485859926bf70b4b2efe.gif

    图片6.jpg (21.88 KB, 下载次数: 2)

    2016-7-13 21:49 上传

    展开全文
  • 蓝牙通信,完整的通信流程!

    万次阅读 多人点赞 2017-12-28 16:30:34
    无线通信方案,有三种方案可以...而本章着重讲的蓝牙之间通信。 首先介绍一下蓝牙的两个广播Receiver。 第一个:蓝牙状态的改变是通过广播接收到的。 // 注册蓝牙状态接收广播 IntentFilter intentFilter = new nt

    无线通信方案,有三种方案可以实施:
    1、NFC 2、蓝牙 3、WIFI
    下面是对这三个知识点做的一个总结,参照对比可以选择合适的方案。而本章着重讲的蓝牙之间通信。
    这里写图片描述

    首先介绍一下蓝牙的两个广播Receiver。
    第一个:蓝牙状态的改变是通过广播接收到的。

     // 注册蓝牙状态接收广播
      IntentFilter intentFilter = new  ntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
       registerReceiver(mReceiverBluetoothStatus,intentFilter);
    
        /**
         * 定义接收蓝牙状态广播receiver
         *
         * @param savedInstanceState
         */
        private BroadcastReceiver mReceiverBluetoothStatus = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context,Intent intent) {
                int status = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,-1);
                switch (status) {
                    case BluetoothAdapter.STATE_OFF:
                        Log.d(TAG,"蓝牙已关闭");
                        break;
                    case BluetoothAdapter.STATE_ON:
                        Log.d(TAG,"蓝牙已打开");
                        break;
                    case BluetoothAdapter.STATE_TURNING_OFF:
                        Log.d(TAG,"蓝牙关闭中...");
                        break;
                    case BluetoothAdapter.STATE_TURNING_ON:
                        Log.d(TAG,"蓝牙打开中...");
                        break;
                    default:
    
                        break;
                }
            }
        };

    第二个:蓝牙搜索到设备、绑定设备(配对)也是通过广播接收的。(搜索到设备系统会自动发一个广播)

       // 注册蓝牙device接收广播
            IntentFilter intentFilterDevice = new IntentFilter();
            // 开始查找
            intentFilterDevice.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
            // 结束查找
            intentFilterDevice.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
            // 查找设备(查找到设备)
            intentFilterDevice.addAction(BluetoothDevice.ACTION_FOUND);
            // 设备扫描模式改变 (自己状态的改变action,当设置可见或者不见时都会发送此广播)
            intentFilterDevice.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
            // 绑定状态
            intentFilterDevice.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
            registerReceiver(mReceiverDeceiver,intentFilterDevice);
      /**
         * 定义接收蓝牙device广播Receiver
         */
        private BroadcastReceiver mReceiverDeceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context,Intent intent) {
                String action = intent.getAction();
                if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
                    // 开始搜索        ——接收广播
                    Log.d(TAG,"开始搜索");
                    mList.clear();
                    mAdapter.refresh(mList);
    
                } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
                    // 查找到设备完成   —— 接收广播
                    Log.d(TAG,"查找到设备完成");
    
                } else if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                    // 搜索到设备       —— 接收广播
                    Log.d(TAG,"搜索到设备");
                    BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                    mList.add(device);
                    mAdapter.refresh(mList);
    
    
                } else if (BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(action)) {
                    // 当自己设备设置蓝牙可见时或者不可见时 —— 接收广播
                    int scanMode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE,0);
                    // 可见时
                    if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
                        Log.d(TAG,"设备可见监听");
                    } else {
                        Log.d(TAG,"设备不可见监听");
                    }
    
                } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
                    // 绑定状态改变回调
                    BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                    if (remoteDevice == null) {
                        Log.d(TAG,"没有绑定设备");
                        return;
                    }
    
                    int status = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,0);
                    if (status == BluetoothDevice.BOND_BONDED) {
                        Log.d(TAG,"绑定设备完成: " + remoteDevice.getName());
                    } else if (status == BluetoothDevice.BOND_BONDING) {
                        Log.d(TAG,"绑定设备中: " + remoteDevice.getName());
                    } else if (status == BluetoothDevice.BOND_NONE) {
                        Log.d(TAG,"取消绑定: ");
                    }
                }
            }
    
        };

    以上基本上就是蓝牙接收状态和搜索到设备,绑定设备一系列改变的操作!基本上已经很全了。下面介绍一个蓝牙通信的流程。

    左为客户端Socket连接的一个流程:首先获取一个客户端Socket(),然后连接上,就可以读取数据和发送数据了。

    右为服务端Socket操作流程:

    蓝牙通信原理介绍:
    蓝牙通信和socket通信原理基本上是一致的,下面我给大家上一张图(图为Socket通信图)。分析一下。
    这里写图片描述

    蓝牙客户端Socket的与Sokcet流程是一样的,只不过参数不同而已。如下:
    1、创建客户端蓝牙Sokcet
    2、创建连接
    3、读写数据
    4、关闭

    服务端socket:
    1、创建服务端蓝牙Socket
    2、绑定端口号(蓝牙忽略)
    3、创建监听listen(蓝牙忽略, 蓝牙没有此监听,而是通过whlie(true)死循环来一直监听的)
    4、通过accept(),如果有客户端连接,会创建一个新的Socket,体现出并发性,可以同时与多个socket通讯)
    5、读写数据
    6、关闭

    下面看客户端代码:

    /**
     * <p>Title: ConnectThread</p >
     * <p>Description: 客户端逻辑: 客户端的线程,处理客户端socket</p >
     * <p>Company: ihaveu</p >
     *
     * @author MaWei
     * @date 2017/12/26
     */
    public class ConnectThread extends Thread{
        private static final UUID MY_UUID = UUID.fromString(Constant.CONNECTTION_UUID);
        /** 客户端socket*/
        private final BluetoothSocket mmSoket;
        /** 要连接的设备*/
        private final BluetoothDevice mmDevice;
        private BluetoothAdapter mBluetoothAdapter;
        /** 主线程通信的Handler*/
        private final Handler mHandler;
        /** 发送和接收数据的处理类*/
        private ConnectedThread mConnectedThread;
    
        public ConnectThread(BluetoothDevice device, BluetoothAdapter bluetoothAdapter, Handler mUIhandler) {
            mmDevice = device;
            mBluetoothAdapter = bluetoothAdapter;
            mHandler = mUIhandler;
    
            BluetoothSocket tmp = null;
            try {
                // 创建客户端Socket
                tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            mmSoket = tmp;
        }
    
        @Override
        public void run() {
            super.run();
            // 关闭正在发现设备.(如果此时又在查找设备,又在发送数据,会有冲突,影响传输效率)
            mBluetoothAdapter.cancelDiscovery();
    
            try {
                // 连接服务器
                mmSoket.connect();
            } catch (IOException e) {
                // 连接异常就关闭
                try {
                    mmSoket.close();
                } catch (IOException e1) {
                }
                return;
            }
    
            manageConnectedSocket(mmSoket);
        }
    
        private void manageConnectedSocket(BluetoothSocket mmSoket) {
            // 通知主线程连接上了服务端socket,更新UI
            mHandler.sendEmptyMessage(Constant.MSG_CONNECTED_TO_SERVER);
            // 新建一个线程进行通讯,不然会发现线程堵塞
            mConnectedThread = new ConnectedThread(mmSoket,mHandler);
            mConnectedThread.start();
        }
    
        /**
         * 关闭当前客户端
         */
        public void cancle() {
            try {
                mmSoket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 发送数据
         * @param data
         */
        public void sendData(byte[] data) {
            if(mConnectedThread != null) {
                mConnectedThread.write(data);
            }
        }
    }

    服务端代码:

    /**
     * <p>Title: AccepThread</p >
     * <p>Description: 服务端Socket通过accept()一直监听客户端连接的线程</p >
     * <p>Company: ihaveu</p >
     *
     * @author MaWei
     * @date 2017/12/26
     */
    public class AccepThread extends Thread {
    
        /** 连接的名称*/
        private static final String NAME = "BluetoothClass";
        /** UUID*/
        private static final UUID MY_UUID = UUID.fromString(Constant.CONNECTTION_UUID);
        /** 服务端蓝牙Sokcet*/
        private final BluetoothServerSocket mmServerSocket;
        private final BluetoothAdapter mBluetoothAdapter;
        /** 线程中通信的更新UI的Handler*/
        private final Handler mHandler;
        /** 监听到有客户端连接,新建一个线程单独处理,不然在此线程中会堵塞*/
        private ConnectedThread mConnectedThread;
    
        public AccepThread(BluetoothAdapter adapter, Handler handler) throws IOException {
            mBluetoothAdapter = adapter;
            this.mHandler = handler;
    
            // 获取服务端蓝牙socket
            mmServerSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
        }
    
        @Override
        public void run() {
            super.run();
            // 连接的客户端soacket
            BluetoothSocket socket = null;
    
            // 服务端是不退出的,要一直监听连接进来的客户端,所以是死循环
            while (true){
                // 通知主线程更新UI,客户端开始监听
                mHandler.sendEmptyMessage(Constant.MSG_START_LISTENING);
                try {
                    // 获取连接的客户端socket
                    socket =  mmServerSocket.accept();
                } catch (IOException e) {
                    // 通知主线程更新UI, 获取异常
                    mHandler.sendEmptyMessage(Constant.MSG_ERROR);
                    e.printStackTrace();
                    // 服务端退出一直监听线程
                    break;
                }
    
                if(socket != null) {
                    // 管理连接的客户端socket
                    manageConnectSocket(socket);
    
                    // 这里应该是手动断开,案例应该是只保证连接一个客户端,所以连接完以后,关闭了服务端socket
    //                try {
    //                    mmServerSocket.close();
    //                    mHandler.sendEmptyMessage(Constant.MSG_FINISH_LISTENING);
    //                } catch (IOException e) {
    //                    e.printStackTrace();
    //                }
                }
            }
        }
    
        /**
         * 管理连接的客户端socket
         * @param socket
         */
        private void manageConnectSocket(BluetoothSocket socket) {
            // 只支持同时处理一个连接
            // mConnectedThread不为空,踢掉之前的客户端
            if(mConnectedThread != null) {
                mConnectedThread.cancle();
            }
    
            // 主线程更新UI,连接到了一个客户端
            mHandler.sendEmptyMessage(Constant.MSG_GOT_A_CLINET);
            // 新建一个线程,处理客户端发来的数据
            mConnectedThread = new ConnectedThread(socket, mHandler);
            mConnectedThread.start();
        }
    
        /**
         * 断开服务端,结束监听
         */
        public void cancle() {
            try {
                mmServerSocket.close();
                mHandler.sendEmptyMessage(Constant.MSG_FINISH_LISTENING);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 发送数据
         * @param data
         */
         public void sendData(byte[] data){
             if(mConnectedThread != null) {
                 mConnectedThread.write(data);
             }
         }
    }

    下面看一个共同通讯处理类:

    /**
     * <p>Title: ConnectedThread</p >
     * <p>Description: 客户端和服务端 处理 发送数据 和获取数据</p >
     * <p>Company: ihaveu</p >
     *
     * @author MaWei
     * @date 2017/12/26
     */
    public class ConnectedThread extends Thread{
        /** 当前连接的客户端BluetoothSocket*/
        private final BluetoothSocket mmSokcet;
        /** 读取数据流*/
        private final InputStream mmInputStream;
        /** 发送数据流*/
        private final OutputStream mmOutputStream;
        /** 与主线程通信Handler*/
        private Handler mHandler;
        private String TAG = "ConnectedThread";
    
        public ConnectedThread(BluetoothSocket socket,Handler handler) {
            mmSokcet = socket;
            mHandler = handler;
    
            InputStream tmpIn = null;
            OutputStream tmpOut = null;
            try {
                tmpIn = socket.getInputStream();
                tmpOut = socket.getOutputStream();
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            mmInputStream = tmpIn;
            mmOutputStream = tmpOut;
        }
    
        @Override
        public void run() {
            super.run();
            byte[] buffer = new byte[1024];
    
            while (true) {
                try {
                    // 读取数据
                    int bytes = mmInputStream.read(buffer);
    
                    if(bytes > 0) {
                        String data = new String(buffer,0,bytes,"utf-8");
                        // 把数据发送到主线程, 此处还可以用广播
                        Message message = mHandler.obtainMessage(Constant.MSG_GOT_DATA,data);
                        mHandler.sendMessage(message);
                    }
    
                    Log.d(TAG, "messge size :" + bytes);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        // 踢掉当前客户端
        public void cancle() {
            try {
                mmSokcet.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 服务端发送数据
         * @param data
         */
        public void write(byte[] data) {
            try {
                mmOutputStream.write(data);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    下面是自己写的一个聊天demo

    /**
     * <p>Title: ChatController</p >
     * <p>Description: 聊天控制器</p >
     * <p>Company: ihaveu</p >
     *
     * @author MaWei
     * @date 2017/12/26
     */
    public class ChatController {
        /** 客户端的线程*/
        private ConnectThread mConnectThread;
        /** 服务端的线程*/
        private AccepThread mAccepThread;
        private ChatProtocol mProtocol = new ChatProtocol();
    
        /**
         * 网络协议的处理函数
         */
        private class ChatProtocol implements ProtocoHandler<String>{
            private static final String CHARSET_NAME = "utf-8";
    
            /**
             * 封包(发送数据)
             * 把发送的数据变成  数组 2进制流
             */
            @Override
            public byte[] encodePackge(String data) {
                if(data == null) {
                    return new byte[0];
                }else {
                    try {
                        return data.getBytes(CHARSET_NAME);
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                        return new byte[0];
                    }
                }
            }
    
            /**
             * 解包(接收处理数据)
             * 把网络上数据变成自己想要的数据体
             */
            @Override
            public String decodePackage(byte[] netData) {
                if(netData == null) {
                    return "";
                }else {
                    try {
                        return new String(netData, CHARSET_NAME);
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                        return "";
                    }
                }
            }
        }
    
        /**
         * 与服务器连接进行聊天
         */
        public void startChatWith(BluetoothDevice device,BluetoothAdapter adapter,Handler handler){
            mConnectThread = new ConnectThread(device, adapter, handler);
            mConnectThread.start();
        }
    
        /**
         * 等待客户端来连接
         * handler : 用来跟主线程通信,更新UI用的
         */
        public void waitingForFriends(BluetoothAdapter adapter, Handler handler) {
            try {
                mAccepThread = new AccepThread(adapter,handler);
                mAccepThread.start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 发出消息
         */
        public void sendMessage(String msg){
            // 封包
            byte[] data = mProtocol.encodePackge(msg);
    
            if(mConnectThread != null) {
                mConnectThread.sendData(data);
            }else if(mAccepThread != null) {
                mAccepThread.sendData(data);
            }
    
        }
    
        /**
         * 网络数据解码
         */
        public String decodeMessage(byte[] data){
            return mProtocol.decodePackage(data);
        }
    
        /**
         * 停止聊天
         */
        public void stopChart(){
            if(mConnectThread != null) {
                mConnectThread.cancle();
            }
            if(mAccepThread != null) {
                mAccepThread.cancle();
            }
        }
    
    
        /**
         * 以下是单例写法
         */
        private static class ChatControlHolder{
            private static ChatController mInstance = new ChatController();
        }
    
        public static ChatController getInstance(){
            return ChatControlHolder.mInstance;
        }
    }

    OK,完成,大家主要看上面的广播,和蓝牙sokcet流程,其他的根据自己业务要求写就OK

    展开全文
  • 蓝牙是个全球标准,所以具备蓝牙功能的两个设备之间都能通信,或者配对(pair)。 通信并不需要直接的光线通道,能穿越大部分的非金属物体。 第一个蓝牙版本于1999年发布。然后2000年出现了据有蓝牙功能的移动电话...

     

    Electromagnetic spectrum,电磁波谱,包括电磁辐射(electromagnetic radiation)所有可能的频率(frequencies), 和与其相应的波长和光子能量(E=hf)。

    根据公式c=hλ,电磁波谱的频率范围从小于 1hertz到10的25次方Hz,波长(wavelengths)从数千米到原子大小。

    短波长的极限被认为几乎等于普朗克长度( 约等于1.616229x10^-35),

    一个物体的电磁波谱专指的是这物体发射或吸收的电磁辐射,又称电磁波的特征频率分布。

    频率范围分成了很多个独立的波段,长波长的极限被认为等于整个宇宙的大小,虽然原则上电磁波谱是无限的,而且是连续的。


     无线电频谱,Radio spectrum,频率介于30Hz和300GHz之间,也叫做射频电波(Radio waves),简称射频、射电,也经常用Radio Frequency(RF)来表示射频、无线电频率。无线电波不用人工导波而在空间传播的电磁波。一般而言,无线电频谱是指9kHz-3000GHz频率范围内无线电频率的总称。无线电技术是指将声音讯号或其他信号经过转换,利用无线电波传播。


    无线电频段中,还包含了ISM频段的定义。

    ISM频段(Industrial Scientific Medical Band),中文意思分别是工业的(Industrial)、科学的(Scientific)和医学的(Medical),因此顾名思义ISM频段就是各国挪出某一段频段主要开放给工业,科学和医学机构使用。应用这些频段无需许可证或费用,只需要遵守一定的发射功率(一般低于1W),并且不要对其它频段造成干扰即可。ISM频段在各国的规定并不统一。如在美国有三个频段902-928 MHz、2400-2483.5 MHz及5725-5850 MHz,而在欧洲900MHz的频段则有部分用于GSM通信。而2.4GHz为各国共同的ISM频段。因此无线局域网(IEEE 802.11b/IEEE 802.11g)、蓝牙、ZigBee等无线网络,均可工作在2.4GHz频段上。

    在国际电联无线电规则ITU-R第5条脚注5.138,5.150和5.280 中指定的ISM频段如下:

    频率范围

    中心频率

    可用性

    6.765–6.795 MHz

    6.780 MHz

    13.553–13.567 MHz

    13.560 MHz

    26.957–27.283 MHz

    27.120 MHz

    40.66–40.70 MHz

    40.68 MHz

    433.05–434.79 MHz

    433.92 MHz

    仅限ITU Region 1

    902–928 MHz

    915 MHz

    仅限ITU Region 2

    2.400–2.500 GHz

    2.450 GHz

    5.725–5.875 GHz

    5.800 GHz

    24–24.25 GHz

    24.125 GHz

    61–61.5 GHz

    61.25 GHz

    122–123 GHz

    122.5 GHz

    244–246 GHz

    245 GHz


    蓝牙的通讯都是通过频率为2.4GHz的ISM(Industrial, Scientific and Medical)波段的电磁波(radio frequencies)。

    2.4G是一种无线技术,由于其频段处于2.400GHz~2.4835GHz之间,所以简称2.4G无线技术。 

    蓝牙是为了短距离通信而产生的无线技术,这项技术由Bluetooth Special Interest Group(SIG)负责监管。

    SIG是非盈利的、非股份制公司,负责蓝牙标准的开发以及蓝牙技术和商标的授权,在1998年由5个公司创立,分别是爱立信、诺基亚、IBM、东芝和英特尔,到2012年,SIG的公司成员数超过了16000。

    蓝牙提供了安全、稳定、低功耗和低成本的设备间信息交换方案。

    蓝牙是个全球标准,所以具备蓝牙功能的两个设备之间都能通信,或者配对(pair)。

    通信并不需要直接的光线通道,能穿越大部分的非金属物体。

    第一个蓝牙版本于1999年发布。然后2000年出现了据有蓝牙功能的移动电话。

    经典蓝牙的频段使用情况,有79个通道(Channels),每个通道间隔1MHz,从2402MHz开始。

    BLE的频段使用情况,分为数据通道和广播通道,有40个通道,每两个通道间隔2MHz。

    参考:

    https://en.wikipedia.org/wiki/Electromagnetic_spectrum

    https://en.wikipedia.org/wiki/Radio_spectrum

    https://en.wikipedia.org/wiki/ISM_radio_band

    Bluetooth® Low Energy Channels - Developer Help

    Introduction to Bluetooth Classic | Argenox

    展开全文
  • 原理类似socket通信,必须有一个作为服务端,一个作为客户端。特别注意的两端的uuid必须相同。一下就是简单蓝牙聊天代码。 1.客户端代码 package com.example.myblooth; import java.io.IOException; import java....
  • 蓝牙通信,完整的通信流程

    千次阅读 2018-12-28 09:21:00
    蓝牙通信原理介绍: 蓝牙通信和socket通信原理基本上是一致的,下面我给大家上一张图(图为Socket通信图)。分析一下。 蓝牙客户端Socket的与Sokcet流程是一样的,只不过参数不同而已。如下: 1、创建客户端...
  • 成长中的小白又来发博客了,这次带来的分享是结合我前两篇『串行口通信』和『初学者模式下的12864液晶(一)』的技术博客综合起来再加今天的蓝牙模块的应用,今天稍微讲讲蓝牙基本的操作和基本接法,重点是几个方面的...
  • 每个人都会用蓝牙,但不是每个人都知道蓝牙的工作原理哦!看看对自己总会有点帮助的。
  • 蓝牙 穿戴手环通信原理

    千次阅读 2016-10-16 12:53:45
    蓝牙 穿戴手环通信原理
  • 本教程介绍了Android 蓝牙通信原理及具体实现,其中包括:客户端服务器结构和对等结构。然后介绍了基于Socket实现的蓝牙通信,以及基于蓝牙实现对等结构网络通讯。我们还介绍了蓝牙r发现服务协议的使用。 Android...
  • 蓝牙,是一种支持设备短距离通信(一般10m内)的无线电技术,经过近几年的发展,我们对它已不再陌生,它也是目前数码产品中不可或缺的模块。蓝牙技术的出现让我们在连接各种设备的时候不再被繁多的数据线所束缚,比如...
  • 有关于 android 平台下 Bluetooth工作原理&流程简介;
  • 解析蓝牙原理

    千次阅读 多人点赞 2017-08-26 13:43:15
    对于手机行业的开发者,要进行蓝牙模块的维护,就必须从Android系统底层,至少框架层开始,了解蓝牙的结构和代码实现原理。这方面的文档、网上的各个论坛的相关资料却少之又少。分析原因,大概因为虽然蓝牙协议是...
  • 本篇讲解蓝牙Mesh网络的通信原理以及在使用蓝牙Mesh开展设计时应掌握的各种重要概念。节点间通信蓝牙Mesh使用海量消息并发传输模式在节点间传输消息。海量并发模式是一种多路径消息传递实现方案,有足够冗余来确保...
  • 本项目分享的是基于蓝牙通信NRF51822远程控制智能自行车设计,附原理图/PCB/控制源码,供网友参考学习。历经三个月,从STM32,再到蓝牙模块NRF51822,到Android几乎都是一个人完成了,除此之外其实还有一个蓝牙通信...
  • 蓝牙通信的源码与PCB电路原理图,主要应用于软硬件结合开发
  • BT08b蓝牙 模块资料

    2020-04-11 21:49:41
    BT08 蓝牙模块资料与AT指令集的使用(BT05通用) 本蓝牙模块遵循V2.1+EDR蓝牙规范。 本模块支持UART接口,并支持SPP蓝牙串口协议,具有成本低、体积小、功耗低、收发灵敏性高等优点,只需配备少许的外围元件就能...
  • RS232接口和蓝牙通信

    2019-03-16 19:49:24
    RS232接口转到蓝牙进行通信,采用无线通信,省去数据线的连接,方便快捷。
  •  //若状态为0,说明继电器为吸合状态,并发送“The Light is turn on”到蓝牙 } else { IN_1=0,flag_z=0;}} //否则则说明继电器为断开状态,则端口置高,并发送“The Light Was turn on”到蓝牙 break; case 98:{ ...
  • 摘要:Siw1701无线电调制解调器是用来解决蓝牙无线通信用的单片集成电路。芯片内部包含一个2.4GHz无线电收发器、GFSK调制解调器和数字控制功能电路。Siw1701可与Siw1750/1760链接控制或者其它微控制器组合,构成蓝牙...
  • 本项目分享的是短距离无线通信QN9021蓝牙模块硬件资料(原理图、贴片图、尺寸CAD),见附件下载。FS-QN9021 是采用NXP 单模BLE(Bluetooth Low Energy)芯片QN9021 作为核心的超低功耗射频收发模块,遵循低功耗蓝牙...
  • BT08B蓝牙模块手册

    2018-08-24 21:00:48
    JDY-30 透传模块是基于蓝牙 2.1 协议标准,工作频段为 2.4GHZ 范围,具有 信号强、数据传输快、性能稳定等特性
  • HC-05蓝牙模块,主从一体机原理总结

    千次阅读 2021-02-24 17:10:06
    原理就是:手机通过蓝牙传输到HC-05上,再通过串口通信和STM32通信;而之前一般都是电脑上通过USB线转串口的方式,通过串口和STM32通信。本质上没有区别的。 这个时候就应该更加深刻地体会到了本文开篇的一句话:...
  • 蓝牙通话机制原理

    万次阅读 2016-09-20 20:55:21
    [摘要]: 本文主要论述基于android 6.0的蓝牙上层(Java层)通话机制;总结了蓝牙通话框架,并且给出了接听电话的详细的流程图;最后说明了apk的实现以及总结了蓝牙/android 相关的知识点。 1, 蓝牙框架 主要代码路径: ...
  • 蓝牙技术是一种无线数据与语音通信的开放性全球规范,它以低成本的近距离无线...蓝牙通信原理和协议 蓝牙技术规定了4种物理接口通用串行总线USBEIA-232,PC卡及通用异步收发器UART接口。1个蓝牙系统一般由射频单元链
  • HC-05蓝牙串口通信模块应该是使用最广泛的一种蓝牙模块之一了。为什么呢? 因为HC05模块是一款高性能主从一体蓝牙串口模块,可以不用知道太多蓝牙相关知识就...所以本文就介绍一下这款蓝牙通信模块的使用,包括蓝...
  • 蓝牙通信工作流程讲解

    万次阅读 2018-06-19 14:35:45
    最近项目上需要蓝牙通讯功能,所以自己私下里学习了一下蓝牙通讯相关的知识。...1、首先是蓝牙通信机制 蓝牙通信也是采用Socket机制,通信双方有一方为服务器端,另一方为客户端,可能有 人会觉...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 10,116
精华内容 4,046
关键字:

蓝牙通信原理