V1.0 / 指南 / 中文

TXSDK_开发入门指南

logo

责任与版权

责任限制

由于产品版本升级或者其他原因,本文档会不定期更新。除非另行约定,泰芯半导体有限公司对本文档所有内容不提供任何担保或授权。

客户应在遵守法律、法规和安全要求的前提下进行产品设计,并做充分验证。泰芯半导体有限公司对应用帮助或客户产品设计不承担任何义务。客户应对其使用泰芯半导体有限公司的产品和应用自行负责。

在适用法律允许的范围内,泰芯半导体有限公司在任何情况下,都不对因使用本文档相关内容及本文档描述的产品而产生的损失和损害进行超过购买支付价款的赔偿(除在涉及人身伤害的情况中根据适用的法律规定的损害赔偿外)。

版权申明

泰芯半导体有限公司保留随时修改本文档中任何信息的权利,无需提前通知且不承担任何责任。

未经泰芯半导体有限公司书面同意,任何单位和个人不得擅自摘抄、复制本文档内容的部分或全部,并不得以任何形式传播。除非获得相关权利人的许可,否则,任何人不能以任何形式对前述软件进行复制、分发、修改、摘录、反编译、反汇编、解密、反向工程、出租、转让、分许可等侵犯本文档描述的享有版权的软件版权的行为,但是适用法禁止此类限制的除外。

修订记录

日期

版本

描 述

修订人

2024-05-07

V1.0

TX

1. 概述

TXSDK是泰芯半导体发布的WiFi/音视频系列芯片开发SDK。本文档是TXSDK的开发入门指南,介绍了SDK的代码框架,基本开发规范以及SDK的基础功能模块。

本文档适用于泰芯所有的WiFi/音视频芯片SDK,作为基础文档,而各个芯片的特有功能部分请查阅芯片的专用文档。

1.1. SDK版本信息

SDK支持开发多种类型的方案,例如低功耗WiFi模组,无线网桥,IoT模组,无线图传等方案。在发布的SDK版本信息中包含了SDK版本,SDK方案类型信息。

例如:TXW81x_IOT-v2.5.3.6-28970

其中:

  • TXW81x :芯片系列名称:TXW81x系列
  • IOT:SDK适用的方案类型:IoT类方案
  • v2.5.3.6:SDK版本号:主版本-2, 分支版本-5, Patch版本-3,方案类型-6(对应IoT)
  • 28970:内部版本号,该版本号是递增的唯一值。

SDK方案类型名词解释:

  • FMAC:低功耗WiFi模组(适用于搭配主控使用)
  • FPV:无人机图传
  • WNB:无线网桥
  • IOT:物联网WiFi模组
  • IPC:网络摄像头

2. SDK目录结构

SDK解压后的目录结构如下所示:

目录说明

  • csky:AliOS内核代码
  • ohos: 鸿蒙LiteOS内核代码
  • doc: SDK开发文档目录
  • libs:SDK发布的库文件
  • project:SDK编译工程目录
  • sdk:SDK代码目录
  • sdk/chip: 芯片启动代码目录(示例是TXW81x系列芯片的启动代码)
  • sdk/driver: 芯片驱动代码目录
  • sdk/hal: SDK驱动抽象层代码目录
  • sdk/include:SDK头文件目录
  • sdk/osal: SDK OS抽象层
  • sdk/lib: SDK library代码目录。SDK默认支持了lwip协议栈,libcurl,posix适配API,mbedtls加密库,mmc协议栈,USB协议栈等。

3. SDK启动流程

sdk/chip目录是芯片启动代码目录,芯片启动流程相关的代码文件是startup.s和system.c。各个型号的芯片启动流程基本一致,如下图所示:

3.1. startup.S

startup.S 文件是芯片启动汇编代码文件。各个芯片的startup.S文件的代码实现思路基本保持一致,所实现的功能包括:中断向量表定义,复位入口函数,加载数据段,清零BSS段,然后跳转执行Systeminit函数和pre_main函数。

3.2. system.c

system.c文件是芯片系统初始化代码文件。各个芯片的system.c文件的代码实现思路基本保持一致。该文件主要实现了2个函数:Systeminit函数和pre_main函数。

Systemint函数实现了对芯片的初始化设置,包括:CPU Cache设置,中断向量表设置,中断初始化,CPU时钟频率设置,CPU TICK设置。

pre_main函数实现了软件系统的初始化,包括Heap初始化,RTOS内核初始化,SDK设备管理模块初始化,外设驱动模块初始化,主工作队列初始化,调用main函数,启动RTOS内核。

system driver initialization

run main function

start RTOS kernel

low power module initialization

system device driver initialization

system device manager initialization

RTOS kernel initialization

system heap initialization

main workqueue initialization

3.3. main.c

main.c 是SDK启动流程的最后阶段,用于进行应用代码初始化。不同类型方案的main.c 差异可能会比较大,但是基础的初始化基本一致,包括:参数区加载,SDK事件模块初始化,AT指令模块初始化,WiFi功能初始化,网络功能(lwip)初始化。

如前面所示main函数是由os_run_func函数启动执行的。不同于常见的main函数,SDK的main函数是在主工作队列中执行,main函数执行后会返回,没有while循环。所以在main函数中添加初始化代码时,不能添加while循环。SDK的工作队列机制在后续的章节会详细介绍。

3.4. 二次loader启动流程

为了应对更加的复杂的应用方案,SDK支持二次loader启动。

TBD...

4. SDK配置头文件

SDK有2个基础配置文件,分别是sys_config.h和project_config.h。在这2个头文件中定义了很多宏定义开关,用于控制SDK功能的编译。

sys_config.h:定义了SDK所需宏定义的默认值,通常无需修改此文件。

project_config.h:在此文件中重定义了需要修改的宏定义。

4.1. sys_config.h

sys_config.h文件定义了SDK所需宏定义的默认值,在该文件的起始位置引用了project_config.h,优先使用project_config.h定义的宏定义值。

sys_config.h中几个重要的宏定义说明:

  • SYS_HEAP_SIZE :定义SDK的heap大小。请根据实际方案的heap需求,调整这个宏定义值。所有的 malloc操作都会从heap中进行分配。
  • WIFI_RX_BUFF_SIZE:定义WiFi功能的rx buffer大小,影响WiFi的RX性能。根据实际方案对WiFi接收性能的需求,可以适当调整这个值。
  • SKB_POOL_SIZE:WiFi功能的tx buffer大小,影响WiFi TX性能。这个宏定义值无需修改,宏定义会根据SYS_HEAP_SIZE和WIFI_RX_BUFF_SIZE的值进行自动计算。所以修改SYS_HEAP_SIZE和WIFI_RX_BUFF_SIZE的值会影响WiFi功能的tx buffer大小。

