建议使用以下浏览器,以获得最佳体验。 IE 9.0+以上版本 Chrome 31+ 谷歌浏览器 Firefox 30+ 火狐浏览器
请选择 进入手机版 | 继续访问电脑版
设置昵称

在此一键设置昵称,即可参与社区互动!

确定
我再想想
选择版块
直达楼层
标签
您还可以添加5个标签
  • 没有搜索到和“关键字”相关的标签
  • 云产品
  • 解决方案
  • 技术领域
  • 通用技术
  • 平台功能
取消

采纳成功

您已采纳当前回复为最佳回复

樊心昊

发帖: 172粉丝: 141

发消息 + 关注

发表于2020年05月17日 15:01:31 1139 3
直达本楼层的链接
楼主
显示全部楼层
[技术干货] 0x04 LiteOS_Lab仓库组件详解--AT(中)

摘要:本贴以前面3篇帖子作为基础(由下至上Uart.c、driver.c),讲解AT框架,这三层之间由下到上是一个依次调用关系,如果对前面的内容不熟悉,请回头看一看(可以通过汇总帖中的链接查看),再来看本贴,你就会更加明白和清晰了。

本系列汇总贴:https://bbs.huaweicloud.com/forum/thread-51806-1-1.html

下图红框中的位置是本帖所讲解内容在整个系统中AT框架的位置

image.png

AT.c文件中用到的结构体


typedef struct
{
    const char             *devname;  //we use the device frame work to do this    要使用名称为"devname"的设备作为at收发使用
    los_dev_t               devhandle;//the device handle used               devname的设备句柄,可以通过该句柄对设备进行操作

    at_cmd_item             cmd;      //the at command,only one command could be excuted
    at_oob_item             oob[CONFIG_AT_OOBTABLEN];    //storage the out of band dealer
    char                 rcvbuf[CONFIG_AT_RECVMAXLEN];  //used storage one frame,read from the at channel 用于缓存收到的数据,大小可以手动调整
    unsigned int            rxdebugmode:2;          //receive debug mode
    unsigned int            txdebugmode:2;          //send debug mode
 
    int                     streammode;
}at_cb_t;    //这里的cb=control block 控制块的意思


AT框架初始化


/*******************************************************************************
function     :this is the  at module initialize function
parameters   :func_read:which will read a frame from the device
              func_write:which will be used to write a frame to the device
instruction  :if you want to use the at frame work, please call this function 
              please supply the function read and write.the read function must be
              a block function controlled by timeout
*******************************************************************************/
/* 该函数用于初始化AT框架,在使用AT框架之前需要先调用该函数 */
int at_init()
{
    int ret = -1;

    (void) memset(&g_at_cb,0,sizeof(g_at_cb));    //清空g_at_cb全局结构体变量中的数据
    g_at_cb.devname = CONFIG_AT_DEVNAME;          //将我们配置用于AT框架设备的名称复制给devname,可以通过Kconfig配置,也可以手动配置


    if(false == osal_semp_create(&g_at_cb.cmd.cmdsync,1,1))    //创建一个信号量,用于同步
    {
        LINK_LOG_DEBUG("%s:cmdsync error\n\r",__FUNCTION__);
        goto EXIT_CMDSYNC;
    }
    if(false == osal_semp_create(&g_at_cb.cmd.respsync,1,0))    //创建一个信号量,用于同步
    {
        LINK_LOG_DEBUG("%s:cmdsync error\n\r",__FUNCTION__);
        goto EXIT_RESPSYNC;
    }
    if(false == osal_mutex_create(&g_at_cb.cmd.cmdlock))        //创建一个互斥锁
    {
        LINK_LOG_DEBUG("%s:cmdlock error\n\r",__FUNCTION__);
        goto EXIT_CMDLOCK;
    }

    if(NULL == osal_task_create("at_rcv",__rcv_task_entry,NULL,0x800,NULL,10))    //创建一个任务,用于接收AT数据
    {
        LINK_LOG_DEBUG("%s:rcvtask create error\n\r",__FUNCTION__);
        goto EXIT_RCVTASK;
    }

    //for the debug
    g_at_cb.rxdebugmode = en_at_debug_ascii;        //这两个模式暂时可以忽略,它们是用于通过shell来调试at设备使用的
    g_at_cb.txdebugmode = en_at_debug_ascii;

    ret = 0;
    return ret;


EXIT_RCVTASK:
    (void) osal_mutex_del(&g_at_cb.cmd.cmdlock);
    g_at_cb.cmd.cmdlock = cn_mutex_invalid;
EXIT_CMDLOCK:
    (void) osal_semp_del(&g_at_cb.cmd.respsync);
    g_at_cb.cmd.respsync = cn_semp_invalid;
EXIT_RESPSYNC:
    (void) osal_semp_del(&g_at_cb.cmd.cmdsync);
    g_at_cb.cmd.cmdsync = cn_semp_invalid;
EXIT_CMDSYNC:
    return ret;
}

使用at_oobregister注册关键词处理函数


