目录

一、AT命令

二、rtthread at组件简介

三、移植到freeRTOS

3.1、数据结构

3.2、API

3.3、at client 流程

3.4、串口数据接收处理

3.5、数据缓存 --- 顺序队列

四、使用示例

4.1、串口配置信息解析

4.2、IP和MAC地址解析

五、最后


一、AT命令

AT 命令集是一种应用于 AT 服务器(AT Server)与 AT 客户端(AT Client)间的设备连接与数据通信的方式。其基本结构如下图所示:

  1. 一般 AT 命令由三个部分组成,分别是:前缀、主体和结束符。其中前缀由字符 AT 构成;主体由命令、参数和可能用到的数据组成;结束符一般为  ("\r\n")
  2. AT Server 向 AT Client 发送的数据分成两种:响应数据和 URC 数据
  • 响应数据: AT Client 发送命令之后收到的 AT Server 响应状态和信息
  • URC 数据: AT Server 主动发送给 AT Client 的数据,一般出现在一些特殊的情况,比如 WIFI 连接断开、TCP 接收数据等,这些情况往往需要用户做出相应操作

    二、rtthread at组件简介

    RT-Thread系统的AT 组件是 AT Server 和 AT Client 的实现,组件完成 AT 命令的发送、命令格式及参数判断、命令的响应、响应数据的接收、响应数据的解析、URC 数据处理等整个 AT 命令数据交互流程。

    通过 AT 组件,设备可以作为 AT Client 使用串口连接其他设备发送并接收解析数据,可以作为 AT Server 让其他设备甚至电脑端连接完成发送数据的响应,也可以在本地 shell 启动 CLI 模式使设备同时支持 AT Server 和 AT Client 功能,该模式多用于设备开发调试。

    AT 组件资源占用

    • AT Client 功能:4.6K ROM 和2.0K RAM;

    • AT Server 功能:4.0K ROM 和2.5K RAM;

    • AT CLI 功能:1.5K ROM ,几乎没有使用RAM

三、移植到freeRTOS

项目中只用到的at client的功能,所以这里也只移植了client部分。移植分为两块,一块是将at client中使用的线程创建、信号量创建收发等rtthread系统API替换成freeRTOS的,这部分比较简单;另一块是串口数据收发部分,rtthread是基于自带的UART设备驱动框架来做的,并带有数据缓存功能,移植过来需要将其剥离,然后实现串口数据的发送、接收及接收数据的缓存功能。移植后

  • 串口数据发送是采用的轮询发送模式
  • 串口数据接收使用的是DMA方式
  • 接收数据缓存使用的是顺序队列

在移植前先来了解下at client的设计思路,有哪些数据结构以及提供了哪些API,注意这些数据结构跟API是移植调整过后的,跟原有的相差不大

3.1、数据结构

at_response 结构体用于响应数据的接收,at_urc_table 结构体是urc数据注册表,at_client 结构体是at client控制句柄

struct at_response
{/* response buffer */char *buf;/* the maximum response buffer size, it set by `at_create_resp()` function */uint16_t buf_size;/* the length of current response buffer */uint16_t buf_len;/* the number of setting response lines, it set by `at_create_resp()` function* == 0: the response data will auto return when received 'OK' or 'ERROR'* != 0: the response data will return when received setting lines number data */uint16_t line_num;/* the count of received response lines */uint16_t line_counts;/* the maximum response time */uint32_t timeout;
};
typedef struct at_response *at_response_t;struct at_client;/* URC(Unsolicited Result Code) object, such as: 'RING', 'READY' request by AT server */
struct at_urc
{const char *cmd_prefix;const char *cmd_suffix;void (*func)(struct at_client *client, const char *data, uint16_t size);
};
typedef struct at_urc *at_urc_t;struct at_urc_table
{size_t urc_size;const struct at_urc *urc;
};
typedef struct at_urc *at_urc_table_t;struct at_client
{    at_status_t status;char end_sign;/* the current received one line data buffer */char *recv_line_buf;/* The length of the currently received one line data */uint16_t recv_line_len;/* The maximum supported receive one line data length */uint16_t recv_line_size;xSemaphoreHandle rx_notice;xSemaphoreHandle lock;at_response_t resp;xSemaphoreHandle resp_notice;at_resp_status_t resp_status;struct at_urc_table *urc_table;uint16_t urc_table_size;/* uart receive queue */struct array_queue *recv_q;/* The maximum supported receive data length */uint16_t recv_queue_size;/* uart receive uart */UART_INDEX_E uart_index;/* handle task */TaskHandle_t parser;
};
typedef struct at_client *at_client_t;

3.2、API

