蓝牙好复杂的说。。。
如果是用ESP-IDF来写的话就更复杂了(如果是用Arduino或者是MicroPython的话还是相对简单的)。。。
但是ESP32的特点就是包含蓝牙,所以我们还是得学一学怎么用ESP-IDF来使用蓝牙。
今天我们先来看看SPP的官方Demo,也是比较实用的一个工作模式,SPP就是Serial Port Profile(串口协议),也就是蓝牙串口,我们之前玩过的蓝牙模块就是用的蓝牙串口这一工作模式。
简单来说就是像使用串口一样去使用蓝牙。
HC-05蓝牙模块AT指令详解_hc05at指令集-CSDN博客文章浏览阅读4.9k次,点赞23次,收藏45次。以上AT指令是我觉得比较常用的,日常使用的话上面这些就够用了。想看完整的手册可以去官网找,或者是直接找卖家要。也可以关注我的公众号“折途想要敲代码”回复关键词“蓝牙”即可免费下载HC-05以及JDY-31的相关资料,内含PC串口助手和安卓手机的蓝牙助手。_hc05at指令集https://blog.csdn.net/m0_63235356/article/details/135621671由于我对蓝牙底层原理的了解几乎为0,一切关于蓝牙的知识都是现学的,乐鑫的API也是翻着官方文档去猜测的,所以如果有说的不对的地方,欢迎大家批评指正。
首先我们需要先创建项目。
在进行到上图这一步的时候,我们选择模板的时候点击左上角选择ESP-IDF,它默认是Extersion的。
然后选择ble_spp_server这个模板。
简单解释一下这个项目的名字。
ble就是Bluetooth Low Energy(蓝牙低功耗)
SPP刚才解释了,是串口通信协议。
server就是服务端,与之对应的是上面的client的客户端,只要我们看懂了服务端的代码,那么客户端的其实也差不了太多。
然后创建完之后我就被劝退了。
不是哥们,七百行代码,你逗我玩呢!?
但是没办法,之后计划中有个小项目要用到蓝牙,所以硬着头皮也得啃下来,至少也得懂的如何修改。
虽然有七百多行代码,但是前两百四十多行之前都是一些宏定义和函数声明,变量定义什么的,所以实际上只有不到五百行(还是好多的~)。
因为Demo里的注释不多,而且都是英文,所以我会在我觉得应该加上注释并且我有能力加上注释的地方加上中文注释,然后再删除一些用不上的代码,把项目文件打包好传到网盘,小伙伴们可以关注我的同名公众号“折途想要敲代码”回复关键词“蓝牙”即可免费下载,还包括蓝牙调试助手的安装包以及蓝牙模块的资料。
接下来进入正题。
一共是两个文件,.h文件我们可以不用看,就是一些宏定义和一个枚举类型(用来表示数据表索引的)
我们直接看.c文件,从app_main开始,也就是从程序入口开始。
上图中我用红字标出了数字,我们一个个来看。
第一个地方是一个蓝牙配置的结构体类型变量,等号右边是一个宏定义,可以直接给我们返回一个默认配置的结构体变量。
如果你想像之前配置其他外设一样自己去定义赋值这个结构体的成员的话,我也不拦着你,但是你先看看下面,这是这个结构体的定义。
所以我们还是用默认配置吧,有特殊需求的话再单独拿出来修改就好。
第二个地方是初始化NVS非易失性存储,也就是Flash,这个我们不用管它,这是常规流程。
第三个地方释放控制器内存,Demo中传入的参数意思是释放经典蓝牙的控制器内存,因为我们使用的是BLE低功耗蓝牙,所以用不着经典蓝牙的控制器,所以给它释放掉。
第四个地方是初始化蓝牙控制器,传入的参数就是第一个地方的那个结构体变量的指针。如果要自定义一些蓝牙配置的话,就要在这之前修改。
我们再往下看,这些函数是什么意思我都打上注释了,所以我们就跳过这一段固定流程,我们在用的时候是不需要修改的。
再往下注册了两个回调函数,一个应用程序标识符,还启动了一个蓝牙串口任务。
我们先看GATT(Generic Attribute Profile,即通用属性配置文件)的回调函数gatts_event_handler,一旦有什么GATT相关的事件发生就会触发这个函数。
这个函数接收三个参数,第一个是事件类型,第二个是GATT的接口类型,具体什么意思我也不太清楚,最后一个是参数,这个参数的类型是一个枚举类型,枚举里包含很多种结构体类型,在遇到不同的事件类型的时候可以使用不同的结构体。
接下来我们看看这个回调函数里的内容,大体可以分为两个部分,第一部分是if代码块,内容是如果是注册应用ID事件那么就把gatts_if存起来。
第二部分是do while代码块。
我不太懂这里为什么要用do while(0),这样就是只执行一遍代码块里的内容,和不用do while是一样的,懂的小伙伴可以在评论区告诉我。
do while里就是用gatts_if来判断应用是否已经被注册,如果注册的话再调用对应任务的回调函数。
这里用了循环遍历spp_profile_tab这个数组来和gatts_if一一比对,我们可以来看一下这个数组。
里面只有一个元素,存放的是一个结构体,成员分别是这个应用的回调函数和gatts_if。
我们再看看这个真正的GATT的回调函数gatts_profile_event_handler,因为我们的gatt的应用就这一个官方示例里默认的。
函数的参数列表和刚刚那个第一层的回调函数是一样的,因为就是用的第一层函数的参数去调用这第二层函数。
首先是把第三个参数取出来,我们刚刚有说这是一个枚举类型,可以根据不同的事件类型我们来选择不同的结构体类型。
接下来就是判断事件类型了。
如果是注册应用ID事件,也就是一开始最早执行的事件,就会开始执行三个函数,第一个是把设备名改成本地设备,但是不改变广播内容,这是什么意思呢?意思就是我们可以改变设备名,也就是我们手机打开蓝牙搜索可以连接的对象的时候看到的名字,不过这个设置完之后并不能真的改变名字。
我们需要在一个数组里修改。
蓝牙广播数据这个数组,我们可以发送多个UTF-8编码字符串(这个我们之前在讲解MQTT的是有涉及到),就是第一个字符表示剩下字符串的长度,接下来就是字符串的内容(使用UTF-8编码)。
在蓝牙广播数据里,是一条条UTF-8编码字符串,第一个字节是表示剩余长度,第二个字节表示这条字符串表示的类型,剩下的字节就是真正载荷的数据,
比如说第三个字符串,第一个字节是0x06,说明接下来六个字节是这条字符串的内容。
第二个字节是0x09,表示的是这一条字符串是来指定蓝牙的本地名称的。
剩下字节就是我们真正要定义的蓝牙名称了。
所以我们要修改蓝牙名称的话,需要在这个数组里修改,我在截图的时候把左侧的代码行数也标出来了,方便大家定位代码的位置。
关于第二个字节,我们可以参考蓝牙官方的手册。小伙伴们可以自己对着下表查找一下剩下两个字符串是表示什么。
我们再回到回调函数。
使用switch语句,面对不同的事件,会有不同的处理方式,我对着乐鑫的代码把中文注释一个个打上去了,我们需要在一些事件做额外处理的话,找到对应位置加上处理逻辑即可,如果有没包含在swtich的事件要用的话,我们在加个case上去就行。
然后有一点我们需要关注的就是在客户端请求写事件中的一段代码,下图中我用红框框出来了。
我们要接收处理来自客户端发送的数据的话就是在这里做处理,官方Demo是直接用串口打印出来,我们有其他要处理的逻辑的话可以在这里把收到的数据存起来,然后在其他运行任务中做进一步繁琐的处理,第一是不要让回调函数看起来太过于繁琐,第二就是不要在回调函数里停留太长时间。
上图可知很多事件其实官方Demo默认是不做处理的,有需要的话我们自己加上去就行。
我们留意一下我框出来的事件,也就是连接和断开连接的这俩事件,这应该是比较值得做文章的两个事件,我们再额外留意一下我用绿框框出的变量is_connected,通过这个变量的值我们可以知道此时是否有连接上蓝牙,比如说后续我们有数据要通过蓝牙发送到手机上的时候,如果我们没有连接上,那么就不能发送。
关于GATT的回调函数我们就介绍这么多,大部分我们是不需要改的,有需要的地方我在上面也提出来了,加下来我们回到程序入口函数,安装顺序看看GAP的回调函数。
GAP就是Generic Access Profile(通用访问配置文件)它的作用就是建立连接的,所以它的回调函数还算简单,但是它的事件类型其实非常多,但是官方Demo里只列出两个,我们一般也不用改,直接用就好,如果有其他需求的话再自己把对应的事件加上去就行。
两个事件分别是广播数据集完成,以及广播完成时,这也没啥可说的(因为其实我对蓝牙的协议也不是很了解,而且蓝牙协议非常复杂)
我们再回到程序入口函数,这时候就剩两个函数了。
第一个函数是注册应用程序,传入的参数是应用ID。
我们在上面讲的GATT部分中就有注册应用事件,也就是在我们调用了这个注册应用函数之后触发了对应的事件。
如果我们有多个应用的话,就需要再多调用一次这个函数,ID记得换一个,并且在spp_profile_tab这个数组里也要多加上对应的元素。
最后就是spp_task_init,这个就不属于乐鑫蓝牙的API了,这个函数的声明和定义都在本文件中。
这个函数里又调用了一个spp_uart_init,也就是初始化串口0的,我们再进入看看。
这个没什么好说的,就是配置一下串口,之前的文章也有讲过关于串口如何配置。
【快速上手ESP32(基于ESP-IDF&VSCode)】05-UART串口通信_esp32串口通信-CSDN博客文章浏览阅读2.5k次,点赞25次,收藏50次。UART,即通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),是一种异步收发传输器,是电脑硬件的一部分,它将要传输的资料在串行通信与并行通信之间加以转换。作为把并行输入信号转成串行输出信号的芯片,UART通常被集成于其他通讯接口的连接上。UART用于异步通信,异步通信以一个字符为传输单位,通信中两个字符之间的时间间隔是不固定的,然而在同一个字符中的两个相邻位之间的时间间隔是固定的。因此,异步通信的特点就是:字符间异步,字符内部各位同步。_esp32串口通信https://blog.csdn.net/m0_63235356/article/details/137482698最后是用FreeRTOS开启了一个任务uart_task,我们再看看这个任务函数执行了什么。
里面是一个for(;;)的死循环,用xQueueReceive卡着。
如果对FreeRTOS不太了解的小伙伴也可以去看看我往期的文章,也在“ESP32”这个专栏里。
如果spp_uart_queue这个队列收到数据了,那么就开始用switch判断事件类型。
如果是收到数据,再进入判断,如果确实是收到数据了,并且蓝牙是连上了(用is_connect这个变量来判断,上文有简单提一下,这边不就用到了),就把串口缓冲区的数据读到temp这个变量里。
接下来再判断,如果数据的大小小于spp_mtu_size - 3(即最大传输单元的大小 - 3),那么就发送指示或通知到指定客户端。
至于为什么数据大小要和spp_mtu_size - 3比,这是因为传输单元除了载荷之外还要再扣掉3字节的报头,所以比较大小的时候要把这3字节扣掉。
如果数据的大小比最大传输单元的大小-3还大,那么就进入另外一大段代码,正如我注释里写的,具体什么内容我也看不懂。
但是我们找到了一个关键函数,那就是esp_ble_gatts_send_indicate。
如果我们要向客户端发送数据,那么就是用的它。
我们可以转到函数声明里看看。
参数挺多挺复杂的,又是ID又是句柄的,但是不用怕,我们直接把Demo里用的直接复制过来。
esp_ble_gatts_send_indicate(spp_gatts_if, spp_conn_id, spp_handle_table[SPP_IDX_SPP_DATA_NTY_VAL], event.size, temp, false);
前面几个参数我们都不用改,我们只需要把倒数第二和第三个参数换了就行,看的出来,倒数第三个参数的意思就是我们这次要发送的数据的大小,倒数第二个参数就是要发送数据的数组。
至此,我们收发数据的函数和地方就都找到了,不过我们还是接着往下看看,对Demo越了解,我们就越容易修改。
回到spp_task_init,还剩两个FreeRTOS的任务。
我们只需要看spp_cmd_task这个任务函数就行了,下面那个test_task是我自己调试用的。
spp_cmd_task实际上没什么内容。
也就是一个死循环,然后等待cmd_cmd_queque这个队列接收数据,然后打印出来。
我们对官方Demo的讲解也就结束了,其实Demo里还有一些函数,但是我们跳过了,因为并不重要,接下来我们来简单修改一下官方Demo,实现一下使用SPP进行数据的收发。
既然我们这个Demo是server服务端,那么就按照我们写服务器的传统,写第一个实例就写一个回声服务器吧,也就是服务端收到什么,我们就发送什么回去。
首先我们在收到数据的那个地方做点修改。
我们把收到的数据通过xQueueSend写到cmd_cmd_queue这个队列里,至于为什么,我们可以参考spp_cmd_task这个任务函数,它也是等待cmd_cmd_queue这个队列的数据然后做其他处理,那么我们也仿造一下官方的Demo。
所以下一处的修改就是在spp_cmd_task里了。
如果我们的cmd_cmd_queue这个队列收到数据了,那么判断,蓝牙是否还连接着(用is_connected判断),然后再判断,数据的大小是否小于最大传输单元大小-3,都满足条件之后,我们就使用esp_ble_gats_send_indicate这个函数把数据发送出去。
最后要修改的地方就是cmd_cmd_queue的大小。
在spp_task_init函数里。
因为我们使用cmd_cmd_queue来传输字符串了,所以单个元素的大小会比较大,我们需要修改一下大小。
然后就完成修改了,实现的功能就是当我们收到数据之后,我们把收到的数据放进cmd_cmd_queue队列里,然后在spp_cmd_task这个任务中收到了来自cmd_cmd_queue这个队列的数据之后,再原封不动的发送回去,当然小伙伴们也可以对这个数据进行一些处理之后再发送。
这边我使用ESP32S3这个型号的板子来进行测试,当收到数据之后会打印在终端上,这是官方Demo原本就有的。
然后在蓝牙串口助手上,我们可以发现,我们发送了什么,就接收到了什么,回声服务器测试成功。
蓝牙串口助手可以直接在微信小程序里搜,随便找一个就行了。
也可以到应用商城下载一个,也可以关注我的同名公众号“折途想要敲代码”回复关键词“蓝牙”即可免费下载资料包,包括蓝牙调试助手的安装包,我修改添加注释过后的代码以及蓝牙模块的资料。
然后眼尖的小伙伴可能注意到了,我的蓝牙名字改成了zhetu,关于如何修改蓝牙名称,文章上面也可以提过了,不过我们再提一下。
第一个是宏定义修改一下。
第二个地方是蓝牙广播数据包这边修改一下。
以上就是本文的全部内容了,由于对蓝牙不是很熟悉,所以说的不对的地方欢迎大家批评指正,这里是折途,我们下次再见。