/*******************************************************************************
function     :you could use this function to register a method to deal the out of band data
parameters   :
instruction  :as you know, we only check the frame begin,using memcmp, so you must
              write the header of the frame as the index
*******************************************************************************/
/*    比如说我们的nbiot模块通过udp/tcp协议连接到了服务器,服务器给nbiot模块下发一条数据,这条数据很可能是异步的,就是我们无法预测什么时候会收到该数据,
      那么如何能处理这个数据呢?还好每条异步数据都会有一个前缀,例如+NSONMI:0,4,说明nbiot设备收到了一帧UDP数据,0号socket接口,长度为4字节,
      我们就可以通过下面这个函数将“+NSONMI:”关键字注册进去,只要at框架发现收到了一帧以“+NSONMI:”开头的数据,就会调用我们注册的的处理函数,
      来处理该数据,比如果根据+NSONMI:0,4中的4,设置4字节大小的空间,并发送AT+NSORF=0,4指令,将数据读出存储。
 */
 /* 传入参数name是该处理结构体的名字,index就是特定数据中会含有的关键字,比如前面说的“+NSONMI:”,len是指index的长度,func是一个函数指针,当发现index
     在数据中出现,就会调用该函数指针来处理数据,args是调用该函数指针时,给函数传入的参数。
  */
int at_oobregister(const char *name,const void *index,size_t len,fn_at_oob func,void *args)
{
    int ret = -1;
    at_oob_item *oob;
    int i = 0;

    if((NULL == func)||(NULL == index))
    {
        return ret;
    }

    for(i =0;i<CONFIG_AT_OOBTABLEN;i++)                //在存储oob结构体的表中寻找一个空位
    {
        oob = &g_at_cb.oob[i];
        if((oob->func == NULL)&&(oob->index == NULL))
        {
            oob->name = name;            //将我们传入的各项参数赋值给OOB结构体
            oob->index = index;
            oob->len = len;
            oob->func = func;
            oob->args = args;
            ret = 0;
            break;
        }
    }

    return ret;
}

流模式使能和禁止函数


/*******************************************************************************
function     :you could use this function to to enable or disable at stream mode.
parameters   :mode:1 for stream mode, 0 for dgram mode.
instruction  :If stream mode is enabled, we can process data from multiple frames.
			  mode equals 0 by default.
*******************************************************************************/
/* 由于UDP和TCP连接的方式有差异,所以根据我们设备选用的模式,可以选择dgram模式和stream模式,默认是dgram模式,
 * 选择了不同的模式,差异主要体现在接收任务中,执行接收任务时,会判断当前采用的模式,使用不同的接收方式

 */
int at_streammode_set(int mode)
{
	g_at_cb.streammode = mode;
	return 0;
}

at框架中的内部函数,只在at框架内部使用


/* 该函数被at_command函数调用,用于向uart设备写入要发送的数据 */
static int __cmd_send(const void *buf,size_t buflen,uint32_t timeout)
{
    int i = 0;
    ssize_t ret = 0;
    int debugmode;

    ret = los_dev_write(g_at_cb.devhandle,0,buf,buflen,timeout);
    if(ret > 0)
    {
        debugmode = g_at_cb.txdebugmode;
        switch (debugmode)                //如果开启了debug模式,则会将发送的数据通过shell显示
        {
            case en_at_debug_ascii:
                LINK_LOG_DEBUG("ATSND:%d Bytes:%s\n\r",(int)ret,(char *)buf);
                break;
            case en_at_debug_hex:
                LINK_LOG_DEBUG("ATSND:%d Bytes:",(int)ret);
                for(i =0;i<ret;i++)
                {
                    LINK_LOG_DEBUG("%02x ",*((uint8_t *)(buf) + i));
                }
                LINK_LOG_DEBUG("\n\r");
                break;
            default:
                break;
        }
        ret = 0;
    }
    else
    {
        ret = -1;
    }
    return ret;
}

/* 该函数在at接收任务中被调用,向driver层中读取uart设备中的数据 */
//this function used to receive data from the AT channel
static int __resp_rcv(void *buf,size_t buflen,uint32_t timeout)
{
    int i = 0;
    ssize_t ret = 0;
    int debugmode;

    ret = los_dev_read(g_at_cb.devhandle,0,buf,buflen,timeout); //调用driver层中的读函数,以缓存区最大字节数来读
    if(ret > 0)                            	//如果读到了数据
    {
        debugmode = g_at_cb.rxdebugmode;	//假设我们使能了rxdebugmode,就会将读到的数据在串口打印
        switch (debugmode)
        {
            case en_at_debug_ascii:
                LINK_LOG_DEBUG("ATRCV:%d Bytes:%s\n\r",(int)ret,(char *)buf);
                break;
            case en_at_debug_hex:
                LINK_LOG_DEBUG("ATRCV:%d Bytes:",(int)ret);
                for(i =0;i<ret;i++)
                {
                    LINK_LOG_DEBUG("%02x ",*((uint8_t *)(buf) + i));
                }
                LINK_LOG_DEBUG("\n\r");
                break;
            default:
                break;
        }
    }  
 
    return ret;
}