/* get AT client object */
at_client_t at_client_get_first(void);
/* AT client initialize and start*/
at_client_t at_client_init(UART_INDEX_E uart_index, uint16_t recv_line_size, uint16_t recv_queue_size);/* AT client send or receive data */
int at_client_obj_send(at_client_t client, char *buf, int size);
int at_client_obj_recv(at_client_t client, char *buf, int size, uint32_t timeout);
/* AT client send commands to AT server and waiter response */
int at_obj_exec_cmd(at_client_t client, at_response_t resp, const char *cmd_expr, ...);/* set AT client a line end sign */
void at_obj_set_end_sign(at_client_t client, char ch);
/* Set URC(Unsolicited Result Code) table */
int at_obj_set_urc_table(at_client_t client, const struct at_urc * table, int size);/* AT response object create and delete */
at_response_t at_create_resp(uint16_t buf_size, uint16_t line_num, uint32_t timeout);
void at_delete_resp(at_response_t resp);
at_response_t at_resp_set_info(at_response_t resp, int buf_size, int line_num, uint32_t timeout);/* AT response line buffer get and parse response buffer arguments */
//获取指定行号的响应数据
const char *at_resp_get_line(at_response_t resp, int resp_line);
//根据关键字获取响应数据
const char *at_resp_get_line_by_kw(at_response_t resp, const char *keyword);
//根据resp_expr格式使用标准sscanf 解析语法,解析指定行的响应数据int at_resp_parse_line_args(at_response_t resp, int resp_line, const char *resp_expr, ...);
//根据resp_expr格式使用标准sscanf 解析语法,解析指定关键字的响应数据
int at_resp_parse_line_args_by_kw(at_response_t resp, const char *keyword, const char *resp_expr, ...);

3.3、at client 流程

用户线程中调用 at_client_init API时会自动创建 parser 线程,parser线程内部实现了 响应数据的接收、响应数据的解析、URC 数据处理等整个AT 命令数据交互流程。

发送数据时的数据流向为:用户线程调用 at_exec_cmd 发送数据 ---> 串口 ---> at server

接收数据时的数据流向为:at server ---> 串口接收,然后发送 rx_notice 信号量 ---> parser线程解析,然后发送 resp_notice 信号量

3.4、串口数据接收处理

在创建at client处理线程时,需要注册好串口数据接收回调函数,这样当串口接收到数据时,就会调用该回调函数,在该回调函数中,会将接收到的数据存入队列中,然后发送信号量通知at client处理线程进行处理

串口驱动:

typedef enum
{UART1_INDEX,UART2_INDEX,UART3_INDEX,UART_INDEX_ALL}UART_INDEX_E;/* stm32 uart dirver class */
struct stm32_uart
{const char *name;USART_TypeDef *Instance;IRQn_Type irq_type;int (*rx_ind)(UART_INDEX_E uart_index, char *recv_data, int recv_len);
};
extern struct stm32_uart stm32_uart_handle[UART_INDEX_ALL];extern void set_uart_rx_indicate(UART_INDEX_E uart_index, int (*set_rx_ind)(UART_INDEX_E uart_index, char *recv_data, int recv_len));
extern void uart_send_data_by_index(UART_INDEX_E index, unsigned char *data, unsigned short len);

串口接收中断:

