diff --git a/applications/VK_Client.c b/applications/VK_Client.c index c5448a6..64ad0b4 100644 --- a/applications/VK_Client.c +++ b/applications/VK_Client.c @@ -35,10 +35,10 @@ no_carrier_match_t _no_carrier_t = {0}; /*关闭TCP连接*/ void client_tcp_close(Cclient_t *client) { + client->connect_sta = false; 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){ @@ -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软件复位*/ int16_t EC800K_Reset(void) { int16_t rst = -1; + lte_diag_module_reset(); + + BSP_LTE_SetDataTxPaused(true); lte_dev.config_sta = false; lte_dev.reset_flag = DTU_CF_NEED_RESET; lte_dev.client1->connect_sta = false; @@ -207,15 +267,17 @@ int16_t client_tcp_connect(Cclient_t *client) { if (client->connect_sta == true) return rtn; + lte_diag_connect_attempt(); + if (lte_dev.reset_flag == DTU_CF_NEED_RESET) { /*查询PDP场景*/ if (0 != BSP_LTE_SendCmd(AT_QIACT, "+QIACT: 1,1,1,", DEFAULT_ERROR, - LTE_CMD_WAIT_LLONG)) { + LTE_CMD_WAIT_QIACT)) { /*如果查询PDP场景未设置成功,则激活PDP场景模式*/ 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, - LTE_CMD_WAIT_LLONG); + LTE_CMD_WAIT_QIDEACT); // client->error_cnt++; 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, - LTE_CMD_WAIT_LLONG); + LTE_CMD_WAIT_NORMAL); 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. 检查网络信号 @@ -236,13 +302,10 @@ int16_t client_tcp_connect(Cclient_t *client) { memset(cmd_str, 0, sizeof(cmd_str)); rt_sprintf(cmd_cmd, AT_QIOPEN_TRANS /*AT_QIOPEN_FORW*/, client->connect_ID, 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, - 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); #ifdef debug_mode EC800K_Reset(); @@ -251,13 +314,17 @@ int16_t client_tcp_connect(Cclient_t *client) { #else client->error_cnt++; if (client->error_cnt > 5) { + lte_diag_disconnect(LTE_DISC_QIOPEN_FAILED); EC800K_Reset(); client->error_cnt = 0; return 0; } + rt_thread_mdelay((client->error_cnt < 5 ? client->error_cnt : 5) * 1000); #endif } client->connect_sta = true; + BSP_LTE_SetDataTxPaused(false); + lte_diag_connect_success(); client->error_cnt = 0; 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); 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(); 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", 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); #endif @@ -301,17 +368,58 @@ int16_t client_tcp_connect(Cclient_t *client) { return rtn; } +#if LTE_SERVER_SILENCE_TIMEOUT_SEC > 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) { if (pLte_dev->client1->connect_sta == true) { 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 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, sizeof(pLte_dev->at_buf)); if (len > 0) { + lte_diag_uart2_rx((rt_size_t)len); /*收到TCP关闭的消息*/ 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; } 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; break; } 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) { no_carrier_match(pLte_dev->at_buf, len); 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; break; } } 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) { - 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{ break; } - last_rx_tick = rt_tick_get(); +#if LTE_SERVER_SILENCE_TIMEOUT_SEC > 0 + last_rx_tick = rt_tick_get(); +#endif } } @@ -365,11 +483,12 @@ int16_t client_yield(CLte_dev_t *pLte_dev) { BSP_LTE_Config(); } client_tcp_connect(pLte_dev->client1); +#if LTE_SERVER_SILENCE_TIMEOUT_SEC > 0 last_rx_tick = rt_tick_get(); +#endif } } - return rtn; } void client_task(void) { diff --git a/applications/VK_Client.h b/applications/VK_Client.h index d171fcf..fc9b604 100644 --- a/applications/VK_Client.h +++ b/applications/VK_Client.h @@ -17,7 +17,7 @@ typedef struct { void client_task(void); -int16_t client_yield( CLte_dev_t *lte_dev ); +void client_yield( CLte_dev_t *lte_dev ); #ifdef __cplusplus } diff --git a/applications/VK_Com.c b/applications/VK_Com.c index 2d5e439..1a68f10 100644 --- a/applications/VK_Com.c +++ b/applications/VK_Com.c @@ -4,9 +4,53 @@ #include "rtdef.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 +#endif + rt_device_t fmu_com = 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) { if (fmu_com_rx_sem != RT_NULL) { rt_sem_release(fmu_com_rx_sem); @@ -33,22 +77,32 @@ void com_task(void) { static uint8_t buff[128]; uint32_t len = 0; +#if ENABLE_MEMORY_USAGE_LOG + rt_tick_t memory_log_start = rt_tick_get(); +#endif while (1) { if (rt_sem_take(fmu_com_rx_sem, 1000) == RT_EOK) { while (1) { len = rt_device_read(fmu_com, 0, buff, sizeof(buff)); 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连接时转发 */ - rt_device_write(lte_dev.com_dev, 0, buff, len); + BSP_LTE_SendData(buff, len); } } else { 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 } } - - diff --git a/applications/drv_ec800k.c b/applications/drv_ec800k.c index a971c5a..709e1a8 100644 --- a/applications/drv_ec800k.c +++ b/applications/drv_ec800k.c @@ -46,11 +46,104 @@ CLte_dev_t lte_dev = { .config_sta = false, .reset_flag = DTU_CF_NEED_RESET, .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) { 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_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; @@ -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, uint32_t waittime) { + lte_diag_at_begin(cmd); LOG_I("%s\n", 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_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); /* 清空缓存 */ @@ -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)) { + lte_diag_at_end(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; } else { + lte_diag_at_end(2); return 2; } } else { + lte_diag_at_end(-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) { + lte_diag.uart2_rx_events++; if (lte_dev.com_dev_rx_sem != RT_NULL) { 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; + 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); } /* diff --git a/applications/drv_ec800k.h b/applications/drv_ec800k.h index 0ae2850..3d6d5ae 100644 --- a/applications/drv_ec800k.h +++ b/applications/drv_ec800k.h @@ -197,6 +197,17 @@ extern "C" { //AT CFG #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_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 typedef struct _connect_state { @@ -223,6 +234,63 @@ typedef struct { unsigned char error_cnt; } 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 { char hard_ver[32]; char imei[32]; @@ -241,6 +309,7 @@ typedef struct { rt_device_t com_dev; rt_sem_t com_dev_rx_sem; + rt_mutex_t data_tx_mutex; rt_uint8_t at_buf[256]; rt_thread_t tid1; @@ -248,6 +317,7 @@ typedef struct { bool reset_flag; bool dev_init_flag; + volatile bool data_tx_paused; } CLte_dev_t; extern CLte_dev_t lte_dev; @@ -282,6 +352,7 @@ void BSP_LTE_PwrOff(void); void BSP_LTE_ResetByKey(void); 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); diff --git a/applications/main.c b/applications/main.c index 5ce6387..d49f795 100644 --- a/applications/main.c +++ b/applications/main.c @@ -56,7 +56,7 @@ int main(void) { /* 创建任务2和任务3,分别从U2和U3收发数据 */ 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) { rt_thread_startup(lte_dev.tid1); } diff --git a/docs/EC800K_TCP透传模式优化方案.md b/docs/EC800K_TCP透传模式优化方案.md new file mode 100644 index 0000000..457c48b --- /dev/null +++ b/docs/EC800K_TCP透传模式优化方案.md @@ -0,0 +1,250 @@ +# EC800K TCP 透传模式优化方案 + +## 1. 适用范围 + +当前工程使用 Quectel EC800K,通过 UART2 连接模块,通过 `AT+QIOPEN` 的透传模式承载 TCP 数据: + +```text +AT+QIOPEN=1,0,"TCP",,,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:,` 后再决定成功或失败,不能在一个循环条件中重复发送同一条 `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 识别。 diff --git a/libraries/HAL_Drivers/drivers/drv_usart_v2.c b/libraries/HAL_Drivers/drivers/drv_usart_v2.c index 225180d..0379d78 100644 --- a/libraries/HAL_Drivers/drivers/drv_usart_v2.c +++ b/libraries/HAL_Drivers/drivers/drv_usart_v2.c @@ -11,6 +11,8 @@ #include "board.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 //#define DRV_DEBUG @@ -1237,6 +1239,7 @@ void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) RT_ASSERT(huart != NULL); struct stm32_uart *uart = (struct stm32_uart *)huart; LOG_D("%s: %s %d\n", __FUNCTION__, uart->config->name, huart->ErrorCode); + lte_diag_uart_error(uart->config->name, huart->ErrorCode); UNUSED(uart); }