494 lines
16 KiB
C++
494 lines
16 KiB
C++
/*----------------------------------------------------------------------------
|
||
* Copyright (c) TJD Technologies Co., Ltd. 2025. All rights reserved.
|
||
*
|
||
* Description: service_pgnss.c
|
||
*
|
||
* Author: luziquan@ss-tjd.com
|
||
*
|
||
* Create: 2025-05-09
|
||
*--------------------------------------------------------------------------*/
|
||
|
||
#include "service_pgnss.h"
|
||
#include "gnss_datatypes.h"
|
||
#include "gnss_nmea_parse.h"
|
||
#include "osal_atomic.h"
|
||
#include "osal_task.h"
|
||
#include "pgnss_encode.h"
|
||
#include "pm_definition.h"
|
||
#include "pm_veto.h"
|
||
#include "rtc_api.h"
|
||
#include "securec.h"
|
||
#include "sys_config.h"
|
||
#include "tiot_service_interface.h"
|
||
#include "tiot_service_interface_ext.h"
|
||
#include <stdio.h>
|
||
#include <string>
|
||
|
||
#define ENABLE_PRINT_INFO 1
|
||
#define ENABLE_DEBUG 0
|
||
|
||
#if ENABLE_PRINT_INFO
|
||
#define static_print_info(...) sys_gps_log_i(__VA_ARGS__) //一般信息打印宏控制
|
||
#define static_print_warn(...) sys_gps_log_w(__VA_ARGS__) //警告信息打印一般常开
|
||
#define static_print_error(...) sys_gps_log_e(__VA_ARGS__) //错误信息打印一般常开
|
||
#if ENABLE_DEBUG
|
||
#define static_print_debug(...) sys_gps_log_d(__VA_ARGS__)
|
||
#else
|
||
#define static_print_debug(...)
|
||
#endif
|
||
#else
|
||
#define static_print_info(...)
|
||
#define static_print_warn(...)
|
||
#define static_print_error(...)
|
||
#endif
|
||
|
||
namespace TJD {
|
||
|
||
int GnssNmeaDecodeFrame(const char *nmea, unsigned int nmeaLen, GnssInfo *gnssInfo,
|
||
GnssNmeaFrameProcess *nmeaFrameProcess);
|
||
|
||
GnssNmeaFrameType GnssNmeaGetFrameType(const char *nmeaBuf, unsigned int length);
|
||
|
||
} // namespace TJD
|
||
|
||
#define MSG_TYPE_ASCII 1
|
||
|
||
static GnssServiceContext g_gnssServiceCtx = {
|
||
.gnssEnabled = false,
|
||
.gnssStarted = false,
|
||
.gnssCallback = {},
|
||
.gnssInfo = {},
|
||
// .location = {},
|
||
.satStatus = {},
|
||
.listenGnssDataThreadId = nullptr,
|
||
};
|
||
|
||
static osThreadId_t g_gnssThreadId = NULL;
|
||
static FILE *g_gnssLogFp = NULL;
|
||
static uint8_t *g_gnssServiceBuff = NULL;
|
||
static osMutexId_t g_gnssMutex = NULL;
|
||
|
||
static int32_t QryVersion(tiot_handle handle)
|
||
{
|
||
const uint8_t qryVersion[] = {0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00};
|
||
// 查询版本号
|
||
int32_t ret = tiot_service_write(handle, qryVersion, sizeof(qryVersion));
|
||
if (ret < 0) {
|
||
static_print_error("error: QryVersion error: %d", ret);
|
||
return 0;
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
// 注入PGNSS,使用默认配置启动
|
||
static int32_t InjectPgnssLessFilesCfgAutoStartFixModeAndNmeaDefault(tiot_handle handle)
|
||
{
|
||
// clang-format off
|
||
const uint8_t cfgColdStartCmd[] = {0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01};
|
||
const uint8_t cfgAutoStartCmd[] = {0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00};
|
||
const uint8_t cfgLogLevelInfo[] = {0x03, 0x00, 0x15, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x10, 0x01, 0x00, 0x03};
|
||
const uint8_t cfgFixModeAllCmd[] = {0x03, 0x00, 0x23, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x1f, 0x00, 0x00, 0x00};
|
||
const uint8_t cfgAllNmeaCmd[] = {0x03, 0x00, 0x04, 0x01, 0x00, 0x00, 0x06, 0x00, 0x03, 0x00,0x02, 0x00, 0xff, 0x00};
|
||
// // const uint8_t cfgNmeaDefaultCmd[] = {0x03,0x00,0x16,0x00,0x00,0x00,0x06,0x00,0x03,0x00,0x02,0x00,0x11,0x00};
|
||
// // const uint8_t cfgDisablePmCmd[] = {0x03,0x00,0x07,0x00,0x00,0x00,0x06,0x00,0x05,0x00,0x02,0x00,0x00,0x00};
|
||
// // const uint8_t cfgEnablePmCmd[] = {0x03,0x00,0x0A,0x00,0x00,0x00,0x06,0x00,0x05,0x00,0x02,0x00,0x01,0x02};
|
||
// clang-format on
|
||
|
||
int32_t ret = 0;
|
||
|
||
// 配置启动模式:自动模式。
|
||
ret = tiot_service_write(handle, cfgAutoStartCmd, sizeof(cfgAutoStartCmd));
|
||
if (ret < 0) {
|
||
static_print_error("cfgAutoStartCmd error: %d", ret);
|
||
return 0;
|
||
}
|
||
|
||
// 配置启动模式:冷启动模式。
|
||
// ret = tiot_service_write(handle, cfgColdStartCmd, sizeof(cfgColdStartCmd));
|
||
// if (ret < 0) {
|
||
// static_print_error("cfgColdStartCmd error: %d", ret);
|
||
// return 0;
|
||
// }
|
||
|
||
// 配置log级别:info级别。
|
||
ret = tiot_service_write(handle, cfgLogLevelInfo, sizeof(cfgLogLevelInfo));
|
||
if (ret < 0) {
|
||
static_print_error("cfgLogLevelInfo error: %d", ret);
|
||
return 0;
|
||
}
|
||
|
||
// 配置使能的GNSS星座:QZSS/GAL/BDS/GLO/GPS。
|
||
ret = tiot_service_write(handle, cfgFixModeAllCmd, sizeof(cfgFixModeAllCmd));
|
||
if (ret < 0) {
|
||
static_print_error("cfgFixModeAllCmd error: %d", ret);
|
||
return 0;
|
||
}
|
||
|
||
// 配置上报NMEA类型:PNT/ZDA/VTG/RMC/GSV/GSA/GLL/GGA。
|
||
ret = tiot_service_write(handle, cfgAllNmeaCmd, sizeof(cfgAllNmeaCmd));
|
||
if (ret < 0) {
|
||
static_print_error("cfgAllNmeaCmd error: %d", ret);
|
||
return 0;
|
||
}
|
||
|
||
// ret = tiot_service_write(handle, cfgDisablePmCmd, sizeof(cfgDisablePmCmd));
|
||
// if (ret < 0) {
|
||
// static_print_error("error: cfgDisablePmCmd error: %d", ret);
|
||
// return 0;
|
||
// }
|
||
|
||
// 注入UTC时间, 注入Pgnss辅助数据前必须先注入参考时间
|
||
struct rtc_time time;
|
||
tjd_driver_rtc_get_ops()->get_rtc_utc_time(&time);
|
||
// 需要用当前的UTC时间替换
|
||
GnssUtcTime utcTime = {static_cast<uint16_t>(time.tm_year),
|
||
static_cast<uint8_t>(time.tm_mon),
|
||
static_cast<uint8_t>(time.tm_mday),
|
||
static_cast<uint8_t>(time.tm_hour),
|
||
static_cast<uint8_t>(time.tm_min),
|
||
static_cast<uint8_t>(time.tm_sec),
|
||
0,
|
||
1};
|
||
uint32_t ts = 0;
|
||
ret = PgnssInjectUtcTime(handle, &utcTime, &ts);
|
||
if (ret == 0) {
|
||
static_print_error("error: PgnssInjectUtcTime failed!");
|
||
return 0;
|
||
}
|
||
static_print_debug("PgnssInjectUtcTime success!");
|
||
|
||
// PGNSS注入
|
||
PgnssInjectLessFiles(handle, ts);
|
||
|
||
return 1;
|
||
}
|
||
|
||
static int32_t StartGnss(tiot_handle handle)
|
||
{
|
||
const uint8_t startCmd[] = {0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x01, 0x00,
|
||
0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||
// 启动GNSS
|
||
int32_t ret = tiot_service_write(handle, startCmd, sizeof(startCmd));
|
||
if (ret < 0) {
|
||
static_print_error("error: start gnss error: %d", ret);
|
||
return 0;
|
||
}
|
||
static_print_debug("start gnss");
|
||
return 1;
|
||
}
|
||
|
||
static int32_t StopGnss(tiot_handle handle)
|
||
{
|
||
const uint8_t stopCmd[] = {0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||
// 停止GNSS
|
||
int32_t ret = tiot_service_write(handle, stopCmd, sizeof(stopCmd));
|
||
if (ret < 0) {
|
||
static_print_error("error: stop gnss error: %d", ret);
|
||
return 0;
|
||
}
|
||
static_print_debug("stop gnss");
|
||
return 1;
|
||
}
|
||
|
||
static void ResetGnssInfo(GnssServiceContext *gnssServiceCtx)
|
||
{
|
||
int32_t ret = memset_s(&gnssServiceCtx->gnssInfo, sizeof(GnssInfo), 0, sizeof(GnssInfo));
|
||
if (ret != EOK) {
|
||
static_print_error("memset_s GnssInfo failed:%d", ret);
|
||
}
|
||
}
|
||
|
||
static void ResetLocationSatelliteStatus(GnssServiceContext *gnssServiceCtx)
|
||
{
|
||
int32_t ret = memset_s(&gnssServiceCtx->location, sizeof(GnssLocation), 0, sizeof(GnssLocation));
|
||
if (ret != EOK) {
|
||
static_print_warn("memset_s GnssLocation failed:%d", ret);
|
||
}
|
||
ret = memset_s(&gnssServiceCtx->satStatus, sizeof(GnssSatelliteStatus), 0, sizeof(GnssSatelliteStatus));
|
||
if (ret != EOK) {
|
||
static_print_warn("memset_s GnssSatelliteStatus failed:%d", ret);
|
||
}
|
||
}
|
||
|
||
static int32_t LocationGnssUtilsFillGnssLocation(const GnssInfo *gnssInfo, GnssLocation *location)
|
||
{
|
||
if (gnssInfo->signal == 0) {
|
||
static_print_warn("Location invalid");
|
||
return GNSS_FAILURE;
|
||
}
|
||
location->fieldValidity = GNSS_NMEA_FIELD_TYPE_SMASK;
|
||
if (gnssInfo->field & GNSS_NMEA_FIELD_TYPE_LAT) {
|
||
location->latitude = gnssInfo->lat;
|
||
location->fieldValidity |= GNSS_LOCATION_LAT_VALID;
|
||
}
|
||
if (gnssInfo->field & GNSS_NMEA_FIELD_TYPE_LON) {
|
||
location->longitude = gnssInfo->lon;
|
||
location->fieldValidity |= GNSS_LOCATION_LONG_VALID;
|
||
}
|
||
if (gnssInfo->field & GNSS_NMEA_FIELD_TYPE_ANTALT) {
|
||
location->altitude = gnssInfo->altitude;
|
||
location->fieldValidity |= GNSS_LOCATION_ALTITUDE_VALID;
|
||
}
|
||
if (gnssInfo->field & GNSS_NMEA_FIELD_TYPE_SPEED) {
|
||
location->speed = gnssInfo->speed;
|
||
location->fieldValidity |= GNSS_LOCATION_SPEED_VALID;
|
||
}
|
||
if (gnssInfo->field & GNSS_NMEA_FIELD_TYPE_TRACK) {
|
||
location->bearing = gnssInfo->trackAng;
|
||
location->fieldValidity |= GNSS_LOCATION_BEARING_VALID;
|
||
}
|
||
return GNSS_SUCCESS;
|
||
}
|
||
|
||
GnssQualityLevel GetGnssSignalQualityLevel(const GnssInfo &gnssInfo)
|
||
{
|
||
const GnssSatelliteList *satList = &gnssInfo.satelist;
|
||
GnssQualityLevel quality = GnssQualityLevel::GNSS_QUAL_POOR;
|
||
|
||
int visableSatNum = 0;
|
||
int CnrMax = 0;
|
||
|
||
GnssSatelliteStatusInfo satInfos = satList->gps;
|
||
for (uint32_t i = 0; i < satInfos.satViewedCnt; ++i) {
|
||
if (satInfos.satelliteStatus[i].cnr > CnrMax) {
|
||
CnrMax = satInfos.satelliteStatus[i].cnr;
|
||
}
|
||
}
|
||
visableSatNum += satInfos.satViewedCnt;
|
||
|
||
satInfos = satList->bds;
|
||
for (uint32_t i = 0; i < satInfos.satViewedCnt; ++i) {
|
||
if (satInfos.satelliteStatus[i].cnr > CnrMax) {
|
||
CnrMax = satInfos.satelliteStatus[i].cnr;
|
||
}
|
||
}
|
||
visableSatNum += satInfos.satViewedCnt;
|
||
|
||
satInfos = satList->glonass;
|
||
for (uint32_t i = 0; i < satInfos.satViewedCnt; ++i) {
|
||
if (satInfos.satelliteStatus[i].cnr > CnrMax) {
|
||
CnrMax = satInfos.satelliteStatus[i].cnr;
|
||
}
|
||
}
|
||
visableSatNum += satInfos.satViewedCnt;
|
||
|
||
satInfos = satList->galileo;
|
||
for (uint32_t i = 0; i < satInfos.satViewedCnt; ++i) {
|
||
if (satInfos.satelliteStatus[i].cnr > CnrMax) {
|
||
CnrMax = satInfos.satelliteStatus[i].cnr;
|
||
}
|
||
}
|
||
visableSatNum += satInfos.satViewedCnt;
|
||
|
||
satInfos = satList->qzss;
|
||
for (uint32_t i = 0; i < satInfos.satViewedCnt; ++i) {
|
||
if (satInfos.satelliteStatus[i].cnr > CnrMax) {
|
||
CnrMax = satInfos.satelliteStatus[i].cnr;
|
||
}
|
||
}
|
||
visableSatNum += satInfos.satViewedCnt;
|
||
|
||
if (gnssInfo.hdop <= 1.5 && visableSatNum >= 8 && CnrMax >= 40) {
|
||
quality = GnssQualityLevel::GNSS_QUAL_HIGH; // 开阔环境高精度定位
|
||
} else if (gnssInfo.pdop <= 6 && visableSatNum >= 4 && CnrMax >= 35) {
|
||
quality = GnssQualityLevel::GNSS_QUAL_MEDIUM; // 城市街道常规导航
|
||
} else {
|
||
quality = GnssQualityLevel::GNSS_QUAL_POOR; // 隧道/室内定位失效
|
||
}
|
||
static_print_debug("quality:%d, visableSatNum:%d, CnrMax:%d", quality, visableSatNum, CnrMax);
|
||
return quality;
|
||
}
|
||
|
||
static void GnssContextDispatchEvent(const GnssInfo &gnssInfo, GnssNmeaFrameType nmeaType,
|
||
const GnssNmeaFrameProcess &nmeaFrameProcess)
|
||
{
|
||
int32_t ret = 0;
|
||
switch (nmeaType) {
|
||
case GnssNmeaFrameType::GNSS_NMEA_FRAME_GGA: {
|
||
const GnssNmeaGGA *gga = &nmeaFrameProcess.nmeaFrame.nmeaGGA;
|
||
break;
|
||
}
|
||
case GnssNmeaFrameType::GNSS_NMEA_FRAME_GSV: {
|
||
if (g_gnssServiceCtx.gnssCallback.onGsvUpdate != NULL) {
|
||
g_gnssServiceCtx.gnssCallback.onGsvUpdate(&gnssInfo.satelist);
|
||
}
|
||
if (g_gnssServiceCtx.gnssCallback.onSignalQualityUpdate != NULL) {
|
||
g_gnssServiceCtx.gnssCallback.onSignalQualityUpdate(GetGnssSignalQualityLevel(gnssInfo));
|
||
}
|
||
break;
|
||
}
|
||
case GnssNmeaFrameType::GNSS_NMEA_FRAME_RMC: {
|
||
break;
|
||
}
|
||
case GnssNmeaFrameType::GNSS_NMEA_FRAME_TH002: {
|
||
ResetLocationSatelliteStatus(&g_gnssServiceCtx);
|
||
ret = LocationGnssUtilsFillGnssLocation(&g_gnssServiceCtx.gnssInfo, &g_gnssServiceCtx.location);
|
||
if (ret == GNSS_SUCCESS) {
|
||
g_gnssServiceCtx.gnssCallback.onLocationUpdate(&g_gnssServiceCtx.location);
|
||
}
|
||
break;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
static void GnssListenDataTask(void *argument)
|
||
{
|
||
int32_t ret = 0;
|
||
|
||
UNUSED(argument);
|
||
|
||
g_gnssServiceCtx.gnssStarted = true;
|
||
|
||
uapi_pm_add_sleep_veto(PM_ID_SYS);
|
||
|
||
tiot_handle handle = tiot_service_open("gn71", NULL);
|
||
if (handle == 0) {
|
||
static_print_error("open tiot service fail");
|
||
goto GNSS_STOP;
|
||
}
|
||
static_print_debug("open tiot service success");
|
||
|
||
if (InjectPgnssLessFilesCfgAutoStartFixModeAndNmeaDefault(handle) == 0) {
|
||
static_print_error("InjectPgnssLessFilesCfgAutoStartFixModeAndNmeaDefault failed");
|
||
goto GNSS_STOP;
|
||
}
|
||
if (QryVersion(handle) == 0) {
|
||
static_print_error("QryVersion failed");
|
||
goto GNSS_STOP;
|
||
}
|
||
if (StartGnss(handle) == 0) {
|
||
static_print_error("start gnss failed");
|
||
goto GNSS_STOP;
|
||
}
|
||
|
||
if (g_gnssServiceBuff == NULL) {
|
||
g_gnssServiceBuff = (uint8_t *)malloc(REPORT_MAX_BYTES * sizeof(uint8_t));
|
||
if (g_gnssServiceBuff == NULL) {
|
||
static_print_error("malloc failed in gps_service_task");
|
||
goto GNSS_STOP;
|
||
}
|
||
}
|
||
|
||
// listening gnss data
|
||
while (true) {
|
||
osMutexAcquire(g_gnssMutex, osWaitForever);
|
||
bool shouldExit = !g_gnssServiceCtx.gnssStarted || g_gnssServiceCtx.gnssEnabled;
|
||
osMutexRelease(g_gnssMutex);
|
||
|
||
if (shouldExit) {
|
||
break;
|
||
}
|
||
|
||
GnssNmeaFrameProcess nmeaFrameProcess = {};
|
||
|
||
ret = tiot_service_read(handle, g_gnssServiceBuff, REPORT_MAX_BYTES, LISTEN_TIME_THR);
|
||
if (ret <= 0) {
|
||
static_print_error("error: tiot_service_read error: %d", ret);
|
||
continue;
|
||
}
|
||
uint16_t *type = (uint16_t *)g_gnssServiceBuff;
|
||
if (type[0] != MSG_TYPE_ASCII) {
|
||
continue;
|
||
}
|
||
g_gnssServiceBuff[ret] = '\0';
|
||
const char *nmeaLine = ((const char *)&g_gnssServiceBuff[MSG_CONTENT_IDX]);
|
||
int32_t nmeaLen = ret - MSG_CONTENT_IDX;
|
||
|
||
GnssNmeaFrameType nmeaType = TJD::GnssNmeaGetFrameType(nmeaLine, nmeaLen);
|
||
static_print_debug("nmeaType:%d nmeaStr:%s nmeaLen:%d", nmeaType, nmeaLine, nmeaLen);
|
||
if (nmeaType >= GNSS_NMEA_FRAME_MAX) {
|
||
static_print_warn("invalid nmeaStr:%s", nmeaLine);
|
||
continue;
|
||
}
|
||
if (nmeaType == GNSS_NMEA_FRAME_GGA) {
|
||
ResetGnssInfo(&g_gnssServiceCtx);
|
||
}
|
||
ret = TJD::GnssNmeaDecodeFrame(nmeaLine, nmeaLen, &g_gnssServiceCtx.gnssInfo, &nmeaFrameProcess);
|
||
if (ret != GNSS_SUCCESS) {
|
||
static_print_warn("invalid nmeaStr:%s", nmeaLine);
|
||
}
|
||
|
||
if (ret == GNSS_SUCCESS) {
|
||
GnssContextDispatchEvent(g_gnssServiceCtx.gnssInfo, nmeaType, nmeaFrameProcess);
|
||
}
|
||
|
||
printf("%s", nmeaLine);
|
||
fprintf(g_gnssLogFp, "%s\n", nmeaLine);
|
||
}
|
||
|
||
GNSS_STOP:
|
||
|
||
StopGnss(handle);
|
||
|
||
tiot_service_close(handle);
|
||
|
||
fclose(g_gnssLogFp);
|
||
g_gnssLogFp = NULL;
|
||
|
||
if (g_gnssServiceBuff) {
|
||
free(g_gnssServiceBuff);
|
||
g_gnssServiceBuff = NULL;
|
||
}
|
||
|
||
uapi_pm_remove_sleep_veto(PM_ID_SYS);
|
||
|
||
static_print_debug("finish gnss");
|
||
|
||
osMutexAcquire(g_gnssMutex, osWaitForever);
|
||
g_gnssServiceCtx.gnssEnabled = false;
|
||
osMutexRelease(g_gnssMutex);
|
||
}
|
||
|
||
void GnssServiceRegisterCallback(GnssCallbackIfaces *callback)
|
||
{
|
||
memcpy_s(&g_gnssServiceCtx.gnssCallback, sizeof(GnssCallbackIfaces), callback, sizeof(GnssCallbackIfaces));
|
||
}
|
||
|
||
uint32_t GnssServiceOpen(void)
|
||
{
|
||
osMutexAcquire(g_gnssMutex, osWaitForever);
|
||
if (g_gnssServiceCtx.gnssStarted || g_gnssServiceCtx.gnssEnabled) {
|
||
osMutexRelease(g_gnssMutex);
|
||
return GNSS_THREAD_RUNNING;
|
||
}
|
||
|
||
// 创建线程前标记状态
|
||
g_gnssServiceCtx.gnssStarted = true;
|
||
osMutexRelease(g_gnssMutex);
|
||
|
||
// 打开日志文件
|
||
g_gnssLogFp = fopen("/user/gnss.log", "w");
|
||
if (g_gnssLogFp == NULL) {
|
||
static_print_error("open log file error");
|
||
return GNSS_FILE_OPEN_FAILURE;
|
||
}
|
||
|
||
osThreadAttr_t task_attr = {"TjdGnssListenDataTask", 0, NULL, 0, NULL, 0x2000, osPriorityHigh, 0, 0};
|
||
task_attr.stack_mem = memalign(16, task_attr.stack_size); // add
|
||
g_gnssThreadId = osThreadNew(GnssListenDataTask, NULL, &task_attr);
|
||
if (g_gnssThreadId == NULL) {
|
||
static_print_error("create gps service task failed");
|
||
return GNSS_THREAD_CREATE_FAILURE;
|
||
}
|
||
return GNSS_SUCCESS;
|
||
}
|
||
|
||
void GnssServiceClose(void)
|
||
{
|
||
osMutexAcquire(g_gnssMutex, osWaitForever);
|
||
if (!g_gnssServiceCtx.gnssStarted) {
|
||
osMutexRelease(g_gnssMutex);
|
||
return;
|
||
}
|
||
|
||
g_gnssServiceCtx.gnssStarted = false;
|
||
g_gnssServiceCtx.gnssEnabled = true; // 设置强制退出标志
|
||
osMutexRelease(g_gnssMutex);
|
||
|
||
// 等待线程实际退出
|
||
if (osThreadGetState(g_gnssThreadId) != osThreadTerminated) {
|
||
osThreadJoin(g_gnssThreadId);
|
||
}
|
||
} |