还在用传统的方式驱动一个通信模组?不如一起来学习下TOS的AT模组框架吧!

2020-09-28 10:45:19 浏览数 (3)

本节基于TOS的AT框架,我实现了一个基于MX 开发板的demo,用于控制之前搭的智能小车,效果如下,详细源码及实验例程请参考文末码云仓库链接:

动手智能小车记(5)-坦克底盘硬件模块大杂烩

1、什么是AT指令?

在嵌入式开发过程中,我们有时候要使用一些通信模组,比如蓝牙、WIFI、4G、NBIOT等等,这些模组内部固件已经将协议栈封装好了,然后模组硬件向外部提供了标准的串口,这样通过与模组的串口相连接,就可以与模组进行通信;一般情况下,厂商都会提供模组的使用手册,只要照着流程去操作模组就可以正常通信了,如下图所示,这是一个MCU、蓝牙模组、手机之间的通信案例:

2、为什么要有AT框架?

一般情况下,在一些物联网产品的项目上可能这样的需求,比如:

常规的一些传感器设备,需要监测环境温度、湿度等等这样的情况:

  • 实现数据上传

共享单车、智能门锁

  • 实现开锁的逻辑

等等。。。

这些需求看起来就非常简单,比如我就用ESP8266 一个后台服务器来实现这样的需求吧,只要后台提供好API接口,那么这类简单的需求分分钟搞定,完全没有任何难度,在应用程序上编写好模组的驱动接口和通信逻辑就可以了。

但是,如果换一个呢??再换一个呢?有可能实现同样的需求,我们还要去实现不一样的驱动流程,这是不是显得很麻烦?基于这样的问题诞生,于是各个厂商分别提出了对应的AT框架思维,那么这种AT框架思维具体是什么样的呢?以驱动ESP8266为例,一般有以下几种模式:

  • AP模式
  • STA模式
  • AP STA模式

以STA模式为例,最后要和云端服务器进行对接,我们首先要完成初始化流程,一般要发以下几个指令:

  • AT RESTORErn 模组复位
  • ATE0rn 关闭回显
  • AT CWMODE=1rn 设置多连接
  • AT CIPMODE=0rn 关闭透传模式
  • AT CIPMUX=1rn 开始多连接模式
  • AT CWJAP="TOS","12345678"rn 连接热点

这样就基本完成了模组的初始化流程,初始化完毕以后,就可以进入数据传输的了,连接服务器,然后开启透传模式,进入透传模式,然后就可以把数据直接传送到后台了,此时还可以读取后台的消息,当我们不需要需要模组的时候,还可以将模组掉电;所以,我们可以在这个基础上把这个驱动流程框架化,即是拥有初始化、连接服务器、发送、接收、关闭等等这些接口。

3、TencetOS tiny AT框架

在TencentOS tiny中,内部就集成了一套简单易用的AT框架,哪怕是不一样的指令,我们也只需要填充对应的方法,然后注册到框架上,就可以顺利与模组进行通信了,以下是TencentOS tiny AT框架的基本组成图:

上图来源于汪兄讲解的PPT

对于应用开发者来说,我们最关注的是SAL interface、也就是网络适配框架,只要模组注网成功,那么在这一层,我们不需要具体去关注模组到底是怎么用AT指令去通信的,我们只需要调用SAL interface的socket、connect、send、recv、close等等接口完成我们与后台的通信或者与别的通信方式的逻辑即可,但是调用SAL接口口还需要去与各个模组进行适配。

代码语言:javascript复制
typedef struct sal_module_st {
    int (*init)(void);

    int (*get_local_mac)(char *mac);

    int (*get_local_ip)(char *ip, char *gw, char *mask);

    int (*parse_domain)(const char *host_name, char *host_ip, size_t host_ip_len);

    int (*connect)(const char *ip, const char *port, sal_proto_t proto);

    int (*send)(int sock, const void *buf, size_t len);

    int (*recv_timeout)(int sock, void *buf, size_t len, uint32_t timeout);

    int (*recv)(int sock, void *buf, size_t len);

    int (*sendto)(int sock, char *ip, char *port, const void *buf, size_t len);

    int (*recvfrom)(int sock, void *buf, size_t len);

    int (*recvfrom_timeout)(int sock, void *buf, size_t len, uint32_t timeout);

    int (*close)(int sock);
} sal_module_t;