void USART3_IRQHandler( void )
{uint8_t tmp;uint16_t rxLen = 0;if(LL_USART_IsActiveFlag_IDLE(USART3)){rxLen = sizeof(uart3_dam_rxbuf) - LL_DMA_GetDataLength(DMA1, LL_DMA_STREAM_1);if(stm32_uart_handle[UART3_INDEX].rx_ind){//调用了注册的数据接收回调函数stm32_uart_handle[UART3_INDEX].rx_ind(UART3_INDEX, uart3_dam_rxbuf, rxLen);}LL_USART_ClearFlag_IDLE(USART3);LL_DMA_DisableStream(DMA1, LL_DMA_STREAM_1);LL_DMA_SetDataLength(DMA1, LL_DMA_STREAM_1, sizeof(uart3_dam_rxbuf));//将 EN 位置“1”以启动新传输之前,DMA_LISR 或 DMA_HISR 寄存器中与数据流相对应的事件标志必须清零。LL_DMA_ClearFlag_TE1(DMA1);LL_DMA_ClearFlag_FE1(DMA1);LL_DMA_ClearFlag_TC1(DMA1);LL_DMA_ClearFlag_HT1(DMA1);LL_DMA_EnableStream (DMA1, LL_DMA_STREAM_1);}...
}

at client处理线程注册的接收回调函数

static int at_client_rx_indicate(UART_INDEX_E uart_index, char *recv_data, int recv_len)
{int idx = 0, i = 0, res;BaseType_t xHigherPriorityTaskWoken;for (idx = 0; idx < AT_CLIENT_NUM_MAX; idx++){if (at_client_table[idx].uart_index == uart_index && at_client_table[idx].status == AT_STATUS_INITIALIZED){for(i=0; i<recv_len; i++){if((res = array_queue_enqueue(at_client_table[idx].recv_q, &recv_data[i])) != 0)break;}xSemaphoreGiveFromISR(at_client_table[idx].rx_notice, &xHigherPriorityTaskWoken);}}return recv_len;
}

3.5、数据缓存 --- 顺序队列

队列跟栈类似都是“操作受限”的线性表,只不过队列是先进先出结构,队列也有两个基本的操作:入队 enqueue(),放一个数据到队列尾部;出队 dequeue(),从队列头部取一个元素。根据实现方式不同,也可以分为两种:使用数组来实现的顺序队列,和使用链表来实现的链式队列。

队列需要两个指针:一个是 head 指针,指向队头;一个是 tail 指针,指向队尾;

用size来表示队列的总大小,num来记录队列的当前元素个数,则可以这样判断:队列满时:num = size,队列空时:num = 0

#define ARRAY_QUEUE_MALLOC(size)   pvPortMalloc(size)
#define ARRAY_QUEUE_CALLOC(n,size) pvPortMalloc(n*size)
#define ARRAY_QUEUE_FREE(p)        vPortFree(p)#define ARRAY_QUEUE_SIZE(pqueue)     (pqueue->size)
#define ARRAY_QUEUE_NUM(pqueue)      (pqueue->num)
#define ARRAY_QUEUE_IS_EMPTY(pqueue) (pqueue->num == 0)
#define ARRAY_QUEUE_IS_FULL(pqueue)  (pqueue->num == pqueue->size)struct array_queue
{int size; /* queue total size */int num;  /* queue used size rang:1-(size-1) */int head; /* points to the the next dequeue data  */int tail; /* points to the the next enqueue data  */int tpsz; /* data type size */void *p;  /* queue space */
};extern struct array_queue* array_queue_creat(int size, int tpsz);
extern int array_queue_init   (struct array_queue *queue, int size, int tpsz);
extern int array_queue_empty  (struct array_queue *queue);
extern int array_queue_clear  (struct array_queue *queue);
extern int array_queue_destory(struct array_queue *queue);
extern int array_queue_enqueue(struct array_queue *queue, void *in_data);
extern int array_queue_dequeue(struct array_queue *queue, void *out_data);

四、使用示例

4.1、串口配置信息解析

客户端发生的数据:

AT+UART?

客户端获取的响应数据:

UART=115200,8,1,0,0\r\n
OK\r\n

解析伪代码如下:

/* 创建服务器响应结构体,64 为用户自定义接收数据最大长度 */
resp = at_create_resp(64, 0, rt_tick_from_millisecond(5000));/* 发送数据到服务器,并接收响应数据存放在 resp 结构体中 */
at_exec_cmd(resp, "AT+UART?");/* 解析获取串口配置信息,1 表示解析响应数据第一行,'%*[^=]'表示忽略等号之前的数据 */
at_resp_parse_line_args(resp, 1,"%*[^=]=%d,%d,%d,%d,%d", &baudrate, &databits, &stopbits, &parity, &control);
printf("baudrate=%d, databits=%d, stopbits=%d, parity=%d, control=%d\n",baudrate, databits, stopbits, parity, control);/* 删除服务器响应结构体 */
at_delete_resp(resp);

4.2、IP和MAC地址解析

客户端发送的数据:

AT+IPMAC?

服务器获取的响应数据:

IP=192.168.1.10\r\n
MAC=12:34:56:78:9a:bc\r\n
OK\r\n

解析伪代码如下:

/* 创建服务器响应结构体,128 为用户自定义接收数据最大长度 */
resp = at_create_resp(128, 0, rt_tick_from_millisecond(5000));at_exec_cmd(resp, "AT+IPMAC?");/* 自定义解析表达式,解析当前行号数据中的信息 */
at_resp_parse_line_args(resp, 1,"IP=%s", ip);
at_resp_parse_line_args(resp, 2,"MAC=%s", mac);
printf("IP=%s, MAC=%s\n", ip, mac);at_delete_resp(resp);

解析数据的关键在于解析表达式的正确定义,因为对于 AT 设备的响应数据,不同设备厂家不同命令的响应数据格式不唯一,所以只能提供自定义解析表达式的形式获取需要信息,at_resp_parse_line_args 解析参数函数的设计基于 sscanf 数据解析方式,使用之前需要先了解基本的解析语法,再结合响应数据设计合适的解析语法。如果不需要解析具体参数,可以直接使用 at_resp_get_line 函数获取一行的具体数据。

五、最后

相关源码修改可从这里下载

rtthread的at组件在freeRTOS上的移植修改代码

rt-thread的at组件在freeRTOS上的移植与应用相关推荐

  1. linux模块移植到freertos,FATFS在嵌入式操作系统FreeRTOS中的移植与应用

    摘 要: FreeRTOS作为一款免费的实时操作系统,系统内核小.裁剪方便.移植性好,广泛应用于对成本敏感的小型嵌入式系统中,但是FreeRTOS本身不带文件管理功能,不便于很多需要经常进行文件存储与 ...

  2. rt thread studio使用QBOOT和片外flash实现OTA升级

    我们这里要使用单片机外部flash作为OTA的下载分区,外部flash硬件连接关系 PB3-->SPI3_CLK PB4-->SPI3_MISO PB5-->SPI3_MOSI PE ...

  3. Yeelink平台使用——远程控制 RT Thread + LwIP+ STM32

    1.前言     [2014年4月重写该博文]     经过若干时间的努力终于搞定了STM32+LwIP和yeelink平台的数据互通,在学习的过程中大部分时间花在以太网协议栈学习上,但是在RT Th ...

  4. 关于RT thread系统节拍时钟的配置

    关于RT thread系统节拍时钟的配置                  -----本文基于rt-thread-3.1.3版本编写 首先,使用RTthread OS时,要配置(或者明白)它的系统节拍 ...

  5. 正点原子FreeRTOS(上)

    更多干货推荐可以去牛客网看看,他们现在的IT题库内容很丰富,属于国内做的很好的了,而且是课程+刷题+面经+求职+讨论区分享,一站式求职学习网站,最最最重要的里面的资源全部免费!!!点击进入------ ...

  6. stm32f429igt6跑linux,TouchGFX在STM32F429IGT6上的移植(FreeRTOS版本)

    TouchGFX在STM32F429IGT6上的移植(FreeRTOS版本) TouchGFX在STM32F429IGT6上的移植(FreeRTOS版本) 目录 一.移植环境 二.应用框架 三.Tou ...

  7. 基于rt thread smart构建EtherCAT主站

    我把源码开源到到了gitee,https://gitee.com/rathon/rt-thread-smart-soem 有兴趣的去可以下载下来跑一下 软件工程推荐用vscode 打开.rt thre ...

  8. rt thread系统下添加wiznet软件包后,不插网线CPU利用率100%问题

    rt thread系统下添加wiznet软件包后如果不插网线的话其他任务运行很卡,使用ps命令发现优先级低的任务很多都超时了 rt thread线程错误码 添加了一个可以查看CPU利用率的软件包CPU ...

  9. stm32f407单片机rt thread 片外spi flash OTA升级配置示例

    参考地址https://www.rt-thread.org/document/site/application-note/system/rtboot/an0028-rtboot/ 第一步,生成Boot ...

最新文章

  1. mysql 密码 You must reset your password using ALTER USER statement before executing this statement....
  2. linux redis经常自动关闭,Linux开启关闭redis
  3. vue-devtools介绍与安装
  4. 【转】如何判断一个文本文件内容的编码格式 UTF-8 ? ANSI(GBK)
  5. element引入的组件大小高度不对_ElementUI 在 按需引入时定义 default size?
  6. 自己动手制作USB启动盘
  7. dll和so文件区别与构成
  8. 操作系统信号量问题-------南北桥问题java实现
  9. wsl 设置阿里云源
  10. JAVA 抽象类与接口
  11. [从头读历史] 第289节 神之物语 忒修斯的故事
  12. Python--正则表达式在线验证的工具(regex)
  13. Linux CPU频率控制
  14. java根据前序和中序建树_Java实现根据前序遍历构建二叉树(前序遍历、中序遍历、后序遍历)...
  15. Vue中使用百度地图做路径分析并根据起终点坐标模拟道路里程桩
  16. android录音波浪动画_Android 自定义 view 实现波浪动画进度条
  17. C++ 的高精度除法
  18. D3D11 骨骼动画(基于MD5格式)
  19. Vue3通透教程【四】Vue3组合API初体验
  20. 996 有加班费,那加班是否合算?

热门文章

  1. ACM中涉及到的数学知识
  2. 阅读笔记 火球——UML大战需求分析 2
  3. jQuery 回调函数和方法链接使用
  4. 想把读取的网络图片显示在UIImageView 视图,居然死活不显示,代码绝对没问题
  5. 大前端(移动端/桌面应用Electron/微信小程序/小程序、快应用框架)
  6. 重要预警 | MindLost勒索软件安全建议,安骑士可检测防御
  7. 教外篇(6):C++ qrencode 实现二维码生成
  8. 2019-12-28
  9. 【初识太极】CAD用到的各种文件格式,你都知道吗?
  10. 基于QT(C++)实现房贷计算器【100010502】