/* 该函数在at_command函数中被调用,用于向g_at_cb.cmd结构体成员中,这个结构体中的内容最终被任务接收函数调用__cmd_match函数使用 */
static int  __cmd_create(const void *cmdbuf,size_t cmdlen,const char *index,void *respbuf,size_t respbuflen,uint32_t timeout)
{
    int  ret = -1;
    at_cmd_item *cmd;    //定义一个命令结构体指针

    cmd = &g_at_cb.cmd; //将全局at控制块结构体中的cmd结构体成员的地址赋值给cmd指针 
    if(osal_semp_pend(cmd->cmdsync,timeout))    //申请一个信号量,为了保持同步
    {
        if(osal_mutex_lock(cmd->cmdlock))    //给cmd加锁,防止多进行访问
        {
            cmd->cmd = cmdbuf;                //将指令内容、长度、期待的回复、接收回复的内存地址、和接收回复的内存大小都写入cmd结构体
            cmd->cmdlen = cmdlen;
            cmd->index = index;
            cmd->respbuf = respbuf;
            cmd->respbuflen = respbuflen;
            (void) osal_semp_pend(cmd->respsync,0); //used to clear the sync
            (void) osal_mutex_unlock(cmd->cmdlock);
        }
        ret = 0;
    }
    return ret;
}

/* 该函数用于清空g_at_cb.cmd结构中的内容 */
//clear the at command here
static int __cmd_clear(void)
{
     at_cmd_item *cmd;

     cmd = &g_at_cb.cmd;
     if(osal_mutex_lock(cmd->cmdlock))
     {
        cmd->cmd = NULL;
        cmd->cmdlen = 0;
        cmd->index = NULL;
        cmd->respbuf = NULL;
        cmd->respbuflen = 0;
        cmd->respdatalen = 0;
        (void) osal_mutex_unlock(cmd->cmdlock);
     }
     (void) osal_semp_post(cmd->cmdsync);
     return 0;
}

/* 该函数在at接收任务中被循环调用,用于检查接收到的数据是否对应我们使用__cmd_send发出去的回应,如果出现我们期待的数据(index),返回0反之返回-1 */
//check if the data received is the at command need
static int  __cmd_match(const void *data,size_t len)
{
    int  ret = -1;
    int  cpylen;
    at_cmd_item *cmd = NULL;

    cmd = &g_at_cb.cmd;
    if(osal_mutex_lock(cmd->cmdlock))    //加锁,防止多个进程同时访问
    {
        if((NULL != cmd->index)&&(NULL != strstr((const char *)data,cmd->index)))    //检查index是否为空、收到的数据中是否含有我们需要的数据
        {
            if(NULL != cmd->respbuf)    //如果定义了respbuf缓存区并且分配了内存,就将与AT指令向对应的回复拷贝进去
            {
                cpylen = len > cmd->respbuflen?cmd->respbuflen:len;
                (void) memcpy((char *)cmd->respbuf,data,cpylen);
                cmd->respdatalen = cpylen;
            }
            else
            {
                cmd->respdatalen = len; //tell the command that how many data has been get
            }
            (void) osal_semp_post(cmd->respsync);
            ret = 0;
        }
        (void) osal_mutex_unlock(cmd->cmdlock);
    }
    return ret;
}

/* 当收到一帧数据时,就会在at接收任务中调用该函数,查看这帧数据中是否有我们向oob表中注册的数据处理函数对应的关键字
    如果有的话,就调用其对应处理函数来处理这帧数据 */
//check if any out of band method could deal the data
static int  __oob_match(void *data,size_t len)
{
    int ret = -1;
    at_oob_item *oob;
    int i = 0;
    for(i =0;i<CONFIG_AT_OOBTABLEN;i++)    //遍历存储oob结构体的数组
    {
        oob = &g_at_cb.oob[i];
        if((oob->func != NULL)&&(oob->index != NULL)&&\
            (0 == memcmp(oob->index,data,oob->len)))
        {
            ret = oob->func(oob->args,data,len);
            break;
        }
    }
    return ret;
}

由于有字数限制,后面的函数在下一节中写出,并在这里贴上链接。

举报
分享

分享文章到朋友圈

分享文章到微博

采纳成功

您已采纳当前回复为最佳回复

极客潇

发帖: 520粉丝: 69

发消息 + 关注

发表于2020年05月21日 08:42:57
直达本楼层的链接
沙发
显示全部楼层

感谢楼主分享

点赞 评论 引用 举报

采纳成功

您已采纳当前回复为最佳回复

scu-w

发帖: 354粉丝: 9

发消息 + 关注

发表于2020年05月25日 17:54:23
直达本楼层的链接
板凳
显示全部楼层

Mark!

点赞 评论 引用 举报

采纳成功

您已采纳当前回复为最佳回复

我是卤蛋

发帖: 121粉丝: 293

级别 : 版主,版块专家

发消息 + 关注

发表于2020年06月26日 11:10:21
直达本楼层的链接
地板
显示全部楼层

感谢分享,持续学习~

点赞 评论 引用 举报

游客

富文本
Markdown
您需要登录后才可以回帖 登录 | 立即注册

结贴

您对问题的回复是否满意?
满意度
非常满意 满意 一般 不满意
我要反馈
0/200