4.2. project_config.h

project_config.h用于对宏定义进行重定义,不需要去修改sys_config.h。

当存在多个类似方案,仅仅是部分宏定义值不一样时,可以在project_config.h中可以添加多个PROJECT_ID 进行选择编译。

5. SDK内存管理

SDK的内存管理是在编译阶段静态划分的,整个SRAM被data/bss 段占用后剩余的内存被分成了3部分:HEAPWiFi RX bufferWiFi TX Buffer。在sys_config.h中由3个宏定义决定各自的大小:SYS_HEAP_SIZE,WIFI_RX_BUFF_SIZE和SKB_POOL_SIZE,这3部分内存划分的说明在4.1章节。

部分型号芯片有内置PSRAM或外挂PSRAM,可以根据PSRAM的用途进行PSRAM进行管理。可以将PSRAM作为HEAP使用,也可以将其作为特定的buffer使用。

5.1. SRAM内存

不同型号芯片的内置SRAM容量会不一样,在工程的ld文件中定义了程序可用的SRAM区间。在ld文件中定义了2个变量:__heap_end 和 __heap_start。这2个变量就是data/bss段占用后的剩余sram区间,该区间又会分成3部分:HEAP,WiFi RX buffer和WiFi TX Buffer。编译完成后也可以从map文件中得知__heap_start的大小。

如上图所示,SRAM_POOL_SIZE是sram剩余总空间(在SystemInit时根据ld文件变量__heap_start和__heap_end计算)。这部分空间又被分成了 SYS_HEAP, WIFI_RX_BUFFER, WIFI_TX_BUFFER 3部分。

HEAP_TAIL_ROOM是在sram的尾巴上预留一部分空间。可以根据方案需求,预留部分memory用作特殊用途。

5.2. PSRAM内存

芯片的内置PSRAM或外挂PSRAM可以作为HEAP使用,也可以作为特定的buffer使用,也可以用来运行代码。不同型号的芯片,PSRAM的使用可能会有不同的规则或限制条件,具体情况需要查阅芯片资料或咨询FAE。

  • PSRAM用来运行代码

如果需要将代码放到PSRAM中运行,请根据实际情况修改ld文件,将代码的运行地址指定到PSRAM区间。

  • PSRAM作为HEAP使用

如果将PSRAM作为系统heap使用,此时有2种情况:

    • PSRAM使用无限制

如果具体芯片的PSRAM在使用上无任何限制,对于程序来说可以等同于SRAM。这种情况下可以在sram_heap初始化之后,使用sysheap_add API将可用的PSRAM区间添加到heap管理器中。程序在使用malloc申请内存时就可以申请到psram空间的内存。同样是使用malloc/free API,程序代码可以无感知的使用PSRAM内存。

    • PSRAM使用有限制

如果具体芯片的PSRAM在使用上有限制,不能完全等同于SRAM;则在需要访问PSRAM空间的代码中使用os_malloc_psram/os_free_psram API进行申请和释放操作。这种情况下malloc/free API不会访问到PSRAM空间,而且在system.c 中需要对psram_heap 进行初始化,具体请查看system.c的malloc_psram_init函数。

  • PSRAM作为特定buffer使用

如果把PSRAM作为特定模块的buffer使用,请自行划分管理PSRAM区间。

5.3. 内存分配算法

SDK提供了2种内存分配算法模块,分别是mmpool1和mmpool3。这2个模块各有自己的优点:

  • mmpool1:分配效率更高,内存利用率更高,主要是适用小内存池和低碎片化的场景。对应的ops使用mmpool1_ops
  • mmpool3:相比mmpool1分配效率和利用率都略低,更适合大内存池,可以改善内存碎片化,降低内存碎片化产生的影响。对应的ops使用mmpool3_ops

内存池控制标识:enum SYSHEAP_FLAGS

enum SYSHEAP_FLAGS {

SYSHEAP_FLAGS_MEM_LEAK_TRACE = (1u << 0), //开启内存泄漏检测

SYSHEAP_FLAGS_MEM_OVERFLOW_CHECK = (1u << 1), //开启内存溢出检测

SYSHEAP_FLAGS_MEM_ALIGN_16 = (1u << 2), //分配地址16字节对齐

SYSHEAP_FLAGS_MEM_ALIGN_32 = (1u << 3), //分配地址32字节对齐

};

5.4. 系统heap

SDK默认定义了1个系统heap:sram_heap,所有的malloc操作都从系统heap中分配内存。系统heap的大小由SYS_HEAP_SIZE宏定义控制,系统heap默认使用了mmpool1分配算法,如下图所示:

如果要修改系统heap的分配模块,需要修改2个地方:

  1. 修改struct sys_sramheap的定义:在sdk/include/lib/heap/sysheap.h文件,将pool字段的类型修改为struct mmpool3。
  2. 修改heap初始化:在system.c中的malloc_init函数,将sram_heap.ops修改为mmpool3_ops。

同时SDK内置了2个与PSRAM相关的宏定义:

  • PSRAM_HEAP:开启psram作为heap使用:psram_heap,初始化代码:malloc_psram_init,对应的是os_malloc_psram/os_free_psram API。
  • PSRAM_TASK_STACK:指定任务堆栈使用psram空间。SDK默认创建task时,task的堆栈空间会使用sram空间,当sram空间不够用时,可以将任务堆栈指定到psram空间。开启此宏定义,会将所有的Task的堆栈都切换到psram_heap空间[需要开启PSRAM_HEAP]。

5.5. 自定义heap

SDK支持添加自定义heap作为一些软件模块的专用内存池。SDK提供了自定义heap的模板文件:xxx_heap.c和xxx_heap.h ,基于模板文件可以简化自定义heap的开发工作。

添加自定义heap需要2个步骤:

  1. 复制模板文件

模板文件在sdk/lib/heap目录下,复制 xxx_heap.c 和 xxx_heap.h 进行重命名,然后在新的 .c/.h 文件中 查找替换 “xxx”为新的内存池名称。同时确认init函数里面设置的mmpool_ops的值是否正确,默认选择使用了mmpool1。

  1. 内存池初始化

