Browse Source

feat: improve EC800K transparent TCP recovery

dev-rebuild
蔡享 1 month ago
parent
commit
2e5b41a6a6
  1. 191
      applications/VK_Client.c
  2. 2
      applications/VK_Client.h
  3. 62
      applications/VK_Com.c
  4. 120
      applications/drv_ec800k.c
  5. 71
      applications/drv_ec800k.h
  6. 2
      applications/main.c
  7. 250
      docs/EC800K_TCP透传模式优化方案.md
  8. 3
      libraries/HAL_Drivers/drivers/drv_usart_v2.c

191
applications/VK_Client.c

@ -35,10 +35,10 @@ no_carrier_match_t _no_carrier_t = {0};
/*关闭TCP连接*/ /*关闭TCP连接*/
void client_tcp_close(Cclient_t *client) { void client_tcp_close(Cclient_t *client) {
client->connect_sta = false;
rt_sprintf(cmd_str, AT_QICLOSE, client->connect_ID); rt_sprintf(cmd_str, AT_QICLOSE, client->connect_ID);
BSP_LTE_SendCmd(cmd_str, DEFAULT_EXPECT, DEFAULT_ERROR, LTE_CMD_WAIT_NORMAL);
// client->connect_sta = false;
// client->error_cnt = 0;
BSP_LTE_SendCmd(cmd_str, DEFAULT_EXPECT, DEFAULT_ERROR, LTE_CMD_WAIT_QICLOSE);
client->error_cnt = 0;
} }
static int no_carrier_char_parser(uint8_t ch){ static int no_carrier_char_parser(uint8_t ch){
@ -139,11 +139,71 @@ static void no_carrier_match(uint8_t * at_buf,rt_size_t len){
} }
} }
static const char *disconnect_reason_name(lte_disconnect_reason_t reason) {
switch (reason) {
case LTE_DISC_CLOSED: return "CLOSED";
case LTE_DISC_NO_CARRIER: return "NO_CARRIER";
case LTE_DISC_NO_CARRIER_SPLIT: return "NO_CARRIER_SPLIT";
case LTE_DISC_AT_TIMEOUT: return "AT_TIMEOUT";
case LTE_DISC_QIOPEN_FAILED: return "QIOPEN_FAILED";
case LTE_DISC_MODULE_RESET: return "MODULE_RESET";
default: return "UNKNOWN";
}
}
static rt_tick_t diag_age(rt_tick_t now, rt_tick_t tick) {
return tick == 0 ? 0 : now - tick;
}
void lte_diag_disconnect(lte_disconnect_reason_t reason) {
rt_tick_t now = rt_tick_get();
rt_size_t heap_total = 0;
rt_size_t heap_used = 0;
rt_size_t heap_max = 0;
rt_memory_info(&heap_total, &heap_used, &heap_max);
lte_diag.disconnect_count++;
lte_diag.last_disconnect_reason = reason;
lte_diag.last_disconnect_tick = now;
LOG_I("[DISC] reason=%s count=%u connected_ticks=%u",
disconnect_reason_name(reason), lte_diag.disconnect_count,
lte_diag.connect_tick == 0 ? 0 : now - lte_diag.connect_tick);
LOG_I("[DISC] uart2_rx=%u uart2_tx=%u events=%u write_fail=%u errors=%u/%u last_err=0x%X",
lte_diag.uart2_rx_bytes, lte_diag.uart2_tx_bytes,
lte_diag.uart2_rx_events, lte_diag.uart2_write_failures,
lte_diag.uart2_error_count, lte_diag.uart3_error_count,
lte_diag.last_uart_error_code);
LOG_I("[DISC] fmu_rx=%u fmu_tx=%u server_rx=%u read_events=%u silence_timeout=%u",
lte_diag.fmu_rx_bytes, lte_diag.fmu_tx_bytes,
lte_diag.server_rx_bytes, lte_diag.server_rx_read_events,
lte_diag.server_silence_timeout_count);
LOG_I("[DISC] age_uart2_rx=%u age_uart2_tx=%u age_fmu_rx=%u age_server_rx=%u",
diag_age(now, lte_diag.last_uart2_rx_tick),
diag_age(now, lte_diag.last_uart2_tx_tick),
diag_age(now, lte_diag.last_fmu_rx_tick),
diag_age(now, lte_diag.last_server_rx_tick));
LOG_I("[DISC] at_cmd=%s result=%d elapsed_ticks=%u timeouts=%u errors=%u",
lte_diag.last_at_cmd, lte_diag.last_at_result,
lte_diag.last_at_elapsed, lte_diag.at_timeouts, lte_diag.at_errors);
LOG_I("[DISC] attempts=%u reconnects=%u qioopen_fail=%u resets=%u",
lte_diag.connect_attempts, lte_diag.reconnect_count,
lte_diag.qioopen_failures, lte_diag.reset_count);
LOG_I("[DISC] csq=%d cereg=%u pdp=%u heap=%u/%u min_free=%u",
lte_dev.csq, lte_dev.eps_sta, lte_dev.reset_flag == DTU_CF_NO_NEED_RESET,
heap_used, heap_total, heap_total - heap_max);
lte_dev.client1->connect_sta = false;
}
/*将EC800K软件复位*/ /*将EC800K软件复位*/
int16_t EC800K_Reset(void) { int16_t EC800K_Reset(void) {
int16_t rst = -1; int16_t rst = -1;
lte_diag_module_reset();
BSP_LTE_SetDataTxPaused(true);
lte_dev.config_sta = false; lte_dev.config_sta = false;
lte_dev.reset_flag = DTU_CF_NEED_RESET; lte_dev.reset_flag = DTU_CF_NEED_RESET;
lte_dev.client1->connect_sta = false; lte_dev.client1->connect_sta = false;
@ -207,15 +267,17 @@ int16_t client_tcp_connect(Cclient_t *client) {
if (client->connect_sta == true) if (client->connect_sta == true)
return rtn; return rtn;
lte_diag_connect_attempt();
if (lte_dev.reset_flag == DTU_CF_NEED_RESET) { if (lte_dev.reset_flag == DTU_CF_NEED_RESET) {
/*查询PDP场景*/ /*查询PDP场景*/
if (0 != BSP_LTE_SendCmd(AT_QIACT, "+QIACT: 1,1,1,", DEFAULT_ERROR, if (0 != BSP_LTE_SendCmd(AT_QIACT, "+QIACT: 1,1,1,", DEFAULT_ERROR,
LTE_CMD_WAIT_LLONG)) {
LTE_CMD_WAIT_QIACT)) {
/*如果查询PDP场景未设置成功,则激活PDP场景模式*/ /*如果查询PDP场景未设置成功,则激活PDP场景模式*/
if (0 != BSP_LTE_SendCmd(AT_QIACT1, DEFAULT_EXPECT, DEFAULT_ERROR, if (0 != BSP_LTE_SendCmd(AT_QIACT1, DEFAULT_EXPECT, DEFAULT_ERROR,
LTE_CMD_WAIT_LLONG)) {
LTE_CMD_WAIT_QIACT)) {
BSP_LTE_SendCmd(AT_QIDEACT, NETCLOSE_SUCCESS, NETCLOSE_FAILURE, BSP_LTE_SendCmd(AT_QIDEACT, NETCLOSE_SUCCESS, NETCLOSE_FAILURE,
LTE_CMD_WAIT_LLONG);
LTE_CMD_WAIT_QIDEACT);
// client->error_cnt++; // client->error_cnt++;
return -1; return -1;
} }
@ -223,9 +285,13 @@ int16_t client_tcp_connect(Cclient_t *client) {
// //
BSP_LTE_SendCmd(AT_CFG_TRANS_SIZE, DEFAULT_EXPECT, DEFAULT_ERROR, BSP_LTE_SendCmd(AT_CFG_TRANS_SIZE, DEFAULT_EXPECT, DEFAULT_ERROR,
LTE_CMD_WAIT_LLONG);
LTE_CMD_WAIT_NORMAL);
BSP_LTE_SendCmd(AT_CFG_TRANS_WAITTM, DEFAULT_EXPECT, DEFAULT_ERROR, BSP_LTE_SendCmd(AT_CFG_TRANS_WAITTM, DEFAULT_EXPECT, DEFAULT_ERROR,
LTE_CMD_WAIT_LLONG);
LTE_CMD_WAIT_NORMAL);
BSP_LTE_SendCmd(AT_CFG_PASSIVE_CLOSED, DEFAULT_EXPECT, DEFAULT_ERROR,
LTE_CMD_WAIT_NORMAL);
BSP_LTE_SendCmd(AT_CFG_TCP_KEEPALIVE, DEFAULT_EXPECT, DEFAULT_ERROR,
LTE_CMD_WAIT_NORMAL);
// //
} }
// 1. 检查网络信号 // 1. 检查网络信号
@ -236,13 +302,10 @@ int16_t client_tcp_connect(Cclient_t *client) {
memset(cmd_str, 0, sizeof(cmd_str)); memset(cmd_str, 0, sizeof(cmd_str));
rt_sprintf(cmd_cmd, AT_QIOPEN_TRANS /*AT_QIOPEN_FORW*/, client->connect_ID, rt_sprintf(cmd_cmd, AT_QIOPEN_TRANS /*AT_QIOPEN_FORW*/, client->connect_ID,
client->mode, client->ip, client->port); client->mode, client->ip, client->port);
rt_sprintf(cmd_str, "+QIOPEN: %d,0", client->connect_ID);
/*打开socket, 这个模块他不返回+QIOPEN:字符串*/
/* 透传模式成功返回 CONNECT;一次尝试只发送一次 QIOPEN。 */
while (0 != BSP_LTE_SendCmd(cmd_cmd, CONNECTE_OK, DEFAULT_ERROR, while (0 != BSP_LTE_SendCmd(cmd_cmd, CONNECTE_OK, DEFAULT_ERROR,
LTE_CMD_WAIT_LLONG) &&
0 != BSP_LTE_SendCmd(cmd_cmd, cmd_str, DEFAULT_ERROR,
LTE_CMD_WAIT_LLONG)) {
LTE_CMD_WAIT_QIOPEN)) {
lte_diag.qioopen_failures++;
client_tcp_close(client); client_tcp_close(client);
#ifdef debug_mode #ifdef debug_mode
EC800K_Reset(); EC800K_Reset();
@ -251,13 +314,17 @@ int16_t client_tcp_connect(Cclient_t *client) {
#else #else
client->error_cnt++; client->error_cnt++;
if (client->error_cnt > 5) { if (client->error_cnt > 5) {
lte_diag_disconnect(LTE_DISC_QIOPEN_FAILED);
EC800K_Reset(); EC800K_Reset();
client->error_cnt = 0; client->error_cnt = 0;
return 0; return 0;
} }
rt_thread_mdelay((client->error_cnt < 5 ? client->error_cnt : 5) * 1000);
#endif #endif
} }
client->connect_sta = true; client->connect_sta = true;
BSP_LTE_SetDataTxPaused(false);
lte_diag_connect_success();
client->error_cnt = 0; client->error_cnt = 0;
lte_dev.reset_flag = DTU_CF_NO_NEED_RESET; lte_dev.reset_flag = DTU_CF_NO_NEED_RESET;
@ -278,7 +345,7 @@ int16_t client_tcp_connect(Cclient_t *client) {
mav_len = mavlink_msg_to_send_buffer(mav_buf, &msg); mav_len = mavlink_msg_to_send_buffer(mav_buf, &msg);
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
rt_device_write(lte_dev.com_dev, 0, mav_buf, mav_len);
BSP_LTE_SendData(mav_buf, mav_len);
} }
rt_device_t fmu_com = fmu_com_get(); rt_device_t fmu_com = fmu_com_get();
if (fmu_com != RT_NULL) { if (fmu_com != RT_NULL) {
@ -289,11 +356,11 @@ int16_t client_tcp_connect(Cclient_t *client) {
rt_sprintf(buf, "{\"sn\":\"%s\",\"client_id\":\"%s\",\"csq\":\"%d\"}\r\n", rt_sprintf(buf, "{\"sn\":\"%s\",\"client_id\":\"%s\",\"csq\":\"%d\"}\r\n",
lte_dev.iccid, lte_dev.imei, lte_dev.csq); lte_dev.iccid, lte_dev.imei, lte_dev.csq);
rt_device_write(lte_dev.com_dev, 0, buf, sizeof(buf));
BSP_LTE_SendData((uint8_t *)buf, sizeof(buf));
rt_device_write(lte_dev.com_dev, 0, buf, sizeof(buf));
BSP_LTE_SendData((uint8_t *)buf, sizeof(buf));
rt_device_write(lte_dev.com_dev, 0, buf, sizeof(buf));
BSP_LTE_SendData((uint8_t *)buf, sizeof(buf));
//hw_printf(buf); //hw_printf(buf);
#endif #endif
@ -301,17 +368,58 @@ int16_t client_tcp_connect(Cclient_t *client) {
return rtn; return rtn;
} }
#if LTE_SERVER_SILENCE_TIMEOUT_SEC > 0
static rt_tick_t last_rx_tick = 0; static rt_tick_t last_rx_tick = 0;
int16_t client_yield(CLte_dev_t *pLte_dev) {
int16_t rtn = 0;
#endif
/* Leave transparent mode only after the UART has been quiet for the
* required guard time. The caller keeps data_tx_paused asserted until the
* next successful QIOPEN. */
static bool client_exit_transparent_mode(CLte_dev_t *pLte_dev) {
rt_tick_t deadline;
rt_ssize_t len;
BSP_LTE_SetDataTxPaused(true);
rt_thread_mdelay(1000);
BSP_LTE_SendData((uint8_t *)"+++", 3);
rt_thread_mdelay(1000);
deadline = rt_tick_get() + 2000;
while ((rt_int32_t)(rt_tick_get() - deadline) < 0) {
if (pLte_dev->com_dev_rx_sem != RT_NULL) {
rt_sem_take(pLte_dev->com_dev_rx_sem, 100);
}
rt_memset(pLte_dev->at_buf, 0, sizeof(pLte_dev->at_buf));
len = rt_device_read(pLte_dev->com_dev, 0, pLte_dev->at_buf,
sizeof(pLte_dev->at_buf) - 1);
if (len > 0) {
lte_diag_uart2_rx((rt_size_t)len);
pLte_dev->at_buf[len] = '\0';
if (rt_strstr((const char *)pLte_dev->at_buf, DEFAULT_EXPECT) != RT_NULL) {
return true;
}
}
}
return false;
}
void client_yield(CLte_dev_t *pLte_dev) {
while (1) { while (1) {
if (pLte_dev->client1->connect_sta == true) { if (pLte_dev->client1->connect_sta == true) {
rt_err_t ret = rt_sem_take(pLte_dev->com_dev_rx_sem, 1000); rt_err_t ret = rt_sem_take(pLte_dev->com_dev_rx_sem, 1000);
#ifdef EDU_TRAINING
if (rt_tick_get() - last_rx_tick > RT_TICK_PER_SECOND * 6) {
LOG_I("timeout!!! reconnect.\n");
pLte_dev->client1->connect_sta = false;
#if LTE_SERVER_SILENCE_TIMEOUT_SEC > 0
if (ret != RT_EOK && rt_tick_get() - last_rx_tick >
RT_TICK_PER_SECOND * LTE_SERVER_SILENCE_TIMEOUT_SEC) {
lte_diag.server_silence_timeout_count++;
LOG_W("server silence timeout, reconnect\n");
if (client_exit_transparent_mode(pLte_dev)) {
pLte_dev->client1->connect_sta = false;
client_tcp_close(pLte_dev->client1);
} else {
lte_diag_disconnect(LTE_DISC_AT_TIMEOUT);
EC800K_Reset();
}
continue;
} }
#endif #endif
if (RT_EOK == ret) { if (RT_EOK == ret) {
@ -321,18 +429,18 @@ int16_t client_yield(CLte_dev_t *pLte_dev) {
len = rt_device_read(pLte_dev->com_dev, 0, pLte_dev->at_buf, len = rt_device_read(pLte_dev->com_dev, 0, pLte_dev->at_buf,
sizeof(pLte_dev->at_buf)); sizeof(pLte_dev->at_buf));
if (len > 0) { if (len > 0) {
lte_diag_uart2_rx((rt_size_t)len);
/*收到TCP关闭的消息*/ /*收到TCP关闭的消息*/
if (strstr((const char *)pLte_dev->at_buf, CLOSED)) { if (strstr((const char *)pLte_dev->at_buf, CLOSED)) {
memset(cmd_str, 0, sizeof(cmd_str));
rt_sprintf(cmd_str, "\"closed\",%d", pLte_dev->client1->connect_ID);
if (NULL != strstr((char *)lte_dev.at_buf, cmd_str)) {
EC800K_Reset();
pLte_dev->client1->connect_sta = false;
break;
}
LOG_W("[URC] closed received");
lte_diag_disconnect(LTE_DISC_CLOSED);
BSP_LTE_SetDataTxPaused(true);
client_tcp_close(pLte_dev->client1);
break; break;
} else if (strstr((const char *)lte_dev.at_buf, "NO CARRIER")) { } else if (strstr((const char *)lte_dev.at_buf, "NO CARRIER")) {
pLte_dev->client1->connect_sta = false;
lte_diag_disconnect(LTE_DISC_NO_CARRIER);
BSP_LTE_SetDataTxPaused(true);
client_tcp_close(pLte_dev->client1);
_no_carrier_t.match_flag = NO_MATCH; _no_carrier_t.match_flag = NO_MATCH;
break; break;
} else if(strstr((const char *)lte_dev.at_buf, "\r\n")) { } else if(strstr((const char *)lte_dev.at_buf, "\r\n")) {
@ -342,21 +450,31 @@ int16_t client_yield(CLte_dev_t *pLte_dev) {
if (_no_carrier_t.match_flag == HALF_MATCH) { if (_no_carrier_t.match_flag == HALF_MATCH) {
no_carrier_match(pLte_dev->at_buf, len); no_carrier_match(pLte_dev->at_buf, len);
if (_no_carrier_t.match_flag == COMPLETE_MATCH) { if (_no_carrier_t.match_flag == COMPLETE_MATCH) {
LOG_I("disconnect!!!\n");
pLte_dev->client1->connect_sta = false;
lte_diag_disconnect(LTE_DISC_NO_CARRIER_SPLIT);
BSP_LTE_SetDataTxPaused(true);
client_tcp_close(pLte_dev->client1);
_no_carrier_t.match_flag = NO_MATCH; _no_carrier_t.match_flag = NO_MATCH;
break; break;
} }
} }
rt_device_t fmu_com = fmu_com_get(); rt_device_t fmu_com = fmu_com_get();
lte_diag.server_rx_bytes += len;
lte_diag.server_rx_read_events++;
lte_diag.last_server_rx_tick = rt_tick_get();
if (fmu_com) { if (fmu_com) {
rt_device_write(fmu_com, 0, pLte_dev->at_buf, len);
rt_ssize_t written = rt_device_write(fmu_com, 0,
pLte_dev->at_buf, len);
if (written > 0) {
lte_diag_fmu_tx((rt_size_t)written);
}
} }
}else{ }else{
break; break;
} }
#if LTE_SERVER_SILENCE_TIMEOUT_SEC > 0
last_rx_tick = rt_tick_get(); last_rx_tick = rt_tick_get();
#endif
} }
} }
@ -365,11 +483,12 @@ int16_t client_yield(CLte_dev_t *pLte_dev) {
BSP_LTE_Config(); BSP_LTE_Config();
} }
client_tcp_connect(pLte_dev->client1); client_tcp_connect(pLte_dev->client1);
#if LTE_SERVER_SILENCE_TIMEOUT_SEC > 0
last_rx_tick = rt_tick_get(); last_rx_tick = rt_tick_get();
#endif
} }
} }
return rtn;
} }
void client_task(void) { void client_task(void) {

2
applications/VK_Client.h

@ -17,7 +17,7 @@ typedef struct {
void client_task(void); void client_task(void);
int16_t client_yield( CLte_dev_t *lte_dev );
void client_yield( CLte_dev_t *lte_dev );
#ifdef __cplusplus #ifdef __cplusplus
} }

62
applications/VK_Com.c

@ -4,9 +4,53 @@
#include "rtdef.h" #include "rtdef.h"
#include "rtthread.h" #include "rtthread.h"
#define ENABLE_MEMORY_USAGE_LOG 0
#if ENABLE_MEMORY_USAGE_LOG
#define DBG_TAG "MEM_LOG"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
#endif
rt_device_t fmu_com = RT_NULL; rt_device_t fmu_com = RT_NULL;
rt_sem_t fmu_com_rx_sem = RT_NULL; rt_sem_t fmu_com_rx_sem = RT_NULL;
#if ENABLE_MEMORY_USAGE_LOG
static rt_size_t thread_stack_used(rt_thread_t thread) {
rt_uint8_t *stack;
rt_size_t unused = 0;
if (thread == RT_NULL || thread->stack_addr == RT_NULL) {
return 0;
}
stack = (rt_uint8_t *)thread->stack_addr;
while (unused < thread->stack_size && stack[unused] == '#') {
unused++;
}
return thread->stack_size - unused;
}
static void log_memory_usage(void) {
rt_size_t total = 0;
rt_size_t used = 0;
rt_size_t max_used = 0;
rt_thread_t lte_thread;
rt_memory_info(&total, &used, &max_used);
LOG_I("heap used=%u total=%u free=%u max=%u", used, total, total - used,
max_used);
lte_thread = rt_thread_find("lte");
if (lte_thread != RT_NULL) {
rt_size_t stack_used = thread_stack_used(lte_thread);
LOG_I("lte stack used=%u total=%u free=%u", stack_used,
lte_thread->stack_size, lte_thread->stack_size - stack_used);
}
}
#endif
static rt_err_t fmu_com_rx_cb(rt_device_t dev, rt_size_t size) { static rt_err_t fmu_com_rx_cb(rt_device_t dev, rt_size_t size) {
if (fmu_com_rx_sem != RT_NULL) { if (fmu_com_rx_sem != RT_NULL) {
rt_sem_release(fmu_com_rx_sem); rt_sem_release(fmu_com_rx_sem);
@ -33,22 +77,32 @@ void com_task(void) {
static uint8_t buff[128]; static uint8_t buff[128];
uint32_t len = 0; uint32_t len = 0;
#if ENABLE_MEMORY_USAGE_LOG
rt_tick_t memory_log_start = rt_tick_get();
#endif
while (1) { while (1) {
if (rt_sem_take(fmu_com_rx_sem, 1000) == RT_EOK) { if (rt_sem_take(fmu_com_rx_sem, 1000) == RT_EOK) {
while (1) { while (1) {
len = rt_device_read(fmu_com, 0, buff, sizeof(buff)); len = rt_device_read(fmu_com, 0, buff, sizeof(buff));
if (len > 0) { if (len > 0) {
if (lte_dev.client1->connect_sta == true) {
lte_diag_fmu_rx(len);
if (lte_dev.client1->connect_sta == true &&
lte_dev.data_tx_paused == false) {
/* 仅在客户端2连接时转发 */ /* 仅在客户端2连接时转发 */
rt_device_write(lte_dev.com_dev, 0, buff, len);
BSP_LTE_SendData(buff, len);
} }
} else { } else {
break; break;
} }
} }
} }
#if ENABLE_MEMORY_USAGE_LOG
if (rt_tick_get() - memory_log_start >= RT_TICK_PER_SECOND * 5) {
memory_log_start = rt_tick_get();
log_memory_usage();
}
#endif
} }
} }

120
applications/drv_ec800k.c

@ -46,11 +46,104 @@ CLte_dev_t lte_dev = {
.config_sta = false, .config_sta = false,
.reset_flag = DTU_CF_NEED_RESET, .reset_flag = DTU_CF_NEED_RESET,
.dev_init_flag = true, .dev_init_flag = true,
.data_tx_paused = false,
}; };
lte_diag_t lte_diag = {0};
void lte_diag_at_begin(const char *cmd) {
rt_size_t i;
lte_diag.at_commands++;
lte_diag.last_at_elapsed = rt_tick_get();
lte_diag.last_at_result = -2;
for (i = 0; i < sizeof(lte_diag.last_at_cmd) - 1 && cmd[i] != '\0'; i++) {
lte_diag.last_at_cmd[i] = (cmd[i] == '\r' || cmd[i] == '\n') ? '_' : cmd[i];
}
lte_diag.last_at_cmd[i] = '\0';
}
void lte_diag_at_end(rt_int32_t result) {
rt_tick_t now = rt_tick_get();
lte_diag.last_at_elapsed = now - lte_diag.last_at_elapsed;
lte_diag.last_at_result = result;
if (result == -1) {
lte_diag.at_timeouts++;
} else if (result != 0) {
lte_diag.at_errors++;
}
}
void lte_diag_uart2_rx(rt_size_t size) {
lte_diag.uart2_rx_bytes += size;
lte_diag.last_uart2_rx_tick = rt_tick_get();
}
void lte_diag_uart2_tx(rt_size_t size) {
lte_diag.uart2_tx_bytes += size;
lte_diag.last_uart2_tx_tick = rt_tick_get();
}
void lte_diag_uart_error(const char *name, rt_uint32_t error_code) {
lte_diag.last_uart_error_code = error_code;
if (name != RT_NULL && rt_strcmp(name, "uart2") == 0) {
lte_diag.uart2_error_count++;
} else if (name != RT_NULL && rt_strcmp(name, "uart3") == 0) {
lte_diag.uart3_error_count++;
}
}
void lte_diag_fmu_rx(rt_size_t size) {
lte_diag.fmu_rx_bytes += size;
lte_diag.last_fmu_rx_tick = rt_tick_get();
}
void lte_diag_fmu_tx(rt_size_t size) {
lte_diag.fmu_tx_bytes += size;
lte_diag.last_fmu_tx_tick = rt_tick_get();
}
void lte_diag_connect_attempt(void) {
lte_diag.connect_attempts++;
if (lte_diag.connect_attempts > 1) {
lte_diag.reconnect_count++;
}
}
void lte_diag_connect_success(void) {
lte_diag.connect_tick = rt_tick_get();
lte_diag.last_server_rx_tick = lte_diag.connect_tick;
}
void lte_diag_module_reset(void) {
lte_diag.reset_count++;
}
void BSP_LTE_SendData(uint8_t *pdata, uint32_t length) { void BSP_LTE_SendData(uint8_t *pdata, uint32_t length) {
if (lte_dev.com_dev != RT_NULL) { if (lte_dev.com_dev != RT_NULL) {
rt_device_write(lte_dev.com_dev, 0, pdata, length);
if (lte_dev.data_tx_mutex != RT_NULL) {
rt_mutex_take(lte_dev.data_tx_mutex, RT_WAITING_FOREVER);
}
rt_ssize_t written = rt_device_write(lte_dev.com_dev, 0, pdata, length);
if (written > 0) {
lte_diag_uart2_tx((rt_size_t)written);
}
if (written != (rt_ssize_t)length) {
lte_diag.uart2_write_failures++;
}
if (lte_dev.data_tx_mutex != RT_NULL) {
rt_mutex_release(lte_dev.data_tx_mutex);
}
}
}
void BSP_LTE_SetDataTxPaused(bool paused) {
if (lte_dev.data_tx_mutex != RT_NULL) {
rt_mutex_take(lte_dev.data_tx_mutex, RT_WAITING_FOREVER);
}
lte_dev.data_tx_paused = paused;
if (lte_dev.data_tx_mutex != RT_NULL) {
rt_mutex_release(lte_dev.data_tx_mutex);
} }
} }
@ -63,7 +156,11 @@ int16_t BSP_LTE_CipSend(uint8_t *pdata, uint32_t length, uint32_t waittime) {
rt_memset(lte_dev.at_buf, 0, sizeof(lte_dev.at_buf)); rt_memset(lte_dev.at_buf, 0, sizeof(lte_dev.at_buf));
rt_device_read(lte_dev.com_dev, 0, lte_dev.at_buf, sizeof(lte_dev.at_buf));
rt_ssize_t read_len = rt_device_read(lte_dev.com_dev, 0, lte_dev.at_buf,
sizeof(lte_dev.at_buf));
if (read_len > 0) {
lte_diag_uart2_rx((rt_size_t)read_len);
}
/* 清空缓存 */ /* 清空缓存 */
uint8_t c; uint8_t c;
@ -81,6 +178,7 @@ int16_t BSP_LTE_CipSend(uint8_t *pdata, uint32_t length, uint32_t waittime) {
int16_t BSP_LTE_SendCmd(const char *cmd, const char *expect, const char *error, int16_t BSP_LTE_SendCmd(const char *cmd, const char *expect, const char *error,
uint32_t waittime) { uint32_t waittime) {
lte_diag_at_begin(cmd);
LOG_I("%s\n", cmd); LOG_I("%s\n", cmd);
BSP_LTE_SendData((uint8_t *)cmd, rt_strlen(cmd)); BSP_LTE_SendData((uint8_t *)cmd, rt_strlen(cmd));
@ -90,7 +188,11 @@ int16_t BSP_LTE_SendCmd(const char *cmd, const char *expect, const char *error,
rt_memset(lte_dev.at_buf, 0, sizeof(lte_dev.at_buf)); rt_memset(lte_dev.at_buf, 0, sizeof(lte_dev.at_buf));
rt_device_read(lte_dev.com_dev, 0, lte_dev.at_buf, sizeof(lte_dev.at_buf));
rt_ssize_t read_len = rt_device_read(lte_dev.com_dev, 0, lte_dev.at_buf,
sizeof(lte_dev.at_buf));
if (read_len > 0) {
lte_diag_uart2_rx((rt_size_t)read_len);
}
LOG_I("%s\n", lte_dev.at_buf); LOG_I("%s\n", lte_dev.at_buf);
/* 清空缓存 */ /* 清空缓存 */
@ -99,14 +201,17 @@ int16_t BSP_LTE_SendCmd(const char *cmd, const char *expect, const char *error,
} }
if (expect != NULL && NULL != rt_strstr((char *)lte_dev.at_buf, expect)) { if (expect != NULL && NULL != rt_strstr((char *)lte_dev.at_buf, expect)) {
lte_diag_at_end(0);
return 0; return 0;
} else if (NULL != rt_strstr((char *)lte_dev.at_buf, error) &&
error != NULL) {
} else if (error != NULL && NULL != rt_strstr((char *)lte_dev.at_buf, error)) {
lte_diag_at_end(1);
return 1; return 1;
} else { } else {
lte_diag_at_end(2);
return 2; return 2;
} }
} else { } else {
lte_diag_at_end(-1);
return -1; return -1;
} }
} }
@ -307,6 +412,7 @@ int16_t BSP_LTE_GetCEREG(int16_t retry) {
} }
static rt_err_t lte_com_rx_cb(rt_device_t dev, rt_size_t size) { static rt_err_t lte_com_rx_cb(rt_device_t dev, rt_size_t size) {
lte_diag.uart2_rx_events++;
if (lte_dev.com_dev_rx_sem != RT_NULL) { if (lte_dev.com_dev_rx_sem != RT_NULL) {
rt_sem_release(lte_dev.com_dev_rx_sem); rt_sem_release(lte_dev.com_dev_rx_sem);
} }
@ -319,6 +425,10 @@ void BSP_LET_Register_Com(rt_device_t com_dev) {
} }
lte_dev.com_dev = com_dev; lte_dev.com_dev = com_dev;
if (lte_dev.data_tx_mutex == RT_NULL) {
lte_dev.data_tx_mutex = rt_mutex_create("lteTx", RT_IPC_FLAG_PRIO);
}
BSP_LTE_SetDataTxPaused(false);
} }
/* /*

71
applications/drv_ec800k.h

@ -197,6 +197,17 @@ extern "C" {
//AT CFG //AT CFG
#define AT_CFG_TRANS_SIZE "AT+QICFG=\"transpktsize\",512\r\n" #define AT_CFG_TRANS_SIZE "AT+QICFG=\"transpktsize\",512\r\n"
#define AT_CFG_TRANS_WAITTM "AT+QICFG=\"transwaittm\",0\r\n" #define AT_CFG_TRANS_WAITTM "AT+QICFG=\"transwaittm\",0\r\n"
#define AT_CFG_PASSIVE_CLOSED "AT+QICFG=\"passiveclosed\",1\r\n"
#define AT_CFG_TCP_KEEPALIVE "AT+QICFG=\"tcp/keepalive\",1,30,25,3\r\n"
#define LTE_CMD_WAIT_QIOPEN (150000)
#define LTE_CMD_WAIT_QICLOSE (10000)
#define LTE_CMD_WAIT_QIACT (150000)
#define LTE_CMD_WAIT_QIDEACT (40000)
/* Set after confirming the server MAVLink heartbeat period. 0 disables the
* application-level silence recovery and leaves NO CARRIER/keepalive active. */
#define LTE_SERVER_SILENCE_TIMEOUT_SEC (15)
// #define GSM_BT_LIVE 5*60*1000 // 5min // #define GSM_BT_LIVE 5*60*1000 // 5min
typedef struct _connect_state { typedef struct _connect_state {
@ -223,6 +234,63 @@ typedef struct {
unsigned char error_cnt; unsigned char error_cnt;
} Cclient_t; } Cclient_t;
typedef enum {
LTE_DISC_CLOSED = 0,
LTE_DISC_NO_CARRIER,
LTE_DISC_NO_CARRIER_SPLIT,
LTE_DISC_AT_TIMEOUT,
LTE_DISC_QIOPEN_FAILED,
LTE_DISC_MODULE_RESET,
} lte_disconnect_reason_t;
typedef struct {
rt_uint32_t uart2_rx_bytes;
rt_uint32_t uart2_tx_bytes;
rt_uint32_t uart2_rx_events;
rt_uint32_t uart2_write_failures;
rt_uint32_t uart2_error_count;
rt_uint32_t uart3_error_count;
rt_uint32_t last_uart_error_code;
rt_uint32_t fmu_rx_bytes;
rt_uint32_t fmu_tx_bytes;
rt_uint32_t at_commands;
rt_uint32_t at_timeouts;
rt_uint32_t at_errors;
rt_uint32_t connect_attempts;
rt_uint32_t reconnect_count;
rt_uint32_t qioopen_failures;
rt_uint32_t reset_count;
rt_uint32_t disconnect_count;
rt_uint32_t server_rx_bytes;
rt_uint32_t server_rx_read_events;
rt_uint32_t server_silence_timeout_count;
rt_tick_t last_uart2_rx_tick;
rt_tick_t last_uart2_tx_tick;
rt_tick_t last_fmu_rx_tick;
rt_tick_t last_fmu_tx_tick;
rt_tick_t last_server_rx_tick;
rt_tick_t connect_tick;
rt_tick_t last_disconnect_tick;
rt_tick_t last_at_elapsed;
rt_int32_t last_at_result;
char last_at_cmd[20];
lte_disconnect_reason_t last_disconnect_reason;
} lte_diag_t;
extern lte_diag_t lte_diag;
void lte_diag_at_begin(const char *cmd);
void lte_diag_at_end(rt_int32_t result);
void lte_diag_uart2_rx(rt_size_t size);
void lte_diag_uart2_tx(rt_size_t size);
void lte_diag_uart_error(const char *name, rt_uint32_t error_code);
void lte_diag_fmu_rx(rt_size_t size);
void lte_diag_fmu_tx(rt_size_t size);
void lte_diag_connect_attempt(void);
void lte_diag_connect_success(void);
void lte_diag_disconnect(lte_disconnect_reason_t reason);
void lte_diag_module_reset(void);
typedef struct { typedef struct {
char hard_ver[32]; char hard_ver[32];
char imei[32]; char imei[32];
@ -241,6 +309,7 @@ typedef struct {
rt_device_t com_dev; rt_device_t com_dev;
rt_sem_t com_dev_rx_sem; rt_sem_t com_dev_rx_sem;
rt_mutex_t data_tx_mutex;
rt_uint8_t at_buf[256]; rt_uint8_t at_buf[256];
rt_thread_t tid1; rt_thread_t tid1;
@ -248,6 +317,7 @@ typedef struct {
bool reset_flag; bool reset_flag;
bool dev_init_flag; bool dev_init_flag;
volatile bool data_tx_paused;
} CLte_dev_t; } CLte_dev_t;
extern CLte_dev_t lte_dev; extern CLte_dev_t lte_dev;
@ -282,6 +352,7 @@ void BSP_LTE_PwrOff(void);
void BSP_LTE_ResetByKey(void); void BSP_LTE_ResetByKey(void);
void BSP_LTE_SendData(uint8_t *pdata, uint32_t length); void BSP_LTE_SendData(uint8_t *pdata, uint32_t length);
void BSP_LTE_SetDataTxPaused(bool paused);
int16_t BSP_LTE_SendDataAT(uint8_t *pdata, uint32_t length); int16_t BSP_LTE_SendDataAT(uint8_t *pdata, uint32_t length);

2
applications/main.c

@ -56,7 +56,7 @@ int main(void) {
/* 创建任务2和任务3,分别从U2和U3收发数据 */ /* 创建任务2和任务3,分别从U2和U3收发数据 */
lte_dev.tid1 = lte_dev.tid1 =
rt_thread_create("lte", task_let_entry, RT_NULL, 2048, 10, 5);
rt_thread_create("lte", task_let_entry, RT_NULL, 1024, 10, 5);
if (lte_dev.tid1) { if (lte_dev.tid1) {
rt_thread_startup(lte_dev.tid1); rt_thread_startup(lte_dev.tid1);
} }

250
docs/EC800K_TCP透传模式优化方案.md

@ -0,0 +1,250 @@
# EC800K TCP 透传模式优化方案
## 1. 适用范围
当前工程使用 Quectel EC800K,通过 UART2 连接模块,通过 `AT+QIOPEN` 的透传模式承载 TCP 数据:
```text
AT+QIOPEN=1,0,"TCP",<server>,<port>,0,2
```
飞控数据保持原有透传路径,不增加新的 TCP 连接,也不要求第一阶段解析完整 MAVLink 数据。
## 2. 目标
- 保持飞控数据的透明传输。
- 使用服务器现有的 MAVLink 心跳检测下行链路。
- 使用 EC800K 的 TCP keepalive 处理网络黑洞。
- 正确处理 `NO CARRIER` 和分包 URC。
- TCP 断开时优先关闭 Socket 并重连,只有 AT 状态机无响应时才复位模块。
- 避免重复发送 `QIOPEN` 和连续快速重连。
## 3. 连接初始化
`QIOPEN` 前完成 PDP 激活和透传参数配置:
```text
AT+QIACT?
AT+QIACT=1 // PDP 未激活时执行
AT+QICFG="transpktsize",512
AT+QICFG="transwaittm",0
AT+QICFG="passiveclosed",1
AT+QICFG="tcp/keepalive",1,30,25,3
```
实际配置前先执行:
```text
AT+QICFG=?
```
确认当前 EC800K 固件支持的参数范围。`tcp/keepalive` 中的 `interval_time` 必须遵守当前固件文档范围,不能直接使用其他模块的参数。
配置后查询并记录:
```text
AT+QICFG="passiveclosed"
AT+QICFG="tcp/keepalive"
AT+QICFG="transpktsize"
AT+QICFG="transwaittm"
```
## 4. 透传状态机
建议将连接状态划分为:
```text
DISCONNECTED
CONNECTING
TRANSPARENT_CONNECTED
SUSPECTED
EXITING_TRANSPARENT
CLOSING
RECONNECT_WAIT
```
正常连接流程:
```text
DISCONNECTED
-> QIACT
-> CONNECTING
-> QIOPEN
-> 收到 CONNECT
-> TRANSPARENT_CONNECTED
```
一次连接尝试只能发送一次 `QIOPEN`。等待 `CONNECT``+QIOPEN:<connectID>,<err>` 后再决定成功或失败,不能在一个循环条件中重复发送同一条 `QIOPEN`
重连间隔建议退避:
```text
1 秒 -> 2 秒 -> 5 秒 -> 10 秒 -> 30 秒
```
## 5. 服务器心跳检测
服务器连接建立后会定期发送 MAVLink 心跳包。第一阶段先不解析 MAVLink,只要透传接收路径收到服务器数据,就更新:
```text
last_server_rx_tick
server_rx_bytes
server_rx_read_events
```
设备超时应设置为服务器心跳周期的 2~3 倍:
| 服务器心跳周期 | 建议设备超时 |
|---|---:|
| 3 秒 | 10~12 秒 |
| 5 秒 | 15 秒左右 |
| 10 秒 | 25~30 秒 |
当前已确认服务器每 3 秒发送一次 MAVLink 心跳,工程宏 `LTE_SERVER_SILENCE_TIMEOUT_SEC` 设置为 `15`,允许连续丢失多个心跳后再进入恢复流程。若服务器心跳周期发生变化,应同步调整该值。一般应设置为心跳周期的 2~3 倍,并保留网络抖动余量。
不要只允许丢失一个心跳包就重连。超时后先标记 `SUSPECTED`,停止普通发送,再执行恢复流程。
第一阶段的“任意下行数据”只用于快速验证链路,不能证明收到的必然是 MAVLink HEARTBEAT,也不能证明服务器一定收到了设备上行数据。读取次数也不等于 MAVLink 包数量,因此应记录为 `server_rx_read_events`。后续如果需要严格判断心跳,再使用现有 MAVLink 库只识别 `MAVLINK_MSG_ID_HEARTBEAT`,不解析其他消息。服务器如果没有固定下行心跳,不能使用该方法作为连接判断。
## 6. 断连事件处理
### 6.1 `NO CARRIER`
在透传模式下,EC800K 因网络错误或 Socket 断开通常会上报:
```text
NO CARRIER
```
模块会退出透传模式。程序应支持完整和分包匹配:
```text
记录断连原因
停止 UART3 -> UART2 的发送
connect_sta = false
执行 AT+QICLOSE=0
进入 RECONNECT_WAIT
```
收到 `NO CARRIER` 后通常不需要再次发送 `+++`,因为模块已经退出透传模式。
### 6.2 服务器心跳超时
如果超过配置的心跳超时时间没有收到服务器数据:
```text
TRANSPARENT_CONNECTED
-> SUSPECTED
-> 暂停飞控数据发送
-> 严格退出透传
-> QICLOSE
-> QIOPEN 重连
```
### 6.3 `+++` 退出透传
只有在程序仍处于透传模式、但需要主动恢复时才发送 `+++`。必须满足:
```text
发送前至少 1 秒没有其他 UART 数据
+++ 输入期间没有其他数据
发送后至少 1 秒没有其他数据
等待模块返回 OK
```
执行期间必须暂停飞控发送线程,否则 `+++` 可能被当作普通 TCP 数据发送。
暂停不能只依赖 `connect_sta=false`。所有 UART2 写路径必须检查统一的发送闸门,必要时使用互斥锁,确保停止当前写操作后再等待 1 秒静默。`+++`、退出后的 AT 命令和恢复透传必须与飞控发送互斥。
## 7. Socket 恢复和模块复位
推荐恢复顺序:
```text
NO CARRIER 或心跳超时
-> 停止业务发送
-> 必要时 +++ 并等待 OK
-> AT+QICLOSE=0
-> 查询/恢复 PDP
-> 单次 QIOPEN
-> 收到 CONNECT 后恢复透传
```
以下情况才执行 EC800K 复位:
- `+++` 超时;
- `AT+QICLOSE` 超时;
- `AT+QIACT` 长时间无响应;
- 连续多个 AT 命令无响应;
- UART2 长时间没有任何模块响应。
TCP 断开本身不等于 EC800K 死机,不能每次 `NO CARRIER` 都直接复位模块。
AT 等待时间必须按官方上限设计:`QIACT` 最长可达 150 秒,`QIOPEN` 建议最长等待 150 秒,`QICLOSE` 默认最长约 10 秒,`QIDEACT` 最长可达 40 秒。当前工程的 7 秒和 500 毫秒等待不能直接用于这些命令,否则会把模块仍在处理的请求误判为超时。一次 `QIOPEN` 超时后必须先完成清理和退避,不能立即重复发送。
## 8. TCP keepalive 与业务心跳的关系
```text
AT+QICFG="tcp/keepalive",1,30,25,3
```
用于 TCP 协议栈探测对端是否仍可达。它不能确认服务器业务程序是否处理了飞控心跳,也不能替代服务器的 MAVLink 心跳。
`AT+QICFG="passiveclosed",1` 用于处理服务器 FIN/RST 已经到达模块的被动关闭。网络黑洞导致 FIN 没有到达时,仍需依靠 TCP keepalive 或服务器心跳超时。
## 9. 日志内容
建议在断连日志中记录:
```text
断连原因
server_rx_bytes
server_rx_read_events
age_server_rx
心跳超时次数
当前连接状态
重连退避时间
UART2 收发字节和错误计数
CSQ/CEREG/PDP 状态
AT 最后一条命令、结果和耗时
```
这些信息可以初步区分:
- 模块明确报告 TCP 断开;
- 服务器心跳停止;
- 网络黑洞导致模块尚未收到 FIN;
- UART 丢数据或溢出;
- PDP/注册状态异常;
- AT 状态机无响应。
## 10. 内存预算
第一阶段不解析 MAVLink,只增加计时器、状态和计数器:
```text
纯状态字段:约 32~64 字节
加 UART2 发送互斥对象:通常约 100~300 字节,取决于 RT-Thread 配置
Flash:约 1~3 KB
```
不新增线程,不新增大块动态内存。
如果后续只增加一个 MAVLink HEARTBEAT 识别器,而不是解析所有消息,预计额外增加:
```text
RAM:约 50~100 字节
Flash:约 1~2 KB
```
## 11. 实施顺序
1. 确认服务器实际 MAVLink 心跳周期。
2. 增加 `passiveclosed``tcp/keepalive` 配置及查询日志。
3. 增加服务器下行数据计时器和超时状态。
4. 修正 `NO CARRIER` 分包解析。
5. 修正 `QIOPEN` 重复发送问题。
6. 实现 `QICLOSE` 优先、复位兜底的恢复流程。
7. 实现严格的 `+++` 退出保护。
8. 增加重连退避和断连诊断日志。
9. 根据现场日志决定是否增加轻量级 MAVLink HEARTBEAT 识别。

3
libraries/HAL_Drivers/drivers/drv_usart_v2.c

@ -11,6 +11,8 @@
#include "board.h" #include "board.h"
#include "drv_usart_v2.h" #include "drv_usart_v2.h"
extern void lte_diag_uart_error(const char *name, rt_uint32_t error_code);
#ifdef RT_USING_SERIAL_V2 #ifdef RT_USING_SERIAL_V2
//#define DRV_DEBUG //#define DRV_DEBUG
@ -1237,6 +1239,7 @@ void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
RT_ASSERT(huart != NULL); RT_ASSERT(huart != NULL);
struct stm32_uart *uart = (struct stm32_uart *)huart; struct stm32_uart *uart = (struct stm32_uart *)huart;
LOG_D("%s: %s %d\n", __FUNCTION__, uart->config->name, huart->ErrorCode); LOG_D("%s: %s %d\n", __FUNCTION__, uart->config->name, huart->ErrorCode);
lte_diag_uart_error(uart->config->name, huart->ErrorCode);
UNUSED(uart); UNUSED(uart);
} }

Loading…
Cancel
Save