对于怎么去绑定(适配)模组和SAL interface,在此之前那我们还需要完成AT framework与HAL(uart)的适配,然后提供SAL interface需要的接口,注册上去,这样我们就可以在SAL上愉快的进行操作了,接下来AT框架具体是怎么解析每个AT指令我们就不需要特别去关心了,感兴趣的可以去研究一下tos_at.c、tos_at.h这两个文件。

在TencentOS tiny SDK中,腾讯官方已经提供了一些热门模组的操作例程,比如esp8266,它是怎么与SAL interface完成适配的呢?如下:

上图来源于戴兄讲解的PPT

这样的话,我们就可以调用TOS提供的SAL接口进行通信了,如下,在sal_module_wrapper.h中查看,详细实现在sal_module_wrapper.c:

代码语言:javascript复制
/**
 * @brief Convert domain to ip address.
 *
 * @attention None
 *
 * @param[in]   host_name   domain name of the host
 * @param[out]  host_ip     ip address of the host
 * @param[out]  host_ip_len ip address buffer length
 *
 * @return  errcode
 */
int tos_sal_module_parse_domain(const char *host_name, char *host_ip, size_t host_ip_len);

/**
 * @brief Connect to remote host.
 *
 * @attention None
 *
 * @param[in]   ip      ip address of the remote host
 * @param[in]   port    port number of the remote host
 * @param[in]   proto   protocol of the connection(TCP/UDP)
 *
 * @return  socket id if succuss, -1 if failed.
 */
int tos_sal_module_connect(const char *ip, const char *port, sal_proto_t proto);

/**
 * @brief Send data to the remote host(TCP).
 *
 * @attention None
 *
 * @param[in]   sock    socket id
 * @param[in]   buf     data to send
 * @param[in]   len     data length
 *
 * @return  data length sent
 */
int tos_sal_module_send(int sock, const void *buf, size_t len);

/**
 * @brief Receive data from the remote host(TCP).
 *
 * @attention None
 *
 * @param[in]   sock    socket id
 * @param[in]   buf     data buffer to hold the data received
 * @param[in]   len     data buffer length
 *
 * @return  data length received
 */
int tos_sal_module_recv(int sock, void *buf, size_t len);

/**
 * @brief Receive data from the remote host(TCP).
 *
 * @attention None
 *
 * @param[in]   sock    socket id
 * @param[in]   buf     data buffer to hold the data received
 * @param[in]   len     data buffer length
 * @param[in]   timeout timeout
 *
 * @return  data length received
 */
int tos_sal_module_recv_timeout(int sock, void *buf, size_t len, uint32_t timeout);

/**
 * @brief Send data to the remote host(UDP).
 *
 * @attention None
 *
 * @param[in]   sock    socket id
 * @param[in]   ip      ip address of the remote host
 * @param[in]   port    port number of the remote host
 * @param[in]   buf     data to send
 * @param[in]   len     data length
 *
 * @return  data length sent
 */
int tos_sal_module_sendto(int sock, char *ip, char *port, const void *buf, size_t len);

/**
 * @brief Receive data from the remote host(UDP).
 *
 * @attention None
 *
 * @param[in]   sock    socket id
 * @param[in]   buf     data buffer to hold the data received
 * @param[in]   len     data buffer length
 *
 * @return  data length received
 */
int tos_sal_module_recvfrom(int sock, void *buf, size_t len);