自定义heap模块创建后,下一步需要安排heap所需的内存空间,有多种方式可以选择,例如:

  • 预留分配:在sys_config.h中使用宏定义为其预留空间,
  • 动态分配:从其它heap(例如sram_heap或psram_heap)中分配一块内存空间。

在解决所需内存空间后,在合适的地方调用 自定义heap的初始化,然后就可以正常使用自定义heap了。

int32 xxx_heap_init(uint32 heap_start, uint32 heap_size, uint32 flags)

  • 参数 heap_start: 该内存池空间的起始地址
  • 参数 heap_size: 该内存池空间的大小
  • 参数 flags: 内存池的控制标识,参见 enum SYSHEAP_FLAGS

5.6. 内存调试

SDK的内存管理模块支持内存使用统计,内存越界检查,内存泄漏跟踪。当遇到内存异常问题,可以使用SDK提供的功能进行分析。不管是sram_heappsram_heap,还是自定义heap都可以使用这些调试功能,在内存池初始化时根据需要开启对应的控制标识,参见 enum SYSHEAP_FLAGS

SDK默认添加宏定义 MEM_TRACE,用于打开 sram_heap/psram_heap的调试功能。

  • 内存统计:监测内存使用和剩余情况。在未开启调试功能时,内存管理模块仅维护“used size”,“free size”和内存碎片信息。使用sysheap_status API可以打印内存池的使用情况。
  • 内存越界检查:对常规的内存操作,例如memcpy,strcpy之类的API提供越界监测功能,需要打开SYSHEAP_FLAGS_MEM_OVERFLOW_CHECK标识。同时需要将memcpy/strcpy这种API替换为 os_memcpy/os_strcpy才能使用此功能。具体代码见sdk/lib/string.c。
  • 内存泄漏追踪:打开SYSHEAP_FLAGS_MEM_LEAK_TRACE标识,内存管理模块在分配内存时会记录每一个内存分配信息。当出现内存泄漏时,通过打印内存分配信息,就可以快速定位产生内存泄漏的代码。

对于其他的内存异常,也可以借助这3个功能进行分析。例如内存异常死机,根据CPU Dump信息查看导致异常的内存地址,然后根据内存调试信息里面的used list,查看是哪个模块分配了此地址,以及这个内存地址的前后内存块是哪个模块申请的,从这些信息中寻找蛛丝马迹。

6. SDK开发基本规范

在使用SDK进行二次开发时建议遵循SDK定义的一些开发规范。

6.1. 头文件引用

SDK的基础头文件都放在sdk/include目录下,编译工程的include path 已添加sdk/include路径。部分库代码的头文件放在源码目录,需要使用时请查看具体模块代码。

SDK提供了basic_include.h 文件,该文件引用了一些SDK基础头文件。使用该头文件可以减少代码开发中的一些头文件引用问题。

6.2. os_xx API说明

为了便于代码移植,SDK对一些基础API进行了简单封装或宏定义封装,这类API的命名都是以“os_”开头,主要集中在osal/string.h文件中。

在sdk/common/string.c定义几个内存操作API,例如_os_memcpy/_os_strcpy等。如果遇到内存溢出系统异常问题时,可以将代码中操作内存的API替换成string.c定义的这些API,然后使用SYSHEAP_FLAGS_MEM_OVERFLOW_CHECK开启内存溢出检测功能,程序在使用这些API时,SDK会对buffer的越界操作进行检测,对分析buffer溢出问题有一定的帮助。

7. SDK设备驱动

7.1. SDK设备管理

SDK使用设备ID对设备进行统一管理,为每个设备分配了唯一的ID。所有的设备驱动结构体定义都必须遵循以下规则:

设备驱动结构体定义的第一个字段是必须是 struct dev_obj 类型,或者是它的父类型(父类型也需要遵循此规则)。

struct dev_obj 是设备的抽象层定义,设备管理器正是基于此类型对设备进行管理。

  • 设备管理模块代码是sdk/hal/dev.c。
  • 设备ID的定义在sdk/include/devid.h

系统初始化时需要向SDK注册所有需要使用的设备(device_init函数)。在各个设备驱动的attach函数中使用 dev_register API进行注册,在需要访问设备时使用dev_get API获取设备对象,然后使用抽象层API访问设备。【每个设备只能注册一次】。

extern int32 dev_register(uint32 dev_id, struct dev_obj *device);

extern struct dev_obj *dev_get(int32 dev_id);

7.2. 驱动抽象层

SDK对常见的外设都定义了抽象层。抽象层屏蔽了具体硬件差异,在应用代码开发时应使用抽象层API,可以使应用代码很方便的在多个型号芯片的SDK之间进行移植。

设备驱动抽象层代码在sdk/hal目录,头文件在 sdk/include/hal/目录。

hal/dev.c是驱动抽象层的顶层模块,struct dev_obj是所有驱动对象的父类型。

dev.c 设计的API如下:

  • int32 dev_init()

device manger初始化,该函数在system.c的pre_main函数中执行。

  • struct dev_obj *dev_get(int32 dev_id)

从系统中获取指定ID的设备,返回类型为struct dev_obj *。由于各个驱动对象均继承于struct dev_obj,所以代码在使用时可以直接转换为具体的驱动类型。

  • int32 dev_register(uint32 dev_id, struct dev_obj *device)

向系统注册设备。在各个驱动的attach函数中会向系统注册设备,设备注册后就可以使用dev_get API获取设备,访问设备。

  • int32 dev_suspend(uint16 type)

该API是低功耗API,系统在休眠前会执行dev_suspend将所有设备挂起。该功能需要设备驱动实现它的suspend函数,对设备进行挂起操作。

  • int32 dev_resume(uint16 type, uint32 wkreason)

该API是低功耗API,系统在休眠唤醒时会执行dev_resume将所有被挂起的设备恢复。该功能需要设备驱动实现它的resume函数,对设备进行恢复。

各个驱动的抽象层API不再一一赘述,直接阅读代码即可。

8. SDK OS抽象层

SDK可以支持多种RTOS;目前已支持AliOS内核,鸿蒙LiteOS内核。为了便于代码移植和开发,SDK对常用的OS功能进行抽象封装,设计了OSAL抽象层API。

假设读者已具备操作系统原理基础知识,本章节仅介绍抽象层所封装的功能

8.1. OS Task

OS Task模块是RTOS task功能的抽象层,代码在 sdk/osal/xxx/task.c,头文件在sdk/include/osal/task.h。

