这是一个 AT 指令驱动,支持无限多条指令,每条指令可以有无限多个参数。
AT 指令简介
AT指令常用格式为:AT+CMD=<xxx>[,<xxx>,<xxx>]
AT指令以AT开始,以\r或者\r\n结尾,参数之间使用,隔开,字符串参数使用双引号””包裹,整形参数不适用双引号。尖括号<xxx>中的名称xxx在AT里是一个语法元素,要求必须指定。尖括号本身不会出现在命令行里。中括号[xxx]中的名称xxx在AT里是一个语法元素,表示可选择指定。中括号本身不出现在命令行里。
AT 指令有 4 种格式:
1、查询指令(AT+<x>=?):测试指令类似于命令行里的help指令,用于提供该命令的使用信息,以及命令参数的取值范围。
2、读取指令(AT+<x>?):用于查询该指令对应功能的当前值。
3、设置指令(AT+<x>=<…>):AT+<x>=<…>
4、执行命令(AT+<x>):执行相关操作。
注意:不是每条指令都支持这 4 种格式。
本驱动支持的参数
本驱动支持有符号整型、无符号整型、浮点型和字符串型四种类型的参数。指令名字不区分大小写,指令参数严格区分大小写。
1、有符号整型:有符号整型参数具体为 32 位有符号数,可以带正负号,支持 10 进制和 16 进制,16 进制数据必须以“0x”或“0X”开始,没有0x”或“0X”的整型参数默认为 10 进制数。
2、无符号整型:无符号整型参数具体为 32 位无符号数,支持 10 进制和 16 进制,16 进制数据必须以“0x”或“0X”开始,没有0x”或“0X”的整型参数默认为 10 进制数。
3、浮点型参数:浮点型参数具体为64位双精度浮点型,可以带正负号。
4、字符串型参数支持带引号(””)和不带引号,规定:字符串中带英文逗号(,)一定要使用引号将其包含起来,不允许出现字符串中的字符串,不允许字符串参数中含回车换行。
本驱动的命令结束符
本驱动不强制要求必须要有结束符,即指令最后没有回车换行也是可以的,但没有结束符的指令必须要间隔 10ms 以上再发下一条指令,即上一条指令结束到发送下一条指令之间必须间隔 10ms 以上。
有回车换行的指令处理速度会比较快。
错误码
AT 指令执行出错会返回错误码
1、ERROR(1):参数数量不对。
2、ERROR(2):参数类型对不上,例如整型参数中混入了其它字符。
3、ERROR(3):不是 AT 指令,该指令不是以 AT 做为开头。
4、ERROR(4):不支持的指令,该指令未注册。
5、ERROR(5):指令格式错误,例如多了空格等。
6、ERROR(6):参数范围出错。
7、ERROR(7):指令无效,例如该指令已经注册但操作非法,例如指令不支持读取,但用户执行了读取操作。
8、ERROR(8):指令处理失败。
移植
本驱动移植要完成两个函数,分别是 ATCmdGetOneByte 和 ATCmdStringOut,这两个函数均在源码,即 ATCmd.c 中定义。
ATCmdGetOneByte 函数用于获取 1 字节的输入数据,若读取成功则返回 AT_ERROR_NONE,读取失败返回 AT_ERROR_FAIL,如下所示。
EnumATErrorType ATCmdGetOneByte(char* byte)
{
//定义一个char型的局部变量
char uData;
//尝试从串口里读取 1 字节数据,读取成功返回 0
if(0 == ReadUARTOneByte(&uData))
{
//输出读到的字节数据
*byte = uData;
//返回 AT_ERROR_NONE,表示读取成功
return AT_ERROR_NONE;
}
//读取失败
else
{
//返回 AT_ERROR_FAIL,表示读取失败
return AT_ERROR_FAIL;
}
}
ATCmdStringOut 函数用于输出字符串,可以用简单的 printf 函数实现,如下所示。当然也可以使用写串口函数,直接将数据发送出去,这样效率更高。
void ATCmdStringOut(char* string)
{
printf("%s", string);
}
最后,每隔 1ms 调用一次 AT 指令轮询函数 ATCmdPole 即可,接收到完整的指令后 ATCmd 模块会尝试解析命令,获取命令形参,并处理命令。
ATCmdPole 函数收到命令结束符,即回车换行后,会立即解析当前指令,这也就是为什么字符串参数中要求不得带回车换行的原因,因为字符串参数里的回车换行会被误认为是指令的结束符。对于不带回车换行结尾的指令,ATCmdPole 开始接收指令后,如果连续 10ms 时间内没有接收到任何数据,就会尝试解析命令。所以使用驱动时,如果指令不带回车换行结尾的话,前后两条指令必须间隔 10ms 以上。有回车换行结尾的指令会处理的更快。
驱动的使用
1、初始化
推荐驱动使用之前要先通过 InitATCmd 函数初始化。ATCmd 模块的初始化是非必要的,注册用户 AT 指令时,会先校验 ATCmd 有没有被预先初始化,如果发现本驱动未被初始化,那么会先执行初始化,然后再注册指令。
//初始化软件
void InitSoftWare(void)
{
InitATCmd(); //初始化 AT 指令模块
}
2、指令的注册
本驱动以注册的方式管理所有指令。实际上,初始化本驱动时,InitATCmd 函数已经注册了“AT+LIST”、“AT+SEARCH”、“AT+WRITE”和“AT+READ”指令,“AT+LIST”指令用于罗列已经注册的所有指令,“AT+SEARCH”指令用于通过关键字查找指令,“AT+WRITE”指令用于向某一地址写入特定值,“AT+READ”指令用于读取某一地址的内容。
指令通过 ATCmdRegister 函数实现,如下所示。示例中向 ATCmd 模块注册了“AT+LED1”指令,用来控制LED1。该指令的回调函数为 ATCmdLed1,支持查询、读取、写入指令,不支持执行指令。
ATCmd 模块使用链表的方式管理所有的 AT 指令,因此用户可以注册无限多的 AT 指令。命令注册时,会按照 ASCII 码顺序将指令编排,以便于“AT+LIST”指令按照 ASCII 码顺序将所有指令罗列出来。
注意:指令的控制块必须是静态变量,一个 AT 指令控制块唯一对应一条 AT 指令。ATCmdRegister 函数会校验该指令控制块有没有被注册过,如果有发现指令控制块被重复注册,就会返回 AT_ERROR_FAIL,表示指令注册失败。同时 ATCmdRegister 函数还会校验该指令名有没有被注册过,如果有重复注册,也一并返回错误。
//初始化LED模块
void InitLED(void)
{
//创建一个 AT 指令控制块,注意该控制块必须是静态的,里边包含了 AT 指令的所有信息
static StructATCmdTcb s_structATCmdLed1Tcb;
//注册 AT 指令 "AT+LED1",用于控制 LED1 的亮灭和查询 LED1 的状态
//该指令支持查询指令、读取指令和设置指令,不支持执行指令
//该指令的回调函数(处理函数)为 ATCmdLed1,需要用户自己实现里边的内部逻辑
ATCmdRegister(&s_structATCmdLed1Tcb, "LED1", ATCmdLed1, AT_CMD_INFO | AT_CMD_READ | AT_CMD_WRITE);
}
3、参数的添加
指令参数的注册通过 ATCmdAddParam 函数实现,如下所示。示例中向“AT+LED1”指令添加了“state”参数,用来控制LED的亮灭。“state”参数是一个字符串型参数,且为必选参数。ATCmdAddParam 函数第三个参数为参数类型,可以是 AT_PARAM_TYPE_INT、AT_PARAM_TYPE_FLOAT、AT_PARAM_TYPE_STRING 或 AT_PARAM_TYPE_UINT,分别表示该参数是 32 位有符号整形、64 位双精度浮点型、字符串型和 32 位无符号型。ATCmdAddParam 函数最后一个参数表示该参数是否可选,为 0 表示该参数是必选的,为 1 则表示该参数是可选的。
ATCmd 模块使用链表的方式管理指令的所有参数,所有用户可以为指令添加无限多的参数,参数可以是可选的和必选的,但是必选参数必须是在可选参数之前。一个参数控制块唯一对应一条参数,不能跨指令共享。
注意:参数的控制块必须是静态变量。如果一条指令只有一个形参,那么该形参必须是必选的。
//初始化LED模块
void InitLED(void)
{
//创建一个 AT 指令控制块,注意该控制块必须是静态的,里边包含了 AT 指令的所有信息
static StructATCmdTcb s_structATCmdLed1Tcb;
//创建一个参数控制块,注意该控制块必须是静态的,一个参数控制块唯一对应一条参数,不能跨指令共享
static StructATParam s_structATCmdLed1Param;
//注册 AT 指令 "AT+LED1",用于控制 LED1 的亮灭和查询 LED1 的状态
//该指令支持查询指令、读取指令和设置指令,不支持执行指令
//该指令的回调函数(处理函数)为 ATCmdLed1,需要用户自己实现里边的内部逻辑
ATCmdRegister(&s_structATCmdLed1Tcb, "LED1", ATCmdLed1, AT_CMD_INFO | AT_CMD_READ | AT_CMD_WRITE);
//为 "AT+LED1" 指令添加 "state" 参数,该参数是字符串型的,且为必选参数
ATCmdAddParam(&s_structATCmdLed1Tcb, &s_structATCmdLed1Param, AT_PARAM_TYPE_STRING, "state", 0);
}
4、命令处理
命令的处理如下所示。回调函数会传回 AT 指令控制块的首地址,因此也不用刻意记下 AT 指令控制块的名字。用户在回调函数中首先要判断这是哪种类型的指令,如果是查询指令,则需要输出执行的相关信息,等等。不支持的指令类型请返回 AT_ERROR_INVALID。
EnumATErrorType ATCmdLed1(StructATCmdTcb* tcb)
{
//定义了一个局部变量 param,用来获取输入参数
//因为 "AT+LED1" 的参数为字符串型,所以 param 要定义为 char*
char* param;
//查询指令
if (AT_CMD_INFO == ATCmdGetCmdType(tcb))
{
ATCmdStringOut("+LED1=<state>\r\n"); //输出指令名及其参数
ATCmdStringOut("+LED1: state(string) is ON or OFF\r\n"); //输出参数取值范围
}
//读取指令,读取 LED 输出状态
else if (AT_CMD_READ == ATCmdGetCmdType(tcb))
{
//读取 LED 引脚的输入电平,如果为 1 表示 LED 正在被点亮,此时要输出 "+LED1: ON\r\n",表示 LED 处于点亮状态
if(1 == GPIO_ReadInputDataBit(GPIOH, GPIO_Pin_12))
{
ATCmdStringOut("+LED1: ON\r\n");
}
//LED 引脚输入为 0,此时要输出 "+LED1: OFF\r\n",表示 LED 处于熄灭状态
else
{
ATCmdStringOut("+LED1: OFF\r\n");
}
}
//写入命令
else if(AT_CMD_WRITE == ATCmdGetCmdType(tcb))
{
//查找并获取 "state" 参数到 param
//如果参数是字符串型,ATCmdGetParamValueWithTcb 将输出字符串首地址到 param
//否则会将有符号整形、无符号整形或浮点型参数转换结果输出到 param
if(AT_ERROR_NONE == ATCmdGetParamValueWithTcb(tcb, "state", ¶m))
{
//字符串比较,看看是否输入了 "ON"
if(AT_ERROR_NONE == ATCmdIsStringEqual(param, "ON"))
{
gpio_bit_set(GPIOH, GPIO_Pin_12); //点亮 LED
}
//字符串比较,看看是否输入了 "OFF"
else if(AT_ERROR_NONE == ATCmdIsStringEqual(param, "OFF"))
{
gpio_bit_reset(GPIOH, GPIO_Pin_12); //熄灭 LED
}
//输入不是 "ON" 或 "OFF",表示字符串参数输入有误
else
{
return AT_ERROR_PARAM_SCALE;
}
}
//该参数获取失败,说明参数解析出错,需要返回 AT_ERROR_PARAN_NUM 表示参数数量出错
else
{
return AT_ERROR_PARAN_NUM;
}
}
//执行命令
else if(AT_CMD_EXECUTE == ATCmdGetCmdType(tcb))
{
return AT_ERROR_INVALID;
}
return AT_ERROR_NONE;
}
获取指令参数时可以使用 ATCmdGetParamValueWithTcb 函数快速查找和获取,该指令会将参数解析结果输出到指定地址,如果是字符串型参数,那么只会输出字符串首地址,而不是将整个字符串拷贝到指定地址。需要注意,如果参数类型为字符串型,那么 param 变量必须要定义为 char*;如果形参为无符号整型, 那么 param 变量必须要定义为 unsigned int;如果形参为有符号整型, 那么 param 变量必须要定义为 int;如果形参为浮点型, 那么 param 变量必须要定义为 double。
字符串比较时可以选用 ATCmdIsStringEqualWithoutCase 函数。ATCmdIsStringEqualWithoutCase 函数同样用于字符串比较,但忽略了大小写,如此一来就可以不用限制参数输入大小写了。
5、命令使用
命令注册好后就可以使用串口助手发送 “AT+LED1” 控制 LED1 的亮灭和查询 LED1 的状态了。发送 “AT+LED1=?” 表示查询该指令的信息,例如参数数量,参数的取值范围等等;发送 “AT+LED1?” 表示查询当前 LED1 的输出状态;发送 “AT+LED1=ON/OFF” 表示设置 LED1 的输出状态,这里的 “ON/OFF” 表示 “ON” 或 “OFF”;发送 “AT+LED1” 指令后将拒绝执行该指令,并返回 “\r\nERROR(7): invalid cmd\r\n”。
指令名不区分大小写,但参数严格区分大小写。
本驱动中自动添加注册了几条 AT 命令,“AT+LIST”用来罗列所有已注册的 AT 指令,“AT+READ”用来读取特定地址数据,“AT+WRITE”用来往特定地址写入数据,“AT+SEARCH”用来通过关键字搜索相关指令。用户可以根据自己的需要自行删除相应指令。