本次教程使用BL706AVB开发板,通过串口访问BL604DVK开发板的AT接口,并连接互联网进行天气数据的收发。开发工作全部基于bl_mcu_sdk在BL706开发板上完成,BL604开发板仅作为Wi-Fi接口供BL706调用,本身不负责数据处理。
开发流程将会分为以下部分进行讲解:
- 开发前的准备工作
- 串口的配置
- AT服务的使用
- Wi-Fi网络连接
- HTTP数据的获取(HTTP GET)
- Json数据的处理
- HTTP数据的发送(HTTP POST)
1 开发前的准备工作
1.1 给BL604开发板烧写AT固件
首先使用Bouffalo Lab Dev Cube集成开发工具为BL604DVK开发板烧写包含了AT服务的固件,固件包含在工程的开源里,而工具的下载和使用方式可以参考以下页面:
http://bouffalolab.gitee.io/bl_mcu_sdk/get_started/bl_dev_cube.html
将BL604开发板通过USB数据线直接连接到PC,并依据如下配置将bl602_AT_whole.bin这一固件烧写至BL604DVK开发板。点击Create&Download按键开始下载之前,必须按下开发板上的BOOT+RESET组合键(先按住BOOT键,然后按下RESET键,最后将两个按键松开,松开的顺序无要求)进入BOOT模式,否则无法进行烧录(BL706开发板也同理)。

当BLDevCube窗口中出现绿色的Success提示后,BL604的烧录就完成了,此时可以断开BL604开发板的连接。固件默认将BL604开发板的GPIO3/4引脚配置为AT端口的RX/TX,在开发板上对应的丝印为SD1/SD2,默认波特率为115200。

1.2 配置BL706开发板的开发环境
BL706开发板支持J-Link、CK-Link、OpenOCD三种下载器,以及Eclipse和CDK两种集成开发环境,其中J-Link和OpenOCD下载器应使用Eclipse环境,而CK-Link下载器应使用CDK环境(兼容性较好,推荐)。硬件连接和开发环境的配置可以参考如下页面:
http://bouffalolab.gitee.io/bl_mcu_sdk/get_started/index.html
本教程使用的是CDK+CK-Link进行开发,后续的相关示例也会以CDK为例。
接下来还需要基于bl_mcu_sdk建立新的工程目录,Eclipse环境和CDK环境分别可以参考以下两个页面:
http://bouffalolab.gitee.io/bl_mcu_sdk/get_started/cmake_quick_start.html
http://bouffalolab.gitee.io/bl_mcu_sdk/get_started/cdk_new_project_quick_start.html
以CDK为例,新建at_control工程,在开发环境中打开工程,并完成基本配置后,目录应如下所示:

1.3 关于几个容易遗漏的要点
- 工程中默认使用了bl706_iot作为目标型号,此处需要在Compiler选项中将其改为bl706_avb(此处以CDK为例,Eclipse同理):

BL706AVB开发板需要连接FUNC1跳线帽才能够使用下载器进行烧录和调试,否则只能够通过UART0进行烧录,且无法使用调试功能。
若需要通过调试器为开发板供电,需要连接调试器转接板上的3V3_JUMP跳线帽(最上方的跳线,如果没有这个跳线帽则说明已经默认连接好了)。此外可以通过转接板上的5V接口给BL604开发板供电(图中杜邦线连接的位置)。

2 串口的配置和使用
2.1 第二串口的配置
BL706AVB开发板默认只启用了一个串口UART0用于程序的烧录和调试,而为了通过AT指令控制BL604开发板,我们还需要将另一个串口UART1也打开,并通过跳线与BL604的AT端口相连。
根据开发版型号,在bl_mcu_sdk\bsp\board\bl702\bl706_avb\目录下,找到pinmux_config.h文件,设置开发板的引脚复用功能。文件中列出的所有引脚(除了已经被用于UART0的引脚之外)都可以被设置为UART1的TX/RX,但是需要注意对应的引脚在开发板上有没有引出插口。本次教程选用了GPIO10/19作为UART1的TX/RX,在开发板上对应丝印DO/DI。



除了引脚配置之外,我们还需要在同一个目录下的peripheral_config.h文件中配置UART1的工作参数,将其波特率修改为115200:

2.2 串口的使用
在bl_mcu_sdk中,芯片上的所有外设都被抽象成了设备(device),具体的使用思路可以参考以下页面,而我们只需要对UART所对应的设备进行操作即可:
http://bouffalolab.gitee.io/bl_mcu_sdk/api_reference/api_overview.html
在main函数中首先完成平台初始化,其中就包括了作为debug端口的UART0的初始化工作,以及UART0的启动流程:

