Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
AT
at_socket
RTT AT组件使用AT+QISEND发送数据流的缺陷优化
发布于 2023-12-25 10:31:16 浏览:1578
订阅该版
[tocm] ## **【问题】** 使用4G模组的最新固件,发现AT组件使用AT+QISEND发送数据流时,如在发送AT+QISEND指令时刻,同时有URC上报,则可能会导致发送失败;而使用4G模组旧版固件,则不存在此问题。 ## **【分析】** 经4G模组排查,发现4G模组的新旧版固件中,修改了模组对于AT+QISEND数据流的接收处理方式: - 旧版本固件,具有bypass功能,即把AT+QISEND指令后收到的指定长度数据,均视为上位机要发送的数据 - 新版本固件,去除了bypass功能,上位机要发送的数据是从模组发送'>'字符后的数据,4G模组在AT+QISEND指令至'>'字符前收到的数据,将被丢弃 关于AT+QISEND,正确的使用方式是:发送方发送完'AT+QISEND'指令后,需等收到'>'字符后,才能发送数据,发送完指定长度数据后,发送方收到'SEND OK',判断为发送成功;收到'SEND FAIL'或'ERROR',判断为发送失败 ***AT组件对AT+QISEND的处理分析如下:*** - 设置end_sign为'>'字符 - 发送'AT+QISEND'指令 - 同步阻塞等待响应 - 转到AT解析,即client_parser()、at_recv_readline(): ```c static int at_recv_readline(at_client_t client) { rt_size_t read_len = 0; char ch = 0, last_ch = 0; rt_bool_t is_full = RT_FALSE; rt_memset(client->recv_line_buf, 0x00, client->recv_bufsz); client->recv_line_len = 0; while (1) { at_client_getchar(client, &ch, RT_WAITING_FOREVER); if (read_len < client->recv_bufsz) { client->recv_line_buf[read_len++] = ch; client->recv_line_len = read_len; } else { is_full = RT_TRUE; } /* is newline or URC data */ if ((ch == '\n' && last_ch == '\r') || (client->end_sign != 0 && ch == client->end_sign) || get_urc_obj(client)) { if (is_full) { LOG_E("read line failed. The line data length is out of buffer size(%d)!", client->recv_bufsz); rt_memset(client->recv_line_buf, 0x00, client->recv_bufsz); client->recv_line_len = 0; return -RT_EFULL; } break; } last_ch = ch; } #ifdef AT_PRINT_RAW_CMD at_print_raw_cmd("recvline", client->recv_line_buf, read_len); #endif return read_len; } static void client_parser(at_client_t client) { const struct at_urc *urc; while(1) { if (at_recv_readline(client) > 0) { if ((urc = get_urc_obj(client)) != RT_NULL) { /* current receive is request, try to execute related operations */ if (urc->func != RT_NULL) { urc->func(client, client->recv_line_buf, client->recv_line_len); } } else if (client->resp != RT_NULL) { at_response_t resp = client->resp; /* current receive is response */ client->recv_line_buf[client->recv_line_len - 1] = '\0'; if (resp->buf_len + client->recv_line_len < resp->buf_size) { /* copy response lines, separated by '\0' */ rt_memcpy(resp->buf + resp->buf_len, client->recv_line_buf, client->recv_line_len); /* update the current response information */ resp->buf_len += client->recv_line_len; resp->line_counts++; } else { client->resp_status = AT_RESP_BUFF_FULL; LOG_E("Read response buffer failed. The Response buffer size is out of buffer size(%d)!", resp->buf_size); } /* check response result */ if (rt_memcmp(client->recv_line_buf, AT_RESP_END_OK, rt_strlen(AT_RESP_END_OK)) == 0 && resp->line_num == 0) { /* get the end data by response result, return response state END_OK. */ client->resp_status = AT_RESP_OK; } else if (rt_strstr(client->recv_line_buf, AT_RESP_END_ERROR) || (rt_memcmp(client->recv_line_buf, AT_RESP_END_FAIL, rt_strlen(AT_RESP_END_FAIL)) == 0)) { client->resp_status = AT_RESP_ERROR; } else if (resp->line_counts == resp->line_num && resp->line_num) { /* get the end data by response line, return response state END_OK.*/ client->resp_status = AT_RESP_OK; } else { continue; } client->resp = RT_NULL; rt_sem_release(client->resp_notice); } else { // log_d("unrecognized line: %.*s", client->recv_line_len, client->recv_line_buf); } } } } ``` - 根据上述代码实现,AT组件按行进行解析,收到`\r\n`、指定的`end_sign`、已注册的URC,均视为2行数据。 - 再看下发送方是如何发送的,如下代码: ```c uint32_t event = 0; int result = 0, event_result = 0; size_t cur_pkt_size = 0, sent_size = 0; at_response_t resp = RT_NULL; int device_socket = (int) socket->user_data; struct at_device *device = (struct at_device *) socket->device; struct at_device_ec200x *ec200x = (struct at_device_ec200x *) device->user_data; rt_mutex_t lock = device->client->lock; RT_ASSERT(buff); resp = at_create_resp(128, 2, rt_tick_from_millisecond(300)); if (resp == RT_NULL) { LOG_E("no memory for resp create."); return -RT_ENOMEM; } rt_mutex_take(lock, RT_WAITING_FOREVER); /* set current socket for send URC event */ ec200x->user_data = (void *) device_socket; /* clear socket send event */ event = SET_EVENT(device_socket, EC200X_EVENT_SEND_OK | EC200X_EVENT_SEND_FAIL); ec200x_socket_event_recv(device, event, 0, RT_EVENT_FLAG_OR); /* set AT client end sign to deal with '>' sign.*/ at_obj_set_end_sign(device->client, '>'); while (sent_size < bfsz) { if (bfsz - sent_size < EC200X_MODULE_SEND_MAX_SIZE) { cur_pkt_size = bfsz - sent_size; } else { cur_pkt_size = EC200X_MODULE_SEND_MAX_SIZE; } /* send the "AT+QISEND" commands to AT server than receive the '>' response on the first line. */ if (at_obj_exec_cmd(device->client, resp, "AT+QISEND=%d,%d", device_socket, (int)cur_pkt_size) < 0) { result = -RT_ERROR; goto __exit; } //rt_thread_mdelay(5);//delay at least 4ms /* send the real data to server or client */ result = (int) at_client_obj_send(device->client, buff + sent_size, cur_pkt_size); if (result == 0) { result = -RT_ERROR; goto __exit; } /* waiting result event from AT URC */ event = SET_EVENT(device_socket, 0); event_result = ec200x_socket_event_recv(device, event, 10 * RT_TICK_PER_SECOND, RT_EVENT_FLAG_OR); if (event_result < 0) { LOG_E("%s device socket(%d) wait event timeout.", device->name, device_socket); result = -RT_ETIMEOUT; goto __exit; } /* waiting OK or failed result */ event = EC200X_EVENT_SEND_OK | EC200X_EVENT_SEND_FAIL; event_result = ec200x_socket_event_recv(device, event, 1 * RT_TICK_PER_SECOND, RT_EVENT_FLAG_OR); if (event_result < 0) { LOG_E("%s device socket(%d) wait sned OK|FAIL timeout.", device->name, device_socket); result = -RT_ETIMEOUT; goto __exit; } /* check result */ if (event_result & EC200X_EVENT_SEND_FAIL) { LOG_E("%s device socket(%d) send failed.", device->name, device_socket); result = -RT_ERROR; goto __exit; } if (type == AT_SOCKET_TCP) { //at_wait_send_finish(socket, cur_pkt_size); rt_thread_mdelay(10); } sent_size += cur_pkt_size; } __exit: /* reset the end sign for data conflict */ at_obj_set_end_sign(device->client, 0); rt_mutex_release(lock); if (resp) { at_delete_resp(resp); } return result > 0 ? sent_size : result; ``` - 划重点:resp = at_create_resp(128, 2, rt_tick_from_millisecond(300)); 也就是只要收到2行数据,即判断为4G模组响应成功,并开始发送数据流。 - 正常情况下,发送方发送完'AT+QISEND'指令后,4G模组收到该指令字符串后,会立刻回复一个\r\n和'>'字符。因已设置'>'字符为end_sign,所以会视为2行数据。所以,正常情况下,上述代码,是可以发送数据成功的。 - 但有时候,世界就是这样,并未如此顺利:**如果在发送方发送完'AT+QISEND'指令后,模组响应该指令前,上报了URC,则会出现本文的问题:**URC上报的格式是“回车换行+URC\r\n”,1) 如果连续有2个URC上报,则发送方视别到有2个回车换行,而判断为响应成功,在未收真正收到'>'字符的情况下就开始发送数据;2) 可能还有其它未知情况导致发送方在未收真正收到'>'字符的情况下就开始发送数据; - 如果真正按'AT+QISEND'指令的描述,在未收到'>'字符情况下发送数据,是有问题的。当然也取决于4G模组对于'AT+QISEND'指令的实际处理。就像本文开头所说,旧版本可以,新版本不可以 ## **【解决方案】** 暂时想到的方案:在AT解析中增加特殊处理:如果已设置end_sign为'>'字符,则只有收到'>'字符后,才返回响应成功,代码如下: ```c #if 1 // 针对end_sign为'>'字符的特殊处理 static int at_recv_readline(at_client_t client, uint8_t *flag) #else static int at_recv_readline(at_client_t client) #endif { rt_size_t read_len = 0; char ch = 0, last_ch = 0; rt_bool_t is_full = RT_FALSE; rt_memset(client->recv_line_buf, 0x00, client->recv_bufsz); client->recv_line_len = 0; while (1) { at_client_getchar(client, &ch, RT_WAITING_FOREVER); if (read_len < client->recv_bufsz) { client->recv_line_buf[read_len++] = ch; client->recv_line_len = read_len; } else { is_full = RT_TRUE; } #if 1 // 针对end_sign为'>'字符的特殊处理 /** * @brief AT发送流数据时(如AT指令:AT+QISEND),一般会要求收到'>'字符后才能发送字节流 * - RTT的AT指令解析为异步处理,在发送完AT+QISEND指令后,如果立刻收到1个或多个URC事件(模组URC格式为:回车换行 + "+QIURC:内容" + 回车换行),然后再收到"回车换行 + >" * - 按AT解析框架,则可能导致在未收到'>'字符,就会开始发送字节流,模组端可能丢弃'>'字符前的字节流数据,导致发送数据的不完整 * - 示例: * - AT+QISEND指令的分析,伪代码: * - resp = at_create_resp(128, 2, rt_tick_from_millisecond(300)); * - at_obj_set_end_sign(device->client, '>'); * - if (at_obj_exec_cmd(device->client, resp, "AT+QISEND=%d,%d", device_socket, (int)cur_pkt_size) < 0) * - 按上述伪代码,收到2行数据即为AT指令成功;2行数据的要求是:2个回车换行 或 一个回车行 + '>'字符 * - 如果发送AT+QISEND指令数据后,模组立即连续上送了2个URC,按URC匹配规则,会得到2个回车换行,所以会在未收到'>'字符条件下就会开始发送字节流数据 * @优化:如果end_sign为'>'字符,则特殊处理 */ if ('>' == client->end_sign) { if (ch == client->end_sign) { *flag = 0x5A; break; } if ((ch == '\n' && last_ch == '\r') || get_urc_obj(client)) { if (is_full) { LOG_E("at recv_line_len %d > %d", client->recv_line_len, client->recv_bufsz); rt_memset(client->recv_line_buf, 0x00, client->recv_bufsz); client->recv_line_len = 0; return -RT_EFULL; } break; } } else #endif /* is newline or URC data */ if ((ch == '\n' && last_ch == '\r') || (client->end_sign != 0 && ch == client->end_sign) || get_urc_obj(client)) { if (is_full) { LOG_E("read line failed. The line data length is out of buffer size(%d)!", client->recv_bufsz); rt_memset(client->recv_line_buf, 0x00, client->recv_bufsz); client->recv_line_len = 0; return -RT_EFULL; } break; } last_ch = ch; } #ifdef AT_PRINT_RAW_CMD at_print_raw_cmd("recvline", client->recv_line_buf, read_len); #endif return read_len; } static void client_parser(at_client_t client) { const struct at_urc *urc; while(1) { #if 1 // 针对end_sign为'>'字符的特殊处理 uint8_t flag = 0; if (at_recv_readline(client, &flag) > 0) #else if (at_recv_readline(client) > 0) #endif { if ((urc = get_urc_obj(client)) != RT_NULL) { /* current receive is request, try to execute related operations */ if (urc->func != RT_NULL) { urc->func(client, client->recv_line_buf, client->recv_line_len); } } else if (client->resp != RT_NULL) { at_response_t resp = client->resp; /* current receive is response */ client->recv_line_buf[client->recv_line_len - 1] = '\0'; if (resp->buf_len + client->recv_line_len < resp->buf_size) { /* copy response lines, separated by '\0' */ rt_memcpy(resp->buf + resp->buf_len, client->recv_line_buf, client->recv_line_len); /* update the current response information */ resp->buf_len += client->recv_line_len; resp->line_counts++; } else { client->resp_status = AT_RESP_BUFF_FULL; LOG_E("Read response buffer failed. The Response buffer size is out of buffer size(%d)!", resp->buf_size); } /* check response result */ #if 1 // 针对end_sign为'>'字符的特殊处理 if ('>' == client->end_sign) { if (0x5A == flag) { client->resp_status = AT_RESP_OK; } else { continue; } } else #endif if (rt_memcmp(client->recv_line_buf, AT_RESP_END_OK, rt_strlen(AT_RESP_END_OK)) == 0 && resp->line_num == 0) { /* get the end data by response result, return response state END_OK. */ client->resp_status = AT_RESP_OK; } else if (rt_strstr(client->recv_line_buf, AT_RESP_END_ERROR) || (rt_memcmp(client->recv_line_buf, AT_RESP_END_FAIL, rt_strlen(AT_RESP_END_FAIL)) == 0)) { client->resp_status = AT_RESP_ERROR; } else if (resp->line_counts == resp->line_num && resp->line_num) { /* get the end data by response line, return response state END_OK.*/ client->resp_status = AT_RESP_OK; } else { continue; } client->resp = RT_NULL; rt_sem_release(client->resp_notice); } else { // log_d("unrecognized line: %.*s", client->recv_line_len, client->recv_line_buf); } } } } ```
11
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
reille
这家伙很懒,什么也没写!
文章
1
回答
5
被采纳
1
关注TA
发私信
相关文章
1
rt-thread 2g/3g/4g通信模块的教程有吗?
2
基于AT指令,用esp8266如何连接mqtt?
3
AT组件使用问题
4
官方AT客户端应用笔记的几个小tip
5
RTT的SAL能够判断使用LWIP或者AT?
6
rt_therad AT组件移植不成功(结贴)
7
AT组件在哪个版本的?
8
AT组件 连接 Onenet 连接失败
9
esp8266 AT指令 MQTT连接问题
10
AT组件使用问题之模块主动上报【已解决】
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
I2C_IIC
ESP8266
UART
WIZnet_W5500
ota在线升级
cubemx
PWM
flash
freemodbus
BSP
packages_软件包
潘多拉开发板_Pandora
定时器
ADC
flashDB
GD32
socket
编译报错
中断
Debug
rt_mq_消息队列_msg_queue
SFUD
msh
keil_MDK
ulog
C++_cpp
MicroPython
本月问答贡献
xusiwei1236
8
个答案
2
次被采纳
踩姑娘的小蘑菇
1
个答案
2
次被采纳
用户名由3_15位
9
个答案
1
次被采纳
bernard
4
个答案
1
次被采纳
RTT_逍遥
3
个答案
1
次被采纳
本月文章贡献
聚散无由
2
篇文章
15
次点赞
catcatbing
2
篇文章
5
次点赞
Wade
2
篇文章
4
次点赞
Ghost_Girls
1
篇文章
7
次点赞
xiaorui
1
篇文章
2
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部