armstrong 发表于 2019-12-10 11:30:36

也发一个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 11:33:45

本帖最后由 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:59

不错!下载下来仔细看看。

armstrong 发表于 2019-12-10 12:10:26

anjiyifan 发表于 2019-12-10 12:03
不错!下载下来仔细看看。

传了个win32命令行的演示【yatengine-win32.zip】,里面有串口收发接口适配的代码,可供参考。

armstrong 发表于 2019-12-10 12:25:15

yatengine-win32.zip演示效果:

jiaowoxiaolu 发表于 2019-12-10 13:29:40

看起来不错

easier 发表于 2019-12-10 14:01:26

看起来不错 ; Mark

lnskngdc 发表于 2019-12-10 14:44:52

早想做at指令配置参数了,哈哈

yanyanyan168 发表于 2019-12-10 15:22:35

Mark,备用,多谢分享

Mrp_Young 发表于 2019-12-10 15:43:55

MARK,肯定会用到

armstrong 发表于 2019-12-10 15:47:09

这是我刚刚用yatengine做的“红外学习播放模块”命令,使用很方便:

armstrong 发表于 2019-12-10 17:51:23

稍微改了一下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);
}

honami520 发表于 2019-12-10 19:00:03

不错,准备在项目里面使用看看

bad_fpga 发表于 2019-12-10 19:17:08

感谢分享AT解析,学习了

负西弱 发表于 2019-12-10 19:27:28

谢谢楼主分享

一号纵队 发表于 2019-12-10 19:36:32

下来学习下,谢了。

dragonbbc 发表于 2019-12-10 20:09:08

mark先,用到的时候再研究

genhao2 发表于 2019-12-10 23:13:43

效果不错,学习一下

关于以后 发表于 2019-12-10 23:31:02

样子不错啊。

ztg328 发表于 2019-12-11 06:33:36

mark 刚在找at解析

lrzxc 发表于 2019-12-11 06:39:14

谢谢分享,markPC端的软件

blueice1108 发表于 2019-12-11 07:19:22

能弄個stm32 的例程嗎

mypc16888 发表于 2019-12-11 07:41:25

不错,at解析

52avr 发表于 2019-12-11 08:43:51

AT命令解析引擎,谢谢楼主分享

落叶知秋 发表于 2019-12-11 08:49:50

看到这个帖子想起了以前写过的那个BASIC解释器{:biggrin:}

我是一个大白菜 发表于 2019-12-11 08:52:29

谢谢楼主分享,应该会用到

jxn98310 发表于 2019-12-11 09:07:32

感谢分享

wx-ta 发表于 2019-12-11 09:12:52

竟然看不懂用途,谁来科普下

armstrong 发表于 2019-12-11 09:16:34

本帖最后由 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...
}
这样,就不用花费时间考虑怎么设计这么个框架,直接加命令就是了;当然如果硬是想自己设计,那也会给你提供思路。总之是有价值的。

makathy 发表于 2019-12-11 09:22:20


感谢分享,

armstrong 发表于 2019-12-11 09:23:32

本帖最后由 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);
}
}


////////////////////////////////////////////////////////////////////////////////


armstrong 发表于 2019-12-11 09:29:15

blueice1108 发表于 2019-12-11 07:19
能弄個stm32 的例程嗎

看31楼回复,这是RTX下使用代码,就差USART驱动没贴出来了。

nydxsydt0 发表于 2019-12-11 14:05:31

好东西先收藏

kinsno 发表于 2019-12-11 17:45:10

armstrong 发表于 2019-12-11 09:29
看31楼回复,这是RTX下使用代码,就差USART驱动没贴出来了。

可以的

AT 引擎源码

赞1个。。

RTX刚使用了一个项目,感觉现在封装起来的CMSIS不如以前的RTX简洁了,简直是个臃肿的大包裹。

捷胜 发表于 2019-12-11 19:03:21

不错,收藏了

dukelec 发表于 2019-12-11 19:10:31

