# 程序异常处理 ***Copyright © Quectel Wireless Solutions Co., Ltd. 2026. All rights reserved.*** --- # 功能概述 在应用开发中,及时识别并处理运行时错误对程序健壮性至关重要。UniRTOS SDK中的错误分为两类: - **可恢复错误**:通过函数返回值(错误码枚举)表示,调用方检查后可选择重试、降级或向上传递。 - **不可恢复错误**: 通过 *QOSA_ASSERT* 触发的断言失败。 CPU异常:非法内存访问、非法指令。 系统级检查:看门狗超时、缓存访问错误、堆栈溢出、堆栈粉碎、堆栈损坏等。 本文介绍针对可恢复错误的处理机制与常用模式。 # 错误码 UniRTOS SDK中的大多数函数以功能组件专属枚举类型返回错误码,成功时统一返回值为 *0*(各功能组件定义为 *QOSA_XXX_SUCCESS = 0* 或 *QOSA_XXX_OK = 0*)。 每个子功能组件的错误枚举均遵循统一的32位编码规则: ```plaintext [31 16] [15 0] 组件标识码 具体错误序号 ``` 高16位为组件ID,由 *qosa_def.h* 中的 *qosa_base_component_e* 枚举定义;低16位为该功能组件内的顺序错误号。每个功能组件通过自身的 *ERRCODE_BASE* 宏将两者合并,以UART为例: ```c #define QOSA_UART_ERRCODE_BASE (QOSA_COMPONENT_API_UART << 16) // 0x80120000 typedef enum { QOSA_UART_SUCCESS = 0, QOSA_UART_EXECUTE_ERR = 1 | QOSA_UART_ERRCODE_BASE, // 0x80120001 QOSA_UART_MEM_ADDR_NULL_ERR, // 0x80120002 QOSA_UART_INVALID_PARAM_ERR, // 0x80120003 QOSA_UART_OPEN_REPEAT_ERR, // 0x80120004 QOSA_UART_NOT_OPEN_ERR, // 0x80120005 } qosa_uart_error_e; ``` 此外,SDK在 *qosa_common_error_e* 中定义了多功能组件的通用错误,基类组件码为 *QOSA_COMPONENT_COMMON(0x8000)*,具体定义如下: ```c /*! common error code */ typedef enum { QOSA_OK = 0, QOSA_ERROR_GENERAL = 1 | (QOSA_COMPONENT_COMMON << 16), /*!< generic error,this error is only returned when a special exception occurs */ QOSA_ERROR_NO_MEMORY, /*!< memory malloc failed */ QOSA_ERROR_PARAM_INVALID, /*!< Parameter input error */ QOSA_ERROR_PARAM_IS_NULL, /*!< Parameter is empty error */ QOSA_ERROR_TIMEOUT, /*!< Operation timeout error */ QOSA_ERROR_NO_RESOURCE, /*!< No resources */ QOSA_ERROR_VALUE_INVALID, /*!< Invalid value */ QOSA_ERROR_OPERATION, /*!< Invalid operation */ QOSA_ERROR_INVALID_SIZE, /*!< Invalid size */ QOSA_ERROR_REQUEST, /*!< Invalid request */ QOSA_ERROR_NO_SPACE, /*!< Queue space full*/ QOSA_ERROR_DELETED, /*!< Delete error */ QOSA_ERROR_UNSUPPORT, /*!< Operation not supported */ QOSA_ERROR_DUPLICATE_NAME, /*!< Duplicate name */ } qosa_common_error_e; ``` 各功能组件ID的完整列表见 *qosa_def.h* 中的 *qosa_base_component_e* 枚举。常用组件ID速查: | **组件宏** | **对应值** | **对应功能** | | --- | --- | --- | | *QOSA_COMPONENT_API_ADC* | 0x8011 | ADC采样 | | *QOSA_COMPONENT_API_UART* | 0x8012 | 串口通信 | | *QOSA_COMPONENT_BSP_GPIO* | 0x8013 | GPIO控制 | | *QOSA_COMPONENT_API_I2C* | 0x8016 | I2C总线 | | *QOSA_COMPONENT_API_SPI* | 0x8018 | SPI总线 | | *QOSA_COMPONENT_API_CAMERA* | 0x8019 | 摄像头 | | *QOSA_COMPONENT_API_USB* | 0x8020 | USB | | *QOSA_COMPONENT_MQTT* | 0x8110 | MQTT客户端 | | *QOSA_COMPONENT_FOTA* | 0x8112 | 固件升级 | ## 错误码解析 32位错误码本身不直观,可按以下方式手动拆解定位: ```c uint32_t err = 0x80160003; uint16_t component = (err >> 16) & 0xFFFF; // 0x8016 → QOSA_COMPONENT_API_I2C uint16_t errnum = err & 0xFFFF; // 0x0003 → QOSA_I2C_INIT_ERR(第3个错误) ``` 日志输出时建议同时打印十六进制原始值与调用位置,便于离线分析: ```c qosa_i2c_error_e ret = qosa_i2c_init(I2C_PORT_0, &cfg); if (ret != QOSA_I2C_SUCCESS) { QLOGE("qosa_i2c_init failed: 0x%08X", (unsigned int)ret); return ret; } ``` ```{note} 部分HAL接口(如 *qosa_usb_get_vbus_state*、*qosa_usb_get_connect_state*)返回的不是错误码枚举,而是状态枚举,其中 *-1* 表示硬件不支持(如 *QOSA_USB_VBUS_NOT_SUPPORT*),*0* 表示未连接,*1* 表示已连接。处理此类接口时须与普通错误码区分对待,不能简单地与 *SUCCESS* 比较。 ``` ## QOSA_ASSERT宏 *QOSA_ASSERT* 的作用与 *assert* 类似,但会在触发时自动记录函数名、行号,然后中止程序执行: ```c // qosa_def.h #define QOSA_ASSERT(a) qosa_assert(__func__, __LINE__, a) ``` **适用场景**:验证程序内部不变量——即在正常情况下绝不应为假的条件。 ```c QOSA_ASSERT(ctx != NULL); // 内部上下文指针不应为NULL QOSA_ASSERT(buf_size <= MAX_BUF); // 缓冲区大小不应超出设计上限 ``` ```{note} *QOSA_ASSERT* 触发后程序将中止,属于不可恢复行为,不得用于处理外部输入校验或普通API返回值。示例代码中使用 *QOSA_ASSERT* 包裹API调用,目的是使示例更简洁,并不代表应用开发的最佳实践。 ``` ## 推荐检查写法 UniRTOS SDK应用代码中推荐使用以下两种写法。 ### 立即返回(无资源需释放) 适用于函数执行路径上没有需要清理的资源时: ```c #define QOS_LOG_TAG "MY_APP" qosa_uart_error_e my_uart_setup(void) { qosa_uart_error_e ret; ret = qosa_uart_open(UART_PORT_0, &open_cfg); if (ret != QOSA_UART_SUCCESS) { QLOGE("uart open failed: 0x%08X", (unsigned int)ret); return ret; } ret = qosa_uart_set_baud_rate(UART_PORT_0, 115200); if (ret != QOSA_UART_SUCCESS) { QLOGE("uart set baud failed: 0x%08X", (unsigned int)ret); qosa_uart_close(UART_PORT_0); return ret; } return QOSA_UART_SUCCESS; } ``` ### goto清理(有资源需释放) 适用于函数中途分配了内存、打开了设备或持有了锁等需要统一释放的场景: ```c qosa_i2c_error_e sensor_init(void) { qosa_i2c_error_e ret = QOSA_I2C_SUCCESS; uint8_t *buf = NULL; buf = qosa_malloc(BUF_SIZE); if (buf == NULL) { QLOGE("malloc failed"); return QOSA_ERROR_NO_MEMORY; } ret = qosa_i2c_init(I2C_PORT_0, &i2c_cfg); if (ret != QOSA_I2C_SUCCESS) { QLOGE("i2c init failed: 0x%08X", (unsigned int)ret); goto cleanup; } ret = qosa_i2c_write(I2C_PORT_0, SENSOR_ADDR, buf, BUF_SIZE); if (ret != QOSA_I2C_SUCCESS) { QLOGE("i2c write failed: 0x%08X", (unsigned int)ret); goto cleanup; } cleanup: qosa_free(buf); return ret; } ``` ## 错误处理模式 ### 重试恢复 部分错误属于瞬态故障(总线繁忙、队列满、短暂超时),可在延迟后重试有限次数: ```c #define MAX_RETRY 3 qosa_i2c_error_e ret; int retry = 0; do { ret = qosa_i2c_write(I2C_PORT_0, addr, buf, len); if (ret == QOSA_I2C_SUCCESS) break; qosa_msleep(10); } while (++retry < MAX_RETRY); if (ret != QOSA_I2C_SUCCESS) { QLOGE("I2C write failed after %d retries: 0x%08X", MAX_RETRY, (unsigned int)ret); return ret; } ``` ### 向上传递 中间件或驱动层通常不适合自行决策错误的处置方式,应在释放已持有的资源后,将原始错误码返回给上层: ```c uint8_t *pkt = qosa_malloc(PKT_SIZE); if (pkt == NULL) { return QOSA_ERROR_NO_MEMORY; } qosa_uart_error_e ret = qosa_uart_write(UART_PORT_0, pkt, PKT_SIZE, TIMEOUT_MS); if (ret != QOSA_UART_SUCCESS) { qosa_free(pkt); return ret; // 原始错误码透传给调用方 } qosa_free(pkt); return QOSA_UART_SUCCESS; ``` ### 能力探测与降级 *NOT_SUPPORT* 类错误码(如 *QOSA_USB_VBUS_NOT_SUPPORT*、*QOSA_USB_STATE_NOT_SUPPORT*)表示当前硬件不具备该能力,属于静态事实而非运行时故障。应在初始化阶段探测能力并决定功能路径,而非每次调用都处理这个返回值: ```c // 初始化时一次性探测VBUS检测能力 static bool s_vbus_supported = false; void usb_feature_init(void) { if (qosa_usb_get_vbus_state() != QOSA_USB_VBUS_NOT_SUPPORT) { s_vbus_supported = true; qosa_usb_bind_vbus_cb(vbus_event_cb); QLOGI("VBUS detection enabled"); } else { QLOGW("VBUS pin not available, falling back to connect state polling"); } } ``` ### 转为不可恢复错误 在初始化阶段,某些API的失败意味着后续所有逻辑都无法正常运行,此时可以合理地使用 *QOSA_ASSERT* 使程序在调试阶段快速暴露问题: ```c // 适用于开发阶段,初始化时的硬性依赖 void app_init(void) { qosa_uart_error_e ret = qosa_uart_open(LOG_UART_PORT, &log_cfg); QOSA_ASSERT(ret == QOSA_UART_SUCCESS); // 继续初始化其他功能组件... } ``` 对中间件或可选功能组件,不建议在失败时直接断言,应返回错误码让应用层自行决策。 # 日志输出 SDK提供按级别分类的日志宏,自动附带函数名和行号。每个源文件顶部须定义 *QOS_LOG_TAG* 用于过滤: ```c #define QOS_LOG_TAG "MY_MODULE" QLOGE(fmt, ...) // ERROR:不可恢复错误 QLOGW(fmt, ...) // WARN :可恢复异常 QLOGI(fmt, ...) // INFO :关键流程节点 QLOGD(fmt, ...) // DEBUG:详细调试信息 QLOGV(fmt, ...) // VERBOSE:高频/低优先级信息 ``` 通过 *qosa_log_control_set* 按位控制输出通道: | **位** | **宏** | **说明** | | --- | --- | --- | | bit0 | *QOSA_LOG_BIT_MASTER_ENABLE* | 日志总开关 | | bit1 | *QOSA_LOG_BIT_DEBUG* | 调试串口 | | bit2 | *QOSA_LOG_BIT_USB* | USB虚拟串口 | | bit3 | *QOSA_LOG_BIT_SDCARD* | SD卡文件 | | bit4 | *QOSA_LOG_BIT_FLASH* | 内置Flash文件 | 量产固件建议仅保留 *MASTER_ENABLE + DEBUG*,关闭USB和Flash输出,降低I/O开销。