然后手动为UART1也完成初始化和设备注册:

然后我们就能够通过device_find函数获取两个串口的设备句柄了:

随后通过device_open启动UART1,再为两个串口分别注册中断函数以及配置中断类型:

到此,我们就完成了BL706开发板上两个串口的配置了。
在使用时,UART的数据接收通过中断回调函数完成,而数据发送则通过device_write函数完成。下面这一例子是UART0的中断回调函数,其功能就是通过debug(UART0)端口接收数据,并将该段数据通过UART1发送,相当于完成了串口的桥接功能。其中dev为设备句柄,args为指向串口数据的指针,而size则是数据的长度:

3 AT服务的使用
完成串口的配置和初始化后,我们就能够通过UART1连接BL604上的AT服务了。相关AT指令的列表可以在这个页面查看:
https://dev.bouffalolab.com/media/doc/602/at_command/html/content/AT.html#
需要注意的是,每一条AT指令都以大写的”AT”开头,以换行回车符”\r\n”结尾,因此在指令的内容(例如需要发送的数据)中需要严格避免使用”\r\n”,同时尽可能避开”AT”的使用,否则会导致AT指令的解析出错。
3.1 AT指令及响应的存储
为了方便AT指令的存储、编辑和调用,以及AT响应的接收,我们首先设置两段缓存,用于保存当前的AT指令以及响应数据:

然后在UART1的中断回调函数中,将接收到的数据利用strncat函数保存至AT_rx缓存。使用字符串连接函数strncat而不是内存复制函数memcpy的原因是为了防止后续数据将之前尚未处理的数据覆盖掉。

3.2 AT指令的发送
在发送AT指令之前,我们首先需要将AT_tx缓存清空:

随后我们就可以使用snprintf函数将AT指令编辑到缓存当中,该函数的使用方法与printf相同,能够将经过格式化的字符串写入指定的内存地址中:

最后调用device_write函数,将缓存中的内容通过UART1发送至AT服务器:

3.3 AT响应的解析
AT的响应主要包括成功响应"\r\nOK"、错误响应"\r\nERROR:"、事件响应"\r\n+EVT:"三种,以及部分指令中以”+指令名”开头的专有响应,例如UART查询响应"\r\n+UART:"、HTTP响应"\r\n+HTTPC:"等。需要注意的是,每条响应都会以换行回车符"\r\n"开头,这一点可以作为判断响应消息起止的帮助。
由于每条AT指令后都会至少有一条成功响应"\r\nOK"或错误响应"\r\nERROR:",因此接下来就以成功响应和错误响应为例,简单说明一下AT响应的解析方法。
首先我们可以在循环中使用strstr函数判断AT_rx缓存当中是否接收到了所需的相应消息。如果strstr函数检测到对应字符串,则返回字符串所在位置的指针,否则返回空指针。当至少检测到一个非空指针后退出循环。

需要注意的是,除了成功响应和错误响应之外,AT指令还可能在遇到错误时不产生任何响应,因此在等待响应时还需要进行超时判断,若经过一定时间后仍然没有收到响应,则判断为超时并退出循环。对应代码修改如下:

在检测循环结束后,我们可以根据是否检出对应的响应字符串来判断指令的成功与否:

其中,当检测到成功响应"\r\nOK"后,使用memmove函数删去成功响应及之前的内容,并返回0;当检测到错误响应时,使用memset函数清空AT_rx缓存并返回1;如果没有检测到任何响应,则判断为超时并返回-1。
我们可以将上述的检测流程单独封装成一个新的函数:

同理,如果需要检测事件响应"\r\n+EVT:",也可以使用类似的思路,只是返回对应的事件编号(利用atoi函数将编号对应的字符串转换为整数)或超时错误。

4 Wi-Fi网络连接
接下来我们就可以利用上述成果将BL604开发板连接到Wi-Fi网络,并访问互联网了。
首先等待1s,随后发送一条”AT\r\n”指令并等待其相应,以确认AT服务已经启动:

然后关闭Wi-Fi自动连接功能,并重启AT服务。重启AT服务是为了避免在BL706启动之前,AT服务当中可能已经存在的错误影响之后程序的运行,而关闭Wi-Fi自动连接是为了阻止重启后BL604使用可能存在错误的Wi-Fi参数自动连接,从而导致产生更多的错误。