本帖最后由 dukelec 于 2019-12-11 19:12 编辑

這個只是做服務器?支持做客戶端嗎?

我表示自己做東西優先選擇二進制協議,字符的太麻煩。

lizuqing 发表于 2019-12-11 22:16:36


谢谢楼主分享,应该会用到

blueice1108 发表于 2019-12-12 13:28:06

armstrong 发表于 2019-12-11 09:29
看31楼回复,这是RTX下使用代码,就差USART驱动没贴出来了。

谢谢 最近正好项目要用上~

atonghua 发表于 2019-12-12 13:36:13

谢谢楼主分享刚好公司搞软件的写了一个字符串协议应该能用到

huangqi412 发表于 2019-12-12 15:51:53

AT感觉好麻烦

fdcnuaa 发表于 2019-12-16 09:57:26

mark at解析

zhongsandaoren 发表于 2019-12-16 10:05:56

AT 服务 指令 mark

heimareed 发表于 2019-12-17 10:57:52

万能的坛友,一直想弄一个,今天又拿来主义了~谢过!

Ray______ 发表于 2019-12-17 11:02:09

解析完的数据也是要遍历么。AT+XXX,XXX那部分

abutter 发表于 2019-12-17 11:12:45

依赖 jsmn 库吗?

armstrong 发表于 2019-12-17 11:16:50

abutter 发表于 2019-12-17 11:12
依赖 jsmn 库吗?

就一个头文件,已经在zip里了

adlkkang 发表于 2019-12-17 12:42:23

楼主用的是可变参数,这样程序可靠吗

denike 发表于 2019-12-17 15:42:59

Mark,备用,多谢分享

qmsolo2004 发表于 2019-12-17 17:44:11

备用,多谢分享!

armstrong 发表于 2019-12-17 22:28:15

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参数传入,代码拿来用即可。

n_mos 发表于 2020-3-2 21:59:24

AT 服务 指令 mark

chewy 发表于 2020-3-2 22:05:09


AT 服务 指令 mark , 估计下个项目中 可以用的到了

jackjiao 发表于 2020-3-2 23:34:52

学习下,一直在找AT解析架构相关的

ponder2077 发表于 2020-3-12 01:14:01

学习了,也许用得着

lihq97 发表于 2020-3-12 07:58:30

学习一下,刚好最近会用到。

altim_li 发表于 2020-3-12 09:20:45

不错,收藏了

dongwang_fl 发表于 2020-3-12 10:28:48

这个很好啊。

lyg407 发表于 2020-3-12 13:57:28

一直想了解 AT 命令如何解析,操作的。想学习一下,竟然今天才看到文章。

收藏学习了!谢谢楼主分享!

zcllom 发表于 2020-7-24 16:44:32

armstrong 发表于 2019-12-10 17:51
稍微改了一下win32演示,让ATE__ConsoleWrite函数调用fwrite输出,这样才可以输出非字符数据,这才是ATE__C ...

在keil中编译的时候报错

oooios 发表于 2020-9-22 08:13:17

多谢分享,拿来主义了

xuekcd 发表于 2020-9-22 09:13:34

biu特否!非常好!

血刃修罗 发表于 2020-9-22 09:15:20

感谢分享,AT学习

tyxjl 发表于 2020-9-22 09:21:07

收藏下,谢谢楼主了

oooios 发表于 2020-9-30 08:52:45

有没有MDK例子?谢谢分享

oooios 发表于 2020-9-30 11:40:39

本帖最后由 oooios 于 2020-9-30 14:47 编辑

你好,麻烦看看这个是怎么会事,谢谢

oooios 发表于 2020-9-30 11:46:34

本帖最后由 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

GNMXD 发表于 2020-9-30 17:23:46

不错,
参数设置中,既然人为加入“[ ]”,转换为json来解析,好方法!

Felix725 发表于 2020-12-10 15:34:37

学习了,jsmn.h要花点时间研究一下。
页: [1]
查看完整版本: 也发一个AT解析引擎源码【Yet Another AT Engine】