程序异常处理¶
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位编码规则:
[31 16] [15 0]
组件标识码 具体错误序号
高16位为组件ID,由 qosa_def.h 中的 qosa_base_component_e 枚举定义;低16位为该功能组件内的顺序错误号。每个功能组件通过自身的 ERRCODE_BASE 宏将两者合并,以UART为例:
#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),具体定义如下:
/*! 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位错误码本身不直观,可按以下方式手动拆解定位:
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个错误)
日志输出时建议同时打印十六进制原始值与调用位置,便于离线分析:
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;
}
备注
部分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 类似,但会在触发时自动记录函数名、行号,然后中止程序执行:
// qosa_def.h
#define QOSA_ASSERT(a) qosa_assert(__func__, __LINE__, a)
适用场景:验证程序内部不变量——即在正常情况下绝不应为假的条件。
QOSA_ASSERT(ctx != NULL); // 内部上下文指针不应为NULL
QOSA_ASSERT(buf_size <= MAX_BUF); // 缓冲区大小不应超出设计上限
备注
QOSA_ASSERT 触发后程序将中止,属于不可恢复行为,不得用于处理外部输入校验或普通API返回值。示例代码中使用 QOSA_ASSERT 包裹API调用,目的是使示例更简洁,并不代表应用开发的最佳实践。
推荐检查写法¶
UniRTOS SDK应用代码中推荐使用以下两种写法。
立即返回(无资源需释放)¶
适用于函数执行路径上没有需要清理的资源时:
#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清理(有资源需释放)¶
适用于函数中途分配了内存、打开了设备或持有了锁等需要统一释放的场景:
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;
}
错误处理模式¶
重试恢复¶
部分错误属于瞬态故障(总线繁忙、队列满、短暂超时),可在延迟后重试有限次数:
#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;
}
向上传递¶
中间件或驱动层通常不适合自行决策错误的处置方式,应在释放已持有的资源后,将原始错误码返回给上层:
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)表示当前硬件不具备该能力,属于静态事实而非运行时故障。应在初始化阶段探测能力并决定功能路径,而非每次调用都处理这个返回值:
// 初始化时一次性探测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 使程序在调试阶段快速暴露问题:
// 适用于开发阶段,初始化时的硬性依赖
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 用于过滤:
#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开销。