随后再次等待AT服务的启动:

然后再次显式地断开Wi-Fi连接(并不是很清楚为什么需要这么做,但是如果跳过了这一步会导致Wi-Fi连接超时失败):

接着将Wi-Fi模式设置为station模式,让BL604作为联网设备接入无线接入点:

配置Wi-Fi名称和密码:

最后等待Wi-Fi连接成功的事件:

注意在接收到成功事件”\r\n+EVT:0”之后还需要额外等待数秒的时间,因为设备连接到无线AP(例如无线路由器)并不代表设备连接上了互联网,通常路由器还需要1-2s完成路由设置等工作之后,才会将新的设备接入外网。
5 HTTP数据的获取(HTTP GET)
连接上互联网后,我们就可以通过HTTP协议获取API的返回数据了。教程中以高德天气的API为例获取了Json格式的天气数据,该部分的具体操作可以根据实际需求进行修改。
首先在主循环中发送HTTP请求,并等待数据接收:

需要注意的是,对于HTTP请求来说,必须先获得数据返回响应"\r\n+HTTPC:",再获得成功响应"\r\nOK"才算作指令完成,此处需要注意判断条件的修改,以及成功响应"\r\nOK"的字符串搜索范围。完整的响应数据如下:

在完成数据接收的等待之后,根据两条必须的响应是否存在,判断数据是否成功接收,如果成功接收则进行数据处理的步骤:

如果失败,则清除接收缓存并等待重试:

HTTP数据的返回包含两个部分,首先是返回数据的长度,其次才是数据本身,只有二者正确对应才能确定数据的有效性。因此接下来就需要对数据的实际长度进行判断,并与声明的数据长度进行对比。
由于接收的数据是Json格式,因此只需要在数据返回响应"\r\n+HTTPC:"之后,利用strchr函数找到第一个左大括号“{”,以及利用strrchr函数找到最后一个右大括号“}”,即可确认全部数据内容,然后根据这一对大括号的位置就能够得到数据的实际长度:

然后判断数据的实际长度和声明长度是否相符,如果二者相符,则将对应的Json结构复制到单独的一个Json缓存当中,并清除成功响应"\r\nOK",否则提示数据出错并等待重试:

6 JSON数据的处理
JSON(JavaScript Object Notation)是一种轻量级的资料交换格式。其内容顺序无关,由属性和值所组成,因此具有易于阅读和处理的优势。我们可以使用cJSON库来对Json数据进行解析以及处理,对应的源码可以在这里获取:
https://github.com/DaveGamble/cJSON
在上一部分当中,我们已经把Json数据保存在了一个单独的json_buff当中,因此现在只需要把整个缓存交给cJSON_Parse函数就能够完成解析,并获得返回的链表表头:

如果返回的表头指针有效,则说明Json解析成功,接下来我们就只需要根据API中Json数据的结构依次提取所需的数据子项即可:

在此基础上,我们还可以新建一个Json结构,并将解析得到的数据按需求填入其中,得到一个新的Json数据体,并通过cJSON_PrintUnformatted函数将新的Json数据打印成字符串放回json_buff当中。需要注意的是,由于新的Json数据需要通过AT指令输出,因此此处必须使用无格式的cJSON_PrintUnformatted函数,而不是带有缩进和换行的cJSON_Print函数,否则会导致AT指令被打断从而出错。

最后在用完Json结构之后不要忘记删除,避免出现爆内存的情况。删除操作中只需要删除 表头即可,链表中的其他部分会被该函数自动删除。

7 HTTP数据的发送(HTTP POST)
最后只需要再次调用HTTP请求,就能够把新生成的Json数据通过HTTP协议发送到服务器上了。

HTTP POST请求的接收有多种方式,这里就以NGINX为例简单展示一下将POST数据保存至log文件当中的方法。
首先在NGINX配置文件中的http域中添加一个自定义的log格式,此处命名为recording,其中内容可以增减,但是至少需要包含$request_body项(即POST的内容):

然后在server域当中添加access_log选项,自动将每次访问记录保存至指定文件:

最后reload NGINX服务以加载新的配置文件,当程序运行一段时间后再访问log文件,就可以在里面看到定时发送的天气数据了。

需要注意的是,由于数据是经过HTTP传输的,log文件中记录的POST内容经过了UTF-8编码以及Unicode escape编码,必须进行两次解码才能正确阅读。我们可以用一个简单的Python程序(或者其他你觉得好用的手段)将log文件进行解码:


以上就是本次教程的全部内容了~