该抽象层对Task常用功能进行了封装,定义了struct os_task结构体。大部分API需要使用struct os_task类型。在需要创建task的代码中需要定义struct os_task变量。

OS Task抽象层的API列表如下:

  • os_task_init:struct os_task变量初始化
  • os_task_priority:获取task的优先级
  • os_task_stacksize:获取task的堆栈大小
  • os_task_set_priority:设置task优先级
  • os_task_set_stack:设置task堆栈,堆栈大小
  • os_task_run:启动task运行
  • os_task_stop:停止task运行
  • os_task_del:删除task;
  • os_task_runtime:获取所有task运行时间统计
  • os_task_current:获取当前运行task的句柄
  • os_task_data:根据task句柄获取task关联的data
  • os_task_suspend:挂起task
  • os_task_resume:恢复task到ready状态
  • os_task_dump:task堆栈信息打印
  • os_task_yield:控制当前task让出CPU
  • os_sched_disable:关调度
  • os_sched_enbale:开调度
  • os_task_hdl2tsk:根据task句柄获取struct os_task类型数据。

在osal/task.h 里面定义宏定义OS_TASK_INIT,用于简化task创建执行,如下所示:

另外task.c 还提供了2个API:os_task_create 和 os_task_destroy,这2个API和struct os_task 结构体无关,开发代码时也可以直接使用这2个API。

使用OS_TASK_INITos_task_create 时都可以为task单独指定自定义堆栈空间,和 PSRAM_TASK_STACK 功能类似。

8.2. OS Mutex

OS Mutex模块是RTOS mutex功能的抽象封装层,代码在sdk/osal/xxx/mutex.c,头文件在 sdk/include/osal/mutex.h。

该抽象层封装了mutex的常用功能,定义了 struct os_mutex 结构体。该抽象层的API都基于此类型进行操作,在需要使用OS Mutex功能的代码中需要定义struct os_mutex类型变量。

OS Mutex抽象层的API列表如下:

  • os_mutex_init:OS Mutex初始化
  • os_mutex_lock:OS Mutex上锁操作,该API需要指定timeout参数,当timeout参数为0时,就是try lock操作。
  • os_mutex_unlock: OS Mutex释放锁
  • os_mutex_del: OS Mutex删除
  • os_mutex_owner: 获取OS Mutex当前被哪个task锁住,发回值是task句柄。

8.3. OS Semaphore

OS Semaphore模块是RTOS semaphore功能的抽层封装层,代码在sdk/osal/xxx/semaphore.c,头文件在sdk/include/osal/semaphore.h。

该抽象层封装了semaphore常用的功能,定义struct os_semaphore结构体。该抽象层的API都基于此类型进行操作,在需要使用OS Semaphore功能的代码中需要定义struct os_semaphore类型变量。

OS Semaphore抽象层的API列表如下:

  • os_sema_init:信号量初始化,可以指定信号量初始值。
  • os_sema_del:删除信号量
  • os_sema_down:对信号量执行down操作,可以指定timeout参数,当timeout参数为0时,就是try down操作。
  • os_sema_up:释放信号量
  • os_sema_count:查看信号量的值

8.4. OS MsgQueue

OS MsgQueue模块是RTOS 消息队列功能的抽层封装层,代码在sdk/osal/xxx/msgqueue.c,头文件在sdk/include/osal/msgqueue.h。

该抽象层封装了消息队列常用的功能,定义struct os_msgqueue结构体。该抽象层的API都基于此类型进行操作,在需要使用消息队列功能的代码中需要定义struct os_msgqueue类型变量。

OS Msgqueue封装的API仅支持往消息队列存取UINT32类型数据。

OS MsgQueue抽象层的API列表如下:

  • os_msgq_init:消息队列初始化,可以指定消息队列的大小。
  • os_msgq_get:从消息队列提取一个数据,可以指定timeout参数,当timeout参数为0时,就是try get操作。
  • os_msgq_put:往消息队列存放一个数据,可以指定timeout参数,当timeout参数为0时,就是try put操作;消息队列可能已满,此时try put操作不会阻塞当前task。
  • os_msgq_del:删除消息队列
  • os_msgq_cnt:获取消息队列当前的消息个数

8.5. OS Timer

OS Timer模块是RTOS软件timer的抽象封装层,代码在sdk/osal/xxx/timer.c,头文件在sdk/include/osal/timer.h。OS Timer抽象层定义了struct os_timer结构体,所有的API都是基于此类型进行操作。

RTOS的软件timer是基于OS TICK功能实现的,因此OS Timer的时间精度取决于OS TICK的周期。例如 当OS HZ为100(即OS TICK为10ms),OS Timer的时间精度为10ms。

同时大多数RTOS的软件timer实现会使用一个task对所有的timer进行管理,因此在timer的timeout回调函数不能执行过多的程序逻辑,也不能有阻塞等待的行为,此类行为会影响其他timer的触发执行,造成不必要的timer delay。

OS Timer抽象层API列表如下:

  • os_timer_init: 软件timer初始化,可以设置timer的触发模式:单次触发或循环触发。
  • os_timer_start: 启动软件timer,需要指定timer周期时间,单位为毫秒。
  • os_timer_stop:停止timer
  • os_timer_del:删除timer
  • os_timer_stat:查看timer当前是否处于活动状态。返回值1:活动状态,返回值0:非活动状态。

8.6. OS Event

OS Event是RTOS事件功能的抽象封装层,代码在sdk/osal/xxx/event.c,头文件在sdk/include/osal/event.h。OS Event抽象层定义了struct os_event结构体,所有API都是基于此类型进行操作。

RTOS事件功能是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输。与信号量不同的是,它可以实现一对多,多对多的任务间同步。即一个任务可以等待多个事件的发生:可以是任意一个事件发生时唤醒任务;也可以是几个事件都发生后才唤醒任务。同样也可以是多个任务同步多个事件。

OS Event抽象层API列表如下:

  • os_event_init:OS事件对象初始化
  • os_event_del:删除事件对象
  • os_event_set:设置事件标识,唤醒等待此事件的任务。
  • os_event_clear:清除事件标识
  • os_event_get:获取事件对象已被设置的标识
  • os_event_wait:等待事件标识,可以设置等待模式,参考 enum OS_EVENT_WMODE。

8.7. OS Condv

OS Condv模块是RTOS条件变量功能的抽象封装层,代码在sdk/osal/xxx/condv.c,头文件在sdk/include/osal/condv.h。OS Condv模块定义了struct os_condv结构体,所有API都是基于此类型进行操作。