/**
 * @brief Receive data from the remote host(UDP).
 *
 * @attention None
 *
 * @param[in]   sock    socket id
 * @param[in]   buf     data buffer to hold the data received
 * @param[in]   len     data buffer length
 * @param[in]   timeout timeout
 *
 * @return  data length received
 */
int tos_sal_module_recvfrom_timeout(int sock, void *buf, size_t len, uint32_t timeout);

/**
 * @brief Close the connection.
 *
 * @attention None
 *
 * @param[in]   sock    socket id
 *
 * @return  errcode
 */
int tos_sal_module_close(int sock);

但是你以为这就完了吗?为了和Posix API无差异化调用,TencentOS tiny官方开发人员开发了一套近似于通用网络操作接口,就类似操作一个文件一样open、read、write、close,这不就更简单了嘛?我们来一睹为快:

tos_at_socket.h

代码语言:javascript复制
#ifndef  _TOS_AT_SOCKET_H_
#define  _TOS_AT_SOCKET_H_

#include "tos_at_socket_lib.h"
#include "tos_at_socket_types.h"

#define AF_INET             0

#define AF_INET6            1

#define AF_UNIX             2

/* Provides sequenced, reliable, bidirectional, connection-mode byte streams, and may provide a transmission mechanism for out-of-band data. */
#define SOCK_STREAM         0

/* Provides datagrams, which are connectionless-mode, unreliable messages of fixed maximum length. */
#define SOCK_DGRAM          1

/* Peeks at an incoming message. The data is treated as unread and the next recv() or similar function shall still return this data. */
#define MSG_PEEK            0x01

/* Requests out-of-band data. The significance and semantics of out-of-band data are protocol-specific. */
#define MSG_OOB             0x02

/* On SOCK_STREAM sockets this requests that the function block until the full amount of data can be returned. The function may return the smaller amount of data if the socket is a message-based socket, if a signal is caught, if the connection is terminated, if MSG_PEEK was specified, or if an error is pending for the socket. */
#define MSG_WAITALL         0x04

int socket(int domain, int type, int protocol);

int connect(int socket, const struct sockaddr *address, socklen_t address_len);

int recv(int socket, void *buffer, size_t length, int flags);

int recvfrom(int socket, void * buffer, size_t length, int flags, struct sockaddr *address, socklen_t *address_len);

int send(int socket, const void *buffer, size_t length, int flags);

int sendto(int socket, const void *message, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len);

int shutdown(int socket, int how);

int read(int socket, void *buffer, size_t length);

int close(int socket);

int write(int socket, const void *buffer, size_t length);

#endif /* _TOS_AT_SOCKET_H_ */

只要注册框架等流程完成以后,模组注网成功,后面就可以直接这样常规操作了:

代码语言:javascript复制
void network_demo(void)
{
    int recv_len = -1;
    int fd, rc, cnt = 0;

    struct sockaddr_in addr;

    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(SERVER_IP);
    addr.sin_port = htons(SERVER_PORT);

    fd = socket(AF_INET, SOCK_STREAM, 0);

    if (fd < 0)
    {
        printf("socket failedn");
        return;
    }

    rc = connect(fd, (struct sockaddr *)&addr, sizeof(addr));

    if (rc < 0)
    {
        printf("connect failedn");
        close(fd);
        return;
    }
 
    while(1)
    {
      //调用send发送数据
      //调用recv接收并处理数据
    }
 
    close(fd);
}

基于TencentOS tiny AT框架的基础上,我编写了一个基于MX 开发板的控制小车例程,源码已更新到码云个人仓库。

个人码云仓库地址:

代码语言:javascript复制
https://gitee.com/morixinguan

我还将之前做的一些项目以及练习例程在近期内全部上传完毕,与大家一起分享交流:

全文参考资料

腾讯物联网终端操作系统SDK文档.pdf

腾讯物联网终端操作系统开发指南.pdf

TencentOS tiny技术讲解与开发实践PPT.pdf

云加社区沙龙(腾讯物联网操作系统TencentOS tiny架构解析与实践).pdf

0 人点赞