一、前言
之前在NONOS下完成了对ESP8266的TCP/UDP通信,今天打算通过STM32使用AT指令控制ESP8266模块作为Server端。下文只考虑作为Client端的STM32。
二、本文参考资料
1.https://www.rt-thread.org/document/site/programming-manual/at/at/
2.https://www.rt-thread.org/document/site/application-note/components/at/an0014-at-client/
三、正文部分
3.1 AT 命令简介
AT 命令(AT Commands)最早是由发明拨号调制解调器(MODEM)的贺氏公司(Hayes)为了控制 MODEM 而发明的控制协议。后来随着网络带宽的升级,速度很低的拨号 MODEM 基本退出一般使用市场,但是 AT 命令保留下来。当时主要的移动电话生产厂家共同为 GSM 研制了一整套 AT 命令,用于控制手机的 GSM 模块。AT 命令在此基础上演化并加入 GSM 07.05 标准以及后来的 GSM 07.07 标准,实现比较健全的标准化。
在随后的 GPRS 控制、3G 模块等方面,均采用的 AT 命令来控制,AT 命令逐渐在产品开发中成为实际的标准。如今,AT 命令也广泛的应用于嵌入式开发领域,AT 命令作为主芯片和通讯模块的协议接口,硬件接口一般为串口,这样主控设备可以通过简单的命令和硬件设计完成多种操作。
AT 命令集是一种应用于 AT 服务器(AT Server)与 AT 客户端(AT Client)间的设备连接与数据通信的方式。 其基本结构如下图所示:
1.一般 AT 命令由三个部分组成,分别是:前缀、主体和结束符。其中前缀由字符 AT 构成;主体由命令、参数和可能用到的数据组成;结束符一般为 ("\r\n")。
2.AT 功能的实现需要 AT Server 和 AT Client 两个部分共同完成。
3.AT Server 主要用于接收 AT Client 发送的命令,判断接收的命令及参数格式,并下发对应的响应数据,或者主动下发数据。
4.AT Client 主要用于发送命令、等待 AT Server 响应,并对 AT Server 响应数据或主动发送的数据进行解析处理,获取相关信息。
5.AT Server 和 AT Client 之间支持多种数据通讯的方式(UART、SPI 等),目前最常用的是串口 UART 通讯方式。
6.AT Server 向 AT Client 发送的数据分成两种:响应数据和 URC 数据。
7.响应数据: AT Client 发送命令之后收到的 AT Server 响应状态和信息。
8.URC 数据: AT Server 主动发送给 AT Client 的数据,一般出现在一些特殊的情况,比如 WIFI 连接断开、TCP 接收数据等,这些情况往往需要用户做出相应操作。
随着 AT 命令的逐渐普及,越来越多的嵌入式产品上使用了 AT 命令,AT 命令作为主芯片和通讯模块的协议接口,硬件接口一般为串口,这样主控设备可以通过简单的命令和硬件设计完成多种操作。
虽然 AT 命令已经形成了一定的标准化,但是不同的芯片支持的 AT 命令并没有完全统一,这直接提高了用户使用的复杂性。对于 AT 命令的发送和接收以及数据的解析没有统一的处理方式。并且在使用 AT 设备连接网络时,只能通过命令完成简单的设备连接和数据收发功能,很难做到对上层网络应用接口的适配,不利于产品设备的开发。
为了方便用户使用 AT 命令,简单的适配不同的 AT 模块, RT-Thread 提供了 AT 组件用于 AT 设备的连接和数据通讯。AT 组件的实现包括客户端的和服务器两部分。
以上内容来自RT-Thread官方文档
说白了就是STM32作为Server端向作为Client端的ESP8266请求消息与响应消息
3.2 AT 组件简介
AT 组件是基于 RT-Thread 系统的 AT Server 和 AT Client 的实现,组件完成 AT 命令的发送、命令格式及参数判断、命令的响应、响应数据的接收、响应数据的解析、URC 数据处理等整个 AT 命令数据交互流程。
通过 AT 组件,设备可以作为 AT Client 使用串口连接其他设备发送并接收解析数据,可以作为 AT Server 让其他设备甚至电脑端连接完成发送数据的响应,也可以在本地 shell 启动 CLI 模式使设备同时支持 AT Server 和 AT Client 功能,该模式多用于设备开发调试。
AT 组件资源占用:
AT Client 功能:4.6K ROM 和 2.0K RAM;
AT Server 功能:4.0K ROM 和 2.5K RAM;
AT CLI 功能: 1.5K ROM ,几乎没有使用 RAM。
3.3 AT Client
AT Client 功能主要用于完成 AT 命令的数据收发和解析过程。
3.3.1 AT Client 配置
1.配置串口支持:
2.开启 AT Client 功能:
配置完成,保存并退出配置选项,输入命令 scons --target=mdk5 生成 MDK 工程;
这里有个要注意的地方!打开工程后去Drivers目录下的stm32f4xx_hal_msp.c的HAL_UART_MspInit函数核对一下串口使能的引脚是否正确
AT Client 使用流程大致如下:at_create_resp() 创建响应结构体 —> at_exec_cmd() 发送命令并接收响应 —> at_resp_get_line()/at_resp_parse_line_args() 打印或解析响应数据 —> at_delete_resp() 删除响应结构体。
3.3.2 AT Client 初始化
at_client_init() 函数
完成对 AT Client 设备初始化、AT Client 移植函数的初始化、AT Client 使用的信号量、互斥锁等资源初始化,并创建 at_client 线程用于 AT Client 中数据的接收的解析以及对 URC 数据的处理。
3.3.3 AT Client 数据收发方式
1.at_response结构体
struct at_response
{
/* 响应缓冲区:存放的数据并不是原始响应数据,而是原始响应数据去除结束符("\r\n")的数据,buf 中每行数据以 '\0' 分割,方便按行获取数据。 */char *buf;/* 本次响应最大支持的接收数据的长度 */rt_size_t buf_size;/* 用户自定义的本次响应数据需要接收的行数,如果没有响应行数限定需求,可以置为 0。* == 0: the response data will auto return when received 'OK' or 'ERROR'* != 0: the response data will return when received setting lines number data */rt_size_t line_num;/* 记录本次 响应 数据总行数 */rt_size_t line_counts;/* 本次响应数据最大响应时间 */rt_int32_t timeout;
};
typedef struct at_response *at_response_t;
2.创建响应结构体
at_response_t at_create_resp(rt_size_t buf_size, rt_size_t line_num, rt_int32_t timeout);
该函数用于创建自定义的响应数据接收结构,用于后面接收并解析发送命令响应数据。
line_num:本次响应需要返回数据的行数,行数是以标准结束符划分
若为 0 ,则接收到 “OK” 或 “ERROR” 数据后结束本次响应接收
若大于 0,接收完当前设置行号的数据后返回成功
函数执行成功,返回指向响应结构体的指针;失败,内存不足
3.发送命令并接收响应
rt_err_t at_exec_cmd(at_response_t resp, const char *cmd_expr, ...);
AT 命令的使用匹配表达式的可变参输入,输入命令的结尾不需要添加命令结束符。
当 at_exec_cmd 函数传入 resp 为 NULL 时说明本次发送数据不考虑处理响应数据直接返回结果。
每次调用完at_exec_cmd()后都会更新at_response结构体内的内容
例子:at_exec_cmd(resp, “ATE0”);
函数执行成功,返回>=0;失败,返回-1;失败,接收响应超时,返回-2
4.删除响应结构体
void at_delete_resp(at_response_t resp);
该函数用于删除创建的响应结构体对象,一般与at_create_resp创建函数成对出现。
3.3.4 AT Client 数据解析方式
AT Client 中数据的解析提供自定义解析表达式的解析形式,其解析语法使用标准的 sscanf 解析语法。开发者可以通过自定义数据解析表达式回去响应数据中有用信息,前提是开发者需要提前查看相关手册了解 AT Client 连接的 AT Server 设备响应数据的基本格式。
1.获取指定行号的响应数据
const char *at_resp_get_line(at_response_t resp, rt_size_t resp_line);
resp_line:需要获取数据的行号
函数执行成功,返回对应行号数据的指针;失败,输入行号错误
行号是以标准数据结束符来判断的,上述发送和接收函数 at_exec_cmd 已经对响应数据的数据和行号进行记录处理存放于 resp 响应结构体中,这里可以直接获取对应行号的数据信息。
2.获取指定关键字的响应数据
const char *at_resp_get_line_by_kw(at_response_t resp, const char *keyword);
keyword:关键字信息
函数执行成功,返回对应行号数据的指针;失败,未找到关键字信息
3.解析指定关键字行的响应数据
int at_resp_parse_line_args(at_response_t resp, rt_size_t resp_line, const char *resp_expr, ...);
resp_line 需要解析数据的行号,行号从 1 开始计数
函数执行成功,返回解析成功的参数个数(>0); 失败,无匹参配数解析表达式的参数(= 0); 失败,参数解析错误(= -1)
4.解析指定行号的响应数据
int at_resp_parse_line_args_by_kw(at_response_t resp, const char *keyword, const char *resp_expr, ...);
3.3.5 AT Client 示例
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>#include <stdlib.h>
#include <string.h>#include <at.h> /* AT 组件头文件 *//* defined the LED0 pin: PB1 */
#define LED0_PIN GET_PIN(B, 1)/* 设置当前 AT 客户端最大支持的一次接收数据的长度 */
#define AT_CLIENT_RECV_BUFF_LEN 512int main(void)
{
int count = 1;/* set LED0 pin mode to output */rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);at_client_init("uart2", AT_CLIENT_RECV_BUFF_LEN);at_response_t resp =RT_NULL;int result = 0;/* 创建响应结构体,设置最大支持响应数据长度为 256 字节(最大响应长度用户根据实际需求自定义),响应数据行数无限制,超时时间为 5 秒 */resp = at_create_resp(256, 0, rt_tick_from_millisecond(5000));if (resp == RT_NULL){
rt_kprintf("No memory for response structure!");return -2;}/* 关闭回显功能 */at_exec_cmd(resp, "ATE0");/* AT Client 发送查询 IP 地址命令并接收 AT Server 响应 *//* 响应数据及信息存放在 resp 结构体中 */result = at_exec_cmd(resp, "AT+CIFSR");if (result != RT_EOK){
rt_kprintf("AT client send commands failed or return response error!");goto __exit;}/* 按行数循环打印接收到的响应数据 */{
const char *line_buffer = RT_NULL;for(rt_size_t line_num = 1; line_num <= resp->line_counts; line_num++){
if((line_buffer = at_resp_get_line(resp, line_num)) != RT_NULL){
rt_kprintf("line %d buffer : %s", line_num, line_buffer);}else{
rt_kprintf("Parse line buffer error!");}}}/* 按自定义表达式(sscanf 解析方式)解析数据,得到对应数据 */{
char resp_arg[AT_CMD_MAX_LEN] = {
0 };/* 自定义数据解析表达式 ,用于解析两双引号之间字符串信息 */const char * resp_expr = "%*[^\"]\"%[^\"]\"";rt_kprintf(" Parse arguments");/* 解析响应数据中第一行数据,得到对应 IP 地址 */if (at_resp_parse_line_args(resp, 1, resp_expr, resp_arg) == 1){
rt_kprintf("Station IP : %s", resp_arg);memset(resp_arg, 0x00, AT_CMD_MAX_LEN);}else{
rt_kprintf("Parse error, current line buff : %s", at_resp_get_line(resp, 4));}/* 解析响应数据中第二行数据,得到对应 MAC 地址 */if (at_resp_parse_line_args(resp, 2, resp_expr, resp_arg) == 1){
rt_kprintf("Station MAC : %s", resp_arg);}else{
rt_kprintf("Parse error, current line buff : %s", at_resp_get_line(resp, 5));goto __exit;}}
__exit:if(resp){
/* 删除 resp 结构体 */at_delete_resp(resp);}//系统运行程序while (count++){
rt_pin_write(LED0_PIN, PIN_HIGH);rt_thread_mdelay(500);rt_pin_write(LED0_PIN, PIN_LOW);rt_thread_mdelay(500);}return RT_EOK;
}
以上代码由官方文档里提供的at_sample_client.c修改而成(除去finsh)