OS Condv抽象层API列表如下:

  • os_condv_init:条件变量初始化
  • os_condv_broadcast:唤醒所有等待的任务
  • os_condv_signal:至少唤醒1一个等待的任务
  • os_condv_del:删除条件变量
  • os_condv_wait:等待条件变量,可以设置等待超时时间,单位为毫秒。

8.8. OS WorkQueue/Work

OS Workqueue是SDK基于RTOS Task功能设计的一种工作队列机制。工作队列的设计初衷是堆栈复用,节省memory资源。头文件是osal/include/osal/work.h。

工作队列需要结合OS Work进行使用,将多个OS Work共用一个工作队列才能达到堆栈复用,节省memory资源的目的。OS Workqueue会使用一个Task对多个OS Work进行调度执行。各个OS Work在同一个任务堆栈中执行,OS Work完成自己的工作后立即退出,Workqueue会继续调度执行其他Work。因此在OS Work的代码逻辑中不能有阻塞等待的行为。

OS Work是将一些需要反复执行,但是优先级不高,实时性要求也不高的代码逻辑封装起来,作为一个Work在工作队列执行,不需要为此类型的代码逻辑创建单独的Task。

OS Workqueue具有延迟执行的功能,可以方便实现一些对延时精度要求不高的定时执行功能,可以节省软件timer的资源。

OS Workqueue可以执行OS Work,也可以执行指定的某个函数:

  • 执行OS Work:需要多次执行的代码逻辑,需要创建1个struct os_work变量。
  • 执行指定函数:仅需要执行一次的代码逻辑,省去创建struct os_work变量。

SDK在系统初始化时会创建一个主工作队列,无特别需求的work都可以放在主工作队列中执行。

main.c文件中的main_wk就是一个work示例,该work在主工作队列中执行,一秒执行一次。注意:在sys_main_loop函数添加代码,不能有延时行为。

OS Workqueue模块API列表如下:

  • os_workqueue_init:初始化工作队列
  • os_work_schedule:将os work加入指定的工作队列进行调度执行
  • os_work_schedule_delay:将os work加入指定的工作队列,延迟执行
  • os_work_cancle:取消os work的执行
  • os_run_func:在主工作队列中执行指定的函数
  • os_run_func_delay:在主工作队列中延迟执行指定的函数
  • os_run_work:在主工作队列执行os work
  • os_run_work_delay:在主工作队列延迟执行os work

main.c里面的 sys_main_loop 就是一个延迟循环执行的os work示例。

宏定义 OS_WORK_INIT 用于对os work进行初始化。

9. SDK事件消息

SDK设计了事件消息模块,采取订阅-发布的交互机制。各个软件模块在运行过程中可以产生并发布相应的事件,事件发布后系统会通知订阅此事件的模块,通知其采取相应的处理。

事件的发布者和订阅者彼此之间没有直接联系。发布者不需要关心是谁需要处理此事件,而订阅者不需要关心是谁产生了此事件,只需要关注如何处理此事件。发布一个事件可以被多个订阅者接收处理。

9.1. 事件类型

SDK对事件消息进行了统一的管理,对事件进行了分类编号。每个事件都有一个唯一的32位ID编号。这个32位的事件ID包含了16位主ID和16位子ID。

宏定义SYS_EVENT用来定义事件ID:

#define SYS_EVENT(main, sub) ((main)<<16|(sub&0xffff))

在sdk/include/lib/common/sysevnt.h文件中可以看到SDK已经支持的事件类型和ID。枚举SYSEVT_MAINID 定义了事件主ID,表示某一类事件;然后每一类事件再定义自己的事件子ID,例如:

enum SYSEVT_MAINID { /* uint16 */

SYS_EVENT_NETWORK = 1, //mainID: for network events

SYS_EVENT_WIFI, //mainID: for wifi module

SYS_EVENT_LMAC, //mainID: for lmac module

SYS_EVENT_SYSTEM, //mainID: for system

SYS_EVENT_BLE, //mainID: for BLE

};

enum SYSEVT_SYSTEM_SUBEVT { /* uint16 */ //subID for system events.

SYSEVT_SYSTEM_RESUME = 1, //subID: system resumed

SYSEVT_SYSTEM_SD_MOUNT, //subID: SD card mounted

SYSEVT_SYSTEM_SD_UNMOUNT, //subID: SD card unmount

};

9.2. 发布事件

使用 sys_event_new API发布一个事件,任意模块的代码都可以发布自己的事件。发布事件时可以携带一个event data,类型是uint32,含义自定义。只有在处理事件的代码才需要知道event data的含义。

发布事件的API

int32 sys_event_new(uint32 event_id, uint32 data);

参数:

  • event_id: 事件ID(mainID+subID)
  • data: 该事件消息的data,可以携带一个uint32自定义数据。

为了方便进行事件发布操作,每一类事件都定义了一个自己的宏定义,例如:

#define SYSEVT_NEW_SYSTEM_EVT(subevt, data) // 发布system 事件消息

#define SYSEVT_NEW_LMAC_EVT(subevt, data) // 发布LMAC类型的事件消息

#define SYSEVT_NEW_WIFI_EVT(subevt, data) // 发布WiFi类型的事件消息

#define SYSEVT_NEW_NETWORK_EVT(subevt, data) // 发布network类型的事件消息

#define SYSEVT_NEW_BLE_EVT(subevt, data) //发布BLE类型的事件消息

开发者可以继续添加事件定义,为自己的代码添加mainID和subID。

9.3. 订阅事件

事件消息被发布后,由SDK进行统一管理。SDK会查询此事件的订阅信息,执行订阅者的回调函数。

订阅事件的API

int32 sys_event_take(uint32 evt_id, sysevt_hdl hdl, uint32 priv)

参数:

  • evt_id :订阅的事件ID。evt_id的值可以是0xffffffff,主ID(子ID为0),或者具体事件ID(主ID+子ID)
    • 0xffffffff:表示订阅所有的事件
    • 主ID:表示订阅某一类事件,例如:SYS_EVENT(SYS_EVENT_WIFI,0),表示所有的WiFi事件(子ID填0)。
    • 事件ID:只订阅某个具体的事件。例如:SYS_EVENT(SYS_EVENT_WIFI,SYSEVT_WIFI_CONNECTTED),表示只订阅WiFi连接事件。
  • hdl :订阅事件的回调函数。该回调函数的返回值定义:
    • SYSEVT_CONTINUE:该事件可以继续传递给其他订阅者;
    • SYSEVT_CONSUMED:该事件已被回调函数消耗,不能继续传递给其他订阅者。
  • priv :自定义私有数据,在执行回调函数时priv参数会传递给回调函数。

