也发一个AT解析引擎源码【Yet Another AT Engine】
本帖最后由 armstrong 于 2019-12-10 18:01 编辑坛友之前发过一个AT解析代码,不是很全;而且不能很好的解析传入的参数,我发一个很好用的:
atengine.h
/*
这是作者声明信息:反正源码给你了,随便你怎么改!
作者: 洪旭耀
QQ: 26750452
*/
#ifndef __AT_ENGINE_H__
#define __AT_ENGINE_H__
////////////////////////////////////////////////////////////////////////////////
#ifdef __cplusplus
extern "C"{
#endif
void ATE_Exec(void);
/* 以下函数需要移植代码来完成适配 */
void ATE__ConsolePurgeRx(void); // 清空接收缓冲区
intATE__ConsoleRead(void); // 从接收缓冲区读取一个字节,返回-1表示EOF
void ATE__ConsoleWrite(char const* p, unsigned num);// 发送一串数据到AT输出接口
void ATE__ConsoleFlushTx(void); // 确保发送缓冲区已空
void ATE__PostInvoke(void (*pf)(void*), void* arg);
#ifdef __cplusplus
}
#endif
////////////////////////////////////////////////////////////////////////////////
#endif /* __AT_ENGINE_H__ */
atengine.c
/*
这是作者声明信息:反正源码给你了,随便你怎么改!
作者: 洪旭耀
QQ: 26750452
*/
#include "atengine.h"
#define JSMN_STATIC
#define JSMN_STRICT
#include "jsmn.h"
#include <stdarg.h>
////////////////////////////////////////////////////////////////////////////////
#ifndef CRLF
#define CRLF "\r\n"
#endif
enum {
ATE_ERR_NONE = 0,
ATE_ERR_OOM, /* Out Of Memory */
ATE_ERR_BADCMD, /* Bad Command */
ATE_ERR_BADARG, /* Bad Argument */
};
typedef struct {
void (*handler)(int argc, char const* argv[]);
char const* name;
} ATCmdItem;
static struct {
unsigned atCmdInd;
char atCmdLine;
jsmntok_t tokens;
char const* arglist;
__IO bool_t echoEnable;
__IO uint8_t errorCode;
} cobj = {
.echoEnable = 1,
};
static void ATE_Echo(int c);
static void ATE_SendResult(void);
static void ATE_ExecuteCommand(void);
static void ATE_SendString(char const s[]);
static void ATE_SendStringFormat(char const* fmt, ...);
static void ATE_ParseCommand(char cl[]);
static bool_t FTK_IsHeadOfString(char const* strMain, char const* strSub);
static bool_t FTK_IsHeadOfStringEx(char const* strMain, char const* strSub, int* pLen);
////////////////////////////////////////////////////////////////////////////////
//| |
//| 函数名称 |: ATE_Exec
//| 功能描述 |: 执行ATE输入,如果收到CRLF就解析并执行
//| |:
//| 参数列表 |:
//| |:
//| 返 回 |:
//| |:
//| 备注信息 |:
//| |:
////////////////////////////////////////////////////////////////////////////////
void ATE_Exec(void)
{
unsigned i;
int ch;
while ((ch = ATE__ConsoleRead()) > 0) {
cobj.atCmdLine = ch;
ATE_Echo(ch);
if (cobj.atCmdInd < 2) {
continue;
}
i = cobj.atCmdInd;
if (cobj.atCmdLine == '\r' && cobj.atCmdLine == '\n') {
// 收到AT命令结束符CRLF
if (cobj.errorCode == ATE_ERR_NONE) {
/*
把CRLF都修改为0,很重要!
因为后续代码里会利用这个 */
cobj.atCmdLine = 0;
cobj.atCmdLine = 0;
ATE_ExecuteCommand();
}
ATE_SendResult();
ATE__ConsolePurgeRx();
cobj.errorCode = ATE_ERR_NONE;
cobj.atCmdInd = 0;
return;
} else if (i == sizeof(cobj.atCmdLine)) {
// AT命令缓冲区已满却还没遇到CRLF,记录错误!
cobj.errorCode = ATE_ERR_OOM;
cobj.atCmdInd = 0;
continue;
}
}
}
static void ATE_SendString(char const s[])
{
size_t len = strlen(s);
ATE__ConsoleFlushTx();
ATE__ConsoleWrite(s, len);
}
static void ATE_SendStringFormat(char const* fmt, ...)
{
static char sbuffer;
va_list arp;
int len;
ATE__ConsoleFlushTx();
va_start(arp, fmt);
len = vsnprintf(sbuffer, sizeof(sbuffer) - 1, fmt, arp);
va_end(arp);
if (len > 0) {
ATE__ConsoleWrite(sbuffer, len);
}
}
static void ATE_Echo(int c)
{
static char cbuf;
if (cobj.echoEnable) {
cbuf = c;
ATE__ConsoleFlushTx();
ATE__ConsoleWrite(cbuf, 1);
}
}
static void ATE_SendResult(void)
{
if (cobj.errorCode == ATE_ERR_NONE) {
ATE_SendString("OK"CRLF);
} else {
ATE_SendString("ERROR"CRLF);
}
}
static void ATE_ExecuteCommand(void)
{
char* pCmdLine = cobj.atCmdLine;
if (FTK_IsHeadOfString(pCmdLine, "AT+")) {
pCmdLine += 3;
ATE_ParseCommand(pCmdLine);
} else if (!strcmp(pCmdLine, "ATE1")) {
cobj.echoEnable = TRUE;
} else if (!strcmp(pCmdLine, "ATE0")) {
cobj.echoEnable = FALSE;
} else if (!strcmp(pCmdLine, "AT")) {
} else {
cobj.errorCode = ATE_ERR_BADCMD;
}
}
bool_t FTK_IsHeadOfString(char const* strMain, char const* strSub)
{
return FTK_IsHeadOfStringEx(strMain, strSub, NULL);
}
bool_t FTK_IsHeadOfStringEx(char const* strMain, char const* strSub, int* pLen)
{
unsigned i;
for (i = 0; strSub; i++) {
if (strMain != strSub) {
return false;
}
}
if (pLen) {
*pLen = i;
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
#define DECL_ATCMD(nam) static void ATE_CMD_##nam(int argc, char const* argv[])
#define REG_ATCMD(nam){ .name = #nam, .handler = ATE_CMD_##nam }
////////////////////////////////////////////////////////////////////////////////
DECL_ATCMD(HELLO);
static ATCmdItem const atCmdLst[] = {
REG_ATCMD(HELLO),
{}
};
static void ATE_ParseCommand(char cl[])
{
static char arg0 = "=";
ATCmdItem const* cmd;
jsmn_parser parser;
jsmntok_t* tok;
int len;
for (cmd = atCmdLst; cmd->handler; cmd++) {
if (FTK_IsHeadOfStringEx(cl, cmd->name, &len)) {
cl += len;
if (cl == '\0' || cl == '?') {
cobj.arglist = cl;
cmd->handler(1, cobj.arglist);
return;
} else if (cl == '=') {
len = strlen(cl);
cl = '[';
cl = ']';
jsmn_init(&parser);
len = jsmn_parse(&parser, cl, len, cobj.tokens, COUNTOF(cobj.tokens));
if (len <= 0) {
// 参数解析失败,返回错误!
cobj.errorCode = ATE_ERR_BADARG;
return;
} else {
cobj.arglist = arg0;
for (int i = 1; i < len; i++) {
tok = &cobj.tokens;
cobj.arglist = cl + tok->start;
cl = 0;
}
cmd->handler(len, cobj.arglist);
}
return;
}
}
}
cobj.errorCode = ATE_ERR_BADCMD;
}
DECL_ATCMD(HELLO)
{
ATE_SendString("The ArgList:"CRLF);
for (int i = 0; i < argc; i++) {
ATE_SendStringFormat("[%2u]: %s"CRLF, i, argv);
}
}
////////////////////////////////////////////////////////////////////////////////
Win32 Console 演示,串口输入输出适配接口也可参考里面的代码:
编辑理由:更新了代码。 本帖最后由 armstrong 于 2019-12-10 12:16 编辑
添加AT命令的方式很简单,参考atengine.c文件下部的代码,比如加入XXX命令就如下:
DECL_ATCMD(HELLO);
DECL_ATCMD(XXX);
static ATCmdItem const atCmdLst[] = {
REG_ATCMD(HELLO),
REG_ATCMD(XXX),
{}
};
DECL_ATCMD(HELLO)
{
ATE_SendString("The ArgList:"CRLF);
for (int i = 0; i < argc; i++) {
ATE_SendStringFormat("[%2u]: %s"CRLF, i, argv);
}
}
DECL_ATCMD(XXX)
{
// TODO...
}
可能需要你定义的几个宏和类型:
#ifndef __IO
#define __IO volatile
#endif
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
#define true TRUE
#define false FALSE
#define COUNTOF(ar) (sizeof(ar)/sizeof(ar))
typedef uint8_t bool_t;
不错!下载下来仔细看看。 anjiyifan 发表于 2019-12-10 12:03
不错!下载下来仔细看看。
传了个win32命令行的演示【yatengine-win32.zip】,里面有串口收发接口适配的代码,可供参考。
yatengine-win32.zip演示效果:
看起来不错 看起来不错 ; Mark 早想做at指令配置参数了,哈哈 Mark,备用,多谢分享
MARK,肯定会用到 这是我刚刚用yatengine做的“红外学习播放模块”命令,使用很方便:
稍微改了一下win32演示,让ATE__ConsoleWrite函数调用fwrite输出,这样才可以输出非字符数据,这才是ATE__ConsoleWrite定义num参数的初衷。
void ATE__ConsolePurgeRx(void)
{
// Nothing todo on win32
}
intATE__ConsoleRead(void)
{
// win32 console 回车键是'\n',
// 以下逻辑为了在'\n'之前插入'\r'
static int bak = 0;
if (bak) {
int r = bak;
bak = 0;
return r;
}
int ch = getchar();
if (ch == '\n') {
bak = '\n';
ch = '\r';
}
return ch;
}
void ATE__ConsoleWrite(char const* p, unsigned num)
{
fwrite(p, 1, num, stdout);
fflush(stdout);
}
void ATE__ConsoleFlushTx(void)
{
fflush(stdout);
}
不错,准备在项目里面使用看看 感谢分享AT解析,学习了 谢谢楼主分享 下来学习下,谢了。 mark先,用到的时候再研究 效果不错,学习一下 样子不错啊。 mark 刚在找at解析 谢谢分享,markPC端的软件 能弄個stm32 的例程嗎 不错,at解析 AT命令解析引擎,谢谢楼主分享 看到这个帖子想起了以前写过的那个BASIC解释器{:biggrin:} 谢谢楼主分享,应该会用到 感谢分享 竟然看不懂用途,谁来科普下 本帖最后由 armstrong 于 2019-12-11 09:26 编辑
wx-ta 发表于 2019-12-11 09:12
竟然看不懂用途,谁来科普下
AT命令的产品还是很多的,很多行业的成品模块都用AT操纵和配置。
当开发类似ESP8266这样的产品,需要实现AT操作接口就用得上。
yatengine把你登记的命令处理成大家熟知的C项目main入口一样:
void AT_XXX(int argc, char const* argv[])
{
// todo...
}
这样,就不用花费时间考虑怎么设计这么个框架,直接加命令就是了;当然如果硬是想自己设计,那也会给你提供思路。总之是有价值的。
感谢分享, 本帖最后由 armstrong 于 2019-12-11 09:55 编辑
honami520 发表于 2019-12-10 19:00
不错,准备在项目里面使用看看
可以放心应用,我也是用在产品里的。
在RTOS中,可以等待到串口数据到来时才调用ATE_Exec,不需要不停循环。
我的AT引擎专门一个线程,如下内容【仅供参考】:
#include "usrinc.h"
#include "cTinyFIFO.h"
#include "atengine.h"
static U64 __Stack1 MEM_PI_STACK;
static __task void __ThreadService(void);
////////////////////////////////////////////////////////////////////////////////
//| |
//| 函数名称 |: APP_CreateATService
//| 功能描述 |: 创建AT命令交互服务
//| |:
//| 参数列表 |:
//| |:
//| 返 回 |:
//| |:
//| 备注信息 |:
//| |:
////////////////////////////////////////////////////////////////////////////////
void APP_CreateATService(void)
{
OS_TID tid = os_tsk_create_user(
__ThreadService,
TSK_PRIO_NORMAL,
__Stack1,
sizeof(__Stack1));
if (tid == 0) {
DBG_PUTS("AT failed.\n");
sys_suspend();
}
}
////////////////////////////////////////////////////////////////////////////////
//| |
//| 函数名称 |: __ThreadService
//| 功能描述 |: 服务线程主循环
//| |:
//| 参数列表 |:
//| |:
//| 返 回 |:
//| |:
//| 备注信息 |:
//| |:
////////////////////////////////////////////////////////////////////////////////
#define EV_FLAG_TX (0x0001)
#define EV_FLAG_RX (0x0002)
#define EV_FLAG_INVOKE(0x0004)
static struct {
OS_TID serviceTID;
__IO uint8_t bPolling;
uint16_t rxLength;
__IO uint8_t* pRead;
uint8_trxBuffer;
void* invokeQueue;
} cobj;
static TickNodeType ticker;
static InvokeItem invokelist;
static void RX__Poller(void);
static void TX__Signal(void);
static void __ThreadService(void)
{
U16 events;
cobj.rxLength = 0;
cobj.serviceTID = os_tsk_self();
cobj.bPolling = TRUE;
cobj.invokeQueue = FIFO_InvokeItemGetObject();
FIFO_InvokeItemCreate(cobj.invokeQueue, invokelist, COUNTOF(invokelist));
DRV_HOSTCOM_PurgeRxFifo();
DRV_HOSTCOM_RegTxSignal(TX__Signal);
os_evt_set(EV_FLAG_TX, cobj.serviceTID);
ticker.callback = RX__Poller;
TICK_Add(&ticker);
ATE__PostInvoke(0, 0);
while (1) {
if (OS_R_EVT == os_evt_wait_or(EV_FLAG_RX | EV_FLAG_INVOKE, TWAIT_FOREVER)) {
events = os_evt_get();
if (events & EV_FLAG_RX) {
ATE_Exec();
cobj.bPolling = TRUE;
}
if (events & EV_FLAG_INVOKE) {
InvokeItem obj;
while (FIFO_InvokeItemDequeue(cobj.invokeQueue, &obj)) {
if (obj.pf) {
obj.pf(obj.arg);
}
}
}
}
}
}
static void TX__Signal(void)
{
// 该函数被ISR调用,所以必须ISR专用API
isr_evt_set(EV_FLAG_TX, cobj.serviceTID);
}
static void RX__Poller(void)
{
// 该函数被Tick中断调用,所以必须ISR专用API
if (cobj.bPolling && DRV_HOSTCOM_GetRxFifoCount()) {
cobj.bPolling = FALSE;
isr_evt_set(EV_FLAG_RX, cobj.serviceTID);
}
}
void ATE__ConsolePurgeRx(void)
{
DRV_HOSTCOM_PurgeRxFifo();
cobj.rxLength = 0;
}
intATE__ConsoleRead(void)
{
unsigned i;
if (cobj.rxLength == 0) {
i = DRV_HOSTCOM_ReadFifo(cobj.rxBuffer, sizeof(cobj.rxBuffer));
if (i == 0) {
return -1;
}
cobj.pRead = cobj.rxBuffer;
cobj.rxLength = i;
}
i = *cobj.pRead++;
cobj.rxLength--;
return i;
}
void ATE__ConsoleWrite(char const* p, unsigned num)
{
DRV_HOSTCOM_TxStream((uint8_t const*)p, num);
}
void ATE__ConsoleFlushTx(void)
{
while (DRV_HOSTCOM_TxIsBusy()) {
os_evt_wait_or(EV_FLAG_TX, 10);
}
}
void ATE__PostInvoke(void (*pf)(void*), void* arg)
{
InvokeItem obj;
obj.pf = pf;
obj.arg = arg;
if (FIFO_InvokeItemEnqueue(cobj.invokeQueue, &obj)) {
os_evt_set(EV_FLAG_INVOKE, cobj.serviceTID);
}
}
void ATE__PostInvokeISR(void (*pf)(void*), void* arg)
{
InvokeItem obj;
obj.pf = pf;
obj.arg = arg;
if (FIFO_InvokeItemEnqueue(cobj.invokeQueue, &obj)) {
isr_evt_set(EV_FLAG_INVOKE, cobj.serviceTID);
}
}
////////////////////////////////////////////////////////////////////////////////
blueice1108 发表于 2019-12-11 07:19
能弄個stm32 的例程嗎
看31楼回复,这是RTX下使用代码,就差USART驱动没贴出来了。 好东西先收藏 armstrong 发表于 2019-12-11 09:29
看31楼回复,这是RTX下使用代码,就差USART驱动没贴出来了。
可以的
AT 引擎源码
赞1个。。
RTX刚使用了一个项目,感觉现在封装起来的CMSIS不如以前的RTX简洁了,简直是个臃肿的大包裹。
不错,收藏了 本帖最后由 dukelec 于 2019-12-11 19:12 编辑
這個只是做服務器?支持做客戶端嗎?
我表示自己做東西優先選擇二進制協議,字符的太麻煩。
谢谢楼主分享,应该会用到 armstrong 发表于 2019-12-11 09:29
看31楼回复,这是RTX下使用代码,就差USART驱动没贴出来了。
谢谢 最近正好项目要用上~ 谢谢楼主分享刚好公司搞软件的写了一个字符串协议应该能用到 AT感觉好麻烦 mark at解析 AT 服务 指令 mark 万能的坛友,一直想弄一个,今天又拿来主义了~谢过! 解析完的数据也是要遍历么。AT+XXX,XXX那部分 依赖 jsmn 库吗? abutter 发表于 2019-12-17 11:12
依赖 jsmn 库吗?
就一个头文件,已经在zip里了 楼主用的是可变参数,这样程序可靠吗 Mark,备用,多谢分享
备用,多谢分享! Ray______ 发表于 2019-12-17 11:02
解析完的数据也是要遍历么。AT+XXX,XXX那部分
应用代码不需要再遍历了,框架会把AT+XXX=a,b,c,d,...
处理成void XXX_handler(int argc, char const* argv[])
的argc和argv参数传入,代码拿来用即可。 AT 服务 指令 mark
AT 服务 指令 mark , 估计下个项目中 可以用的到了 学习下,一直在找AT解析架构相关的 学习了,也许用得着 学习一下,刚好最近会用到。 不错,收藏了 这个很好啊。 一直想了解 AT 命令如何解析,操作的。想学习一下,竟然今天才看到文章。
收藏学习了!谢谢楼主分享! armstrong 发表于 2019-12-10 17:51
稍微改了一下win32演示,让ATE__ConsoleWrite函数调用fwrite输出,这样才可以输出非字符数据,这才是ATE__C ...
在keil中编译的时候报错 多谢分享,拿来主义了 biu特否!非常好! 感谢分享,AT学习 收藏下,谢谢楼主了 有没有MDK例子?谢谢分享 本帖最后由 oooios 于 2020-9-30 14:47 编辑
你好,麻烦看看这个是怎么会事,谢谢 本帖最后由 oooios 于 2020-9-30 14:47 编辑
以解决;重写了void ATE_Exec1(unsigned char *in,unsigned short len),就可以了;
谢谢分享。
The ArgList:
[ 0]: =
[ 1]: 1
[ 2]: 2
[ 3]: 3
[ 4]: 4
[ 5]: true
[ 6]: abc
OK
OK
OK 不错,
参数设置中,既然人为加入“[ ]”,转换为json来解析,好方法! 学习了,jsmn.h要花点时间研究一下。
页:
[1]