事件回调函数定义

typedef sysevt_hdl_res(*sysevt_hdl)(uint32 event_id, uint32 data, uint32 priv);

回调函数的参数:

  • event_id:发布的事件ID
  • data:该事件的数据(每个事件的数据含义会不一样)
  • priv:订阅者的私有数据(也就是sys_event_take函数的第3个参数)

10. SDK系统时间

SDK可以提供2种时间:机器时间(单调递增) 和 NTP时间。时间相关API的声明在sdk/include/osal/time.h,代码实现在sdk/osal/csky/time.c, sdk/osal/ohos/time.c。

  • 机器时间:记录从系统开机后经过的秒数,毫秒数,微秒数,TICK数。单调递增,不受其他功能影响。
  • uint64 os_jiffies(void); //系统开机后经过的 tick数
  • uint32 os_seconds(void); //系统开机后经过的 秒数
  • uint64 os_mseconds(void); //系统开机后经过的 毫秒数
  • uint64 os_useconds(void); //系统开机后经过的 微秒数
  • int clock_gettime(uint32 clk_id, struct timespec *tp); //以timespec 格式返回系统开机后经过的秒数和纳秒数
  • NTP时间:SDK内置了sntp模块可以进行NTP时间校准,也可以使用其他NTP模块进行校准。NTP时间相关的API和C库定义一样,按照标准用法使用即可:
    • gettimeofday,settimeofday,time

11. SDK POSIX接口

为了支持移植第三方代码,SDK实现了部分POSIX接口。代码在sdk/lib/posix目录,头文件在sdk/include/lib/posix目录。实现的功能包括pthread API,socket API和file system API。

使用POSIX接口的代码需要添加头文件: sdk/include/lib/posix/stdio.h 和sdk/include/lib/posix/pthread.h.

11.1. FS接口

在sdk/lib/posix/stdio.c定义常用的fs api,按常规使用方法使用即可。

11.2. Socket接口

为了兼容移植的第三方代码使用read/write API访问socket。在sdk/lib/posix/stdio.c中实现了read/write函数,该函数根据fd的大小识别是执行socket API,还是FS API。其他的socket API请使用lwip的socket API。

11.3. Pthread接口

SDK实现了pthread API,包括 pthread,pthread互斥锁,pthread条件变量,pthread读写锁,pthread消息队列。参考POSIX pthread 接口规范使用即可。

12. SDK参数管理

SDK的系统参数管理默认是双备份,在Flash划分了2个参数区。参数保存和加载时,SDK自动处理参数区的切换。如果在参数保存过程中意外断电或复位,系统开机后会识别到损坏的参数区,加载上一次保存的参数区。

12.1. API列表

SDK参数管理模块的API在sdk/lib/syscfg/syscfg.h。

enum SYSCFG_ERASE_MODE 定义了参数保存时flash的擦除模式

enum SYSCFG_ERASE_MODE{

SYSCFG_ERASE_MODE_SECTOR, //按sector擦除

SYSCFG_ERASE_MODE_BLOCK, //按block擦除

SYSCFG_ERASE_MODE_CHIP, //整个flash擦除(谨慎!危险!)

};

struct syscfg_info定义了参数区的分区信息,指定每个参数分区所在的flash和地址,参数区的大小,以及擦除模式。

struct syscfg_info {

struct spi_nor_flash *flash1, *flash2; //参数区1,参数区2所在的flash

uint32 addr1, addr2; //参数区1,参数区2 的地址

uint32 size; //参数区大小

uint8 erase_mode; //擦除模式

};

API 列表如下:

  • int32 syscfg_init(const char *name, void *cfg, uint32 size);

参数管理功能初始化,仅在系统初始化执行一次,同时会从flash加载参数。

参数说明:

    • name:参数区名称,根据名称确定参数区的存储位置。
    • cfg:加载参数的变量地址
    • size:加载参数的大小
  • int32 syscfg_read(const char *name, void *cfg, uint32 size);

从flash读取参数。

参数说明:

    • name:参数区名称,根据名称确定参数区的存储位置。
    • cfg:加载参数的变量地址
    • size:加载参数的大小
  • int32 syscfg_write(const char *name, void *cfg, uint32 size);

保存参数到flash。

参数说明:

    • name:参数区名称,根据名称确定参数区的存储位置。
    • cfg:参数变量地址
    • size:参数大小
  • int32 syscfg_info_get(struct syscfg_info *pinfo, const char *name);

获取参数区分区信息,该函数在device.c中实现。

参数说明:

  • pinfo:用于填充参数区的存放位置信息
  • name:参数区的名称,根据名称确定参数区的存储位置。在实现syscfg_info_get时根据name识别各个参数功能,分配对应的存储位置。
  • 系统参数区名称为”syscfg”,旧版本SDK API没有这个参数,只支持一个系统参数。
  • void syscfg_loaddef(const char *name);

参数区恢复出厂默认设置。

12.2. 参数分区

SDK的参数支持双备份存放,请根据实际方案需要指定各个参数区的存放位置。SDK默认将系统参数存放在flash末尾的2个sector位置,参数区大小为4K byte。

device.c中的 syscfg_info_get 函数指定了参数区的存放信息,如下图所示:

12.3. 参数扩展

struct sys_config结构体是SDK的系统参数区定义。开发者可以将自己的参数添加在系统参数中,但是只能在该结构体末尾新增字段。当扩展后的sizeof(struct sys_config)超过参数区大小时(默认4K),就需要调整参数区的划分(syscfg_info_get函数)。

12.4. 自定义参数区

SDK默认只有1个参数区:syscfg,如果有某些功能的参数不希望和系统参数一起存放,则可以自定义参数区。自定义参数区时需要规划参数区的存放位置,定义参数区的名称,在syscfg_info_get API中根据name识别需要访问的参数区,返回对应的存储位置信息。在使用 syscfg_read/syscfg_write API时传入自定义参数区的名称即可。

13. SDK C++代码支持

SDK支持添加C++代码。添加C++代码后在系统初始化时需要在main函数中执行do_global_ctors函数,该函数是对C++的全局变量进行初始化。

同时需要检查ld文件是否已经添加.ctors和.dtors的处理。

例如:

. = ALIGN(0x4) ;

__CTOR_LIST__ = . ;

LONG((__CTOR_END__ - __CTOR_LIST__) / 4 - 2)

KEEP(*(SORT(.ctors)))

LONG(0)

__CTOR_END__ = . ;

__DTOR_LIST__ = . ;

LONG((__DTOR_END__ - __DTOR_LIST__) / 4 - 2)

KEEP(*(SORT(.dtors)))

LONG(0)

__DTOR_END__ = . ;

. = ALIGN(0x4) ;

14. SDK内置小模块

SDK内置一些小模块,实现了一些常用的功能函数。开发代码时可以直接使用这些小模块,节省一点开发工作。

14.1. ringbuffer

ringbuffer模块实现了循环buffer的功能,用于单reader,单writer的场景,可以免锁访问,减少系统开销。

代码在sdk/lib/common/rbuffer.c,头文件在sdk/include/common/rbuffer.h。

根据ringbuffer存放数据元素的长度特征,有2种使用方式:

  • 数据大小固定:在定义ringbuffer时可以指定数据元素类型,可以是基础类型,也可以是自定义结构体类型。使用RB_GET/RB_SET 可以一次操作一个元素。
  • 数据大小变长:如果存放的数据大小是可变的,定义ringbuffer时只能指定char类型。使用rbuffer_set/rbuffer_get API对数据进行操作。

ringbuffer设计了2种API

  • 宏定义API:使用宏定义API,可以将ringbuffer内嵌到其他模块的定义中,适用于数据元素大小固定的场景。
    • #define RBUFFER_DEF(name, type, size) //定义ringbuffer,指定元素类型和buffer大小(元素的个数),以数组形式自动分配buffer空间。需要配合RB_INIT宏定义进行初始化。
    • #define RBUFFER_DEF_R(name, type)//定义ringbuffer,仅指定元素类型,buffer空间由外部分配, 需要配合RB_INIT_R宏定义进行初始化。
    • #define RB_GET(rb, val)//从ringbuffer获取一个元素
    • #define RB_SET(rb, val)//向ringbuffer存入一个元素
    • #define RB_INIT(rb, size)//ringbuffer初始化,配合RBUFFER_DEF使用
    • #define RB_INIT_R(rb, size, buff)//ringbuffer初始化,需要指定buffer空间(由外部分配和释放),RBUFFER_DEF_R使用。
    • #define RB_INT_GET(rb, val)//关中断后从ringbuffer获取一个元素
    • #define RB_INT_SET(rb, val)//关中断后向ringbuffer存入一个元素

RB_INT_GET和RB_INT_SET 这2个API建议在符合以下条件的情况下使用:

  1. ringbuffer的存储数据是基础类型,例如 uint8, uint32,指针,...
  2. ringbuffer存在多个reader或多个writer同时访问的情况,或者需要中断上下文执行get/set操作。 通过关中断进行get/set操作,既保证互斥操作,又减少了上锁的开销。
  • 函数API:使用函数API对ringbuffer进行访问,适用于数据元素大小可变的场景。使用struct rbuffer对ringbuffer进行定义。
    • int32 rbuffer_init(struct rbuffer *rb, uint32 size, void *buff);

ringbuffer初始化,指定buffer大小和buffer空间地址。

    • int32 rbuffer_set(struct rbuffer *rb, void *data, uint32 length);

向ringbuffer写入数据。

    • int32 rbuffer_set_force(struct rbuffer *rb, void *data, uint32 length);

向ringbuffer写入数据,当ringbuffer已满时,自动覆盖旧的数据。

    • int32 rbuffer_get(struct rbuffer *rb, void *buff, uint32 size);

从ringbuffer中读取数据。

    • void rbuffer_destroy(struct rbuffer *rb);

销毁ringbuffer。

    • void rbuffer_reset(struct rbuffer *rb);

ringbuffer复位,所保存的数据丢失。

14.2. 字符串转换

SDK内置了一些字符串处理函数,代码在sdk/lib/common/string.c。

  • hexchr2int : hex字符 转换成 int数字,例如字符‘A’转换成数字0x0A。
  • hex2char:hex字符串 转换成char数字,例如字符串“0xAA”转换成数字0xAA。
  • hex2bin:hex字符串 转换成char数组,例如字符串“1122334455”转换成数组{0x11,0x22,0x33,0x44,0x55}。
  • str2mac:mac地址字符串 转换成 mac地址
  • str2ip:ip地址字符串 转换成 IP地址
  • os_atoh:hex字符串 转换成 32位数字
  • os_atohl:hex字符串 转换成 64位数字
  • os_strdup:字符串拷贝函数

14.3. print输出

SDK重新设计了print输出函数,支持输出重定向和打印级别,以及console颜色控制。代码在sdk/lib/common/string.c。

  • 打印输出缓冲区

打印缓冲区大小默认是256byte,由宏定义 PRINT_BUFF_SIZE 控制,可以在project_config.h中重定义PRINT_BUFF_SIZE来修改缓冲区大小。

  • hgprintf

SDK实现的打印输出函数,库函数printf也会被alias 重定向到hgprintf函数。

  • os_printf

带时间戳的打印输出,该宏定义调用了hgprintf,添加了时间戳控制信息。

  • _os_printf

不带时间戳的打印输出,等效于hgprintf。

  • 打印输出等级

SDK支持打印等级控制和console颜色控制。默认的打印等级是0,允许所有的打印信息输出。print_level 函数可以修改打印等级,大于设定的打印等级的信息将会被过滤不输出。SDK定义了8种打印等级,不同等级的console输出颜色也会不同。

#define KERN_EMERG KERN_SOH"0" /* system is unusable */

#define KERN_ALERT KERN_SOH"1" /* action must be taken immediately */

#define KERN_CRIT KERN_SOH"2" /* critical conditions */

#define KERN_ERR KERN_SOH"3" /* error conditions */

#define KERN_WARNING KERN_SOH"4" /* warning conditions */

#define KERN_NOTICE KERN_SOH"5" /* normal but significant condition */

#define KERN_INFO KERN_SOH"6" /* informational */

#define KERN_DEBUG KERN_SOH"7" /* debug-level messages */

打印等级使用方法如下:

os_printf(KERN_EMERG"this is KERN_EMERG log");

os_printf(KERN_ALERT"this is KERN_ALERT log");

os_printf(KERN_CRIT"this is KERN_CRIT log");

os_printf(KERN_ERR"this is KERN_ERR log");

os_printf(KERN_WARNING"this is KERN_WARNING log");

os_printf(KERN_NOTICE"this is KERN_NOTICE log");

os_printf(KERN_INFO"this is KERN_INFO log");

os_printf(KERN_DEBUG"this is KERN_DEBUG log");

  • 打印输出重定向

SDK支持对打印信息输出进行重定向。利用此特性可以将调试信息输出到主控、网口或其他任意输出界面。可实现类似Telnet,SSH的操作界面。

使用print_redirect API进行打印重定向。该API执行后,系统的所有打印信息都将输出到指定的输出函数。

void print_redirect(osprint_hook print, void *priv)

  • dump_hex

将buffer中的数据以hex字符格式打印输出。

  • dump_key

将buffer中的数据以hex密码格式输出。

  • os_memdup

内存数据拷贝函数。

  • os_random_bytes

伪随机数产生函数

  • void print_level(int8 level)

设置SDK打印输出级别,低于设置的level的日志才会被输出。

  • void disable_print(int8_t dis)

关闭SDK打印输出

  • void disable_print_clore(int8_t dis)

关闭SDK打印输出的颜色控制

14.4. errlog功能

在产品测试、运行过程中有可能会出现异常情况,但是又不方便抓取异常日志。为了能抓取异常日志进行有效分析,SDK设计了errlog模块。

errlog模块的功能是将低于指定level的日志信息保存到flash中。当设备出现异常时该模块会将出现异常时的日志信息保存下来,可以在设备重启后再查看异常信息。

errlog API列表:

void sys_errlog_init(int8 level, uint16 buf_size);

errlog功能默认是关闭状态,需要使用sys_errlog_init API打开。

  • 参数level:需要保存的日志级别,小于等于level的日志信息会被保存下来
  • 参数buf_size:errlog功能的buffer size。errlog功能需要使用2个buffer减小日志缓存,避免频繁擦写flash。 buffer size通常设置为flash的sector size或者 sector size/2 。

void sys_errlog_flush(uint32 p1, uint32 p2, uint32 p3);

sys_errlog_flush API用于通知errlog模块将缓存的异常日志刷写到flash。默认情况下errlog模块在缓存的日志将填满buffer时自动写入flash,但是程序可以在检测异常时主动要求errlog刷写日志。

  • 参数 p1 :0xffffffff:表示强行刷写日志。
  • 参数 p2 :未使用
  • 参数 p3 :未使用

void sys_errlog_dump(void);

sys_errlog_dump API用于打印errlog模块保存的日志信息。该API会将flash里面保存的日志信息全部打印出来。

使用errlog功能还需要指定errlog 在flash中的存放位置,使用errlog_flashinfo 函数指定errlog的存储区域,如下图所示:

errlog_flashinfo函数在device.c文件中,如果该文件没有这个函数,可以自行添加。根据设备的flash分区情况,为errlog指定一块区间保存errlog信息。

指定的地址和大小,应该符合flash的擦写特性,例如addr是sector size的整数倍位置,size也是sector size的整数倍。errlog模块会根据size的大小计算有几个sector区间,循环擦写这一片区域。

15. SDK固件生成

15.1. 固件生成过程

15.2. 固件烧录

16. SDK调试辅助

16.1. 串口固件升级

SDK默认支持串口Xmodem协议升级固件,该功能通常是在开发调试阶段使用。

在串口输入at+fwupg指令,芯片进入OTA模式。串口连续输出CCCCCCC, 表示芯片已进入升级模式。然后通过串口工具使用Xmodem协议发送固件文件。

注:secureCRT软件支持Xmodem协议,如图所示:

16.2. CPU使用率

SDK支持打印输出CPU使用率信息。cpu_loading_print()函数可以打印CPU使用率,该打印信息显示了各个Task的CPU使用情况。从该信息可以分析代码运行是否存在异常。

SDK默认支持“at+sysdbg=top,1”命令,该命令可以开启周期性打印CPU使用率信息。默认的打印周期为5秒。

如果因为系统Task异常造成周期性打印无法正常输出时,可以执行at+top指令查看CPU使用率,该指令执行一次仅打印一次。at+top指令是uart中断中执行,只要系统未死机,该指令就可以正常输出。

中断统计

在分析CPU使用率时,也可以把中断执行时间打印出来。在project_config.h文件添加宏定义 SYS_IRQ_STAT,可以打开SDK的中断统计功能,统计各个中断的触发次数,单次最大执行时间和统计周期内总的执行时间。用于分析中断服务程序是否存在异常。打开此功能后,在周期性打印CPU使用率时也会同时打印中断的统计信息。

16.3. 内存分配信息

SDK支持打印输出SDK内存分配情况,可以查看heap总大小,剩余大小。支持内存分配追踪,有助于分析内存使用情况,以及内存泄漏问题。

SDK默认支持“at+sysdbg=heap,1”命令,执行该命令可以开启周期性打印内存分配情况,打印周期默认为5秒。

在system.c 里面的 malloc_init 函数,可以设置sysheap_init函数的flags参数。

上述图片展示了内存剩余大小(共2个内存片段)和总大小。

使用SDK定义的 os_memcpy/os_strcpy等内存copy函数,可以借助上述的内存越界检查功能,进行内存越界定位,代码实现在sdk/lib/common/string.c。

自定义heap的调试,可以在自定义heap的init函数指定flags参数,然后使用自定义heap的status函数,打印heap的分配情况。

16.4. Task状态分析

如果系统运行过程中出现Task卡死/阻塞的情况,可以借助SDK的top指令,导出Task堆栈信息进行分析。at+top 指令可以打印当前系统Task的状态信息,如下所示:

上图中优先级后面括号里的值是Task句柄

执行 at+dumptask 指令可以打印Task的堆栈信息。该指令需要指定Task句柄,在top指令输出信息里面可以找到,如上图箭头所示。

1721706494992

导出Task的堆栈信息后,可以结合asm文件进行分析,追溯还原Task的callstack信息。

在sdk/tools目录下提供了alios_stack.exe工具,可以解析还原部分芯片的Task堆栈数据(目前只支持CK803/CK802/E804 CPU的芯片)。使用方法:

  • 将at+dumptask命令的输出信息保存为 xx_task.txt, 保存到sdk/tools目录下。
  • 执行 .\alios_stack.exe ck803 ..\project\Lst\xxxx.asm xx_task.txt

该工具正确解析后,会打印出该Task的callstack信息,借助此信息进一步分